在PHP中解析带有变量属性的bbcode引号

时间:2011-04-10 12:43:31

标签: php bbcode

我一直在尽力避免来这里问这个问题,并坚持认为我可以自己解决这个问题。我已经做到了,但我认为无论如何我都会来这里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>

所以这似乎是一个非常漫长的方式,但我无法理解另一种方法。任何人都...?

2 个答案:

答案 0 :(得分:2)

我已经设法提出了我自己更优雅的解决方案,这个解决方案代码更少,并且可以使用嵌套引号。

这只会解析引号,引号内和周围的内容仍然需要从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

答案 1 :(得分:0)

  

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

使用正则表达式与BBCode一起使用时,它实际上非常合理......尽管你似乎最后放弃了[b]Test Comment[/b]

正如评论中所提到的,此方法将打破您的标记变为可嵌套的瞬间。 I've previously written about that problem,而且几乎唯一合理的解决方案是构建一个“真正的”解析器来处理那种疯狂。我还没有遇到一个正确执行此操作的现有第三方BBCode解析器。

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