@alicd/templateparser
Version:
walle template parser
520 lines (441 loc) • 19 kB
JavaScript
;
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, '<').replace(/>/g, '>').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;