aardio 文档

preg 正则表达式扩展库使用指南

一. 简介

preg 是使用 PCRE(Perl Compatible Regular Expressions) 组件创建的正则表达式支持库, 兼容 Perl,PHP 正则语法。可在正则表达式的修正符中添加"j" 选项以启用 JavaScript 兼容语法( 注:标准库中的 string.regex 使用 VBS 语法 )

用法与 PHP 大同小异,例如 PHP 中使用正则表达式拆分字符串的代码如下:

$keywords = preg_split ("/[\s,]+/is", "hypertext language,,programming");
var_dump($keywords);

那么在 aardio 代码里用法类似,区别是先创建正则表达式对象,代码如下:

import preg;
import console;

var $keywords = preg("/[\s,]/is").split ( "hypertext language,,programming");
console.varDump( $keywords )
console.pause(true);

preg 对象的 replace 函数 用法类似模式匹配函数 string.replace, 同样可以将替换对象指定为回调函数、替换表、字符串(支持反向引用捕获组)。

preg 对象的全局查找函数 gmatch   用法也类似模式匹配函数 string.gmatch

下面是一个完整的示例:

import console; 
import preg;

var regex = preg("(\w+\:\/\/)(?P<host>[\w.]+)");

var testString = /*
http://bbs.aardio.com
http://www.example.com
*/

console.log( "测试是否匹配", regex.test(testString) );
console.log( "查找匹配位置", regex.find(testString) );
console.log( "获取匹配字符串", regex.match(testString) );

//全局匹配
for scheme,host in regex.gmatch( testString  ) { 
    console.log("发现匹配字符串", scheme,host )
}

console.log( '字符串替换结果\r\n', regex.replace( testString,"ftp://\2" ) );

console.log( '函数替换结果\r\n', regex.replace( testString
        ,function(scheme,host){
                if( host == "bbs.aardio.com" )
                        return "ftp://" + host; 
        }  ) );

//数组匹配,找出所有网址并返回数组
var urls = regex.grep( {
        "http://bbs.aardio.com";
        "www.aardio.net";
        "http://www.example.com";
} );

console.varDump(urls)
regex.free(); 

$keywords = preg("/[\s,]/is").split ( "hypertext language,,programming");
console.varDump( $keywords );
console.pause();

注意:

aardio 所有字符串处理函数原生支持模式匹配,aardio 模式匹配是语法简化的正则表达式,基本语法类似正则表达式,但不支持对捕获分组使用模式运算等,添加了更多实用的功能(例如局部禁用模式匹配、元序列等功能),运行速度更快。模式匹配使用!实现边界断言,而正则表达式使用\b表示单词分界, aardio提供了更多的字符类,例如\a表示字母,\u表示大写字符,\l表示小写字符, \p表示标点符号,以及使用冒号表示中文宽字符等等。

aardio 模式匹配式匹配实现了正则表达式最常用的主要功能,但是运行速度却比 preg 正则表达式的速度快数十倍,比 string.regex 提供的 VBS 正则表达式快数百倍

参考:模式匹配快速入门

二. 创建正则表达式对象:

如下 aardio 代码用于创建 preg 正则表达式对象

import preg;  

var 正则表达式对象 = preg("正则表达式","模式修正符")

注意 aardio 并不推荐将正则表达式放在一对斜杠内 - 这种兼容 PHP 的语法意谓需要多分析一次正则, 所以 preg( "/正则表达式/模式修正符" ) 改为 preg("正则表达式","模式修正符") 更好一些。

\ 是正则表达式的转义符,因为 aardio 单引号内是转义字符串 -  所以你必须用 '\\' 表示 正则表达式中的 "\", 为了避免混淆一般将正则表达式或模式匹配写在双引号中,在 aardio 中双引号包含的是原始字符串。

三. 正则表达式中常用模式

/pattern/ 匹配结果
. 匹配除换行符以外的所有字符
x? 匹配 0 次或一次 x 字符串
x* 匹配 0 次或多次 x 字符串,但匹配可能的最少次数
x+ 匹配 1 次或多次 x 字符串,但匹配可能的最少次数
.* 匹配 0 次或一次的任何字符
.+ 匹配 1 次或多次的任何字符
{m} 匹配刚好是 m 个 的指定字符串
{m,n} 匹配在 m个 以上 n个 以下 的指定字符串
{m,} 匹配 m个 以上 的指定字符串
[] 匹配符合 [] 内的字符
[^] 匹配不符合 [] 内的字符
[0-9] 匹配所有数字字符
[a-z] 匹配所有小写字母字符
[^0-9] 匹配所有非数字字符
[^a-z] 匹配所有非小写字母字符
^ 匹配字符开头的字符
$ 匹配字符结尾的字符
\ 如果要在 pattern 模式中找寻一个特殊字符,如 *,则要在这个字符前加上转义符 \ 将特殊字符还原为字面值
\d 匹配一个数字的字符,和 [0-9] 语法一样
\d+ 匹配多个数字字符串,和 [0-9]+ 语法一样
\D 非数字,其他同 \d
\D+ 非数字,其他同 \d+
\w 英文字母或数字的字符串,和 [a-zA-Z0-9] 语法一样
\w+ [a-zA-Z0-9]+ 语法一样
\W 非英文字母或数字的字符串,和 [^a-zA-Z0-9] 语法一样
\W+ [^a-zA-Z0-9]+ 语法一样
\s 空格,和 [\n\t\r\f] 语法一样
\s+ [\n\t\r\f]+ 一样
\S 非空格,和 [^ \n\t\r\f] 语法一样
\S+ [^ \n\t\r\f]+ 语法一样
\b 匹配以英文字母,数字为边界的字符串
\B 匹配不以英文字母,数值为边界的字符串
a|b|c 匹配符合a字符 或是b字符 或是c字符 的字符串
abc 匹配含有 abc 的字符串
(pattern) () 这个符号会创建『捕获分组』记住所找寻到的字符串,是一个很实用的语法。第一个 () 内所找到的字符串在后面可以使用 \1$1引用,第二个 () 内匹配的结果在后面可以使用 \2$2 变量进行引用,以此类推下去。
\pattern\i i 这个参数表示忽略英文大小写,也就是在匹配字符串的时候,不考虑英文的大小写问题。

四. 正则表达式示例

范例 说明
"/perl/" 找到含有 perl 的字符串
"/^perl/" 找到开头是 perl 的字符串
"/perl$/" 找到结尾是 perl 的字符串
"/c|g|i/" 找到含有 c 或 g 或 i 的字符串
"/cg{2,4}i/" 找到 c 后面跟着 2个到 4个 g ,再跟着 i 的字符串
"/cg{2,}i/" 找到 c 后面跟着 2个以上 g ,再跟着 i 的字符串
"/cg{2}i/" 找到 c 后面跟着 2个 g,再跟着 i 的字符串
"/cg*i/" 找到 c 后面跟着 0个或多个 g ,再跟着 i 的字符串,如同/cg{0,1}i/
"/cg+i/" 找到 c 后面跟着一个以上 g,再跟着 i 的字符串,如同/cg{1,}i/
"/cg?i/" 找到 c 后面跟着 0个或是 1个 g ,再跟着 i 的字符串,如同/cg{0,1}i/
"/c.i/" 找到 c 后面跟着一个任意字符,再跟着 i 的字符串
"/c..i/" 找到 c 后面跟着二个任意字符,再跟着 i 的字符串
"/[cgi]/" 找到符合有这三个字符任意一个的字符串
"/[^cgi]/" 找到没有这三个字符中任意一个的字符串
"/\d/" 找寻符合数字的字符,可以使用/\d+/来表示一个或是多个数字组成的字符串
"/\D/" 找寻符合不是数字的字符,可以使用/\D+/来表示一个或是更多个非数字组成的字符串
"/\*/" 找寻符合 * 这个字符,因为 * 在常规表达式中有它的特殊意思,所以要在这个特殊符号前加上\ 符号,这样才会让这个特殊字符失效
"/abc/i" 找寻符合 abc 的字符串而且不考虑这些字符串的大小写

五. 正则表达式用法说明

1. 贪婪/懒惰

所有限定匹配次数的正则量词运算符都是贪婪的。它们尽可能多地匹配更多更长的目标字符串。在正则运算符后添加 ? 能让表达式仅匹配尽可能短的长度。修改器U也能惰化量词运算符。

下面是一个简单演示:

import console;
import preg;

var str = 'hihihi oops hi';

//使用贪婪的{n,}操作符进行匹配 
var m = preg('/((hi){2,})/').match(str); //返回值将是 'hihihi'
console.log(m)

//操作符匹配,在量词运算符后添加问号表示惰化匹配
var m = preg('/((hi){2,}?)/').match(str);//返回值将是 'hihi'
console.log(m)
console.pause()

2. 分组

1) 捕获组 (Capturing Groups )#

在正则表达式中可以使用括号() 对匹配结果进行分组,在后面可以使用 \1\9 引用前面的捕获组,这称为反向引用(Back referencing)。

反向引用(Back referencing)是指在正则表达式内部引用之前捕获到的内容的方法。

preg().replace()函数的替换串中可以使用 $1$9 引用前面的捕获组,另外可使用$0引用匹配的整个字符串。在 aardio 的这个替换函数里,$0$9 也可以写为 \1\9,其他语言在正则替换串里则必须使用 $符号,这一点要注意区别。

2) 命名捕获组 (Named Capturing Groups) #

在正则表达式中可以使用(?P<name>pattern)格式的语法指定一个正则捕获组的名称,name 指定组名,pattern 则是正则模式。

请看下面的例子:

import console;
import preg;

var regex = preg("/(?P<quote>""|').*?(?P=quote)/");
var m = regex.exec( ' "测试字符串" ' );

//exec返回的对象,可以使用名字直接引用命名分组
console.log("使用的引号", m.quote )
console.pause()

上式中,quote 就是组名,|是改组匹配内容的正则。后面的(?P=quote)是在引用组名为 quote 的捕获组。

3) 非捕获组 (Non-capturing group)#

在正则表达式中可以使用(?:pattern)格式的语法指定一个不捕获匹配结果的分组。

非捕获组同样可以对模式进行分组,并且可以对非捕获组使用其他正则运算符。

非捕获组有更好的性能,并且非捕获组不记录匹配的内容,反向引用也会忽略非捕获组(不计数)。

aardio 模式匹配中则是使用尖括号包含的 元序列 创建非捕获组,格式为 <subpattern>

4) 原子分组(Atomic Groups)

使用 (?>subpattern) 可以创建原子分组。

原子分组是一种特殊的非捕获组,不但不会捕获匹配结果,而且在原子组内部不会记录之前匹配的位置,也不会在后续匹配失败后回溯到之前记录的位置重新匹配。

原子组通常用于提高正则表达式的性能,以避免不必要的回溯。

回溯会尝试更多可能性并匹配到更复杂的结果,但滥用回溯会严重降低匹配性能。

在不必要回溯时使用原子分组可以优化性能。

并且在有些情况下,我们需要禁用回溯才能避免匹配到不需要的结果。

示例: #

import console; 
import preg;

var regex = preg("/(endif|end if|end)(?!\s+else)/");

console.log( regex.match("if ... end",p) )
console.log( regex.match("if ... endif",p) )
console.log( regex.match("if ... end else",p) )
console.log( regex.match("if ... end if else",p) )

console.pause();

在上面的示例中,我们期望匹配 "endif","end if","end" 三个关键词后面都没有 "else" 的字符串。

测试上面的代码,我们意外地发现 "if ... end if else" 总是能匹配成功。这就是回溯功能带来的问题。正则表达式 (endif|end if|end) 遇到字符串 "end if else" 以后,首先匹配 end if 成功,然后发现后面有不应该出现的 else,于是正则表达式回溯到原来的位置匹配 end 成功,这时候它发现后面是if 而不是 else 于是机巧地利用回溯陷阱返回了我们不需要的结果。

解决方法是改用匹配成功就不会往回走的原子分组,将上面的 (endif|end if|end) 改为 (?>endif|end if|end)就可以成功。

参考:在 aardio 模式匹配语法文档中查看相同的示例

3. 单词边界(Word Boundaries)

\b 匹配单词边界,这是一个零宽匹配,不消耗字符长度

例如 /\bend\b/ 只匹配 "end" , 不会匹配 "friend" 或 "ending"。

\B 的作用则完全相反,匹配不是单词边界的位置。

注意:正则表达式又或者 aardio 模式匹配通常都会用大写表示反义。

4. 递归(Recursion)

递归(Recursion)在正则表达式中用于匹配嵌套结构,例如括号嵌套 (this (that)) 或 HTML 标签嵌套 <div><div></div></div>。我们使用 (?R) 来表示递归调用整个模式。

下面是一个匹配嵌套括号的例子:

\(((?>[^()]+)|(?R))*\)

这个表达式的解释如下:

这个表达式会尽可能多地匹配嵌套的括号。

另一个递归的例子是匹配嵌套的 HTML 标签:

<([\w]+)([^<]*?)>((?>[^<]+)|(?R))*<\/\1>

这个表达式的解释如下:

这个表达式综合运用了字符分组、贪婪操作符、回溯以及递归来匹配嵌套的 HTML 标签。

5. 回调(Callbacks)

在处理匹配结果时,有时需要对特定内容进行特别的修改。此时,正则表达式的回调函数就派上用场了。

回调函数用于 preg().replace() 中,实现对匹配结果的动态修改。你可以为 preg().replace() 指定一个函数作为参数,该函数接收匹配结果数组并返回修改后的数组,作为替换的结果。

例如,我们想将某字符串中的首字母转换为大写。

首先,使用正则表达式匹配所有需要大写的字母:

/\b\w/

该表达式使用了单词边界和字符类。接下来,我们需要一个回调函数:

function upperCase(firstLetter) { 
    return string.upper(firstLetter) 
}

函数 upperCase 接收匹配结果数组,并将匹配结果的首字母转换为大写。在这个例子中,firstLetter 代表需要大写的字母。然后,我们利用 preg().replace() 函数实现回调:

import console;
import preg;

function upperCase(c) {  
    return string.upper(c) 
}

var regex = preg("/\b\w/");
var str = regex.replace("very good", upperCase); 
console.log(str)
console.pause()

通过这个简单的回调函数,我们就能实现强大的字符串处理功能。

6. 注释 (Commenting)

注释在正则表达式中虽然不用于匹配字符串,但却是理解和维护复杂正则表达式的重要工具。随着正则表达式变得越来越复杂,理解其匹配逻辑会变得越来越困难。通过在正则表达式中添加注释,可以减少未来的困惑和误解。

要在正则表达式中添加注释,可以使用 (?#comment) 语法,其中 comment 是你的注释内容。例如:

/(?#匹配数字)\d/

如果你计划将代码公开,添加注释显得尤为重要,这样其他人可以更容易地理解和修改你的代码。注释同样有助于你在将来重新审视自己写的代码时快速理解其逻辑。

此外,可以使用 "x""(?x)" 修饰符来格式化正则表达式,使其更具可读性。这个修饰符会让正则引擎忽略表达式中的空格和换行符。需要匹配的空格仍然可以通过 \s\(反斜杠加空格)来表示。

以下是一个使用 "x" 修饰符的示例:

import preg;
import console;

var m = preg("/
\d+   # 匹配数字
\s+   # 匹配空格
\w+   # 匹配单词
/x").match("243523 test");

console.varDump(m);
console.pause();

上述 aardio 代码与以下代码的功能相同:

import preg;
import console;

var m = preg("\d+(?#匹配数字)\s+(?#匹配空格)\w+(?#匹配单词)").match("243523 test");
console.varDump(m);
console.pause();

请始终注意代码的可读性,确保注释清晰明了。

六. 修正符列表

下面列出了当前在 preg 支持库中可能使用的修正符。括号中是这些修正符的内部 PCRE 常量名。修正符中的空格和换行被忽略,其它字符会导致错误。

preg 扩展库在开启此选项( UTF-8 )以后,正则表达式即按字符计数,并可对中文字符使用匹配修饰符,示例如下:

import preg;
import console;

var regex,err = preg("/中+文/u");
$keywords,j = regex.match (  "abc中中中文" );
console.varDump(  $keywords )
console.pause(); 

启用了 UTF-8 编码以后,也可以使用 Unicode 匹配全部中文,如下:

import preg;
import console;

var regex,err = preg("/[\x{4e00}-\x{9fa5}]+/u");
$keywords,j = regex.match (  "abc中文" );
console.varDump(  $keywords )
console.pause(); 

如果在修正符中指定 "j",则可以使用 Javascript 的正则语法指定 Unicode 编码,如下:

import preg;
import console;

var regex,err = preg("/[\u4e00-\u9fa5]+/ju");  
$keywords,j = regex.match (  "abc中文" );
console.varDump(  $keywords )
console.pause();

如果是 UTF-8 编码的字符串, 则需要象下面这样匹配中文

import preg;
import console;

var regex,err = preg("/([\x80-\xFF]+)/");

Markdown 格式