texto - Utilizando PHP substr() y strip_tags() mientras conserva el formateo y sin romper HTML
string strip_tags (10)
Tengo varias cadenas de HTML para cortar a 100 caracteres (del contenido eliminado, no el original) sin pelar etiquetas y sin romper HTML.
Cadena HTML original (288 caracteres):
$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>";
Ajuste estándar: recorte de hasta 100 caracteres y saltos de HTML, el contenido eliminado llega a ~ 40 caracteres:
$content = substr($content, 0, 100)."..."; /* output:
<div>With a <span class=''spanClass''>span over here</span> and a
<div class=''divClass''>nested div ove... */
HTML eliminado: genera el recuento de caracteres correcto, pero obviamente pierde el formato:
$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... */
Solución parcial: al usar HTML Tidy o un purificador para cerrar las etiquetas, se genera un código HTML limpio pero 100 caracteres de HTML no se muestran.
$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>... */
Desafío: para generar n caracteres limpios y n (excluyendo el recuento de caracteres de los elementos 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>...";
Preguntas similares
Aquí está mi intento en el cortador. Quizás ustedes puedan detectar algunos errores. El problema, que encontré con los otros analizadores, es que no cierran las etiquetas correctamente y cortan en medio de una palabra (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;
}
Aquí hay una función que estoy usando en uno de mis proyectos. Está basado en DOMDocument, funciona con HTML5 y es aproximadamente 2 veces más rápido que otras soluciones que he probado (al menos en mi máquina, 0.22 ms vs 0.43 ms usando html_cut($text, $max_length)
desde la respuesta superior en un texto de 500 cadena de caracteres de nodo con un límite de 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;
}
Deberías usar Tidy HTML . Cortas el hilo y luego ejecutas Tidy para cerrar las etiquetas.
Hice otra función para hacerlo, compatible con 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);
}
VER DEMO .
Recomiendo usar html_entity_decode en el inicio de la función, así que conserve los caracteres UTF-8:
$value = html_entity_decode($value);
Independientemente de los 100 problemas de conteo que indique al principio, usted indica en el desafío lo siguiente:
- muestra el recuento de caracteres de strip_tags (el número de caracteres en el texto real del HTML)
- retener el formato HTML cerca
- cualquier etiqueta HTML sin terminar
Aquí está mi propuesta: Bascialmente, analizo cada personaje contando a medida que avanzo. Me aseguro de no contar ningún carácter en ninguna etiqueta HTML. También compruebo al final para asegurarme de que no estoy en medio de una palabra cuando paro. Una vez que me detengo, retrocedo al primer ESPACIO disponible o> como punto de parada.
$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;
}
}
Esto también contará los códigos de retorno, etc. Considerando que tomarán espacio, no los he eliminado.
No es increíble, pero funciona.
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;
}
Ejemplo de uso:
$content = html_cut($content, 100);
No pretendo haber inventado esto, pero hay un método Text::truncate()
muy completo en CakePHP que hace lo que quieres:
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;
}
Use la clase DOMDocument de PHP para normalizar un fragmento de 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)));
Esta pregunta es similar a una pregunta anterior y he copiado y pegado una solución aquí. Si el HTML es enviado por los usuarios, también deberá filtrar posibles vectores de ataque de Javascript, como onmouseover="do_something_evil()"
o <a href="javascript:more_evil();">...</a>
. Herramientas como HTML Purifier fueron diseñadas para atrapar y resolver estos problemas y son mucho más completas que cualquier código que yo pueda publicar.
Use un analizador HTML y pare después de 100 caracteres de texto.
prueba esta función
// 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);
}
y
echo trim_word("<h2 class=''zzzz''>abcasdsdasasdas</h2>","6");