Make WordPress Core


Ignore:
Timestamp:
08/08/2024 07:23:53 AM (2 years 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/src/wp-includes/html-api/class-wp-html-tag-processor.php

    r58866 r58867  
    513513
    514514        /**
     515         * Indicates whether the parser is inside foreign content,
     516         * e.g. inside an SVG or MathML element.
     517         *
     518         * One of 'html', 'svg', or 'math'.
     519         *
     520         * Several parsing rules change based on whether the parser
     521         * is inside foreign content, including whether CDATA sections
     522         * are allowed and whether a self-closing flag indicates that
     523         * an element has no content.
     524         *
     525         * @since 6.7.0
     526         *
     527         * @var string
     528         */
     529        private $parsing_namespace = 'html';
     530
     531        /**
    515532         * What kind of syntax token became an HTML comment.
    516533         *
     
    779796        public function __construct( $html ) {
    780797                $this->html = $html;
     798        }
     799
     800        /**
     801         * Switches parsing mode into a new namespace, such as when
     802         * encountering an SVG tag and entering foreign content.
     803         *
     804         * @since 6.7.0
     805         *
     806         * @param string $new_namespace One of 'html', 'svg', or 'math' indicating into what
     807         *                              namespace the next tokens will be processed.
     808         * @return bool Whether the namespace was valid and changed.
     809         */
     810        public function change_parsing_namespace( string $new_namespace ): bool {
     811                if ( ! in_array( $new_namespace, array( 'html', 'math', 'svg' ), true ) ) {
     812                        return false;
     813                }
     814
     815                $this->parsing_namespace = $new_namespace;
     816                return true;
    781817        }
    782818
     
    844880         *
    845881         * @since 6.5.0
     882         * @since 6.7.0 Recognizes CDATA sections within foreign content.
    846883         *
    847884         * @return bool Whether a token was parsed.
     
    957994                if (
    958995                        $this->is_closing_tag ||
     996                        'html' !== $this->parsing_namespace ||
    959997                        1 !== strspn( $this->html, 'iIlLnNpPsStTxX', $this->tag_name_starts_at, 1 )
    960998                ) {
     
    9971035
    9981036                // Find the closing tag if necessary.
    999                 $found_closer = false;
    10001037                switch ( $tag_name ) {
    10011038                        case 'SCRIPT':
     
    17571794                                        $this->text_length          = $closer_at - $this->text_starts_at;
    17581795                                        $this->bytes_already_parsed = $closer_at + 1;
     1796                                        return true;
     1797                                }
     1798
     1799                                if (
     1800                                        'html' !== $this->parsing_namespace &&
     1801                                        strlen( $html ) > $at + 8 &&
     1802                                        '[' === $html[ $at + 2 ] &&
     1803                                        'C' === $html[ $at + 3 ] &&
     1804                                        'D' === $html[ $at + 4 ] &&
     1805                                        'A' === $html[ $at + 5 ] &&
     1806                                        'T' === $html[ $at + 6 ] &&
     1807                                        'A' === $html[ $at + 7 ] &&
     1808                                        '[' === $html[ $at + 8 ]
     1809                                ) {
     1810                                        $closer_at = strpos( $html, ']]>', $at + 9 );
     1811                                        if ( false === $closer_at ) {
     1812                                                $this->parser_state = self::STATE_INCOMPLETE_INPUT;
     1813
     1814                                                return false;
     1815                                        }
     1816
     1817                                        $this->parser_state         = self::STATE_CDATA_NODE;
     1818                                        $this->text_starts_at       = $at + 9;
     1819                                        $this->text_length          = $closer_at - $this->text_starts_at;
     1820                                        $this->token_length         = $closer_at + 3 - $this->token_starts_at;
     1821                                        $this->bytes_already_parsed = $closer_at + 3;
    17591822                                        return true;
    17601823                                }
     
    26552718
    26562719        /**
     2720         * Returns the namespace of the matched token.
     2721         *
     2722         * @since 6.7.0
     2723         *
     2724         * @return string One of 'html', 'math', or 'svg'.
     2725         */
     2726        public function get_namespace(): string {
     2727                return $this->parsing_namespace;
     2728        }
     2729
     2730        /**
    26572731         * Returns the uppercase name of the matched tag.
    26582732         *
     
    26892763
    26902764                return null;
     2765        }
     2766
     2767        /**
     2768         * Returns the adjusted tag name for a given token, taking into
     2769         * account the current parsing context, whether HTML, SVG, or MathML.
     2770         *
     2771         * @since 6.7.0
     2772         *
     2773         * @return string|null Name of current tag name.
     2774         */
     2775        public function get_qualified_tag_name(): ?string {
     2776                $tag_name = $this->get_tag();
     2777                if ( null === $tag_name ) {
     2778                        return null;
     2779                }
     2780
     2781                if ( 'html' === $this->get_namespace() ) {
     2782                        return $tag_name;
     2783                }
     2784
     2785                $lower_tag_name = strtolower( $tag_name );
     2786                if ( 'math' === $this->get_namespace() ) {
     2787                        return $lower_tag_name;
     2788                }
     2789
     2790                if ( 'svg' === $this->get_namespace() ) {
     2791                        switch ( $lower_tag_name ) {
     2792                                case 'altglyph':
     2793                                        return 'altGlyph';
     2794
     2795                                case 'altglyphdef':
     2796                                        return 'altGlyphDef';
     2797
     2798                                case 'altglyphitem':
     2799                                        return 'altGlyphItem';
     2800
     2801                                case 'animatecolor':
     2802                                        return 'animateColor';
     2803
     2804                                case 'animatemotion':
     2805                                        return 'animateMotion';
     2806
     2807                                case 'animatetransform':
     2808                                        return 'animateTransform';
     2809
     2810                                case 'clippath':
     2811                                        return 'clipPath';
     2812
     2813                                case 'feblend':
     2814                                        return 'feBlend';
     2815
     2816                                case 'fecolormatrix':
     2817                                        return 'feColorMatrix';
     2818
     2819                                case 'fecomponenttransfer':
     2820                                        return 'feComponentTransfer';
     2821
     2822                                case 'fecomposite':
     2823                                        return 'feComposite';
     2824
     2825                                case 'feconvolvematrix':
     2826                                        return 'feConvolveMatrix';
     2827
     2828                                case 'fediffuselighting':
     2829                                        return 'feDiffuseLighting';
     2830
     2831                                case 'fedisplacementmap':
     2832                                        return 'feDisplacementMap';
     2833
     2834                                case 'fedistantlight':
     2835                                        return 'feDistantLight';
     2836
     2837                                case 'fedropshadow':
     2838                                        return 'feDropShadow';
     2839
     2840                                case 'feflood':
     2841                                        return 'feFlood';
     2842
     2843                                case 'fefunca':
     2844                                        return 'feFuncA';
     2845
     2846                                case 'fefuncb':
     2847                                        return 'feFuncB';
     2848
     2849                                case 'fefuncg':
     2850                                        return 'feFuncG';
     2851
     2852                                case 'fefuncr':
     2853                                        return 'feFuncR';
     2854
     2855                                case 'fegaussianblur':
     2856                                        return 'feGaussianBlur';
     2857
     2858                                case 'feimage':
     2859                                        return 'feImage';
     2860
     2861                                case 'femerge':
     2862                                        return 'feMerge';
     2863
     2864                                case 'femergenode':
     2865                                        return 'feMergeNode';
     2866
     2867                                case 'femorphology':
     2868                                        return 'feMorphology';
     2869
     2870                                case 'feoffset':
     2871                                        return 'feOffset';
     2872
     2873                                case 'fepointlight':
     2874                                        return 'fePointLight';
     2875
     2876                                case 'fespecularlighting':
     2877                                        return 'feSpecularLighting';
     2878
     2879                                case 'fespotlight':
     2880                                        return 'feSpotLight';
     2881
     2882                                case 'fetile':
     2883                                        return 'feTile';
     2884
     2885                                case 'feturbulence':
     2886                                        return 'feTurbulence';
     2887
     2888                                case 'foreignobject':
     2889                                        return 'foreignObject';
     2890
     2891                                case 'glyphref':
     2892                                        return 'glyphRef';
     2893
     2894                                case 'lineargradient':
     2895                                        return 'linearGradient';
     2896
     2897                                case 'radialgradient':
     2898                                        return 'radialGradient';
     2899
     2900                                case 'textpath':
     2901                                        return 'textPath';
     2902
     2903                                default:
     2904                                        return $lower_tag_name;
     2905                        }
     2906                }
     2907        }
     2908
     2909        /**
     2910         * Returns the adjusted attribute name for a given attribute, taking into
     2911         * account the current parsing context, whether HTML, SVG, or MathML.
     2912         *
     2913         * @since 6.7.0
     2914         *
     2915         * @param string $attribute_name Which attribute to adjust.
     2916         *
     2917         * @return string|null
     2918         */
     2919        public function get_qualified_attribute_name( $attribute_name ): ?string {
     2920                if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
     2921                        return null;
     2922                }
     2923
     2924                $namespace = $this->get_namespace();
     2925                $lower_name = strtolower( $attribute_name );
     2926
     2927                if ( 'math' === $namespace && 'definitionurl' === $lower_name ) {
     2928                        return 'definitionURL';
     2929                }
     2930
     2931                if ( 'svg' === $this->get_namespace() ) {
     2932                        switch ( $lower_name ) {
     2933                                case 'attributename':
     2934                                        return 'attributeName';
     2935
     2936                                case 'attributetype':
     2937                                        return 'attributeType';
     2938
     2939                                case 'basefrequency':
     2940                                        return 'baseFrequency';
     2941
     2942                                case 'baseprofile':
     2943                                        return 'baseProfile';
     2944
     2945                                case 'calcmode':
     2946                                        return 'calcMode';
     2947
     2948                                case 'clippathunits':
     2949                                        return 'clipPathUnits';
     2950
     2951                                case 'diffuseconstant':
     2952                                        return 'diffuseConstant';
     2953
     2954                                case 'edgemode':
     2955                                        return 'edgeMode';
     2956
     2957                                case 'filterunits':
     2958                                        return 'filterUnits';
     2959
     2960                                case 'glyphref':
     2961                                        return 'glyphRef';
     2962
     2963                                case 'gradienttransform':
     2964                                        return 'gradientTransform';
     2965
     2966                                case 'gradientunits':
     2967                                        return 'gradientUnits';
     2968
     2969                                case 'kernelmatrix':
     2970                                        return 'kernelMatrix';
     2971
     2972                                case 'kernelunitlength':
     2973                                        return 'kernelUnitLength';
     2974
     2975                                case 'keypoints':
     2976                                        return 'keyPoints';
     2977
     2978                                case 'keysplines':
     2979                                        return 'keySplines';
     2980
     2981                                case 'keytimes':
     2982                                        return 'keyTimes';
     2983
     2984                                case 'lengthadjust':
     2985                                        return 'lengthAdjust';
     2986
     2987                                case 'limitingconeangle':
     2988                                        return 'limitingConeAngle';
     2989
     2990                                case 'markerheight':
     2991                                        return 'markerHeight';
     2992
     2993                                case 'markerunits':
     2994                                        return 'markerUnits';
     2995
     2996                                case 'markerwidth':
     2997                                        return 'markerWidth';
     2998
     2999                                case 'maskcontentunits':
     3000                                        return 'maskContentUnits';
     3001
     3002                                case 'maskunits':
     3003                                        return 'maskUnits';
     3004
     3005                                case 'numoctaves':
     3006                                        return 'numOctaves';
     3007
     3008                                case 'pathlength':
     3009                                        return 'pathLength';
     3010
     3011                                case 'patterncontentunits':
     3012                                        return 'patternContentUnits';
     3013
     3014                                case 'patterntransform':
     3015                                        return 'patternTransform';
     3016
     3017                                case 'patternunits':
     3018                                        return 'patternUnits';
     3019
     3020                                case 'pointsatx':
     3021                                        return 'pointsAtX';
     3022
     3023                                case 'pointsaty':
     3024                                        return 'pointsAtY';
     3025
     3026                                case 'pointsatz':
     3027                                        return 'pointsAtZ';
     3028
     3029                                case 'preservealpha':
     3030                                        return 'preserveAlpha';
     3031
     3032                                case 'preserveaspectratio':
     3033                                        return 'preserveAspectRatio';
     3034
     3035                                case 'primitiveunits':
     3036                                        return 'primitiveUnits';
     3037
     3038                                case 'refx':
     3039                                        return 'refX';
     3040
     3041                                case 'refy':
     3042                                        return 'refY';
     3043
     3044                                case 'repeatcount':
     3045                                        return 'repeatCount';
     3046
     3047                                case 'repeatdur':
     3048                                        return 'repeatDur';
     3049
     3050                                case 'requiredextensions':
     3051                                        return 'requiredExtensions';
     3052
     3053                                case 'requiredfeatures':
     3054                                        return 'requiredFeatures';
     3055
     3056                                case 'specularconstant':
     3057                                        return 'specularConstant';
     3058
     3059                                case 'specularexponent':
     3060                                        return 'specularExponent';
     3061
     3062                                case 'spreadmethod':
     3063                                        return 'spreadMethod';
     3064
     3065                                case 'startoffset':
     3066                                        return 'startOffset';
     3067
     3068                                case 'stddeviation':
     3069                                        return 'stdDeviation';
     3070
     3071                                case 'stitchtiles':
     3072                                        return 'stitchTiles';
     3073
     3074                                case 'surfacescale':
     3075                                        return 'surfaceScale';
     3076
     3077                                case 'systemlanguage':
     3078                                        return 'systemLanguage';
     3079
     3080                                case 'tablevalues':
     3081                                        return 'tableValues';
     3082
     3083                                case 'targetx':
     3084                                        return 'targetX';
     3085
     3086                                case 'targety':
     3087                                        return 'targetY';
     3088
     3089                                case 'textlength':
     3090                                        return 'textLength';
     3091
     3092                                case 'viewbox':
     3093                                        return 'viewBox';
     3094
     3095                                case 'viewtarget':
     3096                                        return 'viewTarget';
     3097
     3098                                case 'xchannelselector':
     3099                                        return 'xChannelSelector';
     3100
     3101                                case 'ychannelselector':
     3102                                        return 'yChannelSelector';
     3103
     3104                                case 'zoomandpan':
     3105                                        return 'zoomAndPan';
     3106                        }
     3107                }
     3108
     3109                if ( 'html' !== $namespace ) {
     3110                        switch ( $lower_name ) {
     3111                                case 'xlink:actuate':
     3112                                        return 'xlink actuate';
     3113
     3114                                case 'xlink:arcrole':
     3115                                        return 'xlink arcrole';
     3116
     3117                                case 'xlink:href':
     3118                                        return 'xlink href';
     3119
     3120                                case 'xlink:role':
     3121                                        return 'xlink role';
     3122
     3123                                case 'xlink:show':
     3124                                        return 'xlink show';
     3125
     3126                                case 'xlink:title':
     3127                                        return 'xlink title';
     3128
     3129                                case 'xlink:type':
     3130                                        return 'xlink type';
     3131
     3132                                case 'xml:lang':
     3133                                        return 'xml lang';
     3134
     3135                                case 'xml:space':
     3136                                        return 'xml space';
     3137
     3138                                case 'xmlns':
     3139                                        return 'xmlns';
     3140
     3141                                case 'xmlns:xlink':
     3142                                        return 'xmlns xlink';
     3143                        }
     3144                }
     3145
     3146                return $attribute_name;
    26913147        }
    26923148
     
    29643420                 * for security reasons (to avoid joining together strings that were safe
    29653421                 * when separated, but not when joined).
     3422                 *
     3423                 * @todo Inside HTML integration points and MathML integration points, the
     3424                 *       text is processed according to the insertion mode, not according
     3425                 *       to the foreign content rules. This should strip the NULL bytes.
    29663426                 */
    2967                 return '#text' === $tag_name
     3427                return ( '#text' === $tag_name && 'html' === $this->get_namespace() )
    29683428                        ? str_replace( "\x00", '', $decoded )
    29693429                        : str_replace( "\x00", "\u{FFFD}", $decoded );
Note: See TracChangeset for help on using the changeset viewer.