TemplateCompiler.ts
136 lines
| 3.7 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r175 | import * as format from "./format"; | ||
| import { TraceSource, DebugLevel } from "../log/TraceSource"; | ||||
| import { ITemplateParser, TokenType } from "./TemplateParser"; | ||||
| import m = require("module"); | ||||
| import { isKeyof } from "../safe"; | ||||
| const trace = TraceSource.get(m.id); | ||||
| type TemplateFn = (obj: object) => string; | ||||
| const htmlEscapes = { | ||||
| "&": "&", | ||||
| "<": "<", | ||||
| ">": ">", | ||||
| '"': """, | ||||
| "'": "'", | ||||
| "/": "/" | ||||
| }; | ||||
| // Regex containing the keys listed immediately above. | ||||
| const htmlEscaper = /[&<>"'\/]/g; | ||||
| // Escape a string for HTML interpolation. | ||||
| function escapeHtml(string: any) { | ||||
| return ("" + string).replace(htmlEscaper, match => isKeyof(match, htmlEscapes) ? htmlEscapes[match] : ""); | ||||
| } | ||||
| export class TemplateCompiler { | ||||
| _data: string[]; | ||||
| _code: string[]; | ||||
| _wrapWith = true; | ||||
| constructor() { | ||||
| this._code = []; | ||||
| this._data = []; | ||||
| } | ||||
| compile(parser: ITemplateParser): TemplateFn { | ||||
| this.preamble(); | ||||
| this.visitTemplate(parser); | ||||
| this.postamble(); | ||||
| const text = this._code.join("\n"); | ||||
| try { | ||||
| // tslint:disable-next-line:function-constructor | ||||
| const compiled = new Function("obj, format, $data, escapeHtml", text); | ||||
| /** | ||||
| * Функция форматирования по шаблону | ||||
| * | ||||
| * @type{Function} | ||||
| * @param{Object} obj объект с параметрами для подстановки | ||||
| */ | ||||
| return (obj: object) => compiled(obj || {}, format, this._data, escapeHtml); | ||||
| } catch (e) { | ||||
| trace.traceEvent(DebugLevel, [e, text, this._data]); | ||||
| throw e; | ||||
| } | ||||
| } | ||||
| preamble() { | ||||
| this._code.push( | ||||
| "var $p = [];", | ||||
| "var print = function(){", | ||||
| " $p.push(format.apply(null,arguments));", | ||||
| "};" | ||||
| ); | ||||
| if (this._wrapWith) | ||||
| this._code.push("with(obj){"); | ||||
| } | ||||
| postamble() { | ||||
| if (this._wrapWith) | ||||
| this._code.push("}"); | ||||
| this._code.push("return $p.join('');"); | ||||
| } | ||||
| visitTemplate(parser: ITemplateParser) { | ||||
| while (parser.next()) { | ||||
| switch (parser.token()) { | ||||
| case TokenType.OpenBlock: | ||||
| this.visitCode(parser); | ||||
| break; | ||||
| case TokenType.OpenInlineBlock: | ||||
| this.visitInline(parser); | ||||
| break; | ||||
| case TokenType.OpenFilterBlock: | ||||
| this.visitFilter(parser); | ||||
| break; | ||||
| default: | ||||
| this.visitTextFragment(parser); | ||||
| break; | ||||
| } | ||||
| } | ||||
| } | ||||
| visitInline(parser: ITemplateParser) { | ||||
| const code = ["$p.push("]; | ||||
| while (parser.next()) { | ||||
| if (parser.token() === TokenType.CloseBlock) | ||||
| break; | ||||
| code.push(parser.value()); | ||||
| } | ||||
| code.push(");"); | ||||
| this._code.push(code.join("")); | ||||
| } | ||||
| visitFilter(parser: ITemplateParser) { | ||||
| const code = ["$p.push(escapeHtml("]; | ||||
| while (parser.next()) { | ||||
| if (parser.token() === TokenType.CloseBlock) | ||||
| break; | ||||
| code.push(parser.value()); | ||||
| } | ||||
| code.push("));"); | ||||
| this._code.push(code.join("")); | ||||
| } | ||||
| visitCode(parser: ITemplateParser) { | ||||
| const code = []; | ||||
| while (parser.next()) { | ||||
| if (parser.token() === TokenType.CloseBlock) | ||||
| break; | ||||
| code.push(parser.value()); | ||||
| } | ||||
| this._code.push(code.join("")); | ||||
| } | ||||
| visitTextFragment(parser: ITemplateParser) { | ||||
| const i = this._data.push(parser.value()) - 1; | ||||
| this._code.push("$p.push($data[" + i + "]);"); | ||||
| } | ||||
| } | ||||
