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


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

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

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

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

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

5 ответов:

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

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

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

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

  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
)

недавно я придумал решение:

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