etpl.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731
  1. /**
  2. * ETPL (Enterprise Template)
  3. * Copyright 2013 Baidu Inc. All rights reserved.
  4. *
  5. * @file 模板引擎
  6. * @author errorrik(errorrik@gmail.com)
  7. * otakustay(otakustay@gmail.com)
  8. */
  9. // 有的正则比较长,所以特别放开一些限制
  10. /* jshint maxdepth: 10, unused: false, white: false */
  11. // HACK: 可见的重复代码未抽取成function和var是为了gzip size,吐槽的一边去
  12. (function (root) {
  13. /**
  14. * 对象属性拷贝
  15. *
  16. * @inner
  17. * @param {Object} target 目标对象
  18. * @param {Object} source 源对象
  19. * @return {Object} 返回目标对象
  20. */
  21. function extend( target, source ) {
  22. for ( var key in source ) {
  23. if ( source.hasOwnProperty( key ) ) {
  24. target[ key ] = source[ key ];
  25. }
  26. }
  27. return target;
  28. }
  29. /**
  30. * 随手写了个栈
  31. *
  32. * @inner
  33. * @constructor
  34. */
  35. function Stack() {
  36. this.raw = [];
  37. this.length = 0;
  38. }
  39. Stack.prototype = {
  40. /**
  41. * 添加元素进栈
  42. *
  43. * @param {*} elem 添加项
  44. */
  45. push: function ( elem ) {
  46. this.raw[ this.length++ ] = elem;
  47. },
  48. /**
  49. * 弹出顶部元素
  50. *
  51. * @return {*}
  52. */
  53. pop: function () {
  54. if ( this.length > 0 ) {
  55. var elem = this.raw[ --this.length ];
  56. this.raw.length = this.length;
  57. return elem;
  58. }
  59. },
  60. /**
  61. * 获取顶部元素
  62. *
  63. * @return {*}
  64. */
  65. top: function () {
  66. return this.raw[ this.length - 1 ];
  67. },
  68. /**
  69. * 获取底部元素
  70. *
  71. * @return {*}
  72. */
  73. bottom: function () {
  74. return this.raw[ 0 ];
  75. },
  76. /**
  77. * 根据查询条件获取元素
  78. *
  79. * @param {Function} condition 查询函数
  80. * @return {*}
  81. */
  82. find: function ( condition ) {
  83. var index = this.length;
  84. while ( index-- ) {
  85. var item = this.raw[ index ];
  86. if ( condition( item ) ) {
  87. return item;
  88. }
  89. }
  90. }
  91. };
  92. /**
  93. * 唯一id的起始值
  94. *
  95. * @inner
  96. * @type {number}
  97. */
  98. var guidIndex = 0x2B845;
  99. /**
  100. * 获取唯一id,用于匿名target或编译代码的变量名生成
  101. *
  102. * @inner
  103. * @return {string}
  104. */
  105. function generateGUID() {
  106. return '___' + (guidIndex++);
  107. }
  108. /**
  109. * 构建类之间的继承关系
  110. *
  111. * @inner
  112. * @param {Function} subClass 子类函数
  113. * @param {Function} superClass 父类函数
  114. */
  115. function inherits( subClass, superClass ) {
  116. var F = new Function();
  117. F.prototype = superClass.prototype;
  118. subClass.prototype = new F();
  119. subClass.prototype.constructor = subClass;
  120. // 由于引擎内部的使用场景都是inherits后,逐个编写子类的prototype方法
  121. // 所以,不考虑将原有子类prototype缓存再逐个拷贝回去
  122. }
  123. /**
  124. * HTML Filter替换的字符实体表
  125. *
  126. * @const
  127. * @inner
  128. * @type {Object}
  129. */
  130. var HTML_ENTITY = {
  131. '&': '&',
  132. '<': '&lt;',
  133. '>': '&gt;',
  134. '"': '&quot;',
  135. "'": '&#39;'
  136. };
  137. /**
  138. * HTML Filter的替换函数
  139. *
  140. * @inner
  141. * @param {string} c 替换字符
  142. * @return {string}
  143. */
  144. function htmlFilterReplacer( c ) {
  145. return HTML_ENTITY[ c ];
  146. }
  147. /**
  148. * 默认filter
  149. *
  150. * @inner
  151. * @const
  152. * @type {Object}
  153. */
  154. var DEFAULT_FILTERS = {
  155. /**
  156. * HTML转义filter
  157. *
  158. * @param {string} source 源串
  159. * @return {string}
  160. */
  161. html: function ( source ) {
  162. return source.replace( /[&<>"']/g, htmlFilterReplacer );
  163. },
  164. /**
  165. * URL编码filter
  166. *
  167. * @param {string} source 源串
  168. * @return {string}
  169. */
  170. url: encodeURIComponent,
  171. /**
  172. * 源串filter,用于在默认开启HTML转义时获取源串,不进行转义
  173. *
  174. * @param {string} source 源串
  175. * @return {string}
  176. */
  177. raw: function ( source ) {
  178. return source;
  179. }
  180. };
  181. /**
  182. * 字符串字面化
  183. *
  184. * @inner
  185. * @param {string} source 需要字面化的字符串
  186. * @return {string}
  187. */
  188. function stringLiteralize( source ) {
  189. return '"'
  190. + source
  191. .replace( /\x5C/g, '\\\\' )
  192. .replace( /"/g, '\\"' )
  193. .replace( /\x0A/g, '\\n' )
  194. .replace( /\x09/g, '\\t' )
  195. .replace( /\x0D/g, '\\r' )
  196. // .replace( /\x08/g, '\\b' )
  197. // .replace( /\x0C/g, '\\f' )
  198. + '"';
  199. }
  200. /**
  201. * 字符串格式化
  202. *
  203. * @inner
  204. * @param {string} source 目标模版字符串
  205. * @param {...string} replacements 字符串替换项集合
  206. * @return {string}
  207. */
  208. function stringFormat( source ) {
  209. var args = arguments;
  210. return source.replace(
  211. /\{([0-9]+)\}/g,
  212. function ( match, index ) {
  213. return args[ index - 0 + 1 ];
  214. } );
  215. }
  216. /**
  217. * 用于render的字符串变量声明语句
  218. *
  219. * @inner
  220. * @const
  221. * @type {string}
  222. */
  223. var RENDER_STRING_DECLATION = 'var r="";';
  224. /**
  225. * 用于render的字符串内容添加语句(起始)
  226. *
  227. * @inner
  228. * @const
  229. * @type {string}
  230. */
  231. var RENDER_STRING_ADD_START = 'r+=';
  232. /**
  233. * 用于render的字符串内容添加语句(结束)
  234. *
  235. * @inner
  236. * @const
  237. * @type {string}
  238. */
  239. var RENDER_STRING_ADD_END = ';';
  240. /**
  241. * 用于render的字符串内容返回语句
  242. *
  243. * @inner
  244. * @const
  245. * @type {string}
  246. */
  247. var RENDER_STRING_RETURN = 'return r;';
  248. // HACK: IE8-时,编译后的renderer使用join Array的策略进行字符串拼接
  249. if ( typeof navigator != 'undefined'
  250. && /msie\s*([0-9]+)/i.test( navigator.userAgent )
  251. && RegExp.$1 - 0 < 8
  252. ) {
  253. RENDER_STRING_DECLATION = 'var r=[],ri=0;';
  254. RENDER_STRING_ADD_START = 'r[ri++]=';
  255. RENDER_STRING_RETURN = 'return r.join("");';
  256. }
  257. /**
  258. * 将访问变量名称转换成getVariable调用的编译语句
  259. * 用于if、var等命令生成编译代码
  260. *
  261. * @inner
  262. * @param {string} name 访问变量名
  263. * @return {string}
  264. */
  265. function toGetVariableLiteral( name ) {
  266. name = name.replace( /^\s*\*/, '' );
  267. return stringFormat(
  268. 'gv({0},["{1}"])',
  269. stringLiteralize( name ),
  270. name.replace(
  271. /\[['"]?([^'"]+)['"]?\]/g,
  272. function ( match, name ) {
  273. return '.' + name;
  274. }
  275. )
  276. .split( '.' )
  277. .join( '","' )
  278. );
  279. }
  280. /**
  281. * 解析文本片段中以固定字符串开头和结尾的包含块
  282. * 用于 命令串:<!-- ... --> 和 变量替换串:${...} 的解析
  283. *
  284. * @inner
  285. * @param {string} source 要解析的文本
  286. * @param {string} open 包含块开头
  287. * @param {string} close 包含块结束
  288. * @param {boolean} greedy 是否贪婪匹配
  289. * @param {function({string})} onInBlock 包含块内文本的处理函数
  290. * @param {function({string})} onOutBlock 非包含块内文本的处理函数
  291. */
  292. function parseTextBlock( source, open, close, greedy, onInBlock, onOutBlock ) {
  293. var closeLen = close.length;
  294. var texts = source.split( open );
  295. var level = 0;
  296. var buf = [];
  297. for ( var i = 0, len = texts.length; i < len; i++ ) {
  298. var text = texts[ i ];
  299. if ( i ) {
  300. var openBegin = 1;
  301. level++;
  302. while ( 1 ) {
  303. var closeIndex = text.indexOf( close );
  304. if ( closeIndex < 0 ) {
  305. buf.push( level > 1 && openBegin ? open : '', text );
  306. break;
  307. }
  308. level = greedy ? level - 1 : 0;
  309. buf.push(
  310. level > 0 && openBegin ? open : '',
  311. text.slice( 0, closeIndex ),
  312. level > 0 ? close : ''
  313. );
  314. text = text.slice( closeIndex + closeLen );
  315. openBegin = 0;
  316. if ( level === 0 ) {
  317. break;
  318. }
  319. }
  320. if ( level === 0 ) {
  321. onInBlock( buf.join( '' ) );
  322. onOutBlock( text );
  323. buf = [];
  324. }
  325. }
  326. else {
  327. text && onOutBlock( text );
  328. }
  329. }
  330. if ( level > 0 && buf.length > 0 ) {
  331. onOutBlock( open );
  332. onOutBlock( buf.join( '' ) );
  333. }
  334. }
  335. /**
  336. * 编译变量访问和变量替换的代码
  337. * 用于普通文本或if、var、filter等命令生成编译代码
  338. *
  339. * @inner
  340. * @param {string} source 源代码
  341. * @param {Engine} engine 引擎实例
  342. * @param {boolean} forText 是否为输出文本的变量替换
  343. * @return {string}
  344. */
  345. function compileVariable( source, engine, forText ) {
  346. var code = [];
  347. var options = engine.options;
  348. var toStringHead = '';
  349. var toStringFoot = '';
  350. var wrapHead = '';
  351. var wrapFoot = '';
  352. // 默认的filter,当forText模式时有效
  353. var defaultFilter;
  354. if ( forText ) {
  355. toStringHead = 'ts(';
  356. toStringFoot = ')';
  357. wrapHead = RENDER_STRING_ADD_START;
  358. wrapFoot = RENDER_STRING_ADD_END;
  359. defaultFilter = options.defaultFilter
  360. }
  361. parseTextBlock(
  362. source, options.variableOpen, options.variableClose, 1,
  363. function ( text ) {
  364. // 加入默认filter
  365. // 只有当处理forText时,需要加入默认filter
  366. // 处理if/var/use等command时,不需要加入默认filter
  367. if ( forText && text.indexOf( '|' ) < 0 && defaultFilter ) {
  368. text += '|' + defaultFilter;
  369. }
  370. // variableCode是一个gv调用,然后通过循环,在外面包filter的调用
  371. // 形成filter["b"](filter["a"](gv(...)))
  372. //
  373. // 当forText模式,处理的是文本中的变量替换时
  374. // 传递给filter的需要是字符串形式,所以gv外需要包一层ts调用
  375. // 形成filter["b"](filter["a"](ts(gv(...))))
  376. //
  377. // 当variableName以*起始时,忽略ts调用,直接传递原值给filter
  378. var filterCharIndex = text.indexOf( '|' );
  379. var variableName = (filterCharIndex > 0
  380. ? text.slice( 0, filterCharIndex )
  381. : text).replace( /^\s+/, '' ).replace( /\s+$/, '' );
  382. var filterSource = filterCharIndex > 0
  383. ? text.slice( filterCharIndex + 1 )
  384. : '';
  385. var variableRawValue = variableName.indexOf( '*' ) === 0;
  386. var variableCode = [
  387. variableRawValue ? '' : toStringHead,
  388. toGetVariableLiteral( variableName ),
  389. variableRawValue ? '' : toStringFoot
  390. ];
  391. if ( filterSource ) {
  392. filterSource = compileVariable( filterSource, engine );
  393. var filterSegs = filterSource.split( '|' );
  394. for ( var i = 0, len = filterSegs.length; i < len; i++ ) {
  395. var seg = filterSegs[ i ];
  396. if ( /^\s*([a-z0-9_-]+)(\((.*)\))?\s*$/i.test( seg ) ) {
  397. variableCode.unshift( 'fs["' + RegExp.$1 + '"](' );
  398. if ( RegExp.$3 ) {
  399. variableCode.push(
  400. ',',
  401. RegExp.$3
  402. );
  403. }
  404. variableCode.push( ')' );
  405. }
  406. }
  407. }
  408. code.push(
  409. wrapHead,
  410. variableCode.join( '' ),
  411. wrapFoot
  412. );
  413. },
  414. function ( text ) {
  415. code.push(
  416. wrapHead,
  417. forText ? stringLiteralize( text ) : text,
  418. wrapFoot
  419. );
  420. }
  421. );
  422. return code.join( '' );
  423. }
  424. /**
  425. * 文本节点类
  426. *
  427. * @inner
  428. * @constructor
  429. * @param {string} value 文本节点的内容文本
  430. * @param {Engine} engine 引擎实例
  431. */
  432. function TextNode( value, engine ) {
  433. this.value = value;
  434. this.engine = engine;
  435. }
  436. TextNode.prototype = {
  437. /**
  438. * 获取renderer body的生成代码
  439. *
  440. * @return {string}
  441. */
  442. getRendererBody: function () {
  443. var value = this.value;
  444. var options = this.engine.options;
  445. if ( !value || ( options.strip && /^\s*$/.test( value ) ) ) {
  446. return '';
  447. }
  448. return compileVariable( value, this.engine, 1 );
  449. },
  450. /**
  451. * 获取内容
  452. *
  453. * @return {string}
  454. */
  455. getContent: function () {
  456. return this.value;
  457. }
  458. };
  459. /**
  460. * 命令节点类
  461. *
  462. * @inner
  463. * @constructor
  464. * @param {string} value 命令节点的value
  465. * @param {Engine} engine 引擎实例
  466. */
  467. function Command( value, engine ) {
  468. this.value = value;
  469. this.engine = engine;
  470. this.children = [];
  471. }
  472. Command.prototype = {
  473. /**
  474. * 添加子节点
  475. *
  476. * @param {TextNode|Command} node 子节点
  477. */
  478. addChild: function ( node ) {
  479. this.children.push( node );
  480. },
  481. /**
  482. * 节点open,解析开始
  483. *
  484. * @param {Object} context 语法分析环境对象
  485. */
  486. open: function ( context ) {
  487. var parent = context.stack.top();
  488. this.parent = parent;
  489. parent && parent.addChild( this );
  490. context.stack.push( this );
  491. },
  492. /**
  493. * 节点闭合,解析结束
  494. *
  495. * @param {Object} context 语法分析环境对象
  496. */
  497. close: function ( context ) {
  498. while (context.stack.pop().constructor !== this.constructor) {}
  499. },
  500. /**
  501. * 添加文本节点
  502. *
  503. * @param {TextNode} node 节点
  504. */
  505. addTextNode: function ( node ) {
  506. this.addChild( node );
  507. },
  508. /**
  509. * 获取renderer body的生成代码
  510. *
  511. * @return {string}
  512. */
  513. getRendererBody: function () {
  514. var buf = [];
  515. var children = this.children;
  516. for ( var i = 0; i < children.length; i++ ) {
  517. buf.push( children[ i ].getRendererBody() );
  518. }
  519. return buf.join( '' );
  520. }
  521. };
  522. /**
  523. * 命令自动闭合
  524. *
  525. * @inner
  526. * @param {Object} context 语法分析环境对象
  527. * @param {Function=} CommandType 自闭合的节点类型
  528. */
  529. function autoCloseCommand( context, CommandType ) {
  530. var stack = context.stack;
  531. var closeEnd = CommandType
  532. ? stack.find( function ( item ) {
  533. return item instanceof CommandType;
  534. } )
  535. : stack.bottom();
  536. if ( closeEnd ) {
  537. var node;
  538. do {
  539. node = stack.top();
  540. // 如果节点对象不包含autoClose方法
  541. // 则认为该节点不支持自动闭合,需要抛出错误
  542. // for等节点不支持自动闭合
  543. if ( !node.autoClose ) {
  544. throw new Error( node.type + ' must be closed manually: ' + node.value );
  545. }
  546. node.autoClose( context );
  547. } while ( node !== closeEnd );
  548. }
  549. return closeEnd;
  550. }
  551. /**
  552. * renderer body起始代码段
  553. *
  554. * @inner
  555. * @const
  556. * @type {string}
  557. */
  558. var RENDERER_BODY_START = ''
  559. + 'data=data||{};'
  560. + 'var v={},fs=engine.filters,hg=typeof data.get=="function",'
  561. + 'gv=function(n,ps){'
  562. + 'var p=ps[0],d=v[p];'
  563. + 'if(d==null){'
  564. + 'if(hg){return data.get(n);}'
  565. + 'd=data[p];'
  566. + '}'
  567. + 'for(var i=1,l=ps.length;i<l;i++)if(d!=null)d = d[ps[i]];'
  568. + 'return d;'
  569. + '},'
  570. + 'ts=function(s){'
  571. + 'if(typeof s==="string"){return s;}'
  572. + 'if(s==null){s="";}'
  573. + 'return ""+s;'
  574. + '};'
  575. ;
  576. // v: variables
  577. // fs: filters
  578. // gv: getVariable
  579. // ts: toString
  580. // n: name
  581. // ps: properties
  582. // hg: hasGetter
  583. /**
  584. * Target命令节点类
  585. *
  586. * @inner
  587. * @constructor
  588. * @param {string} value 命令节点的value
  589. * @param {Engine} engine 引擎实例
  590. */
  591. function TargetCommand( value, engine ) {
  592. if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) {
  593. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  594. }
  595. this.master = RegExp.$3;
  596. this.name = RegExp.$1;
  597. Command.call( this, value, engine );
  598. this.contents = {};
  599. }
  600. // 创建Target命令节点继承关系
  601. inherits( TargetCommand, Command );
  602. /**
  603. * Master命令节点类
  604. *
  605. * @inner
  606. * @constructor
  607. * @param {string} value 命令节点的value
  608. * @param {Engine} engine 引擎实例
  609. */
  610. function MasterCommand( value, engine ) {
  611. if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) {
  612. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  613. }
  614. this.master = RegExp.$3;
  615. this.name = RegExp.$1;
  616. Command.call( this, value, engine );
  617. this.contents = {};
  618. }
  619. // 创建Master命令节点继承关系
  620. inherits( MasterCommand, Command );
  621. /**
  622. * Content命令节点类
  623. *
  624. * @inner
  625. * @constructor
  626. * @param {string} value 命令节点的value
  627. * @param {Engine} engine 引擎实例
  628. */
  629. function ContentCommand( value, engine ) {
  630. if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
  631. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  632. }
  633. this.name = RegExp.$1;
  634. Command.call( this, value, engine );
  635. }
  636. // 创建Content命令节点继承关系
  637. inherits( ContentCommand, Command );
  638. /**
  639. * ContentPlaceHolder命令节点类
  640. *
  641. * @inner
  642. * @constructor
  643. * @param {string} value 命令节点的value
  644. * @param {Engine} engine 引擎实例
  645. */
  646. function ContentPlaceHolderCommand( value, engine ) {
  647. if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
  648. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  649. }
  650. this.name = RegExp.$1;
  651. Command.call( this, value, engine );
  652. }
  653. // 创建ContentPlaceHolder命令节点继承关系
  654. inherits( ContentPlaceHolderCommand, Command );
  655. /**
  656. * Import命令节点类
  657. *
  658. * @inner
  659. * @constructor
  660. * @param {string} value 命令节点的value
  661. * @param {Engine} engine 引擎实例
  662. */
  663. function ImportCommand( value, engine ) {
  664. if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
  665. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  666. }
  667. this.name = RegExp.$1;
  668. Command.call( this, value, engine );
  669. }
  670. // 创建Import命令节点继承关系
  671. inherits( ImportCommand, Command );
  672. /**
  673. * Var命令节点类
  674. *
  675. * @inner
  676. * @constructor
  677. * @param {string} value 命令节点的value
  678. * @param {Engine} engine 引擎实例
  679. */
  680. function VarCommand( value, engine ) {
  681. if ( !/^\s*([a-z0-9_]+)\s*=([\s\S]*)$/i.test( value ) ) {
  682. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  683. }
  684. this.name = RegExp.$1;
  685. this.expr = RegExp.$2;
  686. Command.call( this, value, engine );
  687. }
  688. // 创建Var命令节点继承关系
  689. inherits( VarCommand, Command );
  690. /**
  691. * filter命令节点类
  692. *
  693. * @inner
  694. * @constructor
  695. * @param {string} value 命令节点的value
  696. * @param {Engine} engine 引擎实例
  697. */
  698. function FilterCommand( value, engine ) {
  699. if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) {
  700. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  701. }
  702. this.name = RegExp.$1;
  703. this.args = RegExp.$3;
  704. Command.call( this, value, engine );
  705. }
  706. // 创建filter命令节点继承关系
  707. inherits( FilterCommand, Command );
  708. /**
  709. * Use命令节点类
  710. *
  711. * @inner
  712. * @constructor
  713. * @param {string} value 命令节点的value
  714. * @param {Engine} engine 引擎实例
  715. */
  716. function UseCommand( value, engine ) {
  717. if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) {
  718. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  719. }
  720. this.name = RegExp.$1;
  721. this.args = RegExp.$3;
  722. Command.call( this, value, engine );
  723. }
  724. // 创建Use命令节点继承关系
  725. inherits( UseCommand, Command );
  726. /**
  727. * for命令节点类
  728. *
  729. * @inner
  730. * @constructor
  731. * @param {string} value 命令节点的value
  732. * @param {Engine} engine 引擎实例
  733. */
  734. function ForCommand( value, engine ) {
  735. if ( !/^\s*(\$\{[\s\S]+\})\s+as\s+\$\{([0-9a-z_]+)\}\s*(,\s*\$\{([0-9a-z_]+)\})?\s*$/i.test( value ) ) {
  736. throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
  737. }
  738. this.list = RegExp.$1;
  739. this.item = RegExp.$2;
  740. this.index = RegExp.$4;
  741. Command.call( this, value, engine );
  742. }
  743. // 创建for命令节点继承关系
  744. inherits( ForCommand, Command );
  745. /**
  746. * if命令节点类
  747. *
  748. * @inner
  749. * @constructor
  750. * @param {string} value 命令节点的value
  751. * @param {Engine} engine 引擎实例
  752. */
  753. function IfCommand( value, engine ) {
  754. Command.call( this, value, engine );
  755. }
  756. // 创建if命令节点继承关系
  757. inherits( IfCommand, Command );
  758. /**
  759. * elif命令节点类
  760. *
  761. * @inner
  762. * @constructor
  763. * @param {string} value 命令节点的value
  764. * @param {Engine} engine 引擎实例
  765. */
  766. function ElifCommand( value, engine ) {
  767. IfCommand.call( this, value, engine );
  768. }
  769. // 创建elif命令节点继承关系
  770. inherits( ElifCommand, IfCommand );
  771. /**
  772. * else命令节点类
  773. *
  774. * @inner
  775. * @constructor
  776. * @param {string} value 命令节点的value
  777. * @param {Engine} engine 引擎实例
  778. */
  779. function ElseCommand( value, engine ) {
  780. Command.call( this, value, engine );
  781. }
  782. // 创建else命令节点继承关系
  783. inherits( ElseCommand, Command );
  784. /**
  785. * Target和Master的节点状态
  786. *
  787. * @inner
  788. */
  789. var TMNodeState = {
  790. READING: 1,
  791. READED: 2,
  792. APPLIED: 3,
  793. READY: 4
  794. };
  795. /**
  796. * 节点闭合,解析结束
  797. *
  798. * @param {Object} context 语法分析环境对象
  799. */
  800. MasterCommand.prototype.close =
  801. /**
  802. * 节点闭合,解析结束。自闭合时被调用
  803. *
  804. * @param {Object} context 语法分析环境对象
  805. */
  806. MasterCommand.prototype.autoClose =
  807. /**
  808. * 节点闭合,解析结束
  809. *
  810. * @param {Object} context 语法分析环境对象
  811. */
  812. TargetCommand.prototype.close =
  813. /**
  814. * 节点闭合,解析结束。自闭合时被调用
  815. *
  816. * @param {Object} context 语法分析环境对象
  817. */
  818. TargetCommand.prototype.autoClose = function ( context ) {
  819. Command.prototype.close.call( this, context );
  820. this.state = this.master ? TMNodeState.READED : TMNodeState.APPLIED;
  821. context.targetOrMaster = null;
  822. };
  823. /**
  824. * 应用其继承的母版,返回是否成功应用母版
  825. *
  826. * @return {boolean}
  827. */
  828. TargetCommand.prototype.applyMaster =
  829. /**
  830. * 应用其继承的母版,返回是否成功应用母版
  831. *
  832. * @return {boolean}
  833. */
  834. MasterCommand.prototype.applyMaster = function () {
  835. if ( this.state >= TMNodeState.APPLIED ) {
  836. return 1;
  837. }
  838. var masterNode = this.engine.masters[ this.master ];
  839. if ( masterNode && masterNode.applyMaster() ) {
  840. this.children = [];
  841. for ( var i = 0, len = masterNode.children.length; i < len; i++ ) {
  842. var child = masterNode.children[ i ];
  843. if ( child instanceof ContentPlaceHolderCommand ) {
  844. this.children.push.apply(
  845. this.children,
  846. (this.contents[ child.name ] || child).children
  847. );
  848. }
  849. else {
  850. this.children.push( child );
  851. }
  852. }
  853. this.state = TMNodeState.APPLIED;
  854. return 1;
  855. }
  856. };
  857. /**
  858. * 判断target是否ready
  859. * 包括是否成功应用母版,以及import和use语句依赖的target是否ready
  860. *
  861. * @return {boolean}
  862. */
  863. TargetCommand.prototype.isReady = function () {
  864. if ( this.state >= TMNodeState.READY ) {
  865. return 1;
  866. }
  867. var engine = this.engine;
  868. var readyState = 1;
  869. /**
  870. * 递归检查节点的ready状态
  871. *
  872. * @inner
  873. * @param {Command|TextNode} node 目标节点
  874. */
  875. function checkReadyState( node ) {
  876. for ( var i = 0, len = node.children.length; i < len; i++ ) {
  877. var child = node.children[ i ];
  878. if ( child instanceof ImportCommand ) {
  879. var target = engine.targets[ child.name ];
  880. readyState = readyState
  881. && target && target.isReady( engine );
  882. }
  883. else if ( child instanceof Command ) {
  884. checkReadyState( child );
  885. }
  886. }
  887. }
  888. if ( this.applyMaster() ) {
  889. checkReadyState( this );
  890. readyState && (this.state = TMNodeState.READY);
  891. return readyState;
  892. }
  893. };
  894. /**
  895. * 获取target的renderer函数
  896. *
  897. * @return {function(Object):string}
  898. */
  899. TargetCommand.prototype.getRenderer = function () {
  900. if ( this.renderer ) {
  901. return this.renderer;
  902. }
  903. if ( this.isReady() ) {
  904. // console.log( this.name + ' ------------------' );
  905. // console.log(RENDERER_BODY_START +RENDER_STRING_DECLATION
  906. // + this.getRendererBody()
  907. // + RENDER_STRING_RETURN);
  908. var realRenderer = new Function(
  909. 'data', 'engine',
  910. [
  911. RENDERER_BODY_START,
  912. RENDER_STRING_DECLATION,
  913. this.getRendererBody(),
  914. RENDER_STRING_RETURN
  915. ].join( '\n' )
  916. );
  917. var engine = this.engine;
  918. this.renderer = function ( data ) {
  919. return realRenderer( data, engine );
  920. };
  921. return this.renderer;
  922. }
  923. return null;
  924. };
  925. /**
  926. * 获取内容
  927. *
  928. * @return {string}
  929. */
  930. TargetCommand.prototype.getContent = function () {
  931. if ( this.isReady() ) {
  932. var buf = [];
  933. var children = this.children;
  934. for ( var i = 0; i < children.length; i++ ) {
  935. buf.push( children[ i ].getContent() );
  936. }
  937. return buf.join( '' );
  938. }
  939. return '';
  940. };
  941. /**
  942. * 将target或master节点对象添加到语法分析环境中
  943. *
  944. * @inner
  945. * @param {TargetCommand|MasterCommand} targetOrMaster target或master节点对象
  946. * @param {Object} context 语法分析环境对象
  947. */
  948. function addTargetOrMasterToContext( targetOrMaster, context ) {
  949. context.targetOrMaster = targetOrMaster;
  950. var engine = context.engine;
  951. var name = targetOrMaster.name;
  952. var isTarget = targetOrMaster instanceof TargetCommand;
  953. var prop = isTarget ? 'targets' : 'masters';
  954. if ( engine[ prop ][ name ] ) {
  955. switch ( engine.options.namingConflict ) {
  956. case 'override':
  957. engine[ prop ][ name ] = targetOrMaster;
  958. isTarget && context.targets.push( name );
  959. case 'ignore':
  960. break;
  961. default:
  962. throw new Error( ( isTarget ? 'Target' :'Master' )
  963. + ' is exists: ' + name );
  964. }
  965. }
  966. else {
  967. engine[ prop ][ name ] = targetOrMaster;
  968. isTarget && context.targets.push( name );
  969. }
  970. }
  971. /**
  972. * target节点open,解析开始
  973. *
  974. * @param {Object} context 语法分析环境对象
  975. */
  976. TargetCommand.prototype.open =
  977. /**
  978. * master节点open,解析开始
  979. *
  980. * @param {Object} context 语法分析环境对象
  981. */
  982. MasterCommand.prototype.open = function ( context ) {
  983. autoCloseCommand( context );
  984. Command.prototype.open.call( this, context );
  985. this.state = TMNodeState.READING;
  986. addTargetOrMasterToContext( this, context );
  987. };
  988. /**
  989. * Import节点open,解析开始
  990. *
  991. * @param {Object} context 语法分析环境对象
  992. */
  993. ImportCommand.prototype.open =
  994. /**
  995. * Var节点open,解析开始
  996. *
  997. * @param {Object} context 语法分析环境对象
  998. */
  999. VarCommand.prototype.open =
  1000. /**
  1001. * Use节点open,解析开始
  1002. *
  1003. * @param {Object} context 语法分析环境对象
  1004. */
  1005. UseCommand.prototype.open = function ( context ) {
  1006. var parent = context.stack.top();
  1007. this.parent = parent;
  1008. parent.addChild( this );
  1009. };
  1010. /**
  1011. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1012. *
  1013. * @param {Object} context 语法分析环境对象
  1014. */
  1015. UseCommand.prototype.beforeOpen =
  1016. /**
  1017. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1018. *
  1019. * @param {Object} context 语法分析环境对象
  1020. */
  1021. ImportCommand.prototype.beforeOpen =
  1022. /**
  1023. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1024. *
  1025. * @param {Object} context 语法分析环境对象
  1026. */
  1027. VarCommand.prototype.beforeOpen =
  1028. /**
  1029. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1030. *
  1031. * @param {Object} context 语法分析环境对象
  1032. */
  1033. ForCommand.prototype.beforeOpen =
  1034. /**
  1035. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1036. *
  1037. * @param {Object} context 语法分析环境对象
  1038. */
  1039. FilterCommand.prototype.beforeOpen =
  1040. /**
  1041. * 节点open前的处理动作:节点不在target中时,自动创建匿名target
  1042. *
  1043. * @param {Object} context 语法分析环境对象
  1044. */
  1045. IfCommand.prototype.beforeOpen =
  1046. /**
  1047. * 文本节点被添加到分析环境前的处理动作:节点不在target中时,自动创建匿名target
  1048. *
  1049. * @param {Object} context 语法分析环境对象
  1050. */
  1051. TextNode.prototype.beforeAdd = function ( context ) {
  1052. if ( context.stack.bottom() ) {
  1053. return;
  1054. }
  1055. var target = new TargetCommand( generateGUID(), context.engine );
  1056. target.open( context );
  1057. };
  1058. /**
  1059. * 节点解析结束
  1060. * 由于use节点无需闭合,处理时不会入栈,所以将close置为空函数
  1061. *
  1062. * @param {Object} context 语法分析环境对象
  1063. */
  1064. UseCommand.prototype.close =
  1065. /**
  1066. * 节点解析结束
  1067. * 由于import节点无需闭合,处理时不会入栈,所以将close置为空函数
  1068. *
  1069. * @param {Object} context 语法分析环境对象
  1070. */
  1071. ImportCommand.prototype.close =
  1072. /**
  1073. * 节点解析结束
  1074. * 由于else节点无需闭合,处理时不会入栈,闭合由if负责。所以将close置为空函数
  1075. *
  1076. * @param {Object} context 语法分析环境对象
  1077. */
  1078. ElseCommand.prototype.close =
  1079. /**
  1080. * 节点解析结束
  1081. * 由于var节点无需闭合,处理时不会入栈,所以将close置为空函数
  1082. *
  1083. * @param {Object} context 语法分析环境对象
  1084. */
  1085. VarCommand.prototype.close = function () {};
  1086. /**
  1087. * 获取内容
  1088. *
  1089. * @return {string}
  1090. */
  1091. ImportCommand.prototype.getContent = function () {
  1092. var target = this.engine.targets[ this.name ];
  1093. return target.getContent();
  1094. };
  1095. /**
  1096. * 获取renderer body的生成代码
  1097. *
  1098. * @return {string}
  1099. */
  1100. ImportCommand.prototype.getRendererBody = function () {
  1101. var target = this.engine.targets[ this.name ];
  1102. return target.getRendererBody();
  1103. };
  1104. /**
  1105. * 获取renderer body的生成代码
  1106. *
  1107. * @return {string}
  1108. */
  1109. UseCommand.prototype.getRendererBody = function () {
  1110. return stringFormat(
  1111. '{0}engine.render({2},{{3}}){1}',
  1112. RENDER_STRING_ADD_START,
  1113. RENDER_STRING_ADD_END,
  1114. stringLiteralize( this.name ),
  1115. compileVariable( this.args, this.engine ).replace(
  1116. /(^|,)\s*([a-z0-9_]+)\s*=/ig,
  1117. function ( match, start, argName ) {
  1118. return (start || '') + stringLiteralize( argName ) + ':';
  1119. }
  1120. )
  1121. );
  1122. };
  1123. /**
  1124. * 获取renderer body的生成代码
  1125. *
  1126. * @return {string}
  1127. */
  1128. VarCommand.prototype.getRendererBody = function () {
  1129. if ( this.expr ) {
  1130. return stringFormat(
  1131. 'v[{0}]={1};',
  1132. stringLiteralize( this.name ),
  1133. compileVariable( this.expr, this.engine )
  1134. );
  1135. }
  1136. return '';
  1137. };
  1138. /**
  1139. * 获取renderer body的生成代码
  1140. *
  1141. * @return {string}
  1142. */
  1143. IfCommand.prototype.getRendererBody = function () {
  1144. var rendererBody = stringFormat(
  1145. 'if({0}){{1}}',
  1146. compileVariable( this.value, this.engine ),
  1147. Command.prototype.getRendererBody.call( this )
  1148. );
  1149. var elseCommand = this[ 'else' ];
  1150. if ( elseCommand ) {
  1151. return [
  1152. rendererBody,
  1153. stringFormat(
  1154. 'else{{0}}',
  1155. elseCommand.getRendererBody()
  1156. )
  1157. ].join( '' );
  1158. }
  1159. return rendererBody;
  1160. };
  1161. /**
  1162. * 获取renderer body的生成代码
  1163. *
  1164. * @return {string}
  1165. */
  1166. ForCommand.prototype.getRendererBody = function () {
  1167. return stringFormat(
  1168. ''
  1169. + 'var {0}={1};'
  1170. + 'if({0} instanceof Array)'
  1171. + 'for (var {4}=0,{5}={0}.length;{4}<{5};{4}++){v[{2}]={4};v[{3}]={0}[{4}];{6}}'
  1172. + 'else if(typeof {0}==="object")'
  1173. + 'for(var {4} in {0}){v[{2}]={4};v[{3}]={0}[{4}];{6}}',
  1174. generateGUID(),
  1175. compileVariable( this.list, this.engine ),
  1176. stringLiteralize( this.index || generateGUID() ),
  1177. stringLiteralize( this.item ),
  1178. generateGUID(),
  1179. generateGUID(),
  1180. Command.prototype.getRendererBody.call( this )
  1181. );
  1182. };
  1183. /**
  1184. * 获取renderer body的生成代码
  1185. *
  1186. * @return {string}
  1187. */
  1188. FilterCommand.prototype.getRendererBody = function () {
  1189. var args = this.args;
  1190. return stringFormat(
  1191. '{2}fs[{5}]((function(){{0}{4}{1}})(){6}){3}',
  1192. RENDER_STRING_DECLATION,
  1193. RENDER_STRING_RETURN,
  1194. RENDER_STRING_ADD_START,
  1195. RENDER_STRING_ADD_END,
  1196. Command.prototype.getRendererBody.call( this ),
  1197. stringLiteralize( this.name ),
  1198. args ? ',' + compileVariable( args, this.engine ) : ''
  1199. );
  1200. };
  1201. /**
  1202. * content节点open,解析开始
  1203. *
  1204. * @param {Object} context 语法分析环境对象
  1205. */
  1206. ContentCommand.prototype.open = function ( context ) {
  1207. autoCloseCommand( context, ContentCommand );
  1208. Command.prototype.open.call( this, context );
  1209. context.targetOrMaster.contents[ this.name ] = this;
  1210. };
  1211. /**
  1212. * content节点open,解析开始
  1213. *
  1214. * @param {Object} context 语法分析环境对象
  1215. */
  1216. ContentPlaceHolderCommand.prototype.open = function ( context ) {
  1217. autoCloseCommand( context, ContentPlaceHolderCommand );
  1218. Command.prototype.open.call( this, context );
  1219. };
  1220. /**
  1221. * 节点自动闭合,解析结束
  1222. *
  1223. * @param {Object} context 语法分析环境对象
  1224. */
  1225. ContentCommand.prototype.autoClose =
  1226. /**
  1227. * 节点自动闭合,解析结束
  1228. *
  1229. * @param {Object} context 语法分析环境对象
  1230. */
  1231. IfCommand.prototype.autoClose = Command.prototype.close;
  1232. /**
  1233. * 节点自动闭合,解析结束
  1234. * contentplaceholder的自动结束逻辑为,在其开始位置后马上结束
  1235. * 所以,其自动结束时children应赋予其所属的parent,也就是master
  1236. *
  1237. * @param {Object} context 语法分析环境对象
  1238. */
  1239. ContentPlaceHolderCommand.prototype.autoClose = function ( context ) {
  1240. var parentChildren = this.parent.children;
  1241. parentChildren.push.apply( parentChildren, this.children );
  1242. this.children.length = 0;
  1243. this.close( context );
  1244. };
  1245. /**
  1246. * 添加子节点
  1247. *
  1248. * @param {TextNode|Command} node 子节点
  1249. */
  1250. IfCommand.prototype.addChild = function ( node ) {
  1251. var elseCommand = this[ 'else' ];
  1252. ( elseCommand
  1253. ? elseCommand.children
  1254. : this.children
  1255. ).push( node );
  1256. };
  1257. /**
  1258. * elif节点open,解析开始
  1259. *
  1260. * @param {Object} context 语法分析环境对象
  1261. */
  1262. ElifCommand.prototype.open = function ( context ) {
  1263. var elseCommand = new ElseCommand();
  1264. elseCommand.open( context );
  1265. var ifCommand = autoCloseCommand( context, IfCommand );
  1266. ifCommand.addChild( this );
  1267. context.stack.push( this );
  1268. };
  1269. /**
  1270. * else节点open,解析开始
  1271. *
  1272. * @param {Object} context 语法分析环境对象
  1273. */
  1274. ElseCommand.prototype.open = function ( context ) {
  1275. var ifCommand = autoCloseCommand( context, IfCommand );
  1276. ifCommand[ 'else' ] = this;
  1277. context.stack.push( ifCommand );
  1278. };
  1279. /**
  1280. * 命令类型集合
  1281. *
  1282. * @type {Object}
  1283. */
  1284. var commandTypes = {};
  1285. /**
  1286. * 添加命令类型
  1287. *
  1288. * @inner
  1289. * @param {string} name 命令名称
  1290. * @param {Function} Type 处理命令用到的类
  1291. */
  1292. function addCommandType( name, Type ) {
  1293. commandTypes[ name ] = Type;
  1294. Type.prototype.type = name;
  1295. }
  1296. addCommandType( 'target', TargetCommand );
  1297. addCommandType( 'master', MasterCommand );
  1298. addCommandType( 'content', ContentCommand );
  1299. addCommandType( 'contentplaceholder', ContentPlaceHolderCommand );
  1300. addCommandType( 'import', ImportCommand );
  1301. addCommandType( 'use', UseCommand );
  1302. addCommandType( 'var', VarCommand );
  1303. addCommandType( 'for', ForCommand );
  1304. addCommandType( 'if', IfCommand );
  1305. addCommandType( 'elif', ElifCommand );
  1306. addCommandType( 'else', ElseCommand );
  1307. addCommandType( 'filter', FilterCommand );
  1308. /**
  1309. * etpl引擎类
  1310. *
  1311. * @constructor
  1312. * @param {Object=} options 引擎参数
  1313. * @param {string=} options.commandOpen 命令语法起始串
  1314. * @param {string=} options.commandClose 命令语法结束串
  1315. * @param {string=} options.defaultFilter 默认变量替换的filter
  1316. * @param {boolean=} options.strip 是否清除命令标签前后的空白字符
  1317. * @param {string=} options.namingConflict target或master名字冲突时的处理策略
  1318. */
  1319. function Engine( options ) {
  1320. this.options = {
  1321. commandOpen: '<!--',
  1322. commandClose: '-->',
  1323. variableOpen: '${',
  1324. variableClose: '}',
  1325. defaultFilter: 'html'
  1326. };
  1327. this.config( options );
  1328. this.masters = {};
  1329. this.targets = {};
  1330. this.filters = extend({}, DEFAULT_FILTERS);
  1331. }
  1332. /**
  1333. * 配置引擎参数,设置的参数将被合并到现有参数中
  1334. *
  1335. * @param {Object} options 参数对象
  1336. * @param {string=} options.commandOpen 命令语法起始串
  1337. * @param {string=} options.commandClose 命令语法结束串
  1338. * @param {string=} options.defaultFilter 默认变量替换的filter
  1339. * @param {boolean=} options.strip 是否清除命令标签前后的空白字符
  1340. * @param {string=} options.namingConflict target或master名字冲突时的处理策略
  1341. */
  1342. Engine.prototype.config = function ( options ) {
  1343. extend( this.options, options );
  1344. };
  1345. /**
  1346. * 解析模板并编译,返回第一个target编译后的renderer函数。
  1347. *
  1348. * @param {string} source 模板源代码
  1349. * @return {function(Object):string}
  1350. */
  1351. Engine.prototype.compile =
  1352. /**
  1353. * 解析模板并编译,返回第一个target编译后的renderer函数。
  1354. * 该方法的存在为了兼容老模板引擎
  1355. *
  1356. * @param {string} source 模板源代码
  1357. * @return {function(Object):string}
  1358. */
  1359. Engine.prototype.parse = function ( source ) {
  1360. if ( source ) {
  1361. var targetNames = parseSource( source, this );
  1362. if ( targetNames.length ) {
  1363. return this.targets[ targetNames[ 0 ] ].getRenderer();
  1364. }
  1365. }
  1366. return new Function('return ""');
  1367. };
  1368. /**
  1369. * 根据target名称获取编译后的renderer函数
  1370. *
  1371. * @param {string} name target名称
  1372. * @return {function(Object):string}
  1373. */
  1374. Engine.prototype.getRenderer = function ( name ) {
  1375. var target = this.targets[ name ];
  1376. if ( target ) {
  1377. return target.getRenderer();
  1378. }
  1379. };
  1380. /**
  1381. * 根据target名称获取模板内容
  1382. *
  1383. * @param {string} name target名称
  1384. * @return {string}
  1385. */
  1386. Engine.prototype.get = function ( name ) {
  1387. var target = this.targets[ name ];
  1388. if ( target ) {
  1389. return target.getContent();
  1390. }
  1391. return '';
  1392. };
  1393. /**
  1394. * 执行模板渲染,返回渲染后的字符串。
  1395. *
  1396. * @param {string} name target名称
  1397. * @param {Object=} data 模板数据。
  1398. * 可以是plain object,
  1399. * 也可以是带有 {string}get({string}name) 方法的对象
  1400. * @return {string}
  1401. */
  1402. Engine.prototype.render = function ( name, data ) {
  1403. var renderer = this.getRenderer( name );
  1404. if ( renderer ) {
  1405. return renderer( data );
  1406. }
  1407. return '';
  1408. };
  1409. /**
  1410. * 增加过滤器
  1411. *
  1412. * @param {string} name 过滤器名称
  1413. * @param {Function} filter 过滤函数
  1414. */
  1415. Engine.prototype.addFilter = function ( name, filter ) {
  1416. if ( typeof filter == 'function' ) {
  1417. this.filters[ name ] = filter;
  1418. }
  1419. };
  1420. /**
  1421. * 解析源代码
  1422. *
  1423. * @inner
  1424. * @param {string} source 模板源代码
  1425. * @param {Engine} engine 引擎实例
  1426. * @return {Array} target名称列表
  1427. */
  1428. function parseSource( source, engine ) {
  1429. var commandOpen = engine.options.commandOpen;
  1430. var commandClose = engine.options.commandClose;
  1431. var stack = new Stack();
  1432. var analyseContext = {
  1433. engine: engine,
  1434. targets: [],
  1435. stack: stack
  1436. };
  1437. // text节点内容缓冲区,用于合并多text
  1438. var textBuf = [];
  1439. /**
  1440. * 将缓冲区中的text节点内容写入
  1441. *
  1442. * @inner
  1443. */
  1444. function flushTextBuf() {
  1445. if ( textBuf.length > 0 ) {
  1446. var text = textBuf.join( '' );
  1447. var textNode = new TextNode( text, engine );
  1448. textNode.beforeAdd( analyseContext );
  1449. stack.top().addTextNode( textNode );
  1450. textBuf = [];
  1451. if ( engine.options.strip
  1452. && analyseContext.current instanceof Command
  1453. ) {
  1454. textNode.value = text.replace( /^[\x20\t\r]*\n/, '' );
  1455. }
  1456. analyseContext.current = textNode;
  1457. }
  1458. }
  1459. var NodeType;
  1460. /**
  1461. * 判断节点是否是NodeType类型的实例
  1462. * 用于在stack中find提供filter
  1463. *
  1464. * @inner
  1465. * @param {Command} node 目标节点
  1466. * @return {boolean}
  1467. */
  1468. function isInstanceofNodeType( node ) {
  1469. return node instanceof NodeType;
  1470. }
  1471. parseTextBlock(
  1472. source, commandOpen, commandClose, 0,
  1473. function ( text ) { // <!--...-->内文本的处理函数
  1474. var match = /^\s*(\/)?([a-z]+)\s*(:([\s\S]*))?$/.exec( text );
  1475. // 符合command规则,并且存在相应的Command类,说明是合法有含义的Command
  1476. // 否则,为不具有command含义的普通文本
  1477. if ( match
  1478. && ( NodeType = commandTypes[ match[2].toLowerCase() ] )
  1479. && typeof NodeType == 'function'
  1480. ) {
  1481. // 先将缓冲区中的text节点内容写入
  1482. flushTextBuf();
  1483. var currentNode = analyseContext.current;
  1484. if ( engine.options.strip && currentNode instanceof TextNode ) {
  1485. currentNode.value = currentNode.value
  1486. .replace( /\r?\n[\x20\t]*$/, '\n' );
  1487. }
  1488. if ( match[1] ) {
  1489. currentNode = stack.find( isInstanceofNodeType );
  1490. currentNode && currentNode.close( analyseContext );
  1491. }
  1492. else {
  1493. currentNode = new NodeType( match[4], engine );
  1494. if ( typeof currentNode.beforeOpen == 'function' ) {
  1495. currentNode.beforeOpen( analyseContext );
  1496. }
  1497. currentNode.open( analyseContext );
  1498. }
  1499. analyseContext.current = currentNode;
  1500. }
  1501. else if ( !/^\s*\/\//.test( text ) ) {
  1502. // 如果不是模板注释,则作为普通文本,写入缓冲区
  1503. textBuf.push( commandOpen, text, commandClose );
  1504. }
  1505. NodeType = null;
  1506. },
  1507. function ( text ) { // <!--...-->外,普通文本的处理函数
  1508. // 普通文本直接写入缓冲区
  1509. textBuf.push( text );
  1510. }
  1511. );
  1512. flushTextBuf(); // 将缓冲区中的text节点内容写入
  1513. autoCloseCommand( analyseContext );
  1514. return analyseContext.targets;
  1515. }
  1516. var etpl = new Engine();
  1517. etpl.Engine = Engine;
  1518. if ( typeof exports == 'object' && typeof module == 'object' ) {
  1519. // For CommonJS
  1520. exports = module.exports = etpl;
  1521. }
  1522. else if ( typeof define == 'function' && define.amd ) {
  1523. // For AMD
  1524. define( etpl );
  1525. }
  1526. else {
  1527. // For <script src="..."
  1528. root.etpl = etpl;
  1529. }
  1530. })(this);