Community Documentation

Trim a text field to a certain word length

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

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

Another Solution here

Check this out, here
Geshan

Basic Drupal HOw to

Geshan Manandhar
My Blog
My Drupal site

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

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

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

truncate

About this page

Drupal version
Drupal 4.5.x or older, Drupal 4.6.x, Drupal 4.7.x, Drupal 5.x

Theming Guide

Drupal’s online documentation is © 2000-2013 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License. Comments on documentation pages are used to improve content and then deleted.
nobody click here