正則從零到簡樸剖析html標籤

關於正則之前一直是一個”百度程序員”, 或許凌駕一半以至更多的程序員也是, 那末此次來進修一下正則表達式.

事出有因

這部份引見一下需求的由來, 與主要內容無關.

事情上有了如許的需求:

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]婚配以下哪組字符?

  1. 123c
  2. 123d
  3. 123e
  4. 123f

選了1的朋儕你已曉得正則是什麼了. 123[abc]就是正則, 代表婚配內容為: 前三個字符分別為123, 第四個字符是abc中的一個, 這個正則碰到123a, 123b, 123c都能夠婚配勝利, 其他任何都婚配失利.

正則語法

百度了正則表達式看到的東西都用了許多術語, 讓人有點犯渾. 我經由進修把正則籠統為兩個部份: 內容潤飾.

看到一長串正則以為稀里嘩啦, 然則內里的每一個標記一建都屬於內容或是潤飾.

內容

內容的情勢有3種:

  1. 直接婚配: 舉例就是適才的123[abc]中的123, 這類婚配須要完整符合才婚配, 123就唯一婚配123.
  2. 局限婚配: 用中括號示意, 也就是適才例子中的[abc]. 這類狀況也就是三選一. 恣意婚配abc, 而不是婚配abc. 另有兩種情勢: 加-來示意局限, 比方[a-z]; 示意消除局限內的^, 比方[^abc]
  3. 婚配並挑選緩存到子婚配: 用圓括號示意, 圓括號中的內容語法是”直接婚配”但會被記入緩存作為子婚配, 我記得我最初打仗正則就是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就可以夠解決題目.

潤飾

我把潤飾部份分為數目潤飾和邊境潤飾.

  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.

  2. 邊境潤飾: ^示意字符串的頭, $示意字符串的尾, \b示意字母與空格間的位置. 用來給婚配定位, 詳細用法在現實中操縱就會有詳細感受了.

    別的, 正則有一種婚配形式是m, 多行婚配形式, 這個狀況里^$也能婚配每一行的開首和末端.

javascript相干函數

起首明白正則是”正則表達式”與”字符串”發作的婚配關聯.

js有個對象是RegExp, 運用要領是new RegExp(pattern, mode), 或許是用/包裹的字面量: /pattern/mode.

這裏發明提到了mode婚配形式, 一共三種:

  1. g: 全局婚配, 婚配到一次不會住手, /a/婚配aaa, 假如沒有g效果是一個a, 有g效果是3個a.
  2. i: 疏忽大小寫.
  3. m: 多行形式. 和之前提到的\b有聯動.

三個形式不互斥, 疊加的, 也就是能夠new RegExp(patter, 'gin').

正則的要領有:

  1. .test(): 返回是不是婚配勝利, true或許false.
  2. .exec(): 失利返回null, 勝利返回數組, 位置0是婚配內容, 以後是圓括號婚配的內容. 要注意的是exec是疏忽’g’形式的.

字符串的要領:

  1. .replace(pattern, replacement): replacement能夠字符串或要領, 要領的話參數是婚配到的內容.
  2. .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’的形式, 婚配的歷程是如許的:

  1. 舉行第一次婚配, 婚配勝利后把婚配部份消除待婚配內容.
  2. 舉行第二次婚配, 婚配勝利后把婚配部份消除待婚配內容.
  3. 直到婚配失利, 返回一切效果.

舉個例子:

'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>
        )
    }
}

原文地點

    原文作者:小胖
    原文地址: https://segmentfault.com/a/1190000014658277
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞