Advertisement
Alhadis

PHP Tag-balancing function

May 7th, 2014
398
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 4.13 KB | None | 0 0
  1. /**
  2.  * Balances tags of string using a modified stack.
  3.  *
  4.  * @since 2.0.4
  5.  *
  6.  * @author Leonard Lin <leonard@acm.org>
  7.  * @license GPL
  8.  * @copyright November 4, 2001
  9.  * @version 1.1
  10.  * @todo Make better - change loop condition to $text in 1.2
  11.  * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
  12.  *      1.1  Fixed handling of append/stack pop order of end text
  13.  *           Added Cleaning Hooks
  14.  *      1.0  First Version
  15.  *
  16.  * @param string $text Text to be balanced.
  17.  * @return string Balanced text.
  18.  */
  19. function force_balance_tags( $text ) {
  20.     $tagstack = array();
  21.     $stacksize = 0;
  22.     $tagqueue = '';
  23.     $newtext = '';
  24.     // Known single-entity/self-closing tags
  25.     $single_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source' );
  26.     // Tags that can be immediately nested within themselves
  27.     $nestable_tags = array( 'blockquote', 'div', 'object', 'q', 'span' );
  28.  
  29.     // WP bug fix for comments - in case you REALLY meant to type '< !--'
  30.     $text = str_replace('< !--', '<    !--', $text);
  31.     // WP bug fix for LOVE <3 (and other situations with '<' before a number)
  32.     $text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);
  33.  
  34.     while ( preg_match("/<(\/?[\w:]*)\s*([^>]*)>/", $text, $regex) ) {
  35.         $newtext .= $tagqueue;
  36.  
  37.         $i = strpos($text, $regex[0]);
  38.         $l = strlen($regex[0]);
  39.  
  40.         // clear the shifter
  41.         $tagqueue = '';
  42.         // Pop or Push
  43.         if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
  44.             $tag = strtolower(substr($regex[1],1));
  45.             // if too many closing tags
  46.             if( $stacksize <= 0 ) {
  47.                 $tag = '';
  48.                 // or close to be safe $tag = '/' . $tag;
  49.             }
  50.             // if stacktop value = tag close value then pop
  51.             else if ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag
  52.                 $tag = '</' . $tag . '>'; // Close Tag
  53.                 // Pop
  54.                 array_pop( $tagstack );
  55.                 $stacksize--;
  56.             } else { // closing tag not at top, search for it
  57.                 for ( $j = $stacksize-1; $j >= 0; $j-- ) {
  58.                     if ( $tagstack[$j] == $tag ) {
  59.                     // add tag to tagqueue
  60.                         for ( $k = $stacksize-1; $k >= $j; $k--) {
  61.                             $tagqueue .= '</' . array_pop( $tagstack ) . '>';
  62.                             $stacksize--;
  63.                         }
  64.                         break;
  65.                     }
  66.                 }
  67.                 $tag = '';
  68.             }
  69.         } else { // Begin Tag
  70.             $tag = strtolower($regex[1]);
  71.  
  72.             // Tag Cleaning
  73.  
  74.             // If it's an empty tag "< >", do nothing
  75.             if ( '' == $tag ) {
  76.                 // do nothing
  77.             }
  78.             // ElseIf it presents itself as a self-closing tag...
  79.             elseif ( substr( $regex[2], -1 ) == '/' ) {
  80.                 // ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
  81.                 // immediately close it with a closing tag (the tag will encapsulate no text as a result)
  82.                 if ( ! in_array( $tag, $single_tags ) )
  83.                     $regex[2] = trim( substr( $regex[2], 0, -1 ) ) . "></$tag";
  84.             }
  85.             // ElseIf it's a known single-entity tag but it doesn't close itself, do so
  86.             elseif ( in_array($tag, $single_tags) ) {
  87.                 $regex[2] .= '/';
  88.             }
  89.             // Else it's not a single-entity tag
  90.             else {
  91.                 // If the top of the stack is the same as the tag we want to push, close previous tag
  92.                 if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) {
  93.                     $tagqueue = '</' . array_pop( $tagstack ) . '>';
  94.                     $stacksize--;
  95.                 }
  96.                 $stacksize = array_push( $tagstack, $tag );
  97.             }
  98.  
  99.             // Attributes
  100.             $attributes = $regex[2];
  101.             if( ! empty( $attributes ) && $attributes[0] != '>' )
  102.                 $attributes = ' ' . $attributes;
  103.  
  104.             $tag = '<' . $tag . $attributes . '>';
  105.             //If already queuing a close tag, then put this tag on, too
  106.             if ( !empty($tagqueue) ) {
  107.                 $tagqueue .= $tag;
  108.                 $tag = '';
  109.             }
  110.         }
  111.         $newtext .= substr($text, 0, $i) . $tag;
  112.         $text = substr($text, $i + $l);
  113.     }
  114.  
  115.     // Clear Tag Queue
  116.     $newtext .= $tagqueue;
  117.  
  118.     // Add Remaining text
  119.     $newtext .= $text;
  120.  
  121.     // Empty Stack
  122.     while( $x = array_pop($tagstack) )
  123.         $newtext .= '</' . $x . '>'; // Add remaining tags to close
  124.  
  125.     // WP fix for the bug with HTML comments
  126.     $newtext = str_replace("< !--","<!--",$newtext);
  127.     $newtext = str_replace("<    !--","< !--",$newtext);
  128.  
  129.     return $newtext;
  130. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement