Intereting Posts
Вставка значений массива вставляет один идентификатор в базу данных mysql с использованием php и PDO MD5 строки в ActionScript возвращает неверные результаты, когда какой-то hex является частью строки (т. Е. «Abc \ xBF \ x4E») Доступ к данным, отправленным sendBeacon mysql_fetch_assoc (): предоставленный аргумент не является допустимым ресурсом результата MySQL в php работают ли сеансы, когда файлы cookie отключены? JQuery UI не работает должным образом для некоторых слов Отображение количества посетителей Google Analytics на веб-сайте Загрузка PHP замораживает все остальное Доступ к URL-адресам по адресу www.example.com/page вместо www.example.com/page.php Отправка auth в заголовках php curl проблемы с DATETIME в базе данных Хеширование паролей, совместимое с Java и PHP Как я могу вернуть код AdSense из функции в wordpress Невозможно загрузить композитор – 1 и 1 с доступом ssh к серверу Найти min / max в двухмерном массиве

Преобразование отступов с preg_replace (без обратного вызова)

У меня есть XML-фрагмент, возвращаемый DOMDocument::saveXML() . Он уже довольно отступом, с двумя пробелами на уровне, например:

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

Поскольку невозможно настроить DOMDocument ( DOMDocument ) об символах отступов, я подумал, что можно запустить регулярное выражение и изменить отступ, заменив все пары с двумя пробелами на вкладку. Это можно сделать с помощью функции обратного вызова ( 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); 

Теперь мне интересно, можно ли это сделать с функцией обратного вызова (и без e modifier (EVAL)). Любые мастера регулярных выражений с идеей?

Related of "Преобразование отступов с preg_replace (без обратного вызова)"

Вы можете использовать \G :

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

Были некоторые тесты и получили следующие результаты в Win32 с PHP 5.2 и 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 

Удивительно, что обратный вызов быстрее, чем \G в PHP 5.4 (тем не менее, похоже, что он зависит от данных, \G в некоторых других случаях быстрее).

Для \G /^ |\G /m используется и немного быстрее, чем /(?:^|\G) /m . /(?>^|\G) /m еще медленнее, чем /(?:^|\G) /m . /u , /S , /X не повлияли на производительность \G заметно.

В while замена происходит быстрее, если глубина мала (до 4 отступов, 8 пробелов, в моем тесте), но затем становится медленнее по мере увеличения глубины.

Был использован следующий код:

 <?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"; ?> 

На первый взгляд приходит следующее упрощенное решение:

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

Но я предполагаю, что вы хотели бы ограничить замену только ведущим пробелом . В этом случае ваше текущее решение выглядит довольно чистым для меня. Тем не менее, вы можете сделать это без обратного вызова или модификатора e , но вам нужно запустить его рекурсивно, чтобы выполнить задание следующим образом:

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

Удивительно, но мое тестирование показывает, что это почти в два раза быстрее, чем метод обратного вызова. (Я бы догадался об обратном.)

Обратите внимание, что Qtax имеет элегантное решение, которое отлично работает (я дал ему свой +1). Тем не менее, мои тесты показывают, что он медленнее исходного метода обратного вызова. Я думаю, это связано с тем, что выражение /(?:^|\G) /um не позволяет движку регулярных выражений использовать внутреннюю оптимизацию: «привязка в начале шаблона» . Двигатель RE вынужден проверять шаблон на каждую позицию в целевой строке. С образцовыми выражениями, начинающимися с якоря ^ , движку RE нужно только проверять в начале каждой строки, что позволяет ему соответствовать намного быстрее.

Отличный вопрос! +1

Добавление / Исправление:

Я должен извиниться, потому что заявления о производительности, которые я сделал выше, ошибочны . Я запускал регулярные выражения только для одного (не репрезентативного) тестового файла, который имел в основном вкладки в ведущем пробеле. При тестировании с более реалистичным файлом, имеющим множество ведущих пробелов, мой рекурсивный метод выше выполняет значительно медленнее, чем два других метода.

Если кому-то интересно, вот сценарий сравнения, который я использовал для измерения производительности каждого регулярного выражения:

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); ?> 

В приведенном выше сценарии используется следующая удобная небольшая функция бенчмаркинга, которую я написал некоторое время назад:

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 - (ie 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.3fs 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); } ?> 

Когда я запускаю test.php используя содержимое benchmark.inc.php , вот результаты, которые я получаю:

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

Итог: я бы рекомендовал использовать метод Qtax.

Спасибо Qtax!