-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathparse.js
More file actions
126 lines (108 loc) · 3.27 KB
/
parse.js
File metadata and controls
126 lines (108 loc) · 3.27 KB
Edit and raw actions
OlderNewer
1
"use strict";
2
3
const fastDecode = require("fast-decode-uri-component");
4
5
const plusRegex = /\+/g;
6
const Empty = function () {};
7
Empty.prototype = Object.create(null);
8
9
/**
10
* @callback parse
11
* @param {string} input
12
*/
13
function parse(input) {
14
// Optimization: Use new Empty() instead of Object.create(null) for performance
15
// v8 has a better optimization for initializing functions compared to Object
16
const result = new Empty();
17
18
if (typeof input !== "string") {
19
return result;
20
}
21
22
const inputLength = input.length;
23
let key = "";
24
let value = "";
25
let startingIndex = -1;
26
let equalityIndex = -1;
27
let shouldDecodeKey = false;
28
let shouldDecodeValue = false;
29
let keyHasPlus = false;
30
let valueHasPlus = false;
31
let hasBothKeyValuePair = false;
32
let c = 0;
33
34
// Have a boundary of input.length + 1 to access last pair inside the loop.
35
for (let i = 0; i < inputLength + 1; i++) {
36
c = i !== inputLength ? input.charCodeAt(i) : 38;
37
38
// Handle '&' and end of line to pass the current values to result
39
if (c === 38) {
40
hasBothKeyValuePair = equalityIndex > startingIndex;
41
42
// Optimization: Reuse equality index to store the end of key
43
if (!hasBothKeyValuePair) {
44
equalityIndex = i;
45
}
46
47
key = input.slice(startingIndex + 1, equalityIndex);
48
49
// Add key/value pair only if the range size is greater than 1; a.k.a. contains at least "="
50
if (hasBothKeyValuePair || key.length > 0) {
51
// Optimization: Replace '+' with space
52
if (keyHasPlus) {
53
key = key.replace(plusRegex, " ");
54
}
55
56
// Optimization: Do not decode if it's not necessary.
57
if (shouldDecodeKey) {
58
key = fastDecode(key) || key;
59
}
60
61
if (hasBothKeyValuePair) {
62
value = input.slice(equalityIndex + 1, i);
63
64
if (valueHasPlus) {
65
value = value.replace(plusRegex, " ");
66
}
67
68
if (shouldDecodeValue) {
69
value = fastDecode(value) || value;
70
}
71
}
72
const currentValue = result[key];
73
74
if (currentValue === undefined) {
75
result[key] = value;
76
} else {
77
// Optimization: value.pop is faster than Array.isArray(value)
78
if (currentValue.pop) {
79
currentValue.push(value);
80
} else {
81
result[key] = [currentValue, value];
82
}
83
}
84
}
85
86
// Reset reading key value pairs
87
value = "";
88
startingIndex = i;
89
equalityIndex = i;
90
shouldDecodeKey = false;
91
shouldDecodeValue = false;
92
keyHasPlus = false;
93
valueHasPlus = false;
94
}
95
// Check '='
96
else if (c === 61) {
97
if (equalityIndex <= startingIndex) {
98
equalityIndex = i;
99
}
100
// If '=' character occurs again, we should decode the input.
101
else {
102
shouldDecodeValue = true;
103
}
104
}
105
// Check '+', and remember to replace it with empty space.
106
else if (c === 43) {
107
if (equalityIndex > startingIndex) {
108
valueHasPlus = true;
109
} else {
110
keyHasPlus = true;
111
}
112
}
113
// Check '%' character for encoding
114
else if (c === 37) {
115
if (equalityIndex > startingIndex) {
116
shouldDecodeValue = true;
117
} else {
118
shouldDecodeKey = true;
119
}
120
}
121
}
122
123
return result;
124
}
125
126
module.exports = parse;