Trim a text field to a certain word length

Last modified: August 18, 2009 - 18:59

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 sentance, your text will be trimmed to.

notes

  • Because the drupalicious_summarise function 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.

Try the Smarty template engine

wpd - May 27, 2006 - 01:42

If you are using the smarty theme engine (http://drupal.org/project/smarty), just try this:

{$textfieldname|truncate:20}

;)
White Paper Designs

Another Solution here

geshan - June 27, 2008 - 06:24

Check this out, here
Geshan

Basic Drupal HOw to

Slight mod to provide internal "..continued.." notice

MacOfTheEast - March 12, 2007 - 12:05

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

Fat Matt - March 26, 2007 - 17:14

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

Fat Matt - March 28, 2007 - 14:05

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

inversed - March 31, 2008 - 17:44

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

mattapus - February 11, 2009 - 20:43

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("&nbsp;", " ", $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;
    }
  }
}

Returns no proper html

calculus - June 21, 2009 - 15:38

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>.

update content type

sumaiyajaved - July 30, 2009 - 06:24

Make sure you update the content type (Administer - content type - update) to see the effect of the function on the website.

 
 

Drupal is a registered trademark of Dries Buytaert.