123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- var tokenizerUtils = require('../tokenizer/utils');
- var findIdentifierEnd = tokenizerUtils.findIdentifierEnd;
- var findNumberEnd = tokenizerUtils.findNumberEnd;
- var findDecimalNumberEnd = tokenizerUtils.findDecimalNumberEnd;
- var isHex = tokenizerUtils.isHex;
- var tokenizerConst = require('../tokenizer/const');
- var SYMBOL_TYPE = tokenizerConst.SYMBOL_TYPE;
- var IDENTIFIER = tokenizerConst.TYPE.Identifier;
- var PLUSSIGN = tokenizerConst.TYPE.PlusSign;
- var HYPHENMINUS = tokenizerConst.TYPE.HyphenMinus;
- var NUMBERSIGN = tokenizerConst.TYPE.NumberSign;
- var PERCENTAGE = {
- '%': true
- };
- // https://www.w3.org/TR/css-values-3/#lengths
- var LENGTH = {
- // absolute length units
- 'px': true,
- 'mm': true,
- 'cm': true,
- 'in': true,
- 'pt': true,
- 'pc': true,
- 'q': true,
- // relative length units
- 'em': true,
- 'ex': true,
- 'ch': true,
- 'rem': true,
- // viewport-percentage lengths
- 'vh': true,
- 'vw': true,
- 'vmin': true,
- 'vmax': true,
- 'vm': true
- };
- var ANGLE = {
- 'deg': true,
- 'grad': true,
- 'rad': true,
- 'turn': true
- };
- var TIME = {
- 's': true,
- 'ms': true
- };
- var FREQUENCY = {
- 'hz': true,
- 'khz': true
- };
- // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution)
- var RESOLUTION = {
- 'dpi': true,
- 'dpcm': true,
- 'dppx': true,
- 'x': true // https://github.com/w3c/csswg-drafts/issues/461
- };
- // https://drafts.csswg.org/css-grid/#fr-unit
- var FLEX = {
- 'fr': true
- };
- // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume
- var DECIBEL = {
- 'db': true
- };
- // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch
- var SEMITONES = {
- 'st': true
- };
- function consumeFunction(token, addTokenToMatch, getNextToken) {
- var length = 1;
- var cursor;
- do {
- cursor = getNextToken(length++);
- } while (cursor !== null && cursor.node !== token.node);
- if (cursor === null) {
- return false;
- }
- while (true) {
- // consume tokens until cursor
- if (addTokenToMatch() === cursor) {
- break;
- }
- }
- return true;
- }
- // TODO: implement
- // can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed
- // https://drafts.csswg.org/css-values/#calc-notation
- function calc(token, addTokenToMatch, getNextToken) {
- if (token === null) {
- return false;
- }
- var name = token.value.toLowerCase();
- if (name !== 'calc(' &&
- name !== '-moz-calc(' &&
- name !== '-webkit-calc(') {
- return false;
- }
- return consumeFunction(token, addTokenToMatch, getNextToken);
- }
- function attr(token, addTokenToMatch, getNextToken) {
- if (token === null || token.value.toLowerCase() !== 'attr(') {
- return false;
- }
- return consumeFunction(token, addTokenToMatch, getNextToken);
- }
- function expression(token, addTokenToMatch, getNextToken) {
- if (token === null || token.value.toLowerCase() !== 'expression(') {
- return false;
- }
- return consumeFunction(token, addTokenToMatch, getNextToken);
- }
- function url(token, addTokenToMatch, getNextToken) {
- if (token === null || token.value.toLowerCase() !== 'url(') {
- return false;
- }
- return consumeFunction(token, addTokenToMatch, getNextToken);
- }
- function idSelector(token, addTokenToMatch) {
- if (token === null) {
- return false;
- }
- if (token.value.charCodeAt(0) !== NUMBERSIGN) {
- return false;
- }
- if (consumeIdentifier(token.value, 1) !== token.value.length) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- function isNumber(str) {
- return /^[-+]?(\d+|\d*\.\d+)([eE][-+]?\d+)?$/.test(str);
- }
- function consumeNumber(str, allowFraction) {
- var code = str.charCodeAt(0);
- return findNumberEnd(str, code === PLUSSIGN || code === HYPHENMINUS ? 1 : 0, allowFraction);
- }
- function consumeIdentifier(str, offset) {
- var code = str.charCodeAt(offset);
- if (code < 0x80 && SYMBOL_TYPE[code] !== IDENTIFIER && code !== HYPHENMINUS) {
- return offset;
- }
- return findIdentifierEnd(str, offset + 1);
- }
- function astNode(type) {
- return function(token, addTokenToMatch) {
- if (token === null || token.node.type !== type) {
- return false;
- }
- addTokenToMatch();
- return true;
- };
- }
- function dimension(type) {
- return function(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null) {
- return false;
- }
- var numberEnd = consumeNumber(token.value, true);
- if (numberEnd === 0) {
- return false;
- }
- if (type) {
- if (!type.hasOwnProperty(token.value.substr(numberEnd).toLowerCase())) {
- return false;
- }
- } else {
- var unitEnd = consumeIdentifier(token.value, numberEnd);
- if (unitEnd === numberEnd || unitEnd !== token.value.length) {
- return false;
- }
- }
- addTokenToMatch();
- return true;
- };
- }
- function zeroUnitlessDimension(type) {
- var isDimension = dimension(type);
- return function(token, addTokenToMatch, getNextToken) {
- if (isDimension(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null || Number(token.value) !== 0) {
- return false;
- }
- addTokenToMatch();
- return true;
- };
- }
- function number(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null) {
- return false;
- }
- var numberEnd = consumeNumber(token.value, true);
- if (numberEnd !== token.value.length) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- function numberZeroOne(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null || !isNumber(token.value)) {
- return false;
- }
- var value = Number(token.value);
- if (value < 0 || value > 1) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- function numberOneOrGreater(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null || !isNumber(token.value)) {
- return false;
- }
- var value = Number(token.value);
- if (value < 1) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- // TODO: fail on 10e-2
- function integer(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null) {
- return false;
- }
- var numberEnd = consumeNumber(token.value, false);
- if (numberEnd !== token.value.length) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- // TODO: fail on 10e-2
- function positiveInteger(token, addTokenToMatch, getNextToken) {
- if (calc(token, addTokenToMatch, getNextToken)) {
- return true;
- }
- if (token === null) {
- return false;
- }
- var numberEnd = findDecimalNumberEnd(token.value, 0);
- if (numberEnd !== token.value.length || token.value.charCodeAt(0) === HYPHENMINUS) {
- return false;
- }
- addTokenToMatch();
- return true;
- }
- function hexColor(token, addTokenToMatch) {
- if (token === null || token.value.charCodeAt(0) !== NUMBERSIGN) {
- return false;
- }
- var length = token.value.length - 1;
- // valid length is 3, 4, 6 and 8 (+1 for #)
- if (length !== 3 && length !== 4 && length !== 6 && length !== 8) {
- return false;
- }
- for (var i = 1; i < length; i++) {
- if (!isHex(token.value.charCodeAt(i))) {
- return false;
- }
- }
- addTokenToMatch();
- return true;
- }
- // https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
- // https://drafts.csswg.org/css-values-4/#identifier-value
- function customIdent(token, addTokenToMatch) {
- if (token === null) {
- return false;
- }
- var identEnd = consumeIdentifier(token.value, 0);
- if (identEnd !== token.value.length) {
- return false;
- }
- var name = token.value.toLowerCase();
- // § 3.2. Author-defined Identifiers: the <custom-ident> type
- // The CSS-wide keywords are not valid <custom-ident>s
- if (name === 'unset' || name === 'initial' || name === 'inherit') {
- return false;
- }
- // The default keyword is reserved and is also not a valid <custom-ident>
- if (name === 'default') {
- return false;
- }
- // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident)
- addTokenToMatch();
- return true;
- }
- module.exports = {
- 'angle': zeroUnitlessDimension(ANGLE),
- 'attr()': attr,
- 'custom-ident': customIdent,
- 'decibel': dimension(DECIBEL),
- 'dimension': dimension(),
- 'frequency': dimension(FREQUENCY),
- 'flex': dimension(FLEX),
- 'hex-color': hexColor,
- 'id-selector': idSelector, // element( <id-selector> )
- 'ident': astNode('Identifier'),
- 'integer': integer,
- 'length': zeroUnitlessDimension(LENGTH),
- 'number': number,
- 'number-zero-one': numberZeroOne,
- 'number-one-or-greater': numberOneOrGreater,
- 'percentage': dimension(PERCENTAGE),
- 'positive-integer': positiveInteger,
- 'resolution': dimension(RESOLUTION),
- 'semitones': dimension(SEMITONES),
- 'string': astNode('String'),
- 'time': dimension(TIME),
- 'unicode-range': astNode('UnicodeRange'),
- 'url': url,
- // old IE stuff
- 'progid': astNode('Raw'),
- 'expression': expression
- };
|