chore: port css parser to ts (#18134)
This commit is contained in:
parent
64e80f0460
commit
928f7c8766
|
|
@ -111,7 +111,7 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||||
}
|
}
|
||||||
|
|
||||||
function isClauseCombinator(p = pos) {
|
function isClauseCombinator(p = pos) {
|
||||||
return (tokens[p] instanceof css.DelimToken) && (['>', '+', '~'].includes(tokens[p].value));
|
return (tokens[p] instanceof css.DelimToken) && (['>', '+', '~'].includes(tokens[p].value as string));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSelectorClauseEnd(p = pos) {
|
function isSelectorClauseEnd(p = pos) {
|
||||||
|
|
@ -133,9 +133,9 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||||
function consumeArgument(): CSSFunctionArgument {
|
function consumeArgument(): CSSFunctionArgument {
|
||||||
skipWhitespace();
|
skipWhitespace();
|
||||||
if (isNumber())
|
if (isNumber())
|
||||||
return tokens[pos++].value;
|
return tokens[pos++].value!;
|
||||||
if (isString())
|
if (isString())
|
||||||
return tokens[pos++].value;
|
return tokens[pos++].value!;
|
||||||
return consumeComplexSelector();
|
return consumeComplexSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,15 +179,15 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||||
} else if (tokens[pos] instanceof css.ColonToken) {
|
} else if (tokens[pos] instanceof css.ColonToken) {
|
||||||
pos++;
|
pos++;
|
||||||
if (isIdent()) {
|
if (isIdent()) {
|
||||||
if (!customNames.has(tokens[pos].value.toLowerCase())) {
|
if (!customNames.has((tokens[pos].value as string).toLowerCase())) {
|
||||||
rawCSSString += ':' + tokens[pos++].toSource();
|
rawCSSString += ':' + tokens[pos++].toSource();
|
||||||
} else {
|
} else {
|
||||||
const name = tokens[pos++].value.toLowerCase();
|
const name = (tokens[pos++].value as string).toLowerCase();
|
||||||
functions.push({ name, args: [] });
|
functions.push({ name, args: [] });
|
||||||
names.add(name);
|
names.add(name);
|
||||||
}
|
}
|
||||||
} else if (tokens[pos] instanceof css.FunctionToken) {
|
} else if (tokens[pos] instanceof css.FunctionToken) {
|
||||||
const name = tokens[pos++].value.toLowerCase();
|
const name = (tokens[pos++].value as string).toLowerCase();
|
||||||
if (!customNames.has(name)) {
|
if (!customNames.has(name)) {
|
||||||
rawCSSString += `:${name}(${consumeBuiltinFunctionArguments()})`;
|
rawCSSString += `:${name}(${consumeBuiltinFunctionArguments()})`;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class CSSTokenInterface {
|
|
||||||
toSource(): string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tokenize(selector: string): CSSTokenInterface[];
|
|
||||||
|
|
||||||
export class IdentToken extends CSSTokenInterface {}
|
|
||||||
export class FunctionToken extends CSSTokenInterface {}
|
|
||||||
export class AtKeywordToken extends CSSTokenInterface {}
|
|
||||||
export class HashToken extends CSSTokenInterface {}
|
|
||||||
export class StringToken extends CSSTokenInterface {}
|
|
||||||
export class BadStringToken extends CSSTokenInterface {}
|
|
||||||
export class URLToken extends CSSTokenInterface {}
|
|
||||||
export class BadURLToken extends CSSTokenInterface {}
|
|
||||||
export class DelimToken extends CSSTokenInterface {}
|
|
||||||
export class NumberToken extends CSSTokenInterface {}
|
|
||||||
export class PercentageToken extends CSSTokenInterface {}
|
|
||||||
export class DimensionToken extends CSSTokenInterface {}
|
|
||||||
export class IncludeMatchToken extends CSSTokenInterface {}
|
|
||||||
export class DashMatchToken extends CSSTokenInterface {}
|
|
||||||
export class PrefixMatchToken extends CSSTokenInterface {}
|
|
||||||
export class SuffixMatchToken extends CSSTokenInterface {}
|
|
||||||
export class SubstringMatchToken extends CSSTokenInterface {}
|
|
||||||
export class ColumnToken extends CSSTokenInterface {}
|
|
||||||
export class WhitespaceToken extends CSSTokenInterface {}
|
|
||||||
export class CDOToken extends CSSTokenInterface {}
|
|
||||||
export class CDCToken extends CSSTokenInterface {}
|
|
||||||
export class ColonToken extends CSSTokenInterface {}
|
|
||||||
export class SemicolonToken extends CSSTokenInterface {}
|
|
||||||
export class CommaToken extends CSSTokenInterface {}
|
|
||||||
export class OpenParenToken extends CSSTokenInterface {}
|
|
||||||
export class CloseParenToken extends CSSTokenInterface {}
|
|
||||||
export class OpenSquareToken extends CSSTokenInterface {}
|
|
||||||
export class CloseSquareToken extends CSSTokenInterface {}
|
|
||||||
export class OpenCurlyToken extends CSSTokenInterface {}
|
|
||||||
export class CloseCurlyToken extends CSSTokenInterface {}
|
|
||||||
export class EOFToken extends CSSTokenInterface {}
|
|
||||||
|
|
@ -1,939 +0,0 @@
|
||||||
/*
|
|
||||||
* Original at https://github.com/tabatkins/parse-css
|
|
||||||
* licensed under http://creativecommons.org/publicdomain/zero/1.0/
|
|
||||||
*
|
|
||||||
* Modifications copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Changes from https://github.com/tabatkins/parse-css
|
|
||||||
// - Tabs are replaced with two spaces.
|
|
||||||
// - Universal Module Definition wrapper is removed.
|
|
||||||
// - Everything not related to tokenizing - below the first exports block - is removed.
|
|
||||||
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
|
|
||||||
var between = function (num, first, last) { return num >= first && num <= last; }
|
|
||||||
function digit(code) { return between(code, 0x30,0x39); }
|
|
||||||
function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); }
|
|
||||||
function uppercaseletter(code) { return between(code, 0x41,0x5a); }
|
|
||||||
function lowercaseletter(code) { return between(code, 0x61,0x7a); }
|
|
||||||
function letter(code) { return uppercaseletter(code) || lowercaseletter(code); }
|
|
||||||
function nonascii(code) { return code >= 0x80; }
|
|
||||||
function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; }
|
|
||||||
function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; }
|
|
||||||
function nonprintable(code) { return between(code, 0,8) || code == 0xb || between(code, 0xe,0x1f) || code == 0x7f; }
|
|
||||||
function newline(code) { return code == 0xa; }
|
|
||||||
function whitespace(code) { return newline(code) || code == 9 || code == 0x20; }
|
|
||||||
function badescape(code) { return newline(code) || isNaN(code); }
|
|
||||||
|
|
||||||
var maximumallowedcodepoint = 0x10ffff;
|
|
||||||
|
|
||||||
var InvalidCharacterError = function(message) {
|
|
||||||
this.message = message;
|
|
||||||
};
|
|
||||||
InvalidCharacterError.prototype = new Error;
|
|
||||||
InvalidCharacterError.prototype.name = 'InvalidCharacterError';
|
|
||||||
|
|
||||||
function preprocess(str) {
|
|
||||||
// Turn a string into an array of code points,
|
|
||||||
// following the preprocessing cleanup rules.
|
|
||||||
var codepoints = [];
|
|
||||||
for(var i = 0; i < str.length; i++) {
|
|
||||||
var code = str.charCodeAt(i);
|
|
||||||
if(code == 0xd && str.charCodeAt(i+1) == 0xa) {
|
|
||||||
code = 0xa; i++;
|
|
||||||
}
|
|
||||||
if(code == 0xd || code == 0xc) code = 0xa;
|
|
||||||
if(code == 0x0) code = 0xfffd;
|
|
||||||
if(between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i+1), 0xdc00, 0xdfff)) {
|
|
||||||
// Decode a surrogate pair into an astral codepoint.
|
|
||||||
var lead = code - 0xd800;
|
|
||||||
var trail = str.charCodeAt(i+1) - 0xdc00;
|
|
||||||
code = Math.pow(2, 16) + lead * Math.pow(2, 10) + trail;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
codepoints.push(code);
|
|
||||||
}
|
|
||||||
return codepoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringFromCode(code) {
|
|
||||||
if(code <= 0xffff) return String.fromCharCode(code);
|
|
||||||
// Otherwise, encode astral char as surrogate pair.
|
|
||||||
code -= Math.pow(2, 16);
|
|
||||||
var lead = Math.floor(code/Math.pow(2, 10)) + 0xd800;
|
|
||||||
var trail = code % Math.pow(2, 10) + 0xdc00;
|
|
||||||
return String.fromCharCode(lead) + String.fromCharCode(trail);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenize(str) {
|
|
||||||
str = preprocess(str);
|
|
||||||
var i = -1;
|
|
||||||
var tokens = [];
|
|
||||||
var code;
|
|
||||||
|
|
||||||
// Line number information.
|
|
||||||
var line = 0;
|
|
||||||
var column = 0;
|
|
||||||
// The only use of lastLineLength is in reconsume().
|
|
||||||
var lastLineLength = 0;
|
|
||||||
var incrLineno = function() {
|
|
||||||
line += 1;
|
|
||||||
lastLineLength = column;
|
|
||||||
column = 0;
|
|
||||||
};
|
|
||||||
var locStart = {line:line, column:column};
|
|
||||||
|
|
||||||
var codepoint = function(i) {
|
|
||||||
if(i >= str.length) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return str[i];
|
|
||||||
}
|
|
||||||
var next = function(num) {
|
|
||||||
if(num === undefined)
|
|
||||||
num = 1;
|
|
||||||
if(num > 3)
|
|
||||||
throw "Spec Error: no more than three codepoints of lookahead.";
|
|
||||||
return codepoint(i+num);
|
|
||||||
};
|
|
||||||
var consume = function(num) {
|
|
||||||
if(num === undefined)
|
|
||||||
num = 1;
|
|
||||||
i += num;
|
|
||||||
code = codepoint(i);
|
|
||||||
if(newline(code)) incrLineno();
|
|
||||||
else column += num;
|
|
||||||
//console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
var reconsume = function() {
|
|
||||||
i -= 1;
|
|
||||||
if (newline(code)) {
|
|
||||||
line -= 1;
|
|
||||||
column = lastLineLength;
|
|
||||||
} else {
|
|
||||||
column -= 1;
|
|
||||||
}
|
|
||||||
locStart.line = line;
|
|
||||||
locStart.column = column;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
var eof = function(codepoint) {
|
|
||||||
if(codepoint === undefined) codepoint = code;
|
|
||||||
return codepoint == -1;
|
|
||||||
};
|
|
||||||
var donothing = function() {};
|
|
||||||
var parseerror = function() { console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + ".");return true; };
|
|
||||||
|
|
||||||
var consumeAToken = function() {
|
|
||||||
consumeComments();
|
|
||||||
consume();
|
|
||||||
if(whitespace(code)) {
|
|
||||||
while(whitespace(next())) consume();
|
|
||||||
return new WhitespaceToken;
|
|
||||||
}
|
|
||||||
else if(code == 0x22) return consumeAStringToken();
|
|
||||||
else if(code == 0x23) {
|
|
||||||
if(namechar(next()) || areAValidEscape(next(1), next(2))) {
|
|
||||||
var token = new HashToken();
|
|
||||||
if(wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id";
|
|
||||||
token.value = consumeAName();
|
|
||||||
return token;
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x24) {
|
|
||||||
if(next() == 0x3d) {
|
|
||||||
consume();
|
|
||||||
return new SuffixMatchToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x27) return consumeAStringToken();
|
|
||||||
else if(code == 0x28) return new OpenParenToken();
|
|
||||||
else if(code == 0x29) return new CloseParenToken();
|
|
||||||
else if(code == 0x2a) {
|
|
||||||
if(next() == 0x3d) {
|
|
||||||
consume();
|
|
||||||
return new SubstringMatchToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x2b) {
|
|
||||||
if(startsWithANumber()) {
|
|
||||||
reconsume();
|
|
||||||
return consumeANumericToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x2c) return new CommaToken();
|
|
||||||
else if(code == 0x2d) {
|
|
||||||
if(startsWithANumber()) {
|
|
||||||
reconsume();
|
|
||||||
return consumeANumericToken();
|
|
||||||
} else if(next(1) == 0x2d && next(2) == 0x3e) {
|
|
||||||
consume(2);
|
|
||||||
return new CDCToken();
|
|
||||||
} else if(startsWithAnIdentifier()) {
|
|
||||||
reconsume();
|
|
||||||
return consumeAnIdentlikeToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x2e) {
|
|
||||||
if(startsWithANumber()) {
|
|
||||||
reconsume();
|
|
||||||
return consumeANumericToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x3a) return new ColonToken;
|
|
||||||
else if(code == 0x3b) return new SemicolonToken;
|
|
||||||
else if(code == 0x3c) {
|
|
||||||
if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) {
|
|
||||||
consume(3);
|
|
||||||
return new CDOToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x40) {
|
|
||||||
if(wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
|
||||||
return new AtKeywordToken(consumeAName());
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x5b) return new OpenSquareToken();
|
|
||||||
else if(code == 0x5c) {
|
|
||||||
if(startsWithAValidEscape()) {
|
|
||||||
reconsume();
|
|
||||||
return consumeAnIdentlikeToken();
|
|
||||||
} else {
|
|
||||||
parseerror();
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x5d) return new CloseSquareToken();
|
|
||||||
else if(code == 0x5e) {
|
|
||||||
if(next() == 0x3d) {
|
|
||||||
consume();
|
|
||||||
return new PrefixMatchToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x7b) return new OpenCurlyToken();
|
|
||||||
else if(code == 0x7c) {
|
|
||||||
if(next() == 0x3d) {
|
|
||||||
consume();
|
|
||||||
return new DashMatchToken();
|
|
||||||
} else if(next() == 0x7c) {
|
|
||||||
consume();
|
|
||||||
return new ColumnToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == 0x7d) return new CloseCurlyToken();
|
|
||||||
else if(code == 0x7e) {
|
|
||||||
if(next() == 0x3d) {
|
|
||||||
consume();
|
|
||||||
return new IncludeMatchToken();
|
|
||||||
} else {
|
|
||||||
return new DelimToken(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(digit(code)) {
|
|
||||||
reconsume();
|
|
||||||
return consumeANumericToken();
|
|
||||||
}
|
|
||||||
else if(namestartchar(code)) {
|
|
||||||
reconsume();
|
|
||||||
return consumeAnIdentlikeToken();
|
|
||||||
}
|
|
||||||
else if(eof()) return new EOFToken();
|
|
||||||
else return new DelimToken(code);
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeComments = function() {
|
|
||||||
while(next(1) == 0x2f && next(2) == 0x2a) {
|
|
||||||
consume(2);
|
|
||||||
while(true) {
|
|
||||||
consume();
|
|
||||||
if(code == 0x2a && next() == 0x2f) {
|
|
||||||
consume();
|
|
||||||
break;
|
|
||||||
} else if(eof()) {
|
|
||||||
parseerror();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeANumericToken = function() {
|
|
||||||
var num = consumeANumber();
|
|
||||||
if(wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
|
||||||
var token = new DimensionToken();
|
|
||||||
token.value = num.value;
|
|
||||||
token.repr = num.repr;
|
|
||||||
token.type = num.type;
|
|
||||||
token.unit = consumeAName();
|
|
||||||
return token;
|
|
||||||
} else if(next() == 0x25) {
|
|
||||||
consume();
|
|
||||||
var token = new PercentageToken();
|
|
||||||
token.value = num.value;
|
|
||||||
token.repr = num.repr;
|
|
||||||
return token;
|
|
||||||
} else {
|
|
||||||
var token = new NumberToken();
|
|
||||||
token.value = num.value;
|
|
||||||
token.repr = num.repr;
|
|
||||||
token.type = num.type;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeAnIdentlikeToken = function() {
|
|
||||||
var str = consumeAName();
|
|
||||||
if(str.toLowerCase() == "url" && next() == 0x28) {
|
|
||||||
consume();
|
|
||||||
while(whitespace(next(1)) && whitespace(next(2))) consume();
|
|
||||||
if(next() == 0x22 || next() == 0x27) {
|
|
||||||
return new FunctionToken(str);
|
|
||||||
} else if(whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) {
|
|
||||||
return new FunctionToken(str);
|
|
||||||
} else {
|
|
||||||
return consumeAURLToken();
|
|
||||||
}
|
|
||||||
} else if(next() == 0x28) {
|
|
||||||
consume();
|
|
||||||
return new FunctionToken(str);
|
|
||||||
} else {
|
|
||||||
return new IdentToken(str);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeAStringToken = function(endingCodePoint) {
|
|
||||||
if(endingCodePoint === undefined) endingCodePoint = code;
|
|
||||||
var string = "";
|
|
||||||
while(consume()) {
|
|
||||||
if(code == endingCodePoint || eof()) {
|
|
||||||
return new StringToken(string);
|
|
||||||
} else if(newline(code)) {
|
|
||||||
parseerror();
|
|
||||||
reconsume();
|
|
||||||
return new BadStringToken();
|
|
||||||
} else if(code == 0x5c) {
|
|
||||||
if(eof(next())) {
|
|
||||||
donothing();
|
|
||||||
} else if(newline(next())) {
|
|
||||||
consume();
|
|
||||||
} else {
|
|
||||||
string += stringFromCode(consumeEscape())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
string += stringFromCode(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeAURLToken = function() {
|
|
||||||
var token = new URLToken("");
|
|
||||||
while(whitespace(next())) consume();
|
|
||||||
if(eof(next())) return token;
|
|
||||||
while(consume()) {
|
|
||||||
if(code == 0x29 || eof()) {
|
|
||||||
return token;
|
|
||||||
} else if(whitespace(code)) {
|
|
||||||
while(whitespace(next())) consume();
|
|
||||||
if(next() == 0x29 || eof(next())) {
|
|
||||||
consume();
|
|
||||||
return token;
|
|
||||||
} else {
|
|
||||||
consumeTheRemnantsOfABadURL();
|
|
||||||
return new BadURLToken();
|
|
||||||
}
|
|
||||||
} else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) {
|
|
||||||
parseerror();
|
|
||||||
consumeTheRemnantsOfABadURL();
|
|
||||||
return new BadURLToken();
|
|
||||||
} else if(code == 0x5c) {
|
|
||||||
if(startsWithAValidEscape()) {
|
|
||||||
token.value += stringFromCode(consumeEscape());
|
|
||||||
} else {
|
|
||||||
parseerror();
|
|
||||||
consumeTheRemnantsOfABadURL();
|
|
||||||
return new BadURLToken();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
token.value += stringFromCode(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeEscape = function() {
|
|
||||||
// Assume the the current character is the \
|
|
||||||
// and the next code point is not a newline.
|
|
||||||
consume();
|
|
||||||
if(hexdigit(code)) {
|
|
||||||
// Consume 1-6 hex digits
|
|
||||||
var digits = [code];
|
|
||||||
for(var total = 0; total < 5; total++) {
|
|
||||||
if(hexdigit(next())) {
|
|
||||||
consume();
|
|
||||||
digits.push(code);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(whitespace(next())) consume();
|
|
||||||
var value = parseInt(digits.map(function(x){return String.fromCharCode(x);}).join(''), 16);
|
|
||||||
if( value > maximumallowedcodepoint ) value = 0xfffd;
|
|
||||||
return value;
|
|
||||||
} else if(eof()) {
|
|
||||||
return 0xfffd;
|
|
||||||
} else {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var areAValidEscape = function(c1, c2) {
|
|
||||||
if(c1 != 0x5c) return false;
|
|
||||||
if(newline(c2)) return false;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
var startsWithAValidEscape = function() {
|
|
||||||
return areAValidEscape(code, next());
|
|
||||||
};
|
|
||||||
|
|
||||||
var wouldStartAnIdentifier = function(c1, c2, c3) {
|
|
||||||
if(c1 == 0x2d) {
|
|
||||||
return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3);
|
|
||||||
} else if(namestartchar(c1)) {
|
|
||||||
return true;
|
|
||||||
} else if(c1 == 0x5c) {
|
|
||||||
return areAValidEscape(c1, c2);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var startsWithAnIdentifier = function() {
|
|
||||||
return wouldStartAnIdentifier(code, next(1), next(2));
|
|
||||||
};
|
|
||||||
|
|
||||||
var wouldStartANumber = function(c1, c2, c3) {
|
|
||||||
if(c1 == 0x2b || c1 == 0x2d) {
|
|
||||||
if(digit(c2)) return true;
|
|
||||||
if(c2 == 0x2e && digit(c3)) return true;
|
|
||||||
return false;
|
|
||||||
} else if(c1 == 0x2e) {
|
|
||||||
if(digit(c2)) return true;
|
|
||||||
return false;
|
|
||||||
} else if(digit(c1)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var startsWithANumber = function() {
|
|
||||||
return wouldStartANumber(code, next(1), next(2));
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeAName = function() {
|
|
||||||
var result = "";
|
|
||||||
while(consume()) {
|
|
||||||
if(namechar(code)) {
|
|
||||||
result += stringFromCode(code);
|
|
||||||
} else if(startsWithAValidEscape()) {
|
|
||||||
result += stringFromCode(consumeEscape());
|
|
||||||
} else {
|
|
||||||
reconsume();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeANumber = function() {
|
|
||||||
var repr = [];
|
|
||||||
var type = "integer";
|
|
||||||
if(next() == 0x2b || next() == 0x2d) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
}
|
|
||||||
while(digit(next())) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
}
|
|
||||||
if(next(1) == 0x2e && digit(next(2))) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
type = "number";
|
|
||||||
while(digit(next())) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var c1 = next(1), c2 = next(2), c3 = next(3);
|
|
||||||
if((c1 == 0x45 || c1 == 0x65) && digit(c2)) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
type = "number";
|
|
||||||
while(digit(next())) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
}
|
|
||||||
} else if((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
type = "number";
|
|
||||||
while(digit(next())) {
|
|
||||||
consume();
|
|
||||||
repr += stringFromCode(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var value = convertAStringToANumber(repr);
|
|
||||||
return {type:type, value:value, repr:repr};
|
|
||||||
};
|
|
||||||
|
|
||||||
var convertAStringToANumber = function(string) {
|
|
||||||
// CSS's number rules are identical to JS, afaik.
|
|
||||||
return +string;
|
|
||||||
};
|
|
||||||
|
|
||||||
var consumeTheRemnantsOfABadURL = function() {
|
|
||||||
while(consume()) {
|
|
||||||
if(code == 0x29 || eof()) {
|
|
||||||
return;
|
|
||||||
} else if(startsWithAValidEscape()) {
|
|
||||||
consumeEscape();
|
|
||||||
donothing();
|
|
||||||
} else {
|
|
||||||
donothing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var iterationCount = 0;
|
|
||||||
while(!eof(next())) {
|
|
||||||
tokens.push(consumeAToken());
|
|
||||||
iterationCount++;
|
|
||||||
if(iterationCount > str.length*2) return "I'm infinite-looping!";
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CSSParserToken() { throw "Abstract Base Class"; }
|
|
||||||
CSSParserToken.prototype.toJSON = function() {
|
|
||||||
return {token: this.tokenType};
|
|
||||||
}
|
|
||||||
CSSParserToken.prototype.toString = function() { return this.tokenType; }
|
|
||||||
CSSParserToken.prototype.toSource = function() { return ''+this; }
|
|
||||||
|
|
||||||
function BadStringToken() { return this; }
|
|
||||||
BadStringToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
BadStringToken.prototype.tokenType = "BADSTRING";
|
|
||||||
|
|
||||||
function BadURLToken() { return this; }
|
|
||||||
BadURLToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
BadURLToken.prototype.tokenType = "BADURL";
|
|
||||||
|
|
||||||
function WhitespaceToken() { return this; }
|
|
||||||
WhitespaceToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
WhitespaceToken.prototype.tokenType = "WHITESPACE";
|
|
||||||
WhitespaceToken.prototype.toString = function() { return "WS"; }
|
|
||||||
WhitespaceToken.prototype.toSource = function() { return " "; }
|
|
||||||
|
|
||||||
function CDOToken() { return this; }
|
|
||||||
CDOToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
CDOToken.prototype.tokenType = "CDO";
|
|
||||||
CDOToken.prototype.toSource = function() { return "<!--"; }
|
|
||||||
|
|
||||||
function CDCToken() { return this; }
|
|
||||||
CDCToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
CDCToken.prototype.tokenType = "CDC";
|
|
||||||
CDCToken.prototype.toSource = function() { return "-->"; }
|
|
||||||
|
|
||||||
function ColonToken() { return this; }
|
|
||||||
ColonToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
ColonToken.prototype.tokenType = ":";
|
|
||||||
|
|
||||||
function SemicolonToken() { return this; }
|
|
||||||
SemicolonToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
SemicolonToken.prototype.tokenType = ";";
|
|
||||||
|
|
||||||
function CommaToken() { return this; }
|
|
||||||
CommaToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
CommaToken.prototype.tokenType = ",";
|
|
||||||
|
|
||||||
function GroupingToken() { throw "Abstract Base Class"; }
|
|
||||||
GroupingToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
|
|
||||||
function OpenCurlyToken() { this.value = "{"; this.mirror = "}"; return this; }
|
|
||||||
OpenCurlyToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
OpenCurlyToken.prototype.tokenType = "{";
|
|
||||||
|
|
||||||
function CloseCurlyToken() { this.value = "}"; this.mirror = "{"; return this; }
|
|
||||||
CloseCurlyToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
CloseCurlyToken.prototype.tokenType = "}";
|
|
||||||
|
|
||||||
function OpenSquareToken() { this.value = "["; this.mirror = "]"; return this; }
|
|
||||||
OpenSquareToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
OpenSquareToken.prototype.tokenType = "[";
|
|
||||||
|
|
||||||
function CloseSquareToken() { this.value = "]"; this.mirror = "["; return this; }
|
|
||||||
CloseSquareToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
CloseSquareToken.prototype.tokenType = "]";
|
|
||||||
|
|
||||||
function OpenParenToken() { this.value = "("; this.mirror = ")"; return this; }
|
|
||||||
OpenParenToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
OpenParenToken.prototype.tokenType = "(";
|
|
||||||
|
|
||||||
function CloseParenToken() { this.value = ")"; this.mirror = "("; return this; }
|
|
||||||
CloseParenToken.prototype = Object.create(GroupingToken.prototype);
|
|
||||||
CloseParenToken.prototype.tokenType = ")";
|
|
||||||
|
|
||||||
function IncludeMatchToken() { return this; }
|
|
||||||
IncludeMatchToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
IncludeMatchToken.prototype.tokenType = "~=";
|
|
||||||
|
|
||||||
function DashMatchToken() { return this; }
|
|
||||||
DashMatchToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
DashMatchToken.prototype.tokenType = "|=";
|
|
||||||
|
|
||||||
function PrefixMatchToken() { return this; }
|
|
||||||
PrefixMatchToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
PrefixMatchToken.prototype.tokenType = "^=";
|
|
||||||
|
|
||||||
function SuffixMatchToken() { return this; }
|
|
||||||
SuffixMatchToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
SuffixMatchToken.prototype.tokenType = "$=";
|
|
||||||
|
|
||||||
function SubstringMatchToken() { return this; }
|
|
||||||
SubstringMatchToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
SubstringMatchToken.prototype.tokenType = "*=";
|
|
||||||
|
|
||||||
function ColumnToken() { return this; }
|
|
||||||
ColumnToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
ColumnToken.prototype.tokenType = "||";
|
|
||||||
|
|
||||||
function EOFToken() { return this; }
|
|
||||||
EOFToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
EOFToken.prototype.tokenType = "EOF";
|
|
||||||
EOFToken.prototype.toSource = function() { return ""; }
|
|
||||||
|
|
||||||
function DelimToken(code) {
|
|
||||||
this.value = stringFromCode(code);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
DelimToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
DelimToken.prototype.tokenType = "DELIM";
|
|
||||||
DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; }
|
|
||||||
DelimToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
DelimToken.prototype.toSource = function() {
|
|
||||||
if(this.value == "\\")
|
|
||||||
return "\\\n";
|
|
||||||
else
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function StringValuedToken() { throw "Abstract Base Class"; }
|
|
||||||
StringValuedToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
StringValuedToken.prototype.ASCIIMatch = function(str) {
|
|
||||||
return this.value.toLowerCase() == str.toLowerCase();
|
|
||||||
}
|
|
||||||
StringValuedToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
function IdentToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
}
|
|
||||||
IdentToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
IdentToken.prototype.tokenType = "IDENT";
|
|
||||||
IdentToken.prototype.toString = function() { return "IDENT("+this.value+")"; }
|
|
||||||
IdentToken.prototype.toSource = function() {
|
|
||||||
return escapeIdent(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FunctionToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
this.mirror = ")";
|
|
||||||
}
|
|
||||||
FunctionToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
FunctionToken.prototype.tokenType = "FUNCTION";
|
|
||||||
FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; }
|
|
||||||
FunctionToken.prototype.toSource = function() {
|
|
||||||
return escapeIdent(this.value) + "(";
|
|
||||||
}
|
|
||||||
|
|
||||||
function AtKeywordToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
}
|
|
||||||
AtKeywordToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
AtKeywordToken.prototype.tokenType = "AT-KEYWORD";
|
|
||||||
AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; }
|
|
||||||
AtKeywordToken.prototype.toSource = function() {
|
|
||||||
return "@" + escapeIdent(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function HashToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
this.type = "unrestricted";
|
|
||||||
}
|
|
||||||
HashToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
HashToken.prototype.tokenType = "HASH";
|
|
||||||
HashToken.prototype.toString = function() { return "HASH("+this.value+")"; }
|
|
||||||
HashToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
json.type = this.type;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
HashToken.prototype.toSource = function() {
|
|
||||||
if(this.type == "id") {
|
|
||||||
return "#" + escapeIdent(this.value);
|
|
||||||
} else {
|
|
||||||
return "#" + escapeHash(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function StringToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
}
|
|
||||||
StringToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
StringToken.prototype.tokenType = "STRING";
|
|
||||||
StringToken.prototype.toString = function() {
|
|
||||||
return '"' + escapeString(this.value) + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
function URLToken(val) {
|
|
||||||
this.value = val;
|
|
||||||
}
|
|
||||||
URLToken.prototype = Object.create(StringValuedToken.prototype);
|
|
||||||
URLToken.prototype.tokenType = "URL";
|
|
||||||
URLToken.prototype.toString = function() { return "URL("+this.value+")"; }
|
|
||||||
URLToken.prototype.toSource = function() {
|
|
||||||
return 'url("' + escapeString(this.value) + '")';
|
|
||||||
}
|
|
||||||
|
|
||||||
function NumberToken() {
|
|
||||||
this.value = null;
|
|
||||||
this.type = "integer";
|
|
||||||
this.repr = "";
|
|
||||||
}
|
|
||||||
NumberToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
NumberToken.prototype.tokenType = "NUMBER";
|
|
||||||
NumberToken.prototype.toString = function() {
|
|
||||||
if(this.type == "integer")
|
|
||||||
return "INT("+this.value+")";
|
|
||||||
return "NUMBER("+this.value+")";
|
|
||||||
}
|
|
||||||
NumberToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
json.type = this.type;
|
|
||||||
json.repr = this.repr;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
NumberToken.prototype.toSource = function() { return this.repr; };
|
|
||||||
|
|
||||||
function PercentageToken() {
|
|
||||||
this.value = null;
|
|
||||||
this.repr = "";
|
|
||||||
}
|
|
||||||
PercentageToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
PercentageToken.prototype.tokenType = "PERCENTAGE";
|
|
||||||
PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; }
|
|
||||||
PercentageToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
json.repr = this.repr;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
PercentageToken.prototype.toSource = function() { return this.repr + "%"; }
|
|
||||||
|
|
||||||
function DimensionToken() {
|
|
||||||
this.value = null;
|
|
||||||
this.type = "integer";
|
|
||||||
this.repr = "";
|
|
||||||
this.unit = "";
|
|
||||||
}
|
|
||||||
DimensionToken.prototype = Object.create(CSSParserToken.prototype);
|
|
||||||
DimensionToken.prototype.tokenType = "DIMENSION";
|
|
||||||
DimensionToken.prototype.toString = function() { return "DIM("+this.value+","+this.unit+")"; }
|
|
||||||
DimensionToken.prototype.toJSON = function() {
|
|
||||||
var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
|
||||||
json.value = this.value;
|
|
||||||
json.type = this.type;
|
|
||||||
json.repr = this.repr;
|
|
||||||
json.unit = this.unit;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
DimensionToken.prototype.toSource = function() {
|
|
||||||
var source = this.repr;
|
|
||||||
var unit = escapeIdent(this.unit);
|
|
||||||
if(unit[0].toLowerCase() == "e" && (unit[1] == "-" || between(unit.charCodeAt(1), 0x30, 0x39))) {
|
|
||||||
// Unit is ambiguous with scinot
|
|
||||||
// Remove the leading "e", replace with escape.
|
|
||||||
unit = "\\65 " + unit.slice(1, unit.length);
|
|
||||||
}
|
|
||||||
return source+unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeIdent(string) {
|
|
||||||
string = ''+string;
|
|
||||||
var result = '';
|
|
||||||
var firstcode = string.charCodeAt(0);
|
|
||||||
for(var i = 0; i < string.length; i++) {
|
|
||||||
var code = string.charCodeAt(i);
|
|
||||||
if(code == 0x0) {
|
|
||||||
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
|
||||||
between(code, 0x1, 0x1f) || code == 0x7f ||
|
|
||||||
(i == 0 && between(code, 0x30, 0x39)) ||
|
|
||||||
(i == 1 && between(code, 0x30, 0x39) && firstcode == 0x2d)
|
|
||||||
) {
|
|
||||||
result += '\\' + code.toString(16) + ' ';
|
|
||||||
} else if(
|
|
||||||
code >= 0x80 ||
|
|
||||||
code == 0x2d ||
|
|
||||||
code == 0x5f ||
|
|
||||||
between(code, 0x30, 0x39) ||
|
|
||||||
between(code, 0x41, 0x5a) ||
|
|
||||||
between(code, 0x61, 0x7a)
|
|
||||||
) {
|
|
||||||
result += string[i];
|
|
||||||
} else {
|
|
||||||
result += '\\' + string[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHash(string) {
|
|
||||||
// Escapes the contents of "unrestricted"-type hash tokens.
|
|
||||||
// Won't preserve the ID-ness of "id"-type hash tokens;
|
|
||||||
// use escapeIdent() for that.
|
|
||||||
string = ''+string;
|
|
||||||
var result = '';
|
|
||||||
var firstcode = string.charCodeAt(0);
|
|
||||||
for(var i = 0; i < string.length; i++) {
|
|
||||||
var code = string.charCodeAt(i);
|
|
||||||
if(code == 0x0) {
|
|
||||||
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
|
||||||
code >= 0x80 ||
|
|
||||||
code == 0x2d ||
|
|
||||||
code == 0x5f ||
|
|
||||||
between(code, 0x30, 0x39) ||
|
|
||||||
between(code, 0x41, 0x5a) ||
|
|
||||||
between(code, 0x61, 0x7a)
|
|
||||||
) {
|
|
||||||
result += string[i];
|
|
||||||
} else {
|
|
||||||
result += '\\' + code.toString(16) + ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeString(string) {
|
|
||||||
string = ''+string;
|
|
||||||
var result = '';
|
|
||||||
for(var i = 0; i < string.length; i++) {
|
|
||||||
var code = string.charCodeAt(i);
|
|
||||||
|
|
||||||
if(code == 0x0) {
|
|
||||||
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(between(code, 0x1, 0x1f) || code == 0x7f) {
|
|
||||||
result += '\\' + code.toString(16) + ' ';
|
|
||||||
} else if(code == 0x22 || code == 0x5c) {
|
|
||||||
result += '\\' + string[i];
|
|
||||||
} else {
|
|
||||||
result += string[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exportation.
|
|
||||||
exports.tokenize = tokenize;
|
|
||||||
exports.IdentToken = IdentToken;
|
|
||||||
exports.FunctionToken = FunctionToken;
|
|
||||||
exports.AtKeywordToken = AtKeywordToken;
|
|
||||||
exports.HashToken = HashToken;
|
|
||||||
exports.StringToken = StringToken;
|
|
||||||
exports.BadStringToken = BadStringToken;
|
|
||||||
exports.URLToken = URLToken;
|
|
||||||
exports.BadURLToken = BadURLToken;
|
|
||||||
exports.DelimToken = DelimToken;
|
|
||||||
exports.NumberToken = NumberToken;
|
|
||||||
exports.PercentageToken = PercentageToken;
|
|
||||||
exports.DimensionToken = DimensionToken;
|
|
||||||
exports.IncludeMatchToken = IncludeMatchToken;
|
|
||||||
exports.DashMatchToken = DashMatchToken;
|
|
||||||
exports.PrefixMatchToken = PrefixMatchToken;
|
|
||||||
exports.SuffixMatchToken = SuffixMatchToken;
|
|
||||||
exports.SubstringMatchToken = SubstringMatchToken;
|
|
||||||
exports.ColumnToken = ColumnToken;
|
|
||||||
exports.WhitespaceToken = WhitespaceToken;
|
|
||||||
exports.CDOToken = CDOToken;
|
|
||||||
exports.CDCToken = CDCToken;
|
|
||||||
exports.ColonToken = ColonToken;
|
|
||||||
exports.SemicolonToken = SemicolonToken;
|
|
||||||
exports.CommaToken = CommaToken;
|
|
||||||
exports.OpenParenToken = OpenParenToken;
|
|
||||||
exports.CloseParenToken = CloseParenToken;
|
|
||||||
exports.OpenSquareToken = OpenSquareToken;
|
|
||||||
exports.CloseSquareToken = CloseSquareToken;
|
|
||||||
exports.OpenCurlyToken = OpenCurlyToken;
|
|
||||||
exports.CloseCurlyToken = CloseCurlyToken;
|
|
||||||
exports.EOFToken = EOFToken;
|
|
||||||
exports.CSSParserToken = CSSParserToken;
|
|
||||||
exports.GroupingToken = GroupingToken;
|
|
||||||
|
|
||||||
945
packages/playwright-core/src/server/isomorphic/cssTokenizer.ts
Normal file
945
packages/playwright-core/src/server/isomorphic/cssTokenizer.ts
Normal file
|
|
@ -0,0 +1,945 @@
|
||||||
|
/* eslint-disable notice/notice */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The code in this file is licensed under the CC0 license.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
* It is free to use for any purpose. No attribution, permission, or reproduction of this license is required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Original at https://github.com/tabatkins/parse-css
|
||||||
|
// Changes:
|
||||||
|
// - JS is replaced with TS.
|
||||||
|
// - Universal Module Definition wrapper is removed.
|
||||||
|
// - Everything not related to tokenizing - below the first exports block - is removed.
|
||||||
|
|
||||||
|
export interface CSSTokenInterface {
|
||||||
|
toSource(): string;
|
||||||
|
value: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const between = function(num: number, first: number, last: number) { return num >= first && num <= last; };
|
||||||
|
function digit(code: number) { return between(code, 0x30, 0x39); }
|
||||||
|
function hexdigit(code: number) { return digit(code) || between(code, 0x41, 0x46) || between(code, 0x61, 0x66); }
|
||||||
|
function uppercaseletter(code: number) { return between(code, 0x41, 0x5a); }
|
||||||
|
function lowercaseletter(code: number) { return between(code, 0x61, 0x7a); }
|
||||||
|
function letter(code: number) { return uppercaseletter(code) || lowercaseletter(code); }
|
||||||
|
function nonascii(code: number) { return code >= 0x80; }
|
||||||
|
function namestartchar(code: number) { return letter(code) || nonascii(code) || code === 0x5f; }
|
||||||
|
function namechar(code: number) { return namestartchar(code) || digit(code) || code === 0x2d; }
|
||||||
|
function nonprintable(code: number) { return between(code, 0, 8) || code === 0xb || between(code, 0xe, 0x1f) || code === 0x7f; }
|
||||||
|
function newline(code: number) { return code === 0xa; }
|
||||||
|
function whitespace(code: number) { return newline(code) || code === 9 || code === 0x20; }
|
||||||
|
|
||||||
|
const maximumallowedcodepoint = 0x10ffff;
|
||||||
|
|
||||||
|
export class InvalidCharacterError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'InvalidCharacterError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function preprocess(str: string): number[] {
|
||||||
|
// Turn a string into an array of code points,
|
||||||
|
// following the preprocessing cleanup rules.
|
||||||
|
const codepoints = [];
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
let code = str.charCodeAt(i);
|
||||||
|
if (code === 0xd && str.charCodeAt(i + 1) === 0xa) {
|
||||||
|
code = 0xa; i++;
|
||||||
|
}
|
||||||
|
if (code === 0xd || code === 0xc) code = 0xa;
|
||||||
|
if (code === 0x0) code = 0xfffd;
|
||||||
|
if (between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i + 1), 0xdc00, 0xdfff)) {
|
||||||
|
// Decode a surrogate pair into an astral codepoint.
|
||||||
|
const lead = code - 0xd800;
|
||||||
|
const trail = str.charCodeAt(i + 1) - 0xdc00;
|
||||||
|
code = Math.pow(2, 16) + lead * Math.pow(2, 10) + trail;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
codepoints.push(code);
|
||||||
|
}
|
||||||
|
return codepoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringFromCode(code: number) {
|
||||||
|
if (code <= 0xffff) return String.fromCharCode(code);
|
||||||
|
// Otherwise, encode astral char as surrogate pair.
|
||||||
|
code -= Math.pow(2, 16);
|
||||||
|
const lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
|
||||||
|
const trail = code % Math.pow(2, 10) + 0xdc00;
|
||||||
|
return String.fromCharCode(lead) + String.fromCharCode(trail);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
|
const str = preprocess(str1);
|
||||||
|
let i = -1;
|
||||||
|
const tokens: CSSTokenInterface[] = [];
|
||||||
|
let code: number;
|
||||||
|
|
||||||
|
// Line number information.
|
||||||
|
let line = 0;
|
||||||
|
let column = 0;
|
||||||
|
// The only use of lastLineLength is in reconsume().
|
||||||
|
let lastLineLength = 0;
|
||||||
|
const incrLineno = function() {
|
||||||
|
line += 1;
|
||||||
|
lastLineLength = column;
|
||||||
|
column = 0;
|
||||||
|
};
|
||||||
|
const locStart = { line: line, column: column };
|
||||||
|
|
||||||
|
const codepoint = function(i: number): number {
|
||||||
|
if (i >= str.length)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return str[i];
|
||||||
|
};
|
||||||
|
const next = function(num?: number) {
|
||||||
|
if (num === undefined)
|
||||||
|
num = 1;
|
||||||
|
if (num > 3)
|
||||||
|
throw 'Spec Error: no more than three codepoints of lookahead.';
|
||||||
|
return codepoint(i + num);
|
||||||
|
};
|
||||||
|
const consume = function(num?: number): boolean {
|
||||||
|
if (num === undefined)
|
||||||
|
num = 1;
|
||||||
|
i += num;
|
||||||
|
code = codepoint(i);
|
||||||
|
if (newline(code)) incrLineno();
|
||||||
|
else column += num;
|
||||||
|
// console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const reconsume = function() {
|
||||||
|
i -= 1;
|
||||||
|
if (newline(code)) {
|
||||||
|
line -= 1;
|
||||||
|
column = lastLineLength;
|
||||||
|
} else {
|
||||||
|
column -= 1;
|
||||||
|
}
|
||||||
|
locStart.line = line;
|
||||||
|
locStart.column = column;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const eof = function(codepoint?: number): boolean {
|
||||||
|
if (codepoint === undefined) codepoint = code;
|
||||||
|
return codepoint === -1;
|
||||||
|
};
|
||||||
|
const donothing = function() { };
|
||||||
|
const parseerror = function() {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Parse error at index ' + i + ', processing codepoint 0x' + code.toString(16) + '.'); return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeAToken = function(): CSSTokenInterface {
|
||||||
|
consumeComments();
|
||||||
|
consume();
|
||||||
|
if (whitespace(code)) {
|
||||||
|
while (whitespace(next())) consume();
|
||||||
|
return new WhitespaceToken();
|
||||||
|
} else if (code === 0x22) {return consumeAStringToken();} else if (code === 0x23) {
|
||||||
|
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
||||||
|
const token = new HashToken('');
|
||||||
|
if (wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = 'id';
|
||||||
|
token.value = consumeAName();
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x24) {
|
||||||
|
if (next() === 0x3d) {
|
||||||
|
consume();
|
||||||
|
return new SuffixMatchToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x27) {return consumeAStringToken();} else if (code === 0x28) {return new OpenParenToken();} else if (code === 0x29) {return new CloseParenToken();} else if (code === 0x2a) {
|
||||||
|
if (next() === 0x3d) {
|
||||||
|
consume();
|
||||||
|
return new SubstringMatchToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x2b) {
|
||||||
|
if (startsWithANumber()) {
|
||||||
|
reconsume();
|
||||||
|
return consumeANumericToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x2c) {return new CommaToken();} else if (code === 0x2d) {
|
||||||
|
if (startsWithANumber()) {
|
||||||
|
reconsume();
|
||||||
|
return consumeANumericToken();
|
||||||
|
} else if (next(1) === 0x2d && next(2) === 0x3e) {
|
||||||
|
consume(2);
|
||||||
|
return new CDCToken();
|
||||||
|
} else if (startsWithAnIdentifier()) {
|
||||||
|
reconsume();
|
||||||
|
return consumeAnIdentlikeToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x2e) {
|
||||||
|
if (startsWithANumber()) {
|
||||||
|
reconsume();
|
||||||
|
return consumeANumericToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x3a) {return new ColonToken();} else if (code === 0x3b) {return new SemicolonToken();} else if (code === 0x3c) {
|
||||||
|
if (next(1) === 0x21 && next(2) === 0x2d && next(3) === 0x2d) {
|
||||||
|
consume(3);
|
||||||
|
return new CDOToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x40) {
|
||||||
|
if (wouldStartAnIdentifier(next(1), next(2), next(3)))
|
||||||
|
return new AtKeywordToken(consumeAName());
|
||||||
|
else
|
||||||
|
return new DelimToken(code);
|
||||||
|
|
||||||
|
} else if (code === 0x5b) {return new OpenSquareToken();} else if (code === 0x5c) {
|
||||||
|
if (startsWithAValidEscape()) {
|
||||||
|
reconsume();
|
||||||
|
return consumeAnIdentlikeToken();
|
||||||
|
} else {
|
||||||
|
parseerror();
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x5d) {return new CloseSquareToken();} else if (code === 0x5e) {
|
||||||
|
if (next() === 0x3d) {
|
||||||
|
consume();
|
||||||
|
return new PrefixMatchToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x7b) {return new OpenCurlyToken();} else if (code === 0x7c) {
|
||||||
|
if (next() === 0x3d) {
|
||||||
|
consume();
|
||||||
|
return new DashMatchToken();
|
||||||
|
} else if (next() === 0x7c) {
|
||||||
|
consume();
|
||||||
|
return new ColumnToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (code === 0x7d) {return new CloseCurlyToken();} else if (code === 0x7e) {
|
||||||
|
if (next() === 0x3d) {
|
||||||
|
consume();
|
||||||
|
return new IncludeMatchToken();
|
||||||
|
} else {
|
||||||
|
return new DelimToken(code);
|
||||||
|
}
|
||||||
|
} else if (digit(code)) {
|
||||||
|
reconsume();
|
||||||
|
return consumeANumericToken();
|
||||||
|
} else if (namestartchar(code)) {
|
||||||
|
reconsume();
|
||||||
|
return consumeAnIdentlikeToken();
|
||||||
|
} else if (eof()) {return new EOFToken();} else {return new DelimToken(code);}
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeComments = function() {
|
||||||
|
while (next(1) === 0x2f && next(2) === 0x2a) {
|
||||||
|
consume(2);
|
||||||
|
while (true) {
|
||||||
|
consume();
|
||||||
|
if (code === 0x2a && next() === 0x2f) {
|
||||||
|
consume();
|
||||||
|
break;
|
||||||
|
} else if (eof()) {
|
||||||
|
parseerror();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeANumericToken = function() {
|
||||||
|
const num = consumeANumber();
|
||||||
|
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
||||||
|
const token = new DimensionToken();
|
||||||
|
token.value = num.value;
|
||||||
|
token.repr = num.repr;
|
||||||
|
token.type = num.type;
|
||||||
|
token.unit = consumeAName();
|
||||||
|
return token;
|
||||||
|
} else if (next() === 0x25) {
|
||||||
|
consume();
|
||||||
|
const token = new PercentageToken();
|
||||||
|
token.value = num.value;
|
||||||
|
token.repr = num.repr;
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
const token = new NumberToken();
|
||||||
|
token.value = num.value;
|
||||||
|
token.repr = num.repr;
|
||||||
|
token.type = num.type;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeAnIdentlikeToken = function(): CSSTokenInterface {
|
||||||
|
const str = consumeAName();
|
||||||
|
if (str.toLowerCase() === 'url' && next() === 0x28) {
|
||||||
|
consume();
|
||||||
|
while (whitespace(next(1)) && whitespace(next(2))) consume();
|
||||||
|
if (next() === 0x22 || next() === 0x27)
|
||||||
|
return new FunctionToken(str);
|
||||||
|
else if (whitespace(next()) && (next(2) === 0x22 || next(2) === 0x27))
|
||||||
|
return new FunctionToken(str);
|
||||||
|
else
|
||||||
|
return consumeAURLToken();
|
||||||
|
|
||||||
|
} else if (next() === 0x28) {
|
||||||
|
consume();
|
||||||
|
return new FunctionToken(str);
|
||||||
|
} else {
|
||||||
|
return new IdentToken(str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeAStringToken = function(endingCodePoint?: number): CSSParserToken {
|
||||||
|
if (endingCodePoint === undefined) endingCodePoint = code;
|
||||||
|
let string = '';
|
||||||
|
while (consume()) {
|
||||||
|
if (code === endingCodePoint || eof()) {
|
||||||
|
return new StringToken(string);
|
||||||
|
} else if (newline(code)) {
|
||||||
|
parseerror();
|
||||||
|
reconsume();
|
||||||
|
return new BadStringToken();
|
||||||
|
} else if (code === 0x5c) {
|
||||||
|
if (eof(next()))
|
||||||
|
donothing();
|
||||||
|
else if (newline(next()))
|
||||||
|
consume();
|
||||||
|
else
|
||||||
|
string += stringFromCode(consumeEscape());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
string += stringFromCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Internal error');
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeAURLToken = function(): CSSTokenInterface {
|
||||||
|
const token = new URLToken('');
|
||||||
|
while (whitespace(next())) consume();
|
||||||
|
if (eof(next())) return token;
|
||||||
|
while (consume()) {
|
||||||
|
if (code === 0x29 || eof()) {
|
||||||
|
return token;
|
||||||
|
} else if (whitespace(code)) {
|
||||||
|
while (whitespace(next())) consume();
|
||||||
|
if (next() === 0x29 || eof(next())) {
|
||||||
|
consume();
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
consumeTheRemnantsOfABadURL();
|
||||||
|
return new BadURLToken();
|
||||||
|
}
|
||||||
|
} else if (code === 0x22 || code === 0x27 || code === 0x28 || nonprintable(code)) {
|
||||||
|
parseerror();
|
||||||
|
consumeTheRemnantsOfABadURL();
|
||||||
|
return new BadURLToken();
|
||||||
|
} else if (code === 0x5c) {
|
||||||
|
if (startsWithAValidEscape()) {
|
||||||
|
token.value += stringFromCode(consumeEscape());
|
||||||
|
} else {
|
||||||
|
parseerror();
|
||||||
|
consumeTheRemnantsOfABadURL();
|
||||||
|
return new BadURLToken();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token.value += stringFromCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Internal error');
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeEscape = function() {
|
||||||
|
// Assume the the current character is the \
|
||||||
|
// and the next code point is not a newline.
|
||||||
|
consume();
|
||||||
|
if (hexdigit(code)) {
|
||||||
|
// Consume 1-6 hex digits
|
||||||
|
const digits = [code];
|
||||||
|
for (let total = 0; total < 5; total++) {
|
||||||
|
if (hexdigit(next())) {
|
||||||
|
consume();
|
||||||
|
digits.push(code);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (whitespace(next())) consume();
|
||||||
|
let value = parseInt(digits.map(function(x) { return String.fromCharCode(x); }).join(''), 16);
|
||||||
|
if (value > maximumallowedcodepoint) value = 0xfffd;
|
||||||
|
return value;
|
||||||
|
} else if (eof()) {
|
||||||
|
return 0xfffd;
|
||||||
|
} else {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const areAValidEscape = function(c1: number, c2: number) {
|
||||||
|
if (c1 !== 0x5c) return false;
|
||||||
|
if (newline(c2)) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const startsWithAValidEscape = function() {
|
||||||
|
return areAValidEscape(code, next());
|
||||||
|
};
|
||||||
|
|
||||||
|
const wouldStartAnIdentifier = function(c1: number, c2: number, c3: number) {
|
||||||
|
if (c1 === 0x2d)
|
||||||
|
return namestartchar(c2) || c2 === 0x2d || areAValidEscape(c2, c3);
|
||||||
|
else if (namestartchar(c1))
|
||||||
|
return true;
|
||||||
|
else if (c1 === 0x5c)
|
||||||
|
return areAValidEscape(c1, c2);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
};
|
||||||
|
const startsWithAnIdentifier = function() {
|
||||||
|
return wouldStartAnIdentifier(code, next(1), next(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const wouldStartANumber = function(c1: number, c2: number, c3: number) {
|
||||||
|
if (c1 === 0x2b || c1 === 0x2d) {
|
||||||
|
if (digit(c2)) return true;
|
||||||
|
if (c2 === 0x2e && digit(c3)) return true;
|
||||||
|
return false;
|
||||||
|
} else if (c1 === 0x2e) {
|
||||||
|
if (digit(c2)) return true;
|
||||||
|
return false;
|
||||||
|
} else if (digit(c1)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const startsWithANumber = function() {
|
||||||
|
return wouldStartANumber(code, next(1), next(2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeAName = function(): string {
|
||||||
|
let result = '';
|
||||||
|
while (consume()) {
|
||||||
|
if (namechar(code)) {
|
||||||
|
result += stringFromCode(code);
|
||||||
|
} else if (startsWithAValidEscape()) {
|
||||||
|
result += stringFromCode(consumeEscape());
|
||||||
|
} else {
|
||||||
|
reconsume();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Internal parse error');
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeANumber = function() {
|
||||||
|
let repr = '';
|
||||||
|
let type = 'integer';
|
||||||
|
if (next() === 0x2b || next() === 0x2d) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
}
|
||||||
|
while (digit(next())) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
}
|
||||||
|
if (next(1) === 0x2e && digit(next(2))) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
type = 'number';
|
||||||
|
while (digit(next())) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const c1 = next(1), c2 = next(2), c3 = next(3);
|
||||||
|
if ((c1 === 0x45 || c1 === 0x65) && digit(c2)) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
type = 'number';
|
||||||
|
while (digit(next())) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
}
|
||||||
|
} else if ((c1 === 0x45 || c1 === 0x65) && (c2 === 0x2b || c2 === 0x2d) && digit(c3)) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
type = 'number';
|
||||||
|
while (digit(next())) {
|
||||||
|
consume();
|
||||||
|
repr += stringFromCode(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const value = convertAStringToANumber(repr);
|
||||||
|
return { type: type, value: value, repr: repr };
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertAStringToANumber = function(string: string): number {
|
||||||
|
// CSS's number rules are identical to JS, afaik.
|
||||||
|
return +string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const consumeTheRemnantsOfABadURL = function() {
|
||||||
|
while (consume()) {
|
||||||
|
if (code === 0x29 || eof()) {
|
||||||
|
return;
|
||||||
|
} else if (startsWithAValidEscape()) {
|
||||||
|
consumeEscape();
|
||||||
|
donothing();
|
||||||
|
} else {
|
||||||
|
donothing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let iterationCount = 0;
|
||||||
|
while (!eof(next())) {
|
||||||
|
tokens.push(consumeAToken());
|
||||||
|
iterationCount++;
|
||||||
|
if (iterationCount > str.length * 2) throw new Error("I'm infinite-looping!");
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CSSParserToken implements CSSTokenInterface {
|
||||||
|
tokenType = '';
|
||||||
|
value: string | number | undefined;
|
||||||
|
toJSON(): any {
|
||||||
|
return { token: this.tokenType };
|
||||||
|
}
|
||||||
|
toString() { return this.tokenType; }
|
||||||
|
toSource() { return '' + this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadStringToken extends CSSParserToken {
|
||||||
|
override tokenType = 'BADSTRING';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadURLToken extends CSSParserToken {
|
||||||
|
override tokenType = 'BADURL';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WhitespaceToken extends CSSParserToken {
|
||||||
|
override tokenType = 'WHITESPACE';
|
||||||
|
override toString() { return 'WS'; }
|
||||||
|
override toSource() { return ' '; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CDOToken extends CSSParserToken {
|
||||||
|
override tokenType = 'CDO';
|
||||||
|
override toSource() { return '<!--'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CDCToken extends CSSParserToken {
|
||||||
|
override tokenType = 'CDC';
|
||||||
|
override toSource() { return '-->'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ColonToken extends CSSParserToken {
|
||||||
|
override tokenType = ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SemicolonToken extends CSSParserToken {
|
||||||
|
override tokenType = ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommaToken extends CSSParserToken {
|
||||||
|
override tokenType = ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupingToken extends CSSParserToken {
|
||||||
|
override value = '';
|
||||||
|
mirror = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenCurlyToken extends GroupingToken {
|
||||||
|
override tokenType = '{';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = '{';
|
||||||
|
this.mirror = '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloseCurlyToken extends GroupingToken {
|
||||||
|
override tokenType = '}';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = '}';
|
||||||
|
this.mirror = '{';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenSquareToken extends GroupingToken {
|
||||||
|
override tokenType = '[';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = '[';
|
||||||
|
this.mirror = ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloseSquareToken extends GroupingToken {
|
||||||
|
override tokenType = ']';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = ']';
|
||||||
|
this.mirror = '[';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenParenToken extends GroupingToken {
|
||||||
|
override tokenType = '(';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = '(';
|
||||||
|
this.mirror = ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloseParenToken extends GroupingToken {
|
||||||
|
override tokenType = ')';
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.value = ')';
|
||||||
|
this.mirror = '(';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IncludeMatchToken extends CSSParserToken {
|
||||||
|
override tokenType = '~=';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashMatchToken extends CSSParserToken {
|
||||||
|
override tokenType = '|=';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrefixMatchToken extends CSSParserToken {
|
||||||
|
override tokenType = '^=';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SuffixMatchToken extends CSSParserToken {
|
||||||
|
override tokenType = '$=';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubstringMatchToken extends CSSParserToken {
|
||||||
|
override tokenType = '*=';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ColumnToken extends CSSParserToken {
|
||||||
|
override tokenType = '||';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EOFToken extends CSSParserToken {
|
||||||
|
override tokenType = 'EOF';
|
||||||
|
override toSource() { return ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DelimToken extends CSSParserToken {
|
||||||
|
override tokenType = 'DELIM';
|
||||||
|
override value: string = '';
|
||||||
|
|
||||||
|
constructor(code: number) {
|
||||||
|
super();
|
||||||
|
this.value = stringFromCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() { return 'DELIM(' + this.value + ')'; }
|
||||||
|
|
||||||
|
override toJSON() {
|
||||||
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
||||||
|
json.value = this.value;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
override toSource() {
|
||||||
|
if (this.value === '\\')
|
||||||
|
return '\\\n';
|
||||||
|
else
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class StringValuedToken extends CSSParserToken {
|
||||||
|
override value: string = '';
|
||||||
|
ASCIIMatch(str: string) {
|
||||||
|
return this.value.toLowerCase() === str.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
override toJSON() {
|
||||||
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
||||||
|
json.value = this.value;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IdentToken extends StringValuedToken {
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
override tokenType = 'IDENT';
|
||||||
|
override toString() { return 'IDENT(' + this.value + ')'; }
|
||||||
|
override toSource() {
|
||||||
|
return escapeIdent(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FunctionToken extends StringValuedToken {
|
||||||
|
override tokenType = 'FUNCTION';
|
||||||
|
mirror: string;
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
this.mirror = ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() { return 'FUNCTION(' + this.value + ')'; }
|
||||||
|
|
||||||
|
override toSource() {
|
||||||
|
return escapeIdent(this.value) + '(';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AtKeywordToken extends StringValuedToken {
|
||||||
|
override tokenType = 'AT-KEYWORD';
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
override toString() { return 'AT(' + this.value + ')'; }
|
||||||
|
override toSource() {
|
||||||
|
return '@' + escapeIdent(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HashToken extends StringValuedToken {
|
||||||
|
override tokenType = 'HASH';
|
||||||
|
type: string;
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
this.type = 'unrestricted';
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() { return 'HASH(' + this.value + ')'; }
|
||||||
|
|
||||||
|
override toJSON() {
|
||||||
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
||||||
|
json.value = this.value;
|
||||||
|
json.type = this.type;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
override toSource() {
|
||||||
|
if (this.type === 'id')
|
||||||
|
return '#' + escapeIdent(this.value);
|
||||||
|
else
|
||||||
|
return '#' + escapeHash(this.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StringToken extends StringValuedToken {
|
||||||
|
override tokenType = 'STRING';
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() {
|
||||||
|
return '"' + escapeString(this.value) + '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class URLToken extends StringValuedToken {
|
||||||
|
override tokenType = 'URL';
|
||||||
|
constructor(val: string) {
|
||||||
|
super();
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
override toString() { return 'URL(' + this.value + ')'; }
|
||||||
|
override toSource() {
|
||||||
|
return 'url("' + escapeString(this.value) + '")';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NumberToken extends CSSParserToken {
|
||||||
|
override tokenType = 'NUMBER';
|
||||||
|
type: string;
|
||||||
|
repr: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.type = 'integer';
|
||||||
|
this.repr = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() {
|
||||||
|
if (this.type === 'integer')
|
||||||
|
return 'INT(' + this.value + ')';
|
||||||
|
return 'NUMBER(' + this.value + ')';
|
||||||
|
}
|
||||||
|
override toJSON() {
|
||||||
|
const json = super.toJSON();
|
||||||
|
json.value = this.value;
|
||||||
|
json.type = this.type;
|
||||||
|
json.repr = this.repr;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
override toSource() { return this.repr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class PercentageToken extends CSSParserToken {
|
||||||
|
override tokenType = 'PERCENTAGE';
|
||||||
|
repr: string;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.repr = '';
|
||||||
|
}
|
||||||
|
override toString() { return 'PERCENTAGE(' + this.value + ')'; }
|
||||||
|
override toJSON() {
|
||||||
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
||||||
|
json.value = this.value;
|
||||||
|
json.repr = this.repr;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
override toSource() { return this.repr + '%'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DimensionToken extends CSSParserToken {
|
||||||
|
override tokenType = 'DIMENSION';
|
||||||
|
type: string;
|
||||||
|
repr: string;
|
||||||
|
unit: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.type = 'integer';
|
||||||
|
this.repr = '';
|
||||||
|
this.unit = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString() { return 'DIM(' + this.value + ',' + this.unit + ')'; }
|
||||||
|
override toJSON() {
|
||||||
|
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
|
||||||
|
json.value = this.value;
|
||||||
|
json.type = this.type;
|
||||||
|
json.repr = this.repr;
|
||||||
|
json.unit = this.unit;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
override toSource() {
|
||||||
|
const source = this.repr;
|
||||||
|
let unit = escapeIdent(this.unit);
|
||||||
|
if (unit[0].toLowerCase() === 'e' && (unit[1] === '-' || between(unit.charCodeAt(1), 0x30, 0x39))) {
|
||||||
|
// Unit is ambiguous with scinot
|
||||||
|
// Remove the leading "e", replace with escape.
|
||||||
|
unit = '\\65 ' + unit.slice(1, unit.length);
|
||||||
|
}
|
||||||
|
return source + unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeIdent(string: string) {
|
||||||
|
string = '' + string;
|
||||||
|
let result = '';
|
||||||
|
const firstcode = string.charCodeAt(0);
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
const code = string.charCodeAt(i);
|
||||||
|
if (code === 0x0)
|
||||||
|
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
||||||
|
|
||||||
|
if (
|
||||||
|
between(code, 0x1, 0x1f) || code === 0x7f ||
|
||||||
|
(i === 0 && between(code, 0x30, 0x39)) ||
|
||||||
|
(i === 1 && between(code, 0x30, 0x39) && firstcode === 0x2d)
|
||||||
|
)
|
||||||
|
result += '\\' + code.toString(16) + ' ';
|
||||||
|
else if (
|
||||||
|
code >= 0x80 ||
|
||||||
|
code === 0x2d ||
|
||||||
|
code === 0x5f ||
|
||||||
|
between(code, 0x30, 0x39) ||
|
||||||
|
between(code, 0x41, 0x5a) ||
|
||||||
|
between(code, 0x61, 0x7a)
|
||||||
|
)
|
||||||
|
result += string[i];
|
||||||
|
else
|
||||||
|
result += '\\' + string[i];
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHash(string: string) {
|
||||||
|
// Escapes the contents of "unrestricted"-type hash tokens.
|
||||||
|
// Won't preserve the ID-ness of "id"-type hash tokens;
|
||||||
|
// use escapeIdent() for that.
|
||||||
|
string = '' + string;
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
const code = string.charCodeAt(i);
|
||||||
|
if (code === 0x0)
|
||||||
|
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
||||||
|
|
||||||
|
if (
|
||||||
|
code >= 0x80 ||
|
||||||
|
code === 0x2d ||
|
||||||
|
code === 0x5f ||
|
||||||
|
between(code, 0x30, 0x39) ||
|
||||||
|
between(code, 0x41, 0x5a) ||
|
||||||
|
between(code, 0x61, 0x7a)
|
||||||
|
)
|
||||||
|
result += string[i];
|
||||||
|
else
|
||||||
|
result += '\\' + code.toString(16) + ' ';
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeString(string: string) {
|
||||||
|
string = '' + string;
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
const code = string.charCodeAt(i);
|
||||||
|
|
||||||
|
if (code === 0x0)
|
||||||
|
throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
|
||||||
|
|
||||||
|
if (between(code, 0x1, 0x1f) || code === 0x7f)
|
||||||
|
result += '\\' + code.toString(16) + ' ';
|
||||||
|
else if (code === 0x22 || code === 0x5c)
|
||||||
|
result += '\\' + string[i];
|
||||||
|
else
|
||||||
|
result += string[i];
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue