<?php



/**

 * Более продвинутый аналог strip_tags() для корректного вырезания тагов из html кода.

 * Функция strip_tags(), в зависимости от контекста, может работать не корректно.

 * Воможности:

 *   - корректно обрабатываются вхождения типа "a < b > c"

 *   - корректно обрабатывается "грязный" html, когда в значениях атрибутов тагов могут встречаться символы < >

 *   - корректно обрабатывается разбитый html

 *   - вырезаются комментарии, скрипты, стили, PHP, Perl, ASP код, MS Word таги

 *   - автоматически форматируется текст, если он содержит html код

 *   - защита от подделок типа: "<<fake>script>alert('hi')</</fake>script>"

 *

 * @param   string  $s

 * @param   array   $allowable_tags     Массив тагов, которые не будут вырезаны

 * @param   bool    $is_format_spaces   Форматировать пробелы и переносы строк?

 *                                      Текст форматируется автоматически, если он содержит html код:

 *                                      вид текста на выходе (plain) максимально приближеется виду текста в браузере на входе

 *                                      Другими словами, грамотно преобразует text/html в text/plain

 *                                      Если текст содержит html таги, $is_format_spaces = TRUE

 * @param   array   $pair_tags   массив имён парных тагов, которые будут удалены вместе с содержимым

 *                               см. значения по умолчанию

 * @param   array   $para_tags   массив имён парных тагов, которые будут восприниматься как параграфы (если $is_format_spaces = true)

 *                               см. значения по умолчанию

 * @return  string

 *

 * @author   Nasibullin Rinat <n a s i b u l l i n  at starlink ru>

 * @charset  ANSI

 * @version  4.0.4-dev2  :TODO: <pre>text\r\ntext</pre>

 */

function strip_tags_smart(

    /*string*/ $s,

    array $allowable_tags = null,

    /*boolean*/ $is_format_spaces = false,

    array $pair_tags = array('script', 'style', 'map', 'iframe', 'frameset', 'object', 'applet', 'comment', 'button'),

    array $para_tags = array('p', 'td', 'th', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'form', 'title')

)

{

    static $_callback_type  = false;

    static $_allowable_tags = array();

    static $_para_tags      = array();

    #регулярное выражение для атрибутов тагов

    #корректно обрабатывает грязный и битый HTML в однобайтовой или UTF-8 кодировке!

    static $re_attrs_fast_safe =  '(?> (?>[\x20\r\n\t]+|\xc2\xa0)+  #пробельные символы (д.б. обязательно)

                                       (?>

                                         #правильные атрибуты

                                                                        [^>"\']+

                                         | (?<=[\=\x20\r\n\t]|\xc2\xa0) "[^"]*"

                                         | (?<=[\=\x20\r\n\t]|\xc2\xa0) \'[^\']*\'

                                         #разбитые атрибуты

                                         |                              [^>]+

                                       )*

                                   )?';



    if (is_array($s) && $_callback_type === 'strip_tags')

    {

        $tag = strtolower($s[1]);

        if ($_allowable_tags &&

            (array_key_exists($tag, $_allowable_tags) || array_key_exists('<' . trim(strtolower($s[0]), '< />') . '>', $_allowable_tags))

            ) return $s[0];

        if ($tag == 'br') return "\r\n";

        if ($_para_tags && array_key_exists($tag, $_para_tags)) return "\r\n\r\n";

        return '';

    }



    if (($pos = strpos($s, '<') === false) || strpos($s, '>', $pos) === false)  #оптимизация скорости

    {

        #таги не найдены

        return $s;

    }



    #непарные таги (открывающие, закрывающие, !DOCTYPE, MS Word namespace)

    $re_tags = '/<[\/\!]? ([a-zA-Z][a-zA-Z\d]* (?>\:[a-zA-Z][a-zA-Z\d]*)?)' . $re_attrs_fast_safe . '\/?>/sx';



    $patterns = array(

        '/<([\?\%]) .*? \\1>/sx',     #встроенный PHP, Perl, ASP код

        '/<\!\[CDATA\[ .*? \]\]>/sx', #блоки CDATA

        #'/<\!\[  [\x20\r\n\t]* [a-zA-Z] .*?  \]>/sx',  #:DEPRECATED: MS Word таги типа <![if! vml]>...<![endif]>



        '/<\!--.*?-->/s', #комментарии



        #MS Word таги типа "<![if! vml]>...<![endif]>",

        #условное выполнение кода для IE типа "<!--[if expression]> HTML <![endif]-->"

        #условное выполнение кода для IE типа "<![if expression]> HTML <![endif]>"

        #см. http://www.tigir.com/comments.htm

        '/<\! (?>--)?

              \[

              (?> [^\]"\']+ | "[^"]*" | \'[^\']*\' )*

              \]

              (?>--)?

         >/sx',

    );

    if ($pair_tags)

    {

        #парные таги вместе с содержимым:

        foreach ($pair_tags as $k => $v) $pair_tags[$k] = preg_quote($v, '/');

        $patterns[] = '/<((?i:' . implode('|', $pair_tags) . '))' . $re_attrs_fast_safe . '> .*? <\/(?i:\\1)' . $re_attrs_fast_safe . '>/sx';

    }

    #d($patterns);



    $i = 0; #защита от зацикливания

    $max = 99;

    while ($i < $max)

    {

        $s2 = preg_replace($patterns, '', $s);

        if ($i == 0)

        {

            $is_html = ($s2 != $s || preg_match($re_tags, $s2));

            if ($is_html)

            {

                #В библиотеке PCRE для PHP \s - это любой пробельный символ, а именно класс символов [\x09\x0a\x0c\x0d\x20\xa0] или, по другому, [\t\n\f\r \xa0]

                #Если \s используется с модификатором /u, то \s трактуется как [\x09\x0a\x0c\x0d\x20]

                #Браузер не делает различия между пробельными символами,

                #друг за другом подряд идущие символы воспринимаются как один

                #$s = str_replace(array("\r", "\n", "\t"), ' ', $s);

                $s2 = strtr($s2, "\x09\x0a\x0c\x0d", '    ');



                #массив тагов, которые не будут вырезаны

                if ($allowable_tags) $_allowable_tags = array_flip($allowable_tags);



                #парные таги, которые будут восприниматься как параграфы

                if ($para_tags) $_para_tags = array_flip($para_tags);

            }

        }#if



        #обработка тагов

        if ($is_html)

        {

            $_callback_type = 'strip_tags';

            $s2 = preg_replace_callback($re_tags, __FUNCTION__, $s2);

            $_callback_type = false;

        }



        if ($s === $s2) break;

        $s = $s2; $i++;

    }#while

    if ($i >= $max) $s = strip_tags($s); #too many cycles for replace...



    if ($is_format_spaces || $is_html)

    {

        #вырезаем дублирующие пробелы

        $s = preg_replace('/\x20\x20+/s', ' ', trim($s));

        #вырезаем пробелы в начале и в конце строк

        $s = str_replace(array("\r\n\x20", "\x20\r\n"), "\r\n", $s);

        #заменяем 2 и более переносов строк на 2 переноса строк

        $s = preg_replace('/\r\n[\r\n]+/s', "\r\n\r\n", $s);

    }

    return $s;

}



?>