Разделить строку на разделитель, но не если он экранирован

Как я могу разделить строку на разделитель, но не если он экранирован? Например, у меня есть строка:

1|2\|2|3\\|4\\\|4 

Разделитель | и экранированный разделитель равен \| , Кроме того, я хочу игнорировать сбежавшие обратные косые черты, поэтому в \\| | все равно будет ограничителем.

Таким образом, с указанной строкой результат должен быть:

 [0] => 1 [1] => 2\|2 [2] => 3\\ [3] => 4\\\|4 

Используйте темную магию:

 $array = preg_split('~\\\\.(*SKIP)(*FAIL)|\|~s', $string); 

\\\\. совпадает с обратным слэшем, за которым следует символ, (*SKIP)(*FAIL) пропускает его и \| соответствует вашему разделителю.

Вместо split(...) , IMO более интуитивно понятен для использования какой-то «функции сканирования», которая работает как лексический токенизатор. В PHP это будет функция preg_match_all . Вы просто говорите, что хотите совместить:

  1. что-то другое, чем \ или |
  2. или a \ за которым следует \ или |
  3. повторить # 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); 

Но решение черной магии все еще в три раза быстрее.

Для будущих читателей здесь есть универсальное решение. Он основан на идее NikiC с (*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']) result '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 = "\0\0\0_doub"; $escaped = "\0\0\0_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 )