| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 | var tokenizer = require('../tokenizer');var isIdentifierStart = tokenizer.isIdentifierStart;var isHexDigit = tokenizer.isHexDigit;var isDigit = tokenizer.isDigit;var cmpStr = tokenizer.cmpStr;var consumeNumber = tokenizer.consumeNumber;var TYPE = tokenizer.TYPE;var anPlusB = require('./generic-an-plus-b');var urange = require('./generic-urange');var cssWideKeywords = ['unset', 'initial', 'inherit'];var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc('];// https://www.w3.org/TR/css-values-3/#lengthsvar 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-unitvar FLEX = {    'fr': true};// https://www.w3.org/TR/css3-speech/#mixing-props-voice-volumevar DECIBEL = {    'db': true};// https://www.w3.org/TR/css3-speech/#voice-props-voice-pitchvar SEMITONES = {    'st': true};// safe char code getterfunction charCode(str, index) {    return index < str.length ? str.charCodeAt(index) : 0;}function eqStr(actual, expected) {    return cmpStr(actual, 0, actual.length, expected);}function eqStrAny(actual, expected) {    for (var i = 0; i < expected.length; i++) {        if (eqStr(actual, expected[i])) {            return true;        }    }    return false;}// IE postfix hack, i.e. 123\0 or 123px\9function isPostfixIeHack(str, offset) {    if (offset !== str.length - 2) {        return false;    }    return (        str.charCodeAt(offset) === 0x005C &&  // U+005C REVERSE SOLIDUS (\)        isDigit(str.charCodeAt(offset + 1))    );}function outOfRange(opts, value, numEnd) {    if (opts && opts.type === 'Range') {        var num = Number(            numEnd !== undefined && numEnd !== value.length                ? value.substr(0, numEnd)                : value        );        if (isNaN(num)) {            return true;        }        if (opts.min !== null && num < opts.min) {            return true;        }        if (opts.max !== null && num > opts.max) {            return true;        }    }    return false;}function consumeFunction(token, getNextToken) {    var startIdx = token.index;    var length = 0;    // balanced token consuming    do {        length++;        if (token.balance <= startIdx) {            break;        }    } while (token = getNextToken(length));    return length;}// TODO: implement// can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed// https://drafts.csswg.org/css-values/#calc-notationfunction calc(next) {    return function(token, getNextToken, opts) {        if (token === null) {            return 0;        }        if (token.type === TYPE.Function && eqStrAny(token.value, calcFunctionNames)) {            return consumeFunction(token, getNextToken);        }        return next(token, getNextToken, opts);    };}function tokenType(expectedTokenType) {    return function(token) {        if (token === null || token.type !== expectedTokenType) {            return 0;        }        return 1;    };}function func(name) {    name = name + '(';    return function(token, getNextToken) {        if (token !== null && eqStr(token.value, name)) {            return consumeFunction(token, getNextToken);        }        return 0;    };}// =========================// Complex types//// https://drafts.csswg.org/css-values-4/#custom-idents// 4.2. Author-defined Identifiers: the <custom-ident> type// Some properties accept arbitrary author-defined identifiers as a component value.// This generic data type is denoted by <custom-ident>, and represents any valid CSS identifier// that would not be misinterpreted as a pre-defined keyword in that property’s value definition.//// See also: https://developer.mozilla.org/en-US/docs/Web/CSS/custom-identfunction customIdent(token) {    if (token === null || token.type !== TYPE.Ident) {        return 0;    }    var name = token.value.toLowerCase();    // The CSS-wide keywords are not valid <custom-ident>s    if (eqStrAny(name, cssWideKeywords)) {        return 0;    }    // The default keyword is reserved and is also not a valid <custom-ident>    if (eqStr(name, 'default')) {        return 0;    }    // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident)    // Specifications using <custom-ident> must specify clearly what other keywords    // are excluded from <custom-ident>, if any—for example by saying that any pre-defined keywords    // in that property’s value definition are excluded. Excluded keywords are excluded    // in all ASCII case permutations.    return 1;}// https://drafts.csswg.org/css-variables/#typedef-custom-property-name// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.// The <custom-property-name> production corresponds to this: it’s defined as any valid identifier// that starts with two dashes, except -- itself, which is reserved for future use by CSS.// NOTE: Current implementation treat `--` as a valid name since most (all?) major browsers treat it as valid.function customPropertyName(token) {    // ... defined as any valid identifier    if (token === null || token.type !== TYPE.Ident) {        return 0;    }    // ... that starts with two dashes (U+002D HYPHEN-MINUS)    if (charCode(token.value, 0) !== 0x002D || charCode(token.value, 1) !== 0x002D) {        return 0;    }    return 1;}// https://drafts.csswg.org/css-color-4/#hex-notation// The syntax of a <hex-color> is a <hash-token> token whose value consists of 3, 4, 6, or 8 hexadecimal digits.// In other words, a hex color is written as a hash character, "#", followed by some number of digits 0-9 or// letters a-f (the case of the letters doesn’t matter - #00ff00 is identical to #00FF00).function hexColor(token) {    if (token === null || token.type !== TYPE.Hash) {        return 0;    }    var length = token.value.length;    // valid values (length): #rgb (4), #rgba (5), #rrggbb (7), #rrggbbaa (9)    if (length !== 4 && length !== 5 && length !== 7 && length !== 9) {        return 0;    }    for (var i = 1; i < length; i++) {        if (!isHexDigit(token.value.charCodeAt(i))) {            return 0;        }    }    return 1;}function idSelector(token) {    if (token === null || token.type !== TYPE.Hash) {        return 0;    }    if (!isIdentifierStart(charCode(token.value, 1), charCode(token.value, 2), charCode(token.value, 3))) {        return 0;    }    return 1;}// https://drafts.csswg.org/css-syntax/#any-value// It represents the entirety of what a valid declaration can have as its value.function declarationValue(token, getNextToken) {    if (!token) {        return 0;    }    var length = 0;    var level = 0;    var startIdx = token.index;    // The <declaration-value> production matches any sequence of one or more tokens,    // so long as the sequence ...    scan:    do {        switch (token.type) {            // ... does not contain <bad-string-token>, <bad-url-token>,            case TYPE.BadString:            case TYPE.BadUrl:                break scan;            // ... unmatched <)-token>, <]-token>, or <}-token>,            case TYPE.RightCurlyBracket:            case TYPE.RightParenthesis:            case TYPE.RightSquareBracket:                if (token.balance > token.index || token.balance < startIdx) {                    break scan;                }                level--;                break;            // ... or top-level <semicolon-token> tokens            case TYPE.Semicolon:                if (level === 0) {                    break scan;                }                break;            // ... or <delim-token> tokens with a value of "!"            case TYPE.Delim:                if (token.value === '!' && level === 0) {                    break scan;                }                break;            case TYPE.Function:            case TYPE.LeftParenthesis:            case TYPE.LeftSquareBracket:            case TYPE.LeftCurlyBracket:                level++;                break;        }        length++;        // until balance closing        if (token.balance <= startIdx) {            break;        }    } while (token = getNextToken(length));    return length;}// https://drafts.csswg.org/css-syntax/#any-value// The <any-value> production is identical to <declaration-value>, but also// allows top-level <semicolon-token> tokens and <delim-token> tokens// with a value of "!". It represents the entirety of what valid CSS can be in any context.function anyValue(token, getNextToken) {    if (!token) {        return 0;    }    var startIdx = token.index;    var length = 0;    // The <any-value> production matches any sequence of one or more tokens,    // so long as the sequence ...    scan:    do {        switch (token.type) {            // ... does not contain <bad-string-token>, <bad-url-token>,            case TYPE.BadString:            case TYPE.BadUrl:                break scan;            // ... unmatched <)-token>, <]-token>, or <}-token>,            case TYPE.RightCurlyBracket:            case TYPE.RightParenthesis:            case TYPE.RightSquareBracket:                if (token.balance > token.index || token.balance < startIdx) {                    break scan;                }                break;        }        length++;        // until balance closing        if (token.balance <= startIdx) {            break;        }    } while (token = getNextToken(length));    return length;}// =========================// Dimensions//function dimension(type) {    return function(token, getNextToken, opts) {        if (token === null || token.type !== TYPE.Dimension) {            return 0;        }        var numberEnd = consumeNumber(token.value, 0);        // check unit        if (type !== null) {            // check for IE postfix hack, i.e. 123px\0 or 123px\9            var reverseSolidusOffset = token.value.indexOf('\\', numberEnd);            var unit = reverseSolidusOffset === -1 || !isPostfixIeHack(token.value, reverseSolidusOffset)                ? token.value.substr(numberEnd)                : token.value.substring(numberEnd, reverseSolidusOffset);            if (type.hasOwnProperty(unit.toLowerCase()) === false) {                return 0;            }        }        // check range if specified        if (outOfRange(opts, token.value, numberEnd)) {            return 0;        }        return 1;    };}// =========================// Percentage//// §5.5. Percentages: the <percentage> type// https://drafts.csswg.org/css-values-4/#percentagesfunction percentage(token, getNextToken, opts) {    // ... corresponds to the <percentage-token> production    if (token === null || token.type !== TYPE.Percentage) {        return 0;    }    // check range if specified    if (outOfRange(opts, token.value, token.value.length - 1)) {        return 0;    }    return 1;}// =========================// Numeric//// https://drafts.csswg.org/css-values-4/#numbers// The value <zero> represents a literal number with the value 0. Expressions that merely// evaluate to a <number> with the value 0 (for example, calc(0)) do not match <zero>;// only literal <number-token>s do.function zero(next) {    if (typeof next !== 'function') {        next = function() {            return 0;        };    }    return function(token, getNextToken, opts) {        if (token !== null && token.type === TYPE.Number) {            if (Number(token.value) === 0) {                return 1;            }        }        return next(token, getNextToken, opts);    };}// § 5.3. Real Numbers: the <number> type// https://drafts.csswg.org/css-values-4/#numbers// Number values are denoted by <number>, and represent real numbers, possibly with a fractional component.// ... It corresponds to the <number-token> productionfunction number(token, getNextToken, opts) {    if (token === null) {        return 0;    }    var numberEnd = consumeNumber(token.value, 0);    var isNumber = numberEnd === token.value.length;    if (!isNumber && !isPostfixIeHack(token.value, numberEnd)) {        return 0;    }    // check range if specified    if (outOfRange(opts, token.value, numberEnd)) {        return 0;    }    return 1;}// §5.2. Integers: the <integer> type// https://drafts.csswg.org/css-values-4/#integersfunction integer(token, getNextToken, opts) {    // ... corresponds to a subset of the <number-token> production    if (token === null || token.type !== TYPE.Number) {        return 0;    }    // The first digit of an integer may be immediately preceded by `-` or `+` to indicate the integer’s sign.    var i = token.value.charCodeAt(0) === 0x002B ||       // U+002B PLUS SIGN (+)            token.value.charCodeAt(0) === 0x002D ? 1 : 0; // U+002D HYPHEN-MINUS (-)    // When written literally, an integer is one or more decimal digits 0 through 9 ...    for (; i < token.value.length; i++) {        if (!isDigit(token.value.charCodeAt(i))) {            return 0;        }    }    // check range if specified    if (outOfRange(opts, token.value, i)) {        return 0;    }    return 1;}module.exports = {    // token types    'ident-token': tokenType(TYPE.Ident),    'function-token': tokenType(TYPE.Function),    'at-keyword-token': tokenType(TYPE.AtKeyword),    'hash-token': tokenType(TYPE.Hash),    'string-token': tokenType(TYPE.String),    'bad-string-token': tokenType(TYPE.BadString),    'url-token': tokenType(TYPE.Url),    'bad-url-token': tokenType(TYPE.BadUrl),    'delim-token': tokenType(TYPE.Delim),    'number-token': tokenType(TYPE.Number),    'percentage-token': tokenType(TYPE.Percentage),    'dimension-token': tokenType(TYPE.Dimension),    'whitespace-token': tokenType(TYPE.WhiteSpace),    'CDO-token': tokenType(TYPE.CDO),    'CDC-token': tokenType(TYPE.CDC),    'colon-token': tokenType(TYPE.Colon),    'semicolon-token': tokenType(TYPE.Semicolon),    'comma-token': tokenType(TYPE.Comma),    '[-token': tokenType(TYPE.LeftSquareBracket),    ']-token': tokenType(TYPE.RightSquareBracket),    '(-token': tokenType(TYPE.LeftParenthesis),    ')-token': tokenType(TYPE.RightParenthesis),    '{-token': tokenType(TYPE.LeftCurlyBracket),    '}-token': tokenType(TYPE.RightCurlyBracket),    // token type aliases    'string': tokenType(TYPE.String),    'ident': tokenType(TYPE.Ident),    // complex types    'custom-ident': customIdent,    'custom-property-name': customPropertyName,    'hex-color': hexColor,    'id-selector': idSelector, // element( <id-selector> )    'an-plus-b': anPlusB,    'urange': urange,    'declaration-value': declarationValue,    'any-value': anyValue,    // dimensions    'dimension': calc(dimension(null)),    'angle': calc(dimension(ANGLE)),    'decibel': calc(dimension(DECIBEL)),    'frequency': calc(dimension(FREQUENCY)),    'flex': calc(dimension(FLEX)),    'length': calc(zero(dimension(LENGTH))),    'resolution': calc(dimension(RESOLUTION)),    'semitones': calc(dimension(SEMITONES)),    'time': calc(dimension(TIME)),    // percentage    'percentage': calc(percentage),    // numeric    'zero': zero(),    'number': calc(number),    'integer': calc(integer),    // old IE stuff    '-ms-legacy-expression': func('expression')};
 |