Русское сообщество fluxbb

Быстрый лёгкий надёжный форумный движок

Вы не вошли.

Объявление

Вы можете внести свой вклад в содержание сайта. Жертвователи попадут в почетную группу "Спонсоры". Поддержать сайт.

#1 2011-06-20 22:12:10

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Альтернативный парсер BBCODE. Регулярка.

Ищу замену стандартному парсеру бибикодов FluxBB. Мне не нравится, что его трудно расширять новыми кодами.
Смотрел парсеры в некоторых других движках -- тоже не в восторге. Помоему излишне громоздко всё.

Мой "идеальный сферический парсер в вакууме" должен работать так:

  • находить любые теги, оформленные синтаксически правильно, в т.ч. само-закрывающиеся теги типа [ hr /]

  • все теги имеют имена из латинских символов плюс одно исключение -- тег [ * ]

  • учитывать специальные случаи: теги [ code ] и [ nobb ], внутри них не надо искать другие теги

Очень мне захотелось составить такую регулярку, чтобы она вычленяла произвольные теги. Сложнее всего оказалось учесть [ code ]. Вот что получилось:

$universal = '{
\[(?P<tag> (?P<nonested> code|nobb) | \w+ | \*) (?P<attr> [^\]]*?)
(
  (?P<omit> \s* / \]) 
  |
  (
    \] (?P<inner> (?(nonested)(.*?) | ([^\[]*? | (?R))*))
    \[ / (?P=tag) \s* \]
  )
)
}xsm';

preg_match_all($universal, $text, $matches, PREG_OFFSET_CAPTURE);
$elements = array();
foreach ($matches[0] as $key => $match) {
    $elements[] = array(
        'tag'    => $matches['tag'][$key][0],
        'attr'    => isset($matches['attr'][$key][0]) ? $matches['attr'][$key][0] : '',
        'omit'    => ($matches['omit'][$key][1] > -1),
        'inner'    => isset($matches['inner'][$key][0]) ? $matches['inner'][$key][0] : ''
    );
        
}

echo '<pre>';
var_export($elements);
echo '</pre>';

На выходе в массиве $elements будут теги верхнего уровня. Чтобы разобрать вложенные надо обработать той же регуляркой текст из $elements[]['inner']. То есть предполагается некий рекурсивный обработчик.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#2 2011-06-20 22:36:01

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Поясню для тех, кто не очень понимает регуляки.

Здесь используется расширенный синтаксис PCRE с рекурсией (?R) и именованными областями.

Для наглядности я разрядил выражение пробелами и переводами строк, это возможно когда включаешь режим расширенного форматирования - модификатор "x" в конце регулярки.

Квадратные скобки имеют специальное значение в регулярках, поэтому приходится их экранировать типа \[ и \]

"." означает любой символ
"\s" означает любой "пустой" символ -- пробел, табуляция, перевод строки
".*" означает последовательность любых символов, возможно пустая последовательность. жадный поиск
".*?" означает  последовательность любых символов. НЕжадный поиск
"\w+" означает непустую последовательность букв латинского алфавита.

Именованная область помечается как (?P<name> blablabla). После этого в самой регулярке можно подставить её значение через <?P=name). Как ловится пара открывающий тег + закрывающий тег. Упрощенно:

\[(?P<tag>)\w+\] (.*) \[/(?P=tag)\]

Получется что в именованную область tag попадут буквы между квадратными скобками, потом эти буквы подставятся в подстроке [/tag]

Условная конструкция if-then-else реализуется с помощью такого синтаксиса:

(?(name) then | else)

Если область "name" непустая, то пытаемся искать текст "then", а если пустая, то текст "else".


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#3 2011-06-21 07:40:09

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Определять новые теги в этой системе очень просто. Надо только добавить метод-обработчик в класс Parser
Для примера воплотил несколько тегов

Скачать с Rapidshare: bbcodetest-20110621.zip
обновлено 21.06 в 17:14 MSK

Мне не хватает процедуры подготовки исходного текста (preparse). Типа автоматического закрытия [ * ] или правильно оформить ссылку. Если будет время, набросаю -- тоже через регулярки.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#4 2011-06-21 08:59:41

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

У меня ощущение по последним публикациям, что @artoodetoo собрался новый движок написать!? wink

Offline

#5 2011-06-21 13:20:40

Freeman
Участник
Из Санкт-Петербург
Зарегистрирован: 2010-07-31
Сообщений: 128
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@artoodetoo, не хочешь попробовать наш движок? Я всё-таки добился своего, у нас теперь есть форум с полноценной вики-разметкой, внутренней адресацией и прочими плюшками хорошей модели.

Из меня веб-программер хреновый, код писал Proger_XP. Я даже в код не заглядывал. Тем не менее, всё работает. Возможно, это не совсем во FluxBB-шном духе -- "скорость в ущерб остальному", но это рекурсивный, то есть надёжный парсер. Мы его даже на метровом исходнике гоняли -- работает, сцуко, не падает. smile

По идее, движок не зависит от разметки, и теоретически, его можно научить разбирать BB-код. Вот только зачем BB-код, когда есть вики, не пойму.  smile

Мы сейчас на форуме безо всяких панелей, разметку руками вводим, и особых неудобств не испытываем. Для рядовых пользователей панель нужна будет, согласен. Придется брать от WackoWiki или делать похожую.

Offline

#6 2011-06-21 15:12:23

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@Visman, не новый, а "параллельный" )))
совсем новый движок слишком затратно делать.

@Freeman, молодцы. я не считаю что есть какое-то "правильное" решение, к которому должны прийти все. если проект сильно завязан на вики, тогда логично и форум держать в той же разметке. иначе -- bbcode.
она имеет меньше возможностей, зато она похожа на всех форумах. то есть лично мне интереснее иметь удобный парсер bbcode, чтобы заводить новые проекты.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#7 2011-06-21 15:54:20

Freeman
Участник
Из Санкт-Петербург
Зарегистрирован: 2010-07-31
Сообщений: 128
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

artoodetoo пишет:

иначе -- bbcode.
она имеет меньше возможностей, зато она похожа на всех форумах.

Это временное явление. smile Чем больше людей будет продвигать расовую русскую разметку, тем меньше останется буржуйского BB-кода. Синтаксис WackoWiki в Рунете знаком многим.

Offline

#8 2011-06-21 16:20:41

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@Freeman, вики разметке уже огого сколько лет, как-то она не торопится захватывать мир.

кстати я обновил архив с примером парсера. допустимо использовать ссылки на классы CSS, флоаты и выравнивание текста. считаю вебмастеру это серьезная помощь. остро нужен preparse для исправления мелких косяков ввода.

ТЕСТ !


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#9 2011-06-22 13:30:42

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

В тесте добавил синтаксис для code, поправил отступы, добавил stripTag — нужно для индексации, а можно применить в поисковой выдаче.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#10 2011-06-22 14:12:26

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@artoodetoo, по скорости сравнивал вывод постов на оригинальном и новом парсерах?

Offline

#11 2011-06-22 16:55:51

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

нет. но думаю мой будет быстрее wink

edited: на самом деле скорость не главный фактор для меня.
мне кажется, что скорость должна вырасти, но если этого не произойдет я не расстроюсь.
гораздо важнее расширяемость.
я сейчас тружусь над парой реальных проектов типа "форум+странички" и возможности разметки для вебмастера меня волнуют больше, чем милисекунды. попробуйте в существующем парсере добавить в тег [ code ] "язык" для синтаксической разметки ([ code=js ], [ code=css ] )  — это задачка не для слабонервных.

сменить парсер на wiki markup или что-то еще тоже нехорошо, все-таки это форум, я сам могу принять другой стандарт, а заставлять перестраиваться рядовых участников не могу.

в разрабатываемом парсере обработка тегов bbcode сделана абсолютно прозрачно: через отдельные методы.
кроме того, новый парсер может обрабатывать несколько атрибутов тега ([ span class="highlighted" size="24px" ]).
кому не нужны новые возможности, тот просто не будет ими пользоваться, оставаясь на привычном минимальном наборе, а мастера смогут верстать побогаче.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#12 2011-07-12 10:30:39

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@Freeman, я задал вопрос на оффсайте про хорошую интеграцию форума и вики. Наверное тебе есть что сказать народу.

Я таки хочу оставить форуму разметку bbcode но иметь нормальные ссылки туда и сюда. И главное поиск должен быть единым.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#13 2011-07-12 14:10:05

Freeman
Участник
Из Санкт-Петербург
Зарегистрирован: 2010-07-31
Сообщений: 128
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

А в том и загвоздка, что вики как таковой у нас нет. Нам она не нужна. Практика показала, что вести большие документарные проекты проще всего на локальных файлах, пользуясь всеми преимуществами быстрого поиска, согласованных правок и удобством любимого Блокнота (пользую PSPad). Дока рассматривается как набор исходников -- грузится в SVN и рендерится по хуку на сервере.

Поэтому и стояла задача вытащить вики-разметку из вики как таковой. Нам удалось удачно вписать разметку в абстрактную модель, называемую средой (среда Wacko), которая имеет реализации в виде форума, блога и документации. Над единым поиском никто не задумывался: на форуме он и так есть, а дока и блог -- штуки авторские, и ведутся на файлах -- поиск Far-ом.

Мы уже выходили на команду WackoWiki, -- фактически это один Мартин (глава проекта). Он сказал, что пока не готов заниматься интеграцией стороннего движка. Если же мы сами когда-нибудь дойдём до вики, будет проще написать её с нуля, по нормальной постановке, чем ловить баги в чужом коде 2004-го года. Пока это не приоритетная задача.

А разговоры с другими разработчиками об интеграции разметки куда-либо (Drupal и т. п.) пока остаются разговорами.

Offline

#14 2011-07-12 18:38:25

Freeman
Участник
Из Санкт-Петербург
Зарегистрирован: 2010-07-31
Сообщений: 128
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

...или я чего-то не понял. Про синтаксис ссылок отписался на официальном форуме.

Offline

#15 2016-06-06 06:43:30

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@artoodetoo, какой результат по твоему парсеру? А то я тут играюсь со ckeditor + fluxbb, так парсер fluxbb вложенные теги, например div, отказывается обрабатывать.

Offline

#16 2016-06-07 07:50:18

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Ох ничего себе ты древнюю тему поднял big_smile

Тогда я не довел это до товарного вида, голого энтузизама не хватило. Сейчас мне кажется, что ббкод вообще не очень актуален. Как и сами классические форумы.

Мне кажется народ больше тяготеет к двум направлениям:
- вопрос-ответ по типу stackoverflow и
- мультиблоги с коментариями и репостами. так все социалки устроены.

В обоих случаях специальная разметка как таковая не видна, а неявно работают вариации маркдаун.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#17 2016-06-07 07:53:08

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

В любом случае, движок должен иметь парсер как плагин. Пусть вебмастер решает что брать: ббкод или маркдаун или ограниченный хтмл.


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#18 2016-06-11 19:52:57

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

<?php

$universal = '{
\[(?P<tag> (?P<nonested> code|nobb) | \w+ | \*) (?P<attr> [^\]]*?)
(?:
  (?P<omit> \s* / \])
  |
  (?:
    \] (?P<inner> (?(nonested)(.*?) | ([^\[]*? | (?R))*))
    \[ / (?P=tag) \s* \]
  )
)
}xsm';

$text = <<<EOF
[h2={"class":"western","style":"page-break-before: always"}]6.4.Поверки[/h2]
[div={"class":"western","style":"text-align: justify; line-height: 150%"}]На данной вкладке представлен список всех поверок, которые проводились в смене (рисунок 6.6). Для просмотра поверок в смене необходимо выбрать смену нажатием левой кнопкой мыши.[/div]

[size=12pt]Рисунок [background=#00FF00][size=10pt]6.6[/size][/background] – [font="comic sans ms,cursive"]Список поверок[/font][/size]
[hr /]
[img]data:image/png;base64,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[/img]
EOF;

preg_match_all($universal, $text, $matches, PREG_OFFSET_CAPTURE);
$elements = array();
foreach ($matches[0] as $key => $match) {
    $elements[] = array(
        'tag'    => $matches['tag'][$key][0],
        'attr'    => isset($matches['attr'][$key][0]) ? $matches['attr'][$key][0] : '',
        'omit'    => ($matches['omit'][$key][1] > -1),
        'inner'    => isset($matches['inner'][$key][0]) ? $matches['inner'][$key][0] : ''
    );

}

echo '<pre>';
//var_export($matches);
var_export($elements);
echo '</pre>';

Тег img тут не может регулярка уже обработать, упирается в ограничение на число шагов.

И тут ошибка с таймаутом smile https://regex101.com/r/mX1zU0/1 Но если число символов внутри тега уменьшить, то можно увидеть результат и выполненное число шагов.

Offline

#19 2016-06-13 01:09:30

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Мда, рекурсия в регулярке это риск. Элегантно, но хрупко.

Сейчас я бы попробовал такой алгоритм:
1. вырезать фрагменты с [ code ], [ nobb ] и подобными, внутри которых никакие коды не работают. результат обработки хранить отдельно.
2. поиск простой регуляркой на наличие любых ббкодов. просто по шаблону "нечто в квадратных скобках"
3а. выдачу п.2 обрабатывать в цикле: коллбеки для каждого вида ббкодов. конкретный коллбек может учитывать и изменять стек вложенности кодов. а также сигналить ошибки.
3б. текст, которых не в квадратных скобках, обработать на наличие смайлов и сниппетов типа очевидных URL.
4. вставить в текст результат п.1


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#20 2016-06-16 15:35:34

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Вот такой монстроузный набросочек наваял

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

class parser
{
	private $counter_code = 0;
	private $bb_array = array();
	private $regex_types = array(
		'color' => '%^(?:\#(?:[\dA-Fa-f]{3}){1,2}|(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|yellow|white))$%',
		'number' => '%^\d+$%',
		'img' => '%^(?:(?:ht|f)tps?://[^\s<"]+|data:image/[a-z]+;base64,(?:[a-zA-Z\d/\+\=]+))$%'
	);

	public $bbcodes = array(
		'code' => array(
				'open' => '</p><div class="codebox"><pre><code>',
				'close' => '</code></pre></div><p>',
		),
		'b' => array(
				'open' => '<strong>',
				'close' => '</strong>',
		),
		'i' => array(
				'open' => '<em>',
				'close' => '</em>',
		),
		'em' => array(
				'open' => '<em>',
				'close' => '</em>',
		),
		'u' => array(
				'open' => '<span class="bbu">',
				'close' => '</span>',
		),
		's' => array(
				'open' => '<span class="bbs">',
				'close' => '</span>',
		),
		'del' => array(
				'open' => '<del>',
				'close' => '</del>',
		),
		'ins' => array(
				'open' => '<ins>',
				'close' => '</ins>',
		),
		'h' => array(
				'open' => '</p><h5>',
				'close' => '</h5><p>',
		),
		'hr' => array(
				'open' => '</p><hr /><p>',
				'close' => '',
		),
		'color' => array(
				'open' => array(
					'attr' => array(
						'view' => '<span style="color: %attr%;">',
						'type' => 'color',
					),
				),
				'close' => '</span>',
		),
		'colour' => array(
				'open' => array(
					'attr' => array(
						'view' => '<span style="color: %attr%;">',
						'type' => 'color',
					),
				),
				'close' => '</span>',
		),
		'background' => array(
				'open' => array(
					'attr' => array(
						'view' => '<span style="background-color: %attr%;">',
						'type' => 'color',
					),
				),
				'close' => '</span>',
		),
		'size' => array(
				'open' => array(
					'ver1' => array(
						'view' => '<span style="font-size: %attr%px;">',
						'type' => 'number',
					),
					'ver2' => array(
						'view' => '<span style="font-size: %attr%;">',
						'format' => '%^\d+(?:em|ex|pt|px|\%)$%',
					),
				),
				'close' => '</span>',
		),
		'right' => array(
				'open' => '</p><p style="text-align: right;">',
				'close' => '</p><p>',
		),
		'center' => array(
				'open' => '</p><p style="text-align: center;">',
				'close' => '</p><p>',
		),
		'justify' => array(
				'open' => '</p><p style="text-align: justify;">',
				'close' => '</p><p>',
		),
		'mono' => array(
				'open' => '<code>',
				'close' => '</code>',
		),
		'font' => array(
				'open' => array(
					'attr' => array(
						'view' => '<span style="font-family: %attr%;">',
						'format' => '%^[a-z\d, -]+$%i',
					),
				),
				'close' => '</span>',
		),
		'img' => array(
				'open' => array(
					'attr' => array(
						'view' => '<span class="postimg"><img src="%body%" alt="%attr%" /></span>',
						'format' => '%^[^\n\t]+$%',
						'type_body' => 'img',
					),
					'no_attr' => array(
						'view' => '<span class="postimg"><img src="%body%" alt="%body%" /></span>',
						'type_body' => 'img',
					),
				),
				'close' => '',
		),
		'url' => array(
				'open' => array(
					'attr' => array(
						'view' => '<a href="%attr%" rel="nofollow">',
						'format' => '%^.+$%',
					),
					'no_attr' => array(
						'view' => '<a href="%body%" rel="nofollow">%body%',
						'format_body' => '%^.+$%',
					),
				),
				'close' => '</a>',
		),
	);

	// включим счетчик при открытии тега CODE
	private function _do_open_code()
	{
		$this->counter_code = 1;
	}

	// выключим счетчик при закрытии тега CODE
 	private function _do_close_code()
	{
		$this->counter_code = 0;
	}

	// тег HR не имеет закрывающей части
	private function _do_open_hr()
	{
		array_pop($this->bb_array);
	}

	// !!!!!!!!!!! переделать
	private function json_to_attr($tag, $attrjson)
	{
		$arr = json_decode(htmlspecialchars_decode($attrjson, ENT_QUOTES), true);

		if (! is_array($arr)) {
			return null;
		}

		$res = '';

		foreach ($arr as $key => $value) {
			$res .= ' ' . $key . '="' . $value . '"';
		}

		return $res;
	}

	public function parse($text, $is_signature = false)
	{
		$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
		$this->counter_code = 0;
		$this->bb_array = array();

		$parts = preg_split('%(\[(?:/[a-z][a-z\d]*|[a-z][a-z\d]*(?:|=(?:".*?"|\'.*?\'|&quot;.*?&quot;|&\#039;.*?&\#039;|{.*?}|[^\]]*?)))\])%ui', $text, null, PREG_SPLIT_DELIM_CAPTURE);

		echo "<pre>\n";
		var_export($parts);
		echo "</pre>\n";

		$text = '';

		reset($parts);
		while (list(, $part) = each($parts)) {
			// если это не тег, то сохраняем текст и переходим на следующий шаг
			if (! preg_match('%^\[(?:(?P<end>/)|)(?P<tag>[a-z][a-z\d]*)(?(end)|(?:|=(?:{(?P<attrjson>.*?)}|(?P<quote>&quot;|&\#039;|"|\'|)(?P<attr>.*?)(?P=quote))))\]$%ui', $part, $bb)) {
				$text .= $part;
				continue;
			}

			// если такого тега нет в разрешенных, то ...
			if(! isset($this->bbcodes[$bb['tag']])) {
				$text .= $part;
				continue;
			}

			// обрабатываем тег 'code'
			if($this->counter_code) {
				if ($bb['tag'] == 'code') {
					if(empty($bb['end'])) {
						$this->counter_code++;
					} else {
						$this->counter_code--;
					}
				}

				if($this->counter_code) {
					$text .= $part;
					continue;
				}
			}

			// *************
			// открытие тега
			if (empty($bb['end'])) {
				$repl = $this->bbcodes[$bb['tag']]['open'];

				// простая замена. не предусматривает атрибутов. проверок нет
				if (! is_array($repl)) {
					$part = $repl;
				} else { // проверка атрибутов для замены
					// тут будут найденные правила для замены
					$rules = null;

					// атрибуты в json и есть правило для них
					if (! empty($bb['attrjson']) && isset($repl['attrjson'])) {
						$bb['attr'] = $this->json_to_attr( $bb['tag'], $bb['attrjson'] );

						if (! is_null($bb['attr'])) {
							$rules = array( $repl['attrjson'] );
						}
					} else if (isset($bb['attr']) && $bb['attr'] !== '') { // есть атрибут у текущего бб-кода, забираем все правила, кроме двух
						unset( $repl['no_attr'], $repl['attrjson'] );
						$rules = $repl;
					} else if (isset($repl['no_attr'])) { // атрибута нет, но есть правило для этого случая
						$bb['attr'] = '';
						$rules = array( $repl['no_attr'] );
					}

					// ни одного правила не найдено
					if (is_null($rules)) {
						$text .= $part;
						continue;
					}

					$rule = null;

					// перебор правил и их проверка
					foreach ($rules as $cur) {
						// правило не содержит условий, например в 'no_attr'
						if (! is_array($cur)) {
							$rule = array( 'view' => $cur );
						} else {
							// если есть тип для атрибута, то регулярку подставляем из него
							if (isset($cur['type'])) {
								$cur['format'] = $this->regex_types[$cur['type']];
							}

							if (isset($cur['type_body'])) {
								$cur['format_body'] = $this->regex_types[$cur['type_body']];
							}

							// получаем тело бб-кода, если оно требуется для текущего правила
							if (isset($cur['format_body'])) {
								$bb['body'] = current( $parts );
							} else {
								unset( $bb['body'] );
							}

							if (isset($cur['format_body']) && isset($cur['format'])) {
								if (preg_match($cur['format_body'], $bb['body']) && preg_match($cur['format'], $bb['attr'])) {
									$rule = $cur;
									break;
								}
							} else if (isset($cur['format_body'])) {
								if (preg_match($cur['format_body'], $bb['body'])) {
									$rule = $cur;
									break;
								}
							} else {
								if (preg_match($cur['format'], $bb['attr'])) {
									$rule = $cur;
									break;
								}
							}
						}
					}

					// ни одно правило не подошло по условию/формату
					if (is_null($rule)) {
						$text .= $part;
						continue;
					}

					// применяем замену
					$part = str_replace('%attr%', $bb['attr'], $rule['view']);
					if (isset($bb['body'])) {
						$part = str_replace('%body%', $bb['body'], $part);
					}
				}

				$this->bb_array[] = $bb['tag'];

				$method = '_do_open_' . $bb['tag'];

			// *************
			// закрытие тега
			} else {
				if (empty($this->bb_array) || end($this->bb_array) != $bb['tag']) {
					$text .= $part;
					continue;
				}

				$part = $this->bbcodes[$bb['tag']]['close'];

				array_pop($this->bb_array);

				$method = '_do_close_' . $bb['tag'];
			}

			// вызываем дополнительный обработчик открытия/закрытия тега
			if (method_exists($this, $method)) {
				$this->{$method}();
			}

			$text .= $part;

			if (isset($bb['body'])) {
				next($parts);
			}
		}

		// закрываем не закрытые теги
		foreach ($this->bb_array as $tag) {
			$text .= $this->bbcodes[$tag]['close'];
		}

		return $text;
	}
}

$text = '[center="atr"]
[i]
[b][font=Arial]Тест текста[/font][/b][
[color=#ff0000][size=10em]Красный!!![/size][/color]
[/i][/center]
Думаю сделать список загруженных файлов в таком виде
[url=http://(_jpegshare_net_)/4d/7a/4d7a61b75f78abb3f1a46bf692fd65c8.png.html][img]http://(_jpegshare_net_)/thumbs/4d/7a/4d7a61b75f78abb3f1a46bf692fd65c8.jpg[/img][/url]
Нормально? Или не по фен-шую?
Т.е. кнопка удаления - вверху справа (За раз можно удалить только одну картинку, повешу сюда ajax дополнительно).
Внизу справа - вставка ссылки на файл/картинку.
Внизу слева - вставка превью, если есть в наличие.[img]
[h2={"class":"western","style":"page-break-before: always"}]6.4.Поверки[/h2][div={"class":"western","style":"text-align: justify; line-height: 150%"}]На данной вкладке представлен список всех поверок, которые проводились в смене (рисунок 6.6). Для просмотра поверок в смене необходимо выбрать смену нажатием левой кнопкой мыши.[/div]
[img]data:image/png;base64,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[/img]
[size=12pt]Рисунок [background=#00FF00]6.6[/background] – [font="comic sans ms,cursive"]Список поверок[/font][/size]';

$parser = new parser();
echo '<p>'.$parser->parse($text).'</p>';

Правда в нем вывод даже не всех стандартных кодов реализован сейчас.

Offline

#21 2016-06-17 09:39:45

artoodetoo
Admin by chance
Зарегистрирован: 2008-09-09
Сообщений: 887
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

А что это за колдовство с json, зачем?


There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Offline

#22 2016-06-17 10:48:33

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

@artoodetoo, это у меня в ckeditor плагин генерации bb-кодов так написан, чтобы сложные стили и массив атрибутов для html элемента переводить в json. Вот пример таблицы:

[table={"style":"width: 800px;","border":"1","cellpadding":"0","cellspacing":"0"}]
[tbody]
[tr][td={"style":"width: 50px;"}]1[/td][td={"style":"width: 100px;"}]2[/td][td]3[/td][td]4[/td][td]5[/td][/tr]
[tr][td={"colspan":"2","rowspan":"1"}]двойная ширина[/td][td={"colspan":"1","rowspan":"4"}] [/td][td] [/td][td] [/td][/tr]
[tr][td] [/td][td] [/td][td] [/td][td] [/td][/tr]
[tr][td] [/td][td] [/td][td] [/td][td] [/td][/tr]
[tr][td] [/td][td] [/td][td] [/td][td] [/td][/tr]
[/tbody]
[/table]

Вид:
4f7eb6d6c61fabd3260da6be7406a542.png

Offline

#23 2016-07-04 16:09:15

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Вот чего наваял в процессе игры с парсерами:

%\[
(?P<tag> [a-z\*][a-z\d]{0,10})
(?>
  =
  (?>
    { (?P<attrjson> [^\x00-\x1f]+?) }
  |
    (?P<quote> &quot;|&\#039;|"|\' )?
    (?P<attr>
      (?(quote)
        [^\x00-\x1f]+?
      |
        [^\x00-\x1f\]]++
      )
    )
    (?(quote)(?P=quote))
  )
)?
\]
(?P<body>
  (?>
    [^\[]++
    (?:
      (?! \[
        (?:
          / (?P=tag)
        |
          (?P=tag)
          (?>
            =
            (?>
              { [^\x00-\x1f]+? }
            |
              &quot; [^\x00-\x1f]+? &quot;
            |
              &\#039; [^\x00-\x1f]+? &\#039;
            |
              " [^\x00-\x1f]+? "
            |
              \' [^\x00-\x1f]+? \'
            |
              [^\x00-\x1f\]]++
            )
          )?
        )
        \]
      ) \[
      [^\[]*+
    )*+
  | (?:
      (?! \[
        (?:
          / (?P=tag)
        |
          (?P=tag)
          (?>
            =
            (?>
              { [^\x00-\x1f]+? }
            |
              &quot; [^\x00-\x1f]+? &quot;
            |
              &\#039; [^\x00-\x1f]+? &\#039;
            |
              " [^\x00-\x1f]+? "
            |
              \' [^\x00-\x1f]+? \'
            |
              [^\x00-\x1f\]]++
            )
          )?
        )
        \]
      ) \[
      [^\[]*+
    )++
  | (?R)
  )*+
)
\[/ (?P=tag) \]%ix

А вот тестовая проверочка https://regex101.com/r/uR3nJ2/1

Offline

#24 2016-07-05 17:40:22

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Модификатор u (Юникод) примерно в два раза тормозит парсер sad
Все регулярки по возможности следует составлять так, чтобы не нужно было использовать данный модификатор.

Offline

#25 2016-07-07 17:20:00

Visman
Administrator
Из Сибирь
Зарегистрирован: 2009-06-08
Сообщений: 2,236
Сайт

Re: Альтернативный парсер BBCODE. Регулярка.

Еще вот задаюсь вопросом:
Если атрибут у бб-кода взят в одинарные или двойные кавычки, то разрешать ли такие же кавычки внутри содержимого атрибута?

Offline

Подвал доски

Под управлением FluxBB. Хостинг Hostens