# [web] [PHP] Truncating markup text while Reserving html tags

This topic is 3914 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I am not a PHP programmer, and I need help with a code snippet that can truncate some text to a certain number of words, for example: "Hello my name is Zaid, I am a nice guy!", would be truncated to 4 words as: "Hello my name is ..." But here is the problem: Say the string contained: "<i >Hello my <b >name is Zaid</b ></i >", this text truncates to: "<i >Hello my <b >name is ..." What I want to have is something like this: "<i >Hello my <b >name is</b ></i >...", where the tags are automatically enclosed (NOT stripped). I already found a code snippet that can truncate a string to a certain number of words, but it strips the text of HTML tags: <?php function __truncate_filter($item){$numwords = 50; preg_match("/([\S]+\s*){0,$numwords}/",$item -> description, $regs);$item -> description = strip_tags($regs[0]); ///<-- Unwanted behavior$item -> description .= '...'; return $item; } ?> PS: It can be assumed that the string includes only <b > and <i > tags, although a generic function will be more useful... Thanks in advance. #### Share this post ##### Link to post ##### Share on other sites Advertisement That's easy to make. Just truncate the string and then parse the tags. For every unclosed <something> add a </something> at the end. In reverse order of course. For bonus points, calculate the length of the string excluding tags and truncate appropriately. This is off the top of my head and untested. It should work on all tags that do not carry arguments (so <i> works, <a href="#"> not) and it does not fix badly nested tags (<b><i>text</b>text</i> is not corrected) function truncate_html($text, $max_length){$parts = explode('<', $text);$result = '';	$tag_stack = array();$length = 0;	foreach ($parts as$part)	{		$tag = substr($part, 0, strpos($part, '>'));$text_part = substr($part, strpos($part, '>') + 1);		// Open or close a tag				if ($tag{0} == '/') array_pop($tag_stack);		else			$tag_stack[] =$tag;		// Calculate the correct length		if ($length + strlen($text_part) >= $max_length) {$result .= "<$tag>" . substr($text_part, 0, $max_length -$length);			break;		}		else			$result .= '<' .$part;	}		$result .= '&hellip;'; // Close whatever was left open while (sizeof($tag_stack) > 0)	{		$tag = array_pop($tag_stack);		$result .= "</$tag>";	}		return $result;} #### Share this post ##### Link to post ##### Share on other sites Thank you! You saved me a lot of time. I managed to get my website looking fine without having to learn PHP from scratch! I used your code and modified it because it had one side effect: You added '<' at the beginning of every$part, but the first part doesn't have '<' , so I had to check for the first run in the loop, using $fresh... Here is the new modified code: function __truncate_filter($text, $max_length){$parts = explode('<', $item -> description);$result = '';
$tag_stack = array();$fresh = 1;
foreach ($parts as$part)
{
if ($fresh == 0) {$tag = substr($part, 0, strpos($part, '>'));
$text_part = substr($part, strpos($part, '>') + 1); } else {$tag = '';
$text_part =$part;
}

// Open or close a tag
if ($tag{0} == '/') array_pop($tag_stack);
elseif ($fresh == 0)$tag_stack[] = $tag; // Calculate the correct length if (strlen($result . $text_part) >=$max_length)
{
if ($fresh == 0)$result .= "<$tag>";$result .= substr($text_part, 0,$max_length);
$result .= '…'; break; } else { if ($fresh == 1) $result .=$part;
else $result .= '<' .$part;
}

$fresh = 0; } // Close whatever was left open while (sizeof($tag_stack) > 0)
{
$tag = array_pop($tag_stack);
$result .= "</$tag>";
}