资讯专栏INFORMATION COLUMN

正则从零到简单分析html标签

antz / 1574人阅读

摘要:对于正则之前一直是一个百度程序员也许超过一半甚至更多的程序员也是那么这次来学习一下正则表达式事出有因这部分介绍一下需求的由来与主要内容无关工作上有了这样的需求端从来的数据格式是也就是文章内容并夹杂着诸多标签和嵌套然而正在开发的是项目的标签

对于正则之前一直是一个"百度程序员", 也许超过一半甚至更多的程序员也是, 那么这次来学习一下正则表达式.

事出有因

这部分介绍一下需求的由来, 与主要内容无关.

工作上有了这样的需求:

web端从ueditor来的数据格式是html, 也就是

文章内容

, 并夹杂着诸多标签和嵌套.

然而正在开发的是react-native项目, rn的标签和html完全不兼容, 是View, Text, Image等.

那么把从web存入的数据读取到rn上就出了大麻烦, 甚至有些地方要进行跳转, 有些图片要显示, 那么怎么办呢.

通过百度"js如何验证邮箱"已经无法满足需求了, 只能学一下了.

目标

万事有目标, 我们要把一下内容转换成rn的内容:

嘿嘿嘿拖过来的图片哦
abcd

转换结果是:

嘿嘿嘿

拖过来的图片哦

a

b
...

本文会从零基础出发达成这个目标.

讲解顺序: 正则介绍 => 正则语法系统 => 简单的例子讲解 => 尝试实现目标以及碰到的问题 => 实现目标

什么是正则

初中时候学的通配符, 用?代表一个任意字符, 用*代表任意个任意字符来进行搜索, 正则也是如此. 比如:

123[abc]匹配以下哪组字符?

123c

123d

123e

123f

选了1的朋友你已经知道正则是什么了. 123[abc]就是正则, 代表匹配内容为: 前三个字符分别为123, 第四个字符是abc中的一个, 这个正则遇到123a, 123b, 123c都可以匹配成功, 其他任何都匹配失败.

正则语法

百度了正则表达式看到的东西都用了很多术语, 让人有点犯浑. 我经过学习把正则抽象为两个部分: 内容修饰.

看到一长串正则觉得稀里哗啦, 但是里面的每个符号一定都属于内容或是修饰.

内容

内容的形式有3种:

直接匹配: 举例就是刚才的123[abc]中的123, 这种匹配需要完全吻合才能匹配, 123就唯一匹配123.

范围匹配: 用中括号表示, 也就是刚才例子中的[abc]. 这种情况也就是三选一. 任意匹配abc, 而不是匹配abc. 还有两种形式: 加-来表示范围, 比如[a-z]; 表示排除范围内的^, 比如[^abc]

匹配并选择缓存到子匹配: 用圆括号表示, 圆括号中的内容语法是"直接匹配"但会被记入缓存作为子匹配, 我记得我最初接触正则就是url rewrite, 写了url正则之后用$1, $2来重写url.

在范围匹配中, 我们经常会用: 数字/字母, 也就是[0-9], [a-zA-Z], 但是经常用到重复地写麻烦又看不能装逼了, 所以产生了一些快捷方式: d代表[0-9], w代表[0-9a-zA-Z_]这正好是常用的用户名和密码的规则.

这里深入一下圆括号匹配的两个点. 作为拓展, 可以先不看一下的内容直接到下一部分.

因为圆括号中可以用|符号来表示或的关系, 但有时候又不想被加入缓存. 于是可以用?:来表示不需要缓存. 例子:hello (?:world|regular expression), 用来匹配hello world或者hello regular expression, 但又不需要把world储存为缓存.

如果之前已经用圆括号, 那么期望之后出现同样的内容, 可以用1这样加数字来表示. 举个例子: 单引号和双引号, 我们要匹配"123"或者"123", 但是要保持引号一致. ("|")1231就可以解决问题.

修饰

我把修饰部分分为数量修饰和边界修饰.

数量修饰: 符号为{}, 想到了谷歌: go{2,4}gle这个正则可以匹配google, gooogle, goooogle, 代表这个o可以匹配2或者4次. 当然只是为了举例可以枚举, 因为go{2,}gle代表可以无限个o, 这样举例不方便.

与之前的范围匹配一样, 数量修饰也有快捷符号: ?代表{0,1}, *代表{0,}, +代表{1,}. 都很形象, 不用死记, 就像刚才的d for digital, w for word. 看过一个例子: colou?r 这里的?表示可有可无, 美式和英式的拼写都可以匹配.

另外在"无上限"的数量的右边加?代表不贪婪匹配, 会匹配数量最少的内容. 举例: a+匹配aaaaa的结果为aaaaa, a+?匹配aaaaa的结果为a.

边界修饰: ^表示字符串的头, $表示字符串的尾, 表示字母与空格间的位置. 用来给匹配定位, 具体用法在实际中操作就会有具体感受了.

另外, 正则有一种匹配模式是m, 多行匹配模式, 这个情况里^$也能匹配每一行的开头和结尾.

javascript相关函数

首先明确正则是"正则表达式"与"字符串"发生的匹配关系.

js有个对象是RegExp, 使用方法是new RegExp(pattern, mode), 或者是用/包裹的字面量: /pattern/mode.

这里发现提到了mode匹配模式, 一共三种:

g: 全局匹配, 匹配到一次不会停止, /a/匹配aaa, 如果没有g结果是一个a, 有g结果是3个a.

i: 忽略大小写.

m: 多行模式. 和之前提到的有联动.

三个模式不互斥, 叠加的, 也就是可以new RegExp(patter, "gin").

正则的方法有:

.test(): 返回是否匹配成功, true或者false.

.exec(): 失败返回null, 成功返回数组, 位置0是匹配内容, 之后是圆括号匹配的内容. 要注意的是exec是忽略"g"模式的.

字符串的方法:

.replace(pattern, replacement): replacement可以字符串或方法, 方法的话参数是匹配到的内容.

.match(pattern): 返回数组, 所有匹配到的内容.

分析一些简单常用的例子 是否小数
function isDecimal(strValue )  {  
   var  objRegExp= /^d+.d+$/;
   return  objRegExp.test(strValue);  
}  

d代表数字, +代表至少有1个数字, .转移小数点.

连起来看就是: 至少一个数字(d+) 小数点(.) 至少一个数字(d+) .

^$代表头尾, 真个字符串是小数的全部, 而不是包含小数.

是否中文名
function ischina(str) {
    var reg=/^[u4E00-u9FA5]{2,4}$/;   /*定义验证表达式*/
    return reg.test(str);     /*进行验证*/
}

这个范围是中文的编码范围: [u4E00-u9FA5], {2,4}匹配2~4个. 也就是匹配2~4个中文.

是否八位数字
function isStudentNo(str) {
    var reg=/^d{8}$/;   /*定义验证表达式*/
    return reg.test(str);     /*进行验证*/
}
是否电话号码
function isTelCode(str) {
    var reg= /^((0d{2,3}-d{7,8})|(1[3584]d{9}))$/;
    return reg.test(str);
}

分两个部分: 座机号和手机号, 用|隔开了.

座机号: 0开头的三位数或四位数 短杠 7~8位数字.

手机号: 第一位1, 第二位3584的一个, 剩下由9个数字凑满11位电话.

邮箱地址
function IsEmail(str) {
    var reg=/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+$/;
    return reg.test(str);
}
迈向目标

这个章节开始整理实现需求的思路.

先回忆一下正则的规则, 其实很简单, 和加减乘除一样, 有各种符号: [], (), |, -, {}, +, *, ?. 当然也可以很复杂, 因为也和加减乘除一样, 可以嵌套, 而正则的符号本来就多, 嵌套起来更是晕, 有一些符号在不同地方有不同作用, 比如^.(思考题: 分析一下这两个符号有哪些作用, 在什么场景).

那么我们的目标是: 把一段html分析称rn的标签.

因为rn没有parse的功能, 所以不可以使用replace. (replace是代码高亮的常用手段).

所以我们必须把html分解成js对象, 再从js对象里去分析输出rn标签.

因为html标签分为多种, 为了保证完整性和可维护性, 要把各个标签的正则分开写, 也便于之后在分析每个片段的时候来取子匹配, 比如img标签的src, a标签的href.

经过研究, 正则是不可以拼接的, 只有字符串可以拼接. 所以我们要把不同标签的正则写成字符串, 再在需要的时候拼接. new RegExp(pattern)的pattern参数是可以接受字符串的.

匹配text的难题与正则匹配的动作分析

众所周知, 在html里的text是可以光秃秃的(在rn里必须加上Text标签). 那么如何匹配这光秃秃的东西呢, 我开始想了一个办法: 因为text都在标签之外, 也就是"夹在>和<中的字符", 或者在开头(^)和<间的, 或者>和结尾($)间的. 结果标签全都匹配不到了.

原因是这样的, 如果有"g"的模式, 匹配的过程是这样的:

进行第一次匹配, 匹配成功后把匹配部分排除待匹配内容.

进行第二次匹配, 匹配成功后把匹配部分排除待匹配内容.

直到匹配失败, 返回所有结果.

举个例子:

"applebananaapple".match(/(apple|banana)/g)

结果是["apple", "banana", "apple"]

如果把banana的最后一个字母和apple的第一个字母写成一个:

"applebananapple".match(/(apple|banana)/g)

那么结果就是["apple", "banana"]了.

反而利用了这个特点, 把text的正则写成: 不包含<>/ ([^<>/]+), 并添加在最后一个匹配, 就能正确地匹配出text啦.

揭晓答案

写得急促也许有遗漏, 最后贴上完成需求的代码, 语言是rn, 在map输出的时候带着一些项目业务的逻辑请无视.

import React, {Component} from "react"
import {Text, View, Image, Platform, StyleSheet, TouchableOpacity} from "react-native"
import {ENVS} from "../../config/apiHost"

/*
    必须props: @html: html内容
    可选props:  @style: 字体style; @magnifyImg: 显示大图
 */

const regex = {  // "_" for close tag
    p: `]*?>`,
    _p: `

`, span: `]*?>`, _span: ``, br: `
`, a: `]*?href=("|")([^>]+?)1[^>]*?>([^<]+?)`, // $1 是标点符号用来处理匹配 $2 href的带引号的内容 $3 文件名(a标签的innerText), img: ``, // $1 标点符号 $2 src的内容 text: `[^<>/]+`, // 匹配剩下的, 一定要放在最后 } const tobeRemoved = new RegExp(`(?:${[regex.p, regex._p, regex.span, regex._span, regex.br].join("|")})`, "g") const parseToAst = new RegExp(`(?:${[regex.a, regex.img, regex.text].join("|")})`, "g") export default class Parsed extends Component { render () { let str = this.props.html.trim() if (!str) { return ( html attr not passed to component "parseHtml" ) } matches = str.replace(tobeRemoved, "").match(parseToAst) return ( {matches.map((block, index) => { for (let [key, value] of Object.entries(regex)) { let res = new RegExp(value).exec(block) if (res) { if (key === "text") { return ( {block} ) } if (key === "a") { if (res[2].includes("files")) { // 判断附件 if (/[jpg|png|jpeg]/i.test(res[3])) { // 判断图片 let imgId = res[2].match(/d+/)[0] return ( {this.props.magnifyImg && this.props.magnifyImg(ENVS.production.api_base_url + "/files/" + imgId)}} > ) } } } if (key === "img") { return ( {this.props.magnifyImg && this.props.magnifyImg(res[2])}}> ) } } } })} ) } }

原文地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94696.html

相关文章

  • Day06 - Fetch、filter、正则表达式实现快速古诗匹配

    摘要:正则表达式实现快速古诗匹配作者简介是推出的一个天挑战。数据匹配操作使用基础参考文档项目源码分析正则找出匹配的诗句替换高亮的标签构造值会返回带搜索关键字的新数组。执行对大小写不敏感的匹配。 Day06 - Fetch、filter、正则表达式实现快速古诗匹配 作者:©liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战。项目免费提供了 30 ...

    Warren 评论0 收藏0
  • Day06 - Fetch、filter、正则表达式实现快速古诗匹配

    摘要:正则表达式实现快速古诗匹配作者简介是推出的一个天挑战。数据匹配操作使用基础参考文档项目源码分析正则找出匹配的诗句替换高亮的标签构造值会返回带搜索关键字的新数组。执行对大小写不敏感的匹配。 Day06 - Fetch、filter、正则表达式实现快速古诗匹配 作者:©liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战。项目免费提供了 30 ...

    Carl 评论0 收藏0
  • Day06 - Fetch、filter、正则表达式实现快速古诗匹配

    摘要:正则表达式实现快速古诗匹配作者简介是推出的一个天挑战。数据匹配操作使用基础参考文档项目源码分析正则找出匹配的诗句替换高亮的标签构造值会返回带搜索关键字的新数组。执行对大小写不敏感的匹配。 Day06 - Fetch、filter、正则表达式实现快速古诗匹配 作者:©liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战。项目免费提供了 30 ...

    StonePanda 评论0 收藏0
  • Day12 - 键盘输入序列的验证指南

    摘要:监测字符串变化声明字符串输入变化的获取对象更新显示数据事件监听通过监听键盘输入的变化,当键盘弹起时,调用函数。 (Node+Vue+微信公众号开发)企业级产品全栈开发速成周末班首期班(10.28号正式开班,欢迎抢座) 作者:©liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 3...

    joy968 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<