正则基础之——神奇的转义

义方式、元字符出现的位置不同,转义的方式也是林林总总,不一而同。

2         .NET 正则中的字符转义

2.1      .NET 正则中的转义符

绝大多数语言中,“ / ”都被作为转义符,用来转义一些具有特殊意义的字符或字符序列,比如“ /n ”表示换行,“ /t ”表示水平制表符等。而这样的转义,应用到正则中,又会有一些意想不到的变化。

话题由 C# 中一个正则问题引出

string [] test = new string []{"//" , "////" };

Regex reg = new Regex ("^////$" );

foreach (string s in test)

{

     richTextBox2.Text += " 源字符串: " + s.PadRight(5, ' ' ) + " 匹配结果: " + reg.IsMatch(s) + "/n" ;

}

/*-------- 输出--------

源字符串: /    匹配结果: True

源字符串: //   匹配结果: False

*/

对于这个结果,或许有人会感到迷惑,字符串中的“ // ”不是代表一个经过转义的“ / ”字符吗?而“ //// ”不就应该代表两个经过转义的“ / ”字符吗?那么上面正则匹配的结果应该是第一个为 False ,第二个为 True 才对啊?

对于这一问题,直接解释或许不太容易理解,还是换种方式来解释吧。

比如要匹配的字符是这样的

string test = "(" ;

那么正则如何写呢?因为“ ( ”在正则中是有特殊意义的,所以写正则时必须对它进行转义,也就是“ /( ”,而在字符串中,要使用“ // 来表示“ / ”本身,也就是

Regex reg = new Regex ("^//($" );

这个如果理解了,那再把“ ( ”换回“ / ”,同样道理,在字符串中,要使用“ // 来表示“ / ”本身,也就是

Regex reg = new Regex ("^////$" );

通过这样的分析,可以看出,其实在以字符串形式声明的正则中,“ //// ”匹配的实际上就是单独的一个“ / ”字符。总结一下它们之间的关系:

输出到控制台或界面的字符串: /

程序中声明的字符串: string test = "//" ;

程序中声明的正则:Regex reg = new Regex ("^////$" );

这样解释是不是已经可以理解了,那么是不是感觉这样很笨拙?是的,在程序中以字符串形式声明的正则,涉及到转义符时就是这样笨拙的。

所以在 C# 中,还提供了另一种字符串声明方式,在字符串前加个“ @ ”,就可以忽略转义。

string [] test = new string [] { @"/" , @"//" };

Regex reg = new Regex (@"^//$" );

foreach (string s in test)

{

    richTextBox2.Text += " 源字符串: " + s.PadRight(5, ' ' ) + " 匹配结果: " + reg.IsMatch(s) + "/n" ;

}

/*-------- 输出--------

源字符串: /    匹配结果: True

源字符串: //   匹配结果: False

*/

这样就简洁多了,也符合通常的理解。

但同时也带来另一个问题,就是双引号的转义处理。在普通的字符串声明中,可以用“ /” ”对双引号进行转义。

string test = "<a href=/"www.test.com/">only a test</a>" ;

但是在字符串前加了“ @ ”后,“ / ”会被识别为“ / ”字符本身,这样就不能用“ /” ”对双引号进行转义了,需要用“ ”” ”对双引号进行转义。

string test = @"<a href=""www.test.com"">only a test</a>" ;

而在 VB.NET 中,正则的定义只有一种形式,与 C# 中加了“ @ ”后的定义方式是一致的。

Dim test As String () = New String () {"/" , "//" }

Dim reg As Regex = New Regex("^//$" )

For Each s As String In test

    RichTextBox2.Text += " 源字符串:" & s.PadRight(5, " "c ) & " 匹配结果:" & reg.IsMatch(s) & vbCrLf

Next

'-------- 输出--------

' 源字符串:/    匹配结果:True

' 源字符串://   匹配结果:False

'--------------------

2.2      .NET 正则中需要转义的元字符

MSDN 中,以下字符作为正则中的元字符,在匹配其本身时,需要对其进行转义

. $ ^ { [ ( | ) * + ? /

但实际应用中,还要根据实际情况来判断,以上字符可能不需要转义,也可能不止以上字符需要转义。

在正常的正则书写过程中,以上字符的转义通常都能被编写人员正常处理,但是在动态生成正则时,就需要格外的注意,否则变量中包含元字符时,动态生成的正则在编译时可能会抛异常。好在 .NET 中提供了 Regex .Escape 方法来处理这一问题。比如根据动态获取的 id 来提取相应的 div 标签内容。

string id = Regex .Escape(textBox1.Text);

Regex reg = new Regex (@"(?is)<div(?:(?!id=).)*id=(['""]?)" + id  + @"/1[^>]*>(?><div[^>]*>(?<o>)|</div>(?<-o>)|(?:(?!</?div/b).)*)* (?(o)(?!))</div>" );

如果不做转义处理,那么动态获取的 id 如果为“ abc(def ”这种形式,程序运行过程中就会抛出异常了。

2.3      .NET 正则中字符组的转义

在字符组 [] 中,元字符通常是不需要转义的,甚至于“ [ ”也是不需要转义的。

string test = @"the test string:  . $ ^ { [ ( | ) * + ? /" ;

Regex reg = new Regex (@"[.$^{[(|)*+?//]" );

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "/n" ;

}

/*-------- 输出--------

.

$

^

{

[

(

|

)

*

+

?

/

*/

但是在正则书写时,字符组中的“ [ ”还是建议使用“ /[ ”对其转义的,正则本身就已经是非常抽象,可读性很低的了,如果在字符组中再掺杂进这样不经转义的“ [ ”,会使得可读性更差。而且在出现不正确的嵌套时,可能会导致正则编译异常,以下正则在编译时就会抛异常的。

Regex reg = new Regex (@"[.$^{[(]|)*+?//]" );

然而, .NET 的字符组中,是支持集合减法的,在这种正常语法形式下,是允许字符组嵌套的。

string test = @"abcdefghijklmnopqrstuvwxyz" ;

Regex reg = new Regex (@"[a-z-[aeiou]]+" );

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "/n" ;

}

/*-------- 输出--------

bcd

fgh

jklmn

pqrst

vwxyz

*/

这种用法可读性很差,应用也很少见,即使有这种需求也可以通过其它方式实现,了解一下即可,不必深究。

话题再回到转义上,字符组中必须转义的只有“ / ”,而“ [ ”和“ ] ”出现在字符组中时,也是建议一定做转义处理的。另外有两个字符“ ^ ”和“ - ”,出现在字符组中特定位置时,如果要匹配其本身,也是需要转义的。

^ ”出现在字符组开始位置,表示排除型字符组,“ [^Char] ”也就是匹配除字符组中包含的字符之外的任意一个字符,比如“ [^0-9] ”表示除数字外的任意一个字符。所以在字符组中,要匹配“ ^ ”字符本身,要么不放在字符组开始位置,要么用“ /^ ”进行转义。

Regex reg1 = new Regex (@"[0-9^]" );

Regex reg2 = new Regex (@"[/^0-9]" );

这两种方式都表达匹配任意一个数字或普通字符“ ^ ”。

至于“ - ”在字符组中特殊性,举一个例子。

string test = @"$" ;

Regex reg = new Regex (@"[#-*%&]" );

richTextBox2.Text = " 匹配结果:" + reg.IsMatch(test);

/*-------- 输出--------

匹配结果:True

*/

正则表达式中明明没有“ $ ”,为什么匹配结果会是“ True ”呢?

[] 支持用连字符“ - ”连接两个字符,来表示一个字符范围。需要注意的是,“ - ”前后的两个字符是有顺序的,在使用相同的编码时,后面的字符码位应大于或等于前面字符的码位。

for (int i = '#' ; i <= '*' ; i++)

{

     richTextBox2.Text += (char )i + "/n" ;

}

/*-------- 输出--------

#

$

%

&

'

(

)

*

*/

由于“ # ”和“ * ”符合要求,“ [#-*] ”可以表示一个字符范围,其中就包含了字符“ $ ”,所以上面的正则是可以匹配“ $ ”的,如果只是把“ - ”当作一个普通字符处理,那么要么换个位置,要么把“ - ”转义。

Regex reg1 = new Regex (@"[#*%&-]" );

Regex reg2 = new Regex (@"[#/-*%&]" );

这两种方式都表示匹配字符组中列举的字符中的任意一个。

在字符组中,还有一个比较特殊的转义字符,“ /b ”出现在正则表达式中一般位置时,表示单词边界,也就是一侧为组成单词的字符,另一侧不是;而当“ /b ”出现在字符组中时,表示的是退格符,与普通字符串中出现的“ /b ”意义是一样的。

同样的,还有一个容易被忽视,而且经常被忽视的转义符“ | ”,当“ | ”出现在正则表达式中一般位置时,表示左右两侧“或”的关系;而当“ | ”出现在字符组中时,它仅仅表示“ | ”字符本身,没有任何特殊意义,所以如果不是要匹配“ | ”本身,而试图在字符组中使用“ | ”时,是错误的。比如正则表达式“ [a|b] ”表示的是“ a ”、“ b ”、“ | ”中的任意一个,而不是“ a ”或“ b ”。

2.4      .NET 正则应用中不可见字符转义处理

对于一些不可见字符,要在字符串中表示时,需要用转义字符,比较常见的有“ /r ”、“ /n ”、“ /t ”等等,而这些字符在正则中应用,就变得有些神奇了,先看一段代码。

string test = "one line. /n another line." ;

List <Regex > list = new List <Regex >();

list.Add(new Regex ("/n" ));

list.Add(new Regex ("//n" ));

list.Add(new Regex (@"/n" ));

list.Add(new Regex (@"//n" ));

foreach (Regex reg in list)

{

    richTextBox2.Text += " 正则表达式:" + reg.ToString();

    MatchCollection mc = reg.Matches(test);

    foreach (Match m in mc)

    {

        richTextBox2.Text += "   匹配内容:" + m.Value + "   匹配起始位置:" + m.Index + "   匹配长度:" + m.Length;

    }

    richTextBox2.Text += "   匹配总数:" + reg.Matches(test).Count + "/n----------------/n" ;

}

/*-------- 输出--------

正则表达式:

   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式:/n   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式:/n   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式://n   匹配总数:0

----------------

*/

可以看到,前三种写法,输出的正则虽不同,但执行结果却是完全相同的,只有最后一种是没有匹配的。

正则表达式一 Regex ("/n" ) ,其实就是以普通字符串形式来声明正则的,与用 Regex ("a" ) 来匹配字符“a ”是同样的道理,是不经过正则引擎转义的。

正则表达式二Regex ("//n" ) ,是以正则表达式形式来声明正则的,正如正则中的“//// 就等同于字符串中的“ // ”一样,正则中的“ //n ”就等同于字符串中的“ /n ”,是经过正则引擎转义的。

正则表达式三 Regex (@"/n" ) ,与正则表达式二等价,是字符串前加“ @ ”的写法。

正则表达式四 Regex (@"//n" ) ,其实这个表示的是字符“/ ”后面跟一个字符“n ”,是两个字符,这个在源字符串中自然是找不到匹配项的。

这里需要特别注意的还是 /b ”,不同的声明方式,“ /b ”的意义是不同的。

string test = "one line. /n another line." ;

List <Regex > list = new List <Regex >();

list.Add(new Regex ("line/b" ));

list.Add(new Regex ("line//b" ));

list.Add(new Regex (@"line/b" ));

list.Add(new Regex (@"line//b" ));

foreach (Regex reg in list)

{

     richTextBox2.Text += " 正则表达式:" + reg.ToString() + "/n" ;

     MatchCollection mc = reg.Matches(test);

     foreach (Match m in mc)

     {

          richTextBox2.Text += " 匹配内容:" + m.Value + "   匹配起始位置:" + m.Index + "   匹配长度:" + m.Length + "/n" ;

     }

     richTextBox2.Text += " 匹配总数:" + reg.Matches(test).Count + "/n----------------/n" ;

}

/*-------- 输出--------

正则表达式:line_

匹配总数:0

----------------

正则表达式:line/b

匹配内容:line   匹配起始位置:4   匹配长度:4

匹配内容:line   匹配起始位置:20   匹配长度:4

匹配总数:2

----------------

正则表达式:line/b

匹配内容:line   匹配起始位置:4   匹配长度:4

匹配内容:line   匹配起始位置:20   匹配长度:4

匹配总数:2

----------------

正则表达式:line//b

匹配总数:0

----------------

*/

正则表达式一 Regex ("line/b" ) ,这里的“ /b ”是退格符 ,是不经过正则引擎转义的。源字符串中是没有的,所以匹配结果为0

正则表达式二Regex ("line//b" ) ,是以正则表达式形式来声明正则的, 这里的“ //b ”是单词边界,是经过正则引擎转义的。

正则表达式三 Regex (@"line/b" ) ,与正则表达式二等价,指单词边界。

正则表达式四 Regex (@"line//b" ) ,其实这个表示的是字符“/ ”后面跟一个字符“b ”,是两个字符,这个在源字符串中自然是找不到匹配项的。

2.5      .NET 正则应用中其它转义处理

.NET 正则应用中还有一些其它转义方式,虽然用得不多,但也顺便提一下吧。

需求:把字符串中“ < ”和“ > ”之间的数字前加上“ $

string test = "one test <123>, another test <321>" ;

Regex reg = new Regex (@"<(/d+)>" );

string result = reg.Replace(test, "<$$1>" );

richTextBox2.Text = result;

/*-------- 输出--------

one test <$1>, another test <$1>

*/

也许你会惊奇的发现,替换结果不是在数字前加了“ $ ”,而是将所有数字都替换为“ $1 ”了。

为什么会这样呢,这是因为在替换结构中,“ $ ”是有特殊意义的,在它后面接数字,表示对对应编号捕获组匹配结果的引用,而有些情况下,需要在替换结果中出现“ $ ”字符本身,但它后面又跟了数字,这时候就需要用“ $$ ”对它进行转义了。而上面这个例子却恰恰是由于这种转义效果导致出现了异常结果,要规避这一问题,可以使替换结果中不出现对捕获组的引用。

string test = "one test <123>, another test <321>" ;

Regex reg = new Regex (@"(?<=<)(?=/d+>)" );

string result = reg.Replace(test, "$" );

richTextBox2.Text = result;

/*-------- 输出--------

one test <$123>, another test <$321>

*/

3        JavaScript Java 中的转义符

JavaScript Java 中正则的转义符处理,以字符串形式声明时,基本上都是与 .NET 中一致的,简单的介绍一下。

JavaScript 中,以字符串形式声明正则,与 C# 中的表现是一样的,同样会显得很笨拙。

<script type="text/javascript" >

    var data = ["//" , "////" ];

    var reg = new RegExp("^////$" , "" );

    for (var i=0;i<data.length;i++)

    {

        document.write(" 源字符串:" + data[i]  + "   匹配结果:" + reg.test(data[i]) + "<br />" );

    }

</script>

/*-------- 输出--------

源字符串:/ 匹配结果:true

源字符串:// 匹配结果:false

*/

JavaScript 中虽然没有提供 C# 中这种“ @ ”方式的字符串声明方式,但提供了另一种正则表达式的专有声明方式。

<script type="text/javascript" >

    var data = ["//" , "////" ];

    var reg = /^//$/;

    for (var i=0;i<data.length;i++)

    {

        document.write(" 源字符串:" + data[i]  + "   匹配结果:" + reg.test(data[i]) + "<br />" );

    }

</script>

/*-------- 输出--------

源字符串:/ 匹配结果:true

源字符串:// 匹配结果:false

*/

JavaScript

var reg = /Expression/igm;

这种声明方式,一样可以简化含有转义符的正则。

当然,以这种形式声明正则时,“ / ”自然也就成为了元字符,正则中出现这一字符时,必须进行转义处理。比如匹配链接中域名的正则

var reg = /http:////:([^//]+)/ig;

很不幸的是,在 Java 中,目前只提供了一种正则声明方式,也就是字符串形式的声明方式

String test[] = new String[]{ "//" , "////" };

String reg = "^////$" ;

for ( int i=0;i<test. length ;i++)

{

  System. out .println( " 源字符串: " + test[i] + "   匹配结果: " + Pattern.compile (reg).matcher(test[i]).find());

}

/*-------- 输出--------

源字符串:/   匹配结果:true

源字符串://   匹配结果:false

*/

只能期待 Java 的后续版本能提供这方面的优化了。

 

posted @ 2010-01-25 13:18  hibernate3例子  阅读(354)  评论(0编辑  收藏  举报