Пример #1
0
 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;
 }
Пример #2
0
    /**
     *
     * @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);
    }
Пример #3
0
 /**
  * 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;
 }
Пример #4
0
    /**
     * Грамматический разбор 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;
    }
Пример #5
0
    /**
     * "Подсветка" найденных слов для результатов поисковых систем.
     * Ищет все вхождения цифр или целых слов в 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 сущности (&lt; &gt; &amp;) (+ корректно обрабатываем код типа &amp;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;
    }