PHP: etiketleri görmezden, HTML kesecek

12 Cevap php

Ben (bir veritabanı veya metin dosyasından yüklenir) bazı metin kesecek istiyorum, ama etiketleri dahil edilir ve daha az metin iade edilecektir bir sonucu olarak bu yüzden HTML içerir. Bu daha sonra etiketleri kapalı olması, ya da kısmen (yani Düzenli düzgün çalışmayabilir ve daha az içerik hala var) kapalı olmama neden olabilir. Nasıl metnine dayanarak kesebilir (ve daha karmaşık sorunlar neden olabilir gibi bir tabloya almak zaman muhtemelen durdurma).

substr("Hello, my <strong>name</strong> is <em>Sam</em>. I&acute;m a web developer.",0,26)."..."

: Ile sonuçlanacaktır

Hello, my <strong>name</st...

Ne isteyeyim olduğunu:

Hello, my <strong>name</strong> is <em>Sam</em>. I&acute;m...

Bunu nasıl yapabilirim?

Benim soru PHP bunu nasıl iken, C # bunu bilmek iyi olurdu ... Ben bağlantı noktası yöntem üzerinden mümkün olacağını düşünmek gibi bir yerleşik olmadığı sürece ya (Tamam olmalıdır yöntemi).

Tek bir karakter (bu örnekte olduğu gibi yerine 7 karakter) olarak kabul edilmesi gerekir - Ayrıca ben bir HTML varlık &acute; dahil olduğunu unutmayın.

strip_tags bir çare, ama biçimlendirme ve bağlantıları kaybedecek ve yine HTML varlıkları ile sorun olurdu.

12 Cevap

Geçerli XHTML kullanarak varsayarak, bu HTML ayrıştırmak ve emin etiketleri düzgün işlenir yapmak basit. Sadece sözcükler kadar açılmış olan izlemek, ve "dışarı yolda" tekrar bunları kapatmak için emin olmak gerekir.

<?php
header('Content-type: text/plain; charset=utf-8');

function printTruncated($maxLength, $html, $isUtf8=true)
{
    $printedLength = 0;
    $position = 0;
    $tags = array();

    // For UTF-8, we need to count multibyte sequences as one character.
    $re = $isUtf8
        ? '{</?([a-z]+)[^>]*>|&#?[a-zA-Z0-9]+;|[\x80-\xFF][\x80-\xBF]*}'
        : '{</?([a-z]+)[^>]*>|&#?[a-zA-Z0-9]+;}';

    while ($printedLength < $maxLength && preg_match($re, $html, $match, PREG_OFFSET_CAPTURE, $position))
    {
        list($tag, $tagPosition) = $match[0];

        // Print text leading up to the tag.
        $str = substr($html, $position, $tagPosition - $position);
        if ($printedLength + strlen($str) > $maxLength)
        {
            print(substr($str, 0, $maxLength - $printedLength));
            $printedLength = $maxLength;
            break;
        }

        print($str);
        $printedLength += strlen($str);
        if ($printedLength >= $maxLength) break;

        if ($tag[0] == '&' || ord($tag) >= 0x80)
        {
            // Pass the entity or UTF-8 multibyte sequence through unchanged.
            print($tag);
            $printedLength++;
        }
        else
        {
            // Handle the tag.
            $tagName = $match[1][0];
            if ($tag[1] == '/')
            {
                // This is a closing tag.

                $openingTag = array_pop($tags);
                assert($openingTag == $tagName); // check that tags are properly nested.

                print($tag);
            }
            else if ($tag[strlen($tag) - 2] == '/')
            {
                // Self-closing tag.
                print($tag);
            }
            else
            {
                // Opening tag.
                print($tag);
                $tags[] = $tagName;
            }
        }

        // Continue after the tag.
        $position = $tagPosition + strlen($tag);
    }

    // Print any remaining text.
    if ($printedLength < $maxLength && $position < strlen($html))
        print(substr($html, $position, $maxLength - $printedLength));

    // Close any open tags.
    while (!empty($tags))
        printf('</%s>', array_pop($tags));
}


printTruncated(10, '<b>&lt;Hello&gt;</b> <img src="world.png" alt="" /> world!'); print("\n");

printTruncated(10, '<table><tr><td>Heck, </td><td>throw</td></tr><tr><td>in a</td><td>table</td></tr></table>'); print("\n");

printTruncated(10, "<em><b>Hello</b>&#20;w\xC3\xB8rld!</em>"); print("\n");

Encoding note: Yukarıdaki kod XHTML UTF-8 kodlanmış varsayar. (Örneğin {[) (2]} gibi) ASCII uyumlu tek bayt kodlamaları de sadece üçüncü argüman olarak false geçmesi desteklenir. Her print açıklamada tekrar dönüştürme, sonra işlevi çağırmadan önce UTF-8 dönüştürmek için mb_convert_encoding ile destek hack olsa diğer baytlı kodlamaları, desteklenmez.

(Sen always olsa, UTF-8 kullanıyor olmalıdır.)

Edit: karakter varlıkları ve UTF-8 işlemek için güncellendi. Bu karakter bir karakter öğesi ise işlevi çok bir karakter basacaktır Sabit hata.

Ben Søren Løvborg UTF-8 uyumlu hale printTruncated function hafif değişiklikler yaptık:

   /* Truncate HTML, close opened tags
    *
    * @param int, maxlength of the string
    * @param string, html       
    * @return $html
    */  
    function html_truncate($maxLength, $html){

        mb_internal_encoding("UTF-8");

        $printedLength = 0;
        $position = 0;
        $tags = array();

        ob_start();

        while ($printedLength < $maxLength && preg_match('{</?([a-z]+)[^>]*>|&#?[a-zA-Z0-9]+;}', $html, $match, PREG_OFFSET_CAPTURE, $position)){

            list($tag, $tagPosition) = $match[0];

            // Print text leading up to the tag.
            $str = mb_strcut($html, $position, $tagPosition - $position);

            if ($printedLength + mb_strlen($str) > $maxLength){
                print(mb_strcut($str, 0, $maxLength - $printedLength));
                $printedLength = $maxLength;
                break;
            }

            print($str);
            $printedLength += mb_strlen($str);

            if ($tag[0] == '&'){
                // Handle the entity.
                print($tag);
                $printedLength++;
            }
            else{
                // Handle the tag.
                $tagName = $match[1][0];
                if ($tag[1] == '/'){
                    // This is a closing tag.

                    $openingTag = array_pop($tags);
                    assert($openingTag == $tagName); // check that tags are properly nested.

                    print($tag);
                }
                else if ($tag[mb_strlen($tag) - 2] == '/'){
                    // Self-closing tag.
                    print($tag);
                }
                else{
                    // Opening tag.
                    print($tag);
                    $tags[] = $tagName;
                }
            }

            // Continue after the tag.
            $position = $tagPosition + mb_strlen($tag);
        }

        // Print any remaining text.
        if ($printedLength < $maxLength && $position < mb_strlen($html))
            print(mb_strcut($html, $position, $maxLength - $printedLength));

        // Close any open tags.
        while (!empty($tags))
             printf('</%s>', array_pop($tags));


        $bufferOuput = ob_get_contents();

        ob_end_clean();         

        $html = $bufferOuput;   

        return $html;   

    }

Another light changes to Søren Løvborg printTruncated function making it UTF-8 (Needs mbstring) compatible and making it return string not print one. I think it's more useful. And my code not use buffering like Bounce variant, just one more variable.

UDP: bu etiketi utf-8 karakter ile düzgün bir şekilde çalışması için aşağıda listelenen, mb_preg_match işlevi gerekir bağlıyor.

Bu işlev için Soren Løvborg sayesinde büyük, çok iyi.

/* Truncate HTML, close opened tags
*
* @param int, maxlength of the string
* @param string, html       
* @return $html
*/

function htmlTruncate($maxLength, $html)
{
    mb_internal_encoding("UTF-8");
    $printedLength = 0;
    $position = 0;
    $tags = array();
    $out = "";

    while ($printedLength < $maxLength && mb_preg_match('{</?([a-z]+)[^>]*>|&#?[a-zA-Z0-9]+;}', $html, $match, PREG_OFFSET_CAPTURE, $position))
    {
        list($tag, $tagPosition) = $match[0];

        // Print text leading up to the tag.
        $str = mb_substr($html, $position, $tagPosition - $position);
        if ($printedLength + mb_strlen($str) > $maxLength)
        {
            $out .= mb_substr($str, 0, $maxLength - $printedLength);
            $printedLength = $maxLength;
            break;
        }

        $out .= $str;
        $printedLength += mb_strlen($str);

        if ($tag[0] == '&')
        {
            // Handle the entity.
            $out .= $tag;
            $printedLength++;
        }
        else
        {
            // Handle the tag.
            $tagName = $match[1][0];
            if ($tag[1] == '/')
            {
                // This is a closing tag.

                $openingTag = array_pop($tags);
                assert($openingTag == $tagName); // check that tags are properly nested.

                $out .= $tag;
            }
            else if ($tag[mb_strlen($tag) - 2] == '/')
            {
                // Self-closing tag.
                $out .= $tag;
            }
            else
            {
                // Opening tag.
                $out .= $tag;
                $tags[] = $tagName;
            }
        }

        // Continue after the tag.
        $position = $tagPosition + mb_strlen($tag);
    }

    // Print any remaining text.
    if ($printedLength < $maxLength && $position < mb_strlen($html))
        $out .= mb_substr($html, $position, $maxLength - $printedLength);

    // Close any open tags.
    while (!empty($tags))
        $out .= sprintf('</%s>', array_pop($tags));

    return $out;
}

function mb_preg_match(
    $ps_pattern,
    $ps_subject,
    &$pa_matches,
    $pn_flags = 0,
    $pn_offset = 0,
    $ps_encoding = NULL
) {
    // WARNING! - All this function does is to correct offsets, nothing else:
    //(code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER)

    if (is_null($ps_encoding)) $ps_encoding = mb_internal_encoding();

    $pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding));
    $ret = preg_match($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);

    if ($ret && ($pn_flags & PREG_OFFSET_CAPTURE))
        foreach($pa_matches as &$ha_match) {
                $ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
        }

    return $ret;
}

Eğer tidy de kullanabilirsiniz:

function truncate_html($html, $max_length) {   
  return tidy_repair_string(substr($html, 0, $max_length),
     array('wrap' => 0, 'show-body-only' => TRUE), 'utf8'); 
}

CakePHP çerçeve benim için çalışıyor TextHelper bir HTML farkında kesiği () işlevi vardır. Bkz Core-Helpers/Text. MIT lisansı.

% 100 doğru, ama oldukça zor bir yaklaşım:

  1. Iterate charactes using DOM
  2. Kalan öğeleri kaldırmak için DOM yöntemlerini kullanın
  3. DOM dizgeleştirir

Kolay kaba kuvvet yaklaşımı:

  1. Etiketleri PREG_DELIM_CAPTURE ile preg_split('/(<tag>)/') kullanılarak (değil elemanları) ve metin parçaları böler dize.
  2. Eğer (bu bölünmüş her ikinci unsur olacak, sen html_entity_decode() doğru ölçmek için kullanabilirsiniz) istediğiniz metin uzunluğunu ölçün
  3. (Muhtemelen doğranmış işletmenin kurtulmak için sonunda {[) (0]} Döşeme) dize Cut
  4. HTML Tidy ile Fix

Ben görünüşte CakePHP alınan, http://alanwhipple.com/2011/05/25/php-truncate-string-preserving-html-tags-words bulunan güzel bir işlevi kullanılır

Ben yous önermek gibi sadece HTML keser bir fonksiyon yazdım, ama bunun yerine dışarı baskı bir dize değişkeni sadece tüm tutar koyar. hem de HTML varlıkları yönetir.

 /**
     *  function to truncate and then clean up end of the HTML,
     *  truncates by counting characters outside of HTML tags
     *  
     *  @author alex lockwood, alex dot lockwood at websightdesign
     *  
     *  @param string $str the string to truncate
     *  @param int $len the number of characters
     *  @param string $end the end string for truncation
     *  @return string $truncated_html
     *  
     *  **/
        public static function truncateHTML($str, $len, $end = '&hellip;'){
            //find all tags
            $tagPattern = '/(<\/?)([\w]*)(\s*[^>]*)>?|&[\w#]+;/i';  //match html tags and entities
            preg_match_all($tagPattern, $str, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
            //WSDDebug::dump($matches); exit; 
            $i =0;
            //loop through each found tag that is within the $len, add those characters to the len,
            //also track open and closed tags
            // $matches[$i][0] = the whole tag string  --the only applicable field for html enitities  
            // IF its not matching an &htmlentity; the following apply
            // $matches[$i][1] = the start of the tag either '<' or '</'  
            // $matches[$i][2] = the tag name
            // $matches[$i][3] = the end of the tag
            //$matces[$i][$j][0] = the string
            //$matces[$i][$j][1] = the str offest

            while($matches[$i][0][1] < $len && !empty($matches[$i])){

                $len = $len + strlen($matches[$i][0][0]);
                if(substr($matches[$i][0][0],0,1) == '&' )
                    $len = $len-1;


                //if $matches[$i][2] is undefined then its an html entity, want to ignore those for tag counting
                //ignore empty/singleton tags for tag counting
                if(!empty($matches[$i][2][0]) && !in_array($matches[$i][2][0],array('br','img','hr', 'input', 'param', 'link'))){
                    //double check 
                    if(substr($matches[$i][3][0],-1) !='/' && substr($matches[$i][1][0],-1) !='/')
                        $openTags[] = $matches[$i][2][0];
                    elseif(end($openTags) == $matches[$i][2][0]){
                        array_pop($openTags);
                    }else{
                        $warnings[] = "html has some tags mismatched in it:  $str";
                    }
                }


                $i++;

            }

            $closeTags = '';

            if (!empty($openTags)){
                $openTags = array_reverse($openTags);
                foreach ($openTags as $t){
                    $closeTagString .="</".$t . ">"; 
                }
            }

            if(strlen($str)>$len){
                //truncate with new len
                $truncated_html = substr($str, 0, $len);
                //add the end text
                $truncated_html .= $end ;
                //restore any open tags
                $truncated_html .= $closeTagString;


            }else
            $truncated_html = $str;


            return $truncated_html; 
        }

Kırık bir etiket varsa, bir kötü regex ile kesmek bu durumda DomDocument kullanabilirsiniz, olur ki kötü, bir uyarı:

$dom = new DOMDocument();
$dom->loadHTML(substr("Hello, my <strong>name</strong> is <em>Sam</em>. I&acute;m a web developer.",0,26));
$html = preg_replace("/\<\/?(body|html|p)>/", "", $dom->saveHTML());
echo $html;

Çıktı vermelidir: Hello, my <strong>**name**</strong>.

Bounce Søren Løvborg çözümü için multi-byte karakter desteği eklendi - Ben ekledim:

  • Eşleştirilmemiş HTML etiketleri (örneğin, <hr>, <br> <col> vb destek kapalı alamadım - HTML a '/' in sonunda gerekli değildir bu (gerçi XHTML için)),
  • özelleştirilebilir kesme göstergesi (&hellips; ... yani varsayılan),
  • çıkış tampon kullanmadan bir dizge olarak döndürür ve
  • % 100 kapsama ile ünite testleri.

Tüm bu Pastie.

Aşağıdaki başarıyla durumda test kolları basit bir durum-makinesi ayrıştırıcı. Bu tagları takip etmez sanki iç içe etiketleri başarısız. Ben de (bir <a> etiketi bir href-özniteliği örneğin) HTML etiketleri içinde ortaklıklara ilişkin şoklar. Yani bu soruna% 100 çözüm olarak kabul edilemez ama anlamak kolay çünkü daha gelişmiş bir işlev için temel olabilir.

function substr_html($string, $length)
{
    $count = 0;
    /*
     * $state = 0 - normal text
     * $state = 1 - in HTML tag
     * $state = 2 - in HTML entity
     */
    $state = 0;    
    for ($i = 0; $i < strlen($string); $i++) {
        $char = $string[$i];
        if ($char == '<') {
            $state = 1;
        } else if ($char == '&') {
            $state = 2;
            $count++;
        } else if ($char == ';') {
            $state = 0;
        } else if ($char == '>') {
            $state = 0;
        } else if ($state === 0) {
            $count++;
        }

        if ($count === $length) {
            return substr($string, 0, $i + 1);
        }
    }
    return $string;
}

Bu varsa hayal varlık nedeni, bir doğrulayıcı ve ayrıştırıcı kullanmadan yapmak çok zordur

<div id='x'>
    <div id='y'>
        <h1>Heading</h1>
        500 
        lines 
        of 
        html
        ...
        etc
        ...
    </div>
</div>

Bunu nasıl kesecek ve geçerli HTML ile sona planlıyorsunuz?

Kısa bir aramadan sonra, ben yardımcı olabilir this link hangi bulundu.