zoukankan      html  css  js  c++  java
  • 从 Vue parseHTML 来学习正则表达式

    写作本文的起源在于,在分析 Vue 源码中 parseHTML 方法时,发现网上对其中正则的解析文章较少,找到的几篇文章也有些语焉不详。于是静下心逐个表达式分析了其中的正则,以备查看。

    常见正则规则可参见附录 1,Vue parseHTML 正则所用规则均可在其中找到定义。

    Vue parseHTML 中所用的所有正则如下:

    const attribute = /^s*([^s"'<>/=]+)(?:s*(=)s*(?:"([^"]*)"+|'([^']*)'+|([^s"'=<>`]+)))?/
    const dynamicArgAttribute = /^s*((?:v-[w-]+:|@|:|#)[[^=]+][^s"'<>/=]*)(?:s*(=)s*(?:"([^"]*)"+|'([^']*)'+|([^s"'=<>`]+)))?/
    const ncname = `[a-zA-Z_][\-\.0-9_a-zA-Z${unicodeRegExp.source}]*`
    const qnameCapture = `((?:${ncname}\:)?${ncname})`
    const startTagOpen = new RegExp(`^<${qnameCapture}`)
    const startTagClose = /^s*(/?)>/
    const endTag = new RegExp(`^<\/${qnameCapture}[^>]*>`)
    const doctype = /^<!DOCTYPE [^>]+>/i
    const comment = /^<!--/
    const conditionalComment = /^<![/
    

    接下来一个个通过拆解表达式,来分析上述正则规则。

    attribute

    const attribute = /^s*([^s"'<>/=]+)(?:s*(=)s*(?:"([^"]*)"+|'([^']*)'+|([^s"'=<>`]+)))?/
    

    分析其结构:

    1. ^s* 匹配 0 至多个以空白字符开头的字符串空白字符的部分

    2. 捕获组:([^s"'<>/=]+) 匹配并捕获 1 至多次除 空白字符 " ' < > / = 以外的所有字符

    3. 非捕获组:(?:s(=)s(?:"(["]*)"+|'([']*)'+|([^s"'=<>`]+)))?

    • s* 匹配 0 至多个空白字符
    • 捕获组:(=) 匹配并捕获 =
    • s* 匹配 0 至多个空白字符
    • 非捕获组:(?:"([^"]*)"+|'([^']*)'+|([^s"'=<>]+))`
      • "([^"]*)"+
        • " 匹配 "
        • ([^"]*) 匹配并捕获 0 至多个除 " 外的字符
        • "+ 匹配 1 至多次 "
      • '([^']*)'+
        • ' 匹配 '
        • ([^']*) 匹配并捕获 0至多个除 ' 外的字符
        • '+ 匹配 1 至多次 '
      • ([^s"'=<>`]+) 匹配并捕获 1 至多次除 空白字符 " ' = < > ` 外的字符
    • ? 匹配 3 中非捕获组 0 次或 1 次

    小结

    attribute 表达式匹配的是:

    1. 以 0 至多个空白字符开头;

    2. 紧接着 1 至多个除 空白字符 " ' < > / = 以外的字符;

    3. 紧接着 0 至多个空白字符;

    4. 紧接着 =

    5. 紧接着 0 至多个空白字符;

    6. 紧接着匹配 0 次或 1 次:

      (1) " + 0 至多个除 " 外的字符 + "

      (2) 或 ' + 0 至多个除 ' 外的字符 + '

      (3) 或 1 至多次除 空白字符 " ' = < > ` 外的字符

    例如:

    <div id="mydiv" class="myClass" style="color: #ff0000" >
    

    在 Vue 的 parseHTML 时,就能将 id="mydiv"class="myClass"style="color: #ff0000"提取出来。

    dynamicArgAttribute

    const dynamicArgAttribute = /^s*((?:v-[w-]+:|@|:|#)[[^=]+][^s"'<>/=]*)(?:s*(=)s*(?:"([^"]*)"+|'([^']*)'+|([^s"'=<>`]+)))?/
    

    分析其结构:

    1. ^s* 匹配以 0 至多个空白字符开头

    2. 捕获组:((?:v-[w-]+:|@|:|#)[[^=]+][^s"'<>/=]*)

    • 非捕获组:(?:v-[w-]+:|@|:|#) 匹配:

      (1)v- + 1 次或多次包括下划线在内的任意单词字符 + :

      (2)或 @

      (3)或 :

      (4)或 #

    • [[^=]+] 匹配 以 [ + 1 次或多次除 = 外的所有字符 + ]

    • [^s"'<>/=]* 匹配 0 次或多次除 空白字符"'<>/= 以外的字符

    1. 非捕获组:(?:s(=)s(?:"(["]*)"+|'([']*)'+|([^s"'=<>`]+)))?

    已在 attribute 章节分析过。

    小结

    dynamicArgAttribute 用于匹配:

    1. 以 0 至多个空白字符开头

    2. 紧接着:

      (1)v- + 1 次或多次包括下划线在内的任意单词字符 + :

      (2)或 @

      (3)或 :

      (4)或 #

    3. 紧接着以 [ + 1 次或多次除 = 外的所有字符 + ]

    4. 匹配 0 次或多次除 空白字符"'<>/= 以外的字符

    5. 紧接着 0 至多个空白字符;

    6. 紧接着 =

    7. 紧接着 0 至多个空白字符;

    8. 紧接着匹配 0 次或 1 次:

      (1) " + 0 至多个除 " 外的字符 + "

      (2) 或 ' + 0 至多个除 ' 外的字符 + '

      (3) 或 1 至多次除 空白字符 " ' = < > ` 外的字符

    例如:

    <a v-bind:[attributeName]="url"> ... </a>
    
    <a v-on:[eventName]="doSomething"> ... </a>
    

    在 Vue 的 parseHTML 时,就能将 v-bind:[attributeName]="url" 这种动态参数提取出来。

    ncname

    const ncname = `[a-zA-Z_][\-\.0-9_a-zA-Z${unicodeRegExp.source}]*`
    

    首先看 unicodeRegExp

    const unicodeRegExp = /a-zA-Zu00B7u00C0-u00D6u00D8-u00F6u00F8-u037Du037F-u1FFFu200C-u200Du203F-u2040u2070-u218Fu2C00-u2FEFu3001-uD7FFuF900-uFDCFuFDF0-uFFFD/
    

    定义了一系列合法字符,通过 Unicode 字符集范围匹配。

    unicodeRegExp.source 用于拿到正则表达式 unicodeRegExp 的字符串。

    ncname 即是一系列合法字符的集合。

    qnameCapture

    const qnameCapture = `((?:${ncname}\:)?${ncname})`
    

    表示匹配 xxx:xxxxxx 模式的字符。

    startTagOpen

    const startTagOpen = new RegExp(`^<${qnameCapture}`)
    

    startTagOpen 可匹配标签开始部分,即:<xxx:xxx<xxx 的模式。

    <xxx:xxx 代表的是带命名空间的 html 标签,文可参见这里,这种类型的标签主要作用是可以指定标签的命名空间,避免冲突。Vue 对这类的标签也做了解析。

    如:<div <math:div

    startTagClose

    const startTagClose = /^s*(/?)>/
    

    ^s*(/?)> 匹配以 0 至多个空白字符开头,接 0 或 1 个 / ,紧接 > 的字符串。

    如: />

    endTag

    const endTag = new RegExp(`^<\/${qnameCapture}[^>]*>`)
    

    匹配以 </ 开头,后接合法字符,后接 0 至多个除 > 以外的任何字符,最后接 >

    如:</div>

    doctype

    const doctype = /^<!DOCTYPE [^>]+>/i
    

    匹配以 <!DOCTYPE 开头,后接 1 至多次除 > 外的所有字符,后接 >。注意该匹配模式不区分大小写。

    如:<!DOCTYPE html>

    comment

    const comment = /^<!--/
    

    匹配以 <!-- 开头的字符串。

    如:<!--STATUS OK-->

    conditionalComment

    const conditionalComment = /^<![/
    

    匹配以 <![ 开头的字符串。条件注释主要用于做浏览器兼容等,文可参见这里

    总结

    本文以 Vue 源码中 parseHTML 方法为例,分析了其中定义的正则表达式,将常见的正则规则做了梳理,同时可以备查 parseHTML 方法的正则规则,方便后续继续分析该方法。

    附录 1 常见正则规则

    正则里的特殊字符

    * ? + . [ ] ( ) { } | ^ $,共 13 个。
    
    1. * 表示匹配前面字符(或括号括起来的表达式,或方括号括起来的字符集)0 次或 多 次;
    2. ?(1)? 表示匹配前面字符(或括号括起来的表达式,或方括号括起来的字符集)0 次或 1 次;(2)? 紧跟在其它任何一个限制符(如 * + 等)后时,匹配模式为非贪婪,尽可能少地匹配;
    3. + 表示匹配前面字符(或括号括起来的表达式,或方括号括起来的字符集)1 次或 多 次;
    4. . 表示匹配除换行符以外的任意字符 1 次;
    5. [] 字符集,表示匹配方括号内字符中的其中一个,其中的特殊字符会被当做普通字符处理;
    6. () 捕获组,表示匹配其中的子表达式;
    7. {n,m} 匹配前面表达式最少 n 次,最多 m 次
    8. | 或,常用在捕获组中;
    9. ^ 匹配字符串的开始位置
    10. $ 匹配字符串的结束位置

    表达式 () 中常见写法

    1. (pattern) 匹配 pattern 并获取该匹配;
    2. (?:pattern) 匹配 pattern 但不获取该匹配;
    3. pattern1(?=pattern2) 匹配 pattern1 后面跟 pattern2 的字符串,不获取该匹配
    4. pattern1(?!pattern2) 匹配 pattern1 后面不跟 pattern2 的字符串,不获取该匹配
    5. (?<=pattern2)pattern1 匹配 pattern1 前面为 pattern2 的字符串,不获取该匹配
    6. (?<!pattern2)pattern1 匹配 pattern1 前面不为 pattern2 的字符串,不获取该匹配

    字符集 [] 常见写法

    1. [x|y] 匹配 x 或 y,可为字符串
    2. [xyz] 匹配 x 或 y 或 z 的字符
    3. [^xyz] 匹配 非 x 或 y 或 z 的字符
    4. [a-z] 匹配 a 到 z 的任意小写字符

    常见特殊字符

    1.  匹配单词边界
    2. d 匹配一个数字字符
    3. 匹配一个换行符
    4. 匹配一个回车符
    5. 匹配制表符
    6. s 匹配任何空白字符
    7. w 匹配包括下划线的任何单词字符
  • 相关阅读:
    JAVA中堆和栈的区别
    怎么回答面试官:你对Spring的理解?
    如何设计一个高可用、高并发秒杀系统
    这应该是把Java内存区域讲的最清楚的一篇文章
    Spring Cloud底层原理解析
    Spring事务管理详解
    选择合适Redis数据结构,减少80%的内存占用
    最强Java并发编程详解:知识点梳理,BAT面试题等
    深入理解HashMap
    Springboot 优雅停止服务的几种方法
  • 原文地址:https://www.cnblogs.com/lilei94/p/14968735.html
Copyright © 2011-2022 走看看