with texto remove limpiar from especiales eliminar convertir convert caracteres allow all php xml regex domdocument

texto - php strip all tags



Convertir sangría con preg_replace(sin devolución de llamada) (2)

Tengo algunos fragmentos XML devueltos por DOMDocument::saveXML() . Ya está bastante sangrado, con dos espacios por nivel, así:

<?xml version="1.0"?> <root> <error> <a>eee</a> <b>sd</b> </error> </root>

Como no es posible configurar DOMDocument (AFAIK) sobre los caracteres de sangría, pensé que es posible ejecutar una expresión regular y cambiar la sangría al reemplazar todos los pares de dos espacios en una pestaña. Esto se puede hacer con una función de devolución de llamada ( Demo ):

$xml_string = $doc->saveXML(); function callback($m) { $spaces = strlen($m[0]); $tabs = $spaces / 2; return str_repeat("/t", $tabs); } $xml_string = preg_replace_callback(''/^(?:[ ]{2})+/um'', ''callback'', $xml_string);

Ahora me pregunto si es posible hacer esto sin una función de devolución de llamada (y sin el modificador e (EVAL)). Cualquier magos regex con una idea?


La siguiente solución simplista primero viene a la mente:

$xml_string = str_replace('' '', "/t", $xml_string);

Pero supongo que le gustaría limitar el reemplazo solo a los espacios en blanco iniciales . Para ese caso, tu solución actual me parece bastante limpia. Dicho esto, puede hacerlo sin una devolución de llamada o el modificador e , pero necesita ejecutarlo de forma recursiva para hacer el trabajo así:

$re = ''%# Match leading spaces following leading tabs. ^ # Anchor to start of line. (/t*) # $1: Preserve any/all leading tabs. [ ]{2} # Match "n" spaces. %umx''; while(preg_match($re, $xml_string)) $xml_string = preg_replace($re, "$1/t", $xml_string);

Sorprendentemente, mis pruebas muestran que esto es casi el doble de rápido que el método de devolución de llamada. (Habría adivinado lo contrario.)

Tenga en cuenta que Qtax tiene una solución elegante que funciona bien (le di mi +1). Sin embargo, mis puntos de referencia muestran que es más lento que el método de devolución de llamada original. Creo que esto se debe a que la expresión /(?:^|/G) /um no permite que el motor de expresiones regulares aproveche la optimización interna "ancla al principio del patrón" . El motor RE se ve obligado a probar el patrón en todas y cada una de las posiciones en la cadena objetivo. Con las expresiones de patrón que comienzan con el ancla ^ , el motor RE solo necesita verificar al comienzo de cada línea, lo que le permite coincidir mucho más rápido.

Excelente pregunta! +1

Anexo / Corrección:

Debo disculparme porque las declaraciones de rendimiento que hice anteriormente son incorrectas . Corrí las expresiones regulares contra un solo archivo de prueba (no representativo) que tenía principalmente pestañas en el espacio en blanco inicial. Cuando se prueba contra un archivo más realista que tiene muchos espacios iniciales, mi método recursivo anterior funciona significativamente más lento que los otros dos métodos.

Si alguien está interesado, aquí está el script de referencia que utilicé para medir el rendimiento de cada expresión regular:

test.php

<?php // test.php 20120308_1200 require_once(''inc/benchmark.inc.php''); // ------------------------------------------------------- // Test 1: Recursive method. (ridgerunner) function tabify_leading_spaces_1($xml_string) { $re = ''%# Match leading spaces following leading tabs. ^ # Anchor to start of line. (/t*) # $1: Any/all leading tabs. [ ]{2} # Match "n" spaces. %umx''; while(preg_match($re, $xml_string)) $xml_string = preg_replace($re, "$1/t", $xml_string); return $xml_string; } // ------------------------------------------------------- // Test 2: Original callback method. (hakre) function tabify_leading_spaces_2($xml_string) { return preg_replace_callback(''/^(?:[ ]{2})+/um'', ''_callback'', $xml_string); } function _callback($m) { $spaces = strlen($m[0]); $tabs = $spaces / 2; return str_repeat("/t", $tabs); } // ------------------------------------------------------- // Test 3: Qtax''s elegantly simple /G method. (Qtax) function tabify_leading_spaces_3($xml_string) { return preg_replace(''/(?:^|/G) /um'', "/t", $xml_string); } // ------------------------------------------------------- // Verify we get the same results from all methods. $data = file_get_contents(''testdata.txt''); $data1 = tabify_leading_spaces_1($data); $data2 = tabify_leading_spaces_2($data); $data3 = tabify_leading_spaces_3($data); if ($data1 == $data2 && $data2 == $data3) { echo ("GOOD: Same results./n"); } else { exit("BAD: Different results./n"); } // Measure and print the function execution times. $time1 = benchmark_12(''tabify_leading_spaces_1'', $data, 2, true); $time2 = benchmark_12(''tabify_leading_spaces_2'', $data, 2, true); $time3 = benchmark_12(''tabify_leading_spaces_3'', $data, 2, true); ?>

La secuencia de comandos anterior utiliza la siguiente función práctica de evaluación comparativa que escribí hace algún tiempo:

benchmark.inc.php

<?php // benchmark.inc.php /*---------------------------------------------------------------------------- function benchmark_12($funcname, $p1, $reptime = 1.0, $verbose = true, $p2 = NULL) {} By: Jeff Roberson Created: 2010-03-17 Last edited: 2012-03-08 Discussion: This function measures the time required to execute a given function by calling it as many times as possible within an allowed period == $reptime. A first pass determines a rough measurement of function execution time by increasing the $nreps count by a factor of 10 - (i.e. 1, 10, 100, ...), until an $nreps value is found which takes more than 0.01 secs to finish. A second pass uses the value determined in the first pass to compute the number of reps that can be performed within the allotted $reptime seconds. The second pass then measures the time required to call the function the computed number of times (which should take about $reptime seconds). The average function execution time is then computed by dividing the total measured elapsed time by the number of reps performed in that time, and then all the pertinent values are returned to the caller in an array. Note that this function is limited to measuring only those functions having either one or two arguments that are passed by value and not by reference. This is why the name of this function ends with "12". Variations of this function can be easily cloned which can have more than two parameters. Parameters: $funcname: String containing name of function to be measured. The function to be measured must take one or two parameters. $p1: First argument to be passed to $funcname function. $reptime Target number of seconds allowed for benchmark test. (float) (Default=1.0) $verbose Boolean value determines if results are printed. (bool) (Default=true) $p2: Second (optional) argument to be passed to $funcname function. Return value: $result[] Array containing measured and computed values: $result[''funcname''] : $funcname - Name of function measured. $result[''msg''] : $msg - String with formatted results. $result[''nreps''] : $nreps - Number of function calls made. $result[''time_total''] : $time - Seconds to call function $nreps times. $result[''time_func''] : $t_func - Seconds to call function once. $result[''result''] : $result - Last value returned by function. Variables: $time: Float epoch time (secs since 1/1/1970) or benchmark elapsed secs. $i: Integer loop counter. $nreps Number of times function called in benchmark measurement loops. ----------------------------------------------------------------------------*/ function benchmark_12($funcname, $p1, $reptime = 1.0, $verbose = false, $p2 = NULL) { if (!function_exists($funcname)) { exit("/n[benchmark1] Error: function /"{$funcname}()/" does not exist./n"); } if (!isset($p2)) { // Case 1: function takes one parameter ($p1). // Pass 1: Measure order of magnitude number of calls needed to exceed 10 milliseconds. for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) { // Exponentially increase $nreps. $time = microtime(true); // Mark start time. (sec since 1970). for ($i = 0; $i < $n; ++$i) { // Loop $n times. ($n = 1, 10, 100...) $result = ($funcname($p1)); // Call the function over and over... } $time = microtime(true) - $time; // Mark stop time. Compute elapsed secs. $nreps = $n; // Number of reps just measured. } $t_func = $time / $nreps; // Function execution time in sec (rough). // Pass 2: Measure time required to perform $nreps function calls (in about $reptime sec). if ($t_func < $reptime) { // If pass 1 time was not pathetically slow... $nreps = (int)($reptime / $t_func); // Figure $nreps calls to add up to $reptime. $time = microtime(true); // Mark start time. (sec since 1970). for ($i = 0; $i < $nreps; ++$i) { // Loop $nreps times (should take $reptime). $result = ($funcname($p1)); // Call the function over and over... } $time = microtime(true) - $time; // Mark stop time. Compute elapsed secs. $t_func = $time / $nreps; // Average function execution time in sec. } } else { // Case 2: function takes two parameters ($p1 and $p2). // Pass 1: Measure order of magnitude number of calls needed to exceed 10 milliseconds. for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) { // Exponentially increase $nreps. $time = microtime(true); // Mark start time. (sec since 1970). for ($i = 0; $i < $n; ++$i) { // Loop $n times. ($n = 1, 10, 100...) $result = ($funcname($p1, $p2)); // Call the function over and over... } $time = microtime(true) - $time; // Mark stop time. Compute elapsed secs. $nreps = $n; // Number of reps just measured. } $t_func = $time / $nreps; // Function execution time in sec (rough). // Pass 2: Measure time required to perform $nreps function calls (in about $reptime sec). if ($t_func < $reptime) { // If pass 1 time was not pathetically slow... $nreps = (int)($reptime / $t_func); // Figure $nreps calls to add up to $reptime. $time = microtime(true); // Mark start time. (sec since 1970). for ($i = 0; $i < $nreps; ++$i) { // Loop $nreps times (should take $reptime). $result = ($funcname($p1, $p2)); // Call the function over and over... } $time = microtime(true) - $time; // Mark stop time. Compute elapsed secs. $t_func = $time / $nreps; // Average function execution time in sec. } } $msg = sprintf("%s() Nreps:%7d Time:%7.3f s Function time: %.6f sec/n", $funcname, $nreps, $time, $t_func); if ($verbose) echo($msg); return array(''funcname'' => $funcname, ''msg'' => $msg, ''nreps'' => $nreps, ''time_total'' => $time, ''time_func'' => $t_func, ''result'' => $result); } ?>

Cuando ejecuto test.php usando el contenido de benchmark.inc.php , aquí están los resultados que obtengo:

GOOD: Same results.
tabify_leading_spaces_1() Nreps: 1756 Time: 2.041 s Function time: 0.001162 sec
tabify_leading_spaces_2() Nreps: 1738 Time: 1.886 s Function time: 0.001085 sec
tabify_leading_spaces_3() Nreps: 2161 Time: 2.044 s Function time: 0.000946 sec

En resumen: recomendaría usar el método de Qtax.

Gracias Qtax!


Puedes usar /G :

preg_replace(''/^ |/G /m'', "/t", $string);

Hice algunos puntos de referencia y obtuve los siguientes resultados en Win32 con PHP 5.2 y 5.4:

>php -v PHP 5.2.17 (cli) (built: Jan 6 2011 17:28:41) Copyright (c) 1997-2010 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2010 Zend Technologies >php -n test.php XML length: 21100 Iterations: 1000 callback: 2.3627231121063 /G: 1.4221360683441 while: 3.0971200466156 /e: 7.8781840801239 >php -v PHP 5.4.0 (cli) (built: Feb 29 2012 19:06:50) Copyright (c) 1997-2012 The PHP Group Zend Engine v2.4.0, Copyright (c) 1998-2012 Zend Technologies >php -n test.php XML length: 21100 Iterations: 1000 callback: 1.3771259784698 /G: 1.4414191246033 while: 2.7389969825745 /e: 5.5516891479492

Sorprendentemente, la devolución de llamada es más rápida que /G en PHP 5.4 (aunque parece que depende de los datos, /G es más rápido en otros casos).

Para /G /^ |/G /m se usa, y es un poco más rápido que /(?:^|/G) /m . /(?>^|/G) /m es incluso más lento que /(?:^|/G) /m . /u conmutadores /u , /S , /X no afectaron notablemente el rendimiento de /G

Reemplazar es más rápido si la profundidad es baja (hasta aproximadamente 4 indentaciones, 8 espacios, en mi prueba), pero luego disminuye a medida que aumenta la profundidad.

Se utilizó el siguiente código:

<?php $base_iter = 1000; $xml_string = str_repeat(<<<_STR_ <?xml version="1.0"?> <root> <error> <a> eee </a> <b> sd </b> <c> deep deeper still deepest ! </c> </error> </root> _STR_ , 100); //*** while *** $re = ''%# Match leading spaces following leading tabs. ^ # Anchor to start of line. (/t*) # $1: Preserve any/all leading tabs. [ ]{2} # Match "n" spaces. %mx''; function conv_indent_while($xml_string) { global $re; while(preg_match($re, $xml_string)) $xml_string = preg_replace($re, "$1/t", $xml_string); return $xml_string; } //*** /G **** function conv_indent_g($string){ return preg_replace(''/^ |/G /m'', "/t", $string); } //*** callback *** function callback($m) { $spaces = strlen($m[0]); $tabs = $spaces / 2; return str_repeat("/t", $tabs); } function conv_indent_callback($str){ return preg_replace_callback(''/^(?:[ ]{2})+/m'', ''callback'', $str); } //*** callback /e *** function conv_indent_e($str){ return preg_replace(''/^(?: )+/me'', ''str_repeat("/t", strlen("$0")/2)'', $str); } //*** tests function test2() { global $base_iter; global $xml_string; $t = microtime(true); for($i = 0; $i < $base_iter; ++$i){ $s = conv_indent_while($xml_string); if(strlen($s) >= strlen($xml_string)) exit("strlen invalid 2"); } return (microtime(true) - $t); } function test1() { global $base_iter; global $xml_string; $t = microtime(true); for($i = 0; $i < $base_iter; ++$i){ $s = conv_indent_g($xml_string); if(strlen($s) >= strlen($xml_string)) exit("strlen invalid 1"); } return (microtime(true) - $t); } function test0(){ global $base_iter; global $xml_string; $t = microtime(true); for($i = 0; $i < $base_iter; ++$i){ $s = conv_indent_callback($xml_string); if(strlen($s) >= strlen($xml_string)) exit("strlen invalid 0"); } return (microtime(true) - $t); } function test3(){ global $base_iter; global $xml_string; $t = microtime(true); for($i = 0; $i < $base_iter; ++$i){ $s = conv_indent_e($xml_string); if(strlen($s) >= strlen($xml_string)) exit("strlen invalid 02"); } return (microtime(true) - $t); } echo ''XML length: '' . strlen($xml_string) . "/n"; echo ''Iterations: '' . $base_iter . "/n"; echo ''callback: '' . test0() . "/n"; echo ''/G: '' . test1() . "/n"; echo ''while: '' . test2() . "/n"; echo ''/e: '' . test3() . "/n"; ?>