2009-09-01 61 views
2

我有一段文字,如下所示。我需要對它做一個基本的編輯,但無法瞭解我需要的正則表達式。也許這只是漫長的一天,我沒有看到我需要的東西。如何在兩個已知令牌之間進行最小匹配?

的樣本數據:

START ITEM = 1235 
    BEGIN 
     WORD 
     RATE = 98 
     MORE WORDS 
     CODE = XX 
     STUFF 
    END 
    BEGIN 
     TEXT 
     MORE WORDS 
     RATE = 57 
     ADDITIONAL TEXT 
     CODE = YY 
     OTHER THINGS 
    END 
STOP 
START ITEM = 9983 
    BEGIN 
     WORD 
     RATE = 01 
     MORE WORDS 
     CODE = AA 
     STUFF 
    END 
    BEGIN 
     TEXT 
     MORE WORDS 
     RATE = 99 
     ADDITIONAL TEXT 
     CODE = XX 
     OTHER THINGS 
    END 
STOP 

我給予CODEITEM號碼,需要在適當的BEGIN/END部分編輯的速度。幸運的是,這些部分的定義很明確:STOP/STARTBEGIN/END(它們是關鍵字,而不是其他地方)。

我這個工具箱是Perl的正則表達式*

第一個解決方案,我試過沒有工作(值硬編碼):

$tx =~ s/(START \s ITEM \s = \s 9983.*? 
          BEGIN 
           .*? 
           RATE \s = \s)\d+ 
            (.*?  # Goes too far 
           CODE \s = \s XX) 
         /$1$newRate$2 
         /sx; 

因爲指定的代碼捲起匹配太多,找到更正確的代碼,但始終編輯第一個條目。

對此提出建議?


*實際代碼依賴於被添加到正則表達式的堆疊(排序的後處理過濾器的),其各自依次施加到文本做編輯的正則表達式。哎呀,如果我有文本,我可以做一個完整的解析器。但我希望不必打開這些代碼,並堅持使用我的API。

+0

實際上,'。*?'完全按照它應該達到的程度 - 問題在於它開始得太早。但JS Bangs的(修正)解決方案就是解決這個問題的方法。 – 2009-09-02 02:34:27

回答

6

regex非常適合這類問題知之甚少。我推薦一個簡單的迭代求解:

while (<FILE>) { 
    # push lines straight to output until we find the START that we want 
    print OUT $_; 
    next unless m/START ITEM = $number/; 

    # save the lines until we get to the CODE that we want 
    my @lines; 
    while (<FILE>) 
    { 
     push @lines, $_; 
     last if m/CODE = $code/; 
    } 

    # @lines now has everything from the START to the CODE. Get the last RATE in 
    # @lines and change its value. 
    my $strref = \(grep m/RATE/ @lines)[-1]; 
    $$strref = $new_value; 

    # print out the lines we saved and exit the loop 
    print OUT @lines; 
    last; 
} 

編輯:如果你真的想要一個正則表達式,你可以使用像這樣(未經):

$tx =~ s/(START \s+ ITEM \s+ = \s+ 9983.*? 
          BEGIN 
           .*? 
           RATE \s+ = \s+)\d+ 
           ((?: (?! END) .)* 
            CODE \s+ = \s+ XX) 
         /$1$newRate$2 
         /sx; 

增加的(?: (?! END) .)*確保之間的匹配速率和代碼不會越過END。但是這將比非正則表達式的方法慢得多。

+0

你比我快。 +1! – bobbymcr 2009-09-01 20:26:41

+0

當我說我的工具箱裏有perl regexes時,我並不是在開玩笑。實際的代碼依賴於正則表達式被添加到一堆正則表達式(一種回調正則表達式),每個正則表達式依次應用於文本。 哎呀,我可以做一個全功能的解析器,如果我有文本。但我希望不必打開這些代碼,並堅持使用我的API。 – 2009-09-01 20:36:49

+2

聽起來像clintp有DailyWTF條目。 – 2009-09-02 02:28:33

4

雖然我不喜歡它多少回溯,使得包羅萬象貪婪BEGINRATE之間將允許你跳到RATE的部分,在那裏CODE = XX。就像這樣:

$tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+ 
         BEGIN 
          .* 
          RATE \s+ = \s+)\d+ 
... 

與此的主要問題是,如果有必要,將跳轉到另一個ITEM,所以你必須確保你不吞併STOP。像這樣:

my $tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+ 
       BEGIN 
        (?: (?! \b STOP \b) .)* 
        RATE \s+ = \s+)\d+ 
         (.*?  # Goes too far 
        CODE \s+ = \s+ XX) 
      /msx 
      ; 

它仍然回溯比我更想。

(一個小時後)我意識到RATECODE字段,其​​值是XX不能由END進行劃分。因此,另一種解決方案是:

my $tx = qr/(START \s+ ITEM \s+ = \s+ 9983 \s+ 
       BEGIN 
        .*? 
        RATE \s+ = \s+)\d+ 
         ((?:(?!^\s+ END \s* $) .)*? 
        CODE \s+ = \s+ XX) 
         /msx 
         ; 

(我修改這個只在一條線上尋找END本身如果ADDITIONAL TEXT可以包含單個結束,那麼這將是很難無論什麼解析。)

我想這一個不走回頭路爲多,因爲它只是從RATE =開始,然後掃描CODE =它擊中END之前,如果我們沒有CODE = XX,然後將其修剪回到它認爲它匹配RATE和位置去尋找下一個RATE。如果我們不知道Item#9983肯定會有'XX'的代碼,我們可以爲STOP添加負向預覽。


編輯消除錯誤\s問題。

注:這個現在採取以下部分:

START ITEM = 9983 
    BEGIN 
     WORD 
     RATE = 01 
     MORE WORDS 
     CODE = AA 
     STUFF 
    END 
    BEGIN 
     TEXT 
     MORE WORDS 
     RATE = 99 
     ADDITIONAL TEXT <-- DON'T END HERE! 
     CODE = XX 
     OTHER THINGS 
    END 
STOP 
+0

這不是問題。當我從一個非互聯網連接的系統轉錄到我的瀏覽器中時,它已經迷失了方向。抱歉。 – 2009-09-01 20:31:39

+0

@clintp:修正它。 – Axeman 2009-09-01 20:57:31

+0

@Axeman這種貪婪與負向前瞻策略是一個好主意,但是'1235'和'XX'的具體解決方案失敗了。我認爲你可以通過更廣泛地應用這種方法來解決這個問題,使用END的類似lookahead。 – FMc 2009-09-01 21:58:43

0

正則表達式並不總是解析文本的最佳答案。你的例子表明你確實有一個可以用語法表示的文件。使用解析器來提取字段,然後對提取的信息進行更新會更簡單。