1015 | | // clear the shifter |
1016 | | $tagqueue = ''; |
1017 | | // Pop or Push |
1018 | | if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag |
1019 | | $tag = strtolower(substr($regex[1],1)); |
1020 | | // if too many closing tags |
1021 | | if( $stacksize <= 0 ) { |
1022 | | $tag = ''; |
1023 | | // or close to be safe $tag = '/' . $tag; |
1024 | | } |
1025 | | // if stacktop value = tag close value then pop |
1026 | | else if ( $tagstack[$stacksize - 1] == $tag ) { // found closing tag |
1027 | | $tag = '</' . $tag . '>'; // Close Tag |
1028 | | // Pop |
1029 | | array_pop( $tagstack ); |
1030 | | $stacksize--; |
1031 | | } else { // closing tag not at top, search for it |
1032 | | for ( $j = $stacksize-1; $j >= 0; $j-- ) { |
1033 | | if ( $tagstack[$j] == $tag ) { |
1034 | | // add tag to tagqueue |
1035 | | for ( $k = $stacksize-1; $k >= $j; $k--) { |
1036 | | $tagqueue .= '</' . array_pop( $tagstack ) . '>'; |
1037 | | $stacksize--; |
1038 | | } |
1039 | | break; |
1040 | | } |
1041 | | } |
1042 | | $tag = ''; |
1043 | | } |
1044 | | } else { // Begin Tag |
1045 | | $tag = strtolower($regex[1]); |
| 1014 | // Start the position tracking for the new text at 0 |
| 1015 | $newpos = 0; |
1049 | | // If self-closing or '', don't do anything. |
1050 | | if ( substr($regex[2],-1) == '/' || $tag == '' ) { |
1051 | | // do nothing |
1052 | | } |
1053 | | // ElseIf it's a known single-entity tag but it doesn't close itself, do so |
1054 | | elseif ( in_array($tag, $single_tags) ) { |
1055 | | $regex[2] .= '/'; |
1056 | | } else { // Push the tag onto the stack |
1057 | | // If the top of the stack is the same as the tag we want to push, close previous tag |
1058 | | if ( $stacksize > 0 && !in_array($tag, $nestable_tags) && $tagstack[$stacksize - 1] == $tag ) { |
1059 | | $tagqueue = '</' . array_pop ($tagstack) . '>'; |
1060 | | $stacksize--; |
1061 | | } |
1062 | | $stacksize = array_push ($tagstack, $tag); |
1063 | | } |
| 1021 | // Look for matches |
| 1022 | while ( preg_match('/<(\/?[\w:]*)\s*([^>]*)>/S', $text, $matches, PREG_OFFSET_CAPTURE, $pos) ) { |
1070 | | $tag = '<' . $tag . $attributes . '>'; |
1071 | | //If already queuing a close tag, then put this tag on, too |
1072 | | if ( !empty($tagqueue) ) { |
1073 | | $tagqueue .= $tag; |
1074 | | $tag = ''; |
1075 | | } |
| 1034 | // Is it a closing tag or not? |
| 1035 | $closing = ($matches[1][0][0] == '/'); |
| 1036 | |
| 1037 | // Just the tag, no brackets, slashes, or attributes |
| 1038 | $tag = ( $closing ? strtolower(substr($matches[1][0], 1) ) : strtolower($matches[1][0])); |
| 1039 | |
| 1040 | // Find the position of this |
| 1041 | $newpos = strpos($newtext, $matches[0][0], $newpos); |
| 1042 | |
| 1043 | // Skip any empty tags (most likely comments or malformed html tags ... |
| 1044 | // we could make the doc worse by adding a closing tag to a bad html tag) |
| 1045 | if ( empty($matches[1][0]) ) { |
| 1046 | $pos = $matches[0][1] + 1; |
1077 | | $newtext .= substr($text, 0, $i) . $tag; |
1078 | | $text = substr($text, $i + $l); |
1079 | | } |
| 1048 | |
| 1049 | // If it's a known single tag and it's a closing tag, discard it |
| 1050 | elseif ( in_array($tag, $single_tags) && $closing ) { |
| 1051 | if ( $skip_level <= 0 ) |
| 1052 | $newtext = substr($newtext, 0, $newpos) . substr($newtext, $newpos + strlen($matches[0][0])); |
| 1053 | |
| 1054 | // Move to the next match |
| 1055 | $pos = $matches[0][1] + 1; |
| 1056 | } |
| 1057 | |
| 1058 | // If it's a known single tag and it's not properly closed, close it |
| 1059 | elseif ( in_array($tag, $single_tags) && '/>' != substr($matches[0][0], -2) ) { |
| 1060 | |
| 1061 | // Add the properly closed tag to the new text |
| 1062 | if ( $skip_level <= 0 ) |
| 1063 | $newtext = substr($newtext, 0, $newpos) . substr($matches[0][0], 0, -1) . '/>' . substr($newtext, $newpos + strlen($matches[0][0])); |
| 1064 | |
| 1065 | // Move to the next match |
| 1066 | $pos = $matches[0][1] + 1; |
| 1067 | } |
| 1068 | |
| 1069 | // If it's an opening tag, add it to the stack |
| 1070 | elseif ( !$closing ) { |
| 1071 | |
| 1072 | // If it's a self closing tag (regardless of whether we have it in our list) we don't |
| 1073 | // want it in the stack, as it's legitimate XML, but it will mess up our balancing act |
| 1074 | if ( '/>' != substr($matches[0][0], -2) && ($skip_level <= 0 || in_array($tag, $skip_tags)) ) |
| 1075 | array_push($stack, $matches); |
| 1076 | |
| 1077 | // Are we in skip mode? |
| 1078 | if ( in_array($tag, $skip_tags) ) |
| 1079 | $skip_level++; |
| 1080 | |
| 1081 | // Move to the next match |
| 1082 | $pos = $matches[0][1] + 1; |
| 1083 | } |
| 1084 | |
| 1085 | // If we're here, it must be a closing tag |
| 1086 | else { |
1087 | | // Empty Stack |
1088 | | while( $x = array_pop($tagstack) ) |
1089 | | $newtext .= '</' . $x . '>'; // Add remaining tags to close |
| 1100 | // Can we stop skipping? |
| 1101 | if ( $skip_level > 0 && in_array($tag, $skip_tags) ) |
| 1102 | $skip_level--; |
| 1103 | |
| 1104 | // 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 |
| 1105 | // 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 |
| 1106 | } else { |
| 1107 | if ( $skip_level <= 0 ) |
| 1108 | $newtext = substr($newtext, 0, $newpos) . '</' . $lasttag[1][0] . '>' . substr($newtext, $newpos); |
| 1109 | } |
| 1110 | } |
| 1111 | } |
| 1112 | } |
| 1113 | |
| 1114 | // If there are still elements on the stack, add the closing tags at the bottom of the text |
| 1115 | while ( !empty($stack) ) { |
| 1116 | $tag = array_pop($stack); |
| 1117 | $newtext .= '</' . $tag[1][0] . '>'; |
| 1118 | } |