从preg_match_all()突出显示主题字符串中的匹配结果

时间:2012-08-13 14:20:30

标签: php html regex match highlight

我试图用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的文本,但不包含。

4 个答案:

答案 0 :(得分:3)

这似乎适用于我迄今为止所抛出的所有示例。请注意,我已经打破了HTML-mangling部分中的抽象突出显示部分,以便在其他情况下重用:

<?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]',这仍然是正则表达式如何匹配的非常好的指示。

答案 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);

在您开始抱怨之前,先看看您在描述问题时遇到的缺点。我觉得你实际上想要实际解析匹配的结果。但是你想做子匹配。除非你解析正则表达式以找出使用哪些组,否则这不起作用。到目前为止情况并非如此,不是在你的问题中,也不是在这个答案中。

因此,请将此示例仅用于一个子组,该子组也必须是整个模式作为要求。除此之外,这是完全动态的。

相关:

答案 2 :(得分:0)

我不太熟悉在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];
}

其中$textpreg_match_all()

中使用的文字

认为我的解决方案比@ IMSoP快一点,因为他必须每次都排序,什么不排序。但我不确定。

我现在主要担心的是表现。但它可能无法以比这更快的速度发挥作用吗?

我一直试图让递归preg_replace_callback()的事情发生,但到目前为止我还没能做到这一点。 preg_replace_callback()似乎非常非常快。比我目前正在做的要快得多。

答案 3 :(得分:-1)

快速混搭,为什么要使用正则表达式?

$content = "abc";
$endcontent = "";

for($i = 0; $i > strlen($content); $i++)
{
    $endcontent .= "<span class=\"match0\"><span class=\"match1\">" . $content[$i] . "</span></span>";
}

echo $endcontent;