九、前后查找
- 9.1、向前查找
向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个子表达式,而且从格式上看也确实如此。从语法上看,一个向前查找模式其实就是一个以?=
开头的子表达式,需要匹配的文本跟在=
后面
- 在向前查找里,被匹配的文本包含在最终返回的匹配结果里,被称为“不消费”。反之为消费。xiang
- 注意:向前查找和向后查找匹配本身其实是有返回结果的,只是这个结果的字节长度永远是0而已。因此,前后查找操作有时也被称为零宽度匹配操作。
其实,任何一个子表达式都可以转换为一个向前查找表达式,只要给它加上一个?=
前缀即可。在同一个搜索模式里可以使用多个向前查找表达式,它们可以出现在模式里的任意位置(而不仅仅是出现在整个模式的开头,就像你们在上面看到的那样。)
- 9.2、向后查找
?=
被称为向前查找的操作符,许多正则表达式还支持向后查找,也就是查找出现在被匹配文本之前的字符(但不消费它也就是不包含其后的文本),向后查找操作符是?<=
。?<=
与?=
使用方法大同小异,它必须在一个子表达式里,而且后跟要匹配的文本。
let str: String = "ABC01: $23.45\nHGG42: $5.31\nCFMX1: $899.00\nXTC99: $69.96\nTotal items founf : 4" label.attributedText = textRegex(pattern: "(?<=\\$)[0-9.]+" ,str: str, font: 22)
- 我们想要的是以
$
为基础匹配后面的消费金额,显然最后一种匹配最好。
提示:向前查找模式长度是可变的,它们可以包含.和+之类的元字符,所以非常灵活,而向后查找的模式只能是固定长度,这是一条几乎所有的正则表达式实现都遵循的限制。
- 9.3.把向前查找和向后查找结合起来
let str: String = "<HEAD>\n<TITLE>Ben Forta's Homepage</TITLE>\n<HEAD>" label.attributedText = textRegex(pattern: "(?<=<[tT][iI][tT][lL][eE]>).*(?=</[tT][iI][tT][lL][eE]>)" ,str: str, font: 18)
- 说明:眼睛尖锐的大家可能已经看到了,上面我读
<
进行了转义,再强调一下,向前查找?=
(不消费)和向后查找?<=
(不消费)都是针对的子表达式来操作的。 - 9.4、对前后查找取非
一般来说,凡是支持正向前查找的正则表达式也是支持负向前查找的,反之,凡是只是正向后查找的,也是支持负向后查找的。
- 上面大家可能看到了
\b
,单词的边界,去\b
会有问题的,有兴趣的可以自己验证下。
- 9.5、总结
有了前后查找,我们就可以对最终的匹配结果包含哪些内容做出更精确的控制。前后查找操作使我们可以利用子表达式来指定文本操作的发生位置,并收到只匹配不消费的效果。正向前查找要用(?=)
来定义,负向前查找要用(?!)
来定义。有些正则表达式实现还支持正向后查找(响应的操作符是(?<=)
)和负向后查找(相应的操作符是(?!<)
)。
十、嵌入条件
- 10.1、为什么要嵌入条件?(看下面的例子)
(123)456-7890
和123-456-7890
都是可以接收的北美电话号码格式,而1234567890
、(123)-456-789
和(123-456-7890)
虽然都包含着数字正确的字符,但是格式不对,看下面的匹配
let str: String = "123-456-7890\n(123)456-7890\n(123)-456-7890\n(123-456-7890\n1234567890\n123 456 7890" label.attributedText = textRegex(pattern: "\\(?\\d{3}\\)?-?\\d{3}-\\d{4}" ,str: str, font: 24)
- 分析:
\\(?
匹配的是一个可选的左括号, 请注意,这里必须对(
进行转义;\d{3}
匹配前三位数字;\)?
匹配的是一个可选的右括号;-?
匹配的是一个可选的连字符
上面的匹配\\)?-
如果换做[\\)-]?
这样的话)
与-
只能出现一个就能排除第三行,但是无法排除第4行,正确的匹配应该是:只在电话号码里有一个左括号(的时候才去匹配)。更准确地说,应该是如果电话号码里有一个左括号(
,我们的模式必须去匹配)
;如果不是这样,它就必须去匹配-
,总之这种匹配需要条件。 - 10.2、正则表达式里的条件正则表达式里的条件要用
?
来定义。事实上,你们已经见过几种非常特定的条件了。
?
匹配前一个字符或者表达式,如果它存在的话(可有可无)。?=
和?<=
匹配前面或后面的文本,如果它存在的话。嵌入条件语法也是用了?
,这并没有什么让人感到吃惊的地方,因为嵌入条件不外乎以下两种情况。
- 根据前一个回溯引用来进行条件处理。
- 根据前后查找来进行条件处理。
- 10.2.1、回溯引用条件
回溯引用条件只在一个前面的表达式搜索去的成功的情况下才允许使用一个表达式。看下面的例子,我们需要把一段文本里<IMG>
标签全部都找出来;不仅仅如此,如果某个<IMG>
标签是一个链接(被括在<A>和</A>标签之间)的话,你还要把整个链接标签匹配出来。
用来定义这种条件的语法是(?(backrefence)truepregex)
,其中?
表明这是一个条件,括号里的backrefence
是一个回溯引用,truepregex
是一个只在backrefence
存在时才会被执行的表达式。
let str: String = "<!-- Nav bar -->\n<TD>\n<A HREF=/home><IMG SRC=/imges/home.gif></A>\n<IMG SRC=/images/spacer.gif>\n<A HREF=/search><IMG SRC=/imges/home.gif></A>\n</A>\n<IMG SRC=/images/spacer.gif>\n<A HREF=/help><IMG SRC=/imges/home.gif>\n</TD>" label.attributedText = textRegex(pattern: "(<[Aa]\\s+[^>]+>\\s*)?<[Ii][Mm][Gg]\\s+[^>]+>(?(1)\\s*</[Aa]>)" ,str: str, font: 24)
- 分析:这个模式不解释是不容易看明白的。
(<[Aa]\\s+[^>]+>\\s*)?
将匹配一个<A>
或<a>
(以及<A>或<a>标签的任意属性),这个标签可有可无(因为这个子表达式的最后有一个?
)接下来,<[Ii][Mm][Gg]\\s+[^>]+>
匹配一个<IMG>
(大小写均可)及其任意属性。(?(1)\\s*</[Aa]>)
是一个回溯引用条件,?(1)
的含义是:如果第一个回溯引用条件(局具体到上面就是<A>标签)存在,则使用\s*</[Aa]>
继续进行匹配(换句话说,只有当前面的<A>标签匹配成功,才继续进行后面的匹配)。如果(1)存在,\s*</[Aa]>
将匹配结束标签</A>
之后出现的任意空白字符。
注意:(1)
检查第一个回溯引用是否存在,在条件里,回溯引用编号(本例中的1)不需要被转义。因此,?(1)
是正确的,?(\\1)
不正确(但是也能用)。
我们刚才使用的模式只在给定的条件得到满足时候才执行一个表达式。条件还可以有否表达式,否则表达式只在给定的回溯引用不存在(也就是条件没有得到满足)时才会执行。用来定义这种条件的语法是(?(backrefence)true-regex|false-regex)
,这个语法接受一个条件和两个将分别在这个条件得到满足和没有得到满足时执行的表达式。(下面就可以解决上面10.1电话号码的问题)
let str: String = "123-456-7890\n(123)456-7890\n(123)-456-7890\n(123-456-7890\n1234567890\n123 456 7890" label.attributedText = textRegex(pattern: "(\\()?\\d{3}(?(1)\\)|-)\\d{3}-\\d{4}" ,str: str, font: 24)
- 分析:从结果上看,这个模式解决了问题,但它是如何解决问题呢?和前面一样,
(\\()?
也匹配一个可选的左括号,但我们这次把它用括号括起来得到一个子表达式。随后的\d{3}
匹配一位数字的区号。(?(1)\\)|-)
是一个回溯引用条件,它将根据条件是否得到满足而去匹配)
或-
:如果(1)
存在(也就是找到了一个左括号),\\)
必须被匹配;否则,-
必须被匹配。这样一来,只有配对出现的括号才会被匹配;如果没有使用括号或括号不配对,电话号码中的区域和其余数字之间的-
必须被匹配。
- 10.2.2、前后查找条件前后查找条件只在一个向前查找或向后查找操作取得成功的情况下才允许一个表达式被使用。定义一个前后查找条件的语法与定义一个回溯引用的条件的语法大同小异,只需要把回溯引用(括号里的回溯引用标号)替换为一个完整的前后查找表达式就行了。
- 例子一美国邮政编码匹配
let str: String = "11111\n22222\n33333-\n44444-4444" label.attributedText = textRegex(pattern: "\\d{5}(-\\d{4})?" ,str: str, font: 24)
- 上面例子的更正:
let str: String = "11111\n22222\n33333-\n44444-4444" label.attributedText = textRegex(pattern: "\\d{5}(?(?=-)-\\d{4})" ,str: str, font: 24)
- 分析:
\d{5}
匹配前五位数字,接下来是一个(?(?=-)-\\d{4})
形式的向前查找条件。这个条件使用了?=-
来匹配(但不消费)一个连字符,如果条件得到满足(那个连字符存在),-\\d{4}
将匹配那个连字符和随后的4位数字。这样一来,33333-
将被排除在最终的匹配结果之外(它有一个连字符,所以满足给定的条件,但那个连字符后面没有必须出现在那里的4位数字)。平时工作中嵌入查找的模式相当少见,这是因为我们往往可以用最简单的办法来达到同样的目的。
- 10.3、总结
在正则表达式里面可以嵌入条件,只有相当条件得到(或者没有得到)满足时,相应的表达式才会被执行。这种条件可以是一个回溯引用(含义是检查该回溯引用是否存在),也可以是一个前后查找的操作。
十一、元字符表
- 11.1、基本的元字符
.
匹配任意单个字符|
逻辑或操作符[]
匹配字符集合中的一个字符[^]
对集合求非-
定义一个区间,如[a-z]
\
对下一个字符转义
- 11.2、数量元字符
*
匹配前一个字符(子表达式)零次或者多次*?
是*
的懒惰型版本+
匹配前一个字符(子表达式)的一次或多次重复+?
是+
的懒惰型版本?
前一个字符可以可无,也就是最多匹配一次{n}
匹配前一个字符或者表达式n次{m,n}
匹配前一个字符或表达式最少m次,最多n次{m,}
匹配前一个字符至少m次{m,}?
{m,}的懒惰型版本
- 11.3、位置元字符
^
匹配字符串的开头\A
匹配字符串的开头$
匹配字符串的结束\z
匹配字符串的结束\<
单词匹配的开始\>
单词匹配的结束\b
单词匹配的边界(开头和结束)\B
是\b
的反义
- 11.4、特殊字符元字符
[\b]
退格字符\c
匹配一个控制字符\d
匹配任意数字字符\D
匹配任意非数字字符\f
换页符\n
换行符\r
回车符\s
匹配一个空白字符\S
匹配一个非空白字符\t
制表符(Tab字符)\v
垂直制表符\w
匹配任意数字、字母、下划线以及中文汉字\W
匹配任意非数字、非字母、非下划线以及非中文汉字\x
匹配一个十六进制数字\0
匹配一个八进制数字
- 11.5、回溯引用和前后查找
()
定义一个子表达式\1
匹配第一个子表达式\2
代表匹配第二个子表达式?=
向前查找?<=
向后查找?!
负向前查找?<!
负向后查找?()
条件 (if then)?()|
条件(if then else)
- 11.6、大小写转换
\E
结束\L或\U转换\l
把下一个字符转换为小写\L
把后面的字符转换为小写直到遇见\E为止\u
把下一个字符转换为大写\U
把后面的字符转换为大写直到遇见\E为止- 11.7、匹配模式
(?m)
分行匹配模式
十二、多规则匹配
- 主要阐述多规则匹配的用法
let pattern = pattern1 + "|" + pattern2 + "|" + pattern3
看下面
let str = "@joanking:【周杰伦的歌曲】#大眼睛#小猫咪这么尖叫[偷笑]、@老北: 蝉叫、狼这么尖叫[吃惊]、@乐不思蜀:达芬奇#烧饼#妙的笑到最后[挖鼻屎]!~ http://www.baidu.com" do{ // 1.创建规则 let pattern1 = "\\[.*?\\]" let pattern2 = "@.*?:" let pattern3 = "#.*?#" // 多个规则之间使用 | 符号连接 let pattern = pattern1 + "|" + pattern2 + "|" + pattern3 // 2.创建正则表达式对象 let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) // 3.开始匹配 let res = regex.matches(in: str, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, str.count)) // 4取出结果 for checkingRes in res { // print(checkingRes.range) print((str as NSString).substring(with: checkingRes.range)) } }catch { print(error) }
十三、正则练习的demo(有心的人可以看看,可以一起讨论一下回溯引用方面的知识,自己理解的不是很好)
- 测试用的JKRegexdemo