Балансировка тегов. object

09.12.2010

Задача:
Необходимо обеспечить обрезание html текста до определенной длины с балансировкой тегов. Обеспечить полноценную обработку object тегов.

Решение:
Для решения первой части задачи мы возьмем функцию из wordpress, которая называется balance_tags.
Она прекрасно справляется с задачей балансировки тегов до тех пор, пока в тексте не встретится тег object, который содержит вложенные теги param.
Если наша функция truncate обрежет текст на середине тега object? Скажем, таким образом 

<param src=""...

В результате поплывет верстка. Чтобы избежать этого эффекта,
я доработал функцию balance_tags. Алгоритм таков:

1) выгребаем все теги object и засовываем их в массив
2) заменяем их на теги <obj>n</obj>, где n - это порядковый номер тега object
3) обрезаем текст
4) балансируем теги
5) заменяем <obj>n</obj> на соответсвующий тег.

function object2obj($matches)
{
    static $n = 0;
    return '<obj>'.$n++.'</obj>';
}

function obj2object($matches)
{
    global $objects;
    $num = (int)$matches[1];
    if( isset($matches[0]) && isset($objects[0][$num]) )
    {
        return $objects[0][$num];
    }   
    return false;   
}

function balance_tags( $params ) {
    $text = $params['content'];
    $truncate = isset($params['truncate']) ? $params['truncate'] : '';
    
    global $objects;
    

    preg_match_all("/(<object.{1,700}/object>)/mis", $text, $objects);
    if( isset($objects[0]) && sizeof($objects[0]) > 0 )
    {
        $pos = strpos($text, '<object');
        $text = preg_replace_callback(array('/(<object.{1,700}/object>)/mis'), 'object2obj', $text);

        if( $truncate && $pos < $truncate )
        {
            $objlen = 0;
            foreach( $objects[0] as $object )
            {  
                $objlen =+ strlen($object) + 300;
                if( $objlen > $truncate ) break;
            }
            $truncate -= $objlen;   
        }
    }

    if( $truncate )
    {
        $text = truncate($text, $truncate);
    }

    
    $tagstack = array();
    $stacksize = 0;
    $tagqueue = '';
    $newtext = '';
    $single_tags = array('br', 'hr', 'img', 'input'); // Known single-entity/self-closing tags
    $nestable_tags = array('blockquote', 'div', 'span', 'obj'); // Tags that can be immediately nested within themselves

    // WP bug fix for comments - in case you REALLY meant to type '< !--'
    $text = str_replace('< !--', '<    !--', $text);
    // WP bug fix for LOVE <3 (and other situations with '<' before a number)
    $text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);

    while ( preg_match("/<(/?[w:]*)s*([^>]*)>/", $text, $regex) ) {
        $newtext .= $tagqueue;

        $i = strpos($text, $regex[0]);
        $l = strlen($regex[0]);

        // clear the shifter
        $tagqueue = '';
        // Pop or Push
        if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
            $tag = strtolower(substr($regex[1],1));
            // if too many closing tags
            if( $stacksize <= 0 ) {
                $tag = '';
                // or close to be safe $tag = '/' . $tag;
            }
            // if stacktop value = tag close value then pop
            else if ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag
                $tag = '</' . $tag . '>'; // Close Tag
                // Pop
                array_pop( $tagstack );
                $stacksize--;
            } else { // closing tag not at top, search for it
                for ( $j = $stacksize-1; $j >= 0; $j-- ) {
                    if ( $tagstack[$j] == $tag ) {
                    // add tag to tagqueue
                        for ( $k = $stacksize-1; $k >= $j; $k--) {
                            $tagqueue .= '</' . array_pop( $tagstack ) . '>';
                            $stacksize--;
                        }
                        break;
                    }
                }
                $tag = '';
            }
        } else { // Begin Tag
            $tag = strtolower($regex[1]);

            // Tag Cleaning

            // If self-closing or '', don't do anything.
            if ( substr($regex[2],-1) == '/' || $tag == '' ) {
                // do nothing
            }
            // ElseIf it's a known single-entity tag but it doesn't close itself, do so
            elseif ( in_array($tag, $single_tags) ) {
                $regex[2] .= '/';
            } else {    // Push the tag onto the stack
                // If the top of the stack is the same as the tag we want to push, close previous tag
                if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) {
                    $tagqueue = '</' . array_pop ($tagstack) . '>';
                    $stacksize--;
                }
                $stacksize = array_push ($tagstack, $tag);
            }

            // Attributes
            $attributes = $regex[2];
            if( !empty($attributes) )
                $attributes = ' '.$attributes;

            $tag = '<' . $tag . $attributes . '>';
            //If already queuing a close tag, then put this tag on, too
            if ( !empty($tagqueue) ) {
                $tagqueue .= $tag;
                $tag = '';
            }
        }
        $newtext .= substr($text, 0, $i) . $tag;
        $text = substr($text, $i + $l);
    }

    // Clear Tag Queue
    $newtext .= $tagqueue;

    // Add Remaining text
    $newtext .= $text;

    // Empty Stack
    while( $x = array_pop($tagstack) )
        $newtext .= '</' . $x . '>'; // Add remaining tags to close

    // WP fix for the bug with HTML comments
    $newtext = str_replace("< !--","<!--",$newtext);
    $newtext = str_replace("<    !--","< !--",$newtext);

    if( isset($objects[0]) && sizeof($objects) > 0 )
    {
        $newtext = preg_replace_callback(array('/<obj>(d*)</obj>/'), 'obj2object', $newtext);
    }    
    return $newtext;
    
}

function truncate($string, $length = 80, $etc = '', $break_words = false, $middle = false)
{
if ($length == 0)
    return '';

if (strlen($string) > $length) {
    $length -= min($length, strlen($etc));
    if (!$break_words && !$middle) {
        $string = preg_replace('/s+?(S+)?$/', '', substr($string, 0, $length+1));
    }
    if(!$middle) {
        return substr($string, 0, $length) . $etc;
    } else {
        return substr($string, 0, $length/2) . $etc . substr($string, -$length/2);
    }
} else {
    return $string;
}
}

 

вот и все

Теги: php ,html ,
Категории: php ,программирование ,
просмотров: 106

Комментарии

Нет комментариев

добавить комментарий

Ваше имя: *
Ваш email: *
Ваш сайт:
Комментарий: *
*
Получать комментарии на email

Популярные

10 причин, чтобы отказаться от алкоголя и одна – чтобы употреблять алкоголь

Комментариев: 53 | просмотров: 8635
01.09.2009

О вреде микроволновой печи

Комментариев: 9 | просмотров: 3768
13.12.2009

Весело, весело встретим Новый GOD

Комментариев: 9 | просмотров: 2140
17.12.2009

Масонская символика на долларе

Комментариев: 4 | просмотров: 4328
03.03.2009

PHP. strtolower(strtoupper) и UTF-8

Комментариев: 4 | просмотров: 1933
14.05.2009

Библия. Книга Судей. Глава 19

Комментариев: 4 | просмотров: 1491
30.08.2009

Дискотека Авария. Заколебал ты

Комментариев: 4 | просмотров: 408
01.08.2010

Категории

Календарь

ПнВтСрЧтПнСбВс
123456
78910111213
14151617181920
21222324252627

Цитаты

Водка белая, но краснит нос и чернит репутацию

А. П. Чехов