PageSourceSearch

https://www.ngdsh.org.cn/common/template.js

js ngdsh.org.cn collected 2026-10-02 19:04:58 UTC 18,508 bytes, 741 lines download raw bytes

1/*!
2 * artTemplate - Template Engine
3 * https://github.com/aui/artTemplate
4 * Released under the MIT, BSD, and GPL Licenses
5 */
6
7!(function () {
8
9
10    /**
11     * 模板引擎
12     * @name    template
13     * @param   {String}            模板名
14     * @param   {Object, String}    数据。如果为字符串则编译并缓存编译结果
15     * @return  {String, Function}  渲染好的HTML字符串或者渲染方法
16     */
17    var template = function (filename, content) {
18        return typeof content === 'string'
19        ? compile(content, {
20            filename: filename
21        })
22        : renderFile(filename, content);
23    };
24
25
26    template.version = '3.0.0';
27
28
29    /**
30     * 设置全局配置
31     * @name    template.config
32     * @param   {String}    名称
33     * @param   {Any}       值
34     */
35    template.config = function (name, value) {
36        defaults[name] = value;
37    };
38
39
40
41    var defaults = template.defaults = {
42        openTag: '<%',    // 逻辑语法开始标签
43        closeTag: '%>',   // 逻辑语法结束标签
44        escape: true,     // 是否编码输出变量的 HTML 字符
45        cache: true,      // 是否开启缓存(依赖 options 的 filename 字段)
46        compress: false,  // 是否压缩输出
47        parser: null      // 自定义语法格式器 @see: template-syntax.js
48    };
49
50
51    var cacheStore = template.cache = {};
52
53
54    /**
55     * 渲染模板
56     * @name    template.render
57     * @param   {String}    模板
58     * @param   {Object}    数据
59     * @return  {String}    渲染好的字符串
60     */
61    template.render = function (source, options) {
62        return compile(source, options);
63    };
64
65
66    /**
67     * 渲染模板(根据模板名)
68     * @name    template.render
69     * @param   {String}    模板名
70     * @param   {Object}    数据
71     * @return  {String}    渲染好的字符串
72     */
73    var renderFile = template.renderFile = function (filename, data) {
74        var fn = template.get(filename) || showDebugInfo({
75            filename: filename,
76            name: 'Render Error',
77            message: 'Template not found'
78        });
79        return data ? fn(data) : fn;
80    };
81
82
83    /**
84     * 获取编译缓存(可由外部重写此方法)
85     * @param   {String}    模板名
86     * @param   {Function}  编译好的函数
87     */
88    template.get = function (filename) {
89
90        var cache;
91
92        if (cacheStore[filename]) {
93            // 使用内存缓存
94            cache = cacheStore[filename];
95        } else if (typeof document === 'object') {
96            // 加载模板并编译
97            var elem = document.getElementById(filename);
98
99            if (elem) {
100                var source = (elem.value || elem.innerHTML)
101                .replace(/^\s*|\s*$/g, '');
102                cache = compile(source, {
103                    filename: filename
104                });
105            }
106        }
107
108        return cache;
109    };
110
111
112    var toString = function (value, type) {
113
114        if (typeof value !== 'string') {
115
116            type = typeof value;
117            if (type === 'number') {
118                value += '';
119            } else if (type === 'function') {
120                value = toString(value.call(value));
121            } else {
122                value = '';
123            }
124        }
125
126        return value;
127
128    };
129
130
131    var escapeMap = {
132        "<": "&#60;",
133        ">": "&#62;",
134        '"': "&#34;",
135        "'": "&#39;",
136        "&": "&#38;"
137    };
138
139
140    var escapeFn = function (s) {
141        return escapeMap[s];
142    };
143
144    var escapeHTML = function (content) {
145        return toString(content)
146        .replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
147    };
148
149
150    var isArray = Array.isArray || function (obj) {
151        return ({}).toString.call(obj) === '[object Array]';
152    };
153
154
155    var each = function (data, callback) {
156        var i, len;
157        if (isArray(data)) {
158            for (i = 0, len = data.length; i < len; i++) {
159                callback.call(data, data[i], i, data);
160            }
161        } else {
162            for (i in data) {
163                callback.call(data, data[i], i);
164            }
165        }
166    };
167
168
169    var utils = template.utils = {
170
171        $helpers: {},
172
173        $include: renderFile,
174
175        $string: toString,
176
177        $escape: escapeHTML,
178
179        $each: each
180
181    };/**
182 * 添加模板辅助方法
183 * @name    template.helper
184 * @param   {String}    名称
185 * @param   {Function}  方法
186 */
187    template.helper = function (name, helper) {
188        helpers[name] = helper;
189    };
190
191    var helpers = template.helpers = utils.$helpers;
192
193
194
195
196    /**
197     * 模板错误事件(可由外部重写此方法)
198     * @name    template.onerror
199     * @event
200     */
201    template.onerror = function (e) {
202        var message = 'Template Error\n\n';
203        for (var name in e) {
204            message += '<' + name + '>\n' + e[name] + '\n\n';
205        }
206
207        if (typeof console === 'object') {
208            console.error(message);
209        }
210    };
211
212
213    // 模板调试器
214    var showDebugInfo = function (e) {
215
216        template.onerror(e);
217
218        return function () {
219            return '{Template Error}';
220        };
221    };
222
223
224    /**
225     * 编译模板
226     * 2012-6-6 @TooBug: define 方法名改为 compile,与 Node Express 保持一致
227     * @name    template.compile
228     * @param   {String}    模板字符串
229     * @param   {Object}    编译选项
230     *
231     *      - openTag       {String}
232     *      - closeTag      {String}
233     *      - filename      {String}
234     *      - escape        {Boolean}
235     *      - compress      {Boolean}
236     *      - debug         {Boolean}
237     *      - cache         {Boolean}
238     *      - parser        {Function}
239     *
240     * @return  {Function}  渲染方法
241     */
242    var compile = template.compile = function (source, options) {
243
244        // 合并默认配置
245        options = options || {};
246        for (var name in defaults) {
247            if (options[name] === undefined) {
248                options[name] = defaults[name];
249            }
250        }
251
252
253        var filename = options.filename;
254
255
256        try {
257
258            var Render = compiler(source, options);
259
260        } catch (e) {
261
262            e.filename = filename || 'anonymous';
263            e.name = 'Syntax Error';
264
265            return showDebugInfo(e);
266
267        }
268
269
270        // 对编译结果进行一次包装
271
272        function render(data) {
273
274            try {
275
276                return new Render(data, filename) + '';
277
278            } catch (e) {
279
280                // 运行时出错后自动开启调试模式重新编译
281                if (!options.debug) {
282                    options.debug = true;
283                    return compile(source, options)(data);
284                }
285
286                return showDebugInfo(e)();
287
288            }
289
290        }
291
292
293        render.prototype = Render.prototype;
294        render.toString = function () {
295            return Render.toString();
296        };
297
298
299        if (filename && options.cache) {
300            cacheStore[filename] = render;
301        }
302
303
304        return render;
305
306    };
307
308
309
310
311    // 数组迭代
312    var forEach = utils.$each;
313
314
315    // 静态分析模板变量
316    var KEYWORDS =
317        // 关键字
318        'break,case,catch,continue,debugger,default,delete,do,else,false'
319        + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
320        + ',throw,true,try,typeof,var,void,while,with'
321
322        // 保留字
323        + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
324        + ',final,float,goto,implements,import,int,interface,long,native'
325        + ',package,private,protected,public,short,static,super,synchronized'
326        + ',throws,transient,volatile'
327
328        // ECMA 5 - use strict
329        + ',arguments,let,yield'
330
331        + ',undefined';
332
333    var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
334    var SPLIT_RE = /[^\w$]+/g;
335    var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
336    var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
337    var BOUNDARY_RE = /^,+|,+$/g;
338    var SPLIT2_RE = /^$|,+/;
339
340
341    // 获取变量
342    function getVariable(code) {
343        return code
344        .replace(REMOVE_RE, '')
345        .replace(SPLIT_RE, ',')
346        .replace(KEYWORDS_RE, '')
347        .replace(NUMBER_RE, '')
348        .replace(BOUNDARY_RE, '')
349        .split(SPLIT2_RE);
350    };
351
352
353    // 字符串转义
354    function stringify(code) {
355        return "'" + code
356        // 单引号与反斜杠转义
357        .replace(/('|\\)/g, '\\$1')
358        // 换行符转义(windows + linux)
359        .replace(/\r/g, '\\r')
360        .replace(/\n/g, '\\n') + "'";
361    }
362
363
364    function compiler(source, options) {
365
366        var debug = options.debug;
367        var openTag = options.openTag;
368        var closeTag = options.closeTag;
369        var parser = options.parser;
370        var compress = options.compress;
371        var escape = options.escape;
372
373
374
375        var line = 1;
376        var uniq = { $data: 1, $filename: 1, $utils: 1, $helpers: 1, $out: 1, $line: 1 };
377
378
379
380        var isNewEngine = ''.trim;// '__proto__' in {}
381        var replaces = isNewEngine
382        ? ["$out='';", "$out+=", ";", "$out"]
383        : ["$out=[];", "$out.push(", ");", "$out.join('')"];
384
385        var concat = isNewEngine
386            ? "$out+=text;return $out;"
387            : "$out.push(text);";
388
389        var print = "function(){"
390        + "var text=''.concat.apply('',arguments);"
391        + concat
392        + "}";
393
394        var include = "function(filename,data){"
395        + "data=data||$data;"
396        + "var text=$utils.$include(filename,data,$filename);"
397        + concat
398        + "}";
399
400        var headerCode = "'use strict';"
401        + "var $utils=this,$helpers=$utils.$helpers,"
402        + (debug ? "$line=0," : "");
403
404        var mainCode = replaces[0];
405
406        var footerCode = "return new String(" + replaces[3] + ");"
407
408        // html与逻辑语法分离
409        forEach(source.split(openTag), function (code) {
410            code = code.split(closeTag);
411
412            var $0 = code[0];
413            var $1 = code[1];
414
415            // code: [html]
416            if (code.length === 1) {
417
418                mainCode += html($0);
419
420                // code: [logic, html]
421            } else {
422
423                mainCode += logic($0);
424
425                if ($1) {
426                    mainCode += html($1);
427                }
428            }
429
430
431        });
432
433        var code = headerCode + mainCode + footerCode;
434
435        // 调试语句
436        if (debug) {
437            code = "try{" + code + "}catch(e){"
438            + "throw {"
439            + "filename:$filename,"
440            + "name:'Render Error',"
441            + "message:e.message,"
442            + "line:$line,"
443            + "source:" + stringify(source)
444            + ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
445            + "};"
446            + "}";
447        }
448
449
450
451        try {
452
453
454            var Render = new Function("$data", "$filename", code);
455            Render.prototype = utils;
456
457            return Render;
458
459        } catch (e) {
460            e.temp = "function anonymous($data,$filename) {" + code + "}";
461            throw e;
462        }
463
464
465
466
467        // 处理 HTML 语句
468        function html(code) {
469
470            // 记录行号
471            line += code.split(/\n/).length - 1;
472
473            // 压缩多余空白与注释
474            if (compress) {
475                code = code
476                .replace(/\s+/g, ' ')
477                .replace(/<!--[\w\W]*?-->/g, '');
478            }
479
480            if (code) {
481                code = replaces[1] + stringify(code) + replaces[2] + "\n";
482            }
483
484            return code;
485        }
486
487
488        // 处理逻辑语句
489        function logic(code) {
490
491            var thisLine = line;
492
493            if (parser) {
494
495                // 语法转换插件钩子
496                code = parser(code, options);
497
498            } else if (debug) {
499
500                // 记录行号
501                code = code.replace(/\n/g, function () {
502                    line++;
503                    return "$line=" + line + ";";
504                });
505
506            }
507
508
509            // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
510            // <%=#value%> 等同 v2.0.3 之前的 <%==value%>
511            if (code.indexOf('=') === 0) {
512
513                var escapeSyntax = escape && !/^=[=#]/.test(code);
514
515                code = code.replace(/^=[=#]?|[\s;]*$/g, '');
516
517                // 对内容编码
518                if (escapeSyntax) {
519
520                    var name = code.replace(/\s*\([^\)]+\)/, '');
521
522                    // 排除 utils.* | include | print
523
524                    if (!utils[name] && !/^(include|print)$/.test(name)) {
525                        code = "$escape(" + code + ")";
526                    }
527
528                    // 不编码
529                } else {
530                    code = "$string(" + code + ")";
531                }
532
533
534                code = replaces[1] + code + replaces[2];
535
536            }
537
538            if (debug) {
539                code = "$line=" + thisLine + ";" + code;
540            }
541
542            // 提取模板中的变量名
543            forEach(getVariable(code), function (name) {
544
545                // name 值可能为空,在安卓低版本浏览器下
546                if (!name || uniq[name]) {
547                    return;
548                }
549
550                var value;
551
552                // 声明模板变量
553                // 赋值优先级:
554                // [include, print] > utils > helpers > data
555                if (name === 'print') {
556
557                    value = print;
558
559                } else if (name === 'include') {
560
561                    value = include;
562
563                } else if (utils[name]) {
564
565                    value = "$utils." + name;
566
567                } else if (helpers[name]) {
568
569                    value = "$helpers." + name;
570
571                } else {
572
573                    value = "$data." + name;
574                }
575
576                headerCode += name + "=" + value + ",";
577                uniq[name] = true;
578
579
580            });
581
582            return code + "\n";
583        }
584
585
586    };
587
588
589
590    // 定义模板引擎的语法
591
592
593    defaults.openTag = '{{';
594    defaults.closeTag = '}}';
595
596
597    var filtered = function (js, filter) {
598        var parts = filter.split(':');
599        var name = parts.shift();
600        var args = parts.join(':') || '';
601
602        if (args) {
603            args = ', ' + args;
604        }
605
606        return '$helpers.' + name + '(' + js + args + ')';
607    }
608
609
610    defaults.parser = function (code, options) {
611
612        // var match = code.match(/([\w\$]*)(\b.*)/);
613        // var key = match[1];
614        // var args = match[2];
615        // var split = args.split(' ');
616        // split.shift();
617
618        code = code.replace(/^\s/, '');
619
620        var split = code.split(' ');
621        var key = split.shift();
622        var args = split.join(' ');
623
624
625
626        switch (key) {
627
628            case 'if':
629
630                code = 'if(' + args + '){';
631                break;
632
633            case 'else':
634
635                if (split.shift() === 'if') {
636                    split = ' if(' + split.join(' ') + ')';
637                } else {
638                    split = '';
639                }
640
641                code = '}else' + split + '{';
642                break;
643
644            case '/if':
645
646                code = '}';
647                break;
648
649            case 'each':
650
651                var object = split[0] || '$data';
652                var as = split[1] || 'as';
653                var value = split[2] || '$value';
654                var index = split[3] || '$index';
655
656                var param = value + ',' + index;
657
658                if (as !== 'as') {
659                    object = '[]';
660                }
661
662                code = '$each(' + object + ',function(' + param + '){';
663                break;
664
665            case '/each':
666
667                code = '});';
668                break;
669
670            case 'echo':
671
672                code = 'print(' + args + ');';
673                break;
674
675            case 'print':
676            case 'include':
677
678                code = key + '(' + split.join(',') + ');';
679                break;
680
681            default:
682
683                // 过滤器(辅助方法)
684                // {{value | filterA:'abcd' | filterB}}
685                // >>> $helpers.filterB($helpers.filterA(value, 'abcd'))
686                // TODO: {{ddd||aaa}} 不包含空格
687                if (/^\s*\|\s*[\w\$]/.test(args)) {
688
689                    var escape = true;
690
691                    // {{#value | link}}
692                    if (code.indexOf('#') === 0) {
693                        code = code.substr(1);
694                        escape = false;
695                    }
696
697                    var i = 0;
698                    var array = code.split('|');
699                    var len = array.length;
700                    var val = array[i++];
701
702                    for (; i < len; i++) {
703                        val = filtered(val, array[i]);
704                    }
705
706                    code = (escape ? '=' : '=#') + val;
707
708                    // 即将弃用 {{helperName value}}
709                } else if (template.helpers[key]) {
710
711                    code = '=#' + key + '(' + split.join(',') + ');';
712
713                    // 内容直接输出 {{value}}
714                } else {
715
716                    code = '=' + code;
717                }
718
719                break;
720        }
721
722
723        return code;
724    };
725
726
727
728    // RequireJS && SeaJS
729    if (typeof define === 'function') {
730        define('artTemplate', [], function () {
731            return template;
732        });
733
734        // NodeJS
735    } else if (typeof exports !== 'undefined') {
736        module.exports = template;
737    } else {
738        this.template = template;
739    }
740
741})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.