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