original_string: public function truncatePreservingTags($str, $textMaxLength, $postString = '...', $encoding = 'UTF-8')
      {
          // complete string length with tags
          $htmlLength = mb_strlen($str, $encoding);
          // cursor position
          $htmlPos = 0;
          // extracted text length without tags
          $textLength = 0;
          // tags that need to closed
          $closeTag = array();
  
          if ($htmlLength <= $textMaxLength) {
              return $str;
          }
  
          $inEntity = false;
  
          // loop through str, start at cursor position
          for ($i = $htmlPos; $i < $htmlLength; ++$i) {
              // extract multibyte encoded char on cursor position
              $char = mb_substr($str, $i, 1, $encoding);
  
              if ($char === '&') {
                  if (!$inEntity) {
                      $inEntity = true;
                  }
              } elseif ($char === ';') {
                  if ($inEntity) {
                      $inEntity = false;
                  }
              } // check on an angle bracket assuming text following is a tag
              elseif ($char === '<') {
                  // sub cursor position inside the tag (after the first angle bracket)
                  $tagPos = $i + 1;
                  // remember first position inside the tag
                  $tagFirst = $tagPos;
                  // size of tag (including angle brackets <1234>)
                  $tagSize = 1;
                  // tag name
                  $tagName = '';
  
                  // tag is valid ( <tag> OR </tag> )
                  $isTag = true;
                  // tag is a closing tag </tag>
                  $isClosingTag = false;
  
                  // loop through text inside the tag until it is closed
                  for ($j = $tagPos; $j < $htmlLength; ++$j) {
                      // extract multibyte encoded char on sub cursor position
                      $charTag = mb_substr($str, $j, 1, $encoding);
  
                      // another opening angle bracket = first angle bracket serves as "smaller as"
                      // not a valid tag, set mark and break
                      if ($charTag === '<') {
                          $isTag = false;
                          break;
                      }
                      // seems to be an '<>' entity
                      // not a valid tag, set mark and break
                      elseif ($tagFirst === $j
                          && $charTag === '>'
                      ) {
                          $isTag = false;
                          break;
                      } // closing tag, set mark
                      elseif ($tagFirst === $j
                          && $charTag === '/'
                      ) {
                          $isClosingTag = true;
                      } // tag has ended, break
                      elseif ($charTag === '>') {
                          break;
                      } // remember char as tag name
                      else {
                          $tagName .= $charTag;
                      }
  
                      ++$tagSize;
                  }
  
                  // valid tag
                  if (!empty($isTag)) {
                      // closing tag </tag>
                      if (!empty($isClosingTag)) {
                          // remove it from closing array
                          array_pop($closeTag);
                      } // not a closing tag
                      else {
                          // remember to close it
                          $closeTag[] = $tagName;
                      }
  
                      // set cursor position, continue with next char after the tag
                      $i = $i + $tagSize;
                  } // not a valid tag
                  else {
                      // set assumed tag size as additional textLength, because it is text
                      $textLength = $textLength + $tagSize;
                      // set cursor position and reset the last < or > char to start a new check
                      $i = $i + $tagSize - 1;
                  }
              } // normal char, increase textLength
              else {
                  ++$textLength;
              }
  
              // if maximum length reached break
              if (!$inEntity && $textLength >= $textMaxLength) {
                  break;
              }
          }
  
          // set cursor to new position
          $htmlPos = $i + 1;
  
          // extract preview text from 0 to current cursor position
          $ret = mb_substr($str, 0, $htmlPos, $encoding);
  
          // if necessary set placeholder string (before tags get closed)
          if ($textLength >= $textMaxLength) {
              $ret .= $postString;
          }
  
          // if we are between tags, close them correctly
          if (count($closeTag)) {
              foreach (array_reverse($closeTag) as $tag) {
                  $tokens = explode(' ', $tag);
                  $ret .= '</'.$tokens[0].'>';
              }
          }
  
          return $ret;
      }
code: public function truncatePreservingTags($str, $textMaxLength, $postString = '...', $encoding = 'UTF-8')
      {
          // complete string length with tags
          $htmlLength = mb_strlen($str, $encoding);
          // cursor position
          $htmlPos = 0;
          // extracted text length without tags
          $textLength = 0;
          // tags that need to closed
          $closeTag = array();
  
          if ($htmlLength <= $textMaxLength) {
              return $str;
          }
  
          $inEntity = false;
  
          // loop through str, start at cursor position
          for ($i = $htmlPos; $i < $htmlLength; ++$i) {
              // extract multibyte encoded char on cursor position
              $char = mb_substr($str, $i, 1, $encoding);
  
              if ($char === '&') {
                  if (!$inEntity) {
                      $inEntity = true;
                  }
              } elseif ($char === ';') {
                  if ($inEntity) {
                      $inEntity = false;
                  }
              } // check on an angle bracket assuming text following is a tag
              elseif ($char === '<') {
                  // sub cursor position inside the tag (after the first angle bracket)
                  $tagPos = $i + 1;
                  // remember first position inside the tag
                  $tagFirst = $tagPos;
                  // size of tag (including angle brackets <1234>)
                  $tagSize = 1;
                  // tag name
                  $tagName = '';
  
                  // tag is valid ( <tag> OR </tag> )
                  $isTag = true;
                  // tag is a closing tag </tag>
                  $isClosingTag = false;
  
                  // loop through text inside the tag until it is closed
                  for ($j = $tagPos; $j < $htmlLength; ++$j) {
                      // extract multibyte encoded char on sub cursor position
                      $charTag = mb_substr($str, $j, 1, $encoding);
  
                      // another opening angle bracket = first angle bracket serves as "smaller as"
                      // not a valid tag, set mark and break
                      if ($charTag === '<') {
                          $isTag = false;
                          break;
                      }
                      // seems to be an '<>' entity
                      // not a valid tag, set mark and break
                      elseif ($tagFirst === $j
                          && $charTag === '>'
                      ) {
                          $isTag = false;
                          break;
                      } // closing tag, set mark
                      elseif ($tagFirst === $j
                          && $charTag === '/'
                      ) {
                          $isClosingTag = true;
                      } // tag has ended, break
                      elseif ($charTag === '>') {
                          break;
                      } // remember char as tag name
                      else {
                          $tagName .= $charTag;
                      }
  
                      ++$tagSize;
                  }
  
                  // valid tag
                  if (!empty($isTag)) {
                      // closing tag </tag>
                      if (!empty($isClosingTag)) {
                          // remove it from closing array
                          array_pop($closeTag);
                      } // not a closing tag
                      else {
                          // remember to close it
                          $closeTag[] = $tagName;
                      }
  
                      // set cursor position, continue with next char after the tag
                      $i = $i + $tagSize;
                  } // not a valid tag
                  else {
                      // set assumed tag size as additional textLength, because it is text
                      $textLength = $textLength + $tagSize;
                      // set cursor position and reset the last < or > char to start a new check
                      $i = $i + $tagSize - 1;
                  }
              } // normal char, increase textLength
              else {
                  ++$textLength;
              }
  
              // if maximum length reached break
              if (!$inEntity && $textLength >= $textMaxLength) {
                  break;
              }
          }
  
          // set cursor to new position
          $htmlPos = $i + 1;
  
          // extract preview text from 0 to current cursor position
          $ret = mb_substr($str, 0, $htmlPos, $encoding);
  
          // if necessary set placeholder string (before tags get closed)
          if ($textLength >= $textMaxLength) {
              $ret .= $postString;
          }
  
          // if we are between tags, close them correctly
          if (count($closeTag)) {
              foreach (array_reverse($closeTag) as $tag) {
                  $tokens = explode(' ', $tag);
                  $ret .= '</'.$tokens[0].'>';
              }
          }
  
          return $ret;
      }
code_tokens: public function truncatePreservingTags ( $ str , $ textMaxLength , $ postString = '...' , $ encoding = 'UTF-8' ) { // complete string length with tags $ htmlLength = mb_strlen ( $ str , $ encoding ) ; // cursor position $ htmlPos = 0 ; // extracted text length without tags $ textLength = 0 ; // tags that need to closed $ closeTag = array ( ) ; if ( $ htmlLength <= $ textMaxLength ) { return $ str ; } $ inEntity = false ; // loop through str, start at cursor position for ( $ i = $ htmlPos ; $ i < $ htmlLength ; ++ $ i ) { // extract multibyte encoded char on cursor position $ char = mb_substr ( $ str , $ i , 1 , $ encoding ) ; if ( $ char === '&' ) { if ( ! $ inEntity ) { $ inEntity = true ; } } elseif ( $ char === ';' ) { if ( $ inEntity ) { $ inEntity = false ; } } // check on an angle bracket assuming text following is a tag elseif ( $ char === '<' ) { // sub cursor position inside the tag (after the first angle bracket) $ tagPos = $ i + 1 ; // remember first position inside the tag $ tagFirst = $ tagPos ; // size of tag (including angle brackets <1234>) $ tagSize = 1 ; // tag name $ tagName = '' ; // tag is valid ( <tag> OR </tag> ) $ isTag = true ; // tag is a closing tag </tag> $ isClosingTag = false ; // loop through text inside the tag until it is closed for ( $ j = $ tagPos ; $ j < $ htmlLength ; ++ $ j ) { // extract multibyte encoded char on sub cursor position $ charTag = mb_substr ( $ str , $ j , 1 , $ encoding ) ; // another opening angle bracket = first angle bracket serves as "smaller as" // not a valid tag, set mark and break if ( $ charTag === '<' ) { $ isTag = false ; break ; } // seems to be an '<>' entity // not a valid tag, set mark and break elseif ( $ tagFirst === $ j && $ charTag === '>' ) { $ isTag = false ; break ; } // closing tag, set mark elseif ( $ tagFirst === $ j && $ charTag === '/' ) { $ isClosingTag = true ; } // tag has ended, break elseif ( $ charTag === '>' ) { break ; } // remember char as tag name else { $ tagName .= $ charTag ; } ++ $ tagSize ; } // valid tag if ( ! empty ( $ isTag ) ) { // closing tag </tag> if ( ! empty ( $ isClosingTag ) ) { // remove it from closing array array_pop ( $ closeTag ) ; } // not a closing tag else { // remember to close it $ closeTag [ ] = $ tagName ; } // set cursor position, continue with next char after the tag $ i = $ i + $ tagSize ; } // not a valid tag else { // set assumed tag size as additional textLength, because it is text $ textLength = $ textLength + $ tagSize ; // set cursor position and reset the last < or > char to start a new check $ i = $ i + $ tagSize - 1 ; } } // normal char, increase textLength else { ++ $ textLength ; } // if maximum length reached break if ( ! $ inEntity && $ textLength >= $ textMaxLength ) { break ; } } // set cursor to new position $ htmlPos = $ i + 1 ; // extract preview text from 0 to current cursor position $ ret = mb_substr ( $ str , 0 , $ htmlPos , $ encoding ) ; // if necessary set placeholder string (before tags get closed) if ( $ textLength >= $ textMaxLength ) { $ ret .= $ postString ; } // if we are between tags, close them correctly if ( count ( $ closeTag ) ) { foreach ( array_reverse ( $ closeTag ) as $ tag ) { $ tokens = explode ( ' ' , $ tag ) ; $ ret .= '</' . $ tokens [ 0 ] . '>' ; } } return $ ret ; }
docstring: Function truncates a HTML string, preserving and closing all tags.
  
  @param string $str
  @param int    $textMaxLength
  @param string $postString
  @param string $encoding
  
  @return string
docstring_tokens: Function truncates a HTML string preserving and closing all tags .