Sign in to follow this  
zaidgs

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

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


return $result;
}

Share this post


Link to post
Share on other sites
There's an easier way. Just start looping from the second item on (one line change):


function truncate_html($text, $max_length)
{
$parts = explode('<', $text);
$result = array_shift($parts); // This line changed
$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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this