diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php
index 51a1388dfd..ea091d0b00 100644
--- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -2465,17 +2465,47 @@ function force_balance_tags( $text ) {
// WP bug fix for LOVE <3 (and other situations with '<' before a number)
$text = preg_replace( '#<([0-9]{1})#', '<$1', $text );
- while ( preg_match( '/<(\/?[\w:]*)\s*([^>]*)>/', $text, $regex ) ) {
+ $tag_pattern = (
+ '#<' . // start with an opening bracket
+ '(/?)' . // Group 1 - if it's a closing tag it'll have a leading slash
+ '(' . // Group 2 - tag name
+ // custom element tags have more lenient rules than HTML tag names
+ '(?:[a-z0-9]+(?:-[a-z0-9-]+)*)' .
+ '|' .
+ // traditional tag rules approximate HTML tag names
+ '(?:[\w:]+)' .
+ ')' .
+ '(?:' .
+ // we _either_ immediately close the tag with its '>' and have nothing here
+ '\s*' .
+ '(/?)' . // Group 3 - "attributes" for empty tag
+ '|' .
+ // _or_ we must start with space characters to separate the tag name from the attributes (or whitespace)
+ '(\s+)' . // Group 4 - pre-attribute whitespace
+ '([^>]*)' . // Group 5 - attributes
+ ')' .
+ '>#' // end with a closing bracket
+ );
+
+ while ( preg_match( $tag_pattern, $text, $regex ) ) {
+ $full_match = $regex[0];
+ $has_leading_slash = ! empty( $regex[1] );
+ $tag_name = $regex[2];
+ $tag = strtolower( $tag_name );
+ $is_single_tag = in_array( $tag, $single_tags );
+ $pre_attribute_ws = isset( $regex[4] ) ? $regex[4] : '';
+ $attributes = trim( isset( $regex[5] ) ? $regex[5] : $regex[3] );
+ $has_self_closer = '/' === substr( $attributes, -1 );
+
$newtext .= $tagqueue;
- $i = strpos( $text, $regex[0] );
- $l = strlen( $regex[0] );
+ $i = strpos( $text, $full_match );
+ $l = strlen( $full_match );
// clear the shifter
$tagqueue = '';
// Pop or Push
- if ( isset( $regex[1][0] ) && '/' == $regex[1][0] ) { // End Tag
- $tag = strtolower( substr( $regex[1], 1 ) );
+ if ( $has_leading_slash ) { // End Tag
// if too many closing tags
if ( $stacksize <= 0 ) {
$tag = '';
@@ -2501,21 +2531,16 @@ function force_balance_tags( $text ) {
$tag = '';
}
} else { // Begin Tag
- $tag = strtolower( $regex[1] );
-
// Tag Cleaning
-
- // If it's an empty tag "< >", do nothing
- if ( '' == $tag ) {
- // do nothing
- } elseif ( substr( $regex[2], -1 ) == '/' ) { // ElseIf it presents itself as a self-closing tag...
+ if ( $has_self_closer ) { // If it presents itself as a self-closing tag...
// ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
// immediately close it with a closing tag (the tag will encapsulate no text as a result)
- if ( ! in_array( $tag, $single_tags ) ) {
- $regex[2] = trim( substr( $regex[2], 0, -1 ) ) . ">$tag";
+ if ( ! $is_single_tag ) {
+ $attributes = trim( substr( $attributes, 0, -1 ) ) . ">$tag";
}
- } elseif ( in_array( $tag, $single_tags ) ) { // ElseIf it's a known single-entity tag but it doesn't close itself, do so
- $regex[2] .= '/';
+ } elseif ( $is_single_tag ) { // ElseIf it's a known single-entity tag but it doesn't close itself, do so
+ $pre_attribute_ws = ' ';
+ $attributes .= '/';
} else { // Else it's not a single-entity tag
// 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 ) {
@@ -2526,12 +2551,12 @@ function force_balance_tags( $text ) {
}
// Attributes
- $attributes = $regex[2];
- if ( ! empty( $attributes ) && $attributes[0] != '>' ) {
- $attributes = ' ' . $attributes;
+ if ( $has_self_closer && $is_single_tag ) {
+ // we need some space - avoid
and prefer
+ $pre_attribute_ws = ' ';
}
- $tag = '<' . $tag . $attributes . '>';
+ $tag = '<' . $tag . $pre_attribute_ws . $attributes . '>';
//If already queuing a close tag, then put this tag on, too
if ( ! empty( $tagqueue ) ) {
$tagqueue .= $tag;
diff --git a/tests/phpunit/tests/formatting/balanceTags.php b/tests/phpunit/tests/formatting/balanceTags.php
index 783c320780..aeb02d15ff 100644
--- a/tests/phpunit/tests/formatting/balanceTags.php
+++ b/tests/phpunit/tests/formatting/balanceTags.php
@@ -68,12 +68,15 @@ class Tests_Formatting_BalanceTags extends WP_UnitTestCase {
'',
'