Разделить строку разделителем, но не если она экранирована
как я могу разделить строку разделителем, но не если она экранирована? Например, у меня есть строка:
1|2|2|3|4\|4
разделитель |
и экранированный разделитель |
. Кроме того, я хочу игнорировать экранированные обратные косые черты, поэтому в |
the |
все равно будет разделитель.
таким образом, с приведенной выше строкой результат должен быть:
[0] => 1
[1] => 2|2
[2] => 3
[3] => 4\|4
5 ответов:
использовать темную магию:
$array = preg_split('~\\.(*SKIP)(*FAIL)|\|~s', $string);
\\.
соответствует обратной косой черте, за которой следует символ,(*SKIP)(*FAIL)
пропускает его и\|
соответствует вашему разделитель.
вместо
split(...)
, ИМО более интуитивно понятен для использования какой-то функции" сканирования", которая работает как лексический токенизатор. В PHP это будет . Вы просто говорите, что хотите соответствовать:
- что-то другое, чем
\
или|
- или
\
затем\
или|
- повтор #1 или #2 по крайней мере один раз
следующая демонстрация:
$input = "1|2\|2|3\\|4\\\|4"; echo $input . "\n\n"; preg_match_all('/(?:\\.|[^\\|])+/', $input, $parts); print_r($parts[0]);
будет печать:
1|2\|2|3\|4\\|4 Array ( [0] => 1 [1] => 2\|2 [2] => 3\ [3] => 4\\|4 )
недавно я придумал решение:
$array = preg_split('~ ((?<!\\)|(?<=[^\\](\\\\)+)) \| ~x', $string);
но решение черной магии все еще в три раза быстрее.
для будущих читателей, вот универсальное решение. Он основан на идее Никича с
(*SKIP)(*FAIL)
:function split_escaped($delimiter, $escaper, $text) { $d = preg_quote($delimiter, "~"); $e = preg_quote($escaper, "~"); $tokens = preg_split( '~' . $e . '(' . $e . '|' . $d . ')(*SKIP)(*FAIL)|' . $d . '~', $text ); $escaperReplacement = str_replace(['\', '$'], ['\\', '\$'], $escaper); $delimiterReplacement = str_replace(['\', '$'], ['\\', '\$'], $delimiter); return preg_replace( ['~' . $e . $e . '~', '~' . $e . $d . '~'], [$escaperReplacement, $delimiterReplacement], $tokens ); }
сделать попытку:
// the base situation: $text = "asdf\,fds\,ddf,\\,f\,,dd"; $delimiter = ","; $escaper = "\"; print_r(split_escaped($delimiter, $escaper, $text)); // other signs: $text = "dk!%fj%slak!%df!!jlskj%%dfl%isr%!%%jlf"; $delimiter = "%"; $escaper = "!"; print_r(split_escaped($delimiter, $escaper, $text)); // delimiter with multiple characters: $text = "aksd()jflaksd())jflkas(('()j()fkl'()()as()d('')jf"; $delimiter = "()"; $escaper = "'"; print_r(split_escaped($delimiter, $escaper, $text)); // escaper is same as delimiter: $text = "asfl''asjf'lkas'''jfkl''d'jsl"; $delimiter = "'"; $escaper = "'"; print_r(split_escaped($delimiter, $escaper, $text));
выход:
Array ( [0] => asdf,fds,ddf [1] => \ [2] => f, [3] => dd ) Array ( [0] => dk%fj [1] => slak%df!jlskj [2] => [3] => dfl [4] => isr [5] => % [6] => jlf ) Array ( [0] => aksd [1] => jflaksd [2] => )jfl'kas((()j [3] => fkl() [4] => as [5] => d(')jf ) Array ( [0] => asfl'asjf [1] => lkas' [2] => jfkl'd [3] => jsl )
Примечание: существует проблема теоретического уровня:
implode('::', ['a:', ':b'])
иimplode('::', ['a', '', 'b'])
результат та же строка:'a::::b'
. Взрыв может быть также интересной проблемой.
регулярное выражение мучительно медленно. Лучший способ-удалить экранированные символы из строки до разделения, а затем вернуть их обратно:
$foo = 'a,b|,c,d||,e'; function splitEscaped($str, $delimiter,$escapeChar = '\') { //Just some temporary strings to use as markers that will not appear in the original string $double = "_doub"; $escaped = "_esc"; $str = str_replace($escapeChar . $escapeChar, $double, $str); $str = str_replace($escapeChar . $delimiter, $escaped, $str); $split = explode($delimiter, $str); foreach ($split as &$val) $val = str_replace([$double, $escaped], [$escapeChar, $delimiter], $val); return $split; } print_r(splitEscaped($foo, ',', '|'));
который разбивается на ',', НО НЕ если убежал с "|". Он также поддерживает двойное экранирование, поэтому "| | "становится одним" / " после разделения:
Array ( [0] => a [1] => b,c [2] => d| [3] => e )