html2canvas/src/css/syntax/parser.ts

189 lines
5.7 KiB
TypeScript
Raw Normal View History

Typescript conversion (#1828) * initial typescript conversion * test: update overflow+transform ref test * fix: correctly render pseudo element content * fix: testrunner build * fix: karma test urls * test: update underline tests with <u> elements * test: update to es6-promise polyfill * test: remove watch from server * test: remove flow * format: update prettier for typescript * test: update eslint to use typescript parser * test: update linear gradient reftest * test: update test runner * test: update testrunner promise polyfill * fix: handle display: -webkit-flex correctly (fix #1817) * fix: correctly render gradients with clip & repeat (fix #1773) * fix: webkit-gradient function support * fix: implement radial gradients * fix: text-decoration rendering * fix: missing scroll positions for elements * ci: fix ios 11 tests * fix: ie logging * ci: improve device availability logging * fix: lint errors * ci: update to ios 12 * fix: check for console availability * ci: fix build dependency * test: update text reftests * fix: window reference for unit tests * feat: add hsl/hsla color support * fix: render options * fix: CSSKeyframesRule cssText Permission Denied on Internet Explorer 11 (#1830) * fix: option lint * fix: list type rendering * test: fix platform import * fix: ie css parsing for numbers * ci: add minified build * fix: form element rendering * fix: iframe rendering * fix: re-introduce experimental foreignobject renderer * fix: text-shadow rendering * feat: improve logging * fix: unit test logging * fix: cleanup resources * test: update overflow scrolling to work with ie * build: update build to include typings * fix: do not parse select element children * test: fix onclone test to work with older IEs * test: reduce reftest canvas sizes * test: remove dynamic setUp from list tests * test: update linear-gradient tests * build: remove old source files * build: update docs dependencies * build: fix typescript definition path * ci: include test.js on docs website
2019-05-26 01:54:41 +03:00
import {
CSSToken,
DimensionToken,
EOF_TOKEN,
NumberValueToken,
StringValueToken,
Tokenizer,
TokenType
} from './tokenizer';
export type CSSBlockType =
| TokenType.LEFT_PARENTHESIS_TOKEN
| TokenType.LEFT_SQUARE_BRACKET_TOKEN
| TokenType.LEFT_CURLY_BRACKET_TOKEN;
export interface CSSBlock {
type: CSSBlockType;
values: CSSValue[];
}
export interface CSSFunction {
type: TokenType.FUNCTION;
name: string;
values: CSSValue[];
}
export type CSSValue = CSSFunction | CSSToken | CSSBlock;
export class Parser {
private _tokens: CSSToken[];
constructor(tokens: CSSToken[]) {
this._tokens = tokens;
}
static create(value: string): Parser {
const tokenizer = new Tokenizer();
tokenizer.write(value);
return new Parser(tokenizer.read());
}
static parseValue(value: string): CSSValue {
return Parser.create(value).parseComponentValue();
}
static parseValues(value: string): CSSValue[] {
return Parser.create(value).parseComponentValues();
}
parseComponentValue(): CSSValue {
let token = this.consumeToken();
while (token.type === TokenType.WHITESPACE_TOKEN) {
token = this.consumeToken();
}
if (token.type === TokenType.EOF_TOKEN) {
throw new SyntaxError(`Error parsing CSS component value, unexpected EOF`);
}
this.reconsumeToken(token);
const value = this.consumeComponentValue();
do {
token = this.consumeToken();
} while (token.type === TokenType.WHITESPACE_TOKEN);
if (token.type === TokenType.EOF_TOKEN) {
return value;
}
throw new SyntaxError(`Error parsing CSS component value, multiple values found when expecting only one`);
}
parseComponentValues(): CSSValue[] {
const values = [];
while (true) {
let value = this.consumeComponentValue();
if (value.type === TokenType.EOF_TOKEN) {
return values;
}
values.push(value);
values.push();
}
}
private consumeComponentValue(): CSSValue {
const token = this.consumeToken();
switch (token.type) {
case TokenType.LEFT_CURLY_BRACKET_TOKEN:
case TokenType.LEFT_SQUARE_BRACKET_TOKEN:
case TokenType.LEFT_PARENTHESIS_TOKEN:
return this.consumeSimpleBlock(token.type);
case TokenType.FUNCTION_TOKEN:
return this.consumeFunction(token);
}
return token;
}
private consumeSimpleBlock(type: CSSBlockType): CSSBlock {
const block: CSSBlock = {type, values: []};
let token = this.consumeToken();
while (true) {
if (token.type === TokenType.EOF_TOKEN || isEndingTokenFor(token, type)) {
return block;
}
this.reconsumeToken(token);
block.values.push(this.consumeComponentValue());
token = this.consumeToken();
}
}
private consumeFunction(functionToken: StringValueToken): CSSFunction {
const cssFunction: CSSFunction = {
name: functionToken.value,
values: [],
type: TokenType.FUNCTION
};
while (true) {
const token = this.consumeToken();
if (token.type === TokenType.EOF_TOKEN || token.type === TokenType.RIGHT_PARENTHESIS_TOKEN) {
return cssFunction;
}
this.reconsumeToken(token);
cssFunction.values.push(this.consumeComponentValue());
}
}
private consumeToken(): CSSToken {
const token = this._tokens.shift();
return typeof token === 'undefined' ? EOF_TOKEN : token;
}
private reconsumeToken(token: CSSToken): void {
this._tokens.unshift(token);
}
}
export const isDimensionToken = (token: CSSValue): token is DimensionToken => token.type === TokenType.DIMENSION_TOKEN;
export const isNumberToken = (token: CSSValue): token is NumberValueToken => token.type === TokenType.NUMBER_TOKEN;
export const isIdentToken = (token: CSSValue): token is StringValueToken => token.type === TokenType.IDENT_TOKEN;
export const isStringToken = (token: CSSValue): token is StringValueToken => token.type === TokenType.STRING_TOKEN;
export const isIdentWithValue = (token: CSSValue, value: string): boolean =>
isIdentToken(token) && token.value === value;
export const nonWhiteSpace = (token: CSSValue) => token.type !== TokenType.WHITESPACE_TOKEN;
export const nonFunctionArgSeperator = (token: CSSValue) =>
token.type !== TokenType.WHITESPACE_TOKEN && token.type !== TokenType.COMMA_TOKEN;
export const parseFunctionArgs = (tokens: CSSValue[]): CSSValue[][] => {
const args: CSSValue[][] = [];
let arg: CSSValue[] = [];
tokens.forEach(token => {
if (token.type === TokenType.COMMA_TOKEN) {
if (arg.length === 0) {
throw new Error(`Error parsing function args, zero tokens for arg`);
}
args.push(arg);
arg = [];
return;
}
if (token.type !== TokenType.WHITESPACE_TOKEN) {
arg.push(token);
}
});
if (arg.length) {
args.push(arg);
}
return args;
};
const isEndingTokenFor = (token: CSSToken, type: CSSBlockType): boolean => {
if (type === TokenType.LEFT_CURLY_BRACKET_TOKEN && token.type === TokenType.RIGHT_CURLY_BRACKET_TOKEN) {
return true;
}
if (type === TokenType.LEFT_SQUARE_BRACKET_TOKEN && token.type === TokenType.RIGHT_SQUARE_BRACKET_TOKEN) {
return true;
}
return type === TokenType.LEFT_PARENTHESIS_TOKEN && token.type === TokenType.RIGHT_PARENTHESIS_TOKEN;
};