【问题标题】:Parsing bbcode quotes with variable attributes in PHP在 PHP 中解析带有可变属性的 bbcode 引号
【发布时间】:2011-04-10 12:43:30
【问题描述】:

我一直在尽力避免来这里问这个问题,并坚持我可以自己解决。我已经这样做了,但我想我还是会来这里 1) 分享我的解决方案或 2) 获得更好的解决方案。

我知道已经有很多关于这个的 stackoverflow 问题,大多数人说使用 PEAR 库,但没有一个是关于我的具体问题。

基本上我希望能够解析 bbcode 引用标签,但是这个引用可以有可变数量的属性或根本没有属性,所以一个简单的 preg_replace 不会像下划线那样工作标记。

一个字符串中也可以有多个引号标签,这是我如何解决它的一个示例。谁能建议一种更好的方法来避免多个正则表达式和 foreach 循环?

(应该注意我正在解析示例中的强标记,但我在代码的其他地方执行此操作,这是我在此处特别苦苦挣扎并询问的引号)

$string = "[quote name='Rob' user_id='1' id='1' timestamp='1294120376']
My text here
[/quote]

[quote name='Rob' user_id='1' id='2' timestamp='1302442553']
Lorem ipsum dolor sit amet
[/quote]

Test Comment";

preg_match_all('/\[quote(.*?)](.*?)\[\/quote\]/msi', $string, $matches);

$quotes = array();

foreach($matches[1] as $id => $match)
{
    preg_match_all('/(\w*?)=\'(.*?)\'/msi', $match, $attr_matches);

    array_push($quotes, array(
        'text'          =>  trim($matches[2][$id]),
        'attributes'    =>  array_combine($attr_matches[1], $attr_matches[2])
    ));
}

echo '<pre>'.print_r($quotes,1).'</pre>';

这将输出以下内容:

Array
(
    [0] => Array
        (
            [text] => My text here
            [attributes] => Array
                (
                    [name] => Rob
                    [user_id] => 1
                    [id] => 1
                    [timestamp] => 1294120376
                )

        )

    [1] => Array
        (
            [text] => Lorem ipsum dolor sit amet
            [attributes] => Array
                (
                    [name] => Rob
                    [user_id] => 1
                    [id] => 2
                    [timestamp] => 1302442553
                )

        )

)

然后我简单地构建 HTML

$bbcode = '';

foreach($quotes as $quote)
{
    $attributes = array();
    foreach($quote['attributes'] as $key => $value)
    {
        switch($key)
        {
            case 'id':
                $attributes[] = '<a href="'.site_url('forums/findpost/'.$value).'">Permalink</a>';
            break;
            case 'name':
                if(isset($quote['attributes']['user_id']))
                {
                    $attributes[] = 'By <a href="'.site_url('user/profile/'.$quote['attributes']['user_id'].'/'.$value).'">'.$value.'</a>';
                }
                else
                {
                    $attributes[] = 'By '.$value;
                }
            break;
            case 'timestamp':
                $attributes[] = 'On '.date('d F Y - H:i A', $value);
            break;
        }
    }

    if(!empty($attributes))
    {
        $bbcode .= '<p class="citation">'.implode(' | ', $attributes).'</p>';
    }


    $bbcode .= '<blockquote>
        '.$quote['text'].'
    </blockquote>';
}

echo $bbcode;

这将输出以下内容:

<p class="citation">By <a href="http://domain.com/user/profile/1/Rob.html">Rob</a> | <a href="http://domain.com/forums/findpost/1.html">Permalink</a> | On 04 January 2011 - 05:52 AM</p>
<blockquote>
    My text here
</blockquote>

<p class="citation">By <a href="http://domain.com/user/profile/1/Rob.html">Rob</a> | <a href="http://domain.com/forums/findpost/2.html">Permalink</a> | On 10 April 2011 - 14:35 PM</p>
<blockquote>
    Lorem ipsum dolor sit amet
</blockquote>

所以这似乎是一个非常漫长而迂回的方法,但我无法理解另一种方法。有人...?

【问题讨论】:

  • 当你得到这样的输入时它会失败:[quote ...] foo [quote ...] bar [/quote] foo [/quote]
  • 为了我的目的,引号内的引号永远不会发生,但一个简单的解决方案是将它放在一个函数中并递归调用它。
  • 好的。但是你不能简单地递归调用它(至少,不能不改变你的正则表达式)。您将替换 [quote ...] foo [quote ...] bar [/quote] 并留下 [/quote] 悬空。

标签: php bbcode


【解决方案1】:

我已经设法提出了我自己的更优雅的解决方案,它既减少了代码又可以使用嵌套引号。

这只会解析引号,引号内和引号周围的内容仍然需要从 bbcode 转换,但有很多可用的资源。

function parse_quote($matches) {
    $bbcode = '';
    preg_match_all('/(\w*?)=\'(.*?)\'/msi', $matches[1], $attr_matches);
    $attributes = array_combine($attr_matches[1], $attr_matches[2]);
    if(!empty($attributes))
    {
        $attribute_strings = array();
        foreach($attributes as $key => $value)
        {
            switch($key)
            {
                case 'id':
                    $attribute_strings[] = '<a href="http://domain.com/forums/findpost/'.$value.'">Permalink</a>';
                break;
                case 'name':
                    if(isset($quote['attributes']['user_id']))
                    {
                        $attribute_strings[] = 'By <a href="http://domain.com/user/profile/'.$attributes['user_id'].'/'.$value.'">'.$value.'</a>';
                    }
                    else
                    {
                        $attribute_strings[] = 'By '.$value;
                    }
                break;
                case 'timestamp':
                    $attribute_strings[] = 'On '.date('d F Y - H:i A', $value);
                break;
            }
        }


        {
            $citation = '<p class="citation">'.implode(' | ', $attribute_strings).'</p>'."\n";
        }
    }
    else
    {
        $citation = '';
    }

    return $citation.'<blockquote>';
}

$string = "[quote name='Rob' user_id='1' id='1' timestamp='1294120376']
[quote name='Rob' user_id='1' id='2' timestamp='1302442553']
[quote name='Rob' user_id='1' id='3' timestamp='1302442553']
Test at a comment of a third depth
[/quote]
Lorem ipsum dolor sit amet
[/quote]
This is my comment
[/quote]

[b]Test Comment[/b]";

$new_string = str_replace('[/quote]', '</blockquote>', $string);
echo preg_replace_callback('/\[quote(.*?)\]/msi','parse_quote', $new_string);

这应该返回以下内容

    <p class="citation">By Rob | <a href="http://domain.comforums/findpost/1">Permalink</a> | On 04 January 2011 - 05:52 AM</p>
<blockquote>
<p class="citation">By Rob | <a href="http://domain.comforums/findpost/2">Permalink</a> | On 10 April 2011 - 14:35 PM</p>
<blockquote>
<p class="citation">By Rob | <a href="http://domain.comforums/findpost/3">Permalink</a> | On 10 April 2011 - 14:35 PM</p>

<blockquote>
Test at a comment of a third depth
</blockquote>
Lorem ipsum dolor sit amet
</blockquote>
This is my comment
</blockquote>

Test Comment

【讨论】:

    【解决方案2】:

    所以这似乎是一个非常漫长而迂回的方法,但我无法理解另一种方法。

    对于使用 regex 来处理 BBCode,它实际上是相当理智的......虽然你似乎在最后放弃了 [b]Test Comment[/b]

    正如在 cmets 中所提到的,这种方法会在您的标签变得可嵌套的瞬间中断。 I've previously written about that problem,几乎唯一理智的解决方案就是构建一个“真正的”解析器来处理这种疯狂。我还没有遇到可以正确执行此操作的现有第三方 BBCode 解析器。

    但是,由于您认为嵌套不是问题,因此该代码应该可以运行良好。不要忘记过滤不友好字符的标签中的属性。如果site_url 没有这样做,那么您已经创建了一个 XSS 漏洞。

    【讨论】:

    • 感谢您将我带到其他地方的链接,我已经用一个新的解决方案编辑了这个问题,该解决方案的代码更少,并且可以使用嵌套引号。我认为最好的办法是分别替换引号的开头和结尾,而不用担心中间的内容。
    • 只要您不需要实际处理标签之间的任何文本,并且可以相信用户记得关闭他们的打开,那么该解决方案应该非常适合您。
    • 我确实需要处理标签之间的数据,但这可以在其他地方相对容易地发生(例如,强标签和下划线标签)。但是引号足够复杂,需要分开,所以现在我可以做的是解析整个用户输入,它将处理引号内外的所有内容,然后只处理引号。
    • 抱歉,回答我自己的问题的礼仪是什么,您的回答很有用,所以我很乐意接受它作为答案,还是应该保持原样?
    • 如果您认为发布的任何答案都对您没有帮助,那么实际上可以为您自己的问题发布一个新答案,详细说明您为解决您的问题所做的更改。对于可能正在阅读的其他人,仅供参考,a recent change 意味着新用户现在需要等待一天才能发布自我回答。
    猜你喜欢
    • 1970-01-01
    • 2023-04-11
    • 2012-08-09
    • 1970-01-01
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 2012-09-03
    • 2014-08-28
    相关资源
    最近更新 更多