我正在尝试使用从 preg_match_all() 返回的 $matches 数组突出显示主题字符串。让我从一个例子开始:
preg_match_all("/(.)/", "abc", $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
这将返回:
Array
(
[0] => Array
(
[0] => Array
(
[0] => a
[1] => 0
)
[1] => Array
(
[0] => a
[1] => 0
)
)
[1] => Array
(
[0] => Array
(
[0] => b
[1] => 1
)
[1] => Array
(
[0] => b
[1] => 1
)
)
[2] => Array
(
[0] => Array
(
[0] => c
[1] => 2
)
[1] => Array
(
[0] => c
[1] => 2
)
)
)
在这种情况下,我想做的是突出显示总消耗的数据和每个反向引用。
输出应如下所示:
<span class="match0">
<span class="match1">a</span>
</span>
<span class="match0">
<span class="match1">b</span>
</span>
<span class="match0">
<span class="match1">c</span>
</span>
再比如:
preg_match_all("/(abc)/", "abc", $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
应返回:
<span class="match0"><span class="match1">abc</span></span>
我希望这已经足够清楚了。
我想突出显示总体消耗的数据并突出显示每个反向引用。
提前谢谢。如果有什么不清楚的地方,请询问。
注意:它不能破坏 html。正则表达式 AND 输入字符串对代码是未知的,并且是完全动态的。因此,搜索字符串可以是 html,匹配的数据可以包含类似 html 的文本,也可以不包含。
到目前为止,这似乎适用于我抛出的所有示例。请注意,我已经从 HTML 修饰部分破坏了抽象突出显示部分,以便在其他情况下可重用:
<?php
/**
* Runs a regex against a string, and return a version of that string with matches highlighted
* the outermost match is marked with [0]...[/0], the first sub-group with [1]...[/1] etc
*
* @param string $regex Regular expression ready to be passed to preg_match_all
* @param string $input
* @return string
*/
function highlight_regex_matches($regex, $input)
{
$matches = array();
preg_match_all($regex, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
// Arrange matches into groups based on their starting and ending offsets
$matches_by_position = array();
foreach ( $matches as $sub_matches )
{
foreach ( $sub_matches as $match_group => $match_data )
{
$start_position = $match_data[1];
$end_position = $start_position + strlen($match_data[0]);
$matches_by_position[$start_position]['START'][] = $match_group;
$matches_by_position[$end_position]['END'][] = $match_group;
}
}
// Now proceed through that array, annotoating the original string
// Note that we have to pass through BACKWARDS, or we break the offset information
$output = $input;
krsort($matches_by_position);
foreach ( $matches_by_position as $position => $matches )
{
$insertion = '';
// First, assemble any ENDING groups, nested highest-group first
if ( is_array($matches['END']) )
{
krsort($matches['END']);
foreach ( $matches['END'] as $ending_group )
{
$insertion .= "[/$ending_group]";
}
}
// Then, any STARTING groups, nested lowest-group first
if ( is_array($matches['START']) )
{
ksort($matches['START']);
foreach ( $matches['START'] as $starting_group )
{
$insertion .= "[$starting_group]";
}
}
// Insert into output
$output = substr_replace($output, $insertion, $position, 0);
}
return $output;
}
/**
* Given a regex and a string containing unescaped HTML, return a blob of HTML
* with the original string escaped, and matches highlighted using <span> tags
*
* @param string $regex Regular expression ready to be passed to preg_match_all
* @param string $input
* @return string HTML ready to display :)
*/
function highlight_regex_as_html($regex, $raw_html)
{
// Add the (deliberately non-HTML) highlight tokens
$highlighted = highlight_regex_matches($regex, $raw_html);
// Escape the HTML from the input
$highlighted = htmlspecialchars($highlighted);
// Substitute the match tokens with desired HTML
$highlighted = preg_replace('#'[([0-9]+)']#', '<span class="match''1">', $highlighted);
$highlighted = preg_replace('#'[/([0-9]+)']#', '</span>', $highlighted);
return $highlighted;
}
注意:正如hakra在聊天中向我指出的那样,如果正则表达式中的子组可以在一个整体匹配中多次出现(例如'/a(b|c)+/'),preg_match_all
只会告诉你这些匹配中的最后一个 - 所以highlight_regex_matches('/a(b|c)+/', 'abc')
返回'[0]ab[1]c[/1][/0]'
不会像你期望/想要的那样'[0]a[1]b[/1][1]c[/1][/0]'
。不过,该之外的所有匹配组仍然可以正常工作,因此highlight_regex_matches('/a((b|c)+)/', 'abc')
给出了'[0]a[1]b[2]c[/2][/1][/0]'
这仍然很好地指示了正则表达式的匹配方式。
阅读您在第一个答案下的评论,我很确定您并没有真正按照您的意图制定问题。但是,遵循您在具体中要求的内容:
$pattern = "/(.)/";
$subject = "abc";
$callback = function($matches) {
if ($matches[0] !== $matches[1]) {
throw new InvalidArgumentException(
sprintf('you do not match thee requirements, go away: %s'
, print_r($matches, 1))
);
}
return sprintf('<span class="match0"><span class="match1">%s</span></span>'
, htmlspecialchars($matches[1]));
};
$result = preg_replace_callback($pattern, $callback, $subject);
在你现在开始抱怨之前,先看看你在描述问题方面的缺点在哪里。我感觉你实际上想实际解析匹配的结果。但是,您想进行子匹配。除非您也解析正则表达式以找出使用了哪些组,否则这不起作用。到目前为止,情况并非如此,在您的问题中不是,也不是这个答案。
因此,请仅针对一个子组,该子组也必须是整个模式作为要求。除此之外,这是完全动态的。
相关:
- 如何使用 preg_match_all() 获取子组匹配的所有捕获?
- 忽略preg_replace中的 html 标记
我不太熟悉在stackoverflow上发帖,所以我希望我不要搞砸这个。我这样做的方式与@IMSoP几乎相同,但略有不同:
我像这样存储标签:
$tags[ $matched_pos ]['open'][$backref_nr] = "open tag";
$tags[ $matched_pos + $len ]['close'][$backref_nr] = "close tag";
如您所见,几乎与@IMSoP相同。
然后我像这样构造字符串,而不是像@IMSoP那样插入和排序:
$finalStr = "";
for ($i = 0; $i <= strlen($text); $i++) {
if (isset($tags[$i])) {
foreach ($tags[$i] as $tag) {
foreach ($tag as $span) {
$finalStr .= $span;
}
}
}
$finalStr .= $text[$i];
}
其中$text
是preg_match_all()
中使用的文本
我认为我的解决方案比@IMSoP的略快,因为他每次都必须排序,而不是排序。但我不确定。
我现在主要担心的是性能。但可能只是不可能让它比这更快地工作?
我一直在尝试让递归preg_replace_callback()
的事情继续下去,但到目前为止我还没有能够让它工作。 preg_replace_callback() 似乎非常非常快。无论如何,比我目前所做的要快得多。
一个快速的混搭,为什么要使用正则表达式?
$content = "abc";
$endcontent = "";
for($i = 0; $i > strlen($content); $i++)
{
$endcontent .= "<span class='"match0'"><span class='"match1'">" . $content[$i] . "</span></span>";
}
echo $endcontent;