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

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

内容简介:首先在入口文件处,使用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
    }
  }
}

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

查看所有标签

猜你喜欢:

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

Practical Django Projects, Second Edition

Practical Django Projects, Second Edition

James Bennett / Apress / 2009 / 44.99

Build a django content management system, blog, and social networking site with James Bennett as he introduces version 1.1 of the popular Django framework. You’ll work through the development of ea......一起来看看 《Practical Django Projects, Second Edition》 这本书的介绍吧!

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

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器