UNPKG

@alicd/templateparser

Version:

walle template parser

520 lines (441 loc) 19 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * @license * Copyright Alibaba Group and its affiliates. All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var _htmlparser = require('@alicd/htmlparser'); var _htmlparser2 = _interopRequireDefault(_htmlparser); var _config = require('./config'); var _config2 = _interopRequireDefault(_config); var _style = require('./modules/style'); var _style2 = _interopRequireDefault(_style); var _express = require('./modules/express'); var _express2 = _interopRequireDefault(_express); var _output2 = require('./modules/output'); var _output3 = _interopRequireDefault(_output2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var jsRegExp = new RegExp('(\\' + _config2.default.expressStartTag + '.+?\\' + _config2.default.expressEndTag + '{1,2})'); var replaceExpress = new RegExp('^\\' + _config2.default.expressStartTag + '|\\' + _config2.default.expressEndTag + '$', 'g'); var forEach = function forEach(obj, processCallback) { for (var i in obj) { if (obj.hasOwnProperty(i)) { processCallback(obj[i], i); } } }; var isEmptyObject = function isEmptyObject(obj) { var i = 0; if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object') { return null; } forEach(obj, function (item, index) { i = index + 1; }); return !i; }; /** * traverse arr to fill holder. * * @param {Array} tree array * @returns array */ function traverseArrToFillHolder(tree) { tree.forEach(function (treeItem) { for (var i = 0; i < treeItem.length; i++) { // holder: 0 if (treeItem[i] === undefined) { treeItem[i] = 0; } } // handle 'children' if (Array.isArray(treeItem[3])) { if (treeItem[3].length) { traverseArrToFillHolder(treeItem[3]); } else { delete treeItem[3]; } } }); return tree; } var TemplateParser = function () { function TemplateParser(options) { _classCallCheck(this, TemplateParser); this.template = null; this.domJSON = null; this.jsonTree = null; this.commentContentRegExp = /<!--[\w\W\r\n]*?-->/g; this.options = options || {}; } // 需要解析的模板 // 模板的解析结果,不含有上下文变量 // domJSON的解析结果,含有上下文变量 // 模板注释的正则表达式 _createClass(TemplateParser, [{ key: 'render', /** * render template to logic json. * * @param {string} template template * @param {boolean} runtimeOn whether or not use for runtime * @memberof TemplateParser */ value: function render(template, runtimeOn) { this.template = template; this.tagStartReplaceRegExp = new RegExp(_config2.default.tagStartReplaceStr, 'g'); this.tagEndReplaceRegExp = new RegExp(_config2.default.tagEndReplaceStr, 'g'); this.parseTemplate(); this.createJSONTree(runtimeOn); } }, { key: 'preHandleExpress', value: function preHandleExpress(str) { var r = /[\{\}]/g; var state = 0; var result = []; var startIndex = 0; while (r.test(str)) { var lastIndex = r.lastIndex; var matched = str.charAt(lastIndex - 1); if (matched === '{') { state++; if (startIndex === 0) { startIndex = lastIndex; } } else { state--; } if (state === 0) { var slice = str.slice(0, startIndex); var replaced = str.slice(startIndex, lastIndex); replaced = replaced.replace(/</g, '&lt;').replace(/>/g, '&gt;').trim(); result.push(slice); result.push(replaced); r.lastIndex = 0; str = str.slice(lastIndex); startIndex = 0; } } result.push(str); return result.join(''); } /** * 模板解析功能,解析成标准的json树 * @param tpl String/html选择器 */ }, { key: 'parseTemplate', value: function parseTemplate(tpl) { var _this = this; var template = tpl || this.template; var commentContent = this.commentContentRegExp; var temEle = template; var tagStartReplaceStr = _config2.default.tagStartReplaceStr; var tagEndReplaceStr = _config2.default.tagEndReplaceStr; var tagLeft = new RegExp('\\<', 'g'); var tagRight = new RegExp('\\>', 'g'); var temString = null; if (typeof temEle === 'string') { temString = temEle.trim().replace(/[\n\r]/g, ''); } else if ((typeof temEle === 'undefined' ? 'undefined' : _typeof(temEle)) === 'object' && temEle.getAttribute('type') === 'text/template') { temString = temEle.innerHTML.replace(/[\n\r]/g, '').trim(); } else { return false; } if (this.options.comment !== true) { temString = temString.replace(commentContent, ''); } if (!temString) { this.domJSON = null; console.error('!!页面缺少模板代码'); temString = 'null'; } temString = temString.replace(/(<[\s]+)/g, tagStartReplaceStr + ' '); var regex = /(\{[^\{\}]*(?:\{[^\{\}]*\}[^\{\{]*)*\})/; regex = /(\{.+?\})(?=\s+|\/>|>|<|$)/; var arr = temString.split(regex); forEach(arr, function (item, index) { if (!item) { delete arr[index]; return false; } if (_express2.default.test(item)) { arr[index] = item.replace(tagLeft, tagStartReplaceStr).replace(new RegExp('[\\s]?\\>[\\s]?', 'g'), tagEndReplaceStr); } else if (item.split(/(\{.+?\})/)) { var splitArr = item.split(/(\{.+?\})/); forEach(splitArr, function (it, i) { if (!it) { splitArr[i] = ''; return false; } if (_express2.default.test(it)) { splitArr[i] = it.replace(tagRight, tagEndReplaceStr); } }); arr[index] = splitArr.join(''); } }); temString = this.preHandleExpress(temString); var tagReg = new RegExp('(<\\/?[\\w]+[^<>]*[\\/]?>)', 'g'); arr = temString.split(tagReg); forEach(arr, function (item, index) { // 去除页面标签之间的多余空格 item = item.trim(); if (!item.match(/^</) || !item.match(/>$/)) { arr[index] = item.replace(tagLeft, tagStartReplaceStr).replace(tagRight, tagEndReplaceStr); } }); temString = arr.join(''); arr = null; if (temString) { var handler = new _htmlparser2.default.DefaultHandler(function (error, domJSON) { if (!error) { _this.domJSON = domJSON; } else { console.error(error); } }); var parser = new _htmlparser2.default.Parser(handler); var howManyTagsRegexp = new RegExp('(<[\\w]+[^<>]*[\\/]?>)', 'g'); var tags = temString.match(howManyTagsRegexp); this.tagsNumber = tags && tags.length || 0; // parse complete. parser.parseComplete(temString); } } /** * 根据标签名判断是否是组件 * @param tagName 标签名称 * @param componentsRegExp 是否是组件的规则 * @returns {*} */ }, { key: 'judgmentComponents', value: function judgmentComponents(tagName, componentsRegExp) { componentsRegExp = componentsRegExp || this.componentsRegExp; // 惰性函数 this.judgmentComponents = function (_tagName) { var exp = componentsRegExp; return _tagName.match(exp); }; return this.judgmentComponents(tagName); } /** * transform dom json to logic json. * t -> type ( 0 -> text, 1 -> tag, 2 -> component ) * n -> name * a -> attributes * c -> children ( array -> children, string/function -> text node ) * * structure: [t, n, a, c] * holder: 0 * * @param domJSON dom json tree */ }, { key: 'createJSONTree', value: function createJSONTree(runtimeOn) { var _this2 = this; var domJSON = this.domJSON; if (!domJSON) { return false; } var returnJSON = []; var componentsRegExp = _config2.default.componentsRegExp; var loopAttrName = _config2.default.loopAttrName; this.judgmentComponents('div', componentsRegExp); var isCmp = this.judgmentComponents; var _loopJSON = function _loopJSON(domJSON, recurveJSON) { var parentAttrs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; domJSON.map(function (dom, _index) { // map: 0 -> text, 1 -> tag, 2 -> component var domType = dom.type; domType = domType === 'text' ? 0 : 1; // structure: [t, n, a, c] // mark the components type. var nodeJSON = [domType]; // dom type: 1 -> tag if (domType === 1) { // replace dom data dom.data = dom.data.replace(_this2.tagStartReplaceRegExp, '<').replace(_this2.tagEndReplaceRegExp, '>'); if (isCmp(dom.name)) { // structure: [t, n, a, c] nodeJSON[0] = 2; } // node tag name. nodeJSON[1] = dom.name; // check the attribs if (dom.attribs && !isEmptyObject(dom.attribs)) { var attrObj = {}; // replace: class -> className if (dom.attribs.class) { dom.attribs.className = dom.attribs.class; delete dom.attribs.class; } // process `x-bind` attr if (dom.attribs[_config2.default.bindAttrName]) { var _bindPath = _express2.default.filter(dom.attribs[_config2.default.bindAttrName]).trim(); if (_config2.default.xbindState.test(_bindPath)) { dom.attribs['data-bindpath'] = _bindPath.replace(_config2.default.xbindState, ''); } } // traverse attribs forEach(dom.attribs, function (attrValue, attrName) { // handle: boolean if (typeof attrValue === 'boolean') { attrObj[attrName] = attrValue; return; } // handle: `...` instruction if (_config2.default.spreadAttribute.test(attrName)) { attrObj['data-spread'] = (0, _output3.default)(attrValue, runtimeOn); delete attrObj[attrName]; return; } // handle: `key` attr if (attrName === 'key') { attrObj[attrName] = (0, _output3.default)(attrValue, runtimeOn); return; } // trim attrValue = attrValue.trim(); // handle: style attr if (attrName === 'style' || attrName === 'inputStyle' || attrName === 'overlayStyle') { // format attrObj[attrName] = (0, _style2.default)(attrValue, runtimeOn); return false; } // handle: not `x-for` attr if (attrName !== loopAttrName) { if (dom.name === 'Action' && attrName === 'handler') { attrObj[attrName] = attrValue; } else { attrObj[attrName] = (0, _output3.default)(attrValue, runtimeOn); } return false; } // handle: `x-for` attr // structure: [args, loopData] var forObj = []; var loopStr = void 0; // walle syntax: with `{}` syntax if (_express2.default.test(attrValue.trim())) { // replace `{}` to '' loopStr = attrValue.replace(replaceExpress, '').trim(); } // handle split arr var loopStrArr = loopStr.split(' in '); if (loopStrArr && loopStrArr.length === 2) { var argumentsStr = loopStrArr[0].trim(); if (argumentsStr.match(/^\([\w\W]*?\)$/)) { argumentsStr = argumentsStr.replace(/\)$|^\(|\s|\$/g, ''); var argumentsStrArr = argumentsStr.split(','); if (argumentsStrArr.length >= 2) { forObj[0] = argumentsStrArr; } else { forObj[0] = argumentsStrArr; } } else { forObj[0] = [argumentsStr.replace(/^\$/g, '')]; } forObj[1] = (0, _output3.default)(_config2.default.expressStartTag + loopStrArr[1].trim() + _config2.default.expressEndTag, runtimeOn); attrObj[attrName] = forObj; } }); // get the attrs // structure: [t, n, a, c] nodeJSON[2] = attrObj; } // check the children if (dom.children && dom.children.length) { // case: single text node if (dom.children.length === 1 && dom.children[0].type === 'text') { var str = dom.children[0].data.trim(); var children = []; // walle syntax: with `{}` syntax if (_express2.default.test(str)) { children.push({ type: 'text', data: str }); } // walle syntax with `{}` partially else { var arr = str.split(jsRegExp); // remove the useless from head to tail if (!arr[0]) { arr.splice(0, 1); } if (!arr[arr.length - 1]) { arr.splice(arr.length - 1, 1); } // handle the split result forEach(arr, function (_it) { children.push({ type: 'text', data: _it }); }); } // structure: [t, n, a, c] nodeJSON[3] = []; _loopJSON(children, nodeJSON[3], parentAttrs); } // case: other else { nodeJSON[3] = []; _loopJSON(dom.children, nodeJSON[3], parentAttrs); } } } // dom type: 0 -> text // walle syntax: with `{}` syntax else if (_express2.default.test(dom.data)) { // structure: [t, n, a, c] nodeJSON[3] = (0, _output3.default)(dom.data, runtimeOn); } // dom type: 0 -> text // walle syntax: with `{}` syntax partially else { // split the `{}` var _arr = dom.data.split(jsRegExp); // remove the useless from head to tail if (!_arr[0]) { _arr.splice(0, 1); } if (!_arr[_arr.length - 1]) { _arr.splice(_arr.length - 1, 1); } // handle the split result if (_arr.length === 1) { nodeJSON[3] = (0, _output3.default)(dom.data, runtimeOn); } else { forEach(_arr, function (perText) { var c = (0, _output3.default)(perText, runtimeOn); var perJSON = [0, 0, 0, c]; recurveJSON.push(perJSON); }); return false; } } recurveJSON.push(nodeJSON); return null; }); }; _loopJSON(domJSON, returnJSON); // traverse to fill holder with 0 traverseArrToFillHolder(returnJSON); this.logicJSON = returnJSON; } }]); return TemplateParser; }(); exports.default = TemplateParser;