正则表达式基础:正则基础的 环视 Lookaround来源: 发布时间:星期一, 2009年9月7日 浏览:10次 评论:0
1 环视基础环视只进行子表达式匹配不占有匹配到内容不保存到最终匹配结果是零宽度环视匹配最终结果就是个位置环视作用相当于对所在位置加了个附加条件只有满足这个条件环视子表达式才能匹配成功 环视按照方向划分有顺序和逆序两种按照是否匹配有肯定和否定两种组合起来就有 4种环视顺序环视相当于在当前位置右侧附加个条件而逆序环视相当于在当前位置左侧附加个条件 表达式 介绍说明 (?<=Expression) 逆序肯定环视表示所在位置左侧能够匹配Expression (?<!Expression) 逆序否定环视表示所在位置左侧不能匹配Expression (?=Expression) 顺序肯定环视表示所在位置右侧能够匹配Expression (?!Expression) 顺序否定环视表示所在位置右侧不能匹配Expression 对于环视叫法有文档里叫预搜索有叫什么什么断言这里使用了更多人容易接受精通正则表达式中“环视”叫法其实叫什么无所谓只要知道是什么作用就是了就这么几个语法规则 还是很容易记 2 环视匹配原理环视是正则中个难点对于环视理解可以从应用和原理两个角度理解如果想理解得更清晰、深入些还是从原理角度理解好些正则匹配基本原理参考 NFA引擎匹配原理上面提到环视相当于对“所在位置”附加了个条件环视难点在于找到这个“位置”这点解决了环视也就没什么秘密可言了 顺序环视匹配过程对于顺序肯定环视(?=Expression)来说当子表达式Expression匹配成功时(?=Expression)匹配成功并报告(?=Expression)匹配当前位置成功对于顺序否定环视(?!Expression)来说当子表达式Expression匹配成功时(?!Expression)匹配失败;当子表达式Expression匹配失败时(?!Expression)匹配成功并报告(?!Expression)匹配当前位置成功; 顺序肯定环视例子已在NFA引擎匹配原理中讲解过了这里再讲解下顺序否定环视 源串:aa<p>one</p>bb<div>two</div>cc 正则表达式:<(?!/?p\b)[^>]+> 这个正则意义就是匹配除<p…>或</p>的外其余标签 匹配过程: 首先由“<”取得控制权从位置0开始匹配由于“<”匹配“a”失败在位置0处整个表达式匹配失败第次迭代匹配失败正则引擎向前传动由位置1处开始尝试第 2次迭代匹配 重复以上过程直到位置2“<”匹配“<”成功控制权交给“(?!/?p\b)”;“(?!/?p\b)”子表达式取得控制权后进行内部子表达式匹配首先由“/?”取得控制权尝试匹配“p”失败进行回溯不匹配控制权交给“p”;由“p”来尝试匹配“p”匹配成功控制权交给“\b”;由“\b”来尝试匹配位置4匹配成功此时子表达式匹配完成“/?p\b”匹配成功那么环视表达式“(?!/?p\b)”就匹配失败在位置2处整个表达式匹配失败新轮迭代匹配失败正则引擎向前传动由位置3处开始尝试下轮迭代匹配 在位置8处也会遇到轮“/?p\b”匹配“/p”成功而导致环视表达式“(?!/?p\b)”匹配失败从而导致整个表达式匹配失败过程 重复以上过程直到位置14“<”匹配“<”成功控制权交给“(?!/?p\b)”;“/?”尝试匹配“d”失败进行回溯不匹配控制权交给“p”;由“p”来尝试匹配“d”匹配失败已经没有备选状态可供回溯匹配失败此时子表达式匹配完成“/?p\b”匹配失败那么环视表达式“(?!/?p\b)”就匹配成功匹配结果是位置15然后控制权交给“[^>]+”;由“[^>]+”从位置15进行尝试匹配可以成功匹配到“div”控制权交给“>”;由“>”来匹配“>” 此时正则表达式匹配完成报告匹配成功匹配结果为“<div>”开始位置为14结束位置为19其中“<”匹配“<”“(?!/?p\b)”匹配位置15“[^>]+”匹配串“div”“>”匹配“>” 逆序环视基础对于逆序肯定环视(?<=Expression)来说当子表达式Expression匹配成功时(?<=Expression)匹配成功并报告(?<=Expression)匹配当前位置成功对于逆序否定环视(?<!Expression)来说当子表达式Expression匹配成功时(?<!Expression)匹配失败;当子表达式Expression匹配失败时(?<!Expression)匹配成功并报告(?<!Expression)匹配当前位置成功; 顺序环视相当于在当前位置右侧附加个条件所以它匹配尝试是从当前位置开始然后向右尝试匹配直到某位置使得匹配成功或失败为止而逆序环视特殊处在于它相当于在当前位置左侧附加个条件所以它不是在当前位置开始尝试匹配而是从当前位置左侧某位置开始匹配到当前位置为止报告匹配成功或失败 顺序环视尝试匹配起点是确定就是当前位置而匹配终点是不确定逆序环视匹配起点是不确定是当前位置左侧某位置而匹配终点是确定就是当前位置 所以顺序环视相对是简单而逆序环视相对是复杂这也就是为什么大多数语言和工具都提供了对顺序环视支持而只有少数语言提供了对逆序环视支持原因 JavaScript中只支持顺序环视不支持逆序环视 Java中虽然顺序环视和逆序环视都支持但是逆序环视只支持长度确定表达式逆序环视中量词只支持“?”不支持其它长度不定量词长度确定时引擎可以向左查找固定长度位置作为起点开始尝试匹配而如果长度不确定时就要从位置0开始尝试匹配处理复杂度是显而易见 目前只有.NET中支持不确定长度逆序环视 逆序环视匹配过程源串:<div>a test</div> 正则表达式:(?<=<div>)[^<]+(?=</div>) 这个正则意义就是匹配<div>和</div>标签的间内容而不包括<div>和</div>标签本身 匹配过程: 首先由“(?<=<div>)”取得控制权从位置0开始匹配由于位置0是起始位置左侧没有任何内容所以“<div>”必然匹配失败从而环视表达式“(?<=<div>)”匹配失败导致整个表达式在位置0处匹配失败第轮迭代匹配失败正则引擎向前传动由位置1处开始尝试第 2次迭代匹配 直到传动到位置5“(?<=<div>)”取得控制权向左查找5个位置由位置0开始匹配由“<div>”匹配“<div>”成功从而“(?<=<div>)”匹配成功匹配结果为位置5控制权交给“[^<]+”;“[^<]+”从位置5开始尝试匹配匹配“a test”成功控制权交给“(?=</div>)”;由“</div>”匹配“</div>”成功从而“(?=</div>)”匹配成功匹配结果为位置11 此时正则表达式匹配完成报告匹配成功匹配结果为“a test”开始位置为5结束位置为11其中“(?<=<div>)”匹配位置5“[^<]+”匹配“a test”“(?=</div>)”匹配位置11 逆序否定环视匹配过程和上述过程类似区别只是当Expression匹配失败时逆序否定表达式(?<!Expression)才匹配成功 到此环视匹配原理已基本讲解完环视也就没有什么秘密可言了所需要也只是多加练习而已 3 环视应用今天写累了暂时就给出个环视综合应用例子吧至于环视应用场景和窍门技巧后面再整理需求:数字格式化成用“,”货币格式 正则表达式:(?<=\d)(?<!\.\d*)(?=(?:\d{3})+(?:\.\d+|$)) 测试代码: double data = double { 0, 12, 123, 1234, 12345, 123456, 1234567, 123456789, 1234567890, 12.345, 123.456, 1234.56, 12345.6789, 123456.789, 1234567.89, 12345678.9 }; foreach (double d in data) { richTextBox2.Text "源串:" + d..PadRight(15) + "格式化:" + Regex.Replace(d., @"(?<=\d)(?<!\.\d*)(?=(?:\d{3})+(?:\.\d+|$))", ",") + "\n"; } 输出结果: 源串:0 格式化:0 源串:12 格式化:12 源串:123 格式化:123 源串:1234 格式化:1,234 源串:12345 格式化:12,345 源串:123456 格式化:123,456 源串:1234567 格式化:1,234,567 源串:123456789 格式化:123,456,789 源串:1234567890 格式化:1,234,567,890 源串:12.345 格式化:12.345 源串:123.456 格式化:123.456 源串:1234.56 格式化:1,234.56 源串:12345.6789 格式化:12,345.6789 源串:123456.789 格式化:123,456.789 源串:1234567.89 格式化:1,234,567.89 源串:12345678.9 格式化:12,345,678.9 实现分析: 首先根据需求可以确定是把些特定位置替换为“,”接下来就是分析并找到这些位置规律并抽象出来以正则表达式来表示 1、 这个位置左侧必须为数字 2、 这个位置右侧到出现“.”或结尾为止必须是数字且数字个数必须为3倍数 3、 这个位置左侧相隔任意个数字不能出现“.” 由以上 3条就可以完全确定这些位置只要实现以上 3条组合下正则表达式就可以了 根据分析最终匹配结果是个位置所以所有子表达式都要求是零宽度 1、 是对当前所在位置左侧附加条件所以要用到逆序环视要求必须出现所以是肯定符合这条件子表达式即为“(?<=\d)” 2、 是对当前所在位置右侧附加条件所以要用到顺序环视也是要求出现所以是肯定是数字且个数为3倍数即“(?=(?:\d{3})*)”到出现“.”或结尾为止即“(?=(?:\d{3})*(?:\.|$))” 3、 是对当前所在位置左侧附加条件所以要用到逆序环视要求不能出现所以是否定即“(?<!\.\d*)” 零宽度子表达式是非互斥最后匹配都是同个位置所以先后顺序是不影响最后匹配结果可以任意组合只是习惯上把逆序环视写在左侧顺序环视写在右侧 0
相关文章读者评论发表评论 |