Разбить строку по разделителю, но не если она экранирована - PullRequest
52 голосов
/ 05 июня 2011

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

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

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

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

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

Ответы [ 5 ]

107 голосов
/ 05 июня 2011

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

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

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

11 голосов
/ 02 октября 2011

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

  1. что-то отличное от \ или |
  2. или \, за которыми следует \ или |
  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
)
4 голосов
/ 25 ноября 2014

Для будущих читателей, вот универсальное решение.Он основан на идее 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']) приводят к одной и той же строке: 'a::::b'.Взрыв может быть также интересной проблемой.

4 голосов
/ 06 июня 2011

Недавно я разработал решение:

$array = preg_split('~ ((?<!\\\\)|(?<=[^\\\\](\\\\\\\\)+)) \| ~x', $string);

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

0 голосов
/ 02 января 2015

Regex мучительно медленно. Лучший метод - удалить экранированные символы из строки перед разбиением, а затем вернуть их обратно:

$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 ) 
...