UNPKG

8.21 kBJavaScriptView Raw
1'use strict'; // A valid output which means nothing has been parsed.
2// Used as error return / invalid output
3
4function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
5
6function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
7
8function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
9
10var nothingHappend = {
11 prop: {},
12 eaten: ''
13};
14var defaultConfig = {
15 defaultValue: function defaultValue() {
16 return undefined;
17 } // Its a function
18
19};
20
21function parse(value, indexNext, userConfig) {
22 // Main function
23 var letsEat = '';
24 var stopOnBrace = false;
25 var errorDetected = false;
26
27 var config = _objectSpread({}, defaultConfig, {}, userConfig); // Make defaultValue a function if it isn't
28
29
30 if (typeof config.defaultValue !== 'function') {
31 var defaultValue = config.defaultValue;
32
33 config.defaultValue = function () {
34 return defaultValue;
35 };
36 }
37
38 var prop = {};
39 /* They are at least one label and at best two */
40
41 /* ekqsdf <- one label
42 * qsdfqsfd=qsdfqsdf <- two */
43
44 var labelFirst = '';
45 var labelSecond;
46
47 if (indexNext === undefined) {
48 indexNext = 0;
49 }
50 /* 3 types :
51 * .azcv <- class
52 * #poi <- id
53 * dfgh=zert <- key
54 * jkj <- this is also a key but with a user defined value (default is undefined)
55 * jkj= <- this is also a key but with a empty value
56 */
57
58
59 var type;
60 var forbidenCharacters = '\n\r{}'; // A function that detect if it's time to end the parsing
61
62 var shouldStop = function shouldStop() {
63 if (indexNext >= value.length || forbidenCharacters.indexOf(value[indexNext]) > -1) {
64 if (stopOnBrace && value[indexNext] !== '}') {
65 errorDetected = true;
66 }
67
68 return true;
69 }
70
71 return value[indexNext] === '}' && stopOnBrace;
72 };
73
74 var eaten = ''; // Couple of functions that parse same kinds of characters
75 // Used to parse spaces or identifiers
76
77 var eat = function eat(chars) {
78 eaten = '';
79
80 while (indexNext < value.length && forbidenCharacters.indexOf(value.charAt(indexNext)) < 0 && chars.indexOf(value.charAt(indexNext)) >= 0) {
81 letsEat += value.charAt(indexNext);
82 eaten += value.charAt(indexNext);
83 indexNext++;
84 }
85
86 return shouldStop();
87 };
88
89 var eatUntil = function eatUntil(chars) {
90 eaten = '';
91
92 while (indexNext < value.length && forbidenCharacters.indexOf(value.charAt(indexNext)) < 0 && chars.indexOf(value.charAt(indexNext)) < 0) {
93 letsEat += value.charAt(indexNext);
94 eaten += value.charAt(indexNext);
95 indexNext++;
96 } // Ugly but keep the main loop readable
97 // Set the label it should set
98
99
100 if (labelFirst) {
101 labelSecond = eaten;
102 } else {
103 labelFirst = eaten;
104 }
105
106 return shouldStop();
107 }; // In quote, every character is valid except the unescaped quotes and CR or LF
108 // Same function for single and double quote
109
110
111 var eatInQuote = function eatInQuote(quote) {
112 eaten = ''; // First check so value[indexNext-1] will always be valid
113
114 if (value[indexNext] === quote) {
115 return;
116 }
117
118 while (indexNext < value.length && !(quote === value[indexNext] && value[indexNext - 1] !== '\\') && value[indexNext] !== '\n' && value[indexNext] !== '\r') {
119 letsEat += value.charAt(indexNext);
120 eaten += value.charAt(indexNext);
121 indexNext++;
122 } // If we encounter an EOL, there is an error
123 // We are waiting for a quote
124
125
126 if (value[indexNext] === '\n' || value[indexNext] === '\r' || indexNext >= value.length) {
127 errorDetected = true;
128 return true;
129 } // Ugly but keep the main loop readable
130
131
132 if (labelFirst) {
133 labelSecond = eaten.replace(/\\"/g, '"');
134 } else {
135 labelFirst = eaten.replace(/\\"/g, '"');
136 }
137
138 return shouldStop();
139 }; // It's really common to eat only one character so let's make it a function
140
141
142 var eatOne = function eatOne(c, skipStopCheck) {
143 // Miam !
144 letsEat += c;
145 indexNext++;
146 return skipStopCheck ? false : shouldStop();
147 }; // Common parsing of quotes
148
149
150 var eatQuote = function eatQuote(q) {
151 eatOne(q, true);
152 eatInQuote(q, true);
153
154 if (value.charAt(indexNext) !== q) {
155 return nothingHappend;
156 }
157
158 if (eatOne(q)) {
159 return -1;
160 }
161 };
162
163 var idSetByKey = false;
164
165 var addAttribute = function addAttribute() {
166 switch (type) {
167 case 'id':
168 // ID
169 if (idSetByKey) {
170 prop.id = labelFirst;
171 idSetByKey = false;
172 } else {
173 prop.id = prop.id || labelFirst;
174 }
175
176 break;
177
178 case 'class':
179 if (!prop["class"]) {
180 prop["class"] = [];
181 }
182
183 if (prop["class"].indexOf(labelFirst) < 0) {
184 prop["class"].push(labelFirst);
185 }
186
187 break;
188
189 case 'key':
190 if (!labelFirst) {
191 return nothingHappend;
192 }
193
194 if (!(labelFirst in prop)) {
195 if (labelSecond === undefined) {
196 // Here, we have an attribute without value
197 // so it's user defined
198 prop[labelFirst] = config.defaultValue(labelFirst);
199 } else {
200 prop[labelFirst] = labelFirst === 'class' ? [labelSecond] : labelSecond;
201 }
202
203 if (labelFirst === 'id') {
204 idSetByKey = true;
205 }
206 } else if (labelFirst === 'class' && Boolean(labelSecond)) {
207 prop["class"].push(labelSecond);
208 }
209
210 break;
211
212 default:
213 }
214
215 type = undefined;
216 labelFirst = '';
217 labelSecond = undefined;
218 };
219 /** *********************** Start parsing ************************ */
220 // Let's check for leading spaces first
221
222
223 eat(' \t\v');
224
225 if (value[indexNext] === '{') {
226 eatOne('{');
227 stopOnBrace = true;
228 }
229
230 while (!shouldStop()) {
231 // Main loop which extract attributes
232 if (eat(' \t\v')) {
233 break;
234 }
235
236 if (value.charAt(indexNext) === '.') {
237 // Classes
238 type = 'class';
239
240 if (eatOne('.')) {
241 errorDetected = true;
242 break;
243 }
244 } else if (value.charAt(indexNext) === '#') {
245 // ID
246 type = 'id';
247
248 if (eatOne('#')) {
249 errorDetected = true;
250 break;
251 }
252 } else {
253 // Key
254 type = 'key';
255 } // Extract name
256
257
258 if (eatUntil('=\t\b\v  ') || !labelFirst) {
259 break;
260 }
261
262 if (value.charAt(indexNext) === '=' && type === 'key') {
263 // Set labelSecond
264 if (eatOne('=')) {
265 break;
266 }
267
268 if (value.charAt(indexNext) === '"') {
269 var ret = eatQuote('"');
270
271 if (ret === -1) {
272 break;
273 } else if (ret === nothingHappend) {
274 return nothingHappend;
275 }
276 } else if (value.charAt(indexNext) === '\'') {
277 var _ret = eatQuote('\'');
278
279 if (_ret === -1) {
280 break;
281 } else if (_ret === nothingHappend) {
282 return nothingHappend;
283 }
284 } else if (eatUntil(' \t\n\r\v=}')) {
285 break;
286 }
287 } // Add the parsed attribute to the output prop with the ad hoc type
288
289
290 addAttribute();
291 }
292
293 addAttribute();
294
295 if (stopOnBrace) {
296 if (indexNext < value.length && value[indexNext] === '}') {
297 stopOnBrace = false;
298 eatOne('}');
299 } else {
300 return nothingHappend;
301 }
302 }
303
304 return errorDetected ? nothingHappend : {
305 prop: prop,
306 eaten: letsEat
307 };
308}
309
310module.exports = parse;
\No newline at end of file