53、.NET与PHP正则表达式深度解析

.NET与PHP正则表达式深度解析

1 .NET高级特性

在.NET编程中,有一些有趣的特性为正则表达式的使用带来了更多的便利和强大功能。下面将详细介绍使用正则表达式程序集构建正则表达式库、匹配嵌套结构以及捕获对象的相关内容。

1.1 正则表达式程序集

.NET允许将正则表达式对象封装到程序集中,这对于创建正则表达式库非常有用。以下是构建和使用正则表达式程序集的详细步骤:
1. 构建正则表达式程序集 :通过执行特定的代码,可以创建一个包含预定义正则表达式构造函数的程序集(DLL)。以下是一个完整的示例代码:

Option Explicit On
Option Strict On
Imports System.Text.RegularExpressions
Imports System.Reflection

Module BuildMyLibrary
    Sub Main()
        Dim RCInfo() as RegexCompilationInfo = {
            New RegexCompilationInfo(
                "^Subject:\s+(.+)", RegexOptions.IgnoreCase,
                "Subject", "jfriedl.Mail", true
            ),
            New RegexCompilationInfo(
                "^From:\s+(.+)", RegexOptions.IgnoreCase,
                "From", "jfriedl.Mail", true
            ),
            New RegexCompilationInfo(
                "\G(?:^;,)" &
                "(?: " &
                "(?# Either a double-quoted field... )" &
                "\"" &
                "(?# field’s opening quote )" &
                "(?<QuotedField> (?> [^""]+ ; """")+ ) " &
                "\"" &
                "(?# field’s closing quote )" &
                " (?# ...or... )" &
                " ;" &
                "(?# ...some non-quote/non-comma text... )" &
                "(?<UnquotedField> [^"",]+ )" &
                " )",
                RegexOptions.IgnorePatternWhitespace,
                "GetField", "jfriedl.CSV", true
            )
        }
        Dim AN as AssemblyName = new AssemblyName()
        AN.Name = "JfriedlsRegexLibrary"
        AN.Version = New Version("1.0.0.0")
        Regex.CompileToAssembly(RCInfo, AN)
    End Sub
End Module

当执行上述代码时,会在项目的 bin 目录下创建 JfriedlsRegexLibrary.DLL 文件。
2. 使用正则表达式程序集 :在另一个项目中使用该程序集,需要先通过Visual Studio .NET的 Project > Add Reference 对话框将其添加为引用。然后,可以通过以下几种方式使用程序集中的类:
- 导入命名空间

Imports jfriedl
Dim FieldRegex as CSV.GetField = New CSV.GetField
Dim FieldMatch as Match = FieldRegex.Match(Line)
While FieldMatch.Success
    Dim Field as String
    If FieldMatch.Groups(1).Success
        Field = FieldMatch.Groups("QuotedField").Value
        Field = Regex.Replace(Field, """ "" ", """")
    Else
        Field = FieldMatch.Groups("UnquotedField").Value
    End If
    Console.WriteLine("[" & Field & "]")
    FieldMatch = FieldMatch.NextMatch
End While
- **直接使用**:
Dim FieldRegex as jfriedl.CSV.GetField = New jfriedl.CSV.GetField

这两种方式主要是风格上的差异,导入命名空间的方式更简洁,而直接使用的方式能更清晰地表明对象的来源。

1.2 匹配嵌套结构

Microsoft引入了一种有趣的创新方法来匹配平衡结构,这在历史上是正则表达式难以实现的。下面通过一个示例来详细说明其工作原理:

Dim R As Regex = New Regex(" \( " &
    "(?> " &
    "[^()]+ " &
    ";" &
    "\( (?<DEPTH>) " &
    ";" &
    "\) (?<-DEPTH>) " &
    ")+ " &
    "(?(DEPTH)(?!)) " &
    "\)",
    RegexOptions.IgnorePatternWhitespace
)

这个正则表达式可以匹配第一个正确配对的嵌套括号集,例如在 before (nope (yes (here) okay) after 中,会匹配到 (nope (yes (here) okay)) 。其工作原理如下:
1. 每次匹配到 ( 时, (?<DEPTH>) 会将正则表达式对当前括号嵌套深度的计数加1。
2. 每次匹配到 ) 时, (?<-DEPTH>) 会将该深度计数减1。
3. (?(DEPTH)(?!)) 确保在允许最终的 ) 匹配之前,深度计数为零。

这个过程依赖于引擎的回溯栈来跟踪成功匹配的分组。 (?<DEPTH>) () 的命名捕获版本,总是匹配成功,其成功信息会保留在栈中,用于计数左括号。而 (?<-DEPTH>) 会从栈中移除最近的“成功的 DEPTH ”标记,如果栈中没有这样的标记, (?<-DEPTH>) 本身会匹配失败,从而防止正则表达式过度匹配额外的右括号。

1.3 捕获对象

在.NET的对象模型中,捕获对象(Capture object)是一个额外的组件。捕获对象与组对象(Group object)非常相似,都表示在一组捕获括号内匹配的文本,并且都有 Value (匹配的文本)、 Length (匹配文本的长度)和 Index (匹配在目标字符串中的起始位置)等方法。

主要区别在于,每个组对象包含一个捕获对象的集合,这些捕获对象表示组在匹配过程中的所有中间匹配结果以及最终匹配的文本。以下是一个示例:

Dim M as Match = Regex.Match("abcdefghijk", "^(..)+")

这个正则表达式匹配了四次 (..) ,最终 M.Groups(1).Value ij 。但 M.Groups(1) 还包含一个捕获对象的集合,分别表示 ab cd ef gh ij

M.Groups(1).Captures(0).Value is ‘ab’
M.Groups(1).Captures(1).Value is ‘cd’
M.Groups(1).Captures(2).Value is ‘ef’
M.Groups(1).Captures(3).Value is ‘gh’
M.Groups(1).Captures(4).Value is ‘ij’
M.Groups(1).Captures.Count is 5

实际上,组对象的 Value 属性只是组的最终捕获对象的 Value 的简写。

关于捕获对象,还有以下几点需要注意:
1. M.Groups(1).Captures 是一个捕获对象集合,有 Items Count 属性,但通常可以直接通过索引访问集合中的元素。
2. 捕获对象没有 Success 方法,需要检查组对象的 Success 属性。
3. 虽然不太常用,但匹配对象(Match object)也有 Captures 属性,它直接访问第零组的 Captures 属性。由于第零组表示整个匹配,没有中间匹配过程,所以第零组的捕获集合总是只有一个捕获对象,因此 M.Captures M.Groups(0).Captures 的作用不大。

捕获对象是一个有趣的创新,但由于其在对象模型中的“过度集成”,看起来比实际更复杂和令人困惑。一方面,它是一个值得探索的创新;另一方面,在匹配过程中创建额外的捕获组并封装成对象,可能会带来效率负担,除非确实需要这些额外信息。

2 PHP正则表达式

PHP在20世纪90年代后期的网络繁荣初期就迅速流行起来,并且至今仍然非常受欢迎。PHP支持正则表达式,并且有三种不同的正则表达式引擎:“preg”、“ereg”和“mbRereg”。这里主要介绍“preg”引擎。

2.1 “preg”引擎背景和历史

“preg”这个名称来自于所有接口函数名的前缀,代表“Perl Regular Expressions”。该引擎由Andrei Zmievski添加,他对当时的标准“ereg”套件的局限性感到沮丧。“ereg”代表“extended regular expressions”,是一个符合POSIX标准的套件,但按照今天的标准来看,功能相对较少。

Andrei通过编写一个与PCRE(“Perl Compatible Regular Expressions”)的接口创建了“preg”套件。PCRE是一个优秀的基于NFA的正则表达式库,它紧密模仿了Perl的正则表达式语法和语义,提供了Andrei所寻求的强大功能。

在找到PCRE之前,Andrei曾查看过Perl的源代码,但发现其过于复杂,难以理解。幸运的是,英国剑桥大学的Philip Hazel也对Perl的正则表达式源代码感到困惑,于是他创建了PCRE库。随着时间的推移,PCRE不断发展,PHP也随之进化。这里介绍的是PHP 4.4.3和5.1.4版本,它们都集成了PCRE 6.6版本。

2.2 “preg”引擎的正则表达式风格

“preg”引擎的正则表达式风格涵盖了丰富的特性,以下是一个详细的总结表格:
| 类别 | 描述 |
| — | — |
| 字符简写 | \a , [\b] , \e , \f , \n , \r , \t , \octal , \xhex , \x{hex} , \cchar |
| 字符类和类类似结构 | [˙˙˙] , [^˙˙˙] , 点(除换行符外的任意字符,使用 s 模式修饰符时为任意字符), \X (Unicode组合序列), \w , \d , \s , \W , \D , \S (8位), \p{Prop} , \P{Prop} (Unicode属性和脚本), \C (恰好一个字节) |
| 锚点和其他零宽度测试 | ^ , \A (行/字符串开始), $ , \z , \Z (行/字符串结束), \G (当前匹配开始), \b , \B (单词边界), 前瞻和后瞻 (?=˙˙˙) , (?!˙˙˙) , (?<=˙˙˙) , (?<!˙˙˙) |
| 注释和模式修饰符 | 模式修饰符 (?mods-mods) (允许的修饰符: x , s , m , i , X , U ), 模式修改范围 (?mods-mods:˙˙˙) , 注释 (?# ˙˙˙) (使用 x 模式修饰符时,从 # 到换行符或正则表达式结束) |
| 分组、捕获、条件和控制 | 捕获括号 (˙˙˙) , \1 , \2 … 命名捕获 (?P<name>˙˙˙) , (?P=name) 仅分组括号 (?:˙˙˙) 原子分组 (?>˙˙˙) 交替 < 递归 (?R) , (?num) , (?P>name) 条件 (?if then<else) if 可以是前瞻、 (R) (num) ) 贪婪量词 * , + , ? , {n} , {n,} , {x,y} 懒惰量词 *? , +? , ?? , {n}? , {n,}? , {x,y}? 占有量词 *+ , ++ , ?+ , {n}+ , {n,}+ , {x,y}+ 字面量(非元字符)范围 \Q ... \E |

此外,还有一些补充说明:
1. \b 在字符类中表示退格符,在字符类外表示单词边界。
2. 八进制转义仅限于两位和三位的8位值,特殊的一位 \0 序列匹配NUL字节。
3. \xhex 允许一位和两位的十六进制值, \x{hex} 允许任意位数,但大于 \x{FF} 的值只有在使用 u 模式修饰符时才有效。
4. 即使在UTF - 8模式下,单词边界和类简写如 \w 仅适用于ASCII字符。如果需要考虑完整的Unicode字符范围,可以使用 \pL 代替 \w \pN 代替 \d \pZ 代替 \s
5. Unicode支持为Unicode 4.1.0版本,支持无 Is In 前缀的Unicode脚本,如 \p{Cyrillic} ,支持一到两个字母的Unicode属性,但不支持长名称。
6. 默认情况下,“preg”套件的正则表达式是面向字节的, \C 默认等同于 (?s:.) 。但使用 u 修饰符后,正则表达式变为面向UTF - 8,此时一个字符可能由最多六个字节组成,但 \C 仍然只匹配一个字节。
7. \z \Z 都可以在主题字符串的末尾匹配,但 \Z 还可以在最后一个字符为换行符的位置匹配。 $ 的含义取决于 m D 模式修饰符:没有模式修饰符时, $ 的行为与 \Z 相同;使用 m 模式修饰符时,它还可以在嵌入的换行符之前匹配;使用 D 模式修饰符时,它的行为与 \z 相同;如果同时使用 m D 模式修饰符, D 将被忽略。

通过以上介绍,我们可以看到.NET和PHP在正则表达式方面都提供了丰富的功能和特性,开发者可以根据具体需求选择合适的工具和方法来进行正则表达式的使用和开发。

.NET与PHP正则表达式深度解析

3 “preg”引擎的使用与效率考量

在掌握了“preg”引擎的背景、历史和正则表达式风格后,接下来深入探讨其使用方法以及在使用过程中需要关注的效率问题。

3.1 “preg”函数接口使用

“preg”套件提供了一系列函数来处理正则表达式,下面通过几个常见函数来了解其使用方式。

preg_match函数 :用于在字符串中进行一次正则表达式匹配。以下是一个简单的示例:

<?php
$subject = "Hello, World!";
$pattern = '/Hello/';
if (preg_match($pattern, $subject, $matches)) {
    echo "匹配成功:" . $matches[0];
} else {
    echo "未找到匹配项";
}
?>

在上述代码中, preg_match 函数尝试在 $subject 字符串中查找与 $pattern 匹配的内容。如果找到匹配项,结果会存储在 $matches 数组中。

preg_match_all函数 :用于在字符串中查找所有匹配的内容。示例如下:

<?php
$subject = "apple, banana, cherry";
$pattern = '/\w+/';
if (preg_match_all($pattern, $subject, $matches)) {
    foreach ($matches[0] as $match) {
        echo $match . "\n";
    }
}
?>

此代码会找出 $subject 字符串中所有的单词,并逐行输出。

preg_replace函数 :用于替换字符串中匹配的内容。示例如下:

<?php
$subject = "Hello, World!";
$pattern = '/World/';
$replacement = 'Universe';
$result = preg_replace($pattern, $replacement, $subject);
echo $result;
?>

这段代码将 $subject 字符串中的“World”替换为“Universe”并输出结果。

3.2 “preg”特定的效率考量

在使用“preg”引擎时,需要注意一些效率方面的问题,以避免性能瓶颈。以下是一些建议:
1. 合理使用模式修饰符 :模式修饰符可以改变正则表达式的匹配行为,但使用不当可能会影响效率。例如, s 修饰符会使点号匹配任意字符,包括换行符,如果不需要这种匹配方式,就不要使用该修饰符。
2. 避免过度回溯 :正则表达式中的回溯是一种强大的机制,但过度回溯会导致性能下降。可以使用原子分组 (?>...) 或占有量词 *+ , ++ , ?+ 等来减少回溯。例如:

<?php
$subject = "abcabcabc";
$pattern = '/(?>abc)+/';
preg_match($pattern, $subject, $matches);
?>

在这个例子中,使用原子分组可以避免不必要的回溯。
3. 预编译正则表达式 :如果同一个正则表达式需要多次使用,可以将其预编译,以提高效率。示例如下:

<?php
$pattern = '/\d+/';
$compiledPattern = preg_replace_callback($pattern, function ($matches) {
    return $matches[0];
}, '');
// 多次使用预编译的正则表达式
preg_match($compiledPattern, "123");
preg_match($compiledPattern, "456");
?>
4 扩展示例

为了更好地理解和应用前面介绍的知识,下面给出一些扩展示例。

4.1 验证电子邮件地址

使用“preg”引擎可以方便地验证电子邮件地址的格式是否正确。示例代码如下:

<?php
$email = "example@example.com";
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
if (preg_match($pattern, $email)) {
    echo "电子邮件地址格式正确";
} else {
    echo "电子邮件地址格式错误";
}
?>

这个正则表达式可以验证大多数常见的电子邮件地址格式。

4.2 提取HTML标签中的内容

假设我们有一个HTML字符串,需要提取其中的所有标签内容。示例代码如下:

<?php
$html = "<p>这是一个段落</p><h1>标题</h1>";
$pattern = '/<([a-zA-Z]+)>(.*?)<\/\1>/';
if (preg_match_all($pattern, $html, $matches)) {
    for ($i = 0; $i < count($matches[0]); $i++) {
        echo "标签名:" . $matches[1][$i] . ",内容:" . $matches[2][$i] . "\n";
    }
}
?>

此代码会提取出HTML字符串中所有标签的名称和内容,并逐行输出。

5 总结

通过对.NET和PHP正则表达式的深入解析,我们了解到它们各自的特点和优势。在.NET中,正则表达式程序集、匹配嵌套结构和捕获对象等特性为正则表达式的使用提供了更多的灵活性和强大功能。而PHP的“preg”引擎则凭借其丰富的正则表达式风格和一系列实用的函数,成为处理文本匹配和替换的有力工具。

在实际开发中,开发者可以根据具体需求选择合适的平台和方法。同时,在使用正则表达式时,要注意效率问题,合理使用各种特性和技巧,以提高代码的性能和可维护性。

以下是使用mermaid绘制的流程图,展示了使用“preg_match”函数进行匹配的基本流程:

graph TD;
    A[开始] --> B[定义主题字符串和正则表达式模式];
    B --> C[调用preg_match函数进行匹配];
    C --> D{是否匹配成功};
    D -- 是 --> E[处理匹配结果];
    D -- 否 --> F[输出未找到匹配项];
    E --> G[结束];
    F --> G;

通过以上内容,我们对.NET和PHP的正则表达式有了更全面的认识,希望这些知识能帮助开发者在实际项目中更好地运用正则表达式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值