正则老是忘记啊,不总结不行啊!这里我不做特别全面的介绍,但是会介绍比较有用的点。围绕一个例子来讲解!然后逐步优化。跟着例子尝试,你一定能看懂!
- string xml_str = @"""1.0"" encoding=""utf-8""?>
- <soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
- xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
- xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
- <soap:Body>
- <UniRequest xmlns=""http://tempuri.org/"">
- <rb>
- <account>adminaccount>
- <optype>0optype>
- <param>{""moid"":""WO09280001"",""partID"":""3F258-A-MPWN"",""ppid"":""9908108383X2009280100000301
- "",""testStation"":""T00001""}param>
- <password>123password>
- <sericeName>GET_PROCESS_STATUSsericeName>
- rb>
- UniRequest>
- soap:Body>
- soap:Envelope>
- ";
我想把标签中的字符串匹配出来!正则表达式重在匹配二字,后面多多体会。
给出程序:
string param1 = Regex.Match(xml_str, "<param>.*").Value;
看看能匹配得到什么?
<param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
.* 表示,我匹配的字符串是以开头的,
其中的“.” 表示可以匹配任何单个字符,但是不包括换行
其中的“*” 表示 匹配前面的子表达式零次或多次。(要匹配 * 字符,请使用 \*)。
所以 .* 的组合,就能匹配任意多个任意字符!但是如果遇到换行,匹配就打住了。而在这个例子中换行了,所以我们需要多行匹配!
重复这个观念,非常重要,贯穿整个正则的基础逻辑,这里解析一下:
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
点号本身只能匹配一个字符,但是加上*号之后,就能匹配无数个字符!
"重复" 他就像一个驱动器,驱动着匹配继续进行!
“问号”在正则中,扮演连个角色:
1 匹配前面的子表达式零次或一次,
2 指明一个非贪婪限定符。(懒惰限定符 中介绍)
ps(如要匹配 ? 字符,请使用 \?。)
很简单,将 . 替换为 (.|\n) ,现在表达式变成了:
(.|\n)*
string param1 = Regex.Match(xml_str, "(.|\n)*").Value;
看看能匹配得到什么?
- <param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
- ","testStation":"T00001"}param>
- <password>123password>
- <sericeName>GET_PROCESS_STATUSsericeName>
- rb>
- UniRequest>
- soap:Body>
- soap:Envelope>
(.|\n) 其实就是说,我要匹配任意字符,包括换行!这样就能一直匹配到字符串结束了!那这也不是我想要的
(.|\n) 有时会写成 (?:.|\n),表示不分组,因为带了括号就表示分组了,?:表示取消分组,这样能提供匹配效率。这个暂时不管啦!
(.|\n)*
这样很明显,我能就能得到:
- <param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
- ","testStation":"T00001"}param>
但是,我还是不满足, 这对标签也不是我想要的!我只要中间的内容。
看下这张图里的红框部分的解释,它能满足我的需求。
于是,正则表达式最终进化为:
(?<=)(.|\n)*(?=)
string param1 = Regex.Match(xml_str, "(?<=)(.|\n)*(?=)").Value;
我们得到了标签里的json:
- {"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
- ","testStation":"T00001"}
新的需求,我需要取得json中,ppid的值,思路不变,以"ppid" 开头,以引号结束。试试:
string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n)*\"").Value;
注意:引号本身需要转义。
我们得到的匹配内容是:
- "ppid":"9908108383X2009280100000301
- ","testStation":"T00001"
我的说的是以引号结束,这里匹配到了字符串中最后一个引号,这就是贪婪。
匹配到第一个引号时候,此时并不会停止匹配,因为引号也是任意的字符!符合匹配规则。所以重复过程继续进行,后面还有引号所以不会停止!直到最后一个引号才会停止!
string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n)*?\"").Value;
不贪婪,就是在 * 的后面加个 ?,让你扪心自问,是不是太贪了?于是得到:
"ppid":"
懒惰:就是在能使整个匹配成功的前提下使用最少的重复。
这里先说明,有问号的情况就是懒惰,懒惰就是保证重复的次数最少。
这里我们对比常规限定符和懒惰限定符,限定符多了一个问号!问号是懒惰的标识!
把 *? 替换成 {2,}?
string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n){2,}?\"").Value;
得到结果:
- "ppid":"9908108383X2009280100000301
- "
这里2的意思是,先重复两次再说!(重复就是驱动器)
从哪里开始重复?当然是"ppid"后面,重复的这两次相当于匹配了两个字符(冒号和引号)!
相当于把这两个字符跳过了!然后再执行懒惰!由于第二个数字为空({2,})
表示重复次数没有上限,就会一直往后重复!直到满足:
整个匹配成功的前提下使用最少的重复。
为了说明这点,我再举个例子!因为,这个次数问题以前都理解错误了:
如果,原始数据是:
"2113111311311111134444444444444"
我想匹配到第三个3结束也就是:
"21131113113"
哪一段代码可以实现要求?
A:
string param1 = Regex.Match("2113111311311111134444444444444", "2(.|\n){3,}?3").Value;
B:
string param1 = Regex.Match("2113111311311111134444444444444", "2(.|\n){7,}?3").Value;
如果选择对了,说明你理解对了这个重复次数的含义!
答案是B!
首先,我要匹配到第三个3,而不是最后一个,说明我需要用懒惰模式。
其次,因为是第三个3,所以我得强行跳过前面2个3,那我就先强行重复7次,跳过
前面两个三好了!
替换,使用Regex.Replace,比Regex.Match 多一个替换参数。替换就是把匹配的内容,替换成新的内容:(这里的xxx就是要替换的内容,是个普通字符串)
- //替换
- string re = Regex.Replace(xml_str, "(?:.|\n)*", xxx);
这里计算双引号的个数:注意使用的是 Matches
Regex.Matches(param, "\"").Count;
Regex.IsMatch(xml_str, "\"ppid\"")
判断正则表达式是否能匹配到内容,返回一个bool变量。
如果掌握,这个小技巧,你将随心所欲的写出你想要的正则表达式。
我有一个json数字,我想通过正则,告诉key就能取出对应的value。
首先,按思路写出:
Match match = Regex.Match(xml_str, $"(?<=\"{key_name}).*?:");
这样的话,匹配到key然后匹配到冒号,重复就结束了,没有动力了!
得到:
":
所以需要 添加重复过程,添加动力(这是个技巧)!继续往后匹配:
Match match = Regex.Match(xml_str, $"(?<=\"{key_name}).*?:.*?\"");
得到:
":"
再继续添加重复:
Regex.Match(xml_str, $"(?<=\"ppid).*?:.*?\"(.|\n)*?\"")
得到:
- ":"9908108383X2009280100000301
- "
如果,再弄懂分组,你无敌了。
那上面的结果,虽然是匹配到了,但是多了一些东西,我只想要这串码值。此时就可以借助分组来获取,分组就是括号,把自己想要的部分括起来。我加个括号看看。
Regex.Match(xml_str, $"(?<=\"ppid).*?:.*?\"((.|\n)*?)\"");
此时就多了个分组:
然后通过:
match.Groups.Values.ElementAtOrDefault(1)?.Value;
就能拿到一号分组的值!
目前测试来看,第一个分组(Groups[0])就是整个匹配的结果,整个分组是无法取消的。
这种情况下,我有四个分组:
Regex.Match(xml_str, $"(?<=\"ppid)(.*?:.*?\")((.|\n)*?)\"");
通过 ?: 取消分组,提高效率:
Regex.Match(xml_str, $"(?<=\"ppid)(?:.*?:.*?\")((?:.|\n)*?)\"");
此时就只有两个分组了。
var param1 = Regex.Match(xml_str, $"(?<=\"ppid)(.*?:.*?\")(?(?:.|\n)*?)\"" , RegexOptions.ExplicitCapture);
?
RegexOptions.ExplicitCapture,表示仅仅捕获带有显示命名的分组,其他分组相当于,通过 ?: 取消分组。
还有其他的选项,如下:
这个就是增加可读性,你得留白会被忽略,而且可以添加注释。(但是,我测试时,这个选项影响了我的结果)
- namespace VisualMatching.Tool
- {
- public class RegexTool
- {
- //获取标签中的内容
- public static string GetP(string str, string p_name)
- {
- if (!str.Contains(p_name))
- {
- return null;
- }
- return Regex.Match(str, $"(?<=<{p_name}>)(.|\n)*(?={p_name}>)").Value;
- }
-
-
- //寻找字符串中的json
- public static string GetJson(string str)
- {
-
- return Regex.Match(str, "({)(.|\n)*(})").Value;
- }
-
- //json根据key获取value的值
- public static string GetJValue(string str_json, string key_name, bool isStringType = true, bool ignoreCase = false)
- {
- //如果是数字或者布尔类型,就不需要加双引号
- //string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|\\d+)";
- //string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|\\d+\\.\\d+|\\d+)";
- string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|-?\\d+\\.\\d+|-?\\d+)";
-
-
-
-
- if (ignoreCase)//忽略大小写
- {
- Match match = Regex.Match(str_json, pattern, RegexOptions.IgnoreCase);
- return match.Value;
- }
- else
- {
- Match match = Regex.Match(str_json, pattern);
- return match.Value;
- }
-
-
- }
-
- //json替换key对应的value的值
- public static string ReplaceJValue(string str_json, string key_name, string replacement, bool isStringType = true, bool ignoreCase = false)
- {
- string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<={key_name}\\s*:\\s*\")((.|\n)*?(?=\"))";
- if (ignoreCase)
- {
- if (!str_json.ToLower().Contains(key_name.ToLower()))
- {
- return null;
- }
- return Regex.Replace(str_json, pattern, replacement, RegexOptions.IgnoreCase);
- }
- else
- {
- if (!str_json.Contains(key_name))
- {
- return null;
- }
- return Regex.Replace(str_json, pattern, replacement);
- }
-
-
-
- }
- }
- }
- Console.WriteLine("-----------------------------------------------------");
- Console.WriteLine($"{GetP(xml_str, "sericeName")}");
- Console.WriteLine($"{GetJValue(xml_str, "moid")}");
- Console.WriteLine($"{ReplaceJValue(xml_str, "ppid", "89898988989898989")}");
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
本来没准备写这么详细的,结果写着写着就情不自禁的刨根问底,之前不懂的地方也彻底搞懂了,以后正则就是小case!希望也能帮到你!
特别鸣谢这位作者,提供的全面资料。
2024年3月6日
优化了json相关的正则匹配,不经匹配字符串还能匹配数字(负数,小数),匹配bool类型!