|
@@ -1,7 +1,7 @@
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
- * @copyright Copyright (c) 2016 Visman. All rights reserved.
|
|
|
+ * @copyright Copyright (c) 2016-2017 Visman. All rights reserved.
|
|
|
* @author Visman <mio.visman@yandex.ru>
|
|
|
* @link https://github.com/MioVisman/Parserus
|
|
|
* @license https://opensource.org/licenses/MIT The MIT License (MIT)
|
|
@@ -119,6 +119,12 @@ class Parserus
|
|
|
*/
|
|
|
protected $strict = false;
|
|
|
|
|
|
+ /**
|
|
|
+ * Максимальная глубина дерева тегов при строгом режиме поиска ошибок
|
|
|
+ * @var int
|
|
|
+ */
|
|
|
+ protected $maxDepth;
|
|
|
+
|
|
|
/**
|
|
|
* Конструктор
|
|
|
*
|
|
@@ -131,14 +137,15 @@ class Parserus
|
|
|
}
|
|
|
$this->eFlags = $flag | ENT_QUOTES | ENT_SUBSTITUTE;
|
|
|
$this->tRepl = in_array($flag, [ENT_HTML5, ENT_HTML401])
|
|
|
- ? ['<br>', ' ', ' ', ' ']
|
|
|
- : ['<br />', '    ', '  ', '  '];
|
|
|
+ ? ['<br>', ' ', ' ', ' ']
|
|
|
+ : ['<br />', '    ', '  ', '  '];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Метод добавляет один bb-код
|
|
|
*
|
|
|
* @param array $bb Массив описания bb-кода
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function addBBCode(array $bb)
|
|
@@ -174,7 +181,7 @@ class Parserus
|
|
|
}
|
|
|
|
|
|
if (isset($bb['self nesting'])) {
|
|
|
- $res['self nesting'] = (bool) $bb['self nesting'];
|
|
|
+ $res['self nesting'] = (int) $bb['self nesting'] > 0 ? (int) $bb['self nesting'] : false;
|
|
|
}
|
|
|
|
|
|
if (isset($bb['recursive'])) {
|
|
@@ -248,6 +255,7 @@ class Parserus
|
|
|
* Метод задает массив bb-кодов
|
|
|
*
|
|
|
* @param array $bbcodes Массив описаний bb-кодов
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setBBCodes(array $bbcodes)
|
|
@@ -276,6 +284,7 @@ class Parserus
|
|
|
* Метод задает массив смайлов
|
|
|
*
|
|
|
* @param array $smilies Ассоциативный массив смайлов
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setSmilies(array $smilies)
|
|
@@ -331,6 +340,7 @@ class Parserus
|
|
|
* @param string $tpl Строка шаблона, например: <img src="{url}" alt="{alt}">
|
|
|
* @param string $tag Имя тега под которым идет отображение смайлов
|
|
|
* @param array $bl Список тегов в которых не нужно отображать смайлы
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setSmTpl($tpl, $tag = 'img', array $bl = ['url'])
|
|
@@ -356,6 +366,7 @@ class Parserus
|
|
|
* Метод устанавливает список разрешенных bb-кодов
|
|
|
*
|
|
|
* @param mixed $list Массив bb-кодов, null и т.д.
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setWhiteList($list = null)
|
|
@@ -368,6 +379,7 @@ class Parserus
|
|
|
* Метод устанавливает список запрещенных bb-кодов
|
|
|
*
|
|
|
* @param mixed $list Массив bb-кодов, null и т.д.
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setBlackList($list = null)
|
|
@@ -381,6 +393,7 @@ class Parserus
|
|
|
*
|
|
|
* @param string $name Имя переменной
|
|
|
* @param mixed $val Значение переменной
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
public function setAttr($name, $val)
|
|
@@ -392,8 +405,9 @@ class Parserus
|
|
|
/**
|
|
|
* Метод для получения значения переменной
|
|
|
*
|
|
|
- * @param string $name Имя переменной
|
|
|
- * @return mixed|null Значение переменной или null, если переменная не была задана ранее
|
|
|
+ * @param string $name Имя переменной
|
|
|
+ *
|
|
|
+ * @return mixed Значение переменной или null, если переменная не была задана ранее
|
|
|
*/
|
|
|
public function attr($name)
|
|
|
{
|
|
@@ -407,15 +421,16 @@ class Parserus
|
|
|
* @param int $parentId Указатель на родителя
|
|
|
* @param array $attrs Массив атрибутов тега
|
|
|
* @param bool $textOnly Флаг. Если true, то в теле только текст
|
|
|
+ *
|
|
|
* @return int Указатель на данный тег
|
|
|
*/
|
|
|
- protected function addTagNode($tag, $parentId = null, $attrs = [], $textOnly = false)
|
|
|
+ protected function addTagNode($tag, $parentId = null, array $attrs = [], $textOnly = false)
|
|
|
{
|
|
|
$this->data[++$this->dataId] = [
|
|
|
- 'tag' => $tag,
|
|
|
- 'parent' => $parentId,
|
|
|
+ 'tag' => $tag,
|
|
|
+ 'parent' => $parentId,
|
|
|
'children' => [],
|
|
|
- 'attrs' => $attrs,
|
|
|
+ 'attrs' => $attrs,
|
|
|
];
|
|
|
|
|
|
if ($textOnly) {
|
|
@@ -434,14 +449,14 @@ class Parserus
|
|
|
*
|
|
|
* @param string $text Текст
|
|
|
* @param int $parentId Указатель на родителя
|
|
|
+ *
|
|
|
* @return string Пустая строка
|
|
|
*/
|
|
|
protected function addTextNode($text, $parentId)
|
|
|
{
|
|
|
if (isset($text[0])) {
|
|
|
-
|
|
|
$this->data[++$this->dataId] = [
|
|
|
- 'text' => $text,
|
|
|
+ 'text' => $text,
|
|
|
'parent' => $parentId,
|
|
|
];
|
|
|
|
|
@@ -455,6 +470,7 @@ class Parserus
|
|
|
* Метод нормализует содержимое атрибута
|
|
|
*
|
|
|
* @param string $attr Содержимое атрибута полученное из регулярного выражения
|
|
|
+ *
|
|
|
* @return string
|
|
|
*/
|
|
|
protected function getNormAttr($attr)
|
|
@@ -476,6 +492,7 @@ class Parserus
|
|
|
* @param string $tag Имя обрабатываемого тега
|
|
|
* @param string $type "Тип атрибутов" = ' ', '=' или ']'
|
|
|
* @param string $text Текст из которого выделяются атрибуты
|
|
|
+ *
|
|
|
* @return null|array
|
|
|
*/
|
|
|
protected function parseAttrs($tag, $type, $text)
|
|
@@ -575,8 +592,8 @@ class Parserus
|
|
|
|
|
|
return [
|
|
|
'attrs' => $attrs,
|
|
|
- 'tag' => $tagText,
|
|
|
- 'text' => $text,
|
|
|
+ 'tag' => $tagText,
|
|
|
+ 'text' => $text,
|
|
|
];
|
|
|
}
|
|
|
|
|
@@ -584,6 +601,7 @@ class Parserus
|
|
|
* Метод определяет указатель на родительский тег для текущего
|
|
|
*
|
|
|
* @param string $tag Имя тега
|
|
|
+ *
|
|
|
* @return int|false false, если невозможно подобрать родителя
|
|
|
*/
|
|
|
protected function findParent($tag)
|
|
@@ -629,9 +647,10 @@ class Parserus
|
|
|
* @param string $tag Имя тега
|
|
|
* @param array $attrs Массив атрибутов
|
|
|
* @param string $text Текст из которого выделяется тело тега
|
|
|
+ *
|
|
|
* @return array|false false в случае ошибки
|
|
|
*/
|
|
|
- protected function validationTag($tag, $attrs, $text)
|
|
|
+ protected function validationTag($tag, array $attrs, $text)
|
|
|
{
|
|
|
if (empty($attrs)) {
|
|
|
$attrs['no attr'] = null;
|
|
@@ -691,8 +710,8 @@ class Parserus
|
|
|
|
|
|
return [
|
|
|
'attrs' => $attrs,
|
|
|
- 'body' => $flag ? $body : null,
|
|
|
- 'end' => $end,
|
|
|
+ 'body' => $flag ? $body : null,
|
|
|
+ 'end' => $end,
|
|
|
];
|
|
|
}
|
|
|
|
|
@@ -702,6 +721,7 @@ class Parserus
|
|
|
* @param string $tag Имя обрабатываемого тега
|
|
|
* @param string $curText Текст до тега, который еще не был учтен
|
|
|
* @param string $tagText Текст самого тега - [/tag]
|
|
|
+ *
|
|
|
* @return string Пустая строка, если тег удалось закрыть
|
|
|
*/
|
|
|
protected function closeTag($tag, $curText, $tagText) {
|
|
@@ -715,7 +735,9 @@ class Parserus
|
|
|
$curTag = $this->data[$curId]['tag'];
|
|
|
|
|
|
while ($curTag !== $tag && $curId > 0) {
|
|
|
- if (false === $this->bbcodes[$curTag]['auto']) {
|
|
|
+ if ($this->bbcodes[$tag]['type'] === 'inline'
|
|
|
+ || false === $this->bbcodes[$curTag]['auto']
|
|
|
+ ) {
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -753,6 +775,7 @@ class Parserus
|
|
|
$this->smOn = false;
|
|
|
$this->errors = [];
|
|
|
$this->strict = isset($opts['strict']) ? (bool) $opts['strict'] : false;
|
|
|
+ $this->maxDepth = isset($opts['depth']) ? (int) $opts['depth'] : 10;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -760,9 +783,10 @@ class Parserus
|
|
|
*
|
|
|
* @param string $text Обрабатываемый текст
|
|
|
* @param array $opts Ассоциативный массив опций
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
- public function parse($text, $opts = [])
|
|
|
+ public function parse($text, array $opts = [])
|
|
|
{
|
|
|
$this->reset($opts);
|
|
|
$curText = '';
|
|
@@ -774,13 +798,13 @@ class Parserus
|
|
|
while (($match = preg_split('%(\[(/)?(' . ($recCount ? $recTag : '[a-z\*][a-z\d-]{0,10}') . ')((?(1)\]|[=\]\x20])))%i', $text, 2, PREG_SPLIT_DELIM_CAPTURE))
|
|
|
&& isset($match[1])
|
|
|
) {
|
|
|
- /* $match[0] - текст до тега
|
|
|
- * $match[1] - [ + (|/) + имя тега + (]| |=)
|
|
|
- * $match[2] - (|/)
|
|
|
- * $match[3] - имя тега
|
|
|
- * $match[4] - тип атрибутов --> (]| |=)
|
|
|
- * $match[5] - остаток текста до конца
|
|
|
- */
|
|
|
+ /* $match[0] - текст до тега
|
|
|
+ * $match[1] - [ + (|/) + имя тега + (]| |=)
|
|
|
+ * $match[2] - (|/)
|
|
|
+ * $match[3] - имя тега
|
|
|
+ * $match[4] - тип атрибутов --> (]| |=)
|
|
|
+ * $match[5] - остаток текста до конца
|
|
|
+ */
|
|
|
$tagText = $match[1];
|
|
|
$curText .= $match[0];
|
|
|
$text = $match[5];
|
|
@@ -869,13 +893,62 @@ class Parserus
|
|
|
}
|
|
|
|
|
|
$this->addTextNode($curText . $text, $this->curId);
|
|
|
+
|
|
|
+ if ($this->strict) {
|
|
|
+ $this->searchError();
|
|
|
+ }
|
|
|
+
|
|
|
return $this;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Метод проверяет глубину дерева тегов
|
|
|
+ * Метод проверяет лимит вложенности тегов в самих себя
|
|
|
+ *
|
|
|
+ * @param int $id Указатель на текущий тег
|
|
|
+ * @param int $depth Глубина дерева на текущий момент
|
|
|
+ * @param array $tags Массив количества вложений тегов с включенным 'self nesting'
|
|
|
+ *
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function searchError($id = 0, $depth = -1, array $tags = [])
|
|
|
+ {
|
|
|
+ if (isset($this->data[$id]['text'])) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ++$depth;
|
|
|
+
|
|
|
+ if ($depth > $this->maxDepth) {
|
|
|
+ $this->errors[] = [15, $this->maxDepth];
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ $tag = $this->data[$id]['tag'];
|
|
|
+ if (false !== $this->bbcodes[$tag]['self nesting']) {
|
|
|
+ if (isset($tags[$tag])) {
|
|
|
+ ++$tags[$tag];
|
|
|
+ } else {
|
|
|
+ $tags[$tag] = 0;
|
|
|
+ }
|
|
|
+ if ($tags[$tag] > $this->bbcodes[$tag]['self nesting']) {
|
|
|
+ $this->errors[] = [16, $tag, $this->bbcodes[$tag]['self nesting']];
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($this->data[$id]['children'] as $child) {
|
|
|
+ if ($this->searchError($child, $depth, $tags)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Метод возвращает HTML построенный на основании дерева тегов
|
|
|
*
|
|
|
* @param int $id Указатель на текущий тег
|
|
|
+ *
|
|
|
* @return string
|
|
|
*/
|
|
|
public function getHtml($id = 0)
|
|
@@ -952,6 +1025,7 @@ class Parserus
|
|
|
* Метод возвращает текст с bb-кодами построенный на основании дерева тегов
|
|
|
*
|
|
|
* @param int $id Указатель на текущий тег
|
|
|
+ *
|
|
|
* @return string
|
|
|
*/
|
|
|
public function getCode($id = 0)
|
|
@@ -1021,6 +1095,7 @@ class Parserus
|
|
|
* @param string $tag Имя для создания bb-кода
|
|
|
* @param string $pattern Регулярное выражение для поиска
|
|
|
* @param bool $textOnly Флаг. true, если содержимое созданного тега текстовое
|
|
|
+ *
|
|
|
* @return Parserus $this
|
|
|
*/
|
|
|
protected function detect($tag, $pattern, $textOnly)
|
|
@@ -1073,7 +1148,7 @@ class Parserus
|
|
|
$pos = $match[1] + strlen($match[0]);
|
|
|
}
|
|
|
|
|
|
- $this->addTextNode($this->endStr($this->data[$id]['text'], $pos), $pid);
|
|
|
+ $this->addTextNode((string) substr($this->data[$id]['text'], $pos), $pid);
|
|
|
unset($this->data[$id]);
|
|
|
|
|
|
$this->data[$pid]['children'] = array_merge($this->data[$pid]['children'], $arrEnd);
|
|
@@ -1112,6 +1187,7 @@ class Parserus
|
|
|
*
|
|
|
* @param string $mask Маска символов, которые не учитываются при определении пустоты текстовых узлов
|
|
|
* @param int $id Указатель на текущий тег
|
|
|
+ *
|
|
|
* @return bool Если true, то тег/узел пустой
|
|
|
*/
|
|
|
protected function stripEmptyTags_($mask, $id)
|
|
@@ -1155,10 +1231,12 @@ class Parserus
|
|
|
/**
|
|
|
* Метод возвращает массив ошибок
|
|
|
*
|
|
|
- * @param array $lang Массив строк шаблонов описания ошибок
|
|
|
+ * @param array $lang Массив строк шаблонов описания ошибок
|
|
|
+ * @param array $errors Массив, который дополняется ошибками
|
|
|
+ *
|
|
|
* @return array
|
|
|
*/
|
|
|
- public function getErrors(array $lang = [])
|
|
|
+ public function getErrors(array $lang = [], array $errors = [])
|
|
|
{
|
|
|
$defLang = [
|
|
|
1 => 'Тег [%1$s] находится в черном списке',
|
|
@@ -1174,11 +1252,11 @@ class Parserus
|
|
|
11 => 'Тело тега [%1$s] не соответствует шаблону',
|
|
|
12 => 'Тег [%1$s] нельзя открыть внутри аналогичного тега',
|
|
|
13 => 'В теге [%1$s] отсутствует обязательный атрибут \'%2$s\'',
|
|
|
- 14 => 'Все теги пустые'
|
|
|
+ 14 => 'Все теги пустые',
|
|
|
+ 15 => 'Глубина дерева тегов больше %1$s',
|
|
|
+ 16 => 'Тег [%1$s] вложен в себя больше %2$s раз',
|
|
|
];
|
|
|
|
|
|
- $errors = [];
|
|
|
-
|
|
|
foreach ($this->errors as $args) {
|
|
|
$err = array_shift($args);
|
|
|
|
|
@@ -1200,23 +1278,11 @@ class Parserus
|
|
|
* Метод преобразует специальные символы в HTML-сущности
|
|
|
*
|
|
|
* @param string $text
|
|
|
+ *
|
|
|
* @return string
|
|
|
*/
|
|
|
public function e($text)
|
|
|
{
|
|
|
return htmlspecialchars($text, $this->eFlags, 'UTF-8');
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * Метод возвращает окончание строки
|
|
|
- *
|
|
|
- * @param string $str Текст
|
|
|
- * @param int $pos Начальная позиция в байтах с которой идет возврат текста
|
|
|
- * @return string
|
|
|
- */
|
|
|
- protected function endStr($str, $pos)
|
|
|
- {
|
|
|
- $s = substr($str, $pos);
|
|
|
- return false === $s ? '' : $s;
|
|
|
- }
|
|
|
}
|