vue学习—Convert HTML string to AST,如何将html字符串转换为ast数组结构
生活随笔
收集整理的這篇文章主要介紹了
vue学习—Convert HTML string to AST,如何将html字符串转换为ast数组结构
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
獲取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獲取需要解析的模版
解析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 commentif (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// 解析attrwhile (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {attr.start = indexadvance(attr[0].length)attr.end = indexmatch.attrs.push(attr)}if (end) {// 是否匹配到自閉合符號/,匹配到則設置標志屬性unarySlash='/'match.unarySlash = end[1]advance(end[0].length)match.end = indexreturn 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.tagNameconst unarySlash = match.unarySlashif (expectHTML) {// 如標題標簽,不應該被p標簽包裹,如果父級標簽是p,則提前閉合這個p標簽if (lastTag === 'p' && isNonPhrasingTag(tagName)) {parseEndTag(lastTag)}// 如果是可以自閉合的標簽,上個標簽和現在的標簽一樣,則閉合上一個標簽if (canBeLeftOpenTag(tagName) && lastTag === tagName) {parseEndTag(tagName)}}const unary = isUnaryTag(tagName) || !!unarySlashconst l = match.attrs.lengthconst 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.shouldDecodeNewlinesattrs[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*/).lengthattrs[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 oneconst ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)// handle IE svg bug/* istanbul ignore if */if (isIE && ns === 'svg') {attrs = guardIESVGBug(attrs)}// 將傳入的數據轉化成ast對象 type=1let element: ASTElement = createASTElement(tag, attrs, currentParent)if (ns) {element.ns = ns}if (process.env.NODE_ENV !== 'production') {if (options.outputSourceRange) {element.start = startelement.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {cumulated[attr.name] = attrreturn 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 = trueprocess.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 checkif (!inVPre) {processPre(element)if (element.pre) {inVPre = true}}// pre tagif (platformIsPreTag(element.tag)) {inPre = true}// 如果是帶有pre屬性,跳過解析if (inVPre) {// el.attrslist => el.attrsprocessRawAttrs(element)} else if (!element.processed) {// structural directives// 解析v-for= “item in items”,生成element.for,element.alias,element.iteprocessFor(element)// 解析v-if,v-else-if,v-else;v-ifprocessIf(element)// element.once v-once用于渲染一次組件processOnce(element)}// 第一個start tag 為rootif (!root) {root = elementif (process.env.NODE_ENV !== 'production') {// 不能使用slot,template,v-for在root上checkRootConstraints(root)}}// 非自閉合if (!unary) {// last <=> currentParent = element []currentParent = elementstack.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 = expaddIfCondition(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}} }解析純文本
let text, rest, next // 包含 < plain text的情況 if (textEnd >= 0) {rest = html.slice(textEnd) // 包含匹配到的<// 直到匹配到下一個結束、起始、注釋、條件注釋為止while (!endTag.test(rest) &&!startTagOpen.test(rest) &&!comment.test(rest) &&!conditionalComment.test(rest)) {// < in plain text, be forgiving and treat it as text// 文本里面的<作為普通字符,匹配下一個<next = rest.indexOf('<', 1)if (next < 0) breaktextEnd += nextrest = html.slice(textEnd)}// 獲取匹配到的純文本text = html.substring(0, textEnd) }if (textEnd < 0) {text = html }if (text) {advance(text.length) }// 處理純文本 if (options.chars && text) {options.chars(text, index - text.length, index) }chars函數處理文本
chars (text: string, start: number, end: number) {// 父元素外面的text,或者template是純text,都被被忽略if (!currentParent) {if (process.env.NODE_ENV !== 'production') {if (text === template) {warnOnce('Component template requires a root element, rather than just text.',{ start })} else if ((text = text.trim())) {warnOnce(`text "${text}" outside root element will be ignored.`,{ start })}}return}// IE textarea placeholder bug/* istanbul ignore if */if (isIE &¤tParent.tag === 'textarea' &¤tParent.attrsMap.placeholder === text) {return}const children = currentParent.childrenif (inPre || text.trim()) {text = isTextTag(currentParent) ? text : decodeHTMLCached(text) // 轉譯html里面的特殊字符} else if (!children.length) {// remove the whitespace-only node right after an opening tagtext = ''} else if (whitespaceOption) {if (whitespaceOption === 'condense') {// in condense mode, remove the whitespace node if it contains// line break, otherwise condense to a single spacetext = lineBreakRE.test(text) ? '' : ' '} else {text = ' '}} else {text = preserveWhitespace ? ' ' : ''}if (text) {if (whitespaceOption === 'condense') {// condense consecutive whitespaces into single spacetext = text.replace(whitespaceRE, ' ')}let reslet child: ?ASTNodeif (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {// 含有動態文本文本child = {type: 2,expression: res.expression,tokens: res.tokens,text}} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {// 填入text(不含動態文本),或者填入單一一個' '(有則不再填入child = {type: 3,text}}if (child) {if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {child.start = startchild.end = end}// 將文本加入父元素的childrenchildren.push(child)}} }parseText
// preText?_s(_f(text))+lastText? // 純文本返回undefined export function parseText (text: string,delimiters?: [string, string] ): TextParseResult | void {const tagRE = delimiters ? buildRegex(delimiters) : defaultTagREif (!tagRE.test(text)) {return}const tokens = []const rawTokens = []let lastIndex = tagRE.lastIndex = 0let match, index, tokenValuewhile ((match = tagRE.exec(text))) {index = match.index// push text tokenif (index > lastIndex) {rawTokens.push(tokenValue = text.slice(lastIndex, index))tokens.push(JSON.stringify(tokenValue))}// tag tokenconst exp = parseFilters(match[1].trim())tokens.push(`_s(${exp})`)rawTokens.push({ '@binding': exp })lastIndex = index + match[0].length}if (lastIndex < text.length) {rawTokens.push(tokenValue = text.slice(lastIndex))tokens.push(JSON.stringify(tokenValue))}return {expression: tokens.join('+'),tokens: rawTokens} }解析標簽字符串
// End tag: // stack為空匹配到的結尾標簽,除了</br>, </p>都會被忽略 const endTagMatch = html.match(endTag) if (endTagMatch) {const curIndex = indexadvance(endTagMatch[0].length)// 匹配到的正則,start,endparseEndTag(endTagMatch[1], curIndex, index)continue }parseEndTag
function parseEndTag (tagName, start, end) {let pos, lowerCasedTagNameif (start == null) start = indexif (end == null) end = index// Find the closest opened tag of the same typeif (tagName) {lowerCasedTagName = tagName.toLowerCase()for (pos = stack.length - 1; pos >= 0; pos--) {if (stack[pos].lowerCasedTag === lowerCasedTagName) {break}}} else {// If no tag name is provided, clean shoppos = 0}if (pos >= 0) {// Close all the open elements, up the stack,關閉匹配到的標簽之后的所有未閉合標簽for (let i = stack.length - 1; i >= pos; i--) {if (process.env.NODE_ENV !== 'production' &&(i > pos || !tagName) &&options.warn) {options.warn(`tag <${stack[i].tag}> has no matching end tag.`,{ start: stack[i].start })}if (options.end) {options.end(stack[i].tag, start, end)}}// Remove the open elements from the stackstack.length = poslastTag = pos && stack[pos - 1].tag} else if (lowerCasedTagName === 'br') {if (options.start) {options.start(tagName, [], true, start, end)}} else if (lowerCasedTagName === 'p') {// 左邊自閉合,如果是p標簽,輔助閉合if (options.start) {options.start(tagName, [], false, start, end)}if (options.end) {options.end(tagName, start, end)}}}end
end (tag, start, end) {const element = stack[stack.length - 1]// pop stackstack.length -= 1currentParent = stack[stack.length - 1]if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {element.end = end}closeElement(element) }closeElement
function closeElement (element) {// 清除空的子節點trimEndingWhitespace(element)if (!inVPre && !element.processed) {element = processElement(element, options)}// tree management 元素閉合之后,stack為空,且該元素不為root,則查看該元素是否有if或者else-if標簽// 有,則給root增加if對象,無則拋棄if (!stack.length && element !== root) {// allow root elements with v-if, v-else-if and v-elseif (root.if && (element.elseif || element.else)) {if (process.env.NODE_ENV !== 'production') {checkRootConstraints(element)}addIfCondition(root, {exp: element.elseif,block: element})} else if (process.env.NODE_ENV !== 'production') {warnOnce(`Component template should contain exactly one root element. ` +`If you are using v-if on multiple elements, ` +`use v-else-if to chain them instead.`,{ start: element.start })}}// 根元素下面的標簽,閉合后,作為父元素的child插入if (currentParent && !element.forbidden) {if (element.elseif || element.else) {// 存在v-else-if,或者v-else,與同級v-if元素綁定processIfConditions(element, currentParent)} else {if (element.slotScope) {// scoped slot// keep it in the children list so that v-else(-if) conditions can// find it as the prev node.const name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element}currentParent.children.push(element)element.parent = currentParent}}// final children cleanup// filter out scoped slotselement.children = element.children.filter(c => !(c: any).slotScope)// remove trailing whitespace node againtrimEndingWhitespace(element)// check pre stateif (element.pre) {inVPre = false}if (platformIsPreTag(element.tag)) {inPre = false}// apply post-transformsfor (let i = 0; i < postTransforms.length; i++) {postTransforms[i](element, options)}}最后解析出來的ast數組大致如下:
總結
以上是生活随笔為你收集整理的vue学习—Convert HTML string to AST,如何将html字符串转换为ast数组结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019年春运贵州道路客运预计达6700
- 下一篇: C++空间分配器简述学习笔记