source: imaps-frontend/node_modules/css-tree/lib/lexer/generic.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 16.0 KB
Line 
1var tokenizer = require('../tokenizer');
2var isIdentifierStart = tokenizer.isIdentifierStart;
3var isHexDigit = tokenizer.isHexDigit;
4var isDigit = tokenizer.isDigit;
5var cmpStr = tokenizer.cmpStr;
6var consumeNumber = tokenizer.consumeNumber;
7var TYPE = tokenizer.TYPE;
8var anPlusB = require('./generic-an-plus-b');
9var urange = require('./generic-urange');
10
11var cssWideKeywords = ['unset', 'initial', 'inherit'];
12var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc('];
13
14// https://www.w3.org/TR/css-values-3/#lengths
15var LENGTH = {
16 // absolute length units
17 'px': true,
18 'mm': true,
19 'cm': true,
20 'in': true,
21 'pt': true,
22 'pc': true,
23 'q': true,
24
25 // relative length units
26 'em': true,
27 'ex': true,
28 'ch': true,
29 'rem': true,
30
31 // viewport-percentage lengths
32 'vh': true,
33 'vw': true,
34 'vmin': true,
35 'vmax': true,
36 'vm': true
37};
38
39var ANGLE = {
40 'deg': true,
41 'grad': true,
42 'rad': true,
43 'turn': true
44};
45
46var TIME = {
47 's': true,
48 'ms': true
49};
50
51var FREQUENCY = {
52 'hz': true,
53 'khz': true
54};
55
56// https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution)
57var RESOLUTION = {
58 'dpi': true,
59 'dpcm': true,
60 'dppx': true,
61 'x': true // https://github.com/w3c/csswg-drafts/issues/461
62};
63
64// https://drafts.csswg.org/css-grid/#fr-unit
65var FLEX = {
66 'fr': true
67};
68
69// https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume
70var DECIBEL = {
71 'db': true
72};
73
74// https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch
75var SEMITONES = {
76 'st': true
77};
78
79// safe char code getter
80function charCode(str, index) {
81 return index < str.length ? str.charCodeAt(index) : 0;
82}
83
84function eqStr(actual, expected) {
85 return cmpStr(actual, 0, actual.length, expected);
86}
87
88function eqStrAny(actual, expected) {
89 for (var i = 0; i < expected.length; i++) {
90 if (eqStr(actual, expected[i])) {
91 return true;
92 }
93 }
94
95 return false;
96}
97
98// IE postfix hack, i.e. 123\0 or 123px\9
99function isPostfixIeHack(str, offset) {
100 if (offset !== str.length - 2) {
101 return false;
102 }
103
104 return (
105 str.charCodeAt(offset) === 0x005C && // U+005C REVERSE SOLIDUS (\)
106 isDigit(str.charCodeAt(offset + 1))
107 );
108}
109
110function outOfRange(opts, value, numEnd) {
111 if (opts && opts.type === 'Range') {
112 var num = Number(
113 numEnd !== undefined && numEnd !== value.length
114 ? value.substr(0, numEnd)
115 : value
116 );
117
118 if (isNaN(num)) {
119 return true;
120 }
121
122 if (opts.min !== null && num < opts.min) {
123 return true;
124 }
125
126 if (opts.max !== null && num > opts.max) {
127 return true;
128 }
129 }
130
131 return false;
132}
133
134function consumeFunction(token, getNextToken) {
135 var startIdx = token.index;
136 var length = 0;
137
138 // balanced token consuming
139 do {
140 length++;
141
142 if (token.balance <= startIdx) {
143 break;
144 }
145 } while (token = getNextToken(length));
146
147 return length;
148}
149
150// TODO: implement
151// can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed
152// https://drafts.csswg.org/css-values/#calc-notation
153function calc(next) {
154 return function(token, getNextToken, opts) {
155 if (token === null) {
156 return 0;
157 }
158
159 if (token.type === TYPE.Function && eqStrAny(token.value, calcFunctionNames)) {
160 return consumeFunction(token, getNextToken);
161 }
162
163 return next(token, getNextToken, opts);
164 };
165}
166
167function tokenType(expectedTokenType) {
168 return function(token) {
169 if (token === null || token.type !== expectedTokenType) {
170 return 0;
171 }
172
173 return 1;
174 };
175}
176
177function func(name) {
178 name = name + '(';
179
180 return function(token, getNextToken) {
181 if (token !== null && eqStr(token.value, name)) {
182 return consumeFunction(token, getNextToken);
183 }
184
185 return 0;
186 };
187}
188
189// =========================
190// Complex types
191//
192
193// https://drafts.csswg.org/css-values-4/#custom-idents
194// 4.2. Author-defined Identifiers: the <custom-ident> type
195// Some properties accept arbitrary author-defined identifiers as a component value.
196// This generic data type is denoted by <custom-ident>, and represents any valid CSS identifier
197// that would not be misinterpreted as a pre-defined keyword in that property’s value definition.
198//
199// See also: https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
200function customIdent(token) {
201 if (token === null || token.type !== TYPE.Ident) {
202 return 0;
203 }
204
205 var name = token.value.toLowerCase();
206
207 // The CSS-wide keywords are not valid <custom-ident>s
208 if (eqStrAny(name, cssWideKeywords)) {
209 return 0;
210 }
211
212 // The default keyword is reserved and is also not a valid <custom-ident>
213 if (eqStr(name, 'default')) {
214 return 0;
215 }
216
217 // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident)
218 // Specifications using <custom-ident> must specify clearly what other keywords
219 // are excluded from <custom-ident>, if any—for example by saying that any pre-defined keywords
220 // in that property’s value definition are excluded. Excluded keywords are excluded
221 // in all ASCII case permutations.
222
223 return 1;
224}
225
226// https://drafts.csswg.org/css-variables/#typedef-custom-property-name
227// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
228// The <custom-property-name> production corresponds to this: it’s defined as any valid identifier
229// that starts with two dashes, except -- itself, which is reserved for future use by CSS.
230// NOTE: Current implementation treat `--` as a valid name since most (all?) major browsers treat it as valid.
231function customPropertyName(token) {
232 // ... defined as any valid identifier
233 if (token === null || token.type !== TYPE.Ident) {
234 return 0;
235 }
236
237 // ... that starts with two dashes (U+002D HYPHEN-MINUS)
238 if (charCode(token.value, 0) !== 0x002D || charCode(token.value, 1) !== 0x002D) {
239 return 0;
240 }
241
242 return 1;
243}
244
245// https://drafts.csswg.org/css-color-4/#hex-notation
246// The syntax of a <hex-color> is a <hash-token> token whose value consists of 3, 4, 6, or 8 hexadecimal digits.
247// In other words, a hex color is written as a hash character, "#", followed by some number of digits 0-9 or
248// letters a-f (the case of the letters doesn’t matter - #00ff00 is identical to #00FF00).
249function hexColor(token) {
250 if (token === null || token.type !== TYPE.Hash) {
251 return 0;
252 }
253
254 var length = token.value.length;
255
256 // valid values (length): #rgb (4), #rgba (5), #rrggbb (7), #rrggbbaa (9)
257 if (length !== 4 && length !== 5 && length !== 7 && length !== 9) {
258 return 0;
259 }
260
261 for (var i = 1; i < length; i++) {
262 if (!isHexDigit(token.value.charCodeAt(i))) {
263 return 0;
264 }
265 }
266
267 return 1;
268}
269
270function idSelector(token) {
271 if (token === null || token.type !== TYPE.Hash) {
272 return 0;
273 }
274
275 if (!isIdentifierStart(charCode(token.value, 1), charCode(token.value, 2), charCode(token.value, 3))) {
276 return 0;
277 }
278
279 return 1;
280}
281
282// https://drafts.csswg.org/css-syntax/#any-value
283// It represents the entirety of what a valid declaration can have as its value.
284function declarationValue(token, getNextToken) {
285 if (!token) {
286 return 0;
287 }
288
289 var length = 0;
290 var level = 0;
291 var startIdx = token.index;
292
293 // The <declaration-value> production matches any sequence of one or more tokens,
294 // so long as the sequence ...
295 scan:
296 do {
297 switch (token.type) {
298 // ... does not contain <bad-string-token>, <bad-url-token>,
299 case TYPE.BadString:
300 case TYPE.BadUrl:
301 break scan;
302
303 // ... unmatched <)-token>, <]-token>, or <}-token>,
304 case TYPE.RightCurlyBracket:
305 case TYPE.RightParenthesis:
306 case TYPE.RightSquareBracket:
307 if (token.balance > token.index || token.balance < startIdx) {
308 break scan;
309 }
310
311 level--;
312 break;
313
314 // ... or top-level <semicolon-token> tokens
315 case TYPE.Semicolon:
316 if (level === 0) {
317 break scan;
318 }
319
320 break;
321
322 // ... or <delim-token> tokens with a value of "!"
323 case TYPE.Delim:
324 if (token.value === '!' && level === 0) {
325 break scan;
326 }
327
328 break;
329
330 case TYPE.Function:
331 case TYPE.LeftParenthesis:
332 case TYPE.LeftSquareBracket:
333 case TYPE.LeftCurlyBracket:
334 level++;
335 break;
336 }
337
338 length++;
339
340 // until balance closing
341 if (token.balance <= startIdx) {
342 break;
343 }
344 } while (token = getNextToken(length));
345
346 return length;
347}
348
349// https://drafts.csswg.org/css-syntax/#any-value
350// The <any-value> production is identical to <declaration-value>, but also
351// allows top-level <semicolon-token> tokens and <delim-token> tokens
352// with a value of "!". It represents the entirety of what valid CSS can be in any context.
353function anyValue(token, getNextToken) {
354 if (!token) {
355 return 0;
356 }
357
358 var startIdx = token.index;
359 var length = 0;
360
361 // The <any-value> production matches any sequence of one or more tokens,
362 // so long as the sequence ...
363 scan:
364 do {
365 switch (token.type) {
366 // ... does not contain <bad-string-token>, <bad-url-token>,
367 case TYPE.BadString:
368 case TYPE.BadUrl:
369 break scan;
370
371 // ... unmatched <)-token>, <]-token>, or <}-token>,
372 case TYPE.RightCurlyBracket:
373 case TYPE.RightParenthesis:
374 case TYPE.RightSquareBracket:
375 if (token.balance > token.index || token.balance < startIdx) {
376 break scan;
377 }
378
379 break;
380 }
381
382 length++;
383
384 // until balance closing
385 if (token.balance <= startIdx) {
386 break;
387 }
388 } while (token = getNextToken(length));
389
390 return length;
391}
392
393// =========================
394// Dimensions
395//
396
397function dimension(type) {
398 return function(token, getNextToken, opts) {
399 if (token === null || token.type !== TYPE.Dimension) {
400 return 0;
401 }
402
403 var numberEnd = consumeNumber(token.value, 0);
404
405 // check unit
406 if (type !== null) {
407 // check for IE postfix hack, i.e. 123px\0 or 123px\9
408 var reverseSolidusOffset = token.value.indexOf('\\', numberEnd);
409 var unit = reverseSolidusOffset === -1 || !isPostfixIeHack(token.value, reverseSolidusOffset)
410 ? token.value.substr(numberEnd)
411 : token.value.substring(numberEnd, reverseSolidusOffset);
412
413 if (type.hasOwnProperty(unit.toLowerCase()) === false) {
414 return 0;
415 }
416 }
417
418 // check range if specified
419 if (outOfRange(opts, token.value, numberEnd)) {
420 return 0;
421 }
422
423 return 1;
424 };
425}
426
427// =========================
428// Percentage
429//
430
431// §5.5. Percentages: the <percentage> type
432// https://drafts.csswg.org/css-values-4/#percentages
433function percentage(token, getNextToken, opts) {
434 // ... corresponds to the <percentage-token> production
435 if (token === null || token.type !== TYPE.Percentage) {
436 return 0;
437 }
438
439 // check range if specified
440 if (outOfRange(opts, token.value, token.value.length - 1)) {
441 return 0;
442 }
443
444 return 1;
445}
446
447// =========================
448// Numeric
449//
450
451// https://drafts.csswg.org/css-values-4/#numbers
452// The value <zero> represents a literal number with the value 0. Expressions that merely
453// evaluate to a <number> with the value 0 (for example, calc(0)) do not match <zero>;
454// only literal <number-token>s do.
455function zero(next) {
456 if (typeof next !== 'function') {
457 next = function() {
458 return 0;
459 };
460 }
461
462 return function(token, getNextToken, opts) {
463 if (token !== null && token.type === TYPE.Number) {
464 if (Number(token.value) === 0) {
465 return 1;
466 }
467 }
468
469 return next(token, getNextToken, opts);
470 };
471}
472
473// § 5.3. Real Numbers: the <number> type
474// https://drafts.csswg.org/css-values-4/#numbers
475// Number values are denoted by <number>, and represent real numbers, possibly with a fractional component.
476// ... It corresponds to the <number-token> production
477function number(token, getNextToken, opts) {
478 if (token === null) {
479 return 0;
480 }
481
482 var numberEnd = consumeNumber(token.value, 0);
483 var isNumber = numberEnd === token.value.length;
484 if (!isNumber && !isPostfixIeHack(token.value, numberEnd)) {
485 return 0;
486 }
487
488 // check range if specified
489 if (outOfRange(opts, token.value, numberEnd)) {
490 return 0;
491 }
492
493 return 1;
494}
495
496// §5.2. Integers: the <integer> type
497// https://drafts.csswg.org/css-values-4/#integers
498function integer(token, getNextToken, opts) {
499 // ... corresponds to a subset of the <number-token> production
500 if (token === null || token.type !== TYPE.Number) {
501 return 0;
502 }
503
504 // The first digit of an integer may be immediately preceded by `-` or `+` to indicate the integer’s sign.
505 var i = token.value.charCodeAt(0) === 0x002B || // U+002B PLUS SIGN (+)
506 token.value.charCodeAt(0) === 0x002D ? 1 : 0; // U+002D HYPHEN-MINUS (-)
507
508 // When written literally, an integer is one or more decimal digits 0 through 9 ...
509 for (; i < token.value.length; i++) {
510 if (!isDigit(token.value.charCodeAt(i))) {
511 return 0;
512 }
513 }
514
515 // check range if specified
516 if (outOfRange(opts, token.value, i)) {
517 return 0;
518 }
519
520 return 1;
521}
522
523module.exports = {
524 // token types
525 'ident-token': tokenType(TYPE.Ident),
526 'function-token': tokenType(TYPE.Function),
527 'at-keyword-token': tokenType(TYPE.AtKeyword),
528 'hash-token': tokenType(TYPE.Hash),
529 'string-token': tokenType(TYPE.String),
530 'bad-string-token': tokenType(TYPE.BadString),
531 'url-token': tokenType(TYPE.Url),
532 'bad-url-token': tokenType(TYPE.BadUrl),
533 'delim-token': tokenType(TYPE.Delim),
534 'number-token': tokenType(TYPE.Number),
535 'percentage-token': tokenType(TYPE.Percentage),
536 'dimension-token': tokenType(TYPE.Dimension),
537 'whitespace-token': tokenType(TYPE.WhiteSpace),
538 'CDO-token': tokenType(TYPE.CDO),
539 'CDC-token': tokenType(TYPE.CDC),
540 'colon-token': tokenType(TYPE.Colon),
541 'semicolon-token': tokenType(TYPE.Semicolon),
542 'comma-token': tokenType(TYPE.Comma),
543 '[-token': tokenType(TYPE.LeftSquareBracket),
544 ']-token': tokenType(TYPE.RightSquareBracket),
545 '(-token': tokenType(TYPE.LeftParenthesis),
546 ')-token': tokenType(TYPE.RightParenthesis),
547 '{-token': tokenType(TYPE.LeftCurlyBracket),
548 '}-token': tokenType(TYPE.RightCurlyBracket),
549
550 // token type aliases
551 'string': tokenType(TYPE.String),
552 'ident': tokenType(TYPE.Ident),
553
554 // complex types
555 'custom-ident': customIdent,
556 'custom-property-name': customPropertyName,
557 'hex-color': hexColor,
558 'id-selector': idSelector, // element( <id-selector> )
559 'an-plus-b': anPlusB,
560 'urange': urange,
561 'declaration-value': declarationValue,
562 'any-value': anyValue,
563
564 // dimensions
565 'dimension': calc(dimension(null)),
566 'angle': calc(dimension(ANGLE)),
567 'decibel': calc(dimension(DECIBEL)),
568 'frequency': calc(dimension(FREQUENCY)),
569 'flex': calc(dimension(FLEX)),
570 'length': calc(zero(dimension(LENGTH))),
571 'resolution': calc(dimension(RESOLUTION)),
572 'semitones': calc(dimension(SEMITONES)),
573 'time': calc(dimension(TIME)),
574
575 // percentage
576 'percentage': calc(percentage),
577
578 // numeric
579 'zero': zero(),
580 'number': calc(number),
581 'integer': calc(integer),
582
583 // old IE stuff
584 '-ms-legacy-expression': func('expression')
585};
Note: See TracBrowser for help on using the repository browser.