vue parseHTML 函数源码解析AST基本形成

yizhihongxing

下面是关于“vue parseHTML 函数源码解析AST基本形成”的完整攻略:

什么是parseHTML函数

parseHTML是Vue.js中的一个函数,主要用来将HTML字符串解析成AST对象。AST(Abstract Syntax Tree)是指抽象语法树,它是源代码的抽象语法结构的树状表现形式。Vue的模板就是由HTML模板和vue上下文中的数据组合而成。parseHTML的作用是将HTML模板转化为AST对象,方便后续编译和渲染。

parseHTML的源码解析

下面是parseHTML的基本实现过程:

function parseHTML(html, options) {
  var stack = []
  var expectHTML = options.expectHTML
  var isUnaryTag$$1 = options.isUnaryTag || no
  var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no
  var index = 0
  var last, lastTag
  while (html) {
    last = html
    // Make sure we're not in a plaintext content element like script/style
    if (!lastTag || !isPlainTextElement(lastTag)) {
      var textEnd = html.indexOf('<')
      if (textEnd === 0) {
        // Comment
        if (comment.test(html)) {
          var commentEnd = html.indexOf('-->')

          if (commentEnd >= 0) {
            advance(commentEnd + 3)
            continue
          }
        }

        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          var conditionalEnd = html.indexOf(']>')

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

        // Doctype
        var doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }

        // End tag
        var endTagMatch = html.match(endTag)
        if (endTagMatch) {
          var curIndex = index
          advance(endTagMatch[0].length)
          parseEndTag(endTagMatch[1], curIndex, index)
          continue
        }

        // Start tag
        var startTagMatch = parseStartTag()
        if (startTagMatch) {
          handleStartTag(startTagMatch)
          if (shouldIgnoreFirstNewline(last, html)) {
            advance(1)
          }
          continue
        }
      }

      var text = void 0
      if (textEnd >= 0) {
        text = html.substring(0, textEnd)
        advance(textEnd)
      } else {
        text = html
        html = ''
      }

      if (options.chars && text) {
        options.chars(text)
      }
    } else {
      var stackedTag = lastTag.toLowerCase()
      var reStackedTag = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')
      var match = html.match(reStackedTag)
      if (match) {
        var text$$1 = match[1]
        if (options.chars) {
          options.chars(text$$1)
        }
        advance(text$$1.length + match[2].length)
        parseEndTag(stackedTag, index - text$$1.length, index)
      }

      if (!canBeLeftOpenTag$$1(stackedTag)) {
        parseEndTag(stackedTag)
      }
    }

    if (html === last) {
      options.chars && options.chars(html)
      break
    }
  }

  // Clean up any remaining tags
  parseEndTag()

  function advance(n) {
    index += n
    html = html.substring(n)
  }

  function parseStartTag() {
    var start = html.match(startTagOpen)
    if (start) {
      var match = {
        tagName: start[1],
        attrs: [],
        start: index
      }
      advance(start[0].length)
      var end, 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) {
        match.unarySlash = end[1]
        advance(end[0].length)
        match.end = index
        return match
      }
    }
  }

  function handleStartTag(match) {
    var tagName = match.tagName
    var unarySlash = match.unarySlash

    if (expectHTML) {
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag)
      }
      if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
        parseEndTag(tagName)
      }
    }

    var unary = isUnaryTag$$1(tagName) || !!unarySlash

    var l = match.attrs.length
    var attrs = new Array(l)
    for (var i = 0; i < l; i++) {
      var args = match.attrs[i]
      var value = args[3] || args[4] || args[5] || ''
      var 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
      }
    }

    if (!unary) {
      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
      lastTag = tagName
      unarySlash = ''
    }

    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

  function parseEndTag(tagName, start, end) {
    var pos, lowerCasedTagName
    if (start == null) {
      start = index
    }
    if (end == null) {
      end = index
    }

    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase()
    }

    // Find the closest opened tag of the same type
    if (tagName) {
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break
        }
      }
    } else {
      // If no tag name is provided, clean shop
      pos = 0
    }

    if (pos >= 0) {
      // Close all the OPENED tags that appear before the CLOSE tag
      for (var 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.")
          )
        }
        if (options.end) {
          options.end(stack[i].tag, start, end)
        }
      }

      // Remove the closed tags from the stack
      stack.length = pos
      lastTag = pos && stack[pos - 1].tag
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end)
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end)
      }
      if (options.end) {
        options.end(tagName, start, end)
      }
    }
  }
}

实现逻辑解析

上述代码实现了parseHTML函数,其实现逻辑主要有以下几个步骤:

  1. 首先定义了一个stack数组,用于存储解析中遍历到的元素信息。
  2. 循环处理html字符串,如果当前的元素不是纯文本节点,则查找接下来的标签,并依次处理。
  3. 如果发现标签是注释节点,则跳过。
  4. 如果发现标签是条件注释,则跳过。
  5. 如果发现标签是文档类型声明,则跳过。
  6. 如果发现标签是结束标签,则使用parseEndTag函数处理。
  7. 如果发现标签是开始标签,则使用parseStartTag函数处理。
  8. 处理结束后,再次使用parseEndTag函数处理stack数组中剩余的元素。

总体上,parseStartTag函数负责解析开始标签,handleStartTag函数负责处理解析到的开始标签;parseEndTag函数负责解析结束标签。在这三个函数的协调下,parseHTML函数完成了将HTML解析为AST对象的过程。

示例说明

下面给出两个使用parseHTML的示例,以更清晰地展示这个函数的用法:

示例1

<div>
  <p>这是一段HTML模板</p>
  <span>{{message}}</span>
</div>
import { parseHTML } from 'vue/src/compiler/parser/html-parser'

const html = '<div><p>这是一段HTML模板</p><span>{{message}}</span></div>'
const ast = parseHTML(html,{
  expectHTML: true,
  isUnaryTag: function () {},
  canBeLeftOpenTag: function () {},
  chars: function () {}
})
console.log(ast)

上述代码中,我们定义了一个HTML字符串,然后通过parseHTML函数将其解析为AST对象,并输出到控制台。其中,parseHTML函数的参数中包含一些配置项,包括是否期望解析为HTML,是否将指定标签解析为一元标签等。

示例2

<template>
  <div>
    <p>这是一段HTML模板</p>
    <span>{{message}}</span>
  </div>
</template>
import { parseHTML } from 'vue/src/compiler/parser/html-parser'

const html = '<div><p>这是一段HTML模板</p><span>{{message}}</span></div>'
const ast = parseHTML(html,{
  expectHTML: true,
  isUnaryTag: function (tag) {
    return tag === 'template'
  },
  canBeLeftOpenTag: function () {},
  chars: function () {}
})
console.log(ast)

上述代码中,我们定义了一个HTML字符串,其中包含了一个template标签。在解析时,我们通过将isUnaryTag函数的返回值设为true,将template标签解析为一元标签,而不是普通标签。

通过以上两个示例,我们可以看到,parseHTML函数具有很强的灵活性,可以通过不同的配置项对不同的HTML模板进行不同的解析处理。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue parseHTML 函数源码解析AST基本形成 - Python技术站

(0)
上一篇 2023年5月27日
下一篇 2023年5月27日

相关文章

  • node实现socket链接与GPRS进行通信的方法

    要实现Node.js实现与GPRS进行通信的方法,需要考虑以下步骤: Node.js服务端:首先需要在Node.js服务端建立socket通信,用于接受来自GPRS设备的请求。可以使用Node.js的net模块来创建TCP连接。 数据格式:GPRS和Socket通信时,需要协商好数据的格式,因为Socket只支持字符串和Buffer两种数据类型。因此在通信前…

    Vue 2023年5月28日
    00
  • vue如何使用driver.js实现项目功能向导指引

    要在Vue项目中使用driver.js实现功能向导指引,可以按照以下步骤操作: 步骤一:安装driver.js 在Vue项目中使用driver.js之前,需要先安装这个库。可以通过运行下面的命令来安装: npm install driver.js –save 这个命令会在项目中安装driver.js和他的依赖。 步骤二:创建driver.js实例 在Vue…

    Vue 2023年5月27日
    00
  • vue el-switch初始值(默认值)不能正确显示状态问题及解决

    Vue el-switch初始值不能正确显示状态问题及解决攻略 问题描述: vue使用element UI库中的switch组件时,如果设置了默认值,可能会出现初始状态无法正确显示的问题。 问题分析: 这个问题的原因是因为element UI中的switch并没有提供v-model来绑定value值,而是提供了v-model来绑定一个boolean值,也就是…

    Vue 2023年5月27日
    00
  • vue router权限管理实现不同角色显示不同路由

    实现权限管理需要以下步骤: 1. 安装Vue Router npm install vue-router –save 2. 配置路由 在router/index.js文件中,定义路由和对应的组件,并为每个路由定义一个meta字段,用于存放该路由需要的权限。 import Vue from ‘vue’ import Router from ‘vue-rout…

    Vue 2023年5月27日
    00
  • Vue深入理解之v-for中key的真正作用

    首先我们需要了解v-for指令的使用方法。在Vue.js中,通过v-for指令可以很方便地渲染列表数据。使用v-for指令时,一定要加上唯一的key属性,这个属性的作用在于帮助Vue.js区分每个元素,从而提升渲染的性能和效率。 那么,key属性到底有什么作用呢?的确有很多人误解了key属性的作用,认为只是为了区分每个元素,但其实key属性还有很多其他的功能…

    Vue 2023年5月27日
    00
  • vue通过数据过滤实现表格合并

    下面是详细讲解“Vue通过数据过滤实现表格合并”的完整攻略。 1. 背景介绍 在实际开发中,我们经常会遇到需要在表格中合并相邻的单元格的需求,例如合并相邻的行或列中相同的单元格值,以便提高表格的可读性和美观度。实现这个需求的方式有很多种,本文将介绍如何在Vue中通过数据过滤实现表格的合并。 2. 实现过程 2.1 数据处理 首先,我们需要对表格数据进行处理,…

    Vue 2023年5月27日
    00
  • vue中destroyed方法的使用说明

    当一个组件(component)被销毁时,Vue 会自动调用该组件的生命周期钩子函数 destroyed。destroyed 生命周期是在组件的程序和网络活动结束后被调用的,并且在其它生命周期钩子函数后执行。这意味着 Vue 实例及其数据观察者已被解绑定,所有的事件监听器和子组件已被移除,所有的计时器和异步任务已被清理。下面就详细讲解 destroyed 方…

    Vue 2023年5月28日
    00
  • Vue路由vue-router用法讲解

    首先需要明确的是Vue Router是Vue.js官方的路由管理器,用于实现单页应用(SPA)的路由功能。下面我将详细讲解Vue Router的用法。 一、Vue Router的基本使用 1. 安装 使用Vue Router需要先安装它。可以通过以下命令行安装最新版本的Vue Router: npm install vue-router 安装完成后,在需要使…

    Vue 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部