亲爱抱抱
每次看到你很快乐的样子总是让我无比欢畅
你让我看到幸福 让我看到阳光
彼此的那份甜蜜只有你我才能感觉得到
你让我忘掉悠愁 让我忘掉烦恼
你就是我最闪亮的星星
亲爱的让我紧紧的抱抱
每次都是那么地不一样
无论如何我还是喜欢这样抱着你
直到永远
Hei Baby You are my favorite
亲爱的让我再抱一抱
感觉你的温度和气息
这种感觉让我无法忘怀
让我永远陪在你的身边
posted @ 2008-09-12 16:33 李炉烽 阅读(749) 评论(2) 编辑
posted @ 2008-07-28 10:35 李炉烽 阅读(111) 评论(0) 编辑
或许大家还对为何要重写Render方法存有疑惑,希望大家看看我举的例子,能够明白Render方法和其他两个方法的作用,然后真正明白为何一般情况下只须重写Render方法

我们知道我们每次编写控件时,都需要重写Render方法,我们发现在Control类中很多方法可以重写,但我们没有去重写他们,我们需要遵循一个原则,在需要重载的时候再去重写他们

我们还是先来看看与Render方法相关的两个方法
//RenderControl方法的基本实现
 public void RenderControl(HtmlTextWriter writer)
 
{
 
if(Visible)
 
{
 Render(writer);
 }

 }

 
//Render方法基本实现
 protected virtual void Render(HtmlTextWriter writer)
 
{
 RenderChildren(writer);
 }

 
//RenderChildren方式基本实现
 protected virtual void RenderChildren(HtmlTextWriter writer)
 
{
 
foreach (Control c in Controls)
 
{
 c.RenderControl(writer);
 }

 }

相信看过"ASP.NET服务器控件开发技术与实例"这本书的人,肯定看过上面的一段代码.

假设你不理解上面的流程(我也不一定理解,希望我的思路对你有帮助),我认为有一种很好的方式来理解上面的流程,跟大家分享一下.

现在抛开上面的代码,我们来建一个简单的页面,随意的拖几个控件到界面上,注意最后一个三panel控件,如下图


图一

我们知道,每个控件都有Visible和EnableViewState属性,Visible用来设置控件是否被呈现.


图二

现在我们把button控件的Visible属性设置为flase,我们看到了我们预期的效果,接着请启用页面跟踪,这个很重要


图三

在服务器上运行这个页面,大家可以在控件树上看到下面画面


图四

(1)System.Web.UI.LiteralControl

大家可以看到,在我们定义的每个控件之间都有System.Web.UI.LiteralControl.
这里需要说明的是,要理解任何不需要在服务器上处理的任何其他字符串.

如何理解呢?大家打开这个运行页面的源代码页面,如下代码,大家看到没有,除了服务器控件外,我们有其他元素(不需要在服务器上处理的任何其他字符串),包括空格.

示例一
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
  鏃犳爣棰橀〉
</title></head>
<body>
 
<form name="form1" method="post" action="Default1.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTExNTUxMDYxODdkZHVaWm47e5anDettRKviGvS0nDWQ" />
</div>

 
<div>
 
<span id="Label1">Label</span><br />
 
<br />
 
<input name="TextBox1" type="text" id="TextBox1" /><br />
 
<br />
 
<br />
 
<br />
 
<div id="Panel1" style="height:50px;width:125px;">
  
 
</div>
 
&nbsp;</div>
 
<div>

  
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgK/5/fTBwLs0bLrBrVw7YrSp5G/l4sJGPkKN/asFj2W" />
</div></form>
</body>
</html>

为了让大家更加明白System.Web.UI.LiteralControl的意思的,让我们来修改HTML页面,说明:以上代码为运行后的HTML源代码.而不是我们所说的源代码,大家应该明白我所指的源代码的意思.

我们来修改代码,注意:我把<form..以下的标签无空格的写在了一起.看下面修改后的代码

示例二
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" Trace="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
 
<title>无标题页</title>
</head>
<body>
 
<form id="form1" runat="server"><asp:Label ID="Label1" runat="server" Text="Label"></asp:Label><asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><asp:Button ID="Button1" runat="server" Text="Button" Visible="False" /><asp:Panel ID="Panel1" runat="server" Height="50px" Width="125px"></asp:Panel></form>
</body>
</html>

运行效果


图五

现在发现控件之间已经没有System.Web.UI.LiteralControl了,因为我去掉了空格.这个也说明了一点,如果代码很乱的话会影响速度.现在大家应该明白System.Web.UI.LiteralControl的意思了吧.

(2)大家继续看图四的Button1,大家会发现它呈现的大小字节数为0,因为我们设置了Button1的Visible值为False,所以未呈现此控件.

下面我们来理解这一点,大家重新看到RenderControl方法,如果Visible值为True则呈现此控件.
if(Visible)
 Render(writer);

为了理解这个方法,我们来重写此方法,我们以第一次讲的CreditCardForm3控件为例

我们重写RenderControl方法,把Render方法的代码全部拷贝到RenderControl方法中,然后去掉Render方法

然后在asp.net页面使用此控件,定义其Visible值为False

图六

运行这个例子以后,你会发现控件还是呈现了,就是因为你重写了RenderControl方法,使控件的Visible值无效了

所以我们就要加上一个判断
if(Visible) {}


否则的话,此方法呈现的内容没有Visible值.为了更加深刻理解这一点,我们重写基类的RenderControl方法的方法.

base.RenderControl(writer);

你会发现在页面呈现时的控件有两个,一个在RenderControl方法方法输出,一个在Render方法输出,因为
base.RenderControl方法调用了Render方法,当设置控件Visible属性为False时,Render方法输出的内容被隐藏(未被呈现,而RenderControl方法输出的内容仍然存在.现在大家应该了解RenderControl方法的作用了吧.

如果服务器控件的 Visible 属性设置为 true,则向页呈现服务器控件的内容,所以一般情况下我们不重写此方法.因为一般控件都需要Visible 属性,除非特殊情况.
图七

(3)RenderChildren方法

再重新看到图四,大家可以看到,我们拖放的控件是在属于form1的子控件,panel控件是一个容器控件,因为下面没拖放控件,任何其他显示的字符串表现为System.Web.UI.LiteralControl,大家可以拖几个控件到panel里再重新运行看看,会发现拖进去的控件变为panel的子控件.最明显的的测试方法是Wizard控件,拖放一个Wizard控件然后再测试你就会明白了.

RenderChildren方法则判断当前控件是否有子控件,如果有,则根据RenderControl方法判断控件的Visible值来呈现控件.所以大家在重写Render方法时,不重写基类Render方法时,将无法实现RenderChildren方法.带来的后果将是无法呈现子控件.

下面我们来测试一下.我们还是以CreditCardForm3控件为例子(请先把RenderControl方法的内容全注释掉),当未重实现RenderChildren方法时则无法呈现子控件内容,请启动跟踪

将发现其子控件呈现字节为0

图八

由于CreditCardForm3继承了CreditCardForm2,所以重写基类Render方法将会重复输出,我们可以直接在Render方法中重写RenderChildren方法.再来测试.将会发现有些变化

发现其子控件呈现字节并非为0,而是10

图九

说明其子控件还是存在东西的,只不过没有用而已,所以大家可以根据实际需求来确实是否要重写RenderChildren方法,一般的话都会重写Render方法,这样保险一点

好了,现在再来回顾下刚开始给出的代码,通过上面的试验,你是否明白了?

呈现控件的步骤(注意:下面三个方法都可以呈现,不过我们已经说过了,像在RenderControl方法用HtmlTextWriter预先输出的话,就丧失Visible的功能(说不定你就不需要这个功能,那时你就可以重写这个方法了)

(1)RenderControl方法

先判断其Visible然后调用Render方法

(2) Render方法

使用HtmlTextWriter将标记字符和文本输出然后调用RenderChildren方法

(3)RenderChildren方法

判断当前控件是否有子控件,然后再调用RenderControl方法根据子控件的Visible值输出子控件.

我们了解上面三个方法后,就会知道,一般情况下,我们无须重写RenderControl方法和RenderChildren方法.所以最合适的就是重写Render方法了.说了一大堆.目的就是为了说明为什么要重写Render方法.

上次,忘了把代码传上了,不小心只上传了dll文件,不好意思.这次就写这么多.希望大家能够真正明白.大家可以适当的修改代码,这样你会发现更多.

参考文章:ASP.NET2.0服务器控件之Render方法
posted @ 2008-07-28 10:28 李炉烽 阅读(109) 评论(0) 编辑


2008/6/28 上午 12:50:00

很抱歉,本站不歡迎來自 [百度] (Baidu.com) 的訪客 !!

543 | 不爽 | 技術隨筆

沒什麼特別的,只是針對這次的盜文事件,我很不滿意百度 (Baidu.com) 的處理方式而以。

幾個月前曾發生過 BLOGGER.COM 有人直接把我的文章一字不漏的貼在他的 BLOG 上,沒有標示文章出處,最後用我破破的英文跟 GOOGLE 反映之後,GOOGLE 立即作了處理,關閉那位使用者的網頁

這次碰到類似的情況,有耐心的人就聽我講完這無聊的故事吧。無意間我在對岸的入口網站 [百度知道] (類似奇摩知識+的網站),發現有人拿我的文章,一字不漏的貼上去回答問題賺點數,一樣沒有標示文章出處,感到非常的不滿,馬上註冊了帳號,留下回應表示該文侵犯了我的權益,要求引用文章要註明出處,同時也跟站方反應了這個情況,要求站方作妥善的處理。

原本以為事情會像上次一樣,跟 GOOGLE 一樣的處理方式結束。沒料到...

  1. 隔天,發現我的留言被刪了? 嗯... 再補一次。
  2. 跟站方反應的結果? 竟然說這個不尊重智財權的使用者沒有違反規定???? 所以不做任何處理。
  3. 另一方面,留言留了不斷的被刪除,到現在已經被刪了第五次了,第六次的留言不知道會留到什麼時後...。

很無聊的戲就這樣一直演下去... 就是不斷的抗議又被刪除,跟站方反應卻又不理睬...。看來小蝦米是對抗不了大鯨魚的,也只能這樣了。其實我除了文章被盜貼之外沒有什麼具體的損失,就是心理很不爽而以,而更離譜的是百度站方處理的態度...。

資訊隨手可得,不代表資訊是可以任意踐踏的。免費的資訊,不用付費不代表就不需要尊重,也許對岸還有很多使用者沒建立起這樣的觀念,但是百度站方的處理方式也令我跌破眼鏡,有這樣的站方難怪會縱容這樣的使用者... :@

身為渺小不起眼 BLOG 主人,我也只能用消極的抗議,來表示我的不滿。除了寫這篇文章以外,也順帶來個 ASP.NET HttpModule 教學...。針對這次事件,我特地在本站加上了這個 HttpModule,只要查出使用者是透過任何由百度提供的 LINK 而連到本站的話,都會顯示這頁抗議的畫面,如下:

image

顯示了 60 秒抗議畫面後,就會自動進如原本要連結的頁面。在透過正規的管道而得不到妥善的處置,我也只能用消極的抗意來表達我的不滿。請看到的人留個 MESSAGE 支持一下吧,或是有推推王帳號的人也幫忙推一下,一起對不重視智慧財產權的百度表答不滿 & 抗議!

 

抗議之餘,本站再怎樣也是討論進階 .NET 技術的網站,就拿這次的案例,看看這樣的 HttpModule 該怎麼處理! 未來如果你也不幸碰到這樣的事件 (最好不要碰到),可以拿出來用一用! 要替網站加上這樣的功能很簡單,只要在 Web.config 把你寫的 HttpModule 掛上就好。一旦掛上,所有針對這個網站的 Http Request 都會經過你的 HttpModule 處理,任何一個 LINK 都跑不掉!

 

ASP.NET HttpModule開發範例: 把所有來自百度的使用者,引導到抗議的畫面![copy code]
public class SiteBlockerHttpModule : IHttpModule{    public void Init(HttpApplication context)    {        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);    }    void context_AuthenticateRequest(object sender, EventArgs e)    {        HttpApplication application = sender as HttpApplication;        string referer = application.Context.Request.ServerVariables["HTTP_REFERER"];        if (string.IsNullOrEmpty(referer) == false)        {            Uri refererURL = new Uri(referer);            if (refererURL.Host.ToUpperInvariant().Contains("BAIDU.COM") == true)            {                application.Context.Server.Transfer("~/Blogs/ShowBlockedMessage.aspx");            }        }    }}
    
   1:  public class SiteBlockerHttpModule : IHttpModule
    
   2:  {
    
   3:      public void Init(HttpApplication context)
    
   4:      {
    
   5:          context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    
   6:      }
    
   7:   
    
   8:      void context_AuthenticateRequest(object sender, EventArgs e)
    
   9:      {
    
  10:          HttpApplication application = sender as HttpApplication;
    
  11:          string referer = application.Context.Request.ServerVariables["HTTP_REFERER"];
    
  12:   
    
  13:          if (string.IsNullOrEmpty(referer) == false)
    
  14:          {
    
  15:              Uri refererURL = new Uri(referer);
    
  16:              if (refererURL.Host.ToUpperInvariant().Contains("BAIDU.COM") == true)
    
  17:              {
    
  18:                  application.Context.Server.Transfer("~/Blogs/ShowBlockedMessage.aspx");
    
  19:              }
    
  20:          }
    
  21:      }
    
  22:  }
    

 

 

 

 

--

後記: 針對這次事件的記錄:

  1. 2008‎年‎6‎月‎22‎日, ‏‎下午 11:59:16,第一次張貼抗議的回應 
  2. 2008‎年‎6‎月‎23‎日, ‏‎上午 11:44:50,第二次張貼抗議的回應 
  3. 2008‎年‎6‎月‎23‎日, ‏‎下午 04:53:44,第三次張貼抗議的回應 
  4. ‎2008‎年‎6‎月‎23‎日, ‏‎下午 11:32:05,第四次張貼抗議的回應 
  5. ‎2008‎年‎6‎月‎25‎日, ‏‎下午 08:08:14,第五次張貼抗議的回應 
  6. 2008‎年‎6‎月‎26‎日, ‏‎下午 07:26:36,第六次張貼抗議的回應 
  7. ‎2008‎年‎6‎月‎28‎日, ‏‎上午 12:58:54,百度站方的回應
     


評論

2008/6/30 下午 12:10:52

DanLee

百度一向都很垃圾的。用百度的都是低端用户。到纳斯达克圈了钱就NBHH了。

--
問: 7 x 3 = ?

DanLee cn

2008/7/2 下午 04:47:37

chicken

用戶本來就有好有壞, 就算是 GOOGLE 也是一樣.
不過站方放著不管就很離譜了.

--
借問一下, NBHH 是啥意思? 猜不出來..

--
請輸入: "年年行大運"

chicken tw

2008/7/4 上午 02:18:02

DanLee

呵呵,搞IT的人怎么忘了去google上搜这个关键词 “nbhh 什么意思”
虽然我也是搞软件的,但是对于这些搜索网站或者什么门户网站的盈利模式真的还不懂。靠个卖个关键词,排个序就赚钱,而且还很贵,那以后问路都要要钱了。这些网站还往往被风险投资眷顾,真是搞不懂。 “哥们,去前门怎么走啊”“告诉你免费的路线要走三个小时,走一个小时的路线要10块,走2分钟能到的路线要100块,你看着办吧。。。”

--
請輸入: "叭樂雞萬歲"

DanLee cn

2008/7/6 上午 03:23:50

sea

以後寫文章前可在最前頭標明"擅自轉載者是小孬孬喔!"...不然也可以寫"陳水扁萬歲或支持台灣獨立"之類的...Tong


--
問: 3 + 1 = ?

sea tw

2008/7/6 上午 04:20:08

chicken

哈,我開始想把底下的國旗換成綠色的那一面... Tong

--
問: 8 - 7 = ?

chicken tw

新增評論


(將顯示您的Gravatar圖示)

(請輸入: "海娘娘永遠是對的")
  Country flag




 

大家对此有什么看法的话请随时评论,各抒己见
posted @ 2008-07-28 09:48 李炉烽 阅读(280) 评论(1) 编辑
posted @ 2008-07-27 17:37 李炉烽 阅读(147) 评论(2) 编辑
posted @ 2008-07-27 17:25 李炉烽 阅读(364) 评论(0) 编辑
posted @ 2008-07-15 11:17 李炉烽 阅读(264) 评论(4) 编辑
posted @ 2008-07-14 18:10 李炉烽 阅读(31) 评论(0) 编辑
9.      单词边界

元字符<<"b>>也是一种对位置进行匹配的“锚”。这种匹配是0长度匹配。

4种位置被认为是“单词边界”:

1)        在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”)

2)        在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”)

3)        在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后

4)        在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面

 “单词字符”是可以用“"w”匹配的字符,“非单词字符”是可以用“"W”匹配的字符。在大多数的正则表达式实现中,“单词字符”通常包括<<[a-zA-Z0-9_]>>

例如:<<"b4"b>>能够匹配单个的4而不是一个更大数的一部分。这个正则表达式不会匹配“44”中的4

换种说法,几乎可以说<<"b>>匹配一个“字母数字序列”的开始和结束的位置。

“单词边界”的取反集为<<"B>>,他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。

·        深入正则表达式引擎内部

让我们看看把正则表达式<<"bis"b>>应用到字符串“This island is beautiful”。引擎先处理符号<<"b>>。因为"b0长度,所以第一个字符T前面的位置会被考察。因为T是一个“单词字符”,而它前面的字符是一个空字符(void),所以"b匹配了单词边界。接着<<i>>和第一个字符“T”匹配失败。匹配过程继续进行,直到第五个空格符,和第四个字符“s”之间又匹配了<<"b>>。然而空格符和<<i>>不匹配。继续向后,到了第六个字符“i”,和第五个空格字符之间匹配了<<"b>>,然后<<is>>和第六、第七个字符都匹配了。然而第八个字符和第二个“单词边界”不匹配,所以匹配又失败了。到了第13个字符i,因为和前面一个空格符形成“单词边界”,同时<<is>>和“is”匹配。引擎接着尝试匹配第二个<<"b>>。因为第15个空格符和“s”形成单词边界,所以匹配成功。引擎“急着”返回成功匹配的结果。

10. 选择符

正则表达式中“|”表示选择。你可以用选择符匹配多个可能的正则表达式中的一个。

如果你想搜索文字“cat”或“dog”,你可以用<<cat|dog>>。如果你想有更多的选择,你只要扩展列表<<cat|dog|mouse|fish>>

选择符在正则表达式中具有最低的优先级,也就是说,它告诉引擎要么匹配选择符左边的所有表达式,要么匹配右边的所有表达式。你也可以用圆括号来限制选择符的作用范围。如<<"b(cat|dog)"b>>,这样告诉正则引擎把(cat|dog)当成一个正则表达式单位来处理。

·        注意正则引擎的“急于表功”性

正则引擎是急切的,当它找到一个有效的匹配时,它会停止搜索。因此在一定条件下,选择符两边的表达式的顺序对结果会有影响。假设你想用正则表达式搜索一个编程语言的函数列表:GetGetValueSetSetValue。一个明显的解决方案是<<Get|GetValue|Set|SetValue>>。让我们看看当搜索SetValue时的结果。

因为<<Get>><<GetValue>>都失败了,而<<Set>>匹配成功。因为正则导向的引擎都是“急切”的,所以它会返回第一个成功的匹配,就是“Set”,而不去继续搜索是否有其他更好的匹配。

和我们期望的相反,正则表达式并没有匹配整个字符串。有几种可能的解决办法。一是考虑到正则引擎的“急切”性,改变选项的顺序,例如我们使用<<GetValue|Get|SetValue|Set>>,这样我们就可以优先搜索最长的匹配。我们也可以把四个选项结合起来成两个选项:<<Get(Value)?|Set(Value)?>>。因为问号重复符是贪婪的,所以SetValue总会在Set之前被匹配。

一个更好的方案是使用单词边界:<<"b(Get|GetValue|Set|SetValue)"b>><<"b(Get(Value)?|Set(Value)?"b>>。更进一步,既然所有的选择都有相同的结尾,我们可以把正则表达式优化为<<"b(Get|Set)(Value)?"b>>

11. 组与向后引用

把正则表达式的一部分放在圆括号内,你可以将它们形成组。然后你可以对整个组使用一些正则操作,例如重复操作符。

要注意的是,只有圆括号“()”才能用于形成组。“[]”用于定义字符集。“{}”用于定义重复操作。

当用“()”定义了一个正则表达式组后,正则引擎则会把被匹配的组按照顺序编号,存入缓存。当对被匹配的组进行向后引用的时候,可以用“"数字”的方式进行引用。<<"1>>引用第一个匹配的后向引用组,<<"2>>引用第二个组,以此类推,<<"n>>引用第n个组。而<<"0>>则引用整个被匹配的正则表达式本身。我们看一个例子。

假设你想匹配一个HTML标签的开始标签和结束标签,以及标签中间的文本。比如<B>This is a test</B>,我们要匹配<B></B>以及中间的文字。我们可以用如下正则表达式:“<([A-Z][A-Z0-9]*)[^>]*>.*?</"1>

首先,“<”将会匹配“<B>”的第一个字符“<”。然后[A-Z]匹配B[A-Z0-9]*将会匹配0到多次字母数字,后面紧接着0到多个非“>”的字符。最后正则表达式的“>”将会匹配“<B>”的“>”。接下来正则引擎将对结束标签之前的字符进行惰性匹配,直到遇到一个“</”符号。然后正则表达式中的“"1”表示对前面匹配的组“([A-Z][A-Z0-9]*)”进行引用,在本例中,被引用的是标签名“B”。所以需要被匹配的结尾标签为“</B>

你可以对相同的后向引用组进行多次引用,<<([a-c])x"1x"1>>将匹配“axaxa”、“bxbxb”以及“cxcxc”。如果用数字形式引用的组没有有效的匹配,则引用到的内容简单的为空。

一个后向引用不能用于它自身。<<([abc]"1)>>是错误的。因此你不能将<<"0>>用于一个正则表达式匹配本身,它只能用于替换操作中。

后向引用不能用于字符集内部。<<(a)["1b]>>中的<<"1>>并不表示后向引用。在字符集内部,<<"1>>可以被解释为八进制形式的转码。

向后引用会降低引擎的速度,因为它需要存储匹配的组。如果你不需要向后引用,你可以告诉引擎对某个组不存储。例如:<<Get(?:Value)>>。其中“(”后面紧跟的“?:”会告诉引擎对于组(Value),不存储匹配的值以供后向引用。

·        重复操作与后向引用

当对组使用重复操作符时,缓存里后向引用内容会被不断刷新,只保留最后匹配的内容。例如:<<([abc]+)="1>>将匹配“cab=cab”,但是<<([abc])+="1>>却不会。因为([abc])第一次匹配“c”时,“"1”代表“c”;然后([abc])会继续匹配“a”和“b”。最后“"1”代表“b”,所以它会匹配“cab=b”。

应用:检查重复单词--当编辑文字时,很容易就会输入重复单词,例如“the the”。使用<<"b("w+)"s+"1"b>>可以检测到这些重复单词。要删除第二个单词,只要简单的利用替换功能替换掉“"1”就可以了。

·        组的命名和引用

PHPPython中,可以用<<(?P<name>group)>>来对组进行命名。在本例中,词法?P<name>就是对组(group)进行了命名。其中name是你对组的起的名字。你可以用(?P=name)进行引用。

.NET的命名组

.NET framework也支持命名组。不幸的是,微软的程序员们决定发明他们自己的语法,而不是沿用PerlPython的规则。目前为止,还没有任何其他的正则表达式实现支持微软发明的语法。

下面是.NET中的例子:

(?<first>group)(?’second’group)

正如你所看到的,.NET提供两种词法来创建命名组:一是用尖括号“<>”,或者用单引号“’’”。尖括号在字符串中使用更方便,单引号在ASP代码中更有用,因为ASP代码中“<>”被用作HTML标签。

要引用一个命名组,使用"k<name>"k’name’.

当进行搜索替换时,你可以用“${name}”来引用一个命名组。

12. 正则表达式的匹配模式

本教程所讨论的正则表达式引擎都支持三种匹配模式:

<</i>>使正则表达式对大小写不敏感,

<</s>>开启“单行模式”,即点号“.”匹配新行符

<</m>>开启“多行模式”,即“^”和“$”匹配新行符的前面和后面的位置。

·        在正则表达式内部打开或关闭模式

如果你在正则表达式内部插入修饰符(?ism),则该修饰符只对其右边的正则表达式起作用。(?-i)是关闭大小写不敏感。你可以很快的进行测试。<<(?i)te(?-i)st>>应该匹配TEst,但是不能匹配teSTTEST.

13. 原子组与防止回溯

在一些特殊情况下,因为回溯会使得引擎的效率极其低下。

让我们看一个例子:要匹配这样的字串,字串中的每个字段间用逗号做分隔符,第12个字段由P开头。

我们容易想到这样的正则表达式<<^(.*?,){11}P>>。这个正则表达式在正常情况下工作的很好。但是在极端情况下,如果第12个字段不是由P开头,则会发生灾难性的回溯。如要搜索的字串为“1,2,3,4,5,6,7,8,9,10,11,12,13”。首先,正则表达式一直成功匹配直到第12个字符。这时,前面的正则表达式消耗的字串为“1,2,3,4,5,6,7,8,9,10,11,”,到了下一个字符,<<P>>并不匹配“12”。所以引擎进行回溯,这时正则表达式消耗的字串为“1,2,3,4,5,6,7,8,9,10,11”。继续下一次匹配过程,下一个正则符号为点号<<.>>,可以匹配下一个逗号“,”。然而<<>>并不匹配字符“12”中的“1”。匹配失败,继续回溯。大家可以想象,这样的回溯组合是个非常大的数量。因此可能会造成引擎崩溃。

用于阻止这样巨大的回溯有几种方案:

一种简单的方案是尽可能的使匹配精确。用取反字符集代替点号。例如我们用如下正则表达式<<^([^,"r"n]*,){11}P>>,这样可以使失败回溯的次数下降到11次。

另一种方案是使用原子组。

原子组的目的是使正则引擎失败的更快一点。因此可以有效的阻止海量回溯。原子组的语法是<<(?>正则表达式)>>。位于(?>)之间的所有正则表达式都会被认为是一个单一的正则符号。一旦匹配失败,引擎将会回溯到原子组前面的正则表达式部分。前面的例子用原子组可以表达成<<^(?>(.*?,){11})P>>。一旦第十二个字段匹配失败,引擎回溯到原子组前面的<<^>>

14. 向前查看与向后查看

Perl 5 引入了两个强大的正则语法:“向前查看”和“向后查看”。他们也被称作“零长度断言”。他们和锚定一样都是零长度的(所谓零长度即指该正则表达式不消耗被匹配的字符串)。不同之处在于“前后查看”会实际匹配字符,只是他们会抛弃匹配只返回匹配结果:匹配或不匹配。这就是为什么他们被称作“断言”。他们并不实际消耗字符串中的字符,而只是断言一个匹配是否可能。

几乎本文讨论的所有正则表达式的实现都支持“向前向后查看”。唯一的一个例外是Javascript只支持向前查看。

·        肯定和否定式的向前查看

如我们前面提过的一个例子:要查找一个q,后面没有紧跟一个u。也就是说,要么q后面没有字符,要么后面的字符不是u。采用否定式向前查看后的一个解决方案为<<q(?!u)>>。否定式向前查看的语法是<<(?!查看的内容)>>

肯定式向前查看和否定式向前查看很类似:<<(?=查看的内容)>>

如果在“查看的内容”部分有组,也会产生一个向后引用。但是向前查看本身并不会产生向后引用,也不会被计入向后引用的编号中。这是因为向前查看本身是会被抛弃掉的,只保留匹配与否的判断结果。如果你想保留匹配的结果作为向后引用,你可以用<<(?=(regex))>>来产生一个向后引用。

·        肯定和否定式的先后查看

向后查看和向前查看有相同的效果,只是方向相反

否定式向后查看的语法是:<<(?<!查看内容)>>

肯定式向后查看的语法是:<<(?<=查看内容)>>

我们可以看到,和向前查看相比,多了一个表示方向的左尖括号。

例:<<(?<!a)b>>将会匹配一个没有“a”作前导字符的“b”。

值得注意的是:向前查看从当前字符串位置开始对“查看”正则表达式进行匹配;向后查看则从当前字符串位置开始先后回溯一个字符,然后再开始对“查看”正则表达式进行匹配。

·        深入正则表达式引擎内部

让我们看一个简单例子。

把正则表达式<<q(?!u)>>应用到字符串“Iraq”。正则表达式的第一个符号是<<q>>。正如我们知道的,引擎在匹配<<q>>以前会扫过整个字符串。当第四个字符“q”被匹配后,“q”后面是空字符(void)。而下一个正则符号是向前查看。引擎注意到已经进入了一个向前查看正则表达式部分。下一个正则符号是<<u>>,和空字符不匹配,从而导致向前查看里的正则表达式匹配失败。因为是一个否定式的向前查看,意味着整个向前查看结果是成功的。于是匹配结果“q”被返回了。

我们在把相同的正则表达式应用到“quit”。<<q>>匹配了“q”。下一个正则符号是向前查看部分的<<u>>,它匹配了字符串中的第二个字符“i”。引擎继续走到下个字符“i”。然而引擎这时注意到向前查看部分已经处理完了,并且向前查看已经成功。于是引擎抛弃被匹配的字符串部分,这将导致引擎回退到字符“u”。

因为向前查看是否定式的,意味着查看部分的成功匹配导致了整个向前查看的失败,因此引擎不得不进行回溯。最后因为再没有其他的“q”和<<q>>匹配,所以整个匹配失败了。

为了确保你能清楚地理解向前查看的实现,让我们把<<q(?=u)i>>应用到“quit”。<<q>>首先匹配“q”。然后向前查看成功匹配“u”,匹配的部分被抛弃,只返回可以匹配的判断结果。引擎从字符“i”回退到“u”。由于向前查看成功了,引擎继续处理下一个正则符号<<i>>。结果发现<<i>>和“u”不匹配。因此匹配失败了。由于后面没有其他的“q”,整个正则表达式的匹配失败了。

·        更进一步理解正则表达式引擎内部机制

让我们把<<(?<=a)b>>应用到“thingamabob”。引擎开始处理向后查看部分的正则符号和字符串中的第一个字符。在这个例子中,向后查看告诉正则表达式引擎回退一个字符,然后查看是否有一个“a”被匹配。因为在“t”前面没有字符,所以引擎不能回退。因此向后查看失败了。引擎继续走到下一个字符“h”。再一次,引擎暂时回退一个字符并检查是否有个“a”被匹配。结果发现了一个“t”。向后查看又失败了。

向后查看继续失败,直到正则表达式到达了字符串中的“m”,于是肯定式的向后查看被匹配了。因为它是零长度的,字符串的当前位置仍然是“m”。下一个正则符号是<<b>>,和“m”匹配失败。下一个字符是字符串中的第二个“a”。引擎向后暂时回退一个字符,并且发现<<a>>不匹配“m”。

在下一个字符是字符串中的第一个“b”。引擎暂时性的向后退一个字符发现向后查看被满足了,同时<<b>>匹配了“b”。因此整个正则表达式被匹配了。作为结果,正则表达式返回字符串中的第一个“b”。

·        向前向后查看的应用

我们来看这样一个例子:查找一个具有6位字符的,含有“cat”的单词。

首先,我们可以不用向前向后查看来解决问题,例如:

<< cat"w{3}|"wcat"w{2}|"w{2}cat"w|"w{3}cat>>

足够简单吧!但是当需求变成查找一个具有6-12位字符,含有“cat”,“dog”或“mouse”的单词时,这种方法就变得有些笨拙了。

我们来看看使用向前查看的方案。在这个例子中,我们有两个基本需求要满足:一是我们需要一个6位的字符,二是单词含有“cat”。

满足第一个需求的正则表达式为<<"b"w{6}"b>>。满足第二个需求的正则表达式为<<"b"w*cat"w*"b>>

把两者结合起来,我们可以得到如下的正则表达式:

     <<(?="b"w{6}"b)"b"w*cat"w*"b>>

具体的匹配过程留给读者。但是要注意的一点是,向前查看是不消耗字符的,因此当判断单词满足具有6个字符的条件后,引擎会从开始判断前的位置继续对后面的正则表达式进行匹配。

最后作些优化,可以得到下面的正则表达式:

<<"b(?="w{6}"b)"w{0,3}cat"w*>>

15. 正则表达式中的条件测试

条件测试的语法为<<(?ifthen|else)>>。“if”部分可以是向前向后查看表达式。如果用向前查看,则语法变为:<<(?(?=regex)then|else)>>,其中else部分是可选的。

如果if部分为true,则正则引擎会试图匹配then部分,否则引擎会试图匹配else部分。

需要记住的是,向前先后查看并不实际消耗任何字符,因此后面的thenelse部分的匹配时从if测试前的部分开始进行尝试。

16. 为正则表达式添加注释

在正则表达式中添加注释的语法是:<<(?#comment)>>

例:为用于匹配有效日期的正则表达式添加注释:

 (?#year)(19|20)"d"d[- /.](?#month)(0[1-9]|1[012])[- /.](?#day)(0[1-9]|[12][0-9]|3[01])

posted @ 2008-07-14 18:08 李炉烽 阅读(33) 评论(0) 编辑

前言:
       半年前我对正则表达式产生了兴趣,在网上查找过不少资料,看过不少的教程,最后在使用一个正则表达式工具RegexBuddy时发现他的教程写的非常好,可以说是我目前见过最好的正则表达式教程。于是一直想把他翻译过来。这个愿望直到这个五一长假才得以实现,结果就有了这篇文章。关于本文的名字,使用深入浅出似乎已经太俗。但是通读原文以后,觉得只有用深入浅出才能准确的表达出该教程给我的感受,所以也就不能免俗了。
       本文是Jan GoyvaertsRegexBuddy写的教程的译文,版权归原作者所有,欢迎转载。但是为了尊重原作者和译者的劳动,请注明出处!谢谢!

 

1.      什么是正则表达式

基本说来,正则表达式是一种用来描述一定数量文本的模式。Regex代表Regular Express。本文将用<<regex>>来表示一段具体的正则表达式。

一段文本就是最基本的模式,简单的匹配相同的文本。

2.      不同的正则表达式引擎

正则表达式引擎是一种可以处理正则表达式的软件。通常,引擎是更大的应用程序的一部分。在软件世界,不同的正则表达式并不互相兼容。本教程会集中讨论Perl 5 类型的引擎,因为这种引擎是应用最广泛的引擎。同时我们也会提到一些和其他引擎的区别。许多近代的引擎都很类似,但不完全一样。例如.NET正则库,JDK正则包。

3.      文字符号

最基本的正则表达式由单个文字符号组成。如<<a>>,它将匹配字符串中第一次出现的字符“a”。如对字符串“Jack is a boy”。“J”后的“a”将被匹配。而第二个“a”将不会被匹配。

正则表达式也可以匹配第二个“a”,这必须是你告诉正则表达式引擎从第一次匹配的地方开始搜索。在文本编辑器中,你可以使用“查找下一个”。在编程语言中,会有一个函数可以使你从前一次匹配的位置开始继续向后搜索。

类似的,<<cat>>会匹配“About cats and dogs”中的“cat”。这等于是告诉正则表达式引擎,找到一个<<c>>,紧跟一个<<a>>,再跟一个<<t>>

要注意,正则表达式引擎缺省是大小写敏感的。除非你告诉引擎忽略大小写,否则<<cat>>不会匹配“Cat”。

·        特殊字符

对于文字字符,有11个字符被保留作特殊用途。他们是:

[ ] " ^ $ . | ? * + ( )

这些特殊字符也被称作元字符。

如果你想在正则表达式中将这些字符用作文本字符,你需要用反斜杠“"”对其进行换码(escape)。例如你想匹配“1+1=2”,正确的表达式为<<1"+1=2>>.

需要注意的是,<<1+1=2>>也是有效的正则表达式。但它不会匹配“1+1=2”,而会匹配“123+111=234”中的“111=2”。因为“+”在这里表示特殊含义(重复1次到多次)。

在编程语言中,要注意,一些特殊的字符会先被编译器处理,然后再传递给正则引擎。因此正则表达式<<1"+2=2>>C++中要写成“1""+1=2”。为了匹配“C:"temp”,你要用正则表达式<<C:""temp>>。而在C++中,正则表达式则变成了“C:""""temp”。

·        不可显示字符

可以使用特殊字符序列来代表某些不可显示字符:

<<"t>>代表Tab(0x09)

<<"r>>代表回车符(0x0D)

<<"n>>代表换行符(0x0A)

要注意的是Windows中文本文件使用“"r"n”来结束一行而Unix使用“"n”。

4.      正则表达式引擎的内部工作机制

知道正则表达式引擎是如何工作的有助于你很快理解为何某个正则表达式不像你期望的那样工作。

有两种类型的引擎:文本导向(text-directed)的引擎和正则导向(regex-directed)的引擎。Jeffrey Friedl把他们称作DFANFA引擎。本文谈到的是正则导向的引擎。这是因为一些非常有用的特性,如“惰性”量词(lazy quantifiers)和反向引用(backreferences),只能在正则导向的引擎中实现。所以毫不意外这种引擎是目前最流行的引擎。

你可以轻易分辨出所使用的引擎是文本导向还是正则导向。如果反向引用或“惰性”量词被实现,则可以肯定你使用的引擎是正则导向的。你可以作如下测试:将正则表达式<<regex|regex not>>应用到字符串“regex not”。如果匹配的结果是regex,则引擎是正则导向的。如果结果是regex not,则是文本导向的。因为正则导向的引擎是“猴急”的,它会很急切的进行表功,报告它找到的第一个匹配

·        正则导向的引擎总是返回最左边的匹配

这是需要你理解的很重要的一点:即使以后有可能发现一个“更好”的匹配,正则导向的引擎也总是返回最左边的匹配。

当把<<cat>>应用到“He captured a catfish for his cat”,引擎先比较<<c>>和“H”,结果失败了。于是引擎再比较<<c>>和“e”,也失败了。直到第四个字符,<<c>>匹配了“c”。<<a>>匹配了第五个字符。到第六个字符<<t>>没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配。

5.      字符集

字符集是由一对方括号“[]”括起来的字符集合。使用字符集,你可以告诉正则表达式引擎仅仅匹配多个字符中的一个。如果你想匹配一个“a”或一个“e”,使用<<[ae]>>。你可以使用<<gr[ae]y>>匹配graygrey。这在你不确定你要搜索的字符是采用美国英语还是英国英语时特别有用。相反,<<gr[ae]y>>将不会匹配graaygraey。字符集中的字符顺序并没有什么关系,结果都是相同的。

你可以使用连字符“-”定义一个字符范围作为字符集。<<[0-9]>>匹配09之间的单个数字。你可以使用不止一个范围。<<[0-9a-fA-F] >>匹配单个的十六进制数字,并且大小写不敏感。你也可以结合范围定义与单个字符定义。<<[0-9a-fxA-FX]>>匹配一个十六进制数字或字母X。再次强调一下,字符和范围定义的先后顺序对结果没有影响。

·        字符集的一些应用

查找一个可能有拼写错误的单词,比如<<sep[ae]r[ae]te>> <<li[cs]en[cs]e>>

查找程序语言的标识符,<<A-Za-z_][A-Za-z_0-9]*>>(*表示重复0或多次)

查找C风格的十六进制数<<0[xX][A-Fa-f0-9]+>>(+表示重复一次或多次)

·        取反字符集

在左方括号“[”后面紧跟一个尖括号“^”,将会对字符集取反。结果是字符集将匹配任何不在方括号中的字符。不像“.”,取反字符集是可以匹配回车换行符的。

需要记住的很重要的一点是,取反字符集必须要匹配一个字符。<<q[^u]>>并不意味着:匹配一个q,后面没有u跟着。它意味着:匹配一个q,后面跟着一个不是u的字符。所以它不会匹配“Iraq”中的q,而会匹配“Iraq is a country”中的q和一个空格符。事实上,空格符是匹配中的一部分,因为它是一个“不是u的字符”。

如果你只想匹配一个q,条件是q后面有一个不是u的字符,我们可以用后面将讲到的向前查看来解决。

·        字符集中的元字符

需要注意的是,在字符集中只有4字符具有特殊含义。它们是:“] " ^ -”。“]”代表字符集定义的结束;“"”代表转义;“^”代表取反;“-”代表范围定义。其他常见的元字符在字符集定义内部都是正常字符,不需要转义。例如,要搜索星号*或加号+,你可以用<<[+*]>>。当然,如果你对那些通常的元字符进行转义,你的正则表达式一样会工作得很好,但是这会降低可读性。

在字符集定义中为了将反斜杠“"”作为一个文字字符而非特殊含义的字符,你需要用另一个反斜杠对它进行转义。<<[""x]>>将会匹配一个反斜杠和一个X。“]^-”都可以用反斜杠进行转义,或者将他们放在一个不可能使用到他们特殊含义的位置。我们推荐后者,因为这样可以增加可读性。比如对于字符“^”,将它放在除了左括号“[”后面的位置,使用的都是文字字符含义而非取反含义。如<<[x^]>>会匹配一个x^<<[]x]>>会匹配一个“]”或“x”。<<[-x]>><<[x-]>>都会匹配一个“-”或“x”。

·        字符集的简写

因为一些字符集非常常用,所以有一些简写方式。

<<"d>>代表<<[0-9]>>;

<<"w>>代表单词字符。这个是随正则表达式实现的不同而有些差异。绝大多数的正则表达式实现的单词字符集都包含了<<A-Za-z0-9_]>>

<<"s>>代表“白字符”。这个也是和不同的实现有关的。在绝大多数的实现中,都包含了空格符和Tab符,以及回车换行符<<"r"n>>

字符集的缩写形式可以用在方括号之内或之外。<<"s"d>>匹配一个白字符后面紧跟一个数字。<<["s"d]>>匹配单个白字符或数字。<<["da-fA-F]>>将匹配一个十六进制数字。

取反字符集的简写

<<["S]>> = <<[^"s]>>

<<["W]>> = <<[^"w]>>

<<["D]>> = <<[^"d]>>

·        字符集的重复

如果你用“?*+”操作符来重复一个字符集,你将会重复整个字符集。而不仅是它匹配的那个字符。正则表达式<<[0-9]+>>会匹配837以及222

如果你仅仅想重复被匹配的那个字符,可以用向后引用达到目的。我们以后将讲到向后引用。

6.      使用?*+ 进行重复

?:告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。

+:告诉引擎匹配前导字符1次或多次

*:告诉引擎匹配前导字符0次或多次

<[A-Za-z][A-Za-z0-9]*>匹配没有属性的HTML标签,“<”以及“>”是文字符号。第一个字符集匹配一个字母,第二个字符集匹配一个字母或数字。

我们似乎也可以用<[A-Za-z0-9]+>。但是它会匹配<1>。但是这个正则表达式在你知道你要搜索的字符串不包含类似的无效标签时还是足够有效的。

·        限制性重复

许多现代的正则表达式实现,都允许你定义对一个字符重复多少次。词法是:{min,max}minmax都是非负整数。如果逗号有而max被忽略了,则max没有限制。如果逗号和max都被忽略了,则重复min次。

因此{0,}*一样,{1}+ 的作用一样。

你可以用<<"b[1-9][0-9]{3}"b>>匹配1000~9999之间的数字("b”表示单词边界)<<"b[1-9][0-9]{2,4}"b>>匹配一个在100~99999之间的数字。

·        注意贪婪性

假设你想用一个正则表达式匹配一个HTML标签。你知道输入将会是一个有效的HTML文件,因此正则表达式不需要排除那些无效的标签。所以如果是在两个尖括号之间的内容,就应该是一个HTML标签。

许多正则表达式的新手会首先想到用正则表达式<< <.+> >>,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能期望会返回<EM>,然后继续进行匹配的时候,返回</EM>

但事实是不会。正则表达式将会匹配“<EM>first</EM>”。很显然这不是我们想要的结果。原因在于“+”是贪婪的。也就是说,“+”会导致正则表达式引擎试图尽可能的重复前导字符。只有当这种重复会引起整个正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,然后处理正则表达式余下的部分。

和“+”类似,“?*”的重复也是贪婪的。

·        深入正则表达式引擎内部

让我们来看看正则引擎如何匹配前面的例子。第一个记号是“<”,这是一个文字符号。第二个符号是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的结束。然后到了换行符,匹配失败(.”不匹配换行符)。于是引擎开始对下一个正则表达式符号进行匹配。也即试图匹配“>”。到目前为止,“<.+”已经匹配了“<EM>first</EM> test”。引擎会试图将“>”与换行符进行匹配,结果失败了。于是引擎进行回溯。结果是现在“<.+”匹配“<EM>first</EM> tes”。于是引擎将“>”与“t”进行匹配。显然还是会失败。这个过程继续,直到“<.+”匹配“<EM>first</EM”,“>”与“>”匹配。于是引擎找到了一个匹配“<EM>first</EM>”。记住,正则导向的引擎是“急切的”,所以它会急着报告它找到的第一个匹配。而不是继续回溯,即使可能会有更好的匹配,例如“<EM>”。所以我们可以看到,由于“+”的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。

·        用懒惰性取代贪婪性

一个用于修正以上问题的可能方案是用“+”的惰性代替贪婪性。你可以在“+”后面紧跟一个问号“?”来达到这一点。“*”,“{}”和“?”表示的重复也可以用这个方案。因此在上面的例子中我们可以使用“<.+?>”。让我们再来看看正则表达式引擎的处理过程。

再一次,正则表达式记号“<”会匹配字符串的第一个“<”。下一个正则记号是“.”。这次是一个懒惰的“+”来重复上一个字符。这告诉正则引擎,尽可能少的重复上一个字符。因此引擎匹配“.”和字符“E”,然后用“>”匹配“M”,结果失败了。引擎会进行回溯,和上一个例子不同,因为是惰性重复,所以引擎是扩展惰性重复而不是减少,于是“<.+”现在被扩展为“<EM”。引擎继续匹配下一个记号“>”。这次得到了一个成功匹配。引擎于是报告“<EM>”是一个成功的匹配。整个过程大致如此。

·        惰性扩展的一个替代方案

我们还有一个更好的替代方案。可以用一个贪婪重复与一个取反字符集:“<[^>]+>”。之所以说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每一个字符进行回溯。而使用取反字符集则不需要进行回溯。

最后要记住的是,本教程仅仅谈到的是正则导向的引擎。文本导向的引擎是不回溯的。但是同时他们也不支持惰性重复操作。

7.      使用“.”匹配几乎任意字符

在正则表达式中,“.”是最常用的符号之一。不幸的是,它也是最容易被误用的符号之一。

.”匹配一个单个的字符而不用关心被匹配的字符是什么。唯一的例外是新行符。在本教程中谈到的引擎,缺省情况下都是不匹配新行符的。因此在缺省情况下,“.”等于是字符集[^"n"r](Window)[^"n]( Unix)的简写。

这个例外是因为历史的原因。因为早期使用正则表达式的工具是基于行的。它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去。在这些工具中,字符串是不包含新行符的。因此“.”也就从不匹配新行符。

现代的工具和语言能够将正则表达式应用到很大的字符串甚至整个文件上去。本教程讨论的所有正则表达式实现都提供一个选项,可以使“.”匹配所有的字符,包括新行符。在RegexBuddy, EditPad ProPowerGREP等工具中,你可以简单的选中“点号匹配新行符”。在Perl中,“.”可以匹配新行符的模式被称作“单行模式”。很不幸,这是一个很容易混淆的名词。因为还有所谓“多行模式”。多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。

其他语言和正则表达式库也采用了Perl的术语定义。当在.NET Framework中使用正则表达式类时,你可以用类似下面的语句来激活单行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)

·        保守的使用点号“.

点号可以说是最强大的元字符。它允许你偷懒:用一个点号,就能匹配几乎所有的字符。但是问题在于,它也常常会匹配不该匹配的字符。

我会以一个简单的例子来说明。让我们看看如何匹配一个具有“mm/dd/yy”格式的日期,但是我们想允许用户来选择分隔符。很快能想到的一个方案是<<"d"d."d"d."d"d>>。看上去它能匹配日期“02/12/03”。问题在于02512703也会被认为是一个有效的日期。

<<"d"d[-/.]"d"d[-/.]"d"d>>看上去是一个好一点的解决方案。记住点号在一个字符集里不是元字符。这个方案远不够完善,它会匹配“99/99/99”。而<<[0-1]"d[-/.][0-3]"d[-/.]"d"d>>又更进一步。尽管他也会匹配“19/39/99”。你想要你的正则表达式达到如何完美的程度取决于你想达到什么样的目的。如果你想校验用户输入,则需要尽可能的完美。如果你只是想分析一个已知的源,并且我们知道没有错误的数据,用一个比较好的正则表达式来匹配你想要搜寻的字符就已经足够。

 

8.      字符串开始和结束的锚定

锚定和一般的正则表达式符号不同,它不匹配任何字符。相反,他们匹配的是字符之前或之后的位置。“^”匹配一行字符串第一个字符前的位置。<<^a>>将会匹配字符串“abc”中的a<<^b>>将不会匹配“abc”中的任何字符。

类似的,$匹配字符串中最后一个字符的后面的位置。所以<<c$>>匹配“abc”中的c

·        锚定的应用

在编程语言中校验用户输入时,使用锚定是非常重要的。如果你想校验用户的输入为整数,用<<^"d+$>>

用户输入中,常常会有多余的前导空格或结束空格。你可以用<<^"s*>><<"s*$>>来匹配前导空格或结束空格。

·        使用“^”和“$”作为行的开始和结束锚定

如果你有一个包含了多行的字符串。例如:“first line"n"rsecond line(其中"n"r表示一个新行符)。常常需要对每行分别处理而不是整个字符串。因此,几乎所有的正则表达式引擎都提供一个选项,可以扩展这两种锚定的含义。“^”可以匹配字串的开始位置(f之前),以及每一个新行符的后面位置("n"rs之间)。类似的,$会匹配字串的结束位置(最后一个e之后),以及每个新行符的前面(e"n"r之间)

.NET中,当你使用如下代码时,将会定义锚定匹配每一个新行符的前面和后面位置:Regex.Match("string", "regex", RegexOptions.Multiline)

应用:string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--将会在每行的行首插入“> ”。

·        绝对锚定

<<"A>>只匹配整个字符串的开始位置,<<"Z>>只匹配整个字符串的结束位置。即使你使用了“多行模式”,<<"A>><<"Z>>也从不匹配新行符。

即使"Z$只匹配字符串的结束位置,仍然有一个例外的情况。如果字符串以新行符结束,则"Z$将会匹配新行符前面的位置,而不是整个字符串的最后面。这个“改进”是由Perl引进的,然后被许多的正则表达式实现所遵循,包括Java.NET等。如果应用<<^[a-z]+$>>到“joe"n”,则匹配结果是“joe”而不是“joe"n”。

posted @ 2008-07-14 18:07 李炉烽 阅读(128) 评论(2) 编辑