paulwong

          Reqular Expressions

          雖然Reqular Expressions(以下簡稱REs)在這個論壇或是其他網站都可以找到相當多的資料,但是當我自己要學的時候才發現有很多小地方還是看不懂,所以才 以java API裡面的說明為主,把每個符號的解釋一一弄懂,終於對REs有了初步的認識。
          所以這份文件是以java API提到的符號解釋,加上我自己的心得及範例所整理出來的,我用"字元"和"字元組成",這兩大部分來解釋REs的符號,大部分的解釋都有範例,這樣比 較容易瞭解,沒有範例的部分不是太簡單,就是我找不到例子,不過對於認識REs應該沒有多大影響。

          因為我算是REs的初學者,所以難免有觀念上的錯誤,加上很多"冷門"的符號,要找到正確的解釋都很困難,因此我沒把握所有的資料都是正確的,如果各位有發現錯誤,還請加以指正。

          首先是字元,REs的基本元素就是字元,所以對於字元有相當細膩的描述方式,而且很多時候描述方式都不是唯一的,所以不必拘泥於找到最完美的寫法。
          字元分兩部分來解釋。
          1.一般字元,在還沒做字元組合時,下面這些都只是"一個"字元,先要有這個觀念,才不容易弄不清楚字串和字元的區別。
          x : 一個字元,例如 "a" 表示要找含有這個字元的部分。
          [abc] : 這個字元可能是a或b或c
          [^abc] : 這個字元是除了"a" "b" "c"以外的。
          [a-zA-Z] : 這個字元是a到z或是A到Z。
          [a-d[m-p]] : 這個字元是a到d或是m到p (聯集)。
          [a-z&&[def]] : 這個字元是"d", "e", or "f" (交集)。
          [a-z&&[^bc]] : 這個字元是a-z但b c 除外,等價於 [ad-z] (差集)。
          [a-z&&[^m-p]] : 這個字元是a-z但 m-p 除外,等價於 [a-lq-z] (差集)。
          除了放在一開始的"^"以及兩個"&&"以外,放在[]裡面的都當一般字元。
          可是\比較特殊,要\\才能當一個\,所以程式裡面必須寫\\\\,例如,想查"\"用[]包起來要寫成"[\\]",可是java在字串裡面要寫String pattern="[\\\\]"。

          2.特殊字元. : 所有的字元,不一定包含換行。java REs的"."預設是不包含"\r"和"\n"的,但是可以用(?s)來讓"."等於所有字元,(?s)的用法下面還有說明。
          \r : Carriage return。
          \t : TAB。
          \n : 換行。
          \f : 換頁。
          \e : escape。
          \d : 數字0-9。
          \D : 非數字,數字的除外集合。
          \s : 會產生空白的字元也就是 [ \t\n\x0B\f\r],也就是 " "(空白)、"\t"、"\n"、"\x0B"、"\f"、"\r"。
          \S : 非會產生空白的字元,上面的除外集合。
          \w : 文字 a-z A-Z _ 0-9,所有的英文大小寫,數字和底線。
          \W : 非文字,文字的除外集合。
          \ : 把之後的特殊字元當作是一般是字元,例如"\\"等於一個"\","\["等於"["。
          \Q \E:\Q到\E符號之間的特殊符號都當一般字元處理,例如"\Q(?:X)\E",符合的字串是"(?:X)"
          ^ : 行首,例如"^e"會找出所有在行首的"e",在[]裡面如果是第一個代表反相,如果不是第一個也當一般字元。
          $ : 行尾,例如"e$"會找出所有在結尾的"e"。
          \b : 符合文字邊界(word boundary)。也就是說在字與空格之間的位置 例如,'re\b' 符合"are" 裡的're'但是不符合"area"裡的're'
          \B : 符合非文字邊界 例如,'re\B' 符合"area"裡的're'但是不符合"are" 裡的're'
          \A : 輸入的開始。
          \G : 前一個符合的結尾的地方。
          \Z : 輸入的結尾去掉結尾符號的部分。例如字串"ABC\n",pattern "ABC\Z"就可以取得"ABC"。
          \z : 輸入的結尾。例如字串"ABC\n",pattern "ABC\z"就不相符。
          所謂的輸入,是指一次的處理資料,例如String s="ABC\nABC\tABC",就算一個輸入(input sequence)。

          到這裡為止是對字元的描述,字元的所有的可能情況應該都可以涵蓋了,可是光是字元是沒辦法構成字串,所以接下來是把字元組成字串的方法。

          字元組成,字元組成分三部分來解釋,下面符號的X和Y可以是一個字元也可以是一個Group。
          1.簡單組合,就是把字元排在一起。
          XY : 單純排列在一起,例如"ab"就是找"ab"這個字串,和"[ab]"不一樣,"[ab]"是代表"一個字元"可能是a或b。
          X|Y : X or Y,例如 a|b,而以字元而言a|b就等於[ab],所以對單一字元效果不大,主要是用於字元範圍的[]|[]或者群組()|()比較有意義。
          例如[a-z]|[0-9]表示不是小寫就是數字,(abc)|(123)表示是"abc"或是"123"。
          實例 "c|Car"(等同於"[cC]ar")相符字串是"car"或"Car"。<-錯了,下面有被糾正的說明,所以就不改這裡。
          另外abc|def是指"abc"或"def",而不是"ab[cd]ef"。
          (X) : 群組,將多個字元包裝成一個群組,和單純排列不同的地方是,群組可以參照,也可以對群組設定出現次數,
          例如(abc)+是指"abc"出現一次以上,abc+是指ab和一次以上的c。
          群組參照舉例來說比較容易懂,例如"(.)(.)(.).?\3\2\1",可以找出3個字的回文。如"abccba"、"xcdfdcx"。
          群組還有一個值得注意的是,群組0是留給整體的比對的結果,例如上面的例子group 0是"abccba",group 1是"a"、group 2是"b"、group 3是"c"。
          有了群組參照的觀念,後面的non-capturing group就會比較容易瞭解。。

          Group的另一個對應符號是
          \m : m是數字,表示參照前面的group,如上述的範例。

          2.重複次數
          出現次數接在字元之後,表示這個字元出現的次數,接在Group之後就表示group的出現次數。
          次數描述有三種quantifiers,
          Greedy quantifiers
          X?: X出現0或一次
          X+: X出現一次以上
          X*: X出現0或一次以上
          X{n,}: X出現至少n次
          X{n,m}: X出現n到m次
          X{n}: X出現n次

          Reluctant quantifiers
          X??: X出現0或一次
          X+?: X出現一次以上
          X*?: X出現0或一次以上
          X{n,}?: X出現至少n次
          X{n,m}?: X出現n到m次
          X{n}?: X出現n次

          Possessive quantifiers
          X?+: X出現0或一次
          X++: X出現一次以上
          X*+: X出現0或一次以上
          X{n,}+: X出現至少n次
          X{n,m}+: X出現n到m次
          X{n}+: X出現n次

          光看這樣的說明是無法分出三者不同,以下舉例說明。
          Greedy quantifiers
          字串 "xfooxxxxxxfoo"
          pattern ".*foo"
          結果 xfooxxxxxxfoo
          Greedy字面翻譯是貪婪,也就是盡可能的取字串,其實最貪婪的是第三種方法,因為Greedy還會把之後相符的資料留下來,Possessive吃的連骨頭都不剩。

          Reluctant quantifiers
          字串 "xfooxxxxxxfoo"
          pattern ".*?foo"
          結果 xfoo 和 xxxxxxfoo
          Reluctant字面翻譯是勉強,也就是抓最小可能,像這個例子,第一次抓一個x之後發現後面和foo相符,就得第一個結果,然後一直到最後又得到第二個結果。

          Possessive quantifiers
          字串 "xfooxxxxxxfoo"
          pattern ".*+foo"
          結果 沒有相符合資料,因為所有的資料都與"."比較相符,最後沒有剩下的字串可以和foo做比較,所以沒有符合資料。

          3.Special constructs (non-capturing)
          所謂的non-capturing就是說這個group會被比對,但是不會暫存在group裡面,就是最後得到的Group裡面不會有這組資料。
          (?:X) :X會取得,但不會被保留,當之後有用\m的時候,這個Group會不算在內,這樣的處理效能會比較好。
          (?i d m s u x) : 特別設定的flag設為on。
          (?-i -d -m -s -u -x) : 特別設定的flag設為off。
          i d m s u x的說明如下:
          i CASE_INSENSITIVE : 就是不分大小寫。(?i)
          例如
          字串 "ABC"
          pattern 用"abc"會找不到,用"(?i)abc"就會找到"ABC"。

          d UNIX_LINES : \n當作換行,當文件是UNIX的換行格式時,要處理換行就可以打開這個模式。(?d)
          m MULTILINE :多行模式下,^和$是以指每一行,不然是用整個字串的頭尾當^和$。(?m)
          例如
          字串 "ABC\nABC\nABC";
          pattern "^ABC$"會找不到, "(?m)^ABC$"才會找到三個"ABC";

          s DOTALL : 預設java的.不含\n \r,這個模式可以讓.等於所有字元包含\r \n。(?s)
          例如
          字串 "htm\nhtm\nhtm"
          pattern 用".htm"會找不到,用"(?s).htm"就會找到後面兩個"\nhtm"

          u UNICODE_CASE : unicode模式。(?u)
          x COMMENTS :可以在pattern裡面使用註解,會忽略pattern裡面的whitespace,以及"#"一直到結尾。(?x)
          例如
          字串 "ABC"
          pattern 用"A B C #找字串ABC" 會找不到,用"(?x)A B C #找字串ABC",就會找到"ABC"。

          (?idmsux-idmsux:X) :X是non-capturing group並且設定flags on -off。
          X(?=X) : lookahead在要取得的字串右邊,接著X但X不被算在內。例如Jack(?=Sprat) 則只有JackSprat的Jack會被取得,Jack(?=Sprat|Frost),則只有JackSprat和JackFrost的Jack都符合。
          X(?!X) : lookahead在要取得的字串右邊,和上面相反,例如Jack(?!Sprat) 則後面是Sprat的Jack不會被取得。
          (?<=X)X : lookbehind在要取得的字串左邊,例如"(?<=foo)bar",找接在foo之後的"bar"。還有裡面的文字必須已知長度,也就是不 能用"(?<=foo+)" "(?<=foo*)" "(?<=foo{1,})",但是可以用"(?<=foo?)" "(?<=foo{1,2})" "(?<=foo{1})"。
          (?<!X)X : lookbehind在要取得的字串左邊,例如"(?<!foo)bar",找不是接在foo之後的"bar"。關於長度和上面符號有相同限制。
          (?>X) : X, as an independent, non-capturing group。

          因為這幾個符號都是non-capturing的,所以會有一個現象,直接看下面範例會比較容易瞭解
          字串 "abc"
          pattern "a(?:b)c"
          結果 "abc" 但是b沒有變成group 1,這也就是non-capturing。
          如果用 "a(b)c" 會得到 group 0是"abc",group 1是b。

          字串 "abc"
          pattern "a(?=b)c"
          結果 抓不到,因為b並沒有被取出,要"a(?=b).c"才抓的到,而且"a(?=b).c"只會與"abc"相符,不會與"acc"等等相符。

          字串 "abc"
          pattern "a(?<=b)c"
          結果 抓不到,因為b並沒有被取出,要"a.(?<=b)c"才抓的到,而且"a(?<=b).c"只會與"abc"相符,不會與"acc"等等相符。
          由上面例子可知,這幾個符號都不會把符合的字串取出,也就是會比對但是不會算到結果裡面(non-capturing)。
          所以lookahead和lookbehind那四個符號,不適合放在字串中間,另外,因為這幾個符號都是non-capturing,所以在後 面加上大於0的次數都和一次是一樣的,例如字串"XB",pattern"(?<=X){9}B"一樣可以取得B,而pattern""(? <=A){0}B"也可以取得"B"。

          整個java的REs大概就這些了,只是看完這些解釋其實離可以運用還有一小段距離,因為REs需要的是分析pattern的能力,而這種能力要多練習才會。
          在自己能寫出pattern之前,可以拿別人寫好的pattern來測試體會一下,下面是一個簡單的測試程式。
          這個程式是我小修改網路上找到的範例,會回傳符合的group,方便測試結果。
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          import java.util.regex.Matcher;
          import java.util.regex.Pattern;
          ?
          publicclass TestRegular {
          publicstaticvoid main(String[] args) {
          String inputStr = "ABC\nABC\nABC";
          String patternStr = "(?d)ABC";
          Pattern pattern = Pattern.compile(patternStr);
          Matcher matcher = pattern.matcher(inputStr);
          boolean matchFound = matcher.find();
          while(matchFound) {
          System.out.println(matcher.start() + "-" + matcher.end());
          for(int i = 0; i <= matcher.groupCount(); i++) {
          String groupStr = matcher.group(i);
          System.out.println(i + ":" + groupStr);
          }
          if(matcher.end() + 1 <= inputStr.length()) {
          matchFound = matcher.find(matcher.end());
          ?? }else{
          ?? ??break;
          ?? }
          ?? }
          ??}
          }

          測試REs也可以使用一外部工具,例如eclipse的plugin Regex tester,我很多範例跟觀念都是用這個工具去測試的。

          最後"反組譯"幾個例子來練習,就是把別人寫好的pattern試著解釋出來。

          HTML TAG
          </?[a-z][a-z0-9]*[^<>]*>
          開始是<,接著有0或1個/,接著是一個英文字,再接著是不限次數的英文或數字,之後是非"<"或">"的字元不限次數個,最後以">"結尾。
          相符的是"<html>" "</html>" "<h0>"等。
          不相符字串 "<123>"

          HTML TAG 之二
          <([A-Z][A-Z0-9]*)[^>]*>(.*?)</\1>
          開始是<,然後由一個大寫英文字,和不限定個數的數字或大寫字母,構成一個group,接著不限個數的非">"字元,然後是一個 ">",不限定個數的字元(用*?才不會一直取到之後的tag去),然後是tag結束的"</",要和第一個Group的值match,最後 以">"結束。
          相符的有 "<A HREF="www.google.com.tw">Test</A>" "<P></P> "<PRE>Test</PRE>" "<H0></H0>"
          不相符字串 "<abc></def>" "<123></123>"

          IP
          \b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
          前後都是"\b"表示在獨立的一個字的單元,用"?:"是不算group而已,250-255或200-249或0-199,接著一個".",這樣的group有3次,最後再一次0-255的group。
          這樣就是一個0.0.0.0 - 255.255.255.255的IP的pattern,而[0-9][0-9]?也可以改成[0-9]{1,2}。
          相符的字串 "140.115.83.240" "255.255.0.0"
          不相符字串 "256.1.1.2" "-1.300.1.2"

          IP之二
          \b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b

          IP之三
          [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
          這是0.0.0.0 - 999.999.999.999的格式

          其他例子
          "[hc]+at" 相符的有 "hat", "cat", "hhat", "chat", "hcat", "ccchat" etc.
          "[hc]?at" 相符的有 "hat", "cat" and "at"
          "([cC]at)|([dD]og)" 相符的有 "cat", "Cat", "dog" and "Dog"
          "/\*.*\*/" 相符的有 /* Second comment */

          以下幾個(都是抄的)適合檢查輸入的值,因為都從^到$,從字串開始到結束。
          1、非負整數:”^\d+$”
          2、正整數:”^[0-9]*[1-9][0-9]*$”
          3、非正整數:”^((-\d+)|(0+))$”
          4、負整數:”^-[0-9]*[1-9][0-9]*$”
          5、整數:”^-?\d+$”
          6、非負浮點數:”^\d+(\.\d+)?$”
          7、正浮點數:”^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$”
          8、非正浮點數:”^((-\d+\.\d+)?)|(0+(\.0+)?))$”
          9、負浮點數:”^(-((正浮點數正則式)))$”
          10、英文字符串:”^[A-Za-z]+$”
          11、英文大寫串:”^[A-Z]+$”
          12、英文小寫串:”^[a-z]+$”
          13、英文字符數字串:”^[A-Za-z0-9]+$”
          14、英數字加下劃線串:”^\w+$”
          15、E-mail地址:”^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$”
          16、URL:”^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$”

          -------

          學習REs最重要是學會找到字串出現的特徵,特徵有時候是出現的位置,例如位置是行首(^),是行尾($),單字開始(\b),單字裡面(\ B),有時候是前後出現的字串,例如要找html tag的屬性,都會由"<"當開始,到">"結束。而要抓"<a>"的url就可以從"href"開始。但有時候要找的字並沒有 特殊的位置,也沒關係,就把要找的字描述出來也就可以了,例如要抓日期就做一個"(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)[0-9]{2}"的pattern就可以了。

          而其實真正困難的地方在於很多pattern都要看到結果才會瞭解哪裡錯了。
          看這個例子
          字串 "ABCDC"
          pattern "([A-Z]*).\1"
          預期要抓到CDC的,結果卻是"A" "B" "CDC",因為*是0~n所以沒抓也算,而後面的\1如果前面group沒抓到,他也跟著什麼都沒有,最後只有"."抓到第一個結果的"A",同理抓到 第二個結果的"B",然後才抓到預期的"CDC",而改成"([A-Z]+).\1"就只會抓到"CDC"了。

          最後誰能幫忙解釋一下"(?>X)",因為我實在分不出來他和(?:X)有什麼差別....

          posted on 2006-08-23 21:45 paulwong 閱讀(368) 評論(0)  編輯  收藏 所屬分類: J2SE

          主站蜘蛛池模板: 洛川县| 湄潭县| 河津市| 滦平县| 和顺县| 东乡族自治县| 禹州市| 临城县| 武定县| 乌拉特前旗| 屏东市| 远安县| 班玛县| 安龙县| 呼图壁县| 英德市| 江口县| 陈巴尔虎旗| 长乐市| 如皋市| 杭锦后旗| 安陆市| 东平县| 平阳县| 石河子市| 多伦县| 兴海县| 德阳市| 平原县| 孙吴县| 甘德县| 双牌县| 专栏| 即墨市| 越西县| 嘉定区| 曲阜市| 民勤县| 营口市| 巴林左旗| 龙口市|