PHP

PHP и BBCode

У меня периодически появляется потребность в использовании «интерпретатора» BBCode в своих проектах (написанного на PHP), и постоянно нет времени искать какое-то более-менее удобоваримое решение, что в итоге выливается в использование или создание «костылей» для каждого конкретного случая.
Но вот, похоже, получилось найти то, что хотелось.

Моя претензия к подобным готовым решениям обычно в первую очередь заключается в неспособности этих библиотек правильно обрабатывать абзацы. Фактически они обычно вообще не используют абзацы (тэг P), вместо этого в результате своей работы они просто вставляют тег <br /> , заменяя обычные символы переноса строки. Я считаю такой метод эмулирования абзацев в 98 процентах, мягко говоря, не уместным. Но так как перенос строк по средствам <br /> намного легче реализовать вместо «человеческих» <p>, так большинство и делает Некоторые даже придумывают оправдания, что мол с br даже правильнее, отчасти, из-за подобной лени разработчиков различных готовых библиотек, другая часть людей думает, что тэг P является устаревшим (ведь даже во многих готовых продуктах и сайтах абзацы формируются путем использования <br />)

Но, кажется, есть свет в конце туннеля. Это готовый класс для работы с BBCode, который, судя по всему, отлично справляется со своей задачей (ничего лучше пока не видел). Единственный минус в том, что документация, представленная на сайте, не на русском языке. Этот минус я и хочу побороть в этой статье, приведя пример использования класса с русскими комментариями.

Для начала нужно скачать библиотеку (на момент написания статьи версия библиотеки была 0.3.3). В скачанном архиве в папке src вы обнаружите два нужных нам файла: stringparser.class.php и stringparser_bbcode.class.php.

Для примера предположим, что у нас есть пустой файл «index.php» и рядом с ним мы создадим папку «/bbcode/», содержащую в себе два упомянутых выше файла.
Для примера минимальное содержимое файла «index.php» должно быть таким (запустив этот пример можно будет сразу увидеть, работает ли библиотека):
< ?php
//Вставляем файл библиотеки
require_once 'bbcode/stringparser_bbcode.class.php';
 
//Создаем объект класса StringParser_BBCode
$bbcode = new StringParser_BBCode ();
 
//Добавляем объекту класса понятие о тэге [b]
//(в итоге только этот тэг и будет
//обрабатываться этим классом)
$bbcode->addCode ('b', 'simple_replace', null, array ('start_tag' => '<b>', 'end_tag' => '</b>'),
 'inline', array ('block', 'inline'), array ());
 
//Обрабатываем тестовую строку и выводим ее в браузер
echo $bbcode->parse ('Тестовый текст, это слово должно быть [b]жирным
');
?>[/PHP]
Функция addCode

Наибольший интерес в этом коде может вызвать, пожалуй, функция addCode у объекта класса StringParser_BBCode, вот ее прототип и список описание параметров:
void addCode (string $code, string $type, string $callback, string $params, string $content_type,
 array $allowed_in, array $not_allowed_in);

Эта функция добавляет понятие о тех или иных кодах (bb-кодах) для объекта класса, чтоб он мог потом обнаружить эти коды в тексте и соответствующим образом обработать их. Т.е. можно сказать, что изначально объект класса StringParser_BBCode вообще ничего не знает о стандартных bb-кодах и не способен обработать как-либо их. Поэтому этот объект нужно будет после каждой инициализации «обучать» всем разновидностям bb-кодов.

$code (в примере имеет значение ‘b’)
Код, который нужно искать в тексте для обработки. Т.е. если указать код test , то потом в обрабатываемом тексте будет искаться тэг
 и обрабатываться в соответствии с указаниями в других параметрах рассматриваемой функции.

$type (в примере имеет значение ‘simple_replace’)
Указание того, как тэг должен обрабатываться (какого он типа). Есть различные предопределенные типы тэгов, которые будут описаны ниже. В нашем же примере указан тип ‘simple_replace’ который указывает на то, что тэг будет парным (открывающийся тэг [b] и закрывающийся 
) и что эти тэги будут заменены на указанные ниже html тэги.

$callback (в примере имеет значение null)
Позволяет указать имя функции, которая должна будет вызваться при обработке найденного тэга в тексте. В случае с типом тэга ‘simple_replace’ такая функция не вызывается, и, соответственно, в этом параметре можно указать null.

$params (в примере имеет значение array(‘start_tag’ => ‘‘, ‘end_tag’ => ‘‘))
В этом параметре в основном указывается, какой нужно вставлять html тэг взамен bb тэга. Наименование параметров напрямую зависит от того, какой тип тэга мы указали в параметре $type.

$content_type (в примере имеет значение ‘inline’)
Тип внутреннего содержимого тега. Может принимать значения: ‘inline’, ‘block’, ‘link’, ‘image’. Если я не ошибаюсь, можно прописывать и свои типы чтобы потом можно было указывать для этого содержимого свои индивидуальные фильтры (пример использования фильтров смотрите ниже).

$allowed_in (в примере имеет значение array (‘block’, ‘inline’))
В этом параметре можно указать, внутри каких типов объектов может находиться создаваемый bb-код (его обработка будет просто игнорироваться в ином случае). В нашем примере мы указали, что элемент может находиться как внутри блочных элементов, так и внутри линейных.

$not_allowed_in (в примере имеет значение array ())
Имеет назначение, противоположное по смыслу предыдущему параметру.

Виды обработки тэгов

Описание вариантов значения параметра $type в функции addCode.
‘simple_replace’
Описывает простой парный тэг. При использовании этого типа обработки тэга в параметре функции ‘params’ должны обязательно присутствовать две ячейки: $params['start_tag'] и $params['end_tag']. ‘start_tag’ должен в себе содержать аналог открывающегося тэга в хтмл, а ‘end_tag’ – закрывающегося тэга соответственно.
‘simple_replace_single’
То же самое, что ‘simple_replace’, но используется только лишь для одинарных тэгов, которые, собственно говоря, не имеют содержимого (типа br, hr и т.п.). Требует наличия только параметра $params['start_tag'].
‘callback_replace’
При этом типе вы перекладываете на себя обработку по найденным совпадениям (с помощью своей callback функции) для парного тэга.
‘callback_replace_single’
То же самое, что и ‘callback_replace’, но только для одинарных тэгов.
‘usecontent’
То же самое что и ‘callback_replace’, только в содержимом такого тэга другие тэги не будут обрабатываться, например, это удобно для тэга code.
‘usecontent?’
Этот тип может себя вести как ‘usecontent’ или же как ‘callback_replace’ в зависимости от ситуации. Актуальность того или иного варианта определяется за счет присутствия заранее предполагаемого атрибута в bb тэге. Если атрибут найден, то будет использоваться обработка ‘callback_replace’, в другом случае тэг будет обрабатываться как ‘usecontent’. Имя атрибута для поиска указывается через параметр $params['usecontent_param']. Если указано имя default, то подразумевается значение атрибута, присвоенное непосредственно тэгу, например, , значением атрибута default будет текст «http://link». Этот прием часто используется, например, для тэга . Этот тэг может использоваться в двух формах: [url]http://www.example.com/ и [url=http://www.example.com/]Текст ссылки, а так же жирный текст. В первом случае будет использоваться тип ‘usecontent’, т.к. текст ссылки должен выводиться без какого-либо форматирования (и, собственно, сама ссылка будет некорректна, если в ней будут посторонние символы). В другом случае должен быть использован тип ‘callback_replace’, т.к. сама ссылка передается отдельным параметром, а текст, обрамленный в ссылку, вполне может содержать в себе какое-то форматирование.
Примечание: Можно указать несколько параметров для их поиска, для чего в $params['usecontent_param'] нужно передать не строку, а массив, содержащий строки. Например: $bbcode->addCode (…, array(‘usecontent_param’ => array (‘parameter1′, ‘parameter2′)), …);.
‘callback_replace?’
Является противоположным вариантом типа ‘usecontent?’. Если один из атрибутов, указанных в usecontent_param, встречается в тэге, он будет обработан как ‘usecontent’, в противном случае как ‘callback_replace’.

Пример кода из «боевых» условий

Вот пример файла index.php с более расширенной конфигурацией класса для обрабатывания большего числа тэгов, в нем же и можно понять, как работают callback функции и т.п.:
< ?php
//Вставляем файл библиотеки
require_once 'bbcode/stringparser_bbcode.class.php';
 
//Приводит разнообразные переводы строк
//разных операционных систем в единый формат (\n)
function convertlinebreaks ($text) {
 return preg_replace ("/\015\012|\015|\012/", "\n", $text);
}
 
//Удалить все символы, кроме переводов строк
function bbcode_stripcontents ($text) {
 return preg_replace ("/[^\n]/", '', $text);
}
 
//Функция для обработки ссылок
function do_bbcode_url ($action, $attributes, $content, $params, $node_object) {
 if (!isset ($attributes['default'])) {
 $url = $content;
 $text = htmlspecialchars ($content);
 } else {
 $url = $attributes['default'];
 $text = $content;
 }
 //Часть функции, которая занимается
 //только валидацией данных тэга
 if ($action == 'validate') {
 if (substr ($url, 0, 5) == 'data:' || substr ($url, 0, 5) == 'file:'
 || substr ($url, 0, 11) == 'javascript:' || substr ($url, 0, 4) == 'jar:') {
 return false;
 }
 return true;
 }
 //Непосредственное преобразование тэга в
 //html вариант с возвращением результата
 return '<a href="'.htmlspecialchars ($url).'">'.$text.'';
}
 
// Функция для вставки изображений
function do_bbcode_img ($action, $attributes, $content, $params, $node_object) {
 //Часть функции, которая занимается
 //только валидацией данных тэга
 if ($action == 'validate') {
 if (substr ($content, 0, 5) == 'data:' || substr ($content, 0, 5) == 'file:'
 || substr ($content, 0, 11) == 'javascript:' || substr ($content, 0, 4) == 'jar:') {
 return false;
 }
 return true;
 }
 //Непосредственное преобразование тэга в
 //html вариант с возвращением результата
 return '<img src="'.htmlspecialchars($content).'" alt=""/>';
}
 
//Создаем объект класса StringParser_BBCode
$bbcode = new StringParser_BBCode();
 
//Добавляем фильтр (подробнее см. офф. документацию),
//задействуя нашу функцию convertlinebreaks, которая будет
//преобразовывать переводы строки в тексте к единому
$bbcode->addFilter (STRINGPARSER_FILTER_PRE, 'convertlinebreaks');
 
//Добавляем свои парсеры для разных типов объектов
//(подробнее см. офф. документацию)
//Мы указываем, через какую функцию должно пройти
//содержимое этих тэгов, например, через функцию
//htmlspecialchars для предотвращения XSS и т.д.
$bbcode->addParser (array ('block', 'inline', 'link', 'listitem'), 'htmlspecialchars');
$bbcode->addParser (array ('block', 'inline', 'link', 'listitem'), 'nl2br');
$bbcode->addParser ('list', 'bbcode_stripcontents');
 
//Добавляем bb-код [h1], используемый в виде:
//[h1]Текст заголовка первого уровня[/h1]
$bbcode->addCode ('h1', 'simple_replace', null, array ('start_tag' => '<h1>', 'end_tag' => '</h1>'),
 'block', array ('listitem', 'block', 'link'), array ());
//Добавляем bb-код [h2], используемый в виде:
//[h2]Текст заголовка второго уровня[/h2]
$bbcode->addCode ('h2', 'simple_replace', null, array ('start_tag' => '<h2>', 'end_tag' => '</h2>'),
 'block', array ('listitem', 'block', 'link'), array ());
//Добавляем bb-код [h3], используемый в виде:
//[h3]Текст заголовка третьего уровня

$bbcode->addCode ('h3', 'simple_replace', null, array ('start_tag' => '<h3>', 'end_tag' => '</h3>'),
'block', array ('listitem', 'block', 'link'), array ());
//Добавляем bb-код [h4], используемый в виде:
//[h4]Текст заголовка четвертого уровня[/h4]
$bbcode->addCode ('h4', 'simple_replace', null, array ('start_tag' => '<h4>', 'end_tag' => '</h4>'),
'block', array ('listitem', 'block', 'link'), array ());
//Добавляем bb-код [h5], используемый в виде:
//[h5]Текст заголовка пятого уровня[/h5]
$bbcode->addCode ('h5', 'simple_replace', null, array ('start_tag' => '<h5>', 'end_tag' => '</h5>'),
'block', array ('listitem', 'block', 'link'), array ());
//Добавляем bb-код [h6], используемый в виде:
//[h6]Текст заголовка шестого уровня[/h6]
$bbcode->addCode ('h6', 'simple_replace', null, array ('start_tag' => '<h6>', 'end_tag' => '</h6>'),
'block', array ('listitem', 'block', 'link'), array ());

//Устанавливаем флаги для bb-кодов с h1 до h6,
//указывая, что они являются блочными элементами,
//что будет в дальнейшем благотворно влиять на умную
//генерацию html кода. Такой элемент, к примеру, не сможет
//находиться внутри других блочных элементов
$bbcode->setCodeFlag('h1', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
$bbcode->setCodeFlag('h2', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
$bbcode->setCodeFlag('h3', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
$bbcode->setCodeFlag('h4', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
$bbcode->setCodeFlag('h5', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
$bbcode->setCodeFlag('h6', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);

//Добавляем bb-код , используемый в виде: //[b]выделенный текст
$bbcode->addCode ('b', 'simple_replace', null, array ('start_tag' => '<b>', 'end_tag' => '</b>'),
'inline', array ('listitem', 'block', 'inline', 'link'), array ());
//Добавляем bb-код , используемый в виде: //[i]наклонный текст
$bbcode->addCode ('i', 'simple_replace', null, array ('start_tag' => '<i>', 'end_tag' => '</i>'),
'inline', array ('listitem', 'block', 'inline', 'link'), array ());
//Добавляем bb-код , используемый в виде: //[url]http://www.needsite.domain и
//Текст ссылки
$bbcode->addCode ('url', 'usecontent?', 'do_bbcode_url', array ('usecontent_param' => 'default'),
'link', array ('listitem', 'block', 'inline'), array ('link'));
//Добавляем bb-код [link], используемый в виде:
//[link]http://www.needsite.domain[/link]
$bbcode->addCode ('link', 'callback_replace_single', 'do_bbcode_url', array (),
'link', array ('listitem', 'block', 'inline'), array ('link'));
//Добавляем bb-код
$bbcode->addCode ('img', 'usecontent', 'do_bbcode_img', array (),
'image', array ('listitem', 'block', 'inline', 'link'), array ());
//Добавляем bb-код
 (по смыслу то же самое,
//что и [img]), используемый в виде:
//[bild]http://www.needsite.domain/img.jpg

$bbcode->addCode ('bild', 'usecontent', 'do_bbcode_img', array (),
'image', array ('listitem', 'block', 'inline', 'link'), array ());

//Создаем группу image из bb-кодов img и bild
//для последующей возможности задания
//неких правил для этих групп
$bbcode->setOccurrenceType ('img', 'image');
$bbcode->setOccurrenceType ('bild', 'image');
//Указываем, что тэги из группы image
//могут встречаться (обрабатываться) в тексте не более
//двух раз. В нашем случае это нужно для того,
//чтобы пользователь не мог вставить более двух
//картинок в текст сообщения
$bbcode->setMaxOccurrences ('image', 2);

//Добавляем bb-код
$bbcode->addCode ('list', 'simple_replace', null, array ('start_tag' => '<ul>', 'end_tag' => '</ul>'),
 'list', array ('block', 'listitem'), array ());
//Добавляем bb-код [*], указывая, что этот тэг
//может использоваться только внутри тэга
//с типом list (этот тип мы присвоили выше тэгу [list])
$bbcode->addCode ('*', 'simple_replace', null, array ('start_tag' => '<li>', 'end_tag' => '</li>'),
 'listitem', array ('list'), array ());
 
//Устанавливаем флаги для тэгов [list] и [*]
//Указываем, что для кода [*] закрывающийся тэг
//не обязателен, таким образом, возможна будет
//следующая конструкция:
//[list]
//[*] Item
//[*] Item
//

//Закрывающий тэг будет добавляться автоматически
//в процессе формирования html кода
$bbcode->setCodeFlag ('*', 'closetag', BBCODE_CLOSETAG_OPTIONAL);
//Как я понял, этот флаг обозначает, что тэг [*]
//всегда может быть использован только
//в начале новой строки
$bbcode->setCodeFlag ('*', 'paragraphs', true);
//
 является блочным элементом
$bbcode->setCodeFlag ('list', 'paragraph_type', BBCODE_PARAGRAPH_BLOCK_ELEMENT);
//Перед открывающимся тэгом [list]
//символ строки будет устранен
$bbcode->setCodeFlag ('list', 'opentag.before.newline', BBCODE_NEWLINE_DROP);
//Перед закрывающимся тэгом [list]
//символ строки будет устранен
$bbcode->setCodeFlag ('list', 'closetag.before.newline', BBCODE_NEWLINE_DROP);
//В итоге мы можем использовать списки в bb-коде,
//используя вместе теги list и *:
//[list]
//[*] Элемент списка
//[*] Элемент списка
//[*] и т.д.
//


//Активируем обработку параграфов
$bbcode->setRootParagraphHandling (true);
//Как я понял, таким образом указывается,
//какими символами нужно заменять встреченный
//перенос строки внутри абзаца
//(по сути, как обрабатывать пустые абзацы).
$bbcode->setParagraphHandlingParameters ("\n", '<p>', '</p>');

$res_text = "Тестовый текст для проверки работы класса";

//На всякий случай удаляем все оставшиеся
//символы переноса строки в виде "\r",
//если такие остались в тексте
$res_text = str_replace("\r", '', $res_text);

//Вуаля!
echo $bbcode->parse($res_text);[/PHP]
Послесловие

Я, конечно же, не сделал полный перевод документации, а только самый необходимый минимум, за более подробной документацией вы можете обратиться на официальный сайт (вообще, там описано куда больше различных возможностей).

Эту библиотеку также не составляет труда внедрить в какой-либо php фреймворк, я, к примеру, с успехом проделывал это для cackePHP.

Если вы тоже встречали подобные библиотеки (корректно работающие с абзацам! ) , интересно было бы узнать о них.
Добавлено: 20 Августа 2013 03:27:18 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...