eliminar - split php
Dividir cadena por delimitador, pero no si se ha escapado (5)
¿Cómo puedo dividir una cadena por un delimitador, pero no si se escapó? Por ejemplo, tengo una cadena:
1|2/|2|3//|4///|4
El delimitador es |
y un delimitador escapado es /|
. Además, quiero ignorar las barras invertidas escapadas, así que en //|
el |
todavía sería un delimitador.
Entonces, con la cadena anterior, el resultado debería ser:
[0] => 1
[1] => 2/|2
[2] => 3//
[3] => 4///|4
En lugar de split(...)
, es IMO más intuitivo utilizar algún tipo de función de "escaneo" que funciona como un tokenizador léxico. En PHP, esa sería la función preg_match_all
. Simplemente dices que quieres unir:
- algo más que un
/
o|
- o a
/
seguido de un/
o|
- repita # 1 o # 2 al menos una vez
La siguiente demostración:
$input = "1|2//|2|3////|4//////|4";
echo $input . "/n/n";
preg_match_all(''/(?:////.|[^////|])+/'', $input, $parts);
print_r($parts[0]);
se imprimirá:
1|2/|2|3//|4///|4
Array
(
[0] => 1
[1] => 2/|2
[2] => 3//
[3] => 4///|4
)
Para lectores futuros, aquí hay una solución universal. Se basa en la idea de NikiC con (*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
);
}
Pruébalo:
// 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));
Salida:
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
)
Nota: hay un problema de nivel teórico: implode(''::'', [''a:'', '':b''])
e implode(''::'', [''a'', '''', ''b''])
resultado la misma cadena: ''a::::b''
. La implosión también puede ser un problema interesante.
Recientemente ideé una solución:
$array = preg_split(''~ ((?<!////)|(?<=[^////](////////)+)) /| ~x'', $string);
Pero la solución de magia negra es todavía tres veces más rápida.
Regex es dolorosamente lento. Un mejor método es eliminar los caracteres que se escaparon de la cadena antes de dividirlos y volver a ponerlos en:
$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, '','', ''|''));
que se divide en "," pero no si se escapó con "|". También es compatible con el doble escape, por lo que "||" se convierte en un solo "|" después de que ocurra la división:
Array ( [0] => a [1] => b,c [2] => d| [3] => e )
Usa magia oscura
$array = preg_split(''~////.(*SKIP)(*FAIL)|/|~s'', $string);
////.
coincide con una barra invertida seguida de un carácter, (*SKIP)(*FAIL)
saltea y /|
coincide con su delimitador.