natsort.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /**
  2. * ESM Module: Natural Compare Function
  3. */
  4. function naturalCompareFactory(options) {
  5. function splitString(str) {
  6. return str
  7. .replace(tokenRegex, "\0$1\0")
  8. .replace(/\0$/, "")
  9. .replace(/^\0/, "")
  10. .split("\0");
  11. }
  12. function parseToken(token, length) {
  13. return (!token.match(leadingZeroRegex) || length === 1) && parseFloat(token) ||
  14. token.replace(whitespaceRegex, " ").replace(trimRegex, "") ||
  15. 0;
  16. }
  17. options = options || {};
  18. const leadingZeroRegex = /^0/,
  19. whitespaceRegex = /\s+/g,
  20. trimRegex = /^\s+|\s+$/g,
  21. unicodeRegex = /[^\x00-\x80]/,
  22. hexRegex = /^0x[0-9a-f]+$/i,
  23. tokenRegex = /(0x[\da-fA-F]+|(^[\+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|\d+)/g,
  24. dateRegex = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
  25. toLowerCase = String.prototype.toLocaleLowerCase || String.prototype.toLowerCase,
  26. ascending = options.desc ? -1 : 1,
  27. descending = -ascending,
  28. preprocess = options.insensitive
  29. ? (str) => toLowerCase.call("" + str).replace(trimRegex, "")
  30. : (str) => ("" + str).replace(trimRegex, "");
  31. return function compareStrings(a, b) {
  32. const strA = preprocess(a);
  33. const strB = preprocess(b);
  34. if (!strA && !strB) return 0;
  35. if (!strA && strB) return descending;
  36. if (strA && !strB) return ascending;
  37. const tokensA = splitString(strA);
  38. const tokensB = splitString(strB);
  39. const hexMatchA = strA.match(hexRegex);
  40. const hexMatchB = strB.match(hexRegex);
  41. const parsedDateA = hexMatchA && hexMatchB ? parseInt(hexMatchA[0], 16) : tokensA.length > 1 ? Date.parse(strA) : null;
  42. const parsedDateB = hexMatchA && hexMatchB ? parseInt(hexMatchB[0], 16) : parsedDateA && strB.match(dateRegex) ? Date.parse(strB) : null;
  43. if (parsedDateA && parsedDateB) {
  44. if (parsedDateA === parsedDateB) return 0;
  45. return parsedDateA < parsedDateB ? descending : ascending;
  46. }
  47. const maxTokens = Math.max(tokensA.length, tokensB.length);
  48. for (let i = 0; i < maxTokens; i++) {
  49. const tokenA = parseToken(tokensA[i] || "", tokensA.length);
  50. const tokenB = parseToken(tokensB[i] || "", tokensB.length);
  51. if (isNaN(tokenA) !== isNaN(tokenB)) return isNaN(tokenA) ? ascending : descending;
  52. if (unicodeRegex.test(tokenA + tokenB) && tokenA.localeCompare) {
  53. // const localeComparison = tokenA.localeCompare(tokenB);
  54. const localeComparison = tokenA.localeCompare(tokenB, 'zh-CN', {numeric: true, sensitivity: 'base'});
  55. if (localeComparison !== 0) return localeComparison * ascending;
  56. }
  57. if (tokenA < tokenB) return descending;
  58. if (tokenA > tokenB) return ascending;
  59. if ("" + tokenA < "" + tokenB) return descending;
  60. if ("" + tokenA > "" + tokenB) return ascending;
  61. }
  62. return 0;
  63. };
  64. }
  65. export default naturalCompareFactory;
  66. // 使用工厂函数创建比较器
  67. export const naturalCompare = naturalCompareFactory({desc: false, insensitive: true});