|
import { cssWideKeywords } from './generic-const.js'; |
|
import anPlusB from './generic-an-plus-b.js'; |
|
import urange from './generic-urange.js'; |
|
import { |
|
isIdentifierStart, |
|
isHexDigit, |
|
isDigit, |
|
cmpStr, |
|
consumeNumber, |
|
|
|
Ident, |
|
Function as FunctionToken, |
|
AtKeyword, |
|
Hash, |
|
String as StringToken, |
|
BadString, |
|
Url, |
|
BadUrl, |
|
Delim, |
|
Number as NumberToken, |
|
Percentage, |
|
Dimension, |
|
WhiteSpace, |
|
CDO, |
|
CDC, |
|
Colon, |
|
Semicolon, |
|
Comma, |
|
LeftSquareBracket, |
|
RightSquareBracket, |
|
LeftParenthesis, |
|
RightParenthesis, |
|
LeftCurlyBracket, |
|
RightCurlyBracket |
|
} from '../tokenizer/index.js'; |
|
|
|
const calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; |
|
const balancePair = new Map([ |
|
[FunctionToken, RightParenthesis], |
|
[LeftParenthesis, RightParenthesis], |
|
[LeftSquareBracket, RightSquareBracket], |
|
[LeftCurlyBracket, RightCurlyBracket] |
|
]); |
|
|
|
|
|
function charCodeAt(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 (let i = 0; i < expected.length; i++) { |
|
if (eqStr(actual, expected[i])) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
function isPostfixIeHack(str, offset) { |
|
if (offset !== str.length - 2) { |
|
return false; |
|
} |
|
|
|
return ( |
|
charCodeAt(str, offset) === 0x005C && |
|
isDigit(charCodeAt(str, offset + 1)) |
|
); |
|
} |
|
|
|
function outOfRange(opts, value, numEnd) { |
|
if (opts && opts.type === 'Range') { |
|
const num = Number( |
|
numEnd !== undefined && numEnd !== value.length |
|
? value.substr(0, numEnd) |
|
: value |
|
); |
|
|
|
if (isNaN(num)) { |
|
return true; |
|
} |
|
|
|
|
|
|
|
if (opts.min !== null && num < opts.min && typeof opts.min !== 'string') { |
|
return true; |
|
} |
|
|
|
|
|
|
|
if (opts.max !== null && num > opts.max && typeof opts.max !== 'string') { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function consumeFunction(token, getNextToken) { |
|
let balanceCloseType = 0; |
|
let balanceStash = []; |
|
let length = 0; |
|
|
|
|
|
scan: |
|
do { |
|
switch (token.type) { |
|
case RightCurlyBracket: |
|
case RightParenthesis: |
|
case RightSquareBracket: |
|
if (token.type !== balanceCloseType) { |
|
break scan; |
|
} |
|
|
|
balanceCloseType = balanceStash.pop(); |
|
|
|
if (balanceStash.length === 0) { |
|
length++; |
|
break scan; |
|
} |
|
|
|
break; |
|
|
|
case FunctionToken: |
|
case LeftParenthesis: |
|
case LeftSquareBracket: |
|
case LeftCurlyBracket: |
|
balanceStash.push(balanceCloseType); |
|
balanceCloseType = balancePair.get(token.type); |
|
break; |
|
} |
|
|
|
length++; |
|
} while (token = getNextToken(length)); |
|
|
|
return length; |
|
} |
|
|
|
|
|
|
|
|
|
function calc(next) { |
|
return function(token, getNextToken, opts) { |
|
if (token === null) { |
|
return 0; |
|
} |
|
|
|
if (token.type === FunctionToken && 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 customIdent(token) { |
|
if (token === null || token.type !== Ident) { |
|
return 0; |
|
} |
|
|
|
const name = token.value.toLowerCase(); |
|
|
|
|
|
if (eqStrAny(name, cssWideKeywords)) { |
|
return 0; |
|
} |
|
|
|
|
|
if (eqStr(name, 'default')) { |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function customPropertyName(token) { |
|
|
|
if (token === null || token.type !== Ident) { |
|
return 0; |
|
} |
|
|
|
|
|
if (charCodeAt(token.value, 0) !== 0x002D || charCodeAt(token.value, 1) !== 0x002D) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hexColor(token) { |
|
if (token === null || token.type !== Hash) { |
|
return 0; |
|
} |
|
|
|
const length = token.value.length; |
|
|
|
|
|
if (length !== 4 && length !== 5 && length !== 7 && length !== 9) { |
|
return 0; |
|
} |
|
|
|
for (let i = 1; i < length; i++) { |
|
if (!isHexDigit(charCodeAt(token.value, i))) { |
|
return 0; |
|
} |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
function idSelector(token) { |
|
if (token === null || token.type !== Hash) { |
|
return 0; |
|
} |
|
|
|
if (!isIdentifierStart(charCodeAt(token.value, 1), charCodeAt(token.value, 2), charCodeAt(token.value, 3))) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
|
|
function declarationValue(token, getNextToken) { |
|
if (!token) { |
|
return 0; |
|
} |
|
|
|
let balanceCloseType = 0; |
|
let balanceStash = []; |
|
let length = 0; |
|
|
|
|
|
|
|
scan: |
|
do { |
|
switch (token.type) { |
|
|
|
case BadString: |
|
case BadUrl: |
|
break scan; |
|
|
|
|
|
case RightCurlyBracket: |
|
case RightParenthesis: |
|
case RightSquareBracket: |
|
if (token.type !== balanceCloseType) { |
|
break scan; |
|
} |
|
|
|
balanceCloseType = balanceStash.pop(); |
|
break; |
|
|
|
|
|
case Semicolon: |
|
if (balanceCloseType === 0) { |
|
break scan; |
|
} |
|
|
|
break; |
|
|
|
|
|
case Delim: |
|
if (balanceCloseType === 0 && token.value === '!') { |
|
break scan; |
|
} |
|
|
|
break; |
|
|
|
case FunctionToken: |
|
case LeftParenthesis: |
|
case LeftSquareBracket: |
|
case LeftCurlyBracket: |
|
balanceStash.push(balanceCloseType); |
|
balanceCloseType = balancePair.get(token.type); |
|
break; |
|
} |
|
|
|
length++; |
|
} while (token = getNextToken(length)); |
|
|
|
return length; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function anyValue(token, getNextToken) { |
|
if (!token) { |
|
return 0; |
|
} |
|
|
|
let balanceCloseType = 0; |
|
let balanceStash = []; |
|
let length = 0; |
|
|
|
|
|
|
|
scan: |
|
do { |
|
switch (token.type) { |
|
|
|
case BadString: |
|
case BadUrl: |
|
break scan; |
|
|
|
|
|
case RightCurlyBracket: |
|
case RightParenthesis: |
|
case RightSquareBracket: |
|
if (token.type !== balanceCloseType) { |
|
break scan; |
|
} |
|
|
|
balanceCloseType = balanceStash.pop(); |
|
break; |
|
|
|
case FunctionToken: |
|
case LeftParenthesis: |
|
case LeftSquareBracket: |
|
case LeftCurlyBracket: |
|
balanceStash.push(balanceCloseType); |
|
balanceCloseType = balancePair.get(token.type); |
|
break; |
|
} |
|
|
|
length++; |
|
} while (token = getNextToken(length)); |
|
|
|
return length; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function dimension(type) { |
|
if (type) { |
|
type = new Set(type); |
|
} |
|
|
|
return function(token, getNextToken, opts) { |
|
if (token === null || token.type !== Dimension) { |
|
return 0; |
|
} |
|
|
|
const numberEnd = consumeNumber(token.value, 0); |
|
|
|
|
|
if (type !== null) { |
|
|
|
const reverseSolidusOffset = token.value.indexOf('\\', numberEnd); |
|
const unit = reverseSolidusOffset === -1 || !isPostfixIeHack(token.value, reverseSolidusOffset) |
|
? token.value.substr(numberEnd) |
|
: token.value.substring(numberEnd, reverseSolidusOffset); |
|
|
|
if (type.has(unit.toLowerCase()) === false) { |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
if (outOfRange(opts, token.value, numberEnd)) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function percentage(token, getNextToken, opts) { |
|
|
|
if (token === null || token.type !== Percentage) { |
|
return 0; |
|
} |
|
|
|
|
|
if (outOfRange(opts, token.value, token.value.length - 1)) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function zero(next) { |
|
if (typeof next !== 'function') { |
|
next = function() { |
|
return 0; |
|
}; |
|
} |
|
|
|
return function(token, getNextToken, opts) { |
|
if (token !== null && token.type === NumberToken) { |
|
if (Number(token.value) === 0) { |
|
return 1; |
|
} |
|
} |
|
|
|
return next(token, getNextToken, opts); |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function number(token, getNextToken, opts) { |
|
if (token === null) { |
|
return 0; |
|
} |
|
|
|
const numberEnd = consumeNumber(token.value, 0); |
|
const isNumber = numberEnd === token.value.length; |
|
if (!isNumber && !isPostfixIeHack(token.value, numberEnd)) { |
|
return 0; |
|
} |
|
|
|
|
|
if (outOfRange(opts, token.value, numberEnd)) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
|
|
function integer(token, getNextToken, opts) { |
|
|
|
if (token === null || token.type !== NumberToken) { |
|
return 0; |
|
} |
|
|
|
|
|
let i = charCodeAt(token.value, 0) === 0x002B || |
|
charCodeAt(token.value, 0) === 0x002D ? 1 : 0; |
|
|
|
|
|
for (; i < token.value.length; i++) { |
|
if (!isDigit(charCodeAt(token.value, i))) { |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
if (outOfRange(opts, token.value, i)) { |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
export const tokenTypes = { |
|
'ident-token': tokenType(Ident), |
|
'function-token': tokenType(FunctionToken), |
|
'at-keyword-token': tokenType(AtKeyword), |
|
'hash-token': tokenType(Hash), |
|
'string-token': tokenType(StringToken), |
|
'bad-string-token': tokenType(BadString), |
|
'url-token': tokenType(Url), |
|
'bad-url-token': tokenType(BadUrl), |
|
'delim-token': tokenType(Delim), |
|
'number-token': tokenType(NumberToken), |
|
'percentage-token': tokenType(Percentage), |
|
'dimension-token': tokenType(Dimension), |
|
'whitespace-token': tokenType(WhiteSpace), |
|
'CDO-token': tokenType(CDO), |
|
'CDC-token': tokenType(CDC), |
|
'colon-token': tokenType(Colon), |
|
'semicolon-token': tokenType(Semicolon), |
|
'comma-token': tokenType(Comma), |
|
'[-token': tokenType(LeftSquareBracket), |
|
']-token': tokenType(RightSquareBracket), |
|
'(-token': tokenType(LeftParenthesis), |
|
')-token': tokenType(RightParenthesis), |
|
'{-token': tokenType(LeftCurlyBracket), |
|
'}-token': tokenType(RightCurlyBracket) |
|
}; |
|
|
|
|
|
export const productionTypes = { |
|
|
|
'string': tokenType(StringToken), |
|
'ident': tokenType(Ident), |
|
|
|
|
|
'percentage': calc(percentage), |
|
|
|
|
|
'zero': zero(), |
|
'number': calc(number), |
|
'integer': calc(integer), |
|
|
|
|
|
'custom-ident': customIdent, |
|
'custom-property-name': customPropertyName, |
|
'hex-color': hexColor, |
|
'id-selector': idSelector, |
|
'an-plus-b': anPlusB, |
|
'urange': urange, |
|
'declaration-value': declarationValue, |
|
'any-value': anyValue |
|
}; |
|
|
|
export const unitGroups = [ |
|
'length', |
|
'angle', |
|
'time', |
|
'frequency', |
|
'resolution', |
|
'flex', |
|
'decibel', |
|
'semitones' |
|
]; |
|
|
|
|
|
export function createDemensionTypes(units) { |
|
const { |
|
angle, |
|
decibel, |
|
frequency, |
|
flex, |
|
length, |
|
resolution, |
|
semitones, |
|
time |
|
} = units || {}; |
|
|
|
return { |
|
'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)) |
|
}; |
|
} |
|
|
|
export function createGenericTypes(units) { |
|
return { |
|
...tokenTypes, |
|
...productionTypes, |
|
...createDemensionTypes(units) |
|
}; |
|
}; |
|
|