summaryrefslogtreecommitdiffstats
path: root/devtools/shared/natural-sort.js
blob: 548c39541c1f9894f491127c02b4951fd6063c7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Natural Sort algorithm for Javascript - Version 0.8.1 - Released under MIT license
 * Author: Jim Palmer (based on chunking idea from Dave Koelle)
 *
 * Includes pull request to move regexes out of main function for performance
 * increases.
 *
 * Repository:
 *   https://github.com/overset/javascript-natural-sort/
 */

"use strict";

var re = /(^([+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
var sre = /^\s+|\s+$/g; // trim pre-post whitespace
var snre = /\s+/g; // normalize all whitespace to single ' ' character

// eslint-disable-next-line
var dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
var hre = /^0x[0-9a-f]+$/i;
var ore = /^0/;
var b0re = /^\0/;
var e0re = /\0$/;

exports.naturalSortCaseSensitive = function naturalSortCaseSensitive(a, b) {
  return naturalSort(a, b, false);
};

exports.naturalSortCaseInsensitive = function naturalSortCaseInsensitive(a, b) {
  return naturalSort(a, b, true);
};

/**
 * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
 * "the way humans do."
 *
 * This function should only be called via naturalSortCaseSensitive and
 * naturalSortCaseInsensitive.
 *
 * e.g. [3, 2, 1, 10].sort(naturalSort)
 *
 * @param  {Object} a
 *         Passed in by Array.sort(a, b)
 * @param  {Object} b
 *         Passed in by Array.sort(a, b)
 * @param  {Boolean} insensitive
 *         Should the search be case insensitive?
 */
// eslint-disable-next-line complexity
function naturalSort(a, b, insensitive) {
  // convert all to strings strip whitespace
  const i = function(s) {
    return ((insensitive && ("" + s).toLowerCase()) || "" + s).replace(sre, "");
  };
  const x = i(a) || "";
  const y = i(b) || "";
  // chunk/tokenize
  const xN = x
    .replace(re, "\0$1\0")
    .replace(e0re, "")
    .replace(b0re, "")
    .split("\0");
  const yN = y
    .replace(re, "\0$1\0")
    .replace(e0re, "")
    .replace(b0re, "")
    .split("\0");
  // numeric, hex or date detection
  const xD = parseInt(x.match(hre), 16) || (xN.length !== 1 && Date.parse(x));
  const yD =
    parseInt(y.match(hre), 16) || (xD && y.match(dre) && Date.parse(y)) || null;
  const normChunk = function(s, l) {
    // normalize spaces; find floats not starting with '0', string or 0 if
    // not defined (Clint Priest)
    return (
      ((!s.match(ore) || l == 1) && parseFloat(s)) ||
      s.replace(snre, " ").replace(sre, "") ||
      0
    );
  };
  let oFxNcL;
  let oFyNcL;

  // first try and sort Hex codes or Dates
  if (yD) {
    if (xD < yD) {
      return -1;
    } else if (xD > yD) {
      return 1;
    }
  }

  // natural sorting through split numeric strings and default strings
  // eslint-disable-next-line
  for (let cLoc = 0, xNl = xN.length, yNl = yN.length, numS = Math.max(xNl, yNl); cLoc < numS; cLoc++) {
    oFxNcL = normChunk(xN[cLoc] || "", xNl);
    oFyNcL = normChunk(yN[cLoc] || "", yNl);

    // handle numeric vs string comparison - number < string - (Kyle Adams)
    if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
      return isNaN(oFxNcL) ? 1 : -1;
    }
    // if unicode use locale comparison
    // eslint-disable-next-line
    if (/[^\x00-\x80]/.test(oFxNcL + oFyNcL) && oFxNcL.localeCompare) {
      const comp = oFxNcL.localeCompare(oFyNcL);
      return comp / Math.abs(comp);
    }
    if (oFxNcL < oFyNcL) {
      return -1;
    } else if (oFxNcL > oFyNcL) {
      return 1;
    }
  }
  return null;
}