Make WordPress Core


Ignore:
Timestamp:
08/08/2024 07:23:53 AM (15 months ago)
Author:
dmsnell
Message:

HTML API: Add support for SVG and MathML (Foreign content)

As part of work to add more spec support to the HTML API, this patch adds
support for SVG and MathML elements, or more generally, "foreign content."

The rules in foreign content are a mix of XML and HTML parsing rules and
introduce additional complexity into the processor, but is important in
order to avoid getting lost when inside these elements.

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

Props: dmsnell, jonsurrell, westonruter.
See #61576.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/html-api/wpHtmlProcessorHtml5lib.php

    r58859 r58867  
    182182
    183183            if ( $was_text && '#text' !== $token_name ) {
    184                 $output   .= "{$text_node}\"\n";
     184                if ( '' !== $text_node ) {
     185                    $output .= "{$text_node}\"\n";
     186                }
    185187                $was_text  = false;
    186188                $text_node = '';
     
    189191            switch ( $token_type ) {
    190192                case '#tag':
    191                     $tag_name = strtolower( $token_name );
     193                    $namespace = $processor->get_namespace();
     194                    $tag_name  = 'html' === $namespace
     195                        ? strtolower( $processor->get_tag() )
     196                        : "{$namespace} {$processor->get_qualified_tag_name()}";
    192197
    193198                    if ( $is_closer ) {
    194199                        --$indent_level;
    195200
    196                         if ( 'TEMPLATE' === $token_name ) {
     201                        if ( 'html' === $namespace && 'TEMPLATE' === $token_name ) {
    197202                            --$indent_level;
    198203                        }
     
    203208                    $tag_indent = $indent_level;
    204209
    205                     if ( ! WP_HTML_Processor::is_void( $tag_name ) ) {
     210                    if ( 'html' !== $namespace ) {
     211                        if ( ! $processor->has_self_closing_flag() ) {
     212                            ++$indent_level;
     213                        }
     214                    } elseif ( ! WP_HTML_Processor::is_void( $tag_name ) ) {
    206215                        ++$indent_level;
    207216                    }
     
    211220                    $attribute_names = $processor->get_attribute_names_with_prefix( '' );
    212221                    if ( $attribute_names ) {
    213                         sort( $attribute_names, SORT_STRING );
    214 
     222                        $sorted_attributes = array();
    215223                        foreach ( $attribute_names as $attribute_name ) {
     224                            $sorted_attributes[ $attribute_name ] = $processor->get_qualified_attribute_name( $attribute_name );
     225                        }
     226
     227                        /*
     228                         * Sorts attributes to match html5lib sort order.
     229                         *
     230                         *  - First comes normal HTML attributes.
     231                         *  - Then come adjusted foreign attributes; these have spaces in their names.
     232                         *  - Finally come non-adjusted foreign attributes; these have a colon in their names.
     233                         *
     234                         * Example:
     235                         *
     236                         *       From: <math xlink:author definitionurl xlink:title xlink:show>
     237                         *     Sorted: 'definitionURL', 'xlink show', 'xlink title', 'xlink:author'
     238                         */
     239                        uasort(
     240                            $sorted_attributes,
     241                            static function ( $a, $b ) {
     242                                $a_has_ns = str_contains( $a, ':' );
     243                                $b_has_ns = str_contains( $b, ':' );
     244
     245                                // Attributes with `:` should follow all other attributes.
     246                                if ( $a_has_ns !== $b_has_ns ) {
     247                                    return $a_has_ns ? 1 : -1;
     248                                }
     249
     250                                $a_has_sp = str_contains( $a, ' ' );
     251                                $b_has_sp = str_contains( $b, ' ' );
     252
     253                                // Attributes with a namespace ' ' should come after those without.
     254                                if ( $a_has_sp !== $b_has_sp ) {
     255                                    return $a_has_sp ? 1 : -1;
     256                                }
     257
     258                                return $a <=> $b;
     259                            }
     260                        );
     261
     262                        foreach ( $sorted_attributes as $attribute_name => $display_name ) {
    216263                            $val = $processor->get_attribute( $attribute_name );
    217264                            /*
     
    222269                                $val = '';
    223270                            }
    224                             $output .= str_repeat( $indent, $tag_indent + 1 ) . "{$attribute_name}=\"{$val}\"\n";
     271                            $output .= str_repeat( $indent, $tag_indent + 1 ) . "{$display_name}=\"{$val}\"\n";
    225272                        }
    226273                    }
     
    232279                    }
    233280
    234                     if ( 'TEMPLATE' === $token_name ) {
     281                    if ( 'html' === $namespace && 'TEMPLATE' === $token_name ) {
    235282                        $output .= str_repeat( $indent, $indent_level ) . "content\n";
    236283                        ++$indent_level;
     
    243290                    break;
    244291
     292                case '#cdata-section':
    245293                case '#text':
     294                    $text_content = $processor->get_modifiable_text();
     295                    if ( '' === $text_content ) {
     296                        break;
     297                    }
    246298                    $was_text = true;
    247299                    if ( '' === $text_node ) {
    248300                        $text_node .= str_repeat( $indent, $indent_level ) . '"';
    249301                    }
    250                     $text_node .= $processor->get_modifiable_text();
     302                    $text_node .= $text_content;
    251303                    break;
    252304
Note: See TracChangeset for help on using the changeset viewer.