ivi
Version:
Lightweight Embeddable Web UI Library.
346 lines • 13.8 kB
JavaScript
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