编辑
我已经重新编写了代码! 现在它包含以下列出的更改。此外,我进行了广泛的测试(因为它们太多而不会在此发布)以查找错误。到目前为止,我还没有发现任何错误。
现在这个函数已经分成了两部分:有一个独立的函数 preg_split,它接受一个正则表达式并返回一个包含裸表达式(不带分隔符)和修饰符数组的数组。这可能会很方便(实际上已经很方便了;这就是我做出这个改变的原因)。
代码现在正确处理反向引用。这对我的目的来说是必要的。添加它并不困难,用于捕获反向引用的正则表达式看起来很奇怪(实际上可能非常低效,对我来说看起来像 NP-hard——但这只是一种直觉,并且仅适用于奇怪的边缘情况)。顺便问一下,有没有人知道比我的方法更好的检查匹配数量是否为奇数的方法?否定回溯在这里行不通,因为它们只接受固定长度的字符串而不是正则表达式。但是,在这里我需要使用正则表达式来测试前面的反斜杠是否被转义。
此外,我不知道 PHP 在缓存匿名 create_function 使用方面有多好。从性能上讲,这可能不是最佳解决方案,但它似乎足够好。
我已经修复了一个健全性检查中的错误。
我已经删除了过时修饰符的取消,因为我的测试表明这是不必要的。
顺便说一下,这段代码是我正在PHP中为各种语言编写的语法突出显示器的核心组件之一,因为我对其他地方列出的替代方案不满意。
谢谢!
porneL,eyelidlessness,做得很好!非常感谢。 我实际上已经放弃了。
我在您的解决方案基础上进行了改进,并想在此分享。 我没有实现重新编号后向引用,因为在我的情况下这并不相关(我认为…)。 也许以后会变得必要。
一些问题...
有一件事,@eyelidlessness:为什么您觉得有必要取消旧的修饰符?就我所看到的,这并不必要,因为修饰符只是局部应用的。
啊是的,还有一件事。 您转义分隔符的方式似乎过于复杂。 能否解释一下为什么您认为这是必要的? 我相信我的版本也应该可以工作,但我可能非常错误。
另外,我已经更改了您的函数签名以适应我的需求。我认为我的版本更加通用。当然,我可能是错的。
顺便说一句,现在你应该意识到在SO上使用真实姓名的重要性了。;-)我无法在代码中给你真正的信用。:-/
代码
无论如何,我想分享我的目前结果,因为我不敢相信没有人需要这样的东西。代码似乎工作得非常好。 尽管还需要进行广泛的测试。请评论!
没有更多的拖延了...
function preg_merge($glue, array $expressions, array $names = array()) {
// … then, a miracle occurs.
// Sanity check …
$use_names = ($names !== null and count($names) !== 0);
if (
$use_names and count($names) !== count($expressions) or
!is_string($glue)
)
return false;
$result = array();
// For keeping track of the names for sub-matches.
$names_count = 0;
// For keeping track of *all* captures to re-adjust backreferences.
$capture_count = 0;
foreach ($expressions as $expression) {
if ($use_names)
$name = str_replace(' ', '_', $names[$names_count++]);
// Get delimiters and modifiers:
$stripped = preg_strip($expression);
if ($stripped === false)
return false;
list($sub_expr, $modifiers) = $stripped;
// Re-adjust backreferences:
// We assume that the expression is correct and therefore don't check
// for matching parentheses.
$number_of_captures = preg_match_all('/\([^?]|\(\?[^:]/', $sub_expr, $_);
if ($number_of_captures === false)
return false;
if ($number_of_captures > 0) {
// NB: This looks NP-hard. Consider replacing.
$backref_expr = '/
( # Only match when not escaped:
[^\\\\] # guarantee an even number of backslashes
(\\\\*?)\\2 # (twice n, preceded by something else).
)
\\\\ (\d) # Backslash followed by a digit.
/x';
$sub_expr = preg_replace_callback(
$backref_expr,
create_function(
'$m',
'return $m[1] . "\\\\" . ((int)$m[3] + ' . $capture_count . ');'
),
$sub_expr
);
$capture_count += $number_of_captures;
}
// Last, construct the new sub-match:
$modifiers = implode('', $modifiers);
$sub_modifiers = "(?$modifiers)";
if ($sub_modifiers === '(?)')
$sub_modifiers = '';
$sub_name = $use_names ? "?<$name>" : '?:';
$new_expr = "($sub_name$sub_modifiers$sub_expr)";
$result[] = $new_expr;
}
return '/' . implode($glue, $result) . '/';
}
/**
* Strips a regular expression string off its delimiters and modifiers.
* Additionally, normalize the delimiters (i.e. reformat the pattern so that
* it could have used '/' as delimiter).
*
* @param string $expression The regular expression string to strip.
* @return array An array whose first entry is the expression itself, the
* second an array of delimiters. If the argument is not a valid regular
* expression, returns <code>FALSE</code>.
*
*/
function preg_strip($expression) {
if (preg_match('/^(.)(.*)\\1([imsxeADSUXJu]*)$/s', $expression, $matches) !== 1)
return false;
$delim = $matches[1];
$sub_expr = $matches[2];
if ($delim !== '/') {
// Replace occurrences by the escaped delimiter by its unescaped
// version and escape new delimiter.
$sub_expr = str_replace("\\$delim", $delim, $sub_expr);
$sub_expr = str_replace('/', '\\/', $sub_expr);
}
$modifiers = $matches[3] === '' ? array() : str_split(trim($matches[3]));
return array($sub_expr, $modifiers);
}
PS:我已将此帖子设置为维基社区可编辑。你知道这意味着什么……!