| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | const defaultAlphabetIndexMap = [];
|
|---|
| 4 |
|
|---|
| 5 | function isNumberCode(code) {
|
|---|
| 6 | return code >= 48/* '0' */ && code <= 57/* '9' */;
|
|---|
| 7 | }
|
|---|
| 8 |
|
|---|
| 9 | function naturalCompare(a, b, opts) {
|
|---|
| 10 | if (typeof a !== 'string') {
|
|---|
| 11 | throw new TypeError(`The first argument must be a string. Received type '${typeof a}'`);
|
|---|
| 12 | }
|
|---|
| 13 | if (typeof b !== 'string') {
|
|---|
| 14 | throw new TypeError(`The second argument must be a string. Received type '${typeof b}'`);
|
|---|
| 15 | }
|
|---|
| 16 |
|
|---|
| 17 | const lengthA = a.length;
|
|---|
| 18 | const lengthB = b.length;
|
|---|
| 19 | let indexA = 0;
|
|---|
| 20 | let indexB = 0;
|
|---|
| 21 | let alphabetIndexMap = defaultAlphabetIndexMap;
|
|---|
| 22 | let firstDifferenceInLeadingZeros = 0;
|
|---|
| 23 |
|
|---|
| 24 | if (opts) {
|
|---|
| 25 | if (opts.caseInsensitive) {
|
|---|
| 26 | a = a.toLowerCase();
|
|---|
| 27 | b = b.toLowerCase();
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | if (opts.alphabet) {
|
|---|
| 31 | alphabetIndexMap = buildAlphabetIndexMap(opts.alphabet);
|
|---|
| 32 | }
|
|---|
| 33 | }
|
|---|
| 34 |
|
|---|
| 35 | while (indexA < lengthA && indexB < lengthB) {
|
|---|
| 36 | let charCodeA = a.charCodeAt(indexA);
|
|---|
| 37 | let charCodeB = b.charCodeAt(indexB);
|
|---|
| 38 |
|
|---|
| 39 | if (isNumberCode(charCodeA)) {
|
|---|
| 40 | if (!isNumberCode(charCodeB)) {
|
|---|
| 41 | return charCodeA - charCodeB;
|
|---|
| 42 | }
|
|---|
| 43 |
|
|---|
| 44 | let numStartA = indexA;
|
|---|
| 45 | let numStartB = indexB;
|
|---|
| 46 |
|
|---|
| 47 | while (charCodeA === 48/* '0' */ && ++numStartA < lengthA) {
|
|---|
| 48 | charCodeA = a.charCodeAt(numStartA);
|
|---|
| 49 | }
|
|---|
| 50 | while (charCodeB === 48/* '0' */ && ++numStartB < lengthB) {
|
|---|
| 51 | charCodeB = b.charCodeAt(numStartB);
|
|---|
| 52 | }
|
|---|
| 53 |
|
|---|
| 54 | if (numStartA !== numStartB && firstDifferenceInLeadingZeros === 0) {
|
|---|
| 55 | firstDifferenceInLeadingZeros = numStartA - numStartB;
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | let numEndA = numStartA;
|
|---|
| 59 | let numEndB = numStartB;
|
|---|
| 60 |
|
|---|
| 61 | while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) {
|
|---|
| 62 | ++numEndA;
|
|---|
| 63 | }
|
|---|
| 64 | while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) {
|
|---|
| 65 | ++numEndB;
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | let difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length
|
|---|
| 69 | if (difference !== 0) {
|
|---|
| 70 | return difference;
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | while (numStartA < numEndA) {
|
|---|
| 74 | difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++);
|
|---|
| 75 | if (difference !== 0) {
|
|---|
| 76 | return difference;
|
|---|
| 77 | }
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | indexA = numEndA;
|
|---|
| 81 | indexB = numEndB;
|
|---|
| 82 | continue;
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | if (charCodeA !== charCodeB) {
|
|---|
| 86 | if (
|
|---|
| 87 | charCodeA < alphabetIndexMap.length &&
|
|---|
| 88 | charCodeB < alphabetIndexMap.length &&
|
|---|
| 89 | alphabetIndexMap[charCodeA] !== -1 &&
|
|---|
| 90 | alphabetIndexMap[charCodeB] !== -1
|
|---|
| 91 | ) {
|
|---|
| 92 | return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB];
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | return charCodeA - charCodeB;
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | ++indexA;
|
|---|
| 99 | ++indexB;
|
|---|
| 100 | }
|
|---|
| 101 |
|
|---|
| 102 | if (indexA < lengthA) { // `b` is a substring of `a`
|
|---|
| 103 | return 1;
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | if (indexB < lengthB) { // `a` is a substring of `b`
|
|---|
| 107 | return -1;
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 | return firstDifferenceInLeadingZeros;
|
|---|
| 111 | }
|
|---|
| 112 |
|
|---|
| 113 | const alphabetIndexMapCache = {};
|
|---|
| 114 |
|
|---|
| 115 | function buildAlphabetIndexMap(alphabet) {
|
|---|
| 116 | const existingMap = alphabetIndexMapCache[alphabet];
|
|---|
| 117 | if (existingMap !== undefined) {
|
|---|
| 118 | return existingMap;
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | const indexMap = [];
|
|---|
| 122 | const maxCharCode = alphabet.split('').reduce((maxCode, char) => {
|
|---|
| 123 | return Math.max(maxCode, char.charCodeAt(0));
|
|---|
| 124 | }, 0);
|
|---|
| 125 |
|
|---|
| 126 | for (let i = 0; i <= maxCharCode; i++) {
|
|---|
| 127 | indexMap.push(-1);
|
|---|
| 128 | }
|
|---|
| 129 |
|
|---|
| 130 | for (let i = 0; i < alphabet.length; i++) {
|
|---|
| 131 | indexMap[alphabet.charCodeAt(i)] = i;
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | alphabetIndexMapCache[alphabet] = indexMap;
|
|---|
| 135 |
|
|---|
| 136 | return indexMap;
|
|---|
| 137 | }
|
|---|
| 138 |
|
|---|
| 139 | module.exports = naturalCompare;
|
|---|