РЕГИСТРАЦИЯ
Гость
Вход не выполнен.

Умное создание анонсов материалов, форматированных бб кодами.

Умное создание анонсов материалов, форматированных бб кодами.
Категории раздела
0
1000
Кто: MrBoriska, Когда: , Куда: PHP


Дорогие друзья, наверно каждый создатель CMS, форматирование материалов в которой делается с помощью BB кодов, сталкивался с проблемой создания анонсов для таких материалов. В интернете, несмотря на наличие скриптов для закрытия незакрытых HTML тегов, не густо со скриптами для делания того же, но для BB-кодов. Недавно я столкнулся с такой же проблемой, и успешно преодолел её.

Этот скрипт был создан как часть класса PrintText для Atom-M CMS. Первый коммит с ним можно посмотреть тут.

Как оказалось, скрипты для автозакрытия HTML тегов были недостаточно функциональны, они савсем не подходили для BB кодов и... В общем я хотел сказать, что все пришлось писать самому, с нуля.

Для людей сделал вариант только самих функций: 

/**
 * create announce and close opened bb tags
 */
function getAnnounce($str, $start = 0, $length = 500) {
    
    // Ищем теги [announce]
    $start_tag = mb_strpos($str, '[announce]');
    $end_tag = mb_strpos($str, '[/announce]');
    
    // Если есть тег [announce], анонсом будет его содержимое
    if (false !== $start_tag && false !== $end_tag && $end_tag > $start_tag) {
        // Удаляем теги announce из текста
        $end_tag -= 10;
        $str = mb_substr($str,0,$start_tag).mb_substr($str,$start_tag+10);
        $str = mb_substr($str,0,$end_tag).mb_substr($str,$end_tag+11);
        
        $start = $start_tag;
        $length = ($end_tag - $start_tag);
    
    // Иначе берем анонс из начала($start) материала
    } else {
        $start = (int)$start;
        $length = (int)$length;

        if ($length < 1) $length = 500;
        if ($start >= $length) $start = 0;
    }

    $announce = closeOpenTags($str,$start,$length);
    // >>Вот на этом месте удобно разместить парсер BB кодов<<
    if (strlen($announce) > $length) $announce .= '...';
    return $announce;
}


/**
 * Запускает нужные операции, для закрытия незакрытых тегов
 */
function closeOpenTags($str, $start = 0, $length = 500) {
    
    // Открываем теги, которые были открыты, но не закрылись перед началом вырезанного текста.
    if (!empty($start) && is_numeric($start) && $start > 4)
        $content = addTags(searchUnclosedTags(correctCut($str,0,$start)),true);
    else
        $content = '';
    
    // Добавляем остальную часть анонса.
    $content .= correctCut($str,$start,$length);
    $content .= addTags(searchUnclosedTags($content));
    

    return $content;
}
/**
 * Вырезает текст для анонса так, чтобы места разреза были между тегами и не резали сами теги.
 */
function correctCut($str,$start,$length) {
    /* Первый разрез */
    if ($start !== 0) {
        // Помещаем место начального разреза между тегами а не в теге.
        $start_pos = mb_strpos($str, '[',$start);
        $end_pos = mb_strpos($str, ']',$start);
        if ($end_pos !== false && ($start_pos === false || $start_pos > $end_pos)) {
            $length += ($end_pos - $start + 1);
            $start = $end_pos+1;
        }
        // Для тегов, содержимое которых обрезать некорректно.(Если такой тег был разрезан, то помещаем место разреза сразу после такого тега.)
        if (preg_match('#\[\/(url|video|img)\]#u', $str , $matches, PREG_OFFSET_CAPTURE , $start) === 1) {
            $close_pos = $matches[1][1] - 2;
            $start_pos = mb_strpos($str, '[',$start);
            if ($start_pos === $close_pos) {
                $start = ($start_pos + mb_strlen($matches[1][0]));
            }
                
        }
        unset($matches);
    }
    
    
    /* Второй разрез */
    // Помещаем место конечного разреза между тегами, а не в теге.
    if ($start+$length < mb_strlen($str)) {
        $start_pos = mb_strpos($str, '[',$start+$length);
        $end_pos = mb_strpos($str, ']',$start+$length);
        if ($end_pos !== false && ($start_pos === false || $start_pos > $end_pos)) {
            $length += ($end_pos - $start + 1);
        }
        // Для тегов, содержимое которых обрезать некорректно.(Если такой тег был разрезан, то помещаем место разреза сразу после такого тега.)
        if (preg_match('#\[\/(url|video|img)\]#u', $str , $matches, PREG_OFFSET_CAPTURE , $start+$length) === 1) {
            $close_pos = $matches[1][1] - 2;
            $start_pos = mb_strpos($str, '[',$start+$length);
            if ($start_pos === $close_pos) {
                $length = ($start_pos + mb_strlen($matches[1][0]))-$start+1;
            }
                
        }
    }
    
    
    return mb_substr($str,$start,$length);
}
/**
 * Ищет в строке незакрытые теги, возвращает массив массивов, где в нулевом индексе массив имен тегов, а в первом индексе массив соответсвующих именам полных имен тегов.(все содержимое между[] для открывающих тегов)
 */
function searchUnclosedTags($str) {
    // Список тегов, которые следует закрывать.
    $bbCodes = array(
        'left', 'right', 'center', 'list', 'quote', 'spoiler', 'hide',
        'code', 'html', 'xml', 'js', 'php', 'sql', 'css',
        'b', 'i', 's', 'u', 'size', 'url', 'color', 'img'
    );

    $name_tags = array();
    $full_name_tags = array();
    preg_match_all('#\[(\/|!|)([a-zA-Z0-9]+)(\=[^\]\[\'\"]*|)\]#u', $str, $tags);
    if (!empty($tags[2])) {
        foreach ($tags[2] as $key => $tag_name) {
            /* 
            "[{$tags[0][$key]}{$tag_name}{$tag_params}]"
            
            $tags[0][$key] : равно "/", если тег закрывающий. И пустоте, если открывающий.
            $tag_name      : имя тега. может состоять из любых латинских букв и цифр.
            $tag_params    : если и существует то всегда начинается с "=". Не может содержать символы: ",',],[
            */
            if (in_array($tag_name,$bbCodes)) {// Если тег определен для интерпретирования парсером
                if ($tags[1][$key] == '/') {
                    // Нашли закрывающий тег и удалили его из списков неполноценных тегов
                    $key = array_search($tag_name,array_reverse($name_tags,true));// Ведем поиск открывающего тега с тем же имененем с конца списка.
                    unset($name_tags[$key]);
                    unset($full_name_tags[$key]);
                } else {
                    // Нашли открывающий тег
                    $tag_params = ($tags[3][$key]) ? $tags[3][$key] : '';
                    $name_tags[] = $tag_name;
                    $full_name_tags[] = ($tags[1][$key] ? $tags[1][$key] : '').$tag_name.$tag_params;
                }
            }
        }
    }
    
    return array($name_tags,$full_name_tags);
}
/**
 * Принимает массив тегов, возвращает строку закрывающих($open=false) или открывающих ($open=true) тегов
*/
function addTags($tags,$open=false) {

    $add_str = '';
    if ($open) {
        foreach ($tags[1] as $n => $full_tag) {
            $add_str .= "[{$full_tag}]";
        }
    } else {
        foreach (array_reverse($tags[0],true) as $n => $tag) {    
            $add_str .= "[/{$tag}]";
        }
    }
    return $add_str;
}

Главенствующая функция:  getAnnounce($str, $start = 0, $length = 500)

Принимает в первом параметре строку с полным текстом материала. Во втором позицию первого разреза, а в третьем позицию второго, относительно первого.

Особенность:  Если в тексте материала есть бб код [announce] то позиция первого и второго разреза будет определена относительно позиций открывающего и закрывающего этого бб кода. Кроме того, в конце анонса добавляется "..."(многоточие), если материал не слишком короткий.

Если вам не нужно искать тег [announce] и добавлять многоточие автоматически, то можно использовать функцию с более конкретным предназначением: closeOpenTags($str,$start = 0,$length = 500)

Надеюсь сей скрипт поможет сэкономить вам время и нервы)