$info ) { if ( ! in_array( self::lower( $info[0] ), $skip, true ) ) { $words[ $pattern ] = $info; } } } self::$rules = array( 'allow' => $parts ? '/' . self::BEFORE . '(?:' . implode( '|', $parts ) . ')' . self::AFTER . '/iu' : '', 'words' => $words, 'latin' => (bool) $s['check_latin'], 'hints' => YSNO_Dictionary::latin(), 'abbr' => (bool) $s['check_abbr'], ); return self::$rules; } /** HTML → читаемый текст для показа фрагментов (адреса и почта остаются). */ public static function readable( $html ) { $text = preg_replace( '#<(script|style|code|pre|kbd|samp|svg|noscript)\b[^>]*>.*?#is', ' ', (string) $html ); $text = preg_replace( '//s', ' ', $text ); $text = preg_replace( '/\[\/?[a-z0-9_\-]+(?:\s[^\]]*)?\]/i', ' ', $text ); $text = preg_replace( '/<[^>]+>/', ' ', $text ); $text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); return trim( preg_replace( '/\s+/u', ' ', $text ) ); } /** HTML → обычный текст без кода, адресов и шорткодов. */ public static function plain( $html ) { $text = (string) $html; $text = preg_replace( '#<(script|style|code|pre|kbd|samp|svg|noscript)\b[^>]*>.*?#is', ' ', $text ); $text = preg_replace( '//s', ' ', $text ); $text = preg_replace( '/\[\/?[a-z0-9_\-]+(?:\s[^\]]*)?\]/i', ' ', $text ); $text = preg_replace( '/<[^>]+>/', ' ', $text ); $text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $text = preg_replace( '#(?:https?://|www\.)\S+#iu', ' ', $text ); $text = preg_replace( '/[\w.+\-]+@[\w\-]+(?:\.[\w\-]+)+/u', ' ', $text ); $text = preg_replace( '/\b[\w\-]+(?:\.[\w\-]+)*\.(?:ru|su|com|net|org|io|info|biz|shop|online|store|site|pro|me|tech|app|dev|рф)\b/iu', ' ', $text ); $text = preg_replace( '/(? [word, type, count, suggest, note, context]. Тип: latin | abbr | word. */ public static function check( $html ) { $rules = self::rules(); $text = self::plain( $html ); $shown = self::readable( $html ); if ( '' === $text ) { return array(); } $clean = $rules['allow'] ? preg_replace( $rules['allow'], ' ', $text ) : $text; $found = array(); if ( $rules['latin'] || $rules['abbr'] ) { preg_match_all( '/' . self::BEFORE . "[A-Za-z][A-Za-z'’\-]*" . self::AFTER . '/u', $clean, $m ); foreach ( $m[0] as $token ) { $token = rtrim( $token, "-'’" ); if ( strlen( $token ) < 2 || preg_match( '/^[IVXLCDM]+$/', $token ) ) { continue; } $is_abbr = strlen( $token ) <= 6 && strtoupper( $token ) === $token; if ( ( $is_abbr && ! $rules['abbr'] ) || ( ! $is_abbr && ! $rules['latin'] ) ) { continue; } $key = self::lower( $token ); if ( ! isset( $found[ $key ] ) ) { $found[ $key ] = array( 'word' => $token, 'type' => $is_abbr ? 'abbr' : 'latin', 'count' => 0, 'suggest' => (string) ( $rules['hints'][ self::lower( $token ) ] ?? '' ), 'note' => '', 'context' => self::context( $shown, $token ), ); } ++$found[ $key ]['count']; } } if ( $rules['words'] ) { $low = str_replace( 'ё', 'е', self::lower( $clean ) ); foreach ( $rules['words'] as $pattern => $info ) { if ( ! preg_match_all( '/' . self::BEFORE . '(?:' . $pattern . ')' . self::AFTER . '/u', $low, $m ) ) { continue; } $key = 'w:' . $info[0]; if ( ! isset( $found[ $key ] ) ) { $found[ $key ] = array( 'word' => $info[0], 'type' => 'word', 'count' => 0, 'suggest' => (string) $info[1], 'note' => (string) ( $info[2] ?? '' ), 'context' => self::context( $shown, $m[0][0] ), ); } $found[ $key ]['count'] += count( $m[0] ); } } return $found; } /** Доля латинских букв среди всех букв текста: 1 — текст целиком на иностранном языке. */ public static function latin_share( $html ) { $text = self::plain( $html ); $latin = preg_match_all( '/[A-Za-z]/', $text ); $all = preg_match_all( '/\p{L}/u', $text ); return $all ? round( $latin / $all, 2 ) : 0; } /** Фрагмент текста вокруг первого вхождения слова. */ private static function context( $text, $word ) { $pos = function_exists( 'mb_stripos' ) ? mb_stripos( $text, $word, 0, 'UTF-8' ) : stripos( $text, $word ); if ( false === $pos ) { return ''; } $start = max( 0, $pos - 50 ); $len = 100 + mb_strlen( $word, 'UTF-8' ); $frag = mb_substr( $text, $start, $len, 'UTF-8' ); return ( $start > 0 ? '…' : '' ) . $frag . ( $start + $len < mb_strlen( $text, 'UTF-8' ) ? '…' : '' ); } /** Подписи типов. */ public static function type_label( $type ) { $labels = array( 'latin' => __( 'Латиница', 'yasno' ), 'abbr' => __( 'Аббревиатура', 'yasno' ), 'word' => __( 'Англицизм', 'yasno' ), ); return $labels[ $type ] ?? $type; } }