Skip to content

Instantly share code, notes, and snippets.

@leon
Created June 2, 2012 11:25
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save leon/2857883 to your computer and use it in GitHub Desktop.
Save leon/2857883 to your computer and use it in GitHub Desktop.
Symfony 2 Twig Extension that truncates html and preserves tags
<?php
/**
Truncate Html string without stripping tags
register in Resources/config/services.yml with:
services:
truncatehtml.twig.extension:
class: Radley\TwigExtensionBundle\Extension\TruncateHtmlExtension
tags:
- { name: twig.extension }
Usage:
{{ htmlstring|truncatehtml(500)|raw }}
*/
namespace Radley\TwigExtensionBundle\Extension;
class TruncateHtmlString {
function __construct($string, $limit) {
// create dom element using the html string
$this->tempDiv = new \DomDocument();
$this->tempDiv->loadXML('<div>'.$string.'</div>');
// keep the characters count till now
$this->charCount = 0;
$this->encoding = 'UTF-8';
// character limit need to check
$this->limit = $limit;
}
function cut() {
// create empty document to store new html
$this->newDiv = new \DomDocument();
// cut the string by parsing through each element
$this->searchEnd($this->tempDiv->documentElement, $this->newDiv);
$newhtml = $this->newDiv->saveHTML();
return $newhtml;
}
function deleteChildren($node) {
while (isset($node->firstChild)) {
$this->deleteChildren($node->firstChild);
$node->removeChild($node->firstChild);
}
}
function searchEnd($parseDiv, $newParent) {
foreach($parseDiv->childNodes as $ele) {
// not text node
if($ele->nodeType != 3) {
$newEle = $this->newDiv->importNode($ele, true);
if(count($ele->childNodes) === 0) {
$newParent->appendChild($newEle);
continue;
}
$this->deleteChildren($newEle);
$newParent->appendChild($newEle);
$res = $this->searchEnd($ele, $newEle);
if($res)
return $res;
else
continue;
}
// the limit of the char count reached
if(mb_strlen($ele->nodeValue, $this->encoding) + $this->charCount >= $this->limit) {
$newEle = $this->newDiv->importNode($ele);
$newEle->nodeValue = substr($newEle->nodeValue, 0, $this->limit - $this->charCount);
$newParent->appendChild($newEle);
return true;
}
$newEle = $this->newDiv->importNode($ele);
$newParent->appendChild($newEle);
$this->charCount += mb_strlen($newEle->nodeValue, $this->encoding);
}
return false;
}
}
class TruncateHtmlExtension extends \Twig_Extension {
public function getName() {
return 'truncatehtml';
}
public function getFilters() {
return array('truncatehtml' => new \Twig_Filter_Method($this, 'truncatehtml'));
}
public function truncatehtml($html, $limit, $endchar = '&hellip;') {
$output = new TruncateHtmlString($html, $limit);
return $output->cut() . $endchar;
}
}
@lughino
Copy link

lughino commented Dec 7, 2013

Really a great job!
There is only one problem, when he meets with no closing tag type
(slash at the end) an exception is thrown:

An exception has been thrown during the rendering of a template ("Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: br line 1 and div in Entity, line: 1 in /var/www/Acme/src/Acme/CoreBundle/Form/Extension/TruncateHtmlString.php line 16")

Is there a way to solve?
Unfortunately, those tags will automatically create a WYSIWYG editor and I cannot change them.

@ale30p
Copy link

ale30p commented Mar 21, 2014

$this->tempDiv->loadHTML('div'.$string.'div');

@ptsiampas
Copy link

Thanks for that Leon, just a slight addition, I had loadXML freak out when there was an invalid char within the HTML.. So I am adding my fix here for anyone else that wants to use it :)

function __construct( $string, $limit ) {
//...
$this->tempDiv->loadXML( '<div>' . $this->utf8_for_xml($string) . '</div>' );
//...
}

function utf8_for_xml($string){
return preg_replace ('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $string);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment