Make WordPress Core

Changeset 58836


Ignore:
Timestamp:
07/31/2024 04:54:23 PM (6 weeks ago)
Author:
dmsnell
Message:

HTML API: Introduce full parsing mode in HTML Processor.

The HTML Processor has only supported a specific kind of parsing mode
called _the fragment parsing mode_, where it behaves in the same way
that node.innerHTML = html does in the DOM. This mode assumes a
context node and doesn't support parsing an entire document.

As part of work to add more spec support to the HTML API, this patch
introduces a full parsing mode, which can parse a full HTML document
from start to end, including the doctype declaration and head tags.

Developed in https://github.com/wordpress/wordpress-develop/pull/6977
Discussed in https://core.trac.wordpress.org/ticket/61576

Props: dmsnell, jonsurrell.
See #61576.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/html-api/class-wp-html-processor-state.php

    r58779 r58836  
    430430
    431431    /**
     432     * The recognized encoding of the input byte stream.
     433     *
     434     * > The stream of code points that comprises the input to the tokenization
     435     * > stage will be initially seen by the user agent as a stream of bytes
     436     * > (typically coming over the network or from the local file system).
     437     * > The bytes encode the actual characters according to a particular character
     438     * > encoding, which the user agent uses to decode the bytes into characters.
     439     *
     440     * @since 6.7.0
     441     *
     442     * @var string|null
     443     */
     444    public $encoding = null;
     445
     446    /**
     447     * The parser's confidence in the input encoding.
     448     *
     449     * > When the HTML parser is decoding an input byte stream, it uses a character
     450     * > encoding and a confidence. The confidence is either tentative, certain, or
     451     * > irrelevant. The encoding used, and whether the confidence in that encoding
     452     * > is tentative or certain, is used during the parsing to determine whether to
     453     * > change the encoding. If no encoding is necessary, e.g. because the parser is
     454     * > operating on a Unicode stream and doesn't have to use a character encoding
     455     * > at all, then the confidence is irrelevant.
     456     *
     457     * @since 6.7.0
     458     *
     459     * @var string
     460     */
     461    public $encoding_confidence = 'tentative';
     462
     463    /**
    432464     * HEAD element pointer.
    433465     *
  • trunk/src/wp-includes/html-api/class-wp-html-processor.php

    r58833 r58836  
    257257    private $context_node = null;
    258258
    259     /**
    260      * Whether the parser has yet processed the context node,
    261      * if created as a fragment parser.
    262      *
    263      * The context node will be initially pushed onto the stack of open elements,
    264      * but when created as a fragment parser, this context element (and the implicit
    265      * HTML document node above it) should not be exposed as a matched token or node.
    266      *
    267      * This boolean indicates whether the processor should skip over the current
    268      * node in its initial search for the first node created from the input HTML.
    269      *
    270      * @var bool
    271      */
    272     private $has_seen_context_node = false;
    273 
    274259    /*
    275260     * Public Interface Functions
     
    313298        }
    314299
    315         $processor                        = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE );
    316         $processor->state->context_node   = array( 'BODY', array() );
    317         $processor->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
     300        $processor                             = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE );
     301        $processor->state->context_node        = array( 'BODY', array() );
     302        $processor->state->insertion_mode      = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
     303        $processor->state->encoding            = $encoding;
     304        $processor->state->encoding_confidence = 'certain';
    318305
    319306        // @todo Create "fake" bookmarks for non-existent but implied nodes.
     
    337324        $processor->context_node = $context_node;
    338325        $processor->breadcrumbs  = array( 'HTML', $context_node->node_name );
     326
     327        return $processor;
     328    }
     329
     330    /**
     331     * Creates an HTML processor in the full parsing mode.
     332     *
     333     * It's likely that a fragment parser is more appropriate, unless sending an
     334     * entire HTML document from start to finish. Consider a fragment parser with
     335     * a context node of `<body>`.
     336     *
     337     * Since UTF-8 is the only currently-accepted charset, if working with a
     338     * document that isn't UTF-8, it's important to convert the document before
     339     * creating the processor: pass in the converted HTML.
     340     *
     341     * @param string      $html                    Input HTML document to process.
     342     * @param string|null $known_definite_encoding Optional. If provided, specifies the charset used
     343     *                                             in the input byte stream. Currently must be UTF-8.
     344     * @return static|null The created processor if successful, otherwise null.
     345     */
     346    public static function create_full_parser( $html, $known_definite_encoding = 'UTF-8' ) {
     347        if ( 'UTF-8' !== $known_definite_encoding ) {
     348            return null;
     349        }
     350
     351        $processor                             = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE );
     352        $processor->state->encoding            = $known_definite_encoding;
     353        $processor->state->encoding_confidence = 'certain';
    339354
    340355        return $processor;
     
    9941009     */
    9951010    private function step_initial(): bool {
    996         $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_INITIAL . ' state.' );
     1011        $token_name = $this->get_token_name();
     1012        $token_type = $this->get_token_type();
     1013        $op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
     1014        $op         = "{$op_sigil}{$token_name}";
     1015
     1016        switch ( $op ) {
     1017            /*
     1018             * > A character token that is one of U+0009 CHARACTER TABULATION,
     1019             * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1020             * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
     1021             *
     1022             * Parse error: ignore the token.
     1023             */
     1024            case '#text':
     1025                $text = $this->get_modifiable_text();
     1026                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1027                    return $this->step();
     1028                }
     1029                goto initial_anything_else;
     1030                break;
     1031
     1032            /*
     1033             * > A comment token
     1034             */
     1035            case '#comment':
     1036            case '#funky-comment':
     1037            case '#presumptuous-tag':
     1038                $this->insert_html_element( $this->state->current_token );
     1039                return true;
     1040
     1041            /*
     1042             * > A DOCTYPE token
     1043             */
     1044            case 'html':
     1045                $contents = $this->get_modifiable_text();
     1046                if ( ' html' !== $contents ) {
     1047                    /*
     1048                     * @todo When the HTML Tag Processor fully parses the DOCTYPE declaration,
     1049                     *       this code should examine the contents to set the compatability mode.
     1050                     */
     1051                    $this->bail( 'Cannot process any DOCTYPE other than a normative HTML5 doctype.' );
     1052                }
     1053
     1054                /*
     1055                 * > Then, switch the insertion mode to "before html".
     1056                 */
     1057                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML;
     1058                return true;
     1059        }
     1060
     1061        /*
     1062         * > Anything else
     1063         */
     1064        initial_anything_else:
     1065        $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML;
     1066        return $this->step( self::REPROCESS_CURRENT_NODE );
    9971067    }
    9981068
     
    10031073     * logic for the generalized WP_HTML_Processor::step() function.
    10041074     *
    1005      * @since 6.7.0 Stub implementation.
     1075     * @since 6.7.0
    10061076     *
    10071077     * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
     
    10131083     */
    10141084    private function step_before_html(): bool {
    1015         $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML . ' state.' );
     1085        $token_name = $this->get_token_name();
     1086        $token_type = $this->get_token_type();
     1087        $is_closer  = parent::is_tag_closer();
     1088        $op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
     1089        $op         = "{$op_sigil}{$token_name}";
     1090
     1091        switch ( $op ) {
     1092            /*
     1093             * > A DOCTYPE token
     1094             */
     1095            case 'html':
     1096                // Parse error: ignore the token.
     1097                return $this->step();
     1098
     1099            /*
     1100             * > A comment token
     1101             */
     1102            case '#comment':
     1103            case '#funky-comment':
     1104            case '#presumptuous-tag':
     1105                $this->insert_html_element( $this->state->current_token );
     1106                return true;
     1107
     1108            /*
     1109             * > A character token that is one of U+0009 CHARACTER TABULATION,
     1110             * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1111             * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
     1112             *
     1113             * Parse error: ignore the token.
     1114             */
     1115            case '#text':
     1116                $text = $this->get_modifiable_text();
     1117                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1118                    return $this->step();
     1119                }
     1120                goto before_html_anything_else;
     1121                break;
     1122
     1123            /*
     1124             * > A start tag whose tag name is "html"
     1125             */
     1126            case '+HTML':
     1127                $this->insert_html_element( $this->state->current_token );
     1128                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
     1129                return true;
     1130
     1131            /*
     1132             * > An end tag whose tag name is one of: "head", "body", "html", "br"
     1133             *
     1134             * Closing BR tags are always reported by the Tag Processor as opening tags.
     1135             */
     1136            case '-HEAD':
     1137            case '-BODY':
     1138            case '-HTML':
     1139                /*
     1140                 * > Act as described in the "anything else" entry below.
     1141                 */
     1142                goto before_html_anything_else;
     1143                break;
     1144        }
     1145
     1146        /*
     1147         * > Any other end tag
     1148         */
     1149        if ( $is_closer ) {
     1150            // Parse error: ignore the token.
     1151            return $this->step();
     1152        }
     1153
     1154        /*
     1155         * > Anything else.
     1156         *
     1157         * > Create an html element whose node document is the Document object.
     1158         * > Append it to the Document object. Put this element in the stack of open elements.
     1159         * > Switch the insertion mode to "before head", then reprocess the token.
     1160         */
     1161        before_html_anything_else:
     1162        $this->insert_virtual_node( 'HTML' );
     1163        $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
     1164        return $this->step( self::REPROCESS_CURRENT_NODE );
    10161165    }
    10171166
     
    10321181     */
    10331182    private function step_before_head(): bool {
    1034         $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD . ' state.' );
     1183        $token_name = $this->get_token_name();
     1184        $token_type = $this->get_token_type();
     1185        $is_closer  = parent::is_tag_closer();
     1186        $op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
     1187        $op         = "{$op_sigil}{$token_name}";
     1188
     1189        switch ( $op ) {
     1190            /*
     1191             * > A character token that is one of U+0009 CHARACTER TABULATION,
     1192             * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1193             * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
     1194             *
     1195             * Parse error: ignore the token.
     1196             */
     1197            case '#text':
     1198                $text = $this->get_modifiable_text();
     1199                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1200                    return $this->step();
     1201                }
     1202                goto before_head_anything_else;
     1203                break;
     1204
     1205            /*
     1206             * > A comment token
     1207             */
     1208            case '#comment':
     1209            case '#funky-comment':
     1210            case '#presumptuous-tag':
     1211                $this->insert_html_element( $this->state->current_token );
     1212                return true;
     1213
     1214            /*
     1215             * > A DOCTYPE token
     1216             */
     1217            case 'html':
     1218                // Parse error: ignore the token.
     1219                return $this->step();
     1220
     1221            /*
     1222             * > A start tag whose tag name is "html"
     1223             */
     1224            case '+HTML':
     1225                return $this->step_in_body();
     1226
     1227            /*
     1228             * > A start tag whose tag name is "head"
     1229             */
     1230            case '+HEAD':
     1231                $this->insert_html_element( $this->state->current_token );
     1232                $this->state->head_element   = $this->state->current_token;
     1233                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
     1234                return true;
     1235
     1236            /*
     1237             * > An end tag whose tag name is one of: "head", "body", "html", "br"
     1238             * > Act as described in the "anything else" entry below.
     1239             *
     1240             * Closing BR tags are always reported by the Tag Processor as opening tags.
     1241             */
     1242            case '-HEAD':
     1243            case '-BODY':
     1244            case '-HTML':
     1245                goto before_head_anything_else;
     1246                break;
     1247        }
     1248
     1249        if ( $is_closer ) {
     1250            // Parse error: ignore the token.
     1251            return $this->step();
     1252        }
     1253
     1254        /*
     1255         * > Anything else
     1256         *
     1257         * > Insert an HTML element for a "head" start tag token with no attributes.
     1258         */
     1259        before_head_anything_else:
     1260        $this->state->head_element   = $this->insert_virtual_node( 'HEAD' );
     1261        $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
     1262        return $this->step( self::REPROCESS_CURRENT_NODE );
    10351263    }
    10361264
     
    10571285        $op         = "{$op_sigil}{$token_name}";
    10581286
    1059         /*
    1060          * > A character token that is one of U+0009 CHARACTER TABULATION,
    1061          * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
    1062          * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
    1063          */
    1064         if ( '#text' === $op ) {
    1065             $text = $this->get_modifiable_text();
    1066             if ( '' === $text ) {
     1287        switch ( $op ) {
     1288            case '#text':
    10671289                /*
    1068                  * If the text is empty after processing HTML entities and stripping
    1069                  * U+0000 NULL bytes then ignore the token.
     1290                 * > A character token that is one of U+0009 CHARACTER TABULATION,
     1291                 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1292                 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
    10701293                 */
    1071                 return $this->step();
    1072             }
    1073 
    1074             if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
    1075                 // Insert the character.
    1076                 $this->insert_html_element( $this->state->current_token );
    1077                 return true;
    1078             }
    1079         }
    1080 
    1081         switch ( $op ) {
     1294                $text = $this->get_modifiable_text();
     1295                if ( '' === $text ) {
     1296                    /*
     1297                     * If the text is empty after processing HTML entities and stripping
     1298                     * U+0000 NULL bytes then ignore the token.
     1299                     */
     1300                    return $this->step();
     1301                }
     1302
     1303                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1304                    // Insert the character.
     1305                    $this->insert_html_element( $this->state->current_token );
     1306                    return true;
     1307                }
     1308
     1309                goto in_head_anything_else;
     1310                break;
     1311
    10821312            /*
    10831313             * > A comment token
     
    11251355                 */
    11261356                $charset = $this->get_attribute( 'charset' );
    1127                 if ( is_string( $charset ) ) {
     1357                if ( is_string( $charset ) && 'tentative' === $this->state->encoding_confidence ) {
    11281358                    $this->bail( 'Cannot yet process META tags with charset to determine encoding.' );
    11291359                }
     
    11421372                    is_string( $http_equiv ) &&
    11431373                    is_string( $content ) &&
    1144                     0 === strcasecmp( $http_equiv, 'Content-Type' )
     1374                    0 === strcasecmp( $http_equiv, 'Content-Type' ) &&
     1375                    'tentative' === $this->state->encoding_confidence
    11451376                ) {
    11461377                    $this->bail( 'Cannot yet process META tags with http-equiv Content-Type to determine encoding.' );
     
    11941425            /*
    11951426             * > An end tag whose tag name is one of: "body", "html", "br"
     1427             *
     1428             * BR tags are always reported by the Tag Processor as opening tags.
    11961429             */
    11971430            case '-BODY':
    11981431            case '-HTML':
    1199             case '-BR':
    12001432                /*
    12011433                 * > Act as described in the "anything else" entry below.
     
    12741506     */
    12751507    private function step_in_head_noscript(): bool {
    1276         $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT . ' state.' );
     1508        $token_name = $this->get_token_name();
     1509        $token_type = $this->get_token_type();
     1510        $is_closer  = parent::is_tag_closer();
     1511        $op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
     1512        $op         = "{$op_sigil}{$token_name}";
     1513
     1514        switch ( $op ) {
     1515            /*
     1516             * > A character token that is one of U+0009 CHARACTER TABULATION,
     1517             * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1518             * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
     1519             *
     1520             * Parse error: ignore the token.
     1521             */
     1522            case '#text':
     1523                $text = $this->get_modifiable_text();
     1524                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1525                    return $this->step_in_head();
     1526                }
     1527
     1528                goto in_head_noscript_anything_else;
     1529                break;
     1530
     1531            /*
     1532             * > A DOCTYPE token
     1533             */
     1534            case 'html':
     1535                // Parse error: ignore the token.
     1536                return $this->step();
     1537
     1538            /*
     1539             * > A start tag whose tag name is "html"
     1540             */
     1541            case '+HTML':
     1542                return $this->step_in_body();
     1543
     1544            /*
     1545             * > An end tag whose tag name is "noscript"
     1546             */
     1547            case '-NOSCRIPT':
     1548                $this->state->stack_of_open_elements->pop();
     1549                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
     1550                return true;
     1551
     1552            /*
     1553             * > A comment token
     1554             * >
     1555             * > A start tag whose tag name is one of: "basefont", "bgsound",
     1556             * > "link", "meta", "noframes", "style"
     1557             */
     1558            case '#comment':
     1559            case '#funky-comment':
     1560            case '#presumptuous-tag':
     1561            case '+BASEFONT':
     1562            case '+BGSOUND':
     1563            case '+LINK':
     1564            case '+META':
     1565            case '+NOFRAMES':
     1566            case '+STYLE':
     1567                return $this->step_in_head();
     1568
     1569            /*
     1570             * > An end tag whose tag name is "br"
     1571             *
     1572             * This should never happen, as the Tag Processor prevents showing a BR closing tag.
     1573             */
     1574        }
     1575
     1576        /*
     1577         * > A start tag whose tag name is one of: "head", "noscript"
     1578         * > Any other end tag
     1579         */
     1580        if ( '+HEAD' === $op || '+NOSCRIPT' === $op || $is_closer ) {
     1581            // Parse error: ignore the token.
     1582            return $this->step();
     1583        }
     1584
     1585        /*
     1586         * > Anything else
     1587         *
     1588         * Anything here is a parse error.
     1589         */
     1590        in_head_noscript_anything_else:
     1591        $this->state->stack_of_open_elements->pop();
     1592        $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
     1593        return $this->step( self::REPROCESS_CURRENT_NODE );
    12771594    }
    12781595
     
    12931610     */
    12941611    private function step_after_head(): bool {
    1295         $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD . ' state.' );
     1612        $token_name = $this->get_token_name();
     1613        $token_type = $this->get_token_type();
     1614        $is_closer  = parent::is_tag_closer();
     1615        $op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
     1616        $op         = "{$op_sigil}{$token_name}";
     1617
     1618        switch ( $op ) {
     1619            /*
     1620             * > A character token that is one of U+0009 CHARACTER TABULATION,
     1621             * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
     1622             * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
     1623             */
     1624            case '#text':
     1625                $text = $this->get_modifiable_text();
     1626                if ( strlen( $text ) === strspn( $text, " \t\n\f\r" ) ) {
     1627                    // Insert the character.
     1628                    $this->insert_html_element( $this->state->current_token );
     1629                    return true;
     1630                }
     1631                goto after_head_anything_else;
     1632                break;
     1633
     1634            /*
     1635             * > A comment token
     1636             */
     1637            case '#comment':
     1638            case '#funky-comment':
     1639            case '#presumptuous-tag':
     1640                $this->insert_html_element( $this->state->current_token );
     1641                return true;
     1642
     1643            /*
     1644             * > A DOCTYPE token
     1645             */
     1646            case 'html':
     1647                // Parse error: ignore the token.
     1648                return $this->step();
     1649
     1650            /*
     1651             * > A start tag whose tag name is "html"
     1652             */
     1653            case '+HTML':
     1654                return $this->step_in_body();
     1655
     1656            /*
     1657             * > A start tag whose tag name is "body"
     1658             */
     1659            case '+BODY':
     1660                $this->insert_html_element( $this->state->current_token );
     1661                $this->state->frameset_ok    = false;
     1662                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
     1663                return true;
     1664
     1665            /*
     1666             * > A start tag whose tag name is "frameset"
     1667             */
     1668            case '+FRAMESET':
     1669                $this->insert_html_element( $this->state->current_token );
     1670                $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET;
     1671                return true;
     1672
     1673            /*
     1674             * > A start tag whose tag name is one of: "base", "basefont", "bgsound",
     1675             * > "link", "meta", "noframes", "script", "style", "template", "title"
     1676             *
     1677             * Anything here is a parse error.
     1678             */
     1679            case '+BASE':
     1680            case '+BASEFONT':
     1681            case '+BGSOUND':
     1682            case '+LINK':
     1683            case '+META':
     1684            case '+NOFRAMES':
     1685            case '+SCRIPT':
     1686            case '+STYLE':
     1687            case '+TEMPLATE':
     1688            case '+TITLE':
     1689                /*
     1690                 * > Push the node pointed to by the head element pointer onto the stack of open elements.
     1691                 * > Process the token using the rules for the "in head" insertion mode.
     1692                 * > Remove the node pointed to by the head element pointer from the stack of open elements. (It might not be the current node at this point.)
     1693                 */
     1694                $this->bail( 'Cannot process elements after HEAD which reopen the HEAD element.' );
     1695                /*
     1696                 * Do not leave this break in when adding support; it's here to prevent
     1697                 * WPCS from getting confused at the switch structure without a return,
     1698                 * because it doesn't know that `bail()` always throws.
     1699                 */
     1700                break;
     1701
     1702            /*
     1703             * > An end tag whose tag name is "template"
     1704             */
     1705            case '-TEMPLATE':
     1706                return $this->step_in_head();
     1707
     1708            /*
     1709             * > An end tag whose tag name is one of: "body", "html", "br"
     1710             *
     1711             * Closing BR tags are always reported by the Tag Processor as opening tags.
     1712             */
     1713            case '-BODY':
     1714            case '-HTML':
     1715                /*
     1716                 * > Act as described in the "anything else" entry below.
     1717                 */
     1718                goto after_head_anything_else;
     1719                break;
     1720        }
     1721
     1722        /*
     1723         * > A start tag whose tag name is "head"
     1724         * > Any other end tag
     1725         */
     1726        if ( '+HEAD' === $op || $is_closer ) {
     1727            // Parse error: ignore the token.
     1728            return $this->step();
     1729        }
     1730
     1731        /*
     1732         * > Anything else
     1733         * > Insert an HTML element for a "body" start tag token with no attributes.
     1734         */
     1735        after_head_anything_else:
     1736        $this->insert_virtual_node( 'BODY' );
     1737        $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
     1738        return $this->step( self::REPROCESS_CURRENT_NODE );
    12961739    }
    12971740
     
    44704913     * @param string|null $bookmark_name Optional. Name to give bookmark for created virtual node.
    44714914     *                                   Defaults to auto-creating a bookmark name.
    4472      */
    4473     private function insert_virtual_node( $token_name, $bookmark_name = null ): void {
     4915     * @return WP_HTML_Token Newly-created virtual token.
     4916     */
     4917    private function insert_virtual_node( $token_name, $bookmark_name = null ): WP_HTML_Token {
    44744918        $here = $this->bookmarks[ $this->state->current_token->bookmark_name ];
    44754919        $name = $bookmark_name ?? $this->bookmark_token();
     
    44774921        $this->bookmarks[ $name ] = new WP_HTML_Span( $here->start, 0 );
    44784922
    4479         $this->insert_html_element( new WP_HTML_Token( $name, $token_name, false ) );
     4923        $token = new WP_HTML_Token( $name, $token_name, false );
     4924        $this->insert_html_element( $token );
     4925        return $token;
    44804926    }
    44814927
     
    46345080    }
    46355081
     5082    /**
     5083     * Gets an encoding from a given string.
     5084     *
     5085     * This is an algorithm defined in the WHAT-WG specification.
     5086     *
     5087     * Example:
     5088     *
     5089     *     'UTF-8' === self::get_encoding( 'utf8' );
     5090     *     'UTF-8' === self::get_encoding( "  \tUTF-8 " );
     5091     *     null    === self::get_encoding( 'UTF-7' );
     5092     *     null    === self::get_encoding( 'utf8; charset=' );
     5093     *
     5094     * @see https://encoding.spec.whatwg.org/#concept-encoding-get
     5095     *
     5096     * @todo As this parser only supports UTF-8, only the UTF-8
     5097     *       encodings are detected. Add more as desired, but the
     5098     *       parser will bail on non-UTF-8 encodings.
     5099     *
     5100     * @since 6.7.0
     5101     *
     5102     * @param string $label A string which may specify a known encoding.
     5103     * @return string|null Known encoding if matched, otherwise null.
     5104     */
     5105    protected static function get_encoding( string $label ): ?string {
     5106        /*
     5107         * > Remove any leading and trailing ASCII whitespace from label.
     5108         */
     5109        $label = trim( $label, " \t\f\r\n" );
     5110
     5111        /*
     5112         * > If label is an ASCII case-insensitive match for any of the labels listed in the
     5113         * > table below, then return the corresponding encoding; otherwise return failure.
     5114         */
     5115        switch ( strtolower( $label ) ) {
     5116            case 'unicode-1-1-utf-8':
     5117            case 'unicode11utf8':
     5118            case 'unicode20utf8':
     5119            case 'utf-8':
     5120            case 'utf8':
     5121            case 'x-unicode20utf8':
     5122                return 'UTF-8';
     5123
     5124            default:
     5125                return null;
     5126        }
     5127    }
     5128
    46365129    /*
    46375130     * Constants that would pollute the top of the class if they were found there.
  • trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php

    r58833 r58836  
    2626        $processor = WP_HTML_Processor::create_fragment( $html );
    2727
    28         $this->assertTrue( $processor->step(), "Failed to step into supported {$tag_name} element." );
     28        $this->assertTrue( $processor->next_token(), "Failed to step into supported {$tag_name} element." );
    2929        $this->assertSame( $tag_name, $processor->get_tag(), "Misread {$tag_name} as a {$processor->get_tag()} element." );
    3030    }
     
    9191            'INS',
    9292            'LI',
     93            'LINK',
    9394            'ISINDEX', // Deprecated.
    9495            'KBD',
     
    109110            'NEXTID', // Deprecated.
    110111            'NOBR', // Neutralized.
     112            'NOEMBED', // Neutralized.
     113            'NOFRAMES', // Neutralized.
    111114            'NOSCRIPT',
    112115            'OBJECT',
     
    123126            'RUBY',
    124127            'SAMP',
     128            'SCRIPT',
    125129            'SEARCH',
    126130            'SECTION',
     
    131135            'STRIKE',
    132136            'STRONG',
     137            'STYLE',
    133138            'SUB',
    134139            'SUMMARY',
    135140            'SUP',
    136141            'TABLE',
     142            'TEXTAREA',
    137143            'TIME',
     144            'TITLE',
    138145            'TT',
    139146            'U',
     
    141148            'VAR',
    142149            'VIDEO',
     150            'XMP', // Deprecated, use PRE instead.
    143151        );
    144152
    145153        $data = array();
    146154        foreach ( $supported_elements as $tag_name ) {
    147             $data[ $tag_name ] = array( "<{$tag_name}>", $tag_name );
     155            $closer = in_array( $tag_name, array( 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true )
     156                ? "</{$tag_name}>"
     157                : '';
     158
     159            $data[ $tag_name ] = array( "<{$tag_name}>{$closer}", $tag_name );
    148160        }
    149161
     
    183195    public static function data_unsupported_elements() {
    184196        $unsupported_elements = array(
    185             'BODY',
    186             'FRAME',
    187             'FRAMESET',
    188             'HEAD',
    189             'HTML',
    190             'IFRAME',
    191197            'MATH',
    192             'NOEMBED', // Neutralized.
    193             'NOFRAMES', // Neutralized.
    194198            'PLAINTEXT', // Neutralized.
    195             'SCRIPT',
    196             'STYLE',
    197199            'SVG',
    198             'TEXTAREA',
    199             'TITLE',
    200             'XMP', // Deprecated, use PRE instead.
    201200        );
    202201
Note: See TracChangeset for help on using the changeset viewer.