關於正則之前一直是一個”百度程序員”, 或許凌駕一半以至更多的程序員也是, 那末此次來進修一下正則表達式.
事出有因
這部份引見一下需求的由來, 與主要內容無關.
事情上有了如許的需求:
web端從ueditor來的數據格式是html, 也就是<p>文章內容</p>
, 並夾雜着諸多標籤和嵌套.
然則正在開闢的是react-native項目, rn的標籤和html完整不兼容, 是View, Text, Image
等.
那末把從web存入的數據讀取到rn上就出了大貧苦, 以至有些處所要舉行跳轉, 有些圖片要顯現, 那末怎麼辦呢.
經由歷程百度”js怎樣考證郵箱”已沒法滿足需求了, 只能學一下了.
目的
萬事有目的, 我們要把一下內容轉換成rn的內容:
<p>嘿嘿嘿<img class="currentImg" id="currentImg" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524647151050&di=d488d0e93e72f13643d843066ef26836&imgtype=0&src=http%3A%2F%2Fimg02.imgcdc.com%2Fgame%2Fzh_cn%2Fpicnews%2F11128819%2F20160518%2F22678501_20160518152946632994008.jpg" width="201.33333333333" height="302" title="點擊檢察源網頁"/>拖過來的圖片哦<img class="currentImg" id="currentImg" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524647205890&di=5bf77e1d35941def729d2059d91deba8&imgtype=0&src=http%3A%2F%2Fnds.tgbus.com%2FUploadFiles%2F201208%2F20120817142937519.jpg" width="201.17405063291" height="302" title="點擊檢察源網頁"/><br/>a<img src=‘test1’ />b<img src=‘test2’ />c<img src=‘test3’ />d</p>
轉換效果是:
<Text>嘿嘿嘿</Text>
<Image source={{uri: 'https://timgsa.baxxxx'}}></Image>
<Text>拖過來的圖片哦</Text>
<Image source={{uri: 'https://txx'}}></Image>
<Text>a</Text>
<Image source={{uri: 'test1'}}></Image>
<Text>b</Text>
...
本文會從零基本動身殺青這個目的.
解說遞次: 正則引見 => 正則語法體系 => 簡樸的例子解說 => 嘗試實現目的以及碰到的題目 => 實現目的
什麼是正則
初中時刻學的通配符, 用?
代表一個恣意字符, 用*
代表恣意個恣意字符來舉行搜刮, 正則也是云云. 比方:
123[abc]
婚配以下哪組字符?
- 123c
- 123d
- 123e
- 123f
選了1的朋儕你已曉得正則是什麼了. 123[abc]
就是正則, 代表婚配內容為: 前三個字符分別為123, 第四個字符是abc中的一個, 這個正則碰到123a
, 123b
, 123c
都能夠婚配勝利, 其他任何都婚配失利.
正則語法
百度了正則表達式看到的東西都用了許多術語, 讓人有點犯渾. 我經由進修把正則籠統為兩個部份: 內容
和潤飾
.
看到一長串正則以為稀里嘩啦, 然則內里的每一個標記一建都屬於內容
或是潤飾
.
內容
內容的情勢有3種:
- 直接婚配: 舉例就是適才的
123[abc]
中的123
, 這類婚配須要完整符合才婚配,123
就唯一婚配123
. - 局限婚配: 用中括號示意, 也就是適才例子中的
[abc]
. 這類狀況也就是三選一. 恣意婚配a
或b
或c
, 而不是婚配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"
, 然則要堅持引號一致. ('|")123\1
就可以夠解決題目.
潤飾
我把潤飾部份分為數目潤飾和邊境潤飾.
- 數目潤飾: 標記為{}, 想到了谷歌:
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
. - 邊境潤飾:
^
示意字符串的頭,$
示意字符串的尾,\b
示意字母與空格間的位置. 用來給婚配定位, 詳細用法在現實中操縱就會有詳細感受了.別的, 正則有一種婚配形式是
m
, 多行婚配形式, 這個狀況里^
和$
也能婚配每一行的開首和末端.
javascript相干函數
起首明白正則是”正則表達式”與”字符串”發作的婚配關聯.
js有個對象是RegExp
, 運用要領是new RegExp(pattern, mode)
, 或許是用/
包裹的字面量: /pattern/mode
.
這裏發明提到了mode
婚配形式, 一共三種:
- g: 全局婚配, 婚配到一次不會住手,
/a/
婚配aaa
, 假如沒有g效果是一個a
, 有g效果是3個a
. - i: 疏忽大小寫.
- m: 多行形式. 和之前提到的
\b
有聯動.
三個形式不互斥, 疊加的, 也就是能夠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= /^((0\d{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[^>]*?>`,
_p: `<\/p>`,
span: `<span[^>]*?>`,
_span: `<\/span>`,
br: `<br\/>`,
a: `<a[^>]*?href=(\'|")([^>]+?)\\1[^>]*?>([^<]+?)<\\/a>`, // $1 是標點標記用來處置懲罰婚配 $2 href的帶引號的內容 $3 文件名(a標籤的innerText),
img: `<img[^>]*?src=('|")([^>]+?)\\1[^>]*?\\/>`, // $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 (
<Text>html attr not passed to component 'parseHtml'</Text>
)
}
matches = str.replace(tobeRemoved, '').match(parseToAst)
return (
<View>
{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 (
<Text style={this.props.style} key={index}>{block}</Text>
)
}
if (key === 'a') {
if (res[2].includes('files')) { // 推斷附件
if (/[jpg|png|jpeg]/i.test(res[3])) { // 推斷圖片
let imgId = res[2].match(/\d+/)[0]
return (
<TouchableOpacity key={index} onPress={() => {this.props.magnifyImg && this.props.magnifyImg(ENVS.production.api_base_url + '/files/' + imgId)}} >
<Image style={{width: 100, height: 100, margin: 10}} source={{uri: ENVS.production.api_base_url + '/files/' + imgId}}></Image>
</TouchableOpacity>
)
}
}
}
if (key === 'img') {
return (
<TouchableOpacity key={index} onPress={() => {this.props.magnifyImg && this.props.magnifyImg(res[2])}}>
<Image style={{width: 100, height: 100, margin: 10}} source={{uri: res[2]}}></Image>
</TouchableOpacity>
)
}
}
}
})}
</View>
)
}
}