vue学习—Convert HTML string to AST,如何将html字符串转换为ast数组结构

32次阅读

共计 5779 个字符,预计需要花费 15 分钟才能阅读完成。

获取 html 字符串
首先在入口文件处,使用 template 属性或者 el 属性获取到需要解析的 html 字符串
template
1.html 字符串,如
Vue.component(‘alert-box’, {
template: `
<div class=”demo-alert-box”>
<strong>Error!</strong>
<slot></slot>
</div>
`
})
2. 如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板
el
类型:string | Element 通过 el 获取需要解析的模版
export function query (el: string | Element): Element {
if (typeof el === ‘string’) {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== ‘production’ && warn(
‘Cannot find element: ‘ + el
)
return document.createElement(‘div’)
}
return selected
} else {
return el
}
}
解析 html 字符串
通过 while 循环结合正则表达式逐步匹配拆解 html 字符串
匹配注释与 html 声明文件
// Comment:
// 添加在 root 元素下面的 comment 会被忽略
if (comment.test(html)) {
const commentEnd = html.indexOf(‘–>’)

if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
}
advance(commentEnd + 3)
continue
}
}

// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
// ie comment
if (conditionalComment.test(html)) {
const conditionalEnd = html.indexOf(‘]>’)

if (conditionalEnd >= 0) {
advance(conditionalEnd + 2)
continue
}
}

// Doctype:
// match
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
匹配标签起始位置
匹配标签起始位置,startTagOpen 匹配到,但是 startTagClose 匹配失败,那么失败前的 html 片段就会被抛弃。
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeLetters}]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)

/** 解析起始标签,使用正则匹配 attrs,并将匹配到的正则数组放到 attrs 数组里面 */
function parseStartTag () {
// 标签名
const start = html.match(startTagOpen)
if (start) {
const match = {
tagName: start[1],
attrs: [],
start: index
}
advance(start[0].length)
let end, attr
// 解析 attr
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
attr.start = index
advance(attr[0].length)
attr.end = index
match.attrs.push(attr)
}
if (end) {
// 是否匹配到自闭合符号 /,匹配到则设置标志属性 unarySlash=’/’
match.unarySlash = end[1]
advance(end[0].length)
match.end = index
return match
}
}
}

匹配函数首先匹配标签起始位置,匹配失败则返回进入下一步。
匹配成功则创建 match 对象,tagName 为匹配到的标签名。然后切割 html 字符串,进行下一步匹配。
通过 while 循环,匹配起始标签上的 attr,动态 attr,将匹配到的正则放入 attrs 数组。
最后匹配起始标签结束符 const startTagClose = /^\s*(\/?)>/

匹配成功,则根据是否匹配到自闭合标志 / 来给 unarySlash 属性赋值,最后返回 match 对象,进行下一步处理。

进一步处理 macth 对象
/** 解析上一步获取的正则 attrs,保存为 {name, value} 格式,
* 并且将被浏览器转译的换行或特殊字符或者 href 里面的换行反转为相应符号,
* 最后将 tagname,attrs 等传递给调用函数的 start 函数 */
function handleStartTag (match) {
const tagName = match.tagName
const unarySlash = match.unarySlash

if (expectHTML) {
// 如标题标签,不应该被 p 标签包裹,如果父级标签是 p,则提前闭合这个 p 标签
if (lastTag === ‘p’ && isNonPhrasingTag(tagName)) {
parseEndTag(lastTag)
}
// 如果是可以自闭合的标签,上个标签和现在的标签一样,则闭合上一个标签
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}

const unary = isUnaryTag(tagName) || !!unarySlash

const l = match.attrs.length
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
// 优先获取匹配到的第三个正则捕获
const value = args[3] || args[4] || args[5] || ”
const shouldDecodeNewlines = tagName === ‘a’ && args[1] === ‘href’
? options.shouldDecodeNewlinesForHref
: options.shouldDecodeNewlines
attrs[i] = {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
}
if (process.env.NODE_ENV !== ‘production’ && options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length
attrs[i].end = args.end
}
}

// 非自闭合标签,存入 stack 数组
if (!unary) {
stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
// 1. 修改 lastTag,保存堆中的最上层数组项
lastTag = tagName
}

// 将【匹配到的元素返回给上一级解析
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
start 函数解析标签起始对象
createASTElement
export function createASTElement (
tag: string,
attrs: Array<ASTAttr>,
parent: ASTElement | void
): ASTElement {
return {
type: 1,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
rawAttrsMap: {},
parent,
children: []
}
}
start (tag, attrs, unary, start) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === ‘svg’) {
attrs = guardIESVGBug(attrs)
}

// 将传入的数据转化成 ast 对象 type=1
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}

if (process.env.NODE_ENV !== ‘production’) {
if (options.outputSourceRange) {
element.start = start
element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
cumulated[attr.name] = attr
return cumulated
}, {})
}
attrs.forEach(attr => {
if (invalidAttributeRE.test(attr.name)) {
warn(
`Invalid dynamic argument expression: attribute names cannot contain ` +
`spaces, quotes, <, >, / or =.`,
{
start: attr.start + attr.name.indexOf(`[`),
end: attr.start + attr.name.length
}
)
}
})
}

if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true
process.env.NODE_ENV !== ‘production’ && warn(
‘Templates should only be responsible for mapping the state to the ‘ +
‘UI. Avoid placing tags with side-effects in your templates, such as ‘ +
`<${tag}>` + ‘, as they will not be parsed.’,
{start: element.start}
)
}

// apply pre-transforms
// 提前解析 <input :type=’type’ v-model=’input’ />
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element
}

// v-pre check
if (!inVPre) {
processPre(element)
if (element.pre) {
inVPre = true
}
}
// pre tag
if (platformIsPreTag(element.tag)) {
inPre = true
}

// 如果是带有 pre 属性,跳过解析
if (inVPre) {
// el.attrslist => el.attrs
processRawAttrs(element)
} else if (!element.processed) {
// structural directives
// 解析 v -for=“item in items”, 生成 element.for,element.alias,element.ite
processFor(element)
// 解析 v -if,v-else-if,v-else;v-if
processIf(element)
// element.once v-once 用于渲染一次组件
processOnce(element)
}

// 第一个 start tag 为 root
if (!root) {
root = element
if (process.env.NODE_ENV !== ‘production’) {
// 不能使用 slot,template,v-for 在 root 上
checkRootConstraints(root)
}
}

// 非自闭合
if (!unary) {
// last <=> currentParent = element []
currentParent = element
stack.push(element)
} else {
closeElement(element)
}
}
processIf
/** 如果解析到 v -if,给 element 增加 if 对象,如果解析到 else 或者 v -else-if,
* 标记,等到标签闭合的时候做处理。
*/
function processIf (el) {
const exp = getAndRemoveAttr(el, ‘v-if’)
if (exp) {
el.if = exp
addIfCondition(el, {
exp: exp,
block: el
})
} else {
if (getAndRemoveAttr(el, ‘v-else’) != null) {
el.else = true
}
const elseif = getAndRemoveAttr(el, ‘v-else-if’)
if (elseif) {
el.elseif = elseif
}
}
}

正文完
 0