2015-03-18 13 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

+0

@mhall, 1) спасибо за ваш вклад выше, мне будет интересно его прочитать, 2) (я OP), 3) хороший улов - я изменил код, разделив на "@", а затем декодирование перед заменой. –

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 
) 
Смежные вопросы