| @@ -0,0 +1,49 | |||
|
|
1 | import request = require("dojo/request"); | |
|
|
2 | import m = require("module"); | |
|
|
3 | import { TraceSource } from "../log/TraceSource"; | |
|
|
4 | import { TemplateCompiler } from "./TemplateCompiler"; | |
|
|
5 | import { TemplateParser } from "./TemplateParser"; | |
|
|
6 | import { isNullOrEmptyString } from "../safe"; | |
|
|
7 | import { MapOf } from "../interfaces"; | |
|
|
8 | ||
|
|
9 | type TemplateFn = (obj: object) => string; | |
|
|
10 | ||
|
|
11 | const trace = TraceSource.get(m.id); | |
|
|
12 | ||
|
|
13 | function compile(str: string) { | |
|
|
14 | if (isNullOrEmptyString(str)) | |
|
|
15 | return () => ""; | |
|
|
16 | ||
|
|
17 | const parser = new TemplateParser(str); | |
|
|
18 | const compiler = new TemplateCompiler(); | |
|
|
19 | ||
|
|
20 | return compiler.compile(parser); | |
|
|
21 | } | |
|
|
22 | ||
|
|
23 | const cache: MapOf<TemplateFn> = {}; | |
|
|
24 | ||
|
|
25 | interface OnLoadFn<T> { | |
|
|
26 | (res: T): void; | |
|
|
27 | error(e: any): void; | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | compile.load = (id: string, require: Require, callback: OnLoadFn<TemplateFn>) => { | |
|
|
31 | const url = require.toUrl(id); | |
|
|
32 | if (url in cache) { | |
|
|
33 | trace.debug("{0} -> {1}: cached", id, url); | |
|
|
34 | callback(cache[url]); | |
|
|
35 | } else { | |
|
|
36 | trace.debug("{0} -> {1}: load", id, url); | |
|
|
37 | request(url).then(compile).then((tc: TemplateFn) => { | |
|
|
38 | trace.debug("{0}: compiled", url); | |
|
|
39 | callback(cache[url] = tc); | |
|
|
40 | }, (err: any) => { | |
|
|
41 | callback.error({ | |
|
|
42 | inner: err, | |
|
|
43 | src: "@implab/core/text/template-compile" | |
|
|
44 | }); | |
|
|
45 | }); | |
|
|
46 | } | |
|
|
47 | }; | |
|
|
48 | ||
|
|
49 | export = compile; | |
| @@ -0,0 +1,102 | |||
|
|
1 | import { format } from "./StringFormat"; | |
|
|
2 | import { TraceSource, DebugLevel } from "../log/TraceSource"; | |
|
|
3 | import { ITemplateParser, TokenType } from "./TemplateParser"; | |
|
|
4 | ||
|
|
5 | const trace = TraceSource.get("@implab/text/TemplateCompiler"); | |
|
|
6 | ||
|
|
7 | type TemplateFn = (obj: object) => string; | |
|
|
8 | ||
|
|
9 | export class TemplateCompiler { | |
|
|
10 | ||
|
|
11 | _data: string[]; | |
|
|
12 | _code: string[]; | |
|
|
13 | _wrapWith = true; | |
|
|
14 | ||
|
|
15 | constructor() { | |
|
|
16 | this._code = []; | |
|
|
17 | this._data = []; | |
|
|
18 | } | |
|
|
19 | ||
|
|
20 | compile(parser: ITemplateParser): TemplateFn { | |
|
|
21 | this.preamble(); | |
|
|
22 | this.visitTemplate(parser); | |
|
|
23 | this.postamble(); | |
|
|
24 | ||
|
|
25 | const text = this._code.join("\n"); | |
|
|
26 | ||
|
|
27 | try { | |
|
|
28 | const compiled = new Function("obj, format, $data", text); | |
|
|
29 | /** | |
|
|
30 | * Функция форматирования по шаблону | |
|
|
31 | * | |
|
|
32 | * @type{Function} | |
|
|
33 | * @param{Object} obj объект с параметрами для подстановки | |
|
|
34 | */ | |
|
|
35 | return (obj: object) => compiled(obj || {}, format, this._data); | |
|
|
36 | } catch (e) { | |
|
|
37 | trace.traceEvent(DebugLevel, [e, text, this._data]); | |
|
|
38 | throw e; | |
|
|
39 | } | |
|
|
40 | } | |
|
|
41 | ||
|
|
42 | preamble() { | |
|
|
43 | this._code.push( | |
|
|
44 | "var $p = [];", | |
|
|
45 | "var print = function(){", | |
|
|
46 | " $p.push(format.apply(null,arguments));", | |
|
|
47 | "};" | |
|
|
48 | ); | |
|
|
49 | ||
|
|
50 | if (this._wrapWith) | |
|
|
51 | this._code.push("with(obj){"); | |
|
|
52 | } | |
|
|
53 | ||
|
|
54 | postamble() { | |
|
|
55 | if (this._wrapWith) | |
|
|
56 | this._code.push("}"); | |
|
|
57 | ||
|
|
58 | this._code.push("return $p.join('');"); | |
|
|
59 | } | |
|
|
60 | ||
|
|
61 | visitTemplate(parser: ITemplateParser) { | |
|
|
62 | while (parser.next()) { | |
|
|
63 | switch (parser.token()) { | |
|
|
64 | case TokenType.OpenBlock: | |
|
|
65 | this.visitCode(parser); | |
|
|
66 | break; | |
|
|
67 | case TokenType.OpenInlineBlock: | |
|
|
68 | this.visitInline(parser); | |
|
|
69 | break; | |
|
|
70 | default: | |
|
|
71 | this.visitTextFragment(parser); | |
|
|
72 | break; | |
|
|
73 | } | |
|
|
74 | } | |
|
|
75 | } | |
|
|
76 | ||
|
|
77 | visitInline(parser: ITemplateParser) { | |
|
|
78 | const code = ["$p.push("]; | |
|
|
79 | while (parser.next()) { | |
|
|
80 | if (parser.token() === TokenType.CloseBlock) | |
|
|
81 | break; | |
|
|
82 | code.push(parser.value()); | |
|
|
83 | } | |
|
|
84 | code.push(");"); | |
|
|
85 | this._code.push(code.join("")); | |
|
|
86 | } | |
|
|
87 | ||
|
|
88 | visitCode(parser: ITemplateParser) { | |
|
|
89 | const code = []; | |
|
|
90 | while (parser.next()) { | |
|
|
91 | if (parser.token() === TokenType.CloseBlock) | |
|
|
92 | break; | |
|
|
93 | code.push(parser.value()); | |
|
|
94 | } | |
|
|
95 | this._code.push(code.join("")); | |
|
|
96 | } | |
|
|
97 | ||
|
|
98 | visitTextFragment(parser: ITemplateParser) { | |
|
|
99 | const i = this._data.push(parser.value()); | |
|
|
100 | this._code.push("$p.push($data[" + i + "]);"); | |
|
|
101 | } | |
|
|
102 | } | |
| @@ -0,0 +1,64 | |||
|
|
1 | import { argumentNotEmptyString } from "../safe"; | |
|
|
2 | import { MapOf } from "../interfaces"; | |
|
|
3 | ||
|
|
4 | const splitRx = /(<%=|\[%=|<%|\[%|%\]|%>)/; | |
|
|
5 | ||
|
|
6 | export enum TokenType { | |
|
|
7 | None, | |
|
|
8 | Text, | |
|
|
9 | OpenInlineBlock, | |
|
|
10 | OpenBlock, | |
|
|
11 | CloseBlock | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | const tokenMap: MapOf<TokenType> = { | |
|
|
15 | "<%": TokenType.OpenBlock, | |
|
|
16 | "[%": TokenType.OpenBlock, | |
|
|
17 | "<%=": TokenType.OpenInlineBlock, | |
|
|
18 | "[%=": TokenType.OpenInlineBlock, | |
|
|
19 | "%>": TokenType.CloseBlock, | |
|
|
20 | "%]": TokenType.CloseBlock | |
|
|
21 | }; | |
|
|
22 | ||
|
|
23 | export interface ITemplateParser { | |
|
|
24 | next(): boolean; | |
|
|
25 | token(): TokenType; | |
|
|
26 | value(): string; | |
|
|
27 | } | |
|
|
28 | ||
|
|
29 | export class TemplateParser implements ITemplateParser { | |
|
|
30 | ||
|
|
31 | _tokens: string[]; | |
|
|
32 | _pos = -1; | |
|
|
33 | _type: TokenType; | |
|
|
34 | _value: string; | |
|
|
35 | ||
|
|
36 | constructor(text: string) { | |
|
|
37 | argumentNotEmptyString(text, "text"); | |
|
|
38 | ||
|
|
39 | this._tokens = text.split(splitRx); | |
|
|
40 | this._type = TokenType.None; | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | next() { | |
|
|
44 | this._pos++; | |
|
|
45 | if (this._pos < this._tokens.length) { | |
|
|
46 | this._value = this._tokens[this._pos]; | |
|
|
47 | this._type = tokenMap[this._value] || TokenType.Text; | |
|
|
48 | return true; | |
|
|
49 | } else { | |
|
|
50 | this._type = TokenType.None; | |
|
|
51 | this._value = undefined; | |
|
|
52 | return false; | |
|
|
53 | } | |
|
|
54 | } | |
|
|
55 | ||
|
|
56 | token() { | |
|
|
57 | return this._type; | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | value() { | |
|
|
61 | return this._value; | |
|
|
62 | } | |
|
|
63 | ||
|
|
64 | } | |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
