Source for file Jevix.class.php
Documentation is available at Jevix.class.php
* Jevix — средство автоматического применения правил набора текстов,
* наделённое способностью унифицировать разметку HTML/XML документов,
* контролировать перечень допустимых тегов и аттрибутов,
* предотвращать возможные XSS-атаки в коде документов.
* http://code.google.com/p/jevix/
* @author ur001 <ur001ur001@gmail.com>, http://ur001.habrahabr.ru
* + cfgSetTagParamsAutoAdd() deprecated. Вместо него следует использовать cfgSetTagParamDefault() с более удобным синтаксисом
* + Исправлен критический баг с обработкой атрибутов тегов https://code.google.com/p/jevix/issues/detail?id=1
* + Удаление атрибутов тегов с пустым значением. Атрибуты без значений (checked, nowrap) теперь превращаются в checked="checked"
* + Исправлен тест, проведена небольшая ревизия кода
* + Функции для работы со строками заменены на аналогичные mb_*, чтобы не перегружать через mbstring.func_overload (ev.y0ga@mail.ru)
* + cfgSetAutoReplace теперь регистронезависимый
* + Возможность указать через cfgSetTagIsEmpty теги с пустым содержанием, которые не будут адалены парсером (rus.engine)
* + фикс бага удаления контента тега при разном регистре открывающего и закрывающего тегов (rus.engine)
* + Исправлено поведение парсера при установке правила sfgParamsAutoAdd(). Теперь
* параметр устанавливается только в том случае, если его вообще нет в
* обрабатываемом тексте. Если есть - оставляется оригинальное значение. (deadyaga)
* + Исправлен баг с закрывающимися тегами приводящий к созданию непарного тега рушащего вёрстку
* + Небольшая чистка кода
* + Добавлен символьный класс Jevix::RUS для определния русских символов
* + Авторасстановка пробелов после пунктуации только для кирилицы
* + Добавлена настройка cfgSetTagNoTypography() отключающая типографирование в указанном теге
* + Немного переделан алгоритм обработки кавычек. Он стал более строгим
* + Знак дюйма 33" больше не превращается в открывающуюся кавычку. Однако варриант "мой 24" монитор" - парсер не переварит.
* + Расширена функциональность для проверки атрибутов тега:
* можно указать тип атрибута ( 'colspan'=>'#int', 'value' => '#text' )
* в Jevix, по-умолчанию, определён массив типов для нескольких стандартных атрибутов (src, href, width, height)
* + Расширена функциональность для проверки атрибутов тега:
* можно задавать список дозможных значений атрибута ( 'align'=>array('left', 'right', 'center') )
* + Обычные "кавычки" сохраняются как "e; если они были так написаны
* + Добавлены разрешённые протоколы https и ftp для ссылок (a href="https://...)
* + Исправлено типографирование ?.. и !.. (две точки в конце больше не превращаются в троеточие)
* + Отключено автоматическое добавление пробела после точки для латиницы из-за чего невозможно было написать
* index.php или .htaccess
* + Добавлена настройка автодобавления параметров тегов. Непример rel = "nofolow" для ссылок.
* Спасибо Myroslav Holyak (vbhjckfd@gmail.com)
* + Исправлен баг с удалением пробелов (например в "123 — 123")
* + Исправлена ошибка из-за которой иногда не срабатывало автоматическое преобразования URL в ссылу
* + Добавлена настройка cfgSetAutoLinkMode для отключения автоматического преобразования URL в ссылки
* + Автодобавление пробела после точки, если после неё идёт русский символ
* + Добавлена настройка cfgSetAutoBrMode. При установке в false, переносы строк не будут автоматически заменяться на BR
* + Изменена обработка HTML-сущностей. Теперь все сущности имеющие эквивалент в Unicode (за исключением <>)
* автоматически преобразуются в символ
* + Добавлена обработка преформатированных тегов <pre>, <code>. Для задания используйте cfgSetTagPreformatted()
* + Добавлена настройка cfgSetXHTMLMode. При отключении пустые теги будут оформляться как <br>, при включенном - <br/>
* + Несколько незначительных багфиксов
const PUNCTUATUON = 0x400;
const HTML_QUOTE = 0x2000;
const TAG_QUOTE = 0x4000;
const QUOTE_CLOSE = 0x8000;
const STATE_TAG_PARAMS = 1;
const STATE_TAG_PARAM_VALUE = 2;
const STATE_INSIDE_TAG = 3;
const STATE_INSIDE_NOTEXT_TAG = 4;
const STATE_INSIDE_PREFORMATTED_TAG = 5;
public $entities1 = array('"'=> '"', "'"=> ''', '&'=> '&', '<'=> '<', '>'=> '>');
public $entities2 = array('<'=> '<', '>'=> '>', '"'=> '"');
public $textQuotes = array(array('«', '»'), array('„', '“'));
public $defaultTagParamRules = array('href' => '#link', 'src' => '#image', 'width' => '#int', 'height' => '#int', 'text' => '#text', 'title' => '#text');
* Константы для класификации тегов
const TR_TAG_ALLOWED = 1; // Тег позволен
const TR_PARAM_ALLOWED = 2; // Параметр тега позволен (a->title, a->src, i->alt)
const TR_PARAM_REQUIRED = 3; // Параметр тега влятся необходимым (a->href, img->src)
const TR_TAG_SHORT = 4; // Тег может быть коротким (img, br)
const TR_TAG_CUT = 5; // Тег необходимо вырезать вместе с контентом (script, iframe)
const TR_TAG_CHILD = 6; // Тег может содержать другие теги
const TR_TAG_CONTAINER = 7; // Тег может содержать лишь указанные теги. В нём не может быть текста
const TR_TAG_CHILD_TAGS = 8; // Теги которые может содержать внутри себя другой тег
const TR_TAG_PARENT = 9; // Тег в котором должен содержаться данный тег
const TR_TAG_PREFORMATTED = 10; // Преформатированные тег, в котором всё заменяется на HTML сущности типа <pre> сохраняя все отступы и пробелы
const TR_PARAM_AUTO_ADD = 11; // Auto add parameters + default values (a->rel[=nofollow])
const TR_TAG_NO_TYPOGRAPHY = 12; // Отключение типографирования для тега
const TR_TAG_IS_EMPTY = 13; // Не короткий тег с пустым содержанием имеет право существовать
const TR_TAG_NO_AUTO_BR = 14; // Тег в котором не нужна авто-расстановка <br>
* Классы символов генерируются symclass.php
protected $chClasses = array(0=> 512,1=> 512,2=> 512,3=> 512,4=> 512,5=> 512,6=> 512,7=> 512,8=> 512,9=> 32,10=> 66048,11=> 512,12=> 512,13=> 66048,14=> 512,15=> 512,16=> 512,17=> 512,18=> 512,19=> 512,20=> 512,21=> 512,22=> 512,23=> 512,24=> 512,25=> 512,26=> 512,27=> 512,28=> 512,29=> 512,30=> 512,31=> 512,32=> 32,97=> 71,98=> 71,99=> 71,100=> 71,101=> 71,102=> 71,103=> 71,104=> 71,105=> 71,106=> 71,107=> 71,108=> 71,109=> 71,110=> 71,111=> 71,112=> 71,113=> 71,114=> 71,115=> 71,116=> 71,117=> 71,118=> 71,119=> 71,120=> 71,121=> 71,122=> 71,65=> 71,66=> 71,67=> 71,68=> 71,69=> 71,70=> 71,71=> 71,72=> 71,73=> 71,74=> 71,75=> 71,76=> 71,77=> 71,78=> 71,79=> 71,80=> 71,81=> 71,82=> 71,83=> 71,84=> 71,85=> 71,86=> 71,87=> 71,88=> 71,89=> 71,90=> 71,1072=> 11,1073=> 11,1074=> 11,1075=> 11,1076=> 11,1077=> 11,1078=> 11,1079=> 11,1080=> 11,1081=> 11,1082=> 11,1083=> 11,1084=> 11,1085=> 11,1086=> 11,1087=> 11,1088=> 11,1089=> 11,1090=> 11,1091=> 11,1092=> 11,1093=> 11,1094=> 11,1095=> 11,1096=> 11,1097=> 11,1098=> 11,1099=> 11,1100=> 11,1101=> 11,1102=> 11,1103=> 11,1040=> 11,1041=> 11,1042=> 11,1043=> 11,1044=> 11,1045=> 11,1046=> 11,1047=> 11,1048=> 11,1049=> 11,1050=> 11,1051=> 11,1052=> 11,1053=> 11,1054=> 11,1055=> 11,1056=> 11,1057=> 11,1058=> 11,1059=> 11,1060=> 11,1061=> 11,1062=> 11,1063=> 11,1064=> 11,1065=> 11,1066=> 11,1067=> 11,1068=> 11,1069=> 11,1070=> 11,1071=> 11,48=> 337,49=> 337,50=> 337,51=> 337,52=> 337,53=> 337,54=> 337,55=> 337,56=> 337,57=> 337,34=> 57345,39=> 16385,46=> 1281,44=> 1025,33=> 1025,63=> 1281,58=> 1025,59=> 1281,1105=> 11,1025=> 11,47=> 257,38=> 257,37=> 257,45=> 257,95=> 257,61=> 257,43=> 257,35=> 257,124=> 257,);
* Установка конфигурационного флага для одного или нескольких тегов
* @param array|string$tags тег(и)
* @param mixed $value значеник=е флага
* @param boolean $createIfNoExists если тег ещё не определён - создть его
protected function _cfgSetTagsFlag($tags, $flag, $value, $createIfNoExists = true){
if(!is_array($tags)) $tags = array($tags);
throw new Exception("Тег $tag отсутствует в списке разрешённых тегов");
* КОНФИГУРАЦИЯ: Разрешение или запрет тегов
* Все не разрешённые теги считаются запрещёнными
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Коротие теги типа <img>
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Преформатированные теги, в которых всё заменяется на HTML сущности типа <pre>
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Теги в которых отключено типографирование типа <code>
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Не короткие теги которые не нужно удалять с пустым содержанием, например, <param name="code" value="die!"></param>
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Теги внутри который не нужна авто-расстановка <br/>, например, <ul></ul> и <ol></ol>
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Тег необходимо вырезать вместе с контентом (script, iframe)
* @param array|string$tags тег(и)
* КОНФИГУРАЦИЯ: Добавление разрешённых параметров тега
* @param string|array$params разрешённые параметры
if(!isset ($this->tagsRules[$tag])) throw new Exception("Тег $tag отсутствует в списке разрешённых тегов");
if(!is_array($params)) $params = array($params);
// Если ключа со списком разрешенных параметров не существует - создаём ео
if(!isset ($this->tagsRules[$tag][self::TR_PARAM_ALLOWED])) {
$this->tagsRules[$tag][self::TR_PARAM_ALLOWED] = array();
foreach($params as $key => $value){
$this->tagsRules[$tag][self::TR_PARAM_ALLOWED][$key] = $value;
$this->tagsRules[$tag][self::TR_PARAM_ALLOWED][$value] = true;
* КОНФИГУРАЦИЯ: Добавление необходимых параметров тега
* @param string|array$params разрешённые параметры
if(!isset ($this->tagsRules[$tag])) throw new Exception("Тег $tag отсутствует в списке разрешённых тегов");
if(!is_array($params)) $params = array($params);
// Если ключа со списком разрешенных параметров не существует - создаём ео
if(!isset ($this->tagsRules[$tag][self::TR_PARAM_REQUIRED])) {
$this->tagsRules[$tag][self::TR_PARAM_REQUIRED] = array();
foreach($params as $param){
$this->tagsRules[$tag][self::TR_PARAM_REQUIRED][$param] = true;
/* КОНФИГУРАЦИЯ: Установка тегов которые может содержать тег-контейнер
* @param string|array $childs разрешённые теги
* @param boolean $isContainerOnly тег является только контейнером других тегов и не может содержать текст
* @param boolean $isChildOnly вложенные теги не могут присутствовать нигде кроме указанного тега
function cfgSetTagChilds($tag, $childs, $isContainerOnly = false, $isChildOnly = false){
if(!isset ($this->tagsRules[$tag])) throw new Exception("Тег $tag отсутствует в списке разрешённых тегов");
if(!is_array($childs)) $childs = array($childs);
// Тег является контейнером и не может содержать текст
if($isContainerOnly) $this->tagsRules[$tag][self::TR_TAG_CONTAINER] = true;
// Если ключа со списком разрешенных тегов не существует - создаём ео
if(!isset ($this->tagsRules[$tag][self::TR_TAG_CHILD_TAGS])) {
$this->tagsRules[$tag][self::TR_TAG_CHILD_TAGS] = array();
foreach($childs as $child){
$this->tagsRules[$tag][self::TR_TAG_CHILD_TAGS][$child] = true;
// Указанный тег должен сущеаствовать в списке тегов
if(!isset ($this->tagsRules[$child])) throw new Exception("Тег $child отсутствует в списке разрешённых тегов");
if(!isset ($this->tagsRules[$child][self::TR_TAG_PARENT])) $this->tagsRules[$child][self::TR_TAG_PARENT] = array();
$this->tagsRules[$child][self::TR_TAG_PARENT][$tag] = true;
// Указанные разрешённые теги могут находится только внтутри тега-контейнера
if($isChildOnly) $this->tagsRules[$child][self::TR_TAG_CHILD] = true;
* CONFIGURATION: Adding autoadd attributes and their values to tag. If the 'rewrite' set as true, the attribute value will be replaced
* @param string|array$params array of pairs array('name'=>attributeName, 'value'=>attributeValue, 'rewrite'=>true|false)
* @deprecated устаревший синтаксис. Используйте cfgSetTagParamAutoAdd
throw new Exception("cfgSetTagParamsAutoAdd() is Deprecated. Use cfgSetTagParamDefault() instead");
* КОНФИГУРАЦИЯ: Установка дефолтных значений для атрибутов тега
* @param string $param атрибут
* @param string $value значение
* @param boolean $isRewrite заменять указанное значение дефолтным
if(!isset ($this->tagsRules[$tag])) throw new Exception("Tag $tag is missing in allowed tags list");
if(!isset ($this->tagsRules[$tag][self::TR_PARAM_AUTO_ADD])) {
$this->tagsRules[$tag][self::TR_PARAM_AUTO_ADD] = array();
$this->tagsRules[$tag][self::TR_PARAM_AUTO_ADD][$param] = array('value'=> $value, 'rewrite'=> $isRewrite);
$this->autoReplace = array('from' => $from, 'to' => $to);
* Включение или выключение режима XTML
* @param boolean $isXHTMLMode
$this->br = $isXHTMLMode ? '<br/>' : '<br>';
* Включение или выключение режима замены новых строк на <br/>
* @param boolean $isAutoBrMode
* Включение или выключение режима автоматического определения ссылок
* @param boolean $isAutoLinkMode
function parse($text, &$errors){
$this->state = self::STATE_TEXT;
* Получение следующего символа из входной строки
* @return string считанный символ
protected function getCh(){
* Перемещение на указанную позицию во входной строке и считывание символа
* @return string символ в указанной позиции
* Сохранить текущее состояние
if(!count($this->states)) throw new Exception('Конец стека');
if(!isset ($this->states[$index])) throw new Exception('Неверный индекс стека');
$state = $this->states[$index];
$this->curPos = $state['pos'];
$this->curCh = $state['ch'];
* Проверяет точное вхождение символа в текущей позиции
* Если символ соответствует указанному автомат сдвигается на следующий
protected function matchCh($ch, $skipSpaces = false){
if($this->curCh == $ch) {
* Проверяет точное вхождение символа указанного класса в текущей позиции
* Если символ соответствует указанному классу автомат сдвигается на следующий
* @param int $chClass класс символа
* @return string найденый символ или false
protected function matchChClass($chClass, $skipSpaces = false){
* Проверка на точное совпадение строки в текущей позиции
* Если строка соответствует указанной автомат сдвигается на следующий после строки символ
protected function matchStr($str, $skipSpaces = false){
* Пропуск текста до нахождения указанного символа
* @param string $ch сиимвол
* @return string найденый символ или false
* Пропуск текста до нахождения указанной строки или символа
* @param string $str строка или символ ля поиска
if($this->curCh == $firstCh){
for($i = 1; $i< $len ; $i++ ){
// текущий символ не равен текущему символу проверяемой строки?
if($this->curCh != $str[$i]){
// При неудаче откатываемся с переходим на следующий символ
* Возвращает класс символа
return $this->curChClass == slf::SPACE;
* Получает име (тега, параметра) по принципу 1 сиивол далее цифра или символ
protected function name(&$name = '', $minus = false){
while((($this->curChClass & self::NAME) == self::NAME || ($minus && $this->curCh== '-'))){
protected function tag(&$tag, &$params, &$content, &$short){
if(!$this->tagOpen($tag, $params, $short)) return false;
// Сохраняем кавычки и состояние
//$oldQuotesopen = $this->quotesOpened;
$oldState = $this->state;
//$this->quotesOpened = 0;
// Если в теге не должно быть текста, а только другие теги
// Переходим в состояние self::STATE_INSIDE_NOTEXT_TAG
if(!empty($this->tagsRules[$tag][self::TR_TAG_PREFORMATTED])){
$this->state = self::STATE_INSIDE_PREFORMATTED_TAG;
} elseif(!empty($this->tagsRules[$tag][self::TR_TAG_CONTAINER])){
$this->state = self::STATE_INSIDE_NOTEXT_TAG;
} elseif(!empty($this->tagsRules[$tag][self::TR_TAG_NO_TYPOGRAPHY])) {
$this->state = self::STATE_INSIDE_TAG;
$this->state = self::STATE_INSIDE_TAG;
if($this->state == self::STATE_INSIDE_PREFORMATTED_TAG){
$isTagClose = $this->tagClose($closeTag);
if($isTagClose && ($tag != $closeTag)) {
$this->eror("Неверный закрывающийся тег $closeTag. Ожидалось закрытие $tag");
// Восстанавливаем предыдущее состояние и счетчик кавычек
$this->state = $oldState;
//$this->quotesOpened = $oldQuotesopen;
protected function preformatted(&$content = '', $insideTag = null){
// Пытаемся найти закрывающийся тег
// Возвращаемся назад, если тег был найден
// Если закрылось то, что открылось - заканчиваем и возвращаем true
if($isClosedTag && $tag == $insideTag) return;
protected function tagOpen(&$name, &$params, &$short = false){
if(!$this->matchCh('<')) return false;
// Пробуем получить список атрибутов тега
$short = !empty($this->tagsRules[$name][self::TR_TAG_SHORT]);
// Short && XHTML && !Slash || Short && !XHTML && !Slash = ERROR
//if(($short && $this->isXHTMLMode && !$slash) || (!$short && !$this->isXHTMLMode && $slash)){
protected function tagParams(&$params = array()){
return count($params) > 0;
protected function tagParam(&$name, &$value){
if(!$this->name($name, true)) return false;
// Стремная штука - параметр без значения <input type="checkbox" checked>, <td nowrap class=b>
if($quote && !$this->matchCh($quote, true)){
// Нормальный параметр с кавычкамию Получаем пока не кавычки и не конец
// Экранируем символы HTML которые не могут быть в параметрах
// Символ ескейпа <a href="javascript::alert(\"hello\")">
if($this->curCh == '\\') $escape = true;
// долбаный параметр без кавычек. получаем его пока не пробел и не > и не конец
// Экранируем символы HTML которые не могут быть в параметрах
if(!$this->matchCh('<')) return false;
protected function makeTag($tag, $params, $content, $short, $parentTag = null){
// Получаем правила фильтрации тега
// Проверка - родительский тег - контейнер, содержащий только другие теги (ul, table, etc)
$parentTagIsContainer = $parentTag && isset ($this->tagsRules[$parentTag][self::TR_TAG_CONTAINER]);
// Вырезать тег вместе с содержанием
if($tagRules && isset ($this->tagsRules[$tag][self::TR_TAG_CUT])) return '';
if(!$tagRules || empty($tagRules[self::TR_TAG_ALLOWED])) return $parentTagIsContainer ? '' : $content;
// Если тег находится внутри другого - может ли он там находится?
if($parentTagIsContainer){
if(!isset ($this->tagsRules[$parentTag][self::TR_TAG_CHILD_TAGS][$tag])) return '';
// Тег может находится только внтури другого тега
if(isset ($tagRules[self::TR_TAG_CHILD])){
if(!isset ($tagRules[self::TR_TAG_PARENT][$parentTag])) return $content;
foreach($params as $param=> $value){
if(empty($value)) continue;
// Атрибут тега разрешён? Какие возможны значения? Получаем список правил
$paramAllowedValues = isset ($tagRules[self::TR_PARAM_ALLOWED][$param]) ? $tagRules[self::TR_PARAM_ALLOWED][$param] : false;
if(empty($paramAllowedValues)) continue;
// Если есть список разрешённых параметров тега
$this->eror("Недопустимое значение для атрибута тега $tag $param=$value");
// Если атрибут тега помечен как разрешённый, но правила не указаны - смотрим в массив стандартных правил для атрибутов
switch($paramAllowedValues){
$this->eror("Недопустимое значение для атрибута тега $tag $param=$value. Ожидалось число");
$this->eror('Попытка вставить JavaScript в URI');
// Первый символ должен быть a-z0-9 или #!
$this->eror('URI: Первый символ адреса должен быть буквой или цифрой');
// HTTP в начале если нет
//if(!preg_match('/^(http|https|ftp):\/\//ui', $value) && !preg_match('/^(\/|\#)/ui', $value) ) $value = 'http://'.$value;
// Ява-скрипт в пути к картинке
$this->eror('Попытка вставить JavaScript в пути к изображению');
// HTTP в начале если нет
//if(!preg_match('/^http:\/\//ui', $value) && !preg_match('/^\//ui', $value)) $value = 'http://'.$value;
$this->eror("Неверное описание атрибута тега в настройке Jevix: $param => $paramAllowedValues");
$resParams[$param] = $value;
// Проверка обязятельных параметров тега
// Если нет обязательных параметров возвращаем только контент
$requiredParams = isset ($tagRules[self::TR_PARAM_REQUIRED]) ? array_keys($tagRules[self::TR_PARAM_REQUIRED]) : array();
foreach($requiredParams as $requiredParam){
if(empty($resParams[$requiredParam])) return $content;
// Автодобавляемые параметры
if(!empty($tagRules[self::TR_PARAM_AUTO_ADD])){
foreach($tagRules[self::TR_PARAM_AUTO_ADD] as $name => $aValue) {
// If there isn't such attribute - setup it
if(!array_key_exists($name, $resParams) or ($aValue['rewrite'] and $resParams[$name] != $aValue['value'])) {
$resParams[$name] = $aValue['value'];
// Пустой некороткий тег удаляем кроме исключений
if (!isset ($tagRules[self::TR_TAG_IS_EMPTY]) or !$tagRules[self::TR_TAG_IS_EMPTY]) {
if(!$short && empty($content)) return '';
foreach($resParams as $param => $value) {
$text.= ' '. $param. '="'. $value. '"';
// Закрытие тега (если короткий то без контента)
if(isset ($tagRules[self::TR_TAG_CONTAINER])) $text .= "\r\n";
if(!$short) $text.= $content. '</'. $tag. '>';
if($parentTagIsContainer) $text .= "\r\n";
if($tag == 'br') $text.= "\r\n";
if(!$this->matchStr('<!--')) return false;
protected function anyThing(&$content = '', $parentTag = null){
// Если мы находимся в режиме тега без текста
// пропускаем контент пока не встретится <
if($this->state == self::STATE_INSIDE_NOTEXT_TAG && $this->curCh!= '<'){
if($this->curCh == '<' && $this->tag($tag, $params, $text, $shortTag)){
// Преобразуем тег в текст
$tagText = $this->makeTag($tag, $params, $text, $shortTag, $parentTag);
// Пропускаем пробелы после <br> и запрещённых тегов, которые вырезаются парсером
} elseif (empty($tagText)){
// Конец тега или символ <
} elseif($this->curCh == '<') {
// Если встречается <, но это не тег
// то это либо закрывающийся тег либо знак <
// Если это закрывающийся тег, то мы делаем откат
// Но если мы не внутри тега, то просто пропускаем его
if($this->state == self::STATE_INSIDE_TAG || $this->state == self::STATE_INSIDE_NOTEXT_TAG) {
$this->eror('Не ожидалось закрывающегося тега '. $name);
if($this->state != self::STATE_INSIDE_NOTEXT_TAG) $content.= $this->entities2['<'];
} elseif($this->text($text)){
* Пропуск переводов строк подсчет кол-ва
* @param int $count ссылка для возвращения числа переводов строк
protected function skipNL(&$count = 0){
// Если символ новый строки ткой же как и первый увеличиваем счетчик
// новых строк. Это сработает при любых сочетаниях
// \r\n\r\n, \r\r, \n\n - две перевода
if($nl == $firstNL) $count++ ;
// Между переводами строки могут встречаться пробелы
protected function dash(&$dash){
if($this->curCh != '-') return false;
if(!($this->curChClass & self::PUNCTUATUON)) return false;
$punctuation = $this->curCh;
// Проверяем ... и !!! и ?.. и !..
if($punctuation == '.' && $this->curCh == '.'){
$punctuation = $this->dotes;
} elseif($punctuation == '!' && $this->curCh == '!'){
} elseif (($punctuation == '?' || $punctuation == '!') && $this->curCh == '.'){
// Далее идёт слово - добавляем пробел
if($punctuation != '.') $punctuation.= ' ';
// Далее идёт пробел, перенос строки, конец текста
protected function number(&$num){
if(!(($this->curChClass & self::NUMERIC) == self::NUMERIC)) return false;
while(($this->curChClass & self::NUMERIC) == self::NUMERIC){
if($this->curCh<> '&') return false;
* @param boolean $spacesBefore были до этого пробелы
* @param string $quote кавычка
* @param boolean $closed закрывающаяся
protected function quote($spacesBefore, &$quote, &$closed){
// Если не одна кавычка ещё не была открыта и следующий символ - не буква - то это нифига не кавычка
// Закрывается тогда, одна из кавычек была открыта и (до кавычки не было пробела или пробел или пунктуация есть после кавычки)
// Или, если открыто больше двух кавычек - точно закрываем
protected function makeQuote($closed, $level){
if($level > $levels) $level = $levels;
return $this->textQuotes[$level][$closed ? 1 : 0];
protected function text(&$text){
$newWord = true; // Возможно начало нового слова
// Включено типографирование?
// Первый символ может быть <, это значит что tag() вернул false
// и < к тагу не относится
// автопреобразование сущностей...
// Автопунктуация выключена
// Если встретилась пунктуация - добавляем ее
// Сохраняем пробел перед точкой если класс следующий символ - латиница
if($spCount && $punctuation == '.' && ($this->curChClass & self::LAT)) $punctuation = ' '. $punctuation;
} elseif ($typoEnabled && ($spCount || $newLine) && $this->curCh == '-' && $this->dash($dash)){
} elseif ($typoEnabled && ($this->curChClass & self::HTML_QUOTE) && $this->quote($spCount, $quote, $closed)){
// Исправляем ситуацию если кавычка закрыввается раньше чем открывается
if($spCount) $quote = ' '. $quote;
// после пробелов снова возможно новое слово
$br = $this->br. $this->nl;
$text.= $brCount == 1 ? $br : $br. $br;
// Помечаем что новая строка и новое слово
$text.= $this->makeTag('a' , array('href' => $href), $url, false);
// Экранируем символы HTML которые нельзя сувать внутрь тега (но не те? которые не могут быть в параметрах)
// Совершенно непечатаемые символы которые никуда не годятся
protected function url(&$url, &$href){
$urlChMask = self::URL | self::ALPHA;
protected function eror($message){
for($i = $this->curPos; $i < $strEnd; $i++ ){
* Функция ord() для мультибайтовы строк
* @param string $c символ utf-8
* @return int код символа
return ($h & 0x1F) << 6 | (ord($c{1}) & 0x3F);
return ($h & 0x0F) << 12 | (ord($c{1}) & 0x3F) << 6
return ($h & 0x0F) << 18 | (ord($c{1}) & 0x3F) << 12
| (ord($c{2}) & 0x3F) << 6
* Функция chr() для мультибайтовы строк
* @param int $c код символа
* @return string символ utf-8
} else if ($c <= 0x7FF) {
return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
} else if ($c <= 0xFFFF) {
return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
} else if ($c <= 0x10FFFF) {
return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
. chr(0x80 | $c >> 6 & 0x3F)
|