2015-03-18 84 views
-1

解析反斜槓轉義說我有一個功能templateMap其中,對於$array每個子陣列,取代的@n每次發生(對於某些n)在給定的$string其值從該子陣列,子陣列返回的新的數組。也說,我想允許用戶反斜槓的@符號,(這意味着允許\到太反斜線)。與str_replace函數

例如:

function templateMap ($string, $array) { 
    $newArray = array(); 
    foreach($array as $subArray) { 
     foreach($subArray as $replacements) { 
      ... 
     } 
    } 
    return $newArray; 
} 


// for grouping mysql statements with parentheses 
templateMap("(@)", array(" col1 < 5 && col2 > 6 ", " col3 < 3 || col4 > 7")); 

這將產生

array("(col1 < 5 && col2 > 6)", "(col3 < 3 || col4 > 7)") 

下面是使用多個參數更復雜的例子 - 可能不容易實現

templateMap("You can tweet @0 \@2 @1", array(
    array("Sarah", "ssarahtweetzz"), 
    array("John", "jjohnsthetweetiest"), 
    ... 
)); 

/* output: 
array(
    "You can tweet Sarah @2 ssarahtweetzz", 
    "You can tweet John @2 jjohnsthetweetiest" 
) 
*/ 

有沒有一種辦法通過一系列str_replace調用完成此操作? (與正則表達式或簡單的狀態機相反)。

我想到的一件事是用一些在當前字符串中找不到的奇特字符串(如zzzzzz)取代\@,但當然,那麼你必須檢查字符串是否在給定的字符串中,並相應地修改它。

回答

1

在進行替換時,除了需要替換的替換外,不能有任何@ ...因此我們必須刪除所有的\@序列。 但是,當我們擺脫所有的\@序列,不能有這實際上是\\@(兩個反斜槓後跟一個@)序列的一部分的任何\@。 爲了擺脫\\序列的,我們可以用一個新的轉義字符%

具體來說,如果我們逃避%%%,那麼我們就可以逃脫任何其他序列?%?其中?是任意字符,並保證該?%?可以去逃跑,因爲%絕不會在中間單獨出現。

// wrapper for native strings to make chaining easier 
class String { 
    private $str; 
    public function __construct ($str) { 
     $this->str = $str; 
    } 
    public function replace ($search, $substitute) { 
     return new self(str_replace($search, $substitute, $this->str)); 
    } 
    public function toRaw() { 
     return $this->str; 
    } 
} 

function templateMap ($str, $arr) { 
    $encodedStr = (new String($str))->replace('%', '%%') 
     ->replace('\\\\', '?%?')->replace('\@', '!%!'); 
    $newArr = array(); 
    foreach($arr as $el) { 
     $encodedStrPieces = explode("@", $encodedStr->toRaw()); 
     foreach($encodedStrPieces as $i => $piece) { 
      $encodedStrPieces[$i] = (new String($piece))->replace("@", $el) 
      ->replace('!%!', '@')->replace('?%?', '\\') 
      ->replace('%%', '%')->toRaw(); 
     } 
     $newArr[] = implode($el, $encodedStrPieces); 
    } 
    return $newArr; 
} 


$arr = templateMap("(@\@)", array("hello", "goodbye")); 
var_dump($arr); // => ["([email protected])", "([email protected])"] 
+0

我不知道是該爲OP一個問題,但這種解決方案將不會保留兩個百分號。對於'$ arr = templateMap(「(@ \ @)」,array(「hel %% o」));'它會產生'(hel%o @)'而不是'(hel %% o @)' – mhall 2015-03-19 07:57:15

+0

@mhall,1)感謝上面的貢獻,我會有興趣閱讀它,2)(我是OP),3)很好的捕獲 - 我已經通過分割「@」來修改代碼,然後在替換之前進行解碼。 – 2015-03-19 15:32:33

1

我認爲主要問題僅限於當只使用str_replace是,你必須在這串已被取代(如所有出現在更換一次)幾乎沒有控制,你需要特別注意選擇一個佔位符時爲\@轉義序列。有兩種插入值組合可能會產生佔位符字符串,因此當佔位符替換被還原時會變成@字符。

下面是蠻力一種解決方案,試圖處理這個問題。它檢查一個佔位符,在對模板字符串,替換值和最終的字符串時,確保佔位符內沒有任何這些字符串和最初引入\@佔位符的數量相匹配佔位符的數量恢復出現。你可能想設置一個默認的佔位符,而不是xyz(比如零個字符或者什麼),這對你最合適,以避免不必要的處理。

它可以用兩種替代模式(@@<n>)調用,但目前它們不能混用。

這不是我寫過的最漂亮的代碼,但考慮到str_replace約束,它仍然是我的注意力,我希望它對你有一些幫助。

function templateMap ($string, $array, $defaultPlaceholder = "xyz") 
{ 
    $newArray = array(); 

    // Create an array of the subject string and replacement arrays 
    $knownStrings = array($string); 
    foreach ($array as $subArray) { 
     if (is_array($subArray)) { 
      $knownStrings = array_merge($knownStrings, array_values($subArray)); 
     } 
     else { 
      $knownStrings[] = $subArray; 
     } 
    } 

    $placeHolder = ''; 

    while (true) { 
     if (!$placeHolder) { 
      // This is the first try, so let's try the default placeholder 
      $placeHolder = $defaultPlaceholder; 
     } 
     else { 
      // We've been here before - we need to try another placeholder 
      $placeHolder = uniqid('bs-placeholder-', true); 
     } 

     // Try to find a placeholder that does not appear in any of the strings 
     foreach ($knownStrings as $knownString) { 
      // Does $placeHolder exist in $knownString? 
      str_replace($placeHolder, 'whatever', $knownString, $count); 
      if ($count > 0) { 
       // Placeholder candidate was found in one of the strings 
       continue 2; // Start over 
      } 
     } 

     // Will go for placeholder "$placeHolder" 
     foreach ($array as $subArray) { 
      $newString = $string; 

      // Apply placeholder for \@ - remember number of replacements 
      $newString = str_replace(
       '\@', $placeHolder, $newString, $numberOfFirstReplacements 
      ); 

      if (is_array($subArray)) { 
       // Make substitution on @<n> 
       for ($i = 0; $i <= 9; $i++) { 
        @$newString = str_replace("@$i", $subArray[$i], $newString); 
       } 
      } 
      else { 
       // Make substitution on @ 
       @$newString = str_replace("@", $subArray, $newString); 
      } 

      // Revert placeholder for \@ - remember number of replacements 
      $newString = str_replace(
       $placeHolder, '@', $newString, $numberOfSecondReplacements 
      ); 

      if ($numberOfFirstReplacements != $numberOfSecondReplacements) { 
       // Darn - value substitution caused used placeholder to appear, 
       // ruining our day - we need some other placeholder 
       $newArray = array(); 
       continue 2; 
      } 

      // Looks promising 
      $newArray[] = $newString; 
     } 

     // All is well that ends well 
     break; 
    } 
    return $newArray; 
} 

$a = templateMap(
    "(@ and one escaped \@)", 
    array(" col1 < 5 && col2 > 6", " col3 < 3 || col4 > 7") 
); 
print_r($a); 

$a = templateMap(
    "You can tweet @0 \@2 @1", 
    array(
     array("Sarah", "ssarahtweetz"), 
     array("John", "jjohnsthetweetiest"), 
    ) 
); 
print_r($a); 

輸出:

Array 
(
    [0] => (col1 < 5 && col2 > 6 and one escaped @) 
    [1] => (col3 < 3 || col4 > 7 and one escaped @) 
) 
Array 
(
    [0] => You can tweet Sarah @2 ssarahtweetz 
    [1] => You can tweet John @2 jjohnsthetweetiest 
)