以下简单的解决方案首先浮现在脑海中:
$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有一个优雅的解决方案,可以正常运行(我给了它我的赞成)。然而,我的基准测试显示它比原始的回调方法慢。我认为这是因为表达式
/(?:^|\G) /um
不允许正则表达式引擎利用内部优化中的“模式开头锚点”。RE引擎被强制针对目标字符串中的每个位置测试模式。对于以
^
锚点开头的模式表达式,RE引擎只需要在每行开头检查,这使得匹配速度更快。
非常好的问题!+1
附加/更正:
我必须道歉,因为我上面所说的性能陈述是错误的。我只对一个(不具代表性的)测试文件运行了正则表达式,该文件主要包含导致缩进的制表符。当针对具有大量前导空格的更现实的文件进行测试时,我上面的递归方法的性能显着低于其他两种方法。
如果有人感兴趣,这是我用来测量每个正则表达式性能的基准测试脚本:
<?php
require_once('inc/benchmark.inc.php');
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;
}
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);
}
function tabify_leading_spaces_3($xml_string) {
return preg_replace('/(?:^|\G) /um', "\t", $xml_string);
}
$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");
}
$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
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)) {
for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) {
$time = microtime(true);
for ($i = 0; $i < $n; ++$i) {
$result = ($funcname($p1));
}
$time = microtime(true) - $time;
$nreps = $n;
}
$t_func = $time / $nreps;
if ($t_func < $reptime) {
$nreps = (int)($reptime / $t_func);
$time = microtime(true);
for ($i = 0; $i < $nreps; ++$i) {
$result = ($funcname($p1));
}
$time = microtime(true) - $time;
$t_func = $time / $nreps;
}
} else {
for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) {
$time = microtime(true);
for ($i = 0; $i < $n; ++$i) {
$result = ($funcname($p1, $p2));
}
$time = microtime(true) - $time;
$nreps = $n;
}
$t_func = $time / $nreps;
if ($t_func < $reptime) {
$nreps = (int)($reptime / $t_func);
$time = microtime(true);
for ($i = 0; $i < $nreps; ++$i) {
$result = ($funcname($p1, $p2));
}
$time = microtime(true) - $time;
$t_func = $time / $nreps;
}
}
$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);
}
?>
当我使用
benchmark.inc.php
中的代码运行
test.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!
\G
代表的含义。 - hakre\G
应该匹配前一个匹配的结尾。 - Qtax\G
与PCRE不同,因为PCRE需要一个偏移量。@NikiC:依赖于PCRE库吗?看起来新版本会存储上次匹配的偏移量并使用它。 - hakre\G
示例! - ridgerunner