企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] * * * * * ## 1 源代码文件 ### 1-1 解析实现 ~~~ src\strategy\lexer.js ~~~ ### 1-2 解析生成 ~~~ src\vdom\ ~~~ ## 2 流程分析 ### 2-1 节点内容解析 ~~~ src\strategy\lexer.js function lexer(text, recursive) { // 所有生成节点数组 var nodes = [] // recursive参数使用 true 解析子节点时调用 if (recursive && !rbind.test(text)) { return nodes } // recursive参数使用 false 文本数组存储??1,??2,这里实现嵌套 if (!recursive) { text = text.replace(rstring, dig) } // 反复迭代解析 do { // 初始化解析状态outerHTML,node var outerHTML = '' var node = false //创建文本虚拟节点 VText var match = text.match(rtext) if (match) { outerHTML = match[0] node = new VText(outerHTML.replace(rfill, fill)) } //创建注释虚拟节点 VComment if (!node) { match = text.match(rcomment) if (match) { outerHTML = match[0] node = new VComment(match[1].replace(rfill, fill)) // 移除紧挨着<!--ms-for-end:xxxx-->前的空白节点 // 注释节点空白清除 var nodeValue = node.nodeValue if (rspBeforeForEnd.test(nodeValue)) { var sp = nodes[nodes.length - 1] if (sp && sp.type === '#text' && rsp.test(sp.nodeValue)) { nodes.pop() } } } } //创建闭标签虚拟节点 if (!node) { // 闭合节点 match = text.match(rfullTag) if (match) { //贪婪匹配 outerHTML,可能匹配过多 outerHTML = match[0] //节点类型 var type = match[1].toLowerCase() outerHTML = clipOuterHTML(outerHTML, type) //节点属性 match = outerHTML.match(rvoidTag) var props = {} if (match[2]) { handleProps(match[2], props) } //节点innerHTML var innerHTML = outerHTML.slice(match[0].length, (type.length + 3) * -1) // 节点组织 node = { type: type, props: props, template: innerHTML.replace(rfill, fill).trim(), children: [] } node = modifyProps(node, innerHTML, nodes) } } //创建自闭合标签虚拟节点 if (!node) { match = text.match(rvoidTag) if (match) { // outerHTML = match[0] // 节点类型 type = match[1].toLowerCase() // 节点属性 props = {} if (match[2]) { handleProps(match[2], props) } // 节点结点 node = { type: type, props: props, template: '', children: [], isVoidTag: true } modifyProps(node, '', nodes) } } if (node) { // 创建节点成功 保存到数组 nodes.push(node) text = text.slice(outerHTML.length) if (node.type === '#comment' && rspAfterForStart.test(node.nodeValue)) { node.signature = makeHashCode('for') //移除紧挨着<!--ms-for:xxxx-->后的空白节点 text = text.replace(rleftSp, '') } } else { // 创建节点失败 直接跳出 break } } while (1); // 非递归的情况 if (!recursive) { maps = {} } return nodes } ~~~ > text:待解析节点内容 > recursive:是否属于递归解析,节点内解析子节点 * * * * * > 准备返回节点数组 > 检查recursive,是否属于子节点解析 > 循环解析主体 > 1 虚拟文本节点匹配解析Vtext > 2 虚拟注释节点匹配解析VComment > 3 双标签生成虚拟节点VElement > 4 单标签生成虚拟节点VElement > 5 元素节点解析的modifyProps()递归调用lexer()解析子节点 ### 2-2 虚拟节点创建 ~~~ var match = text.match(rtext) node = new VText(outerHTML.replace(rfill, fill)) ~~~ > 虚拟文本节点 ~~~ match = text.match(rcomment) node = new VComment(match[1].replace(rfill, fill)) ~~~ > 虚拟注释节点 ~~~ match = text.match(rfullTag) node = modifyProps(node, innerHTML, nodes) match = text.match(rvoidTag) modifyProps(node, '', nodes) ~~~ > 虚拟元素节点 ### 2-3 handleProps(str, props)属性操作 ~~~ var ramp = /&amp;/g var rnowhite = /\S+/g var rquote = /&quot;/g var rnogutter = /\s*=\s*/g function handleProps(str, props) { str.replace(rnogutter, '=').replace(rnowhite, function (el) { var arr = el.split('='), value = arr[1] || '', name = arr[0].toLowerCase() if (arr.length === 2) { if (value.indexOf('??') === 0) { value = value.replace(rfill, fill). slice(1, -1). replace(ramp, '&'). replace(rquote, '"') } } props[name] = value }) } ~~~ ### 2-4 modifyProps(node, innerHTML, nodes)元素节点修正与递归解析 ~~~ function modifyProps(node, innerHTML, nodes) { // 节点类型 var type = node.type if (node.props['ms-skip']) { // 包含ms-skip属性的节点 node.skipContent = true } else { // 非包含ms-skip属性的节点 // 根据节点属性操作 switch (type) { case 'style': case 'script': case 'noscript': case 'template': case 'textarea': node.skipContent = true if (type === 'textarea') { node.props.type = 'textarea' } break case 'input': if (!node.props.type) { node.props.type = 'text' } case 'xmp': node.children.push(new VText(node.template)) break case 'option': node.children.push(new VText(trimHTML(node.template))) break default: // 递归解析子节点 if (!node.isVoidTag) { var childs = lexer(innerHTML, true) node.children = childs // 如果是table节点 if (type === 'table') { addTbody(node.children) } } break } // for类型表达式 var forExpr = node.props['ms-for'] if (forExpr) { nodes.push({ type: '#comment', nodeValue: 'ms-for:' + forExpr, signature: makeHashCode('for') }) delete node.props['ms-for'] nodes.push(node) node = { type: '#comment', nodeValue: 'ms-for-end:' } } } return node } ~~~ ### 2-5 clipOuterHTML(matchText, type) 截取OuterHTML ~~~ var openStr = '(?:\\s+[^>=]*?(?:=[^>]+?)?)*>' var tagCache = {}// 缓存所有匹配开标签闭标签的正则 var rchar = /./g var regArgs = avalon.msie < 9 ? 'ig' : 'g'//IE6-8,标签名都是大写 function clipOuterHTML(matchText, type) { var opens = [] var closes = [] // 开标签与闭标签的缓存 var ropen = tagCache[type + 'open'] || (tagCache[type + 'open'] = new RegExp('<' + type + openStr, regArgs)) var rclose = tagCache[type + 'close'] || (tagCache[type + 'close'] = new RegExp('<\/' + type + '>', regArgs)) /* jshint ignore:start */ //注意,页面有时很长,b的数值就很大,如 //000000000<000000011>000000041<000000066>000000096<000000107> matchText.replace(ropen, function (_, b) { //取得所有开标签的位置 opens.push(('0000000000' + b + '<').slice(-10)) return _.replace(rchar, '1') }).replace(rclose, function (_, b) { //取得所有闭标签的位置 closes.push(('0000000000' + b + '>').slice(-10)) }) /* jshint ignore:end */ /*<div> <div>01</div> <div>02</div> </div> <div>222</div> <div>333</div> 会变成 000< 005< 012> 018< 025> 031> 037< 045> 051< 059> 再变成 <<><>> <> <> 最后获取正确的>的索引值,这里为<<><>>的最后一个字符,*/ var pos = opens.concat(closes).sort() var gtlt = pos.join('').replace(rnumber, '') var k = 0, last = 0 for (var i = 0, n = gtlt.length; i < n; i++) { var c = gtlt.charAt(i) if (c === '<') { k += 1 } else { k -= 1 } if (k === 0) { last = i break } } // (</>为三个字符) var findex = parseFloat(pos[last]) + type.length + 3 //取得正确的outerHTML return matchText.slice(0, findex) } ~~~ ### 2-7 addTbody(nodes) table修正 ~~~ function addTbody(nodes) { var tbody, needAddTbody = false, count = 0, start = 0, n = nodes.length for (var i = 0; i < n; i++) { var node = nodes[i] if (!tbody) { if (node.type === 'tr') { tbody = { type: 'tbody', template: '', children: [], props: {} } tbody.children.push(node) needAddTbody = true if (start === 0) start = i nodes[i] = tbody } } else { if (node.type !== 'tr' && node.type.charAt(0) !== "#") { tbody = false } else { tbody.children.push(node) count++ nodes[i] = 0 } } } if (needAddTbody) { for (i = start; i < n; i++) { if (nodes[i] === 0) { nodes.splice(i, 1) i-- count-- if (count === 0) { break } } } } } ~~~ ## 4 总结 ### 4-1 意义 解析dom内容为虚拟dom ### 4-2 思路 递归调用lexer()解析节点 ### 4-3 其他操作 [附:虚拟DOM](http://www.kancloud.cn/zmwtp/avalon2/137892)