Index: wp-includes/formatting.php
===================================================================
--- wp-includes/formatting.php	(revision 18495)
+++ wp-includes/formatting.php	(working copy)
@@ -981,11 +981,14 @@
  * @since 2.0.4
  *
  * @author Leonard Lin <leonard@acm.org>
+ * @author Kurt Payne <kpayne@godaddy.com>
  * @license GPL
  * @copyright November 4, 2001
  * @version 1.1
  * @todo Make better - change loop condition to $text in 1.2
  * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
+ *		1.2  Updated to fix bug 18312 - improper balancing of nested lists
+ *			 Added the ability to skip cleaning of certain tags      
  *		1.1  Fixed handling of append/stack pop order of end text
  *			 Added Cleaning Hooks
  *		1.0  First Version
@@ -994,103 +997,129 @@
  * @return string Balanced text.
  */
 function force_balance_tags( $text ) {
-	$tagstack = array();
-	$stacksize = 0;
-	$tagqueue = '';
-	$newtext = '';
 	$single_tags = array('br', 'hr', 'img', 'input'); // Known single-entity/self-closing tags
-	$nestable_tags = array('blockquote', 'div', 'span'); // Tags that can be immediately nested within themselves
+	$skip_tags = array('script', 'style'); // No fixing should be done inside these tags
 
 	// WP bug fix for comments - in case you REALLY meant to type '< !--'
 	$text = str_replace('< !--', '<    !--', $text);
 	// WP bug fix for LOVE <3 (and other situations with '<' before a number)
 	$text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);
 
-	while ( preg_match("/<(\/?[\w:]*)\s*([^>]*)>/", $text, $regex) ) {
-		$newtext .= $tagqueue;
+	// Copy the old text to a new field
+	$newtext = $text;
 
-		$i = strpos($text, $regex[0]);
-		$l = strlen($regex[0]);
+	// Keeping track of the tags we've seen
+	$stack = array();
 
-		// clear the shifter
-		$tagqueue = '';
-		// Pop or Push
-		if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
-			$tag = strtolower(substr($regex[1],1));
-			// if too many closing tags
-			if( $stacksize <= 0 ) {
-				$tag = '';
-				// or close to be safe $tag = '/' . $tag;
-			}
-			// if stacktop value = tag close value then pop
-			else if ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag
-				$tag = '</' . $tag . '>'; // Close Tag
-				// Pop
-				array_pop( $tagstack );
-				$stacksize--;
-			} else { // closing tag not at top, search for it
-				for ( $j = $stacksize-1; $j >= 0; $j-- ) {
-					if ( $tagstack[$j] == $tag ) {
-					// add tag to tagqueue
-						for ( $k = $stacksize-1; $k >= $j; $k--) {
-							$tagqueue .= '</' . array_pop( $tagstack ) . '>';
-							$stacksize--;
-						}
-						break;
-					}
-				}
-				$tag = '';
-			}
-		} else { // Begin Tag
-			$tag = strtolower($regex[1]);
+	// Start the position tracking for the new text at 0
+	$newpos = 0;
 
-			// Tag Cleaning
+	// Start off with a skip level of 0.  Skipping tags can be nested and we
+	// have to roll / unroll each one every time we open / close a skip tag
+	$skip_level = 0;
 
-			// If self-closing or '', don't do anything.
-			if ( substr($regex[2],-1) == '/' || $tag == '' ) {
-				// do nothing
-			}
-			// ElseIf it's a known single-entity tag but it doesn't close itself, do so
-			elseif ( in_array($tag, $single_tags) ) {
-				$regex[2] .= '/';
-			} else {	// Push the tag onto the stack
-				// If the top of the stack is the same as the tag we want to push, close previous tag
-				if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) {
-					$tagqueue = '</' . array_pop ($tagstack) . '>';
-					$stacksize--;
-				}
-				$stacksize = array_push ($tagstack, $tag);
-			}
+	// Look for matches
+	while ( preg_match('/<(\/?[\w:]*)\s*([^>]*)>/S', $text, $matches, PREG_OFFSET_CAPTURE, $pos) ) {        
 
-			// Attributes
-			$attributes = $regex[2];
-			if( !empty($attributes) )
-				$attributes = ' '.$attributes;
+		/**
+		 * Matches:
+		 *  [0][0] = whole match (e.g. <div style="align:center;">
+		 *  [0][1] = numeric position in $text (e.g. 0)
+		 *  [1][0] = just the tag (e.g. div or /div)
+		 *  [1][1] = numeric position in $text (e.g. 1)
+		 *  [2][0] = any attributes (e.g. style="align:center;")
+		 *  [2][1] = numeric position in $text (e.g. 5)
+		 */
 
-			$tag = '<' . $tag . $attributes . '>';
-			//If already queuing a close tag, then put this tag on, too
-			if ( !empty($tagqueue) ) {
-				$tagqueue .= $tag;
-				$tag = '';
-			}
+		// Is it a closing tag or not?
+		$closing = ($matches[1][0][0] == '/');
+
+		// Just the tag, no brackets, slashes, or attributes
+		$tag = ( $closing ? strtolower(substr($matches[1][0], 1) ) : strtolower($matches[1][0]));
+
+		// Find the position of this 
+		$newpos = strpos($newtext, $matches[0][0], $newpos);
+        
+		// Skip any empty tags (most likely comments or malformed html tags ...
+		// we could make the doc worse by adding a closing tag to a bad html tag)
+		if ( empty($matches[1][0]) ) {
+			$pos = $matches[0][1] + 1;
 		}
-		$newtext .= substr($text, 0, $i) . $tag;
-		$text = substr($text, $i + $l);
-	}
+
+		// If it's a known single tag and it's a closing tag, discard it
+		elseif ( in_array($tag, $single_tags) && $closing ) {            
+			if ( $skip_level <= 0 )
+				$newtext = substr($newtext, 0, $newpos) . substr($newtext, $newpos + strlen($matches[0][0]));
+
+			// Move to the next match
+			$pos = $matches[0][1] + 1;
+		}
+
+		// If it's a known single tag and it's not properly closed, close it
+		elseif ( in_array($tag, $single_tags) && '/>' != substr($matches[0][0], -2) ) {
+
+			// Add the properly closed tag to the new text
+			if ( $skip_level <= 0 )
+				$newtext = substr($newtext, 0, $newpos) . substr($matches[0][0], 0, -1) . '/>' . substr($newtext, $newpos + strlen($matches[0][0]));
+
+			// Move to the next match
+			$pos = $matches[0][1] + 1;
+		}
+
+		// If it's an opening tag, add it to the stack
+		elseif ( !$closing ) {
+
+			// If it's a self closing tag (regardless of whether we have it in our list) we don't
+			// want it in the stack, as it's legitimate XML, but it will mess up our balancing act
+			if ( '/>' != substr($matches[0][0], -2) && ($skip_level <= 0 || in_array($tag, $skip_tags)) )
+				array_push($stack, $matches);
+
+			// Are we in skip mode?
+			if ( in_array($tag, $skip_tags) )
+				$skip_level++;
+
+			// Move to the next match
+			$pos = $matches[0][1] + 1;
+		}
+        
+        // If we're here, it must be a closing tag
+        else {
 
-	// Clear Tag Queue
-	$newtext .= $tagqueue;
+			// If the stack is empty, the closing tag must be useless, skip it, move to the next match
+			if ( empty($stack) ) {
+				if ( $skip_level <= 0 )
+					$newtext = substr($newtext, 0, $newpos) . substr($newtext, $newpos + strlen($matches[0][0]));
+				$pos = $matches[0][1] + 1;
+			} else {
+				$lasttag = array_pop($stack);
 
-	// Add Remaining text
-	$newtext .= $text;
+				// If the closing tag matches the last tag on the stack, just move to the next match
+				if ( strtolower($lasttag[1][0]) == $tag ) {
+					$pos = $matches[0][1] + 1;
 
-	// Empty Stack
-	while( $x = array_pop($tagstack) )
-		$newtext .= '</' . $x . '>'; // Add remaining tags to close
+				// Can we stop skipping?
+				if ( $skip_level > 0 && in_array($tag, $skip_tags) )
+					$skip_level--;
+
+				// If it doesn't match, add in a closing tag, but don't remove it from the original text.  This will force the tag to run through the
+				// loop again and continue to go up a level in the stack until it finds its match or until the stack is empty and the tag is discarded
+				} else {
+					if ( $skip_level <= 0 )
+						$newtext = substr($newtext, 0, $newpos) . '</' . $lasttag[1][0] . '>' . substr($newtext, $newpos);
+				}
+			}
+		}
+    }
+
+	// If there are still elements on the stack, add the closing tags at the bottom of the text
+	while ( !empty($stack) ) {
+		$tag = array_pop($stack);
+		$newtext .= '</' . $tag[1][0] . '>';
+	}
 
 	// WP fix for the bug with HTML comments
-	$newtext = str_replace("< !--","<!--",$newtext);
-	$newtext = str_replace("<    !--","< !--",$newtext);
+	$newtext = str_replace('< !--', '<!--', $newtext);
+	$newtext = str_replace('<    !--', '< !--', $newtext);
 
 	return $newtext;
 }
