2010-03-08 3 views
35

Ho diverse stringhe HTML da tagliare a 100 caratteri (del contenuto sottoposto a stripping, non dell'originale) senza rimuovere tag e senza infrangere HTML.Uso di substr.s di PHP() e strip_tags() pur mantenendo la formattazione e senza interrompere HTML

stringa HTML originale (288 caratteri):

$content = "<div>With a <span class='spanClass'>span over here</span> and a 
<div class='divClass'>nested div over <div class='nestedDivClass'>there</div> 
</div> and a lot of other nested <strong><em>texts</em> and tags in the air 
<span>everywhere</span>, it's a HTML taggy kind of day.</strong></div>"; 

assetto standard: Trim a 100 caratteri e le interruzioni di HTML, contenuti spogliato tratta di ~ 40 caratteri:

$content = substr($content, 0, 100)."..."; /* output: 
<div>With a <span class='spanClass'>span over here</span> and a 
<div class='divClass'>nested div ove... */ 

Stripped HTML: Le uscite correggono il numero di caratteri ma ovviamente perdono la formattazione:

$content = substr(strip_tags($content)), 0, 100)."..."; /* output: 
With a span over here and a nested div over there and a lot of other nested 
texts and tags in the ai... */ 

soluzione parziale: utilizzando HTML Tidy o depuratore per chiudere le uscite tag HTML pulito, ma 100 caratteri di HTML non vengono visualizzati contenuti.

$content = substr($content, 0, 100)."..."; 
$tidy = new tidy; $tidy->parseString($content); $tidy->cleanRepair(); /* output: 
<div>With a <span class='spanClass'>span over here</span> and a 
<div class='divClass'>nested div ove</div></div>... */ 

Sfide: Per output HTML pulito e n caratteri (escluso il conteggio dei caratteri di elementi HTML):

$content = cutHTML($content, 100); /* output: 
<div>With a <span class='spanClass'>span over here</span> and a 
<div class='divClass'>nested div over <div class='nestedDivClass'>there</div> 
</div> and a lot of other nested <strong><em>texts</em> and tags in the 
ai</strong></div>..."; 

Domande simili

risposta

33

Non eccezionale, ma funziona.

function html_cut($text, $max_length) 
{ 
    $tags = array(); 
    $result = ""; 

    $is_open = false; 
    $grab_open = false; 
    $is_close = false; 
    $in_double_quotes = false; 
    $in_single_quotes = false; 
    $tag = ""; 

    $i = 0; 
    $stripped = 0; 

    $stripped_text = strip_tags($text); 

    while ($i < strlen($text) && $stripped < strlen($stripped_text) && $stripped < $max_length) 
    { 
     $symbol = $text{$i}; 
     $result .= $symbol; 

     switch ($symbol) 
     { 
      case '<': 
       $is_open = true; 
       $grab_open = true; 
       break; 

      case '"': 
       if ($in_double_quotes) 
        $in_double_quotes = false; 
       else 
        $in_double_quotes = true; 

      break; 

      case "'": 
       if ($in_single_quotes) 
        $in_single_quotes = false; 
       else 
        $in_single_quotes = true; 

      break; 

      case '/': 
       if ($is_open && !$in_double_quotes && !$in_single_quotes) 
       { 
        $is_close = true; 
        $is_open = false; 
        $grab_open = false; 
       } 

       break; 

      case ' ': 
       if ($is_open) 
        $grab_open = false; 
       else 
        $stripped++; 

       break; 

      case '>': 
       if ($is_open) 
       { 
        $is_open = false; 
        $grab_open = false; 
        array_push($tags, $tag); 
        $tag = ""; 
       } 
       else if ($is_close) 
       { 
        $is_close = false; 
        array_pop($tags); 
        $tag = ""; 
       } 

       break; 

      default: 
       if ($grab_open || $is_close) 
        $tag .= $symbol; 

       if (!$is_open && !$is_close) 
        $stripped++; 
     } 

     $i++; 
    } 

    while ($tags) 
     $result .= "</".array_pop($tags).">"; 

    return $result; 
} 

Esempio di utilizzo:

$content = html_cut($content, 100); 
+3

Direi incredibile, funziona, esattamente come la sfida delinea ... –

+0

Ho ancora cose come 'inviarci un'email a Read More' tutto il tempo. Immagino che qualcosa non vada nel mio contenuto proveniente dal database ma qualcuno ha un'idea? Grazie! – TomShreds

+1

Che ne dici del supporto di utf-8? –

3

Utilizzare un HTML parser e fermarsi dopo 100 caratteri di testo.

+1

+1 per parser HTML, e pendente verso una soluzione se testo uscita esclude HTML. Un rapido esempio potrebbe essere stato buono. –

16

Non sto sostenendo di aver inventato questo, ma c'è un molto completo Text::truncate() method in CakePHP che fa quello che si vuole:

function truncate($text, $length = 100, $ending = '...', $exact = true, $considerHtml = false) { 
    if (is_array($ending)) { 
     extract($ending); 
    } 
    if ($considerHtml) { 
     if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { 
      return $text; 
     } 
     $totalLength = mb_strlen($ending); 
     $openTags = array(); 
     $truncate = ''; 
     preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); 
     foreach ($tags as $tag) { 
      if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) { 
       if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) { 
        array_unshift($openTags, $tag[2]); 
       } else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) { 
        $pos = array_search($closeTag[1], $openTags); 
        if ($pos !== false) { 
         array_splice($openTags, $pos, 1); 
        } 
       } 
      } 
      $truncate .= $tag[1]; 

      $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); 
      if ($contentLength + $totalLength > $length) { 
       $left = $length - $totalLength; 
       $entitiesLength = 0; 
       if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) { 
        foreach ($entities[0] as $entity) { 
         if ($entity[1] + 1 - $entitiesLength <= $left) { 
          $left--; 
          $entitiesLength += mb_strlen($entity[0]); 
         } else { 
          break; 
         } 
        } 
       } 

       $truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength); 
       break; 
      } else { 
       $truncate .= $tag[3]; 
       $totalLength += $contentLength; 
      } 
      if ($totalLength >= $length) { 
       break; 
      } 
     } 

    } else { 
     if (mb_strlen($text) <= $length) { 
      return $text; 
     } else { 
      $truncate = mb_substr($text, 0, $length - strlen($ending)); 
     } 
    } 
    if (!$exact) { 
     $spacepos = mb_strrpos($truncate, ' '); 
     if (isset($spacepos)) { 
      if ($considerHtml) { 
       $bits = mb_substr($truncate, $spacepos); 
       preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); 
       if (!empty($droppedTags)) { 
        foreach ($droppedTags as $closingTag) { 
         if (!in_array($closingTag[1], $openTags)) { 
          array_unshift($openTags, $closingTag[1]); 
         } 
        } 
       } 
      } 
      $truncate = mb_substr($truncate, 0, $spacepos); 
     } 
    } 

    $truncate .= $ending; 

    if ($considerHtml) { 
     foreach ($openTags as $tag) { 
      $truncate .= '</'.$tag.'>'; 
     } 
    } 

    return $truncate; 
} 
+0

cosa succede se si ha un po 'di offset? diciamo prendi html fino a 100 caratteri e poi la prossima volta prendi da 101 a 200? possibile? –

5

Utilizzare PHP per normalizzare un frammento di HTML:

$dom= new DOMDocument(); 
$dom->loadHTML('<div><p>Hello World');  
$xpath = new DOMXPath($dom); 
$body = $xpath->query('/html/body'); 
echo($dom->saveXml($body->item(0))); 

Questa domanda è simile a un earlier question e ho copiati e incollati un'unica soluzione qui. Se l'HTML viene inviato dagli utenti, dovrai anche filtrare i potenziali vettori di attacco Javascript come onmouseover="do_something_evil()" o <a href="javascript:more_evil();">...</a>. Strumenti come HTML Purifier sono stati progettati per catturare e risolvere questi problemi e sono molto più completi di qualsiasi codice che potrei pubblicare.

+1

+1 per HTML Purifier –

+0

Questa è una soluzione eccellente e concisa. –

1

A prescindere dai problemi 100 conteggio si Stato all'inizio, si indica nella sfida seguente:

  • uscita il conteggio dei caratteri di strip_tags (il numero di caratteri nel testo effettivo visualizzato del HTML)
  • mantengono la formattazione HTML tag di chiusura
  • qualsiasi incompiuto HTML

Ecco la mia proposta: Bascialmente, analizzo ogni personaggio contando mentre vado. Mi assicuro che NON contenga alcun carattere in alcun tag HTML. Controllo anche alla fine per assicurarmi di non essere nel mezzo di una parola quando mi fermo. Una volta che mi fermo, torno indietro al primo SPAZIO disponibile o> come punto di arresto.

$position = 0; 
$length = strlen($content)-1; 

// process the content putting each 100 character section into an array 
while($position < $length) 
{ 
    $next_position = get_position($content, $position, 100); 
    $data[] = substr($content, $position, $next_position); 
    $position = $next_position; 
} 

// show the array 
print_r($data); 

function get_position($content, $position, $chars = 100) 
{ 
    $count = 0; 
    // count to 100 characters skipping over all of the HTML 
    while($count <> $chars){ 
     $char = substr($content, $position, 1); 
     if($char == '<'){ 
      do{ 
       $position++; 
       $char = substr($content, $position, 1); 
      } while($char !== '>'); 
      $position++; 
      $char = substr($content, $position, 1); 
     } 
     $count++; 
     $position++; 
    } 
echo $count."\n"; 
    // find out where there is a logical break before 100 characters 
    $data = substr($content, 0, $position); 

    $space = strrpos($data, " "); 
    $tag = strrpos($data, ">"); 

    // return the position of the logical break 
    if($space > $tag) 
    { 
     return $space; 
    } else { 
     return $tag; 
    } 
} 

Ciò inoltre contare i codici di ritorno ecc Considerando prenderanno spazio, non li ho rimossi.

+0

+1 per lo sforzo e la considerazione per finire l'ultima parola invece di tagliarlo.Immagino che un valore che potresti passare sarebbe tagliato da una stringa alla interruzione logica PRECEDENTE o alla rottura logica NEXT. È probabile che il prossimo passi il limite del personaggio se è importante. Grazie per lo sforzo! –

0

Ecco il mio tentativo al cutter. Forse voi ragazzi potete prendere qualche bug. Il problema, ho trovato con gli altri parser, è che essi non chiudere i tag correttamente e hanno tagliato nel mezzo di una parola (bla)

function cutHTML($string, $length, $patternsReplace = false) { 
    $i = 0; 
    $count = 0; 
    $isParagraphCut = false; 
    $htmlOpen = false; 
    $openTag = false; 
    $tagsStack = array(); 

    while ($i < strlen($string)) { 
     $char = substr($string, $i, 1); 
     if ($count >= $length) { 
      $isParagraphCut = true; 
      break; 
     } 

     if ($htmlOpen) { 
      if ($char === ">") { 
       $htmlOpen = false; 
      } 
     } else { 
      if ($char === "<") { 
       $j = $i; 
       $char = substr($string, $j, 1); 

       while ($j < strlen($string)) { 
        if($char === '/'){ 
         $i++; 
         break; 
        } 
        elseif ($char === ' ') { 
         $tagsStack[] = substr($string, $i, $j); 
        } 
        $j++; 
       } 
       $htmlOpen = true; 
      } 
     } 

     if (!$htmlOpen && $char != ">") { 
      $count++; 
     } 

     $i++; 
    } 

    if ($isParagraphCut) { 
     $j = $i; 
     while ($j > 0) { 
      $char = substr($string, $j, 1); 
      if ($char === " " || $char === ";" || $char === "." || $char === "," || $char === "<" || $char === "(" || $char === "[") { 
       break; 
      } else if ($char === ">") { 
       $j++; 
       break; 
      } 
      $j--; 
     } 
     $string = substr($string, 0, $j); 
     foreach($tagsStack as $tag){ 
      $tag = strtolower($tag); 
      if($tag !== "img" && $tag !== "br"){ 
       $string .= "</$tag>"; 
      } 
     } 
     $string .= "..."; 
    } 

    if ($patternsReplace) { 
     foreach ($patternsReplace as $value) { 
      if (isset($value['pattern']) && isset($value["replace"])) { 
       $string = preg_replace($value["pattern"], $value["replace"], $string); 
      } 
     } 
    } 
    return $string; 
} 
+0

Quasi! Ma continuo a ricevere: 'inviaci un'email allo Read More 'quando sto usando quel codice (come per una buona risposta). Qualche idea? Grazie! – TomShreds

+0

non è sicuro, ma sembra che tu abbia estratto il testo da qualche database e che la citazione sia stata cambiata da "a "e; prova usando htmlspecialchars-decode (http://php.net/manual/en/function.htmlspecialchars-decode.php) – Kubee

1

Ecco una funzione che sto utilizzando in uno dei miei progetti. È basato su DOMDocument, funziona con HTML5 ed è circa 2 volte più veloce di altre soluzioni che ho provato (almeno sulla mia macchina, 0,22 ms vs 0,43 ms utilizzando html_cut($text, $max_length) dalla risposta superiore su una stringa di 500 caratteri di testo con un limite di 400).

function cut_html ($html, $limit) { 
    $dom = new DOMDocument(); 
    $dom->loadHTML(mb_convert_encoding("<div>{$html}</div>", "HTML-ENTITIES", "UTF-8"), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); 
    cut_html_recursive($dom->documentElement, $limit); 
    return substr($dom->saveHTML($dom->documentElement), 5, -6); 
} 

function cut_html_recursive ($element, $limit) { 
    if($limit > 0) { 
     if($element->nodeType == 3) { 
      $limit -= strlen($element->nodeValue); 
      if($limit < 0) { 
       $element->nodeValue = substr($element->nodeValue, 0, strlen($element->nodeValue) + $limit); 
      } 
     } 
     else { 
      for($i = 0; $i < $element->childNodes->length; $i++) { 
       if($limit > 0) { 
        $limit = cut_html_recursive($element->childNodes->item($i), $limit); 
       } 
       else { 
        $element->removeChild($element->childNodes->item($i)); 
        $i--; 
       } 
      } 
     } 
    } 
    return $limit; 
} 
0

provare questa funzione

// trim the string function 
function trim_word($text, $length, $startPoint=0, $allowedTags=""){ 
    $text = html_entity_decode(htmlspecialchars_decode($text)); 
    $text = strip_tags($text, $allowedTags); 
    return $text = substr($text, $startPoint, $length); 
} 

e

echo trim_word("<h2 class='zzzz'>abcasdsdasasdas</h2>","6"); 
2

ho fatto un'altra funzione per farlo, supporta UTF-8:

/** 
* Limit string without break html tags. 
* Supports UTF8 
* 
* @param string $value 
* @param int $limit Default 100 
*/ 
function str_limit_html($value, $limit = 100) 
{ 

    if (mb_strwidth($value, 'UTF-8') <= $limit) { 
     return $value; 
    } 

    // Strip text with HTML tags, sum html len tags too. 
    // Is there another way to do it? 
    do { 
     $len   = mb_strwidth($value, 'UTF-8'); 
     $len_stripped = mb_strwidth(strip_tags($value), 'UTF-8'); 
     $len_tags  = $len - $len_stripped; 

     $value = mb_strimwidth($value, 0, $limit + $len_tags, '', 'UTF-8'); 
    } while ($len_stripped > $limit); 

    // Load as HTML ignoring errors 
    $dom = new DOMDocument(); 
    @$dom->loadHTML('<?xml encoding="utf-8" ?>'.$value, LIBXML_HTML_NODEFDTD); 

    // Fix the html errors 
    $value = $dom->saveHtml($dom->getElementsByTagName('body')->item(0)); 

    // Remove body tag 
    $value = mb_strimwidth($value, 6, mb_strwidth($value, 'UTF-8') - 13, '', 'UTF-8'); // <body> and </body> 
    // Remove empty tags 
    return preg_replace('/<(\w+)\b(?:\s+[\w\-.:]+(?:\s*=\s*(?:"[^"]*"|"[^"]*"|[\w\-.:]+))?)*\s*\/?>\s*<\/\1\s*>/', '', $value); 
} 

SEE DEMO.

Consiglieresti uso html_entity_decode in inizio della funzione, così preservare l'UTF-8:

$value = html_entity_decode($value);