'ее_замена'.
*/
public $mnemonics = array();
/*
Флажок, включающий/выключающий автоматические ссылки.
Смотрите файл config/parser.config.php
*/
public $autolinks = true;
/*
Массив замен для автоматических ссылок.
Смотрите файл config/parser.config.php
*/
public $preg_autolinks = array(
'pattern' => array(),
'replacement' => array(),
'highlight' => array(),
);
public $is_close = false;
public $lbr = 0;
public $rbr = 0;
/* Статистические сведения по обработке BBCode */
public $stat = array(
'time_parse' => 0, // Время парсинга
'time_html' => 0, // Время генерации HTML-а
'count_tags' => 0, // Число тегов BBCode
'count_level' => 0 // Число уровней вложенности тегов BBCode
);
/*
Модель поведения тега (в плане вложенности), которому сопоставлен экземпляр
данного класса. Модели поведения могут быть следующими:
'a' - ссылки, якоря
'caption' - заголовки таблиц
'code' - линейные контейнеры кода
'div' - блочные элементы
'hr' - горизонталные линии
'img' - картинки
'li' - элементы списков
'p' - блочные элементы типа абзацев и заголовков
'pre' - блочные контейнеры кода
'span' - линейные элементы
'table' - таблицы
'td' - ячейки таблиц
'tr' - строки таблиц
'ul' - списки
Конкретное содержание в понятие "модель поведения" вкладывается настройками
в файле config/parser.config.php
*/
public $behaviour = 'div';
/*
Текущая дирректория.
*/
private $_current_path;
/*
Для нужд парсера. - Позиция очередного обрабатываемого символа.
*/
private $_cursor;
/*
Массив объектов, - представителей отдельных тегов.
*/
private $_tag_objects = array();
/*
Массив пар: 'модель_поведения_тегов' => массив_моделей_поведений_тегов.
Накладывает ограничения на вложенность тегов. Теги с моделями поведения, не
указанными в массиве справа, вложенные в тег с моделью поведения, указанной
слева, будут игнорироваться как неправильно вложенные.
Смотрите файл config/parser.config.php
*/
private $_children;
/*
Массив пар: 'модель_поведения_тегов' => массив_моделей_поведений_тегов.
Накладывает ограничения на вложенность тегов.
Тег, принадлежещий указанной слева модели поведения тегов должен закрыться,
как только начинается тег, принадлежещий какой то из моделей поведения,
указанных справа.
Смотрите файл config/parser.config.php
*/
private $_ends;
/* Конструктор класса */
function bbcode($code = '') {
$this->_current_path = dirname(__FILE__) . DIRECTORY_SEPARATOR;
include $this->_current_path . 'config' . DIRECTORY_SEPARATOR . 'parser.config.php';
include $this->_current_path . 'config' . DIRECTORY_SEPARATOR . 'tags.php';
$this->tags = $tags;
$this->_children = $children;
$this->_ends = $ends;
$this->parse($code);
}
/*
get_token() - Функция парсит текст BBCode и возвращает очередную пару
"число (тип лексемы) - лексема"
Лексема - подстрока строки $this -> text, начинающаяся с позиции
$this -> _cursor
Типы лексем могут быть следующие:
0 - открывющая квадратная скобка ("[")
1 - закрывающая квадратная cкобка ("]")
2 - двойная кавычка ('"')
3 - апостроф ("'")
4 - равенство ("=")
5 - прямой слэш ("/")
6 - последовательность пробельных символов
(" ", "\t", "\n", "\r", "\0" или "\x0B")
7 - последовательность прочих символов, не являющаяся именем тега
8 - имя тега
*/
function get_token() {
$token = '';
$token_type = false;
$char_type = false;
while (true) {
$token_type = $char_type;
if (! isset($this -> text{$this -> _cursor})) {
if (false === $char_type) {
return false;
} else {
break;
}
}
$char = $this -> text{$this -> _cursor};
switch ($char) {
case '[':
$char_type = 0;
break;
case ']':
$char_type = 1;
break;
case '"':
$char_type = 2;
break;
case "'":
$char_type = 3;
break;
case "=":
$char_type = 4;
break;
case '/':
$char_type = 5;
break;
case ' ':
$char_type = 6;
break;
case "\t":
$char_type = 6;
break;
case "\n":
$char_type = 6;
break;
case "\r":
$char_type = 6;
break;
case "\0":
$char_type = 6;
break;
case "\x0B":
$char_type = 6;
break;
default:
$char_type = 7;
}
if (false === $token_type) {
$token = $char;
} elseif (5 >= $token_type) {
break;
} elseif ($char_type == $token_type) {
$token .= $char;
} else {
break;
}
$this -> _cursor += 1;
}
if (isset($this -> tags[strtolower($token)])) {
$token_type = 8;
}
return array($token_type, $token);
}
function parse($code = null) {
$time_start = $this->_getmicrotime();
if (is_array($code)) {
$is_tree = false;
foreach ($code as $val) {
if (isset($val['val'])) {
$this->tree = $code;
$this->syntax = $this->get_syntax();
$is_tree = true;
break;
}
}
if (! $is_tree) {
$this->syntax = $code;
$this->get_tree();
}
$this->text = '';
foreach ($this->syntax as $val) {
$this->text .= $val['str'];
}
$this->stat['time_parse'] = $this->_getmicrotime() - $time_start;
return $this->syntax;
} elseif ($code) {
$this->text = $code;
}
/*
Используем метод конечных автоматов
Список возможных состояний автомата:
0 - Начало сканирования или находимся вне тега. Ожидаем что угодно.
1 - Встретили символ "[", который считаем началом тега. Ожидаем имя
тега, или символ "/".
2 - Нашли в теге неожидавшийся символ "[". Считаем предыдущую строку
ошибкой. Ожидаем имя тега, или символ "/".
3 - Нашли в теге синтаксическую ошибку. Текущий символ не является "[".
Ожидаем что угодно.
4 - Сразу после "[" нашли символ "/". Предполагаем, что попали в
закрывающий тег. Ожидаем имя тега или символ "]".
5 - Сразу после "[" нашли имя тега. Считаем, что находимся в
открывающем теге. Ожидаем пробел или "=" или "/" или "]".
6 - Нашли завершение тега "]". Ожидаем что угодно.
7 - Сразу после "[/" нашли имя тега. Ожидаем "]".
8 - В открывающем теге нашли "=". Ожидаем пробел или значение атрибута.
9 - В открывающем теге нашли "/", означающий закрытие тега. Ожидаем
"]".
10 - В открывающем теге нашли пробел после имени тега или имени
атрибута. Ожидаем "=" или имя другого атрибута или "/" или "]".
11 - Нашли '"' начинающую значение атрибута, ограниченное кавычками.
Ожидаем что угодно.
12 - Нашли "'" начинающий значение атрибута, ограниченное апострофами.
Ожидаем что угодно.
13 - Нашли начало незакавыченного значения атрибута. Ожидаем что угодно.
14 - В открывающем теге после "=" нашли пробел. Ожидаем значение
атрибута.
15 - Нашли имя атрибута. Ожидаем пробел или "=" или "/" или "]".
16 - Находимся внутри значения атрибута, ограниченного кавычками.
Ожидаем что угодно.
17 - Завершение значения атрибута. Ожидаем пробел или имя следующего
атрибута или "/" или "]".
18 - Находимся внутри значения атрибута, ограниченного апострофами.
Ожидаем что угодно.
19 - Находимся внутри незакавыченного значения атрибута. Ожидаем что
угодно.
20 - Нашли пробел после значения атрибута. Ожидаем имя следующего
атрибута или "/" или "]".
Описание конечного автомата:
*/
$finite_automaton = array(
// Предыдущие | Состояния для текущих событий (лексем) |
// состояния | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 => array( 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )
, 1 => array( 2 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 5 )
, 2 => array( 2 , 3 , 3 , 3 , 3 , 4 , 3 , 3 , 5 )
, 3 => array( 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )
, 4 => array( 2 , 6 , 3 , 3 , 3 , 3 , 3 , 3 , 7 )
, 5 => array( 2 , 6 , 3 , 3 , 8 , 9 , 10 , 3 , 3 )
, 6 => array( 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )
, 7 => array( 2 , 6 , 3 , 3 , 3 , 3 , 3 , 3 , 3 )
, 8 => array( 13 , 13 , 11 , 12 , 13 , 13 , 14 , 13 , 13 )
, 9 => array( 2 , 6 , 3 , 3 , 3 , 3 , 3 , 3 , 3 )
, 10 => array( 2 , 6 , 3 , 3 , 8 , 9 , 3 , 15 , 15 )
, 11 => array( 16 , 16 , 17 , 16 , 16 , 16 , 16 , 16 , 16 )
, 12 => array( 18 , 18 , 18 , 17 , 18 , 18 , 18 , 18 , 18 )
, 13 => array( 19 , 6 , 19 , 19 , 19 , 19 , 17 , 19 , 19 )
, 14 => array( 2 , 3 , 11 , 12 , 13 , 13 , 3 , 13 , 13 )
, 15 => array( 2 , 6 , 3 , 3 , 8 , 9 , 10 , 3 , 3 )
, 16 => array( 16 , 16 , 17 , 16 , 16 , 16 , 16 , 16 , 16 )
, 17 => array( 2 , 6 , 3 , 3 , 3 , 9 , 20 , 15 , 15 )
, 18 => array( 18 , 18 , 18 , 17 , 18 , 18 , 18 , 18 , 18 )
, 19 => array( 19 , 6 , 19 , 19 , 19 , 19 , 20 , 19 , 19 )
, 20 => array( 2 , 6 , 3 , 3 , 3 , 9 , 3 , 15 , 15 )
);
// Закончили описание конечного автомата
$mode = 0;
$this->syntax = array();
$decomposition = array();
$token_key = -1;
$value = '';
$name = '';
$type = false;
$this ->_cursor = 0;
// Сканируем массив лексем с помощью построенного автомата:
while ($token = $this -> get_token()) {
$previous_mode = $mode;
$mode = $finite_automaton[$previous_mode][$token[0]];
if (-1 < $token_key) {
$type = $this -> syntax[$token_key]['type'];
} else {
$type = false;
}
switch ($mode) {
case 0:
if ('text' === $type) {
$this -> syntax[$token_key]['str'] .= $token[1];
} else {
$this -> syntax[++$token_key] = array(
'type' => 'text',
'str' => $token[1]
);
}
break;
case 1:
$decomposition = array(
'name' => '',
'type' => '',
'str' => '[',
'layout' => array(array(0, '['))
);
break;
case 2:
if ('text' === $type) {
$this -> syntax[$token_key]['str'] .= $decomposition['str'];
} else {
$this -> syntax[++$token_key] = array(
'type' => 'text',
'str' => $decomposition['str']
);
}
$decomposition = array(
'name' => '',
'type' => '',
'str' => '[',
'layout' => array(array(0, '['))
);
break;
case 3:
if ('text' === $type) {
$this -> syntax[$token_key]['str'] .= $decomposition['str'];
$this -> syntax[$token_key]['str'] .= $token[1];
} else {
$this -> syntax[++$token_key] = array(
'type' => 'text',
'str' => $decomposition['str'].$token[1]
);
}
$decomposition = array();
break;
case 4:
$decomposition['type'] = 'close';
$decomposition['str'] .= '/';
$decomposition['layout'][] = array(1, '/');
break;
case 5:
$decomposition['type'] = 'open';
$name = strtolower($token[1]);
$decomposition['name'] = $name;
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(2, $token[1]);
$decomposition['attrib'][$name] = '';
break;
case 6:
if (! isset($decomposition['name'])) {
$decomposition['name'] = '';
}
if (13 == $previous_mode || 19 == $previous_mode) {
$decomposition['layout'][] = array(7, $value);
}
$decomposition['str'] .= ']';
$decomposition['layout'][] = array( 0, ']' );
$this -> syntax[++$token_key] = $decomposition;
$decomposition = array();
break;
case 7:
$decomposition['name'] = strtolower($token[1]);
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(2, $token[1]);
break;
case 8:
$decomposition['str'] .= '=';
$decomposition['layout'][] = array(3, '=');
break;
case 9:
$decomposition['type'] = 'open/close';
$decomposition['str'] .= '/';
$decomposition['layout'][] = array(1, '/');
break;
case 10:
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(4, $token[1]);
break;
case 11:
$decomposition['str'] .= '"';
$decomposition['layout'][] = array(5, '"');
$value = '';
break;
case 12:
$decomposition['str'] .= "'";
$decomposition['layout'][] = array(5, "'");
$value = '';
break;
case 13:
$decomposition['attrib'][$name] = $token[1];
$value = $token[1];
$decomposition['str'] .= $token[1];
break;
case 14:
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(4, $token[1]);
break;
case 15:
$name = strtolower($token[1]);
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(6, $token[1]);
$decomposition['attrib'][$name] = '';
break;
case 16:
$decomposition['str'] .= $token[1];
$decomposition['attrib'][$name] .= $token[1];
$value .= $token[1];
break;
case 17:
$decomposition['str'] .= $token[1];
$decomposition['layout'][] = array(7, $value);
$value = '';
$decomposition['layout'][] = array(5, $token[1]);
break;
case 18:
$decomposition['str'] .= $token[1];
$decomposition['attrib'][$name] .= $token[1];
$value .= $token[1];
break;
case 19:
$decomposition['str'] .= $token[1];
$decomposition['attrib'][$name] .= $token[1];
$value .= $token[1];
break;
case 20:
$decomposition['str'] .= $token[1];
if ( 13 == $previous_mode || 19 == $previous_mode ) {
$decomposition['layout'][] = array(7, $value);
}
$value = '';
$decomposition['layout'][] = array(4, $token[1]);
break;
}
}
if (count($decomposition)) {
if ('text' === $type) {
$this -> syntax[$token_key]['str'] .= $decomposition['str'];
} else {
$this -> syntax[++$token_key] = array(
'type' => 'text',
'str' => $decomposition['str']
);
}
}
$this->get_tree();
$this->stat['time_parse'] = $this->_getmicrotime() - $time_start;
return $this->syntax;
}
function specialchars($string) {
$chars = array(
'[' => '@l;',
']' => '@r;',
'"' => '@q;',
"'" => '@a;',
'@' => '@at;'
);
return strtr($string, $chars);
}
function unspecialchars($string) {
$chars = array(
'@l;' => '[',
'@r;' => ']',
'@q;' => '"',
'@a;' => "'",
'@at;' => '@'
);
return strtr($string, $chars);
}
/*
Функция проверяет, должен ли тег с именем $current закрыться, если
начинается тег с именем $next.
*/
function must_close_tag($current, $next) {
if (isset($this -> tags[$current])) {
$this->_includeTagFile($current);
$class_vars = get_class_vars($this -> tags[$current]);
$current_behaviour = $class_vars['behaviour'];
} else {
$current_behaviour = $this->behaviour;
}
if (isset($this -> tags[$next])) {
$this->_includeTagFile($next);
$class_vars = get_class_vars($this -> tags[$next]);
$next_behaviour = $class_vars['behaviour'];
} else {
$next_behaviour = $this->behaviour;
}
$must_close = false;
if (isset($this->_ends[$current_behaviour])) {
$must_close = in_array($next_behaviour, $this->_ends[$current_behaviour]);;
}
return $must_close;
}
/*
Возвращает true, если тег с именем $parent может иметь непосредственным
потомком тег с именем $child. В противном случае - false.
Если $parent - пустая строка, то проверяется, разрешено ли $child входить в
корень дерева BBCode.
*/
function isPermissiblyChild($parent, $child) {
$parent = (string) $parent;
$child = (string) $child;
if (isset($this -> tags[$parent])) {
$this->_includeTagFile($parent);
$class_vars = get_class_vars($this -> tags[$parent]);
$parent_behaviour = $class_vars['behaviour'];
} else {
$parent_behaviour = $this->behaviour;
}
if (isset($this -> tags[$child])) {
$this->_includeTagFile($child);
$class_vars = get_class_vars($this -> tags[$child]);
$child_behaviour = $class_vars['behaviour'];
} else {
$child_behaviour = $this->behaviour;
}
$permissibly = true;
if (isset($this->_children[$parent_behaviour])) {
$permissibly = in_array(
$child_behaviour, $this->_children[$parent_behaviour]
);
}
return $permissibly;
}
function normalize_bracket($syntax) {
$structure = array();
$structure_key = -1;
$level = 0;
$open_tags = array();
foreach ($syntax as $val) {
unset($val['layout']);
switch ($val['type']) {
case 'text':
$val['str'] = $this -> unspecialchars($val['str']);
$type = (-1 < $structure_key)
? $structure[$structure_key]['type'] : false;
if ('text' === $type) {
$structure[$structure_key]['str'] .= $val['str'];
} else {
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = $level;
}
break;
case 'open/close':
$val['attrib'] = array_map(
array(&$this, 'unspecialchars'), $val['attrib']
);
foreach (array_reverse($open_tags,true) as $ult_key => $ultimate) {
if ($this -> must_close_tag($ultimate, $val['name'])) {
$structure[++$structure_key] = array(
'type' => 'close',
'name' => $ultimate,
'str' => '',
'level' => --$level
);
unset($open_tags[$ult_key]);
} else {
break;
}
}
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = $level;
break;
case 'open':
$this->_includeTagFile($val['name']);
$val['attrib'] = array_map(
array(&$this, 'unspecialchars'), $val['attrib']
);
foreach (array_reverse($open_tags,true) as $ult_key => $ultimate) {
if ($this -> must_close_tag($ultimate, $val['name'])) {
$structure[++$structure_key] = array(
'type' => 'close',
'name' => $ultimate,
'str' => '',
'level' => --$level
);
unset($open_tags[$ult_key]);
} else { break; }
}
$class_vars = get_class_vars($this -> tags[$val['name']]);
if ($class_vars['is_close']) {
$val['type'] = 'open/close';
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = $level;
} else {
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = $level++;
$open_tags[] = $val['name'];
}
break;
case 'close':
if (! count($open_tags)) {
$type = (-1 < $structure_key)
? $structure[$structure_key]['type'] : false;
if ( 'text' === $type ) {
$structure[$structure_key]['str'] .= $val['str'];
} else {
$structure[++$structure_key] = array(
'type' => 'text',
'str' => $val['str'],
'level' => 0
);
}
break;
}
if (! $val['name']) {
end($open_tags);
list($ult_key, $ultimate) = each($open_tags);
$val['name'] = $ultimate;
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = --$level;
unset($open_tags[$ult_key]);
break;
}
if (! in_array($val['name'],$open_tags)) {
$type = (-1 < $structure_key)
? $structure[$structure_key]['type'] : false;
if ('text' === $type) {
$structure[$structure_key]['str'] .= $val['str'];
} else {
$structure[++$structure_key] = array(
'type' => 'text',
'str' => $val['str'],
'level' => $level
);
}
break;
}
foreach (array_reverse($open_tags,true) as $ult_key => $ultimate) {
if ($ultimate != $val['name']) {
$structure[++$structure_key] = array(
'type' => 'close',
'name' => $ultimate,
'str' => '',
'level' => --$level
);
unset($open_tags[$ult_key]);
} else {
break;
}
}
$structure[++$structure_key] = $val;
$structure[$structure_key]['level'] = --$level;
unset($open_tags[$ult_key]);
}
}
foreach (array_reverse($open_tags,true) as $ult_key => $ultimate) {
$structure[++$structure_key] = array(
'type' => 'close',
'name' => $ultimate,
'str' => '',
'level' => --$level
);
unset($open_tags[$ult_key]);
}
return $structure;
}
function get_tree() {
/* Превращаем $this -> syntax в правильную скобочную структуру */
$structure = $this -> normalize_bracket($this -> syntax);
/* Отслеживаем, имеют ли элементы неразрешенные подэлементы.
Соответственно этому исправляем $structure. */
$normalized = array();
$normal_key = -1;
$level = 0;
$open_tags = array();
$not_tags = array();
$this -> stat['count_tags'] = 0;
foreach ($structure as $val) {
switch ($val['type']) {
case 'text':
$type = (-1 < $normal_key)
? $normalized[$normal_key]['type'] : false;
if ('text' === $type) {
$normalized[$normal_key]['str'] .= $val['str'];
} else {
$normalized[++$normal_key] = $val;
$normalized[$normal_key]['level'] = $level;
}
break;
case 'open/close':
$this->_includeTagFile($val['name']);
$is_open = count($open_tags);
end($open_tags);
$parent = $is_open ? current($open_tags) : $this->tag;
$permissibly = $this->isPermissiblyChild($parent, $val['name']);
if (! $permissibly) {
$type = (-1 < $normal_key)
? $normalized[$normal_key]['type'] : false;
if ( 'text' === $type ) {
$normalized[$normal_key]['str'] .= $val['str'];
} else {
$normalized[++$normal_key] = array(
'type' => 'text',
'str' => $val['str'],
'level' => $level
);
}
break;
}
$normalized[++$normal_key] = $val;
$normalized[$normal_key]['level'] = $level;
$this -> stat['count_tags'] += 1;
break;
case 'open':
$this->_includeTagFile($val['name']);
$is_open = count($open_tags);
end($open_tags);
$parent = $is_open ? current($open_tags) : $this->tag;
$permissibly = $this->isPermissiblyChild($parent, $val['name']);
if (! $permissibly) {
$not_tags[$val['level']] = $val['name'];
$type = (-1 < $normal_key)
? $normalized[$normal_key]['type'] : false;
if ( 'text' === $type ) {
$normalized[$normal_key]['str'] .= $val['str'];
} else {
$normalized[++$normal_key] = array(
'type' => 'text',
'str' => $val['str'],
'level' => $level
);
}
break;
}
$normalized[++$normal_key] = $val;
$normalized[$normal_key]['level'] = $level++;
$ult_key = count($open_tags);
$open_tags[$ult_key] = $val['name'];
$this -> stat['count_tags'] += 1;
break;
case 'close':
$not_normal = isset($not_tags[$val['level']])
&& $not_tags[$val['level']] = $val['name'];
if ($not_normal) {
unset($not_tags[$val['level']]);
$type = (-1 < $normal_key)
? $normalized[$normal_key]['type'] : false;
if ( 'text' === $type ) {
$normalized[$normal_key]['str'] .= $val['str'];
} else {
$normalized[++$normal_key] = array(
'type' => 'text',
'str' => $val['str'],
'level' => $level
);
}
break;
}
$normalized[++$normal_key] = $val;
$normalized[$normal_key]['level'] = --$level;
$ult_key = count($open_tags) - 1;
unset($open_tags[$ult_key]);
$this -> stat['count_tags'] += 1;
break;
}
}
unset($structure);
// Формируем дерево элементов
$result = array();
$result_key = -1;
$open_tags = array();
$this -> stat['count_level'] = 0;
foreach ($normalized as $val) {
switch ($val['type']) {
case 'text':
if (! $val['level']) {
$result[++$result_key] = array(
'type' => 'text',
'str' => $val['str']
);
break;
}
$open_tags[$val['level']-1]['val'][] = array(
'type' => 'text',
'str' => $val['str']
);
break;
case 'open/close':
if (! $val['level']) {
$result[++$result_key] = array(
'type' => 'item',
'name' => $val['name'],
'attrib' => $val['attrib'],
'val' => array()
);
break;
}
$open_tags[$val['level']-1]['val'][] = array(
'type' => 'item',
'name' => $val['name'],
'attrib' => $val['attrib'],
'val' => array()
);
break;
case 'open':
$open_tags[$val['level']] = array(
'type' => 'item',
'name' => $val['name'],
'attrib' => $val['attrib'],
'val' => array()
);
break;
case 'close':
if (! $val['level']) {
$result[++$result_key] = $open_tags[0];
unset($open_tags[0]);
break;
}
$open_tags[$val['level']-1]['val'][] = $open_tags[$val['level']];
unset($open_tags[$val['level']]);
break;
}
if ($val['level'] > $this -> stat['count_level']) {
$this -> stat['count_level'] += 1;
}
}
$this -> tree = $result;
return $result;
}
function get_syntax($tree = false) {
if (! is_array($tree)) {
$tree = $this -> tree;
}
$syntax = array();
foreach ($tree as $elem) {
if ('text' == $elem['type']) {
$syntax[] = array(
'type' => 'text',
'str' => $this -> specialchars($elem['str'])
);
} else {
$sub_elems = $this -> get_syntax($elem['val']);
$str = '';
$layout = array(array(0, '['));
foreach ($elem['attrib'] as $name => $val) {
$val = $this -> specialchars($val);
if ($str) {
$str .= ' ';
$layout[] = array(4, ' ');
$layout[] = array(6, $name);
} else {
$layout[] = array(2, $name);
}
$str .= $name;
if ($val) {
$str .= '="'.$val.'"';
$layout[] = array(3, '=');
$layout[] = array(5, '"');
$layout[] = array(7, $val);
$layout[] = array(5, '"');
}
}
if (count($sub_elems)) {
$str = '['.$str.']';
} else {
$str = '['.$str.' /]';
$layout[] = array(4, ' ');
$layout[] = array(1, '/');
}
$layout[] = array(0, ']');
$syntax[] = array(
'type' => count($sub_elems) ? 'open' : 'open/close',
'str' => $str,
'name' => $elem['name'],
'attrib' => $elem['attrib'],
'layout' => $layout
);
foreach ($sub_elems as $sub_elem) { $syntax[] = $sub_elem; }
if (count($sub_elems)) {
$syntax[] = array(
'type' => 'close',
'str' => '[/'.$elem['name'].']',
'name' => $elem['name'],
'layout' => array(
array(0, '['),
array(1, '/'),
array(2, $elem['name']),
array(0, ']')
)
);
}
}
}
return $syntax;
}
function insert_smiles($text) {
$text = htmlspecialchars($text,ENT_NOQUOTES);
if ($this -> autolinks) {
$search = $this -> preg_autolinks['pattern'];
$replace = $this -> preg_autolinks['replacement'];
$text = preg_replace($search, $replace, $text);
}
$text = str_replace(' ', ' ', nl2br($text));
$text = strtr($text, $this -> mnemonics);
return $text;
}
function highlight() {
$time_start = $this -> _getmicrotime();
$chars = array(
'@l;' => '@l;',
'@r;' => '@r;',
'@q;' => '@q;',
'@a;' => '@a;',
'@at;' => '@at;'
);
$search = $this -> preg_autolinks['pattern'];
$replace = $this -> preg_autolinks['highlight'];
$str = '';
foreach($this -> syntax as $elem) {
if ('text' == $elem['type']) {
$elem['str'] = strtr(htmlspecialchars($elem['str']), $chars);
foreach ($this -> mnemonics as $mnemonic => $value) {
$elem['str'] = str_replace(
$mnemonic,
''.$mnemonic.'',
$elem['str']
);
}
$elem['str'] = preg_replace($search, $replace, $elem['str']);
$str .= $elem['str'];
} else {
$str .= '';
foreach ($elem['layout'] as $val) {
switch ($val[0]) {
case 0:
$str .= ''.$val[1]
.'';
break;
case 1:
$str .= '/';
break;
case 2:
$str .= ''.$val[1]
.'';
break;
case 3:
$str .= '=';
break;
case 4:
$str .= $val[1];
break;
case 5:
if (! trim($val[1])) {
$str .= $val[1];
} else {
$str .= ''.$val[1]
.'';
}
break;
case 6:
$str .= ''
.htmlspecialchars($val[1]).'';
break;
case 7:
if (! trim($val[1])) {
$str .= $val[1];
} else {
$str .= ''
.strtr(htmlspecialchars($val[1]), $chars)
.'';
}
break;
default:
$str .= $val[1];
}
}
$str .= '';
}
}
$str = nl2br($str);
$str = str_replace(' ', ' ', $str);
$this -> stat['time_html'] = $this -> _getmicrotime() - $time_start;
return $str;
}
function get_html($elems = null) {
$time_start = $this -> _getmicrotime();
if (! is_array($elems)) {
$elems =& $this -> tree;
}
$result = '';
$lbr = 0;
$rbr = 0;
foreach ($elems as $elem) {
if ('text' === $elem['type']) {
$elem['str'] = $this -> insert_smiles($elem['str']);
for ($i=0; $i < $rbr; ++$i) {
$elem['str'] = ltrim($elem['str']);
if ('
' === substr($elem['str'], 0, 6)) {
$elem['str'] = substr_replace($elem['str'], '', 0, 6);
}
}
$result .= $elem['str'];
} else {
$this->_includeTagFile($elem['name']);
$handler = $this->tags[$elem['name']];
/* Убираем лишние переводы строк */
$class_vars = get_class_vars($handler);
$lbr = $class_vars['lbr'];
$rbr = $class_vars['rbr'];
for ($i=0; $i < $lbr; ++$i) {
$result = rtrim($result);
if ('
' === substr($result, -6)) {
$result = substr_replace($result, '', -6, 6);
}
}
/* Обрабатываем содержимое элемента */
$tag = $this->_tag_objects[$handler];
$tag->autolinks = $this->autolinks;
$tag->tags = $this->tags;
$tag->mnemonics = $this->mnemonics;
$tag->tag = $elem['name'];
$tag->attrib = $elem['attrib'];
$tag->tree = $elem['val'];
$result .= $tag -> get_html();
}
}
$result = preg_replace(
"'\s*
\s*
\s*'si", "\n
\n", $result
);
$this->stat['time_html'] = $this->_getmicrotime() - $time_start;
return $result;
}
/**
* Функция преобразует строку URL в соответствии с RFC 3986
* @param string $url
* @return string
*/
function checkUrl($url) {
$parse = parse_url($url);
$out = '';
if (isset($parse['scheme'])) {
$out .= $parse['scheme'] . '://';
}
if (isset($parse['user']) && isset($parse['pass'])) {
$out .= rawurlencode($parse['user']) . ':' . rawurlencode($parse['pass']) . '@';
} else if (isset($parse['user'])) {
$out .= rawurlencode($parse['user']) . '@';
}
if (isset($parse['host'])) {
$out .= rawurlencode($parse['host']);
}
if (isset($parse['port'])) {
$out .= ':' . $parse['port'];
}
if (isset($parse['path'])) {
$out .= str_replace('%2F', '/', rawurlencode($parse['path']));
}
if (isset($parse['query'])) {
parse_str($parse['query'], $query);
// PHP 5.1.2
$out .= '?' . str_replace('+', '%20', rtrim(http_build_query($query, '', '&'), '='));
}
if (isset($parse['fragment'])) {
$out .= '#' . rawurlencode($parse['fragment']);
}
return $out;
}
/**
* Функция возвращает текущий UNIX timestamp с микросекундами в формате float
* @return float
*/
function _getmicrotime() {
return microtime(true);
}
/*
Функция проверяет, доступен ли класс - обработчик тега с именем $tagName и,
если нет, пытается подключить файл с соответствующим классом. Если это не
возможно, переназначает тегу обработчик, - сопоставляет ему класс bbcode.
Затем инициализирует объект обработчика (если он еще не инициализирован).
*/
function _includeTagFile($tagName) {
if (! class_exists($this->tags[$tagName])) {
$tag_file = $this->_current_path
. str_replace('_', DIRECTORY_SEPARATOR, $this->tags[$tagName])
. '.php';
if (is_file($tag_file)) {
include_once $tag_file;
} else {
$this->tags[$tagName] = 'bbcode';
}
}
$handler = $this->tags[$tagName];
if (! isset($this->_tag_objects[$handler])) {
$this->_tag_objects[$handler] = new $handler;
}
return true;
}
}