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

栏目: Html · 发布时间: 5年前

内容简介:首先在入口文件处,使用template属性或者el属性获取到需要解析的html字符串1.html字符串,如2.如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板

获取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
      }
    }
  }
const startTagClose = /^\s*(\/?)>/

进一步处理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
    }
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ACM国际大学生程序设计竞赛亚洲区预选赛真题题解

ACM国际大学生程序设计竞赛亚洲区预选赛真题题解

郭炜 / 电子工业 / 2011-7 / 49.00元

ACM国际大学生程序设计竞赛(ACM International Collegiate Programming Contest,简称ACM/ICPC)是世界上历史最悠久,规模最大、最具声望的程序设计竞赛,一直受到众多国际知名大学的重视,全球著名IT公司更是争相招募竞赛的优胜者。 该项赛事分为各大洲预选赛和全球总决赛两个阶段。北京大学多次在亚洲区预选赛中负责命题工作,是中国在ACM/ICPC命......一起来看看 《ACM国际大学生程序设计竞赛亚洲区预选赛真题题解》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具