Разбор кавычек bbcode с переменными атрибутами в PHP

Я изо всех сил старался не приходить сюда и не спрашивать об этом, настаивая на том, что смогу решить это самостоятельно. Я сделал это, но я думал, что все равно приду сюда, чтобы 1) поделиться своим решением или 2) получить лучшее решение.

Я знаю, что по этому вопросу уже есть тонна вопросов о переполнении стека, большинство говорят, что используют библиотеку 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 ответа

Решение

Мне удалось придумать собственное, более элегантное решение, которое меньше кода и будет работать с вложенными кавычками.

Это будет только анализировать кавычки, содержимое внутри и вокруг кавычек все еще нужно будет конвертировать из 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

Так что это кажется очень длинным и округлым, но я не могу понять другой метод.

Для использования регулярных выражений для работы с BBCode, это довольно разумно... хотя вы, кажется, отбросили [b]Test Comment[/b] в конце.

Как упоминалось в комментариях, этот метод сломает момент, когда ваши теги станут вложенными. Ранее я писал об этой проблеме, и почти единственное разумное решение - это создание "настоящего" парсера для борьбы с этим безумием. Мне еще предстоит встретиться с существующим сторонним парсером BBCode, который делает это правильно.

Однако, поскольку вы не считаете, что вложение является проблемой, этот код должен работать достаточно хорошо. Не забудьте отфильтровать атрибуты в тегах для недружественных символов. Если site_url не делает этого, значит, вы создали уязвимость XSS.

Другие вопросы по тегам