Last updated April 7, 2012. Created by Dublin Drupaller on January 25, 2006.
Edited by shamio, draenen, ronald_istos, mattbrigade. Log in to edit this page.
description
This recyclable snippet allows you to automatically trim or summarise a textfield in your custom-layout.tpl.php files.
This has been tested and works with Drupal 4.6.x and Drupal 4.7.
usage
The code (called the drupalicious_summarise function) sits in your template.php file so you can call it from various tpl.php layout files, such as node.tpl.php, node-image.tpl.php, flexinode.tpl.php etc. using just one simple line of code.
The drupalicious_summarise function will trim the text field to the closest sentence, so it doesn't cut your text off mid-sentence.
Step 1 of 2
In a text editor like notepad.exe, create a file called template.php using the the following snippet. If you already have a template.php file, simply add it to your existing one.
<?php
function drupalicious_summarise($paragraph, $limit) {
$textfield = strtok($paragraph, " ");
while ($textfield) {
$text .= " $textfield";
$words++;
if (($words >= $limit) && ((substr($textfield, -1) == "!") || (substr($textfield, -1) == "."))) {
break;
}
$textfield = strtok(" ");
}
return ltrim($text);
}
?> Step 2 of 2
Insert the following snippet in your custom-layout.tpl.php file.
<?php print drupalicious_summarise($textfieldname,20); ?>$textfieldname = the name of the field you want trimmed.
20 = the number of words, to the nearest sentence, your text will be trimmed to.
notes
- Because the
drupalicious_summarisefunction is sitting in your template.php file, this snippet is recyclable. All you need to do is use the<?php print drupalicious_summarise($textfieldname,20);?>line each time you want to use it.
Comments
Try the Smarty template engine
If you are using the smarty theme engine (http://drupal.org/project/smarty), just try this:
{$textfieldname|truncate:20};)
White Paper Designs
White Paper Designs
Another Solution here
Check this out, here
Geshan
Basic Drupal HOw to
Geshan Manandhar
My Blog
My Drupal site
Slight mod to provide internal "..continued.." notice
I've slightly modified the original function to provide a "..continued.." notice internally as opposed to having to checking in every page as a previous user suggested. Here's the script with the mod included:
<?php
function drupalicious_summarise($paragraph, $limit)
{
$extra = " [..continued..]";
$textfield = strtok($paragraph, " ");
while($textfield)
{
$text .= " $textfield";
$words++;
if(($words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")))
break;
$textfield = strtok(" ");
}
if (str_word_count($text) >= $limit) $text = $text . $extra;
return ltrim($text);
}
?>
MOTE
drupalicious_summarise doesn't trim text with a new paragraph
Hi all,
I relized that the "drupalicious_summarise" function doesn't trim text when a new paragraph begins. I found this caused the teaser text to fun far longer than desirable for my project.
The reason for this is that the function only looks for a period followed by a blank space, not a period followed by a end/new paragraph html tag ("
<p>" or "</p>").I figured I'd post the fix just in case this issue was bugging anyone else (the new code also looks for other types on end punctuation like exclamation and question marks).
Enjoy!
function drupalicious_summarise($paragraph, $limit) {
$textfield = strtok($paragraph, " <");
while($textfield) {
if ((substr($textfield, 1,1) == ">") || (substr($textfield, 2,1) == ">")) {
$text .= "<" . $textfield;
$words++;
if(($words >= $limit) && ((substr($textfield, 0,3) == "<p>") || (substr($textfield, 0,4) == "</p>"))) {
break;
};
$textfield = strtok(" <");
} else {
$text .= " " . $textfield;
$words++;
if (($words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")||(substr($textfield, -1) == ":")||(substr($textfield, -1) == "?"))) {
break;
};
$textfield = strtok(" <");
}
}
//$text .= "<p>" . $words;
return ltrim($text);
}
My mistake
The code I posted above has a pretty big flaw. If there are any other HTML tags before the end paragraph, there's a chance the tag will be broken which can really mess up the order of things.
This is because one of the if/else statements looks to see if the "strtok()" token is an HTML tag by counting the first one or two characters. This works fine as long as the tag in question is only one of two characters in length (i.e.
"<p>", "</p>", "<ul>", "<em>", etc.). If a tag like"<strong>"is met, let me say that the result is far from desirable.I've since fixed this bug, and made sure to thoroughly test it. I should note that I am pretty new to PHP, so I may not of gone about fixing this issue the easiest, most efficient way. If anybody figures out a better solution, please let me know as I'd love to learn from more advanced users.
Here you go!
(Note that this revision involves many more steps than my original solution)
<?php
function drupalicious_summarise($paragraph, $limit) {
// STEP 1: trim the text down to the nearest sentence determined by $limit
// NOTE: This step will only end text if the End Punctuation is met by a blank space, NOT a new or close paragraph tag ("<p>" or "</p>").
$textfield = strtok($paragraph, " ");
while($textfield) {
$text .= " " . $textfield;
$words++;
// Check if the limit has been met. ALSO check if the last character in the token word is an End Punctuation. If not, move on to the next word
if (($words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")||(substr($textfield, -1) == ":")||(substr($textfield, -1) == "?"))) {
break;
} else {
$textfield = strtok(" ");
};
};
// STEP 2: Once text has been trimmed to the word limit (to the nearest sentence), check for any HTML closing tags (">")
$textfield = strtok($text, ">");
while($textfield) {
// Only re-add the closing tag if there is a tag to close (text might of been trimmed in first step where closing tag would of been excluded)
if ((substr($textfield, -1,1) == "!") || (substr($textfield, -1,1) == ".") || (substr($textfield, -1,1) == ":") || (substr($textfield, -1,1) == "?")) {
$revText .= $textfield;
} else {
$revText .= $textfield . ">";
};
$tags++;
// Count the number of words when each closing tag is met to ensure the word count isn't under the limit ($limit)
// Note: Using a different word count method in order not to break the current String position in the "strtok()" method
$wordCount = explode(" ", $revText);
$words = count($wordCount);
// If word count is over, check to see if the tag being closed in a paragraph. If so, return the trim text
if ($words >=$limit) {
if (($tags >= 2) && ((substr($textfield, -2,2) == "<p") || (substr($textfield, -3,3) == "</p") )) {
$wordCount = ""; // Dump the array to save memory
break;
} else {
$textfield = strtok(">");
};
} else {
$textfield = strtok(">");
};
};
return ltrim($revText);
};
?>
Latest update of this script
I was using this and having some problems. Short amounts of text were resulting in an extra greater than sign and strong tags were breaking the results. This is Fat Matt's updated code which works great for me now.
<?php
function drupalicious_summarise($paragraph, $limit) {
// STEP 1: trim the text down to the nearest sentence determined by $limit
// NOTE: This step will only end text if the End Punctuation is met by a blank space, NOT a new or close paragraph tag ("<p>" or "</p>").
$textfield = strtok($paragraph, " ");
while($textfield) {
$text .= " " . $textfield;
$words++;
// Check if the limit has been met. ALSO check if the last character in the token word is an End Punctuation. If not, move on to the next word
if (($words >= $limit) && ((substr($textfield, -1) == "!")||(substr($textfield, -1) == ".")||(substr($textfield, -1) == ":")||(substr($textfield, -1) == "?"))) { break;
} else {
$textfield = strtok(" ");
};
};
// STEP 2: Once text has been trimmed to the word limit (to the nearest sentence), check for any HTML closing tags (">")
$tags = 0;
$textfield = strtok($text, ">");
while($textfield) {
// Only re-add the closing tag if there is a tag to close (text might of been trimmed in first step where closing tag would of been excluded)
if ((substr($textfield, -1,1) == "!") || (substr($textfield, -1,1) == ".") || (substr($textfield, -1,1) == ":") || (substr($textfield, -1,1) == "?")) {
//if ((substr($textfield, -1,1) != "/") || (substr($textfield, -2,1) != "/")) {
$revText .= $textfield;
} else {
$revText .= $textfield . ">"; // . '---' . $tags . '---';
};
$tags++;
// Count the number of words when each closing tag is met to ensure the word count isn't under the limit ($limit)
// Note: Using a different word count method in order not to break the current String position in the "strtok()" method
$wordCount = explode(" ", $revText);
$words = count($wordCount);
// If word count is over, check to see if the tag being closed in a paragraph. If so, return the trim text
if ($words >=$limit) {
if (($tags >= 2) && ((substr($textfield, -2,2) == '<p') || (substr($textfield, -3,3) == '</p') )) {
$wordCount = ""; // Dump the array to save memory
break;
} else {
$textfield = strtok(">");
};
} elseif (substr($textfield, -3,3) == '</p') {
$wordCount = ""; // Dump the array to save memory
break;
} else {
$textfield = strtok(">");
};
};
if (substr($revText,-2,2) != "p>") {
$revText .= "</p>";
return ltrim($revText);
} else {
return ltrim($revText);
};
};
?>
Revised
Hello,
I've gone through my previous code in great detail (I'm fat matt), and did my best to improve on it. I'm pretty sure I eliminated most of the bugs mentioned above though I haven't tested it 100% of scenarios. I can guarantee that it works much better than before.
Feel free to e-mail me if this code is doing anything funky to your site.
/**
* Create Teaser Text
// */
function drupalicious_summarise($paragraph, $limit) {
// Arrays that will be used...
$endpunctuation = array('!', '.', '?');
$blocktags = array("</div>", "</p>");
$inlinetags = array('em', 'strong');
// Clean-up teaser a little to make it easier to process
$paragraph = trim($paragraph);
$paragraph = str_replace(" ", " ", $paragraph);
$paragraph = str_replace("\n", "", $paragraph);
$paragraph = str_replace("\r", "", $paragraph);
$paragraph = str_replace(" ", " ", $paragraph);
foreach ($blocktags as $endtag) {
$starttag = str_replace('/', '', $endtag);
$paragraph = str_replace("$starttag ", $starttag, $paragraph);
$paragraph = str_replace("$endtag ", $endtag, $paragraph);
$paragraph = str_replace(" $starttag", $starttag, $paragraph);
$paragraph = str_replace(" $endtag", $endtag, $paragraph);
}
$paragraph = str_replace("<br /> ", "<br />", $paragraph);
$paragraph = str_replace(" <br />", "<br />", $paragraph);
// Because the "<br />" tag has a space in it, temporarily remove it
$paragraph = str_replace("<br />", " <br>", $paragraph); // Make sure there's a space before "<br>" to register a new word
$textfield = strtok($paragraph, " ");
// Trim the text down to the nearest determined by $limit
while($textfield) {
$text .= " " . $textfield;
$words++;
// Check if the limit has been met
if ($words >= $limit) {
// Check if the last character in the token word is an End Punctuation. If not, move on to the next word
foreach ($endpunctuation as $endmark) {
if (substr($textfield, -1) == $endmark) {
$endteaser = true;
break;
}
}
// Check if the last character in the token word is an end tag. If not, move on to the next word
foreach ($blocktags as $endtag) {
$endtag_textfield = strripos($textfield, $endtag);
if ($endtag_textfield != false) {
$endtagfind = strripos($text, $endtag);
$text = substr($text, 0, $endtagfind);
$text .= $endtag;
$endteaser = true;
break;
}
}
// Check if the last character in the token word is a double breaker ("<br><br>"). If not, move on to the next
$endtag = "<br><br>";
$endtag_textfield = strripos($textfield, $endtag);
if ($endtag_textfield != false) {
$endtagfind = strripos($text, $endtag);
$text = substr($text, 0, $endtagfind);
// Retrun the incorrect "<br>" tags to their proper form
$text = str_replace("<br>", "<br />", $text);
$endteaser = true;
break;
}
if ($endteaser == true) {
//echo $words;
break;
} else {
$textfield = strtok(" ");
}
} else {
$textfield = strtok(" ");
}
}
$revtext = $text;
$revtext = ltrim($revtext);
$revtext = str_replace("<br>", "<br />", $revtext);
// Check for an inline tag left open
foreach ($inlinetags as $tag) {
$tagstart_pos = strripos($revtext, "<$tag>");
$tagend_pos = strripos($revtext, "</$tag>");
if ($tagstart_pos > $tagend_pos) {
$revtext .= "</$tag>";
}
}
// Check for a block-element tag left open
foreach ($blocktags as $endtag) {
$starttag = str_replace('/', '', $endtag);
$brokentag = str_replace('</', '', $endtag);
$startcount = strlen($starttag);
$brokencount = strlen($brokentag);
if (substr($revtext, 0, $startcount) == $starttag && substr($revtext, -($brokentag), $brokentag) != $brokentag) {
$revtext .= $endtag;
return $revtext;
} else {
return $revtext;
}
}
}
I don't mean to sound picky, I'm just looking for a little consistency.
Returns no proper html
Hello,
mattapus thank you for your code. It works better than the previous, but it doesnt close the
<p>properly. It misses the last</p>.This function works great,
This function works great, but I notice it doesn't work for sentences that end with quotes (or single quotes):
"Where are you?"
Roberts said, "I am awesome."
Would this work to account for that:
$endpunctuation = array('!', '.', '?', '!"', '."', '?"');
update content type
Make sure you update the content type (Administer - content type - update) to see the effect of the function on the website.
Regards,
Sumaiya Javed
Web Developer
www.sumaiyajaved.com
www.phpjavascript.com
Alternative Method
Here is an alternative method to trim a text field. This will break on words (not sentences), but it could be expanded to break on sentences. Also, if it trims the text, then it will strip any HTML tags from the text so that no hanging elements get left and break the DOM. It also puts the more... in a span and assigns a class to it so that it can be matched in jquery.
function trim_text($text, $limit, $moreclass) {$extra = "<span class=\"$moreclass\">more...</span>";
preg_match_all('/(\S+\s+)/',strip_tags($text),$matches);
if ($limit < count($matches[0])) {
return rtrim(implode('',array_slice($matches[0],0,$limit))) . $extra;
} else {
return $text;
}
}
If Views is installed...
<?php
$trimmed_text = views_trim_text($alter, $value);
?>
See http://api.lullabot.com/views_trim_text
Joe
hydeinteractive.com
http://sensisagency.com
http://www.hydeinteractive.com/
truncate
Seems like truncate can do the same: http://api.drupal.org/api/drupal/includes--unicode.inc/function/truncate...