Разбор кавычек 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.