2011-09-22 29 views
2

我偶然發現了PHP的preg_replace函數和一些正則表達式模式的一個奇怪的錯誤。我想要做的是替換由括號分隔的自定義標籤,並將其轉換爲HTML。正則表達式必須考慮定製的「填充」標籤,這些標籤將保留在輸出的HTML中,以便在頁面加載時替換它(例如替換站點名稱)。PHP PREG_REPLACE根據順序檢查得出錯誤結果

每個正則表達式模式都可以自行工作,但由於某些原因,如果先檢查其中一種模式,則其中一些將早退出函數。當我偶然發現這一點時,我使用preg_match和一個foreach循環來檢查模式,然後再返回結果 - 所以假設它對每個模式都是新鮮的。

這也沒有工作。

校驗碼:

function replaceLTags($originalString){ 
    $patterns = array(
       '#^\[l\]([^\s]+)\[/l\]$#i' => '<a href="$1">$1</a>', 
       '#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'=> '<a href="$1">$2</a>', 
       '#^\[l=([^\s]+) title=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" title="$2">$3</a>', 
       '#^\[l=([^\s]+) rel=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" rel="$2">$3</a>', 
       '#^\[l=([^\s]+) onClick=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2">$3</a>', 
       '#^\[l=([^\s]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" style="$2">$3</a>', 
       '#^\[l=([^\s]+) onClick=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2" style="$3">$4</a>', 
       '#^\[l=([^\s]+) class=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" style="$3">$4</a>', 
       '#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+)] target=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" rel="$3" target="$4">$5</a>' 
      ); 

    foreach ($patterns as $pattern => $replace){ 
     if (preg_match($pattern, $originalString)){ 
      return preg_replace($pattern, $replace, $originalString); 
     } 
    } 
} 

$string = '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]'; 

echo $alteredString = $format->replaceLTags($string); 

上述 「字符串」 會出來爲:

<a href="[site_url">/site-category/ class=hello rel=nofollow target=_blank]Hello there</a> 

當應該站出來爲:

<a href="[site_url]/site-category/" class="hello" rel="nofollow" target="_blank">Hello there</a> 

但是,如果將該模式進一步移動到列表中以便更早檢查,它將會正確格式化。

我很難過,因爲它好像每次檢查它時都會覆蓋字符串,即使這沒有意義。

+0

你真的想在這裏'return'? '返回的preg_replace($模式,$取代,$ originalString);'它應該是'$ originalString =的preg_replace($模式,$取代,$ originalString);'這樣的循環能夠繼續處理? – drew010

+0

我想到了,德魯。但只要找不到匹配,foreach循環就應該繼續。如果發現匹配,你不希望循環繼續,它只會浪費CPU資源。這就是爲什麼我告訴它一旦找到它就返回。 我希望得到它拉出屬性名稱和值這樣我就可以有希望能夠通過在標籤的所有屬性回溯一個正則表達式(不管類型:IMG,A,P,股利等),但到目前爲止,我還沒有能夠得到它的成功。上週我花了太多時間在上面。 – rexibit

回答

2

對我來說,你做的工作比你需要的要多得多。爲什麼不使用preg_replace_callback在單獨的步驟中處理屬性,而不是爲每個可能的屬性列表使用單獨的正則表達式/替換項?例如:

function replaceLTags($originalString){ 
    return preg_replace_callback('#\[l=((?>[^\s\[\]]+|\[site_url\])+)([^\]]*)\](.*?)\[/l\]#', 
           replaceWithinTags, $originalString); 
} 

function replaceWithinTags($groups){ 
    return '<a href="' . $groups[1] . '"' . 
     preg_replace('#(\s+\w+)=(\S+)#', '$1="$2"', $groups[2]) . 
     '>' . $groups[3] . '</a>'; 
} 

看到一個完整的演示here(更新;見註釋)。

下面是基於新的信息的代碼,是在給予的更新版本:

function replaceLTags($originalString){ 
    return preg_replace_callback('#\[l=((?>[^\s\[\]]+|\[\w+\])+)([^\]]*)\](.*?)\[/l\]#', 
           replaceWithinTags, $originalString); 
} 

function replaceWithinTags($groups){ 
    return '<a href="' . $groups[1] . '"' . 
     preg_replace(
      '#(\s+[^\s=]+)\s*=\s*([^\s=]+(?>\s+[^\s=]+)*(?!\s*=))#', 
      '$1="$2"', $groups[2]) . 
     '>' . $groups[3] . '</a>'; 
} 

demo

在第一個正則表達式,我改變[site_url]\[\w+\],因此它可以匹配任何自定義填充標籤。

這裏是第二正則表達式的擊穿:

(\s+[^\s=]+) # the attribute name and its leading whitespace 
\s*=\s* 
(
    [^\s=]+ # the first word of the attribute value 
    (?>\s+[^\s=]+)* # the second and subsequent words, if any 
    (?!\s*=) # prevents the group above from consuming tag names 
) 

最棘手的部分是匹配的多字的屬性值。 (?>\s+[^\s=]+)*總是會消耗一個標籤名稱(如果有),但先行強制它原路返回。通常情況下,它一次只能退出一個角色,但原子組有效地迫使它全部退回或根本不退出。

+0

太棒了Alan!但它缺少兩件事:「。'>''」在groups數組中的第二項之後,因此閉合>不會被排除在第一個鏈接標記之外,並且能夠獲取多個單詞作爲除URL之外的其他屬性。當我試圖像你的一樣制定一個通用的功能時(上週離你很近),這就是我上週阻礙我的原因。如果我有積分,我會給你+1。 – rexibit

+0

我認爲屬性值不能包含空格,因爲在原始字符串中沒有任何引號。你是說他們中的一些可以被引用?如果是這樣,可以用雙引號和其他引用單引號引用,如在HTML中?在'class = hello world'中,屬性值是否可以包含空格*而不引用*?我修復了缺少的尖括號錯誤,但是我會在等待反饋的其他問題上擱置。 –

+0

在方括號標記中,屬性值不會被引用,因爲它們是在運行htmlentities之前傳遞的,因此對於特殊字符將刪除任何引號。 – rexibit

1

你搞砸了正則表達式。如果您在每次迭代中打印字符串爲:

foreach ($patterns as $pattern => $replace){ 
    echo "String: $originalString\n"; 
    if (preg_match($pattern, $originalString)){ 
     return preg_replace($pattern, $replace, $originalString); 
    } 
} 

您將看到該字符串未被修改。從我的跑步中,我發現第二個正則表達式匹配。我給preg_match打了一個電話第三個參數並打印了比賽。這是我得到的:

Array (
    [0] => [l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l] 
    [1] => [site_url 
    [2] => /site-category/ class=hello rel=nofollow target=_blank]Hello there) 
+0

感謝您指出了這一點!我明白你在說什麼。我忘了爲每個模式的第一個標籤逃避尾隨結束]。我正在研究如何讓第二種模式在[。希望這會解決它。 – rexibit

0

以下是一些通用代碼,您可以使用少量表達式,您可以隨時刪除最終字符串中不允許的任何標記。

<?php 

function replaceLTags($originalString) { 
    if (preg_match('#^\[l\]([^\s]+)\[/l\]$#i', $originalString)) { 
     // match a link with no description or tags 
     return preg_replace('#^\[l\]([^\s]+)\[/l\]$#i', '<a href="$1">$1</a>', $originalString); 
    } else if (preg_match('#^\[l=([^\s]+)\s*([^\]]*)\](.*?)\[/l\]#i', $originalString, $matches)) { 
     // match a link with title and/or tags 
     $attribs = $matches[2]; 
     $attrStr = ''; 
     if (preg_match_all('#([^=]+)=([^\s\]]+)#i', $attribs, $attribMatches) > 0) { 
      $attrStr = ' '; 
      for ($i = 0; $i < sizeof($attribMatches[0]); ++$i) { 
       $attrStr .= $attribMatches[1][$i] . '="' . $attribMatches[2][$i] . '" '; 
      } 
      $attrStr = rtrim($attrStr); 
     } 

     return '<a href="' . $matches[1] . '"' . $attrStr . '>' . $matches[3] . '</a>'; 
    } else { 
     return $originalString; 
    } 
} 

$strings = array(
    '[l]http://www.stackoverflow.com[/l]', 
    '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]', 
    '[l=[site_url]/page.php?q=123]Link[/l]', 
    '[l=http://www.stackoverflow.com/careers/ target=_blank class=default]Stack overflow[/l]' 
); 

foreach($strings as $string) { 
    $altered = replaceLTags($string); 
    echo "{$altered}<br />\n"; 
} 
+0

真的很棒德魯。我會用它,但我需要能夠在屬性值中包含多個單詞(如標題,樣式或具有空格的JavaScript)。除此之外,真的很酷。如果我有積分,我會給你+1。 – rexibit

+0

我看到,沒有引用值的引號,它確實使得使用類似這樣的東西有點困難。你可以匹配'(class | rel | title | target | ...)=(...)',但是編寫該表達式的第二部分變得困難。 – drew010

1

你手頭緊迫的問題的原因是雙重的:

首先,在適用的正則表達式(最後一個數組中的)一個錯字。在:" target="之前,它有一個無關的字面右方括號。換句話說,這樣的:

'#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+)] target=([^\[]+)]([^\[]+)\[/l\]$#i'

應改爲:

'#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+) target=([^\[]+)]([^\[]+)\[/l\]$#i'

其次,有陣列中的兩個正則表達式這都匹配相同的字符串,而不幸的是,更多的特異性的二(正則表達式是我們想要的那個),第二。匹配於其他更一般的正則表達式是第二個陣列中:

'#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'

配售更一般的正則表達式最後併除去多餘的方括號解決了這個問題。這裏是你的固定應用了上述兩種變化的原始代碼:

function replaceLTags($originalString){ 
    $patterns = array(
       '#^\[l\]([^\s]+)\[/l\]$#i' => '<a href="$1">$1</a>', 
       '#^\[l=([^\s]+) title=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" title="$2">$3</a>', 
       '#^\[l=([^\s]+) rel=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" rel="$2">$3</a>', 
       '#^\[l=([^\s]+) onClick=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2">$3</a>', 
       '#^\[l=([^\s]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" style="$2">$3</a>', 
       '#^\[l=([^\s]+) onClick=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2" style="$3">$4</a>', 
       '#^\[l=([^\s]+) class=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" style="$3">$4</a>', 
       '#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+) target=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" rel="$3" target="$4">$5</a>', 
       '#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'=> '<a href="$1">$2</a>' 
      ); 

    foreach ($patterns as $pattern => $replace){ 
     if (preg_match($pattern, $originalString)){ 
      return preg_replace($pattern, $replace, $originalString); 
     } 
    } 
} 

$string = '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]'; 

echo $alteredString = $format->replaceLTags($string); 

請注意,這只是解決你的問題中所描述的立即採取具體錯誤,不解決與您正試圖完成什麼一些更基本的問題。我已經提出了一個有些更好的解決方案作爲一個回答你的後續問題:How do I make this REGEX ignore = in a tag's attribute?

但正如其他人所說的,將兩種不同的標記語言在一起,並與正則表達式的處理是自找麻煩。