五、重复匹配
- 5.1.匹配一个或者多个字符(
+
)
+
号匹配一个或多个字符(至少一个;不匹配0
个字符的情况),比如c
匹配c
本身,c+
将匹配一个或多个连续出现的c
。类似地,[0-9]
匹配任意单个数字,[0-9]+
将匹配一个或多个连续的数字。
let str: String = "The world for.text@text.text is a fine place, and worth fighting The world is a finefor.hext@tdxt.text place, and worth fighting for." label.attributedText = textRegex(pattern: "\\w+@\\w+\\.\\w+",str: str, font: 22)
- 如何把上面的
for
也匹配上
let str: String = "The world for.text@text.text is a fine place, and worth fighting The world is a finefor hext@tdxt.text place, and worth fighting for." label.attributedText = textRegex(pattern: "[\\w.]+@\\w+\\.\\w+",str: str, font: 22)
- 说明:上面没有对字符集合
[\w.]
里的.
字符进行转义。尽管如此,它还是把原始文本里的.
字符匹配出来了。一般来说,当在字符集合里使用的时候,像.
和+
这样的元字符将被解释为普通的字符,不需要被转义,但转义了也没有坏处。[\w.]
的使用效果与[\w.\.]
是一样的
- 5.2.匹配零个或多个字符
let str: String = "The world .text@text.com is a fine place, and worth fighting The world is a finefor hext@tdxt.text place, and worth fighting for." label.attributedText = textRegex(pattern: "\\w+[\\w.]*@[\\w.]+\\.\\w+",str: str, font: 22)
*
与+
的区别是:+
匹配一个或者多个字符(或字符集合),最少也要匹配一次;*
匹配零个或任意多个字符(或字符集合),可以没有匹配。*
是一个元字符。如果需要匹配*
本身,就必须对它进行转义。- 5.3.匹配零个或者一个字符(
?
)?
只能匹配一个字符(或字符集合)的零次或一次出现,最多不超过一次,如果需要在一段文本里匹配某个特定的字符(或字符集合)而该字符可能出现,也可能不出现,?
无疑是最佳的选择。
let str: String = "The URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instand." label.attributedText = textRegex(pattern: "https?://[\\w.]+",str: str, font: 22)
- 同样
?
也是一个元字符,匹配本身的话也需要进行转义。 - 5.4.匹配的重复的次数
- 5.4.1、为重复匹配次数设定一个精确的值
let str: String = "<BODY BGCOLOR=#336633> TEXT=#FFFFFF MARGINWIDTH=0 MARGINHEIGHT=0 TOPMARGIN=0" label.attributedText = textRegex(pattern: "[0-9A-Fa-f]{6}",str: str, font: 22)
{6}
:意味着要连续匹配6次[0-9A-Fa-f]
区间的值- 5.4.2、为重复匹配次数设定一个区间
{}
:语法不仅仅可以设置匹配的次数,还可以设置匹配的最小和最大次数
如: {1,3}代表连续匹配1~3次之间,当然之前的?其实就是等价于?
={0,1}
- 5.4.3、匹配“至少重复多少次”
{}
语法还可以设置至少重复的次数,也就是不用设置最大重复的次数,比如{2,}
let str: String = "1001: $496.80 1002: $1290.69 1003: $26.43 1004: $613.42 1007" label.attributedText = textRegex(pattern: "\\d+: \\$\\d{3,}\\.\\d{2}",str: str, font: 22)
- 注意:
{3,}
中的逗号千万别漏掉,否则就变成了精准匹配3位了。
- 5.5.防止过度匹配
?
只能匹配0个或者一个字符,{n}和{m,n}也有一个重复次数的上限,之前的例子都没有上限,下面举一个例子来说明
说明:贪婪型为什么会匹配and,那是因为贪婪型元字符在进行匹配的时候是多多益善而不是适可而止。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到第一个匹配为止。
- 解决办法是:在贪婪型元字符的后面加上
?
- 下面是常用的贪婪型元字符和他们的懒惰型版本
- 5.6.总结
正则表达式的真正威力体现在重复次数匹配方面。上面介绍了+
(匹配字符或字符集合的一次或多次重复出现),*
(匹配字符或字符集合的0次或多次重复出现),?
(匹配字符或字符集合的0次或一次出现)等几个元字符的用法。要想获得更精准的控制,你可以使用{}
语法来精确地设定一个重复次数或是重复次数的最小值和最大值。元字符分“贪婪型”和“懒惰型”两种,使用时要防止过度匹配时候可以使用懒惰型元字符来创造适合自己的表达式。
六、位置匹配
- 6.1.边界
下面的例子只是想配cat
这个单词,但是scattered
也被匹配到了,这不是我们想要的结果,后面再解决。
let str: String = "The cat scattered his food all over the room." label.attributedText = textRegex(pattern: "cat",str: str, font: 22)
- 6.2.单词边界
\b
可以用来限制单词的边界,也就是规定单词的开头和结尾。,如下所示
let str: String = "The cat scattered his food all over the room." label.attributedText = textRegex(pattern: "\\bcat\\b",str: str, font: 22)
- 说明:
\b
:匹配的是这样一个位置,这个位置位于一个能够用来构成单词的字符(字母,数字,下划线,也就是与\w
相匹配的字符)和一个不能用来构成单词的字符(也就是与\w相匹配的字符)之间。
注意:要想精准的匹配某一个单词必须在其前后都要加上\b单词\b
- 如果不想匹配一个单词的边界,那么就可以使用
\B
了,看下面的例子
let str: String = "Please enter the nine-digit id as it appears on your color - coded pass-key." label.attributedText = textRegex(pattern: "\\B-\\B",str: str, font: 22)
\\B-\\B
: 将匹配一个前后都不是单词边界的连字符。nine-digit
和pass-key
不能与之匹配,但color - coded
中的连字符可以与之匹配。
匹配单词的还有\\<单词\\>
,但是swift4.0不支持,本人已经验证过了。- 6.3、字符的边界
单词边界可以用来进行与单词有关位置的匹配(单词的开头,单词的结束,整个单词等)。字符串边界有着类似的用途,只不过是用来进行与字符串有关的位置匹配而已(字符串的开头,字符串的结束,整个字符串等)。用来定义字符串边界的元字符有两个:一个是用来定义字符串开头的^
,另一个是用来定义字符串结尾的$
. - 总结:正则表达式不仅仅可以用来匹配任意长度的文本块,还可以用来匹配出现在字符串中特定位置的文本。
\b
用来指定一个单词的边界(\B
刚好相反)。^
和$
用来指定字符串的边界(字符串的开头和字符串的结束)。如果与(?m)
配合使用,^
和$
还将匹配在一个换行处开头或结束的字符串(此时,换行符将被视为一个字符串分隔符)。
七、使用字表达式(元字符
和字符
是正则表达式
的基本构件
)
- 7.1、什么是子表达式?
let str: String = "Hello, my name is Ben Forta, and I am the author of books on SQL, ColdFusion, WAP, Windows 2000, and other subjects." label.attributedText = textRegex(pattern: " {2,}",str: str, font: 22)
:是HTML语言中的非换行空格字符。在这里使用模式 {2,}
的本意是希望它能把 连续两次或更多次的重复出现找出来,但它没能给出我们所预期的结果。为什么会这样?因为{2,}只作用于紧挨着它的前一个字符,那是一个分号。如此一来,这个模式只能匹配像 ;;;
这样的文本,但无法匹配  
。
- 7.2、子表达式
字表达式:是一个更大的表达式的一部分;把一个表达式划分为一系列表达式的目的是为了把那些字表达式当做一个独立元素来使用。字表达式必须用()
括起来。()
是元字符。如果要匹配()
的话需要进行对它转义。看下面的例子
let str: String = "Hello, my name is Ben Forta, and I am the author of books on SQL, ColdFusion, WAP, Windows 2000, and other subjects." label.attributedText = textRegex(pattern: "( ){2,}",str: str, font: 22)
解释:( )
是一个字表达式,它将被视为一个独立的元素,而紧跟着在它后面的{2,}
将做用于这个字表达式而不是仅仅作用于;
。
let str: String = "Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:" label.attributedText = textRegex(pattern: "(\\d{1,3}\\.){3}\\d{1,3}",str: str, font: 22)
上面的例子中 (\\d{1,3}\\.)
是一个子表达式,上面的匹配规则还可以写为"(\\d{1,3}\\.?){4}"
和"(\\d{1,3}\\.){3}(\\d{1,3})"
。
- 下面再写一个子表达式的匹配
let str: String = "Hope clouds 1987-09-02 observation." label.attributedText = textRegex(pattern: "(19|20)\\d{2}",str: str, font: 22)
- 解释:
|
是或的意思。 - 7.3、子表达式的嵌套(下面的例子没有正确匹配:待解决)
子表达式是允许嵌套的,实际上,子表达式允许多重嵌套,这种嵌套的层次在理论上没有限制,但在实际上应该合理的嵌套。下面的例子将全面的展示
let str: String = "Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:" label.attributedText = textRegex(pattern: "(((\\d{1,2})|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))\\.){3}(((25[0-5])|(2[0-4]\\d)|(1\\d{2})|\\d{1,2}))" ,str: str, font: 22)
- 注意:最后一个匹配的时候三位数要放到前面,否则的话匹配到两位数,
200
的最后一个0
就不能匹配上了 - 7.4、总结
子表达式的作用是把同一个表达式的各个相关部分组合在一起。子表达式必须用()
来定义。子表达式的常见用途包括: 对重复次数元字符的作用对象作出精确的设定和控制、对|
操作符条件作出准确额定义等,如有必要,子表达式还允许嵌套使用。
八、回溯引用,前后一致匹配
- 8.1、回溯引用的作用(看下面的例子)
let str: String = "<BODY><H1>Welcom to my Homepage</H1>\nContent is divided into two sections:<BR>\n <H2>ColodFusion</H2>\nInformation about Macromedia ColodFusion.\n <H2>Wrieless</H2>\n Information about Buletooth, 802.11, and more.\n <H2>This is not valid HTML</H3>\n</BODY>" label.attributedText = textRegex(pattern: "<[Hh][1-6]>.*?</[Hh][1-6]>" ,str: str, font: 22)
- 解释:上面匹配按照规则是正确的,但是按照语法最后一个匹配是不正确的,因为H2和H3是不对应的标签。为了解决这个问题,下面阐述:回溯引用
- 8.2、回溯引用匹配
- 8.2.1、寻找连着的两个相同单词
let str: String = "This is a block of of text,serveral words here are repeated,and and they should not not be." label.attributedText = textRegex(pattern: "[ ]+(\\w+)[ ]+\\1" ,str: str, font: 22)
- 解释:
[ ]+
匹配一个或多个空格,\w+
匹配一个或多个字母数字字符,[ ]+
匹配随后的空格,注意\w+
是括在括号里的,它是一个子表达式。这个子表达式不是用来进行重复匹配的,这里不涉及到重复匹配的问题。这个子表达式只是把整个模式的一部分单独划分出来以便在后面引用。这个模式的最后一部分是\1
;这是一个回溯引用。而它引用的正是前面划分出来的那个子表达式;当(\w+)
匹配到单子of
的时候,\1
也匹配单词of
;当(\w+)
匹配到单词and的时候,\1
也匹配到单词and
。
回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。\1
代表模式里的第一个子表达式,\2
代表模式里的第2
个子表达式,以此类推,上面的例子将匹配到同一个单词的连续两次重复出现。
- 8.2.2、解决8.1的最后一个不正确标签的匹配
let str: String = "<BODY><H1>Welcom to my Homepage</H1>\nContent is divided into two sections:<BR>\n <H2>ColodFusion</H2>\nInformation about Macromedia ColodFusion.\n <H2>Wrieless</H2>\n Information about Buletooth, 802.11, and more.\n <H2>This is not valid HTML</H3>\n</BODY>" label.attributedText = textRegex(pattern: "<[Hh]([1-6])>.*?</[Hh]\\1>" ,str: str, font: 22)
- 解释:
([1-6])
是一个集合的子表达式,\1
匹配前面匹配到的([1-6])
,所以说H2
只能匹配到H2
注意:不同的正则表达式在实现回溯引用的语法方面往往有着巨大的差异。
提示:回溯引用只能用来引用模式里的子表达式((用(和))括起来的正则表达式片段)。回溯引用通常从1开始计数(\1、\2等)。在许多实现里,第0个匹配(\0)可以用来代表整个正则表达式。其实子表达式是通过它们的相对位置来引用的:\1对应着第1个子表达式,\5对应着第5个子表达式等等。这种语法是有问题的:如果子表达式的相对位置发生了变化,整个模式也许就不能再完成原来的工作,删除或者添加子表达式的后果可能更为严重。解决办法:是运用命令捕获。 - 8.3、回溯引用在替换操作中的应用(没理解透)
- 8.4.大小写转换
- 替换使用:
$\\U$2\E$3
let str: String = "<BODY><H1>Welcom to my Homepage</H1>\nContent is divided into two sections:<BR>\n <H2>ColodFusion</H2>\nInformation about Macromedia ColodFusion." label.attributedText = textRegex(pattern: "(<[Hh]1>)(.*?)(</[Hh]1>)" ,str: str, font: 22)
- 分析:
"(<[Hh]1>)(.*?)(</[Hh]1>)"
是三个子表达式就是为了使用回溯引用,说一下替换部分:$1
包含着开始标签,U$2\E
把第二个子表达式(b标题文字)转换为大写,$3
包含着结束标签。
- 8.5、总结
子表达式用来定义字符或表达式的集合。除了可以用在重复匹配操作中意外,子表达式还可以在模式的内部被引用。这种引用被称为回溯引用。回溯引用的语法在不同的正则表达式实现里有很大的差异。回溯引用在文本匹配和文本替换操作里非常有用。