| @@ -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 |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
