      1 /** @module utility-functions */
      2 define(['underscore'], function(_) {
      3   /**
      4    *
      5    * Sorting function that deals with alpha and numeric elements.
      6    *
      7    * @param {String[]} list A list of strings to sort
      8    *
      9    * @return {String[]} The sorted list of strings
     10    * @function naturalSort
     11    */
     12   function naturalSort(list) {
     13     var numericPart = [], alphaPart = [], result = [];
     14
     15     // separate the numeric and the alpha elements of the array
     16     // Note that NaN and +/- Infinity are not considered numeric elements
     17     for (var index = 0; index < list.length; index++) {
     18       var valAsFloat = parseFloat(list[index]);
     19       if (isNaN(valAsFloat) || !isFinite(valAsFloat)) {
     20         alphaPart.push(list[index]);
     21       }
     22       else {
     23         numericPart.push(list[index]);
     24       }
     25     }
     26
     27     // ignore casing of the strings, taken from:
     28     // http://stackoverflow.com/a/9645447/379593
     29     alphaPart.sort(function(a, b) {
     30       return a.toLowerCase().localeCompare(b.toLowerCase());
     31     });
     32
     33     // sort in ascending order
     34     numericPart.sort(function(a, b) {return parseFloat(a) - parseFloat(b)});
     35
     36       return result.concat(alphaPart, numericPart);
     37   }
     38
     39
     40   /**
     41    *
     42    * Utility function that splits the lineage into taxonomic levels
     43    * and returns the taxonomic level specified
     44    *
     45    * @param {String} lineage The taxonomic string, with levels seperated by
     46    * semicolons.
     47    * @param {Integer} levelIndex The taxonomic level to truncate to.
     48    * 1 = Kingdom, 2 = Phylum, etc.
     49    *
     50    * @return {String} The taxonomic string truncated to desired level.
     51    * @function truncateLevel
     52    */
     53   function truncateLevel(lineage, levelIndex) {
     54     if (levelIndex === 0) {
     55       return lineage;
     56     }
     57     var levels = lineage.split(';');
     58     var taxaLabel = '';
     59     for (var i = 0; (i < levelIndex && i < levels.length); i++) {
     60       var level = levels[i];
     61       if (level[level.length - 1] == '_') {
     62         taxaLabel += ';' + level;
     63       }else {
     64         taxaLabel = level;
     65       }
     66     }
     67     return taxaLabel;
     68   }
     69
     70   /**
     71    *
     72    * Utility function to convert an XML DOM documents to a string useful for
     73    * unit testing. This code is based on
     74    * [this SO answer]{@link http://stackoverflow.com/a/1750890}
     75    *
     76    * @param {Node} node XML DOM object, usually as created by the document
     77    * object.
     78    *
     79    * @return {String} Representation of the node object.
     80    * @function convertXMLToString
     81    */
     82   function convertXMLToString(node) {
     83     if (typeof(XMLSerializer) !== 'undefined') {
     84       var serializer = new XMLSerializer();
     85       return serializer.serializeToString(node);
     86     }
     87     else if (node.xml) {
     88       return node.xml;
     89     }
     90   }
     91
     92   /**
     93    *
     94    * Split list of string values into numeric and non-numeric values
     95    *
     96    * @param {String[]} values The values to check
     97    * @return {Object} Object with two keys, `numeric` and `nonNumeric`.
     98    * `numeric` holds an array of all numeric values found. `nonNumeric` holds
     99    * an array of the remaining values.
    100    */
    101    function splitNumericValues(values) {
    102     var numeric = [];
    103     var nonNumeric = [];
    104     _.each(values, function(element) {
    105         // http://stackoverflow.com/a/9716488
    106         if (!isNaN(parseFloat(element)) && isFinite(element)) {
    107           numeric.push(element);
    108         }
    109         else {
    110           nonNumeric.push(element);
    111         }
    112       });
    113     return {numeric: numeric, nonNumeric: nonNumeric};
    114    }
    115
    116   /**
    117    *
    118    * Escape special characters in a string for use in a regular expression.
    119    * Credits go to [this SO answer]{@link http://stackoverflow.com/a/5306111}
    120    *
    121    * @param {String} regex string to escape for use in a regular expression.
    122    *
    123    * @return {String} String with escaped characters for use in a regular
    124    * expression.
    125    * @function escapeRegularExpression
    126    */
    127   function escapeRegularExpression(regex) {
    128     return regex.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
    129   }
    130
    131   /**
    132    *
    133    * Clean a string in HTML formatted strings that get created with the
    134    * namespace tag in some browsers and not in others. Intended to facilitate
    135    * testing.
    136    *
    137    * @param {String} htmlString string to remove namespace from.
    138    *
    139    * @return {String} String without namespace.
    140    * @function cleanHTML
    141    */
    142   function cleanHTML(htmlString) {
    143     return htmlString.replace(' xmlns="http://www.w3.org/1999/xhtml"', '');
    144   }
    145
    146   return {'truncateLevel': truncateLevel, 'naturalSort': naturalSort,
    147           'convertXMLToString': convertXMLToString,
    148           'escapeRegularExpression': escapeRegularExpression,
    149           'cleanHTML': cleanHTML, 'splitNumericValues': splitNumericValues};
    150 });
    151