Скажи у меня есть функция templateMap
который для каждого подмассива $array
, заменяет каждое вхождение @n
(для некоторых n
) в данной $string
со значениями из этого подмассива, возвращая новый массив подмассивов. Также скажите, что я хочу разрешить пользователю обратную косую черту @
символ (что означает \
быть убитым тоже).
Например:
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
, но, конечно, тогда вы должны проверить, находится ли строка в данной строке, и соответственно изменить ее.
Я думаю, что главная проблема, когда ограничивается только использованием str_replace
является то, что у вас мало контроля над тем, какие строки были заменены (так как все вхождения заменяются сразу), и вам нужно быть особенно осторожным при выборе заполнителя для \@
escape-последовательность Существует вероятность того, что два введенных значения вместе сгенерируют строку-заполнитель и, следовательно, будут превращены в @
символ, когда замена заполнителя отменяется.
Ниже приведено грубое решение, которое пытается справиться с этим. Он проверяет по одному заполнителю за раз по строке шаблона, значениям замены и окончательной строке, проверяя, что заполнитель не появляется ни в одной из этих строк и что количество заполнителей, первоначально введенных для \@
соответствует количеству возвращенных заполнителей. Вы, вероятно, хотели бы установить заполнитель по умолчанию вместо 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
)
При замене не может быть никаких @
кроме тех, которые нуждаются в замене … поэтому мы должны избавиться от всех \@
последовательности.
Но когда мы избавимся от всех \@
последовательности, не может быть \@
которые на самом деле являются частью \\@
(две обратные косые черты, сопровождаемые @
Последовательность
Чтобы избавиться от \\
последовательности, мы можем использовать новый escape-символ %
,
В частности, если мы сбежим %
как %%
тогда мы можем избежать любой другой последовательности как ?%?
где ?
любой персонаж и будет гарантировано, что ?%?
можно избежать, так как %
никогда не появится один в середине.
// 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); // => ["(hello@)", "(goodbye@)"]