private function _bigram_exists($word, $lang) { $word = $lang === 'en' ? strtolower($word) : UTF8::lowercase($word); #шаг 0. #проверяем слова в списке слов-исключений if (array_key_exists($word, $this->words_exceptions[$lang])) { return false; } #шаг 1. #проверка на 4 согласные буквы подряд; пример: больши{нств}о, юрисконсу{льтс}тво if (preg_match('/(?:' . $this->consonant_lc[$lang] . '){4}/sxSX', $word, $m) && !array_key_exists($m[0], $this->consonants4_lc[$lang])) { return true; } #шаг 2. #проверка на 3 гласные буквы подряд; пример: длиннош{еее}, зм{еео}бразный if (preg_match('/(?:' . $this->vowel_lc[$lang] . '){3}/sxSX', $word, $m) && !array_key_exists($m[0], $this->vowels3_lc[$lang])) { return true; } #шаг 3. $length = UTF8::strlen($word); for ($pos = 0, $limit = $length - 1; $pos < $limit; $pos++) { /* TODO Качество проверки по несуществующим биграммам можно немного повысить, если учитывать не только начало и конец слова, но и все позиции биграмм в слове. */ $ss = UTF8::substr($word, $pos, 2); if ($pos === 0) { $ss = ' ' . $ss; } elseif ($pos === $limit - 1) { $ss = $ss . ' '; } #ending of word if (array_key_exists($ss, $this->bigrams)) { return true; } } return false; }
/** * * @param string $s строка для проверки * @param string $delta ширина найденного фрагмента в словах * (кол-во слов от матного слова слева и справа, максимально 10) * @param string $continue строка, которая будет вставлена в начале и в конце фрагмента * @param bool $is_html расценивать строку как HTML код? * в режиме $is_html === TRUE html код игнорируется, а html сущности заменяются в "чистый" UTF-8 * @param string|null $replace строка, на которую заменять матный фрагмент, например: '[ой]' ($replace д.б. в кодировке $charset) * опция работает в PHP >= 5.2.0 * @param string $charset кодировка символов (родная кодировка -- UTF-8, для других будет прозрачное перекодирование) * @return bool|string|int|null Если $replace === NULL, то возвращает FALSE, если мат не обнаружен, иначе фрагмент текста с матерным словом. * Если $replace !== NULL, то возвращает исходную строку, где фрагменты мата заменены на $replace. * В случае возникновения ошибки возвращает код ошибки > 0 (integer): * * PREG_INTERNAL_ERROR * * PREG_BACKTRACK_LIMIT_ERROR (see also pcre.backtrack_limit) * * PREG_RECURSION_LIMIT_ERROR (see also pcre.recursion_limit) * * PREG_BAD_UTF8_ERROR * * PREG_BAD_UTF8_OFFSET_ERROR (since PHP 5.3.0) * Или -1, если ReflectionTypeHint вернул ошибку */ public static function parse($s, $delta = 3, $continue = "…", $is_html = true, $replace = null, $charset = 'UTF-8') { if (!ReflectionTypeHint::isValid()) { return -1; } if ($s === null) { return null; } static $re_badwords = null; if ($re_badwords === null) { #предлоги русского языка: #[всуо]| #по|за|на|об|до|от|вы|вс|вз|из|ис| #под|про|при|над|низ|раз|рас|воз|вос| #пооб|повы|пона|поза|недо|пере|одно| #полуза|произ|пораз|много| $pretext = array('[уyоoаa]_? (?=[еёeхx])', '[вvbсc]_? (?=[хпбмгжxpmgj])', '[вvbсc]_?[ъь]_? (?=[еёe])', 'ё_? (?=[бb6])', '[вvb]_?[ыi]_?', '[зz3]_?[аa]_?', '[нnh]_?[аaеeиi]_?', '[вvb]_?[сc]_? (?=[хпбмгжxpmgj])', '[оo]_?[тtбb6]_? (?=[хпбмгжxpmgj])', '[оo]_?[тtбb6]_?[ъь]_? (?=[еёe])', '[иiвvb]_?[зz3]_? (?=[хпбмгжxpmgj])', '[иiвvb]_?[зz3]_?[ъь]_? (?=[еёe])', '[иi]_?[сc]_? (?=[хпбмгжxpmgj])', '[пpдdg]_?[оo]_? (?> [бb6]_? (?=[хпбмгжxpmgj]) | [бb6]_? [ъь]_? (?=[еёe]) | [зz3]_? [аa] _? )?', '[пp]_?[рr]_?[оoиi]_?', '[зz3]_?[лl]_?[оo]_?', '[нnh]_?[аa]_?[дdg]_? (?=[хпбмгжxpmgj])', '[нnh]_?[аa]_?[дdg]_?[ъь]_? (?=[еёe])', '[пp]_?[оoаa]_?[дdg]_? (?=[хпбмгжxpmgj])', '[пp]_?[оoаa]_?[дdg]_?[ъь]_? (?=[еёe])', '[рr]_?[аa]_?[зz3сc]_? (?=[хпбмгжxpmgj])', '[рr]_?[аa]_?[зz3сc]_?[ъь]_? (?=[еёe])', '[вvb]_?[оo]_?[зz3сc]_? (?=[хпбмгжxpmgj])', '[вvb]_?[оo]_?[зz3сc]_?[ъь]_? (?=[еёe])', '[нnh]_?[еe]_?[дdg]_?[оo]_?', '[пp]_?[еe]_?[рr]_?[еe]_?', '[oо]_?[дdg]_?[нnh]_?[оo]_?', '[кk]_?[oо]_?[нnh]_?[оo]_?', '[мm]_?[уy]_?[дdg]_?[oоaа]_?', '[oо]_?[сc]_?[тt]_?[оo]_?', '[дdg]_?[уy]_?[рpr]_?[оoаa]_?', '[хx]_?[уy]_?[дdg]_?[оoаa]_?', '[мm]_?[нnh]_?[оo]_?[гg]_?[оo]_?(?![еёe]\\s)', '[мm]_?[оo]_?[рpr]_?[дdg]_?[оoаa]_?', '[мm]_?[оo]_?[зz3]_?[гg]_?[оoаa]_?', '[дdg]_?[оo]_?[лl]_?[бb6]_?[оoаa]_?', '[оo]_?[сc]_?[тt]_?[рpr]_?[оo]_?'); $badwords = array('([х]_?[й])', '(?<=\\PL) %RE_PRETEXT%? [hхx]_?[уyu]_?[ийiеeёяюju] #хуй, хуя, хую, хуем, хуёвый, охуительный #исключения: (?<! _hue(?=_) #HUE -- цветовая палитра | _hue(?=so_) #hueso -- испанское слово | _хуе(?=дин) #Хуедин -- город в Румынии | _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля | _хуе(?=ндай_) #Hyundai -- марка корейского автомобиля )', '(?<=\\PL) %RE_PRETEXT%? [hхx]_?[уyu]_?[ийi1]_?[лoо0] #хуйлo #исключения: (?<! _hue(?=_) #HUE -- цветовая палитра | _hue(?=so_) #hueso -- испанское слово | _хуе(?=дин) #Хуедин -- город в Румынии | _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля )', '(?<=\\PL) %RE_PRETEXT%? [hхx]_?[уyu]_?[л]_?[oо0] #ху*лo #исключения: (?<! _hue(?=_) #HUE -- цветовая палитра | _hue(?=so_) #hueso -- испанское слово | _хило(?=_) #хило | _хуе(?=дин) #Хуедин -- город в Румынии | _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля )', '(?<=\\PL) %RE_PRETEXT%? [hхx]_?[ийi1]_?[лl]_?[oоo0] #ху*лo #исключения: (?<! _hue(?=_) #HUE -- цветовая палитра | _hue(?=so_) #hueso -- испанское слово | _хуе(?=дин) #Хуедин -- город в Румынии | _хило(?=_) #хило | _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля )', '(?<=\\PL) %RE_PRETEXT%? [hхx]_?[ийi1]_?[l]_?[oоo0] #ху*лo #исключения: (?<! _hue(?=_) #HUE -- цветовая палитра | _hue(?=so_) #hueso -- испанское слово | _хуе(?=дин) #Хуедин -- город в Румынии | _хило(?=_) #хило | _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля )', '(?<=\\PL) %RE_PRETEXT%? [пp]_?[иieеё]_?[зz3]_?[дd](?=_?[:vowel:])', '(?<=\\PL) [cсs]_?[oaао]_?[cсs]_?[iи]\\b', '(?<=\\PL) [oо]_?[tт]_?[cсs]_?[oо]_?[cсs]_?[iи]', '(?<=\\PL) [cсs]_?[oо]_?[cсs]_?[аa]_?[тt]_?[ь]', '(?<=\\PL) %RE_PRETEXT%? [eеё]_? #исключения (?<!н[eе][её]_|т_е_) #неё, т.е. большие [бb6]_? (?= [уyиi]_ #ебу, еби | [ыиiоoaаеeёуy]_?[:consonant:] #ебут, ебать, ебись, ебёт, поеботина, выебываться, ёбарь #исключения (?<!_ebo[kt](?=_)|буд) #ebook, eboot, ее будут (?<!_ebo[kt](?=_)|бур) #ebook, eboot, ее будут (?<!_ebo[kt](?=_)|был) #ebook, eboot, е был (?<!_и[kt](?=_)|бай) #и бай | [лl](?:[оoаaыиiя]|ya) #ебло, ебла, ебливая, еблись, еблысь, ёбля | [нn]_?[уy] #ёбнул, ёбнутый | [кk]_?[аa] #взъёбка | [сc]_?[тt] #ебсти )', '(?<=\\PL) %RE_PRETEXT% (?<= \\pL\\pL|\\pL_\\pL_) [eеё]_?[бb6] #долбоёб, дураёб, изъёб, заёб, заебай, разъебай, мудоёбы ', '(?<=\\PL) я[еуи][бb][уy] (?=\\PL)', '(?<=\\PL) ёб (?=\\PL)', '\\b[ёeе]_?[бb]\\b', '\\b[ёeе]_?[бb]_?[tт]\\b', '[ё]_?[бb] ', '([т]_?[в]_?[о]_?[ю]_?[м]_?[а]_?[т]_?[ь])', '(?<=\\PL) ибу (?=\\PL)', '(?<=\\PL) %RE_PRETEXT%? [бb6]_?[лl]_?(?:я|ya)(?: _ #бля | _?[тдtd] #блять, бляди )', '(?<=\\PL) [пp]_?[иieе]_?[дdg]_?[eеaаoо]_?[rpр]', '(?<=\\PL) \\w*[пp]_?[иieе]_?[дdg]_?[eеaаoо]_?[rpр]\\b #исключения: (?<!импидор) #Импидор ', '(?<=\\PL) [дв]_?[еэ]_?[бb]_?[иi]_?[лl]\\b', '(?<=\\PL) [yу][р]_?[оo]_?[д]_?(?![л])', '(?<=\\PL) [мm]_?[уy]_?[дdg]_?[аa] #мудак, мудачок #исключения: (?<!_myda(?=s_)) #Chelonia mydas -- морская зеленая (суповая) черепаха ', '(?<=\\PL) [zж]_?h?_?[оo]_?[pп]_?[aаyуыiеeoо]', '(?<=\\PL) [мm]_?[аa]_?[нnh]_?[дdg]_?[aаyуыiеeoо]_ #манд[ауыео] #исключения: (?<! манда(?=[лн]|рин) | manda(?=[ln]|rin) ) ', '(?<=\\PL) [гg]_?[оo]_?[вvb]_?[нnh]_?[оoаaяеeyу]', '(?<=\\PL) f_?u_?[cс]_?k', '[^р]_?[scс]_?[yуu]_?[kк]_?[aаiи]', '[^р]_?[scс]_?[yуu]_?[4ч]_?[кk]', '\\bл_?[оo]_?[хx]\\b', '(?<=\\PL) [шщ]_?[лl]_?[ю]_?[хш]'); $trans = array('_' => '\\x20', '\\pL' => '[^\\x20\\d]', '\\PL' => '[\\x20\\d]', '[:vowel:]' => '[аеиоуыэюяёaeioyu]', '[:consonant:]' => '[^аеиоуыэюяёaeioyu\\x20\\d]'); $re_badwords = str_replace('%RE_PRETEXT%', '(?:' . implode('|', $pretext) . ')', '~' . implode('|', $badwords) . '~sxuSX'); $re_badwords = strtr($re_badwords, $trans); } $s = UTF8::convert_from($s, $charset); $replace = UTF8::convert_from($replace, $charset); $ss = $s; #saves original string if ($is_html) { #скрипты не вырезаем, т.к. м.б. обходной маневр на с кодом на javascript: #<script>document.write('сло'+'во')</script> #хотя давать пользователю возможность использовать код на javascript нехорошо $s = is_callable(array('HTML', 'strip_tags')) ? HTML::strip_tags($s, null, true, array('comment', 'style', 'map', 'frameset', 'object', 'applet')) : strip_tags($s); #заменяем html-сущности в "чистый" UTF-8 $s = UTF8::html_entity_decode($s, $is_htmlspecialchars = true); } if (strtoupper(substr($charset, 0, 3)) === 'UTF') { #remove combining diactrical marks $additional_chars = array(""); $s = UTF8::diactrical_remove($s, $additional_chars); } #ВотБ/\яПидорыОхуелиБлятьНахуйПохуйПи3децПолный if (version_compare(PHP_VERSION, '5.2.0', '>=')) { $s = preg_replace('~ [\\p{Lu}3] (?>\\p{Ll}+|/\\\\|[@36]+)++ #Вот (?= [\\p{Lu}3] (?:\\p{Ll} |/\\\\|[@36] ) ) #Бля ~sxuSX', '$0 ', $s); } $s = UTF8::lowercase($s); #получаем в массив только буквы и цифры #"с_л@о#во,с\xc2\xa7лово.Слово" -> "с л о во с лово слово слово слово слово" preg_match_all('~(?> \\xd0[\\xb0-\\xbf]|\\xd1[\\x80-\\x8f\\x91] #[а-я] | /\\\\ #л | @ #а | [a-z\\d]+ )+ ~sxSX', $s, $m); $s = ' ' . implode(' ', $m[0]) . ' '; $trans = array('/\\' => 'л', '@' => 'а'); $s = strtr($s, $trans); #цифровые подделки под буквы $trans = array('~ [3з]++ [3з\\x20]*+ ~sxuSX' => 'з', '~ [6б]++ [6б\\x20]*+ ~sxuSX' => 'б'); $s = preg_replace(array_keys($trans), array_values($trans), $s); #убираем все повторяющиеся символы, ловим обман типа "х-у-у-й" #"сллоооовоо слово х у у й" --> "слово слово х у й" $s = preg_replace('/( [\\xd0\\xd1][\\x80-\\xbf] \\x20? #optimized [а-я] | [a-z\\d] \\x20? ) \\1+ /sxSX', '$1', $s); //echo $s if ($replace === null || version_compare(PHP_VERSION, '5.2.0', '<')) { $result = preg_match($re_badwords, $s, $m, PREG_OFFSET_CAPTURE); if (function_exists('preg_last_error') && preg_last_error() !== PREG_NO_ERROR) { return preg_last_error(); } if ($result === false) { return 1; } #PREG_INTERNAL_ERROR = 1 if ($result && $replace === null) { list($word, $offset) = $m[0]; $s1 = substr($s, 0, $offset); $s2 = substr($s, $offset + strlen($word)); $delta = intval($delta); if ($delta === 0) { $fragment = '[' . trim($word) . ']'; } else { if ($delta < 1 || $delta > 10) { $delta = 3; } preg_match('/ (?> \\x20 (?>[\\xd0\\xd1][\\x80-\\xbf]|[a-z\\d]+)++ ){1,' . $delta . '}+ \\x20?+ $/sxSX', $s1, $m1); preg_match('/^ (?>[\\xd0\\xd1][\\x80-\\xbf]|[a-z\\d]+)*+ #ending \\x20?+ (?> (?>[\\xd0\\xd1][\\x80-\\xbf]|[a-z\\d]+)++ \\x20 ){0,' . $delta . '}+ /sxSX', $s2, $m2); $fragment = (ltrim(@$m1[0]) !== ltrim($s1) ? $continue : '') . trim((isset($m1[0]) ? $m1[0] : '') . '[' . trim($word) . ']' . (isset($m2[0]) ? $m2[0] : '')) . (rtrim(@$m2[0]) !== rtrim($s2) ? $continue : ''); } return UTF8::convert_to($fragment, $charset); } /* $wordsfinal=array( 'еб' ,'eb' ,'ёб' ,'ёb' ); $patternfinal = '/('.join($wordsfinal, '|').')/iu'; //print_r($s); //print_r($pattern_final); $resultNew = preg_match($patternfinal, $s,$m,PREG_OFFSET_CAPTURE); echo "ddd"; print_r($m); if ($resultNew === false) return 1; #PREG_INTERNAL_ERROR = 1 if ($resultNew) { return true; } //print_r($resultNew); //print_r($re_badwords); //print_r($m); */ /* $censor = new CensorWords; $censor->setDictionary("ru"); //$censor->generateCensorChecks(true); $resNew = $censor->censorString($s,true); print_r($resNew); if( count($resNew['matched'])>0 ) { return true; } */ //$pattern_final = '/('.join($words_final, '|').')/i'; //$resultNew = preg_match($pattern_final, $s); //print_r($resultNew); //print_r($pattern_final); //if ($result === false) return 1; #PREG_INTERNAL_ERROR = 1 //if ($result && $replace === null) //{ // return UTF8::convert_to($fragment, $charset); //} return false; } $result = preg_match_all($re_badwords, $s, $m); if (function_exists('preg_last_error') && preg_last_error() !== PREG_NO_ERROR) { return preg_last_error(); } if ($result === false) { return 1; } #PREG_INTERNAL_ERROR = 1 if ($result > 0) { #d($s, $m[0]); $s = $ss; #замена матного фрагмента на $replace foreach ($m[0] as $w) { $re_w = '~' . preg_replace_callback('~(?:/\\\\|[^\\x20])~suSX', array('self', '_make_regexp_callback'), $w) . '~sxuiSX'; $ss = preg_replace($re_w, $replace, $ss); #d($re_w); } while ($ss !== $s) { $ss = self::parse($s = $ss, $delta, $continue, $is_html, $replace, 'UTF-8'); } } return UTF8::convert_to($ss, $charset); }
/** * Make regular expression for case insensitive match * Example (non ASCII): "123_слово_test" => "123_(с|С)(л|Л)(о|О)(в|В)(о|О)_[tT][eE][sS][tT]" * Example (only ASCII): "123_test" => "(?i:123_test)" * * @param string $s * @param string|null $delimiter If the optional delimiter is specified, it will also be escaped. * This is useful for escaping the delimiter that is required by the PCRE functions. * The / is the most commonly used delimiter. * @return string|bool|null Returns FALSE if error occurred */ public static function preg_quote_case_insensitive($s, $delimiter = null) { if (!ReflectionTypeHint::isValid()) { return false; } if (is_null($s)) { return $s; } if (self::is_ascii($s)) { return '(?i:' . preg_quote($s, $delimiter) . ')'; } #speed improve $s_re = ''; $s_lc = UTF8::lowercase($s); if ($s_lc === false) { return false; } $s_uc = UTF8::uppercase($s); if ($s_uc === false) { return false; } $chars_lc = UTF8::str_split($s_lc); if ($chars_lc === false) { return false; } $chars_uc = UTF8::str_split($s_uc); if ($chars_uc === false) { return false; } foreach ($chars_lc as $i => $char) { if ($chars_lc[$i] === $chars_uc[$i]) { $s_re .= preg_quote($chars_lc[$i], $delimiter); } elseif (self::is_ascii($chars_lc[$i])) { $s_re .= '[' . preg_quote($chars_lc[$i] . $chars_uc[$i], $delimiter) . ']'; } else { $s_re .= '(' . preg_quote($chars_lc[$i], $delimiter) . '|' . preg_quote($chars_uc[$i], $delimiter) . ')'; } } return $s_re; }
/** * Грамматический разбор html кода на предложения и слова * * @param string $s Html текст * @param array|null $words Массив всех слов: * array(<абсол._поз._слова> => <слово>, ...) * @param array|null $sentences Массив предложений: * array( * <номер_предложения> => array( * <абсол._поз._слова> => <слово>, * ... * ), * ... * ) * Внимание! Значение передаётся по ссылке: * $sentences[$sentence_pos][$abs_pos] =& $words[$abs_pos]; * @param array|null $uniques Массив уникальных слов, отсортированный по ключам. * В ключах слова в нижнем регистре, в значениях кол-во их появлений в тексте. * @param array|null $offset_map Распределение абс. позиций слов к абс. байтовым позициям в нормализованном тексте: * array(<абсол._поз._слова> => <байт._поз._слова>, ...) * @return string Нормализованный текст */ public function parse($s, array &$words = null, array &$sentences = null, array &$uniques = null, array &$offset_map = null) { $s = $this->normalize($s); /* Розенталь: "(?)" ставится после слова для выражения сомнения или недоумения "(!)" ставится после слова для выражения автора к чужому тексту (согласия, одобрения или иронии, возмущения) */ preg_match_all('~(?>#1 letters ( #\\p{L}++ (?>' . $this->re_langs . ') #special (?> \\# (?!\\p{L}|\\d) #programming languages: C# | \\+\\+?+ (?!\\p{L}|\\d) #programming languages: C++, T++, B+ trees, Европа+; but not C+E, C+5 )?+ ) #2 numbers | ( \\d++ #digits (?> % (?!\\p{L}|\\d) )?+ #brand names: 120% ) #| \\p{Nd}++ #decimal number #| \\p{Nl}++ #letter number #| \\p{No}++ #other number #paragraph (see self::normalize()) | \\r\\r #sentence end by dot | \\. (?=[\\x20' . " " . '] (?!\\p{Ll}) #following symbol not letter in lowercase ) #sentence end by other | (?<!\\() #previous symbol not bracket [!?;…]++ #sentence end #following symbol not (?!["\\)' . "»" . "”" . "’" . "“" . '] ) ) ~sxuSX', $s, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); #cleanup $words = array(); $sentences = array(); $uniques = array(); $offset_map = array(); #init $sentence_pos = 0; #номер предложения $abs_pos = 0; #номер абсолютной позиции слова в тексте $w_prev = false; #предыдущее слово foreach ($m as $i => $a) { $is_alpha = $is_digit = false; if ($is_digit = array_key_exists(2, $a)) { list($w, $pos) = $a[2]; } elseif ($is_alpha = array_key_exists(1, $a)) { list($w, $pos) = $a[1]; } else { list($w, $pos) = $a[0]; if ($w !== '.') { if (!empty($sentences[$sentence_pos])) { $w_prev = false; $sentence_pos++; } continue; } if (!empty($sentences[$sentence_pos])) { $tmp = $w_prev; $w_prev = false; if ($tmp === false || UTF8::strlen($tmp) < 2 && !ctype_digit($tmp) || is_array($this->dot_reductions) && array_key_exists(UTF8::lowercase($tmp), $this->dot_reductions)) { continue; } $sentence_pos++; } continue; } $w_prev = $w; $words[$abs_pos] = $w; $sentences[$sentence_pos][$abs_pos] =& $words[$abs_pos]; $offset_map[$abs_pos] = $pos; $abs_pos++; } $uniques = array_count_values(explode(PHP_EOL, UTF8::lowercase(implode(PHP_EOL, $words)))); ksort($uniques, SORT_REGULAR); #d($words, $sentences, $uniques, $offset_map); return $s; }
/** * "Подсветка" найденных слов для результатов поисковых систем. * Ищет все вхождения цифр или целых слов в html коде и обрамляет их заданными тэгами. * Текст должен быть в кодировке UTF-8. * * @param string|null $s Текст, в котором искать * @param array|null $words Массив поисковых слов * @param bool $is_case_sensitive Искать с учётом от регистра? * @param string $tpl HTML шаблон для замены * @return string|bool|null returns FALSE if error occured */ public static function words_highlight($s, array $words = null, $is_case_sensitive = false, $tpl = '<span class="highlight">%s</span>') { if (!ReflectionTypeHint::isValid()) { return false; } if (is_null($s)) { return $s; } #оптимизация для пустых значений if (!strlen($s) || !$words) { return $s; } #оптимизация #{{{ $s2 = UTF8::lowercase($s); foreach ($words as $k => $word) { $word = UTF8::lowercase(trim($word, ".. *")); if ($word == '' || strpos($s2, $word) === false) { unset($words[$k]); } } if (!$words) { return $s; } #}}} #d($words); #кэширование построения рег. выражения для "подсвечивания" слов в функции при повторных вызовах static $func_cache = array(); $cache_id = md5(serialize(array($words, $is_case_sensitive, $tpl))); if (!array_key_exists($cache_id, $func_cache)) { $re_words = array(); foreach ($words as $word) { $is_mask = substr($word, -1) === '*'; if ($is_mask) { $word = rtrim($word, '*'); } $is_digit = ctype_digit($word); #рег. выражение для поиска слова с учётом регистра или цифр: $re_word = preg_quote($word, '~'); #рег. выражение для поиска слова НЕЗАВИСИМО от регистра: if (!$is_case_sensitive && !$is_digit) { if (UTF8::is_ascii($word)) { $re_word = '(?i:' . $re_word . ')'; } else { $lc = UTF8::str_split(UTF8::lowercase($re_word)); $uc = UTF8::str_split(UTF8::uppercase($re_word)); $re_word = array(); foreach ($lc as $i => $tmp) { $re_word[] = '[' . $lc[$i] . $uc[$i] . ']'; } $re_word = implode('', $re_word); } } #d($re_word); if ($is_digit) { $append = $is_mask ? '\\d*+' : '(?!\\d)'; } else { $append = $is_mask ? '\\p{L}*+' : '(?!\\p{L})'; } $re_words[$is_digit ? 'digits' : 'words'][] = $re_word . $append; } if (array_key_exists('words', $re_words) && $re_words['words']) { #поиск вхождения слова: $re_words['words'] = '(?<!\\p{L}) #просмотр назад (\\b не подходит и работает медленнее) (?:' . implode(PHP_EOL . '| ', $re_words['words']) . ') '; } if (array_key_exists('digits', $re_words) && $re_words['digits']) { #поиск вхождения цифры: $re_words['digits'] = '(?<!\\d) #просмотр назад (\\b не подходит и работает медленнее) (?:' . implode(PHP_EOL . '| ', $re_words['digits']) . ') '; } #d(implode(PHP_EOL . '| ', $re_words)); $func_cache[$cache_id] = '~(?> #встроенный PHP, Perl, ASP код <([\\?\\%]) .*? \\1> \\K #блоки CDATA | <\\!\\[CDATA\\[ .*? \\]\\]> \\K #MS Word тэги типа "<![if! vml]>...<![endif]>", #условное выполнение кода для IE типа "<!--[if lt IE 7]>...<![endif]-->": | <\\! (?>--)? \\[ (?> [^\\]"\']+ | "[^"]*" | \'[^\']*\' )* \\] (?>--)? > \\K #комментарии | <\\!-- .*? --> \\K #парные тэги вместе с содержимым | <((?i:noindex|script|style|comment|button|map|iframe|frameset|object|applet))' . self::$re_attrs . '(?<!/)> .*? </(?i:\\2)> \\K #парные и непарные тэги | <[/\\!]?+[a-zA-Z][a-zA-Z\\d]*+' . self::$re_attrs . '> \\K #html сущности (< > &) (+ корректно обрабатываем код типа &amp;nbsp;) | &(?> [a-zA-Z][a-zA-Z\\d]++ | \\#(?> \\d{1,4}+ | x[\\da-fA-F]{2,4}+ ) ); \\K | ' . implode(PHP_EOL . '| ', $re_words) . ' ) ~suxSX'; #d($func_cache[$cache_id]); } $s = preg_replace_callback($func_cache[$cache_id], function (array $m) use($tpl) { return $m[0] !== '' ? sprintf($tpl, $m[0]) : $m[0]; }, $s); return $s; }