diff --git a/test/browser/demo/async.js b/test/browser/demo/dep/async/0.0.0/async.js similarity index 100% rename from test/browser/demo/async.js rename to test/browser/demo/dep/async/0.0.0/async.js diff --git a/test/browser/demo/baidubce-sdk.bundle.js b/test/browser/demo/dep/baidubce-sdk/0.0.0/baidubce-sdk.bundle.js similarity index 100% rename from test/browser/demo/baidubce-sdk.bundle.js rename to test/browser/demo/dep/baidubce-sdk/0.0.0/baidubce-sdk.bundle.js diff --git a/test/browser/demo/dep/etpl/3.0.0.md5 b/test/browser/demo/dep/etpl/3.0.0.md5 new file mode 100644 index 0000000..f3ff9a4 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0.md5 @@ -0,0 +1,11 @@ +{ + "LICENSE": "5c597a93416a6b2fd475a4b6a5ae567f", + "README.md": "112e5f8aef658636f95f3829c0438d7a", + "doc/api.md": "3f730a37c3a97efe68b40981ae095d2b", + "doc/config.md": "8ebb6ec1a5d1a7c006ec7ac0371278d9", + "doc/syntax.md": "3c25984820a384c9149163f49846980b", + "main.js": "5c131f6789741ceba9117a95552d2918", + "package.json": "a0ec59a0969ab0b8baf493c5143689ac", + "src/main.js": "41a67fe16af3251b578f1bd0bd3f0b3b", + "src/tpl.js": "012520cf5f6e01fba5293ef27106b3ea" +} \ No newline at end of file diff --git a/test/browser/demo/dep/etpl/3.0.0/.editorconfig b/test/browser/demo/dep/etpl/3.0.0/.editorconfig new file mode 100644 index 0000000..be222b1 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/.editorconfig @@ -0,0 +1,38 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[**.js] +indent_style = space +indent_size = 4 + +[**.css] +indent_style = space +indent_size = 4 + +[**.less] +indent_style = space +indent_size = 4 + +[**.styl] +indent_style = space +indent_size = 4 + +[**.html] +indent_style = space +indent_size = 4 + +[**.tpl] +indent_style = space +indent_size = 4 + +[**.json] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/test/browser/demo/dep/etpl/3.0.0/.npmignore b/test/browser/demo/dep/etpl/3.0.0/.npmignore new file mode 100644 index 0000000..32bcdf1 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/.npmignore @@ -0,0 +1 @@ +/test \ No newline at end of file diff --git a/test/browser/demo/dep/etpl/3.0.0/.travis.yml b/test/browser/demo/dep/etpl/3.0.0/.travis.yml new file mode 100644 index 0000000..a46b328 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - 0.10 +branches: + only: + - master \ No newline at end of file diff --git a/test/browser/demo/dep/etpl/3.0.0/LICENSE b/test/browser/demo/dep/etpl/3.0.0/LICENSE new file mode 100644 index 0000000..9723a0c --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013, Baidu Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or +without modification, are permitted provided that the following conditions +are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +Neither the name of Baidu Inc. nor the names of its contributors may be used +to endorse or promote products derived from this software without specific +prior written permission of Baidu Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test/browser/demo/dep/etpl/3.0.0/README.md b/test/browser/demo/dep/etpl/3.0.0/README.md new file mode 100644 index 0000000..0d89cb5 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/README.md @@ -0,0 +1,117 @@ +# ETPL (Enterprise Template) + +[![Build Status](https://travis-ci.org/ecomfe/etpl.svg?branch=master)](https://travis-ci.org/ecomfe/etpl) + +ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。 + + +## Start + +ETpl可以在`CommonJS/AMD`的模块定义环境中使用,也能直接在页面下通过`script`标签引用。 + + +### 浏览器环境 + +直接通过script标签引用,你可以获得一个全局的`etpl`变量 + +```html + +``` + +在AMD环境的模块定义时,你可以通过`同步require`获得ETpl模块 + +```javascript +define(function (require) { + var etpl = require('etpl'); +}); +``` + +在AMD环境,你也可以通过`异步require`获得ETpl模块 + +```javascript +require([ 'etpl' ], function (etpl) { +}); +``` + +*在AMD环境下,请确保你的require.config配置能够让Loader找到ETpl模块* + +### Node.JS环境 + +你可以通过`npm`来安装ETpl + +``` +$ npm install etpl +``` + +安装完成后,你就可以通过`require`获得一个ETpl模块,正常地使用它 + +```javascript +var etpl = require('etpl'); +``` + +### 使用 + +使用ETPL模块,对模板源代码进行编译,会能得到编译后的function + +```javascript +var render = etpl.compile('Hello ${name}!'); +``` + +执行这个function,传入数据对象,就能得到模板执行的结果了 + +```javascript +var text = render({ name: 'etpl' }); +``` + +查看更多例子,或者对模板渲染结果有疑虑,就去ETPL的[example](http://ecomfe.github.io/etpl/example.html)看看吧。 + + +## Documents + +通过文档,你可以更详细地了解ETpl的语法格式、使用方法、API等内容。 + +- [模板语法](doc/syntax.md) +- [API](doc/api.md) +- [配置参数](doc/config.md) + + +## Compatibility + +### ETpl3的新语法 + +我们认为,当前流行的通过`block`来表达模板继承中的变化,是更好的表达方式。所以在ETpl3中,我们优化了母版的语法,删除了`master`、`contentplacehoder`、`content`标签,引入了`block`标签。 + +对于ETpl2的使用者,我们提供一个[etpl2to3](https://github.com/ecomfe/etpl2to3)工具,能够帮助你平滑地将ETpl2的模板翻译成ETpl3。 + + +### get + +ETpl2中,为了前向兼容,Engine的`get`方法可以根据target名称获取模板内容。 + +ETpl3不再支持该方法,所有的模板都通过render来使用: + +- 直接使用engine实例的render方法 +- 调用renderer function + +如果仍需要该功能,说明你正在维护一个遗留系统,并且没有很频繁的升级需求。请继续使用ETpl2。 + + +### merge + +ETpl的前身是[ER框架](https://github.com/ecomfe/er)自带的简易模板引擎,其基本与前身保持兼容。但出于代码体积和使用频度的考虑,ETpl删除了`merge`API。如果想要该API,请在自己的应用中加入如下代码: + +```javascript +/** + * 执行模板渲染,并将渲染后的字符串作为innerHTML填充到HTML元素中。 + * 兼容老版本的模板引擎api + * + * @param {HTMLElement} element 渲染字符串填充的HTML元素 + * @param {string} name target名称 + * @param {Object=} data 模板数据 + */ +etpl.merge = function ( element, name, data ) { + if ( element ) { + element.innerHTML = this.render( name, data ); + } +}; +``` diff --git a/test/browser/demo/dep/etpl/3.0.0/main.js b/test/browser/demo/dep/etpl/3.0.0/main.js new file mode 100644 index 0000000..f75835e --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/main.js @@ -0,0 +1,9 @@ +/** + * ETPL (Enterprise Template) + * Copyright 2013 Baidu Inc. All rights reserved. + * + * @file Node入口 + * @author firede(firede@firede.us) + */ + +module.exports = require('./src/main'); diff --git a/test/browser/demo/dep/etpl/3.0.0/package.json b/test/browser/demo/dep/etpl/3.0.0/package.json new file mode 100644 index 0000000..248ffe0 --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/package.json @@ -0,0 +1,19 @@ +{ + "name": "etpl", + "version": "3.0.0", + "contributors": [ + { "name": "erik", "email": "errorrik@gmail.com" }, + { "name": "otakustay", "email": "otakustay@gmail.com" }, + { "name": "firede", "email": "firede@firede.us" } + ], + "main": "main", + "homepage": "http://ecomfe.github.io/etpl/", + "repository": "git://github.com/ecomfe/etpl", + "description": "ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。", + "scripts": { + "test": "jasmine-node test/spec" + }, + "devDependencies": { + "jasmine-node": "1.14.2" + } +} diff --git a/test/browser/demo/dep/etpl/3.0.0/src/main.js b/test/browser/demo/dep/etpl/3.0.0/src/main.js new file mode 100644 index 0000000..0eb2afe --- /dev/null +++ b/test/browser/demo/dep/etpl/3.0.0/src/main.js @@ -0,0 +1,1645 @@ +/** + * ETPL (Enterprise Template) + * Copyright 2013 Baidu Inc. All rights reserved. + * + * @file 模板引擎 + * @author errorrik(errorrik@gmail.com) + * otakustay(otakustay@gmail.com) + */ + + +// HACK: 可见的重复代码未抽取成function和var是为了gzip size,吐槽的一边去 + +(function (root) { + /** + * 对象属性拷贝 + * + * @inner + * @param {Object} target 目标对象 + * @param {Object} source 源对象 + * @return {Object} 返回目标对象 + */ + function extend(target, source) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + target[key] = source[key]; + } + } + + return target; + } + + /** + * 随手写了个栈 + * + * @inner + * @constructor + */ + function Stack() { + this.raw = []; + this.length = 0; + } + + Stack.prototype = { + /** + * 添加元素进栈 + * + * @param {*} elem 添加项 + */ + push: function (elem) { + this.raw[this.length++] = elem; + }, + + /** + * 弹出顶部元素 + * + * @return {*} + */ + pop: function () { + if (this.length > 0) { + var elem = this.raw[--this.length]; + this.raw.length = this.length; + return elem; + } + }, + + /** + * 获取顶部元素 + * + * @return {*} + */ + top: function () { + return this.raw[this.length - 1]; + }, + + /** + * 获取底部元素 + * + * @return {*} + */ + bottom: function () { + return this.raw[0]; + }, + + /** + * 根据查询条件获取元素 + * + * @param {Function} condition 查询函数 + * @return {*} + */ + find: function (condition) { + var index = this.length; + while (index--) { + var item = this.raw[index]; + if (condition(item)) { + return item; + } + } + } + }; + + /** + * 唯一id的起始值 + * + * @inner + * @type {number} + */ + var guidIndex = 0x2B845; + + /** + * 获取唯一id,用于匿名target或编译代码的变量名生成 + * + * @inner + * @return {string} + */ + function generateGUID() { + return '___' + (guidIndex++); + } + + /** + * 构建类之间的继承关系 + * + * @inner + * @param {Function} subClass 子类函数 + * @param {Function} superClass 父类函数 + */ + function inherits(subClass, superClass) { + /* jshint -W054 */ + var F = new Function(); + F.prototype = superClass.prototype; + subClass.prototype = new F(); + subClass.prototype.constructor = subClass; + /* jshint +W054 */ + // 由于引擎内部的使用场景都是inherits后,逐个编写子类的prototype方法 + // 所以,不考虑将原有子类prototype缓存再逐个拷贝回去 + } + + /** + * HTML Filter替换的字符实体表 + * + * @const + * @inner + * @type {Object} + */ + var HTML_ENTITY = { + /* jshint ignore:start */ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + /* jshint ignore:end */ + }; + + /** + * HTML Filter的替换函数 + * + * @inner + * @param {string} c 替换字符 + * @return {string} + */ + function htmlFilterReplacer(c) { + return HTML_ENTITY[c]; + } + + /** + * 默认filter + * + * @inner + * @const + * @type {Object} + */ + var DEFAULT_FILTERS = { + /** + * HTML转义filter + * + * @param {string} source 源串 + * @return {string} + */ + html: function (source) { + return source.replace(/[&<>"']/g, htmlFilterReplacer); + }, + + /** + * URL编码filter + * + * @param {string} source 源串 + * @return {string} + */ + url: encodeURIComponent, + + /** + * 源串filter,用于在默认开启HTML转义时获取源串,不进行转义 + * + * @param {string} source 源串 + * @return {string} + */ + raw: function (source) { + return source; + } + }; + + /** + * 字符串字面化 + * + * @inner + * @param {string} source 需要字面化的字符串 + * @return {string} + */ + function stringLiteralize(source) { + return '"' + + source + .replace(/\x5C/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x0A/g, '\\n') + .replace(/\x09/g, '\\t') + .replace(/\x0D/g, '\\r') + // .replace( /\x08/g, '\\b' ) + // .replace( /\x0C/g, '\\f' ) + + '"'; + } + + /** + * 对字符串进行可用于new RegExp的字面化 + * + * @inner + * @param {string} source 需要字面化的字符串 + * @return {string} + */ + function regexpLiteral(source) { + return source.replace(/[\^\[\]\$\(\)\{\}\?\*\.\+]/g, function (c) { + return '\\' + c; + }); + } + + /** + * 字符串格式化 + * + * @inner + * @param {string} source 目标模版字符串 + * @param {...string} replacements 字符串替换项集合 + * @return {string} + */ + function stringFormat(source) { + var args = arguments; + return source.replace( + /\{([0-9]+)\}/g, + function (match, index) { + return args[index - 0 + 1]; + }); + } + + /** + * 用于render的字符串变量声明语句 + * + * @inner + * @const + * @type {string} + */ + var RENDER_STRING_DECLATION = 'var r="";'; + + /** + * 用于render的字符串内容添加语句(起始) + * + * @inner + * @const + * @type {string} + */ + var RENDER_STRING_ADD_START = 'r+='; + + /** + * 用于render的字符串内容添加语句(结束) + * + * @inner + * @const + * @type {string} + */ + var RENDER_STRING_ADD_END = ';'; + + /** + * 用于render的字符串内容返回语句 + * + * @inner + * @const + * @type {string} + */ + var RENDER_STRING_RETURN = 'return r;'; + + // HACK: IE8-时,编译后的renderer使用join Array的策略进行字符串拼接 + if (typeof navigator !== 'undefined' + && /msie\s*([0-9]+)/i.test(navigator.userAgent) + && RegExp.$1 - 0 < 8 + ) { + RENDER_STRING_DECLATION = 'var r=[],ri=0;'; + RENDER_STRING_ADD_START = 'r[ri++]='; + RENDER_STRING_RETURN = 'return r.join("");'; + } + + /** + * 将访问变量名称转换成getVariable调用的编译语句 + * 用于if、var等命令生成编译代码 + * + * @inner + * @param {string} name 访问变量名 + * @return {string} + */ + function toGetVariableLiteral(name) { + name = name.replace(/^\s*\*/, ''); + return stringFormat( + 'gv({0},["{1}"])', + stringLiteralize(name), + name.replace( + /\[['"]?([^'"]+)['"]?\]/g, + function (match, name) { + return '.' + name; + } + ) + .split('.') + .join('","') + ); + } + + /** + * 解析文本片段中以固定字符串开头和结尾的包含块 + * 用于 命令串: 和 变量替换串:${...} 的解析 + * + * @inner + * @param {string} source 要解析的文本 + * @param {string} open 包含块开头 + * @param {string} close 包含块结束 + * @param {boolean} greedy 是否贪婪匹配 + * @param {function({string})} onInBlock 包含块内文本的处理函数 + * @param {function({string})} onOutBlock 非包含块内文本的处理函数 + */ + function parseTextBlock(source, open, close, greedy, onInBlock, onOutBlock) { + var closeLen = close.length; + var texts = source.split(open); + var level = 0; + var buf = []; + + for (var i = 0, len = texts.length; i < len; i++) { + var text = texts[i]; + + if (i) { + var openBegin = 1; + level++; + while (1) { + var closeIndex = text.indexOf(close); + if (closeIndex < 0) { + buf.push(level > 1 && openBegin ? open : '', text); + break; + } + + level = greedy ? level - 1 : 0; + buf.push( + level > 0 && openBegin ? open : '', + text.slice(0, closeIndex), + level > 0 ? close : '' + ); + text = text.slice(closeIndex + closeLen); + openBegin = 0; + + if (level === 0) { + break; + } + } + + if (level === 0) { + onInBlock(buf.join('')); + onOutBlock(text); + buf = []; + } + } + else { + text && onOutBlock(text); + } + } + + if (level > 0 && buf.length > 0) { + onOutBlock(open); + onOutBlock(buf.join('')); + } + } + + /** + * 编译变量访问和变量替换的代码 + * 用于普通文本或if、var、filter等命令生成编译代码 + * + * @inner + * @param {string} source 源代码 + * @param {Engine} engine 引擎实例 + * @param {boolean} forText 是否为输出文本的变量替换 + * @return {string} + */ + function compileVariable(source, engine, forText) { + var code = []; + var options = engine.options; + + var toStringHead = ''; + var toStringFoot = ''; + var wrapHead = ''; + var wrapFoot = ''; + + // 默认的filter,当forText模式时有效 + var defaultFilter; + + if (forText) { + toStringHead = 'ts('; + toStringFoot = ')'; + wrapHead = RENDER_STRING_ADD_START; + wrapFoot = RENDER_STRING_ADD_END; + defaultFilter = options.defaultFilter; + } + + parseTextBlock( + source, options.variableOpen, options.variableClose, 1, + + function (text) { + // 加入默认filter + // 只有当处理forText时,需要加入默认filter + // 处理if/var/use等command时,不需要加入默认filter + if (forText && text.indexOf('|') < 0 && defaultFilter) { + text += '|' + defaultFilter; + } + + // variableCode是一个gv调用,然后通过循环,在外面包filter的调用 + // 形成filter["b"](filter["a"](gv(...))) + // + // 当forText模式,处理的是文本中的变量替换时 + // 传递给filter的需要是字符串形式,所以gv外需要包一层ts调用 + // 形成filter["b"](filter["a"](ts(gv(...)))) + // + // 当variableName以*起始时,忽略ts调用,直接传递原值给filter + var filterCharIndex = text.indexOf('|'); + var variableName = ( + filterCharIndex > 0 + ? text.slice(0, filterCharIndex) + : text + ).replace(/^\s+/, '').replace(/\s+$/, ''); + var filterSource = filterCharIndex > 0 + ? text.slice(filterCharIndex + 1) + : ''; + + var variableRawValue = variableName.indexOf('*') === 0; + var variableCode = [ + variableRawValue ? '' : toStringHead, + toGetVariableLiteral(variableName), + variableRawValue ? '' : toStringFoot + ]; + + if (filterSource) { + filterSource = compileVariable(filterSource, engine); + var filterSegs = filterSource.split('|'); + for (var i = 0, len = filterSegs.length; i < len; i++) { + var seg = filterSegs[i]; + + if (/^\s*([a-z0-9_-]+)(\((.*)\))?\s*$/i.test(seg)) { + variableCode.unshift('fs["' + RegExp.$1 + '"]('); + + if (RegExp.$3) { + variableCode.push(',', RegExp.$3); + } + + variableCode.push(')'); + } + } + } + + code.push( + wrapHead, + variableCode.join(''), + wrapFoot + ); + }, + + function (text) { + code.push( + wrapHead, + forText ? stringLiteralize(text) : text, + wrapFoot + ); + } + ); + + return code.join(''); + } + + /** + * 文本节点类 + * + * @inner + * @constructor + * @param {string} value 文本节点的内容文本 + * @param {Engine} engine 引擎实例 + */ + function TextNode(value, engine) { + this.value = value; + this.engine = engine; + } + + TextNode.prototype = { + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + getRendererBody: function () { + var value = this.value; + var options = this.engine.options; + + if (!value + || (options.strip && /^\s*$/.test(value)) + ) { + return ''; + } + + return compileVariable(value, this.engine, 1); + }, + + /** + * 复制节点的方法 + * + * @return {TextNode} + */ + clone: function () { + return this; + } + }; + + /** + * 命令节点类 + * + * @inner + * @constructor + * @param {string} value 命令节点的value + * @param {Engine} engine 引擎实例 + */ + function Command(value, engine) { + this.value = value; + this.engine = engine; + this.children = []; + this.cloneProps = []; + } + + Command.prototype = { + /** + * 添加子节点 + * + * @param {TextNode|Command} node 子节点 + */ + addChild: function (node) { + this.children.push(node); + }, + + /** + * 节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + open: function (context) { + var parent = context.stack.top(); + parent && parent.addChild(this); + context.stack.push(this); + }, + + /** + * 节点闭合,解析结束 + * + * @param {Object} context 语法分析环境对象 + */ + close: function (context) { + if (context.stack.top() === this) { + context.stack.pop(); + } + }, + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + getRendererBody: function () { + var buf = []; + var children = this.children; + for (var i = 0; i < children.length; i++) { + buf.push(children[i].getRendererBody()); + } + + return buf.join(''); + }, + + /** + * 复制节点的方法 + * + * @return {Command} + */ + clone: function () { + var node = new this.constructor(this.value, this.engine); + for (var i = 0, l = this.children.length; i < l; i++) { + node.addChild(this.children[i].clone()); + } + + for (var i = 0, l = this.cloneProps.length; i < l; i++) { + var prop = this.cloneProps[i]; + node[prop] = this[prop]; + } + + return node; + } + }; + + /** + * 命令自动闭合 + * + * @inner + * @param {Object} context 语法分析环境对象 + * @param {Function=} CommandType 自闭合的节点类型 + */ + function autoCloseCommand(context, CommandType) { + var stack = context.stack; + var closeEnd = CommandType + ? stack.find( + function (item) { + return item instanceof CommandType; + } + ) + : stack.bottom(); + + if (closeEnd) { + var node; + + while ((node = stack.top()) !== closeEnd) { + /* jshint ignore:start */ + // 如果节点对象不包含autoClose方法 + // 则认为该节点不支持自动闭合,需要抛出错误 + // for等节点不支持自动闭合 + if (!node.autoClose) { + throw new Error(node.type + ' must be closed manually: ' + node.value); + } + /* jshint ignore:end */ + + node.autoClose(context); + } + + closeEnd.close(context); + } + + return closeEnd; + } + + /** + * renderer body起始代码段 + * + * @inner + * @const + * @type {string} + */ + var RENDERER_BODY_START = '' + + 'data=data||{};' + + 'var v={},fs=engine.filters,hg=typeof data.get=="function",' + + 'gv=function(n,ps){' + + 'var p=ps[0],d=v[p];' + + 'if(d==null){' + + 'if(hg){return data.get(n);}' + + 'd=data[p];' + + '}' + + 'for(var i=1,l=ps.length;i= TargetState.APPLIED) { + return 1; + } + + var blocks = this.blocks; + + function replaceBlock(node) { + var children = node.children; + + if (children instanceof Array) { + for (var i = 0, len = children.length; i < len; i++) { + var child = children[i]; + if (child instanceof BlockCommand && blocks[child.name]) { + child = children[i] = blocks[child.name]; + } + + replaceBlock(child); + } + } + } + + var master = this.engine.targets[masterName]; + if (master && master.applyMaster(master.master)) { + this.children = master.clone().children; + replaceBlock(this); + this.state = TargetState.APPLIED; + return 1; + } + }; + + /** + * 判断target是否ready + * 包括是否成功应用母版,以及import语句依赖的target是否ready + * + * @return {boolean} + */ + TargetCommand.prototype.isReady = function () { + if (this.state >= TargetState.READY) { + return 1; + } + + var engine = this.engine; + var readyState = 1; + + /** + * 递归检查节点的ready状态 + * + * @inner + * @param {Command|TextNode} node 目标节点 + */ + function checkReadyState(node) { + for (var i = 0, len = node.children.length; i < len; i++) { + var child = node.children[i]; + if (child instanceof ImportCommand) { + var target = engine.targets[child.name]; + readyState = readyState + && target && target.isReady(engine); + } + else if (child instanceof Command) { + checkReadyState(child); + } + } + } + + if (this.applyMaster(this.master)) { + checkReadyState(this); + readyState && (this.state = TargetState.READY); + return readyState; + } + }; + + /** + * 获取target的renderer函数 + * + * @return {function(Object):string} + */ + TargetCommand.prototype.getRenderer = function () { + if (this.renderer) { + return this.renderer; + } + + if (this.isReady()) { + // console.log(this.name + ' ------------------'); + // console.log(RENDERER_BODY_START + RENDER_STRING_DECLATION + // + this.getRendererBody() + // + RENDER_STRING_RETURN); + + /* jshint -W054 */ + var realRenderer = new Function( + 'data', 'engine', + [ + RENDERER_BODY_START, + RENDER_STRING_DECLATION, + this.getRendererBody(), + RENDER_STRING_RETURN + ].join('\n') + ); + /* jshint +W054 */ + + var engine = this.engine; + this.renderer = function (data) { + return realRenderer(data, engine); + }; + + return this.renderer; + } + + return null; + }; + + /** + * 将target节点对象添加到语法分析环境中 + * + * @inner + * @param {TargetCommand} target target节点对象 + * @param {Object} context 语法分析环境对象 + */ + function addTargetToContext(target, context) { + context.target = target; + + var engine = context.engine; + var name = target.name; + + if (engine.targets[name]) { + switch (engine.options.namingConflict) { + /* jshint ignore:start */ + case 'override': + engine.targets[name] = target; + context.targets.push(name); + case 'ignore': + break; + /* jshint ignore:end */ + default: + throw new Error('Target exists: ' + name); + } + } + else { + engine.targets[name] = target; + context.targets.push(name); + } + } + + /** + * target节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + TargetCommand.prototype.open = function (context) { + autoCloseCommand(context); + Command.prototype.open.call(this, context); + this.state = TargetState.READING; + addTargetToContext(this, context); + }; + + /** + * Var节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + VarCommand.prototype.open = + + /** + * Use节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + UseCommand.prototype.open = function (context) { + context.stack.top().addChild(this); + }; + + /** + * Block节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + BlockCommand.prototype.open = function (context) { + Command.prototype.open.call(this, context); + (context.imp || context.target).blocks[this.name] = this; + }; + + /** + * elif节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + ElifCommand.prototype.open = function (context) { + var elseCommand = new ElseCommand(); + elseCommand.open(context); + + var ifCommand = autoCloseCommand(context, IfCommand); + ifCommand.addChild(this); + context.stack.push(this); + }; + + /** + * else节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + ElseCommand.prototype.open = function (context) { + var ifCommand = autoCloseCommand(context, IfCommand); + ifCommand.addChild(this); + context.stack.push(this); + }; + + /** + * import节点open,解析开始 + * + * @param {Object} context 语法分析环境对象 + */ + ImportCommand.prototype.open = function (context) { + this.parent = context.stack.top(); + this.target = context.target; + Command.prototype.open.call(this, context); + this.state = TargetState.READING; + context.imp = this; + }; + + /** + * 节点解析结束 + * 由于use节点无需闭合,处理时不会入栈,所以将close置为空函数 + * + * @param {Object} context 语法分析环境对象 + */ + UseCommand.prototype.close = + + /** + * 节点解析结束 + * 由于var节点无需闭合,处理时不会入栈,所以将close置为空函数 + * + * @param {Object} context 语法分析环境对象 + */ + VarCommand.prototype.close = function () {}; + + /** + * 节点解析结束 + * + * @param {Object} context 语法分析环境对象 + */ + ImportCommand.prototype.close = function (context) { + Command.prototype.close.call(this, context); + this.state = TargetState.READED; + context.imp = null; + }; + + /** + * 节点闭合,解析结束 + * + * @param {Object} context 语法分析环境对象 + */ + TargetCommand.prototype.close = function (context) { + Command.prototype.close.call(this, context); + this.state = this.master ? TargetState.READED : TargetState.APPLIED; + context.target = null; + }; + + /** + * 节点自动闭合,解析结束 + * ImportCommand的自动结束逻辑为,在其开始位置后马上结束 + * 所以,其自动结束时children应赋予其所属的parent + * + * @param {Object} context 语法分析环境对象 + */ + ImportCommand.prototype.autoClose = function (context) { + // move children to parent + var parentChildren = this.parent.children; + parentChildren.push.apply(parentChildren, this.children); + this.children.length = 0; + + // move blocks to target + for (var key in this.blocks) { + this.target.blocks[key] = this.blocks[key]; + } + this.blocks = {}; + + // do close + this.close(context); + }; + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + UseCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + ImportCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + VarCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + ForCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + FilterCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + BlockCommand.prototype.beforeOpen = + + /** + * 节点open前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + IfCommand.prototype.beforeOpen = + + /** + * 文本节点被添加到分析环境前的处理动作:节点不在target中时,自动创建匿名target + * + * @param {Object} context 语法分析环境对象 + */ + TextNode.prototype.beforeAdd = function (context) { + if (context.stack.bottom()) { + return; + } + + var target = new TargetCommand(generateGUID(), context.engine); + target.open(context); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + ImportCommand.prototype.getRendererBody = function () { + this.applyMaster(this.name); + return Command.prototype.getRendererBody.call(this); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + UseCommand.prototype.getRendererBody = function () { + return stringFormat( + '{0}engine.render({2},{{3}}){1}', + RENDER_STRING_ADD_START, + RENDER_STRING_ADD_END, + stringLiteralize(this.name), + compileVariable(this.args, this.engine).replace( + /(^|,)\s*([a-z0-9_]+)\s*=/ig, + function (match, start, argName) { + return (start || '') + stringLiteralize(argName) + ':'; + } + ) + ); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + VarCommand.prototype.getRendererBody = function () { + if (this.expr) { + return stringFormat( + 'v[{0}]={1};', + stringLiteralize(this.name), + compileVariable(this.expr, this.engine) + ); + } + + return ''; + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + IfCommand.prototype.getRendererBody = function () { + return stringFormat( + 'if({0}){{1}}', + compileVariable(this.value, this.engine), + Command.prototype.getRendererBody.call(this) + ); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + ElseCommand.prototype.getRendererBody = function () { + return stringFormat( + '}else{{0}', + Command.prototype.getRendererBody.call(this) + ); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + ForCommand.prototype.getRendererBody = function () { + return stringFormat( + /* jshint ignore:start */ + '' + + 'var {0}={1};' + + 'if({0} instanceof Array)' + + 'for (var {4}=0,{5}={0}.length;{4}<{5};{4}++){v[{2}]={4};v[{3}]={0}[{4}];{6}}' + + 'else if(typeof {0}==="object")' + + 'for(var {4} in {0}){v[{2}]={4};v[{3}]={0}[{4}];{6}}', + /* jshint ignore:end */ + generateGUID(), + compileVariable(this.list, this.engine), + stringLiteralize(this.index || generateGUID()), + stringLiteralize(this.item), + generateGUID(), + generateGUID(), + Command.prototype.getRendererBody.call(this) + ); + }; + + /** + * 获取renderer body的生成代码 + * + * @return {string} + */ + FilterCommand.prototype.getRendererBody = function () { + var args = this.args; + return stringFormat( + '{2}fs[{5}]((function(){{0}{4}{1}})(){6}){3}', + RENDER_STRING_DECLATION, + RENDER_STRING_RETURN, + RENDER_STRING_ADD_START, + RENDER_STRING_ADD_END, + Command.prototype.getRendererBody.call(this), + stringLiteralize(this.name), + args ? ',' + compileVariable(args, this.engine) : '' + ); + }; + + /** + * 命令类型集合 + * + * @type {Object} + */ + var commandTypes = {}; + + /** + * 添加命令类型 + * + * @inner + * @param {string} name 命令名称 + * @param {Function} Type 处理命令用到的类 + */ + function addCommandType(name, Type) { + commandTypes[name] = Type; + Type.prototype.type = name; + } + + addCommandType('target', TargetCommand); + addCommandType('block', BlockCommand); + addCommandType('import', ImportCommand); + addCommandType('use', UseCommand); + addCommandType('var', VarCommand); + addCommandType('for', ForCommand); + addCommandType('if', IfCommand); + addCommandType('elif', ElifCommand); + addCommandType('else', ElseCommand); + addCommandType('filter', FilterCommand); + + + /** + * etpl引擎类 + * + * @constructor + * @param {Object=} options 引擎参数 + * @param {string=} options.commandOpen 命令语法起始串 + * @param {string=} options.commandClose 命令语法结束串 + * @param {string=} options.variableOpen 变量语法起始串 + * @param {string=} options.variableClose 变量语法结束串 + * @param {string=} options.defaultFilter 默认变量替换的filter + * @param {boolean=} options.strip 是否清除命令标签前后的空白字符 + * @param {string=} options.namingConflict target名字冲突时的处理策略 + */ + function Engine(options) { + this.options = { + commandOpen: '', + commandSyntax: /^\s*(\/)?([a-z]+)\s*(?::([\s\S]*))?$/, + variableOpen: '${', + variableClose: '}', + defaultFilter: 'html' + }; + + this.config(options); + this.targets = {}; + this.filters = extend({}, DEFAULT_FILTERS); + } + + /** + * 配置引擎参数,设置的参数将被合并到现有参数中 + * + * @param {Object} options 参数对象 + * @param {string=} options.commandOpen 命令语法起始串 + * @param {string=} options.commandClose 命令语法结束串 + * @param {string=} options.variableOpen 变量语法起始串 + * @param {string=} options.variableClose 变量语法结束串 + * @param {string=} options.defaultFilter 默认变量替换的filter + * @param {boolean=} options.strip 是否清除命令标签前后的空白字符 + * @param {string=} options.namingConflict target名字冲突时的处理策略 + */ + Engine.prototype.config = function (options) { + extend(this.options, options); + }; + + /** + * 解析模板并编译,返回第一个target编译后的renderer函数。 + * + * @param {string} source 模板源代码 + * @return {function(Object):string} + */ + Engine.prototype.compile = + + /** + * 解析模板并编译,返回第一个target编译后的renderer函数。 + * 该方法的存在为了兼容老模板引擎 + * + * @param {string} source 模板源代码 + * @return {function(Object):string} + */ + Engine.prototype.parse = function (source) { + if (source) { + var targetNames = parseSource(source, this); + if (targetNames.length) { + return this.targets[targetNames[0]].getRenderer(); + } + } + + /* jshint -W054 */ + return new Function('return ""'); + /* jshint +W054 */ + }; + + /** + * 根据target名称获取编译后的renderer函数 + * + * @param {string} name target名称 + * @return {function(Object):string} + */ + Engine.prototype.getRenderer = function (name) { + var target = this.targets[name]; + if (target) { + return target.getRenderer(); + } + }; + + /** + * 执行模板渲染,返回渲染后的字符串。 + * + * @param {string} name target名称 + * @param {Object=} data 模板数据。 + * 可以是plain object, + * 也可以是带有 {string}get({string}name) 方法的对象 + * @return {string} + */ + Engine.prototype.render = function (name, data) { + var renderer = this.getRenderer(name); + if (renderer) { + return renderer(data); + } + + return ''; + }; + + /** + * 增加过滤器 + * + * @param {string} name 过滤器名称 + * @param {Function} filter 过滤函数 + */ + Engine.prototype.addFilter = function (name, filter) { + if (typeof filter === 'function') { + this.filters[name] = filter; + } + }; + + /** + * 解析源代码 + * + * @inner + * @param {string} source 模板源代码 + * @param {Engine} engine 引擎实例 + * @return {Array} target名称列表 + */ + function parseSource(source, engine) { + var commandOpen = engine.options.commandOpen; + var commandClose = engine.options.commandClose; + var commandSyntax = engine.options.commandSyntax; + + var stack = new Stack(); + var analyseContext = { + engine: engine, + targets: [], + stack: stack, + target: null + }; + + // text节点内容缓冲区,用于合并多text + var textBuf = []; + + /** + * 将缓冲区中的text节点内容写入 + * + * @inner + */ + function flushTextBuf() { + var text; + if (textBuf.length > 0 && (text = textBuf.join(''))) { + var textNode = new TextNode(text, engine); + textNode.beforeAdd(analyseContext); + + stack.top().addChild(textNode); + textBuf = []; + + if (engine.options.strip + && analyseContext.current instanceof Command + ) { + textNode.value = text.replace(/^[\x20\t\r]*\n/, ''); + } + analyseContext.current = textNode; + } + } + + var NodeType; + + parseTextBlock( + source, commandOpen, commandClose, 0, + + function (text) { // 内文本的处理函数 + var match = commandSyntax.exec(text); + + // 符合command规则,并且存在相应的Command类,说明是合法有含义的Command + // 否则,为不具有command含义的普通文本 + if (match + && (NodeType = commandTypes[match[2].toLowerCase()]) + && typeof NodeType === 'function' + ) { + // 先将缓冲区中的text节点内容写入 + flushTextBuf(); + + var currentNode = analyseContext.current; + if (engine.options.strip && currentNode instanceof TextNode) { + currentNode.value = currentNode.value + .replace(/\r?\n[\x20\t]*$/, '\n'); + } + + if (match[1]) { + currentNode = autoCloseCommand(analyseContext, NodeType); + } + else { + currentNode = new NodeType(match[3], engine); + if (typeof currentNode.beforeOpen === 'function') { + currentNode.beforeOpen(analyseContext); + } + currentNode.open(analyseContext); + } + + analyseContext.current = currentNode; + } + else if (!/^\s*\/\//.test(text)) { + // 如果不是模板注释,则作为普通文本,写入缓冲区 + textBuf.push(commandOpen, text, commandClose); + } + + NodeType = null; + }, + + function (text) { // 外,普通文本的处理函数 + // 普通文本直接写入缓冲区 + textBuf.push(text); + } + ); + + + flushTextBuf(); // 将缓冲区中的text节点内容写入 + autoCloseCommand(analyseContext); + + return analyseContext.targets; + } + + var etpl = new Engine(); + etpl.Engine = Engine; + + if (typeof exports === 'object' && typeof module === 'object') { + // For CommonJS + exports = module.exports = etpl; + } + else if (typeof define === 'function' && define.amd) { + // For AMD + define(etpl); + } + else { + // For - + @@ -89,6 +57,19 @@ +
+ + + + + + + + + + +
名称类型大小时间
+