UNPKG

ivi

Version:

Lightweight Embeddable Web UI Library.

346 lines 13.8 kB
import { NODE_TYPE_TEXT, NODE_TYPE_EXPR, NODE_TYPE_ELEMENT, PROPERTY_TYPE_DIRECTIVE, PROPERTY_TYPE_VALUE, PROPERTY_TYPE_DOMVALUE, PROPERTY_TYPE_EVENT, PROPERTY_TYPE_STYLE, PROPERTY_TYPE_ATTRIBUTE, } from "../template/ir.js"; import { TemplateParserError, TemplateScanner, } from "../template/parser.js"; import { VOID_ELEMENTS } from "../template/shared.js"; export const parseTemplate = (s, type) => { const parser = new TemplateParser(s); return { type, children: parser.parse(), }; }; export class TemplateParser extends TemplateScanner { constructor(statics) { super(statics); } parse() { return this.parseChildrenList(); } parseChildrenList() { const children = []; let whitespaceState = this.whitespace(); while (!this.isEnd()) { const c = this.peekCharCode(); if (c !== -1) { if (c === 60 /* CharCode.LessThan */) { if (whitespaceState & 1 /* WhitespaceState.Whitespace */) { if (!(whitespaceState & 2 /* WhitespaceState.ContainsNewline */) || (whitespaceState & 4 /* WhitespaceState.ContainsVerticalTab */)) { children.push(SPACE_TEXT_NODE); } } if (this.peekCharCode(1) === 47 /* CharCode.Slash */) { break; } children.push(this.parseElement()); } else { children.push({ type: NODE_TYPE_TEXT, value: this.parseText(whitespaceState), }); } } else { const expr = this.expr(); if (expr !== -1) { if (whitespaceState & 1 /* WhitespaceState.Whitespace */) { if (!(whitespaceState & 2 /* WhitespaceState.ContainsNewline */) || (whitespaceState & 4 /* WhitespaceState.ContainsVerticalTab */)) { children.push(SPACE_TEXT_NODE); } } children.push({ type: NODE_TYPE_EXPR, value: expr, }); } else { break; } } whitespaceState = this.whitespace(); } return children; } parseElement() { if (!this.charCode(60 /* CharCode.LessThan */)) { throw new TemplateParserError("Expected a '<' character.", this.e, this.i); } const tag = this.regExp(IDENTIFIER); if (tag === void 0) { throw new TemplateParserError("Expected a valid tag name.", this.e, this.i); } this.whitespace(); const properties = this.parseAttributes(); let children; if (this.charCode(47 /* CharCode.Slash */)) { if (!this.charCode(62 /* CharCode.MoreThan */)) { throw new TemplateParserError("Expected a '>' character.", this.e, this.i); } children = []; } else { if (!this.charCode(62 /* CharCode.MoreThan */)) { throw new TemplateParserError("Expected a '>' character.", this.e, this.i); } if (!VOID_ELEMENTS.test(tag)) { children = this.parseChildrenList(); if (!this.charCode(60 /* CharCode.LessThan */)) { throw new TemplateParserError("Expected a '<' character.", this.e, this.i); } if (!this.charCode(47 /* CharCode.Slash */)) { throw new TemplateParserError("Expected a '/' character.", this.e, this.i); } if (!this.string(tag)) { throw new TemplateParserError(`Expected a '${tag}' tag name.`, this.e, this.i); } this.whitespace(); if (!this.charCode(62 /* CharCode.MoreThan */)) { throw new TemplateParserError("Expected a '>' character.", this.e, this.i); } } else { children = []; } } return { type: NODE_TYPE_ELEMENT, tag, properties, children, }; } parseAttributes() { const properties = []; while (!this.isEnd()) { const c = this.peekCharCode(); if (c === -1) { // shorthand syntax for directives properties.push({ type: PROPERTY_TYPE_DIRECTIVE, key: null, value: this.expr(), hoist: false, }); this.whitespace(); continue; } if (c === 47 /* CharCode.Slash */ || c === 62 /* CharCode.MoreThan */) { return properties; } if (c === 46 /* CharCode.Dot */) { // .property this.i++; const key = this.regExp(JS_PROPERTY); if (key === void 0) { throw new TemplateParserError("Expected a valid property name.", this.e, this.i); } this.dynamicProp(properties, PROPERTY_TYPE_VALUE, key); } else if (c === 42 /* CharCode.Asterisk */) { // *value this.i++; const key = this.regExp(JS_PROPERTY); if (key === void 0) { throw new TemplateParserError("Expected a valid property name.", this.e, this.i); } this.dynamicProp(properties, PROPERTY_TYPE_DOMVALUE, key); } else if (c === 126 /* CharCode.Tilde */) { // ~style this.i++; const key = this.regExp(IDENTIFIER); if (key === void 0) { throw new TemplateParserError("Expected a valid style name.", this.e, this.i); } let value; if (!this.charCode(61 /* CharCode.EqualsTo */)) { throw new TemplateParserError("Expected a '=' character.", this.e, this.i); } const c = this.peekCharCode(); if (c !== -1) { if (c !== 34 /* CharCode.DoubleQuote */) { throw new TemplateParserError("Expected a string or an expression.", this.e, this.i); } value = this.parseAttributeString(); } else { value = this.expr(); if (value === -1) { throw new TemplateParserError("Expected a string or an expression.", this.e, this.i); } } properties.push({ type: PROPERTY_TYPE_STYLE, key, value, hoist: false, }); } else if (c === 64 /* CharCode.AtSign */) { // @event this.i++; const key = this.regExp(IDENTIFIER); if (key === void 0) { throw new TemplateParserError("Expected a valid event name.", this.e, this.i); } this.dynamicProp(properties, PROPERTY_TYPE_EVENT, key); } else { const key = this.regExp(IDENTIFIER); if (key === void 0) { throw new TemplateParserError("Expected a valid attribute name.", this.e, this.i); } let value = true; if (this.charCode(61 /* CharCode.EqualsTo */)) { // = const c = this.peekCharCode(); if (c !== -1) { if (c !== 34 /* CharCode.DoubleQuote */) { throw new TemplateParserError("Expected a string or an expression.", this.e, this.i); } value = this.parseAttributeString(); } else { value = this.expr(); if (value === -1) { throw new TemplateParserError("Expected a string or an expression.", this.e, this.i); } } } properties.push({ type: PROPERTY_TYPE_ATTRIBUTE, key, value, }); } this.whitespace(); } throw new TemplateParserError("Expected a '>' character", this.e, this.i); } dynamicProp(properties, type, key) { if (!this.charCode(61 /* CharCode.EqualsTo */)) { throw new TemplateParserError("Expected a '=' character.", this.e, this.i); } const value = this.expr(); if (value === -1) { throw new TemplateParserError("Expected an expression.", this.e, this.i); } properties.push({ type, key, value, hoist: false, }); } parseAttributeString() { const text = this.text; const textLength = text.length; let i = this.i; let s = ""; let c = text.charCodeAt(i); let delimCharCode; if (c === 39 /* CharCode.SingleQuote */) { delimCharCode = 39 /* CharCode.SingleQuote */; } else if (c === 34 /* CharCode.DoubleQuote */) { delimCharCode = 34 /* CharCode.DoubleQuote */; } else { throw new TemplateParserError("Expected ' or \" character.", this.e, i); } let start = ++i; while (i < textLength) { c = text.charCodeAt(i++); if (c === delimCharCode) { const end = i - 1; this.i = i; return s + text.substring(start, end); } } throw new TemplateParserError(`Attribute string should be closed with a '${String.fromCharCode(delimCharCode)}' character`, this.e, i); } parseText(state) { const text = this.text; let chars = []; while (!this.isEnd()) { if (this.i < text.length) { const c = text.charCodeAt(this.i); if (c === 60 /* CharCode.LessThan */) { break; } if (c === 32 /* CharCode.Space */ || c === 9 /* CharCode.Tab */) { this.i++; state |= 1 /* WhitespaceState.Whitespace */; continue; } if (c === 10 /* CharCode.Newline */ || c === 13 /* CharCode.CarriageReturn */) { this.i++; state |= 2 /* WhitespaceState.ContainsNewline */; continue; } if (c === 11 /* CharCode.VerticalTab */) { this.i++; state |= 4 /* WhitespaceState.ContainsVerticalTab */; continue; } if (state & 1 /* WhitespaceState.Whitespace */) { if ((state & (8 /* WhitespaceState.TextContent */ | 4 /* WhitespaceState.ContainsVerticalTab */)) || !(state & 2 /* WhitespaceState.ContainsNewline */)) { chars.push(32 /* CharCode.Space */); } } state = 8 /* WhitespaceState.TextContent */; chars.push(c); this.i++; } if (this.peekExpr() !== -1) { break; } } if (state & 1 /* WhitespaceState.Whitespace */) { if (!(state & 2 /* WhitespaceState.ContainsNewline */) || (state & 4 /* WhitespaceState.ContainsVerticalTab */)) { chars.push(32 /* CharCode.Space */); } } if (chars.length !== 0) { if (chars.length > (1 << 16)) { throw new TemplateParserError("Text string is too long (>64k)", this.e, this.i); // Text nodes are splitted into two nodes when they exceed their length limit (64k). // https://github.com/chromium/chromium/blob/91159249db3086f17b28b7a060f55ec0345c24c7/third_party/blink/renderer/core/dom/text.h#L42 } return _String.fromCharCode(...chars); } return ""; } whitespace() { const text = this.text; let state = 0; let i = this.i; while (i < text.length) { const c = text.charCodeAt(i); if (c === 32 /* CharCode.Space */ || c === 9 /* CharCode.Tab */) { i++; continue; } if (c === 10 /* CharCode.Newline */ || c === 13 /* CharCode.CarriageReturn */) { i++; state |= 2 /* WhitespaceState.ContainsNewline */; continue; } if (c === 11 /* CharCode.VerticalTab */) { i++; state |= 4 /* WhitespaceState.ContainsVerticalTab */; continue; } break; } if (i !== this.i) { this.i = i; return state | 1 /* WhitespaceState.Whitespace */; } return 0; } } const _String = String; const IDENTIFIER = /[a-zA-Z_][\w-]*/y; const JS_PROPERTY = /[a-zA-Z_$][\w]*/y; const SPACE_TEXT_NODE = { type: NODE_TYPE_TEXT, value: " ", }; //# sourceMappingURL=parser.js.map