blob: 1e604decf0f4a58e8afdb884ad23be9fd2e4cd90 [file] [log] [blame]
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
};