使用PHP substr()和strip_tags(),同时保留格式并且不破坏HTML

我有各种不同的HTMLstring可以切割成100个字符(剥离的内容,而不是原始的),没有剥离标签,也不会破坏HTML。

原始HTMLstring (288个字符):

$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>"; 

标准修剪:修剪为100个字符和HTML中断,剥离的内容可达40个字符:

 $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:输出正确的字符数,但显然没有格式化:

 $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... */ 

部分解决scheme:使用HTML Tidy或Purifierclosures标签输出干净的HTML,但100个字符的HTML不显示的内容。

 $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>... */ 

挑战:输出干净的HTML和n个字符(不包括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>..."; 

类似的问题

  • 如何在不破坏标签的情况下剪辑HTML片段
  • 剪切HTMLstring而不破坏HTML标签

不令人惊讶,但工程。

 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; } 

用法示例:

 $content = html_cut($content, 100); 

我并没有声称已经发明了这个,但CakePHP中有一个非常完整的Text::truncate()方法,

 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('/<\/([az]+)>/', $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; } 

使用PHP的DOMDocument类来标准化一个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))); 

这个问题与之前的问题类似,我在这里复制并粘贴了一个解决scheme。 如果HTML是由用户提交的,您还需要过滤出像onmouseover="do_something_evil()"<a href="javascript:more_evil();">...</a>这样的潜在Javascript攻击媒介。 像HTML Purifier这样的工具被devise来捕捉和解决这些问题,并且比我可以发布的任何代码更全面。

使用HTMLparsing器,并在100个字符的文本后停止。

你应该使用整洁的HTML 。 您剪下string,然后运行Tidyclosures标签。

( 学分信用到期 )

我做了另一个function,支持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); } 

看DEMO 。

我build议在函数启动时使用html_entity_decode,所以保留UTF-8字符:

  $value = html_entity_decode($value); 

不pipe你一开始就提到的100个问题,你在挑战中指出:

  • 输出strip_tags的字符数(实际显示的HTML文本中的字符数)
  • 保留HTML格式closures
  • 任何未完成的HTML标签

这是我的build议:基本上,我parsing每个字符,随着我走。 我确定不要计算任何HTML标记中的任何字符。 当我停下来的时候,我还要检查一下,以确保我不在一个字的中间。 一旦我停下来,我回到第一个可用的SPACE或者>停止点。

 $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; } } 

这也将计算返回代码等,考虑到他们将占用空间,我没有删除它们。

这是我在我的一个项目中使用的一个函数。 它基于DOMDocument,与HTML5一起工作,比我试过的其他解决scheme(至less在我的机器上是0.22毫秒vs 0.43毫秒,比使用html_cut($text, $max_length)的顶级答案中的html_cut($text, $max_length)限制为400的节点字符string)。

 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; } 

这是我的尝试在切割机。 也许你们可以抓到一些错误。 这个问题,我发现与其他parsing器,是他们没有正确closures标签,他们在一个字中间切(blah)

 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; } 

试试这个function

 // 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); } 

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