@@ -2903,10 +2903,39 @@ public function get_tag(): ?string {
29032903 }
29042904
29052905 /**
2906- * Returns the adjusted tag name for a given token, taking into
2907- * account the current parsing context, whether HTML, SVG, or MathML.
2906+ * Returns the normative adjusted tag name for an element in its given
2907+ * namespace, usually for display or XML output only.
2908+ *
2909+ * For checking if a tag has a given name, rely on {@see static::get_tag()}
2910+ * which takes HTML case-folding rules into account and always returns an
2911+ * ASCII upper-case variant of the matched tag name.
2912+ *
2913+ * - Some SVG tags are mixed-case, such as `altGlyph`.
2914+ * - HTML custom elements’ casing is preserved.
2915+ * - All other tag names will be lower-cased.
2916+ *
2917+ * Example:
2918+ *
2919+ * $processor = new WP_HTML_Tag_Processor( '<div><Task-Item>' );
2920+ * $processor->next_tag();
2921+ * 'DIV' === $processor->get_tag();
2922+ * 'div' === $processor->get_qualified_tag_name();
2923+ *
2924+ * $processor->next_tag();
2925+ * 'TASK-ITEM' === $processor->get_tag();
2926+ * 'Task-Item' === $processor->get_qualified_tag_name();
2927+ *
2928+ * $processor = WP_HTML_Processor::create_fragment( '<svg><altglyph/><rect/></svg>' );
2929+ * $processor->next_tag( 'altglyph' );
2930+ * 'ALTGLYPH' === $processor->get_tag();
2931+ * 'altGlyph' === $processor->get_qualified_tag_name();
2932+ *
2933+ * $processor->next_tag();
2934+ * 'RECT' === $processor->get_tag();
2935+ * 'rect' === $processor->get_qualified_tag_name();
29082936 *
29092937 * @since 6.7.0
2938+ * @since 6.9.0 Respects mixed-case SVG and case-sensitive custome element tag names.
29102939 *
29112940 * @return string|null Name of current tag name.
29122941 */
@@ -2916,140 +2945,160 @@ public function get_qualified_tag_name(): ?string {
29162945 return null ;
29172946 }
29182947
2919- if ( 'html ' === $ this ->get_namespace () ) {
2920- return $ tag_name ;
2948+ $ lower_tag_name = strtolower ( $ tag_name );
2949+ $ namespace = $ this ->get_namespace ();
2950+
2951+ switch ( $ namespace ) {
2952+ case 'html ' :
2953+ // Preserve casing of custom elements.
2954+ return str_contains ( $ tag_name , '- ' )
2955+ ? substr ( $ this ->html , $ this ->tag_name_starts_at , $ this ->tag_name_length )
2956+ : $ lower_tag_name ;
2957+
2958+ case 'math ' :
2959+ return $ lower_tag_name ;
29212960 }
29222961
2923- $ lower_tag_name = strtolower ( $ tag_name );
2924- if ( ' math ' === $ this -> get_namespace () ) {
2962+ if ( ' svg ' !== $ namespace ) {
2963+ // This should be unreachable, but prevents tools from inaccurately reporting type errors.
29252964 return $ lower_tag_name ;
29262965 }
29272966
2928- if ( 'svg ' === $ this ->get_namespace () ) {
2929- switch ( $ lower_tag_name ) {
2930- case 'altglyph ' :
2931- return 'altGlyph ' ;
2967+ switch ( $ lower_tag_name ) {
2968+ case 'altglyph ' :
2969+ return 'altGlyph ' ;
29322970
2933- case 'altglyphdef ' :
2934- return 'altGlyphDef ' ;
2971+ case 'altglyphdef ' :
2972+ return 'altGlyphDef ' ;
29352973
2936- case 'altglyphitem ' :
2937- return 'altGlyphItem ' ;
2974+ case 'altglyphitem ' :
2975+ return 'altGlyphItem ' ;
29382976
2939- case 'animatecolor ' :
2940- return 'animateColor ' ;
2977+ case 'animatecolor ' :
2978+ return 'animateColor ' ;
29412979
2942- case 'animatemotion ' :
2943- return 'animateMotion ' ;
2980+ case 'animatemotion ' :
2981+ return 'animateMotion ' ;
29442982
2945- case 'animatetransform ' :
2946- return 'animateTransform ' ;
2983+ case 'animatetransform ' :
2984+ return 'animateTransform ' ;
29472985
2948- case 'clippath ' :
2949- return 'clipPath ' ;
2986+ case 'clippath ' :
2987+ return 'clipPath ' ;
29502988
2951- case 'feblend ' :
2952- return 'feBlend ' ;
2989+ case 'feblend ' :
2990+ return 'feBlend ' ;
29532991
2954- case 'fecolormatrix ' :
2955- return 'feColorMatrix ' ;
2992+ case 'fecolormatrix ' :
2993+ return 'feColorMatrix ' ;
29562994
2957- case 'fecomponenttransfer ' :
2958- return 'feComponentTransfer ' ;
2995+ case 'fecomponenttransfer ' :
2996+ return 'feComponentTransfer ' ;
29592997
2960- case 'fecomposite ' :
2961- return 'feComposite ' ;
2998+ case 'fecomposite ' :
2999+ return 'feComposite ' ;
29623000
2963- case 'feconvolvematrix ' :
2964- return 'feConvolveMatrix ' ;
3001+ case 'feconvolvematrix ' :
3002+ return 'feConvolveMatrix ' ;
29653003
2966- case 'fediffuselighting ' :
2967- return 'feDiffuseLighting ' ;
3004+ case 'fediffuselighting ' :
3005+ return 'feDiffuseLighting ' ;
29683006
2969- case 'fedisplacementmap ' :
2970- return 'feDisplacementMap ' ;
3007+ case 'fedisplacementmap ' :
3008+ return 'feDisplacementMap ' ;
29713009
2972- case 'fedistantlight ' :
2973- return 'feDistantLight ' ;
3010+ case 'fedistantlight ' :
3011+ return 'feDistantLight ' ;
29743012
2975- case 'fedropshadow ' :
2976- return 'feDropShadow ' ;
3013+ case 'fedropshadow ' :
3014+ return 'feDropShadow ' ;
29773015
2978- case 'feflood ' :
2979- return 'feFlood ' ;
3016+ case 'feflood ' :
3017+ return 'feFlood ' ;
29803018
2981- case 'fefunca ' :
2982- return 'feFuncA ' ;
3019+ case 'fefunca ' :
3020+ return 'feFuncA ' ;
29833021
2984- case 'fefuncb ' :
2985- return 'feFuncB ' ;
3022+ case 'fefuncb ' :
3023+ return 'feFuncB ' ;
29863024
2987- case 'fefuncg ' :
2988- return 'feFuncG ' ;
3025+ case 'fefuncg ' :
3026+ return 'feFuncG ' ;
29893027
2990- case 'fefuncr ' :
2991- return 'feFuncR ' ;
3028+ case 'fefuncr ' :
3029+ return 'feFuncR ' ;
29923030
2993- case 'fegaussianblur ' :
2994- return 'feGaussianBlur ' ;
3031+ case 'fegaussianblur ' :
3032+ return 'feGaussianBlur ' ;
29953033
2996- case 'feimage ' :
2997- return 'feImage ' ;
3034+ case 'feimage ' :
3035+ return 'feImage ' ;
29983036
2999- case 'femerge ' :
3000- return 'feMerge ' ;
3037+ case 'femerge ' :
3038+ return 'feMerge ' ;
30013039
3002- case 'femergenode ' :
3003- return 'feMergeNode ' ;
3040+ case 'femergenode ' :
3041+ return 'feMergeNode ' ;
30043042
3005- case 'femorphology ' :
3006- return 'feMorphology ' ;
3043+ case 'femorphology ' :
3044+ return 'feMorphology ' ;
30073045
3008- case 'feoffset ' :
3009- return 'feOffset ' ;
3046+ case 'feoffset ' :
3047+ return 'feOffset ' ;
30103048
3011- case 'fepointlight ' :
3012- return 'fePointLight ' ;
3049+ case 'fepointlight ' :
3050+ return 'fePointLight ' ;
30133051
3014- case 'fespecularlighting ' :
3015- return 'feSpecularLighting ' ;
3052+ case 'fespecularlighting ' :
3053+ return 'feSpecularLighting ' ;
30163054
3017- case 'fespotlight ' :
3018- return 'feSpotLight ' ;
3055+ case 'fespotlight ' :
3056+ return 'feSpotLight ' ;
30193057
3020- case 'fetile ' :
3021- return 'feTile ' ;
3058+ case 'fetile ' :
3059+ return 'feTile ' ;
30223060
3023- case 'feturbulence ' :
3024- return 'feTurbulence ' ;
3061+ case 'feturbulence ' :
3062+ return 'feTurbulence ' ;
30253063
3026- case 'foreignobject ' :
3027- return 'foreignObject ' ;
3064+ case 'foreignobject ' :
3065+ return 'foreignObject ' ;
30283066
3029- case 'glyphref ' :
3030- return 'glyphRef ' ;
3067+ case 'glyphref ' :
3068+ return 'glyphRef ' ;
30313069
3032- case 'lineargradient ' :
3033- return 'linearGradient ' ;
3070+ case 'lineargradient ' :
3071+ return 'linearGradient ' ;
30343072
3035- case 'radialgradient ' :
3036- return 'radialGradient ' ;
3073+ case 'radialgradient ' :
3074+ return 'radialGradient ' ;
30373075
3038- case 'textpath ' :
3039- return 'textPath ' ;
3076+ case 'solidcolor ' :
3077+ return 'solidColor ' ;
30403078
3041- default :
3042- return $ lower_tag_name ;
3043- }
3079+ case 'textarea ' :
3080+ return 'textArea ' ;
3081+
3082+ case 'textpath ' :
3083+ return 'textPath ' ;
30443084 }
30453085
3046- // This unnecessary return prevents tools from inaccurately reporting type errors.
3047- return $ tag_name ;
3086+ return $ lower_tag_name ;
30483087 }
30493088
30503089 /**
3051- * Returns the adjusted attribute name for a given attribute, taking into
3052- * account the current parsing context, whether HTML, SVG, or MathML.
3090+ * Returns the normative adjusted attribute name for an element’s attribute
3091+ * in the element’s namespace, usually for display or XML output only.
3092+ *
3093+ * For checking which attributes are present on a given tag, rely on
3094+ * {@see static::get_attribute_names_with_prefix()} which takes into account
3095+ * HTML case-folding rules and always returns an ASCII lower-case variant of
3096+ * the attribute names.
3097+ *
3098+ * - Some SVG attribute names are mixed-case, such as `baseFrequency`.
3099+ * - The `definitionURL` in SVG and MathML is mixed-case.
3100+ * - Some HTML attributes which have special meaning in XML are separated
3101+ * into a namespace and local name, such as `xml:lang` turning into `xml lang`.
30533102 *
30543103 * @since 6.7.0
30553104 *
@@ -3074,12 +3123,14 @@ public function get_qualified_attribute_name( $attribute_name ): ?string {
30743123 case 'attributename ' :
30753124 return 'attributeName ' ;
30763125
3126+ // @todo Is this right?
30773127 case 'attributetype ' :
30783128 return 'attributeType ' ;
30793129
30803130 case 'basefrequency ' :
30813131 return 'baseFrequency ' ;
30823132
3133+ // @todo Is this right?
30833134 case 'baseprofile ' :
30843135 return 'baseProfile ' ;
30853136
@@ -3191,6 +3242,7 @@ public function get_qualified_attribute_name( $attribute_name ): ?string {
31913242 case 'requiredextensions ' :
31923243 return 'requiredExtensions ' ;
31933244
3245+ // @todo Is this right?
31943246 case 'requiredfeatures ' :
31953247 return 'requiredFeatures ' ;
31963248
0 commit comments