對於正則以前一直是一個"百度程序員", 也許超過一半甚至更多的程序員也是, 那麼此次來學習一下正則表達式.javascript
這部分介紹一下需求的由來, 與主要內容無關.html
工做上有了這樣的需求: java
web端從ueditor來的數據格式是html, 也就是<p>文章內容</p>
, 並夾雜着諸多標籤和嵌套. react
然而正在開發的是react-native項目, rn的標籤和html徹底不兼容, 是View, Text, Image
等. git
那麼把從web存入的數據讀取到rn上就出了大麻煩, 甚至有些地方要進行跳轉, 有些圖片要顯示, 那麼怎麼辦呢.程序員
經過百度"js如何驗證郵箱"已經沒法知足需求了, 只能學一下了.web
萬事有目標, 咱們要把一下內容轉換成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>
轉換結果是:express
<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> ...
本文會從零基礎出發達成這個目標.react-native
講解順序: 正則介紹 => 正則語法系統 => 簡單的例子講解 => 嘗試實現目標以及碰到的問題 => 實現目標
初中時候學的通配符, 用?
表明一個任意字符, 用*
表明任意個任意字符來進行搜索, 正則也是如此. 好比:
123[abc]
匹配如下哪組字符?
選了1的朋友你已經知道正則是什麼了. 123[abc]
就是正則, 表明匹配內容爲: 前三個字符分別爲123, 第四個字符是abc中的一個, 這個正則遇到123a
, 123b
, 123c
均可以匹配成功, 其餘任何都匹配失敗.
百度了正則表達式看到的東西都用了不少術語, 讓人有點犯渾. 我通過學習把正則抽象爲兩個部分: 內容
和修飾
.
看到一長串正則以爲稀里嘩啦, 可是裏面的每一個符號必定都屬於內容
或是修飾
.
內容的形式有3種:
123[abc]
中的123
, 這種匹配須要徹底吻合才能匹配, 123
就惟一匹配123
.[abc]
. 這種狀況也就是三選一. 任意匹配a
或b
或c
, 而不是匹配abc
. 還有兩種形式: 加-
來表示範圍, 好比[a-z]
; 表示排除範圍內的^
, 好比[^abc]
$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
, 多行匹配模式, 這個狀況裏^
和$
也能匹配每一行的開頭和結尾.
首先明確正則是"正則表達式"與"字符串"發生的匹配關係.
js有個對象是RegExp
, 使用方法是new RegExp(pattern, mode)
, 或者是用/
包裹的字面量: /pattern/mode
.
這裏發現提到了mode
匹配模式, 一共三種:
/a/
匹配aaa
, 若是沒有g結果是一個a
, 有g結果是3個a
.\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參數是能夠接受字符串的.
衆所周知, 在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> ) } }