@@ -3468,3 +3468,153 @@ function wp_remove_surrounding_empty_script_tags( $contents ) {
34683468 );
34693469 }
34703470}
3471+
3472+ /**
3473+ * Return the corresponding JavaScript `dataset` name for an attribute
3474+ * if it represents a custom data attribute, or `null` if not.
3475+ *
3476+ * Custom data attributes appear in an element's `dataset` property in a
3477+ * browser, but there's a specific way the names are translated from HTML
3478+ * into JavaScript. This function indicates how the name would appear in
3479+ * JavaScript if a browser would recognize it as a custom data attribute.
3480+ *
3481+ * Example:
3482+ *
3483+ * // Dash-letter pairs turn into capital letters.
3484+ * 'postId' === wp_js_dataset_name( 'data-post-id' );
3485+ * 'Before' === wp_js_dataset_name( 'data--before' );
3486+ * '-One--Two---' === wp_js_dataset_name( 'data---one---two---' );
3487+ *
3488+ * // Not every attribute name will be interpreted as a custom data attribute.
3489+ * null === wp_js_dataset_name( 'post-id' );
3490+ * null === wp_js_dataset_name( 'data' );
3491+ *
3492+ * // Some very surprising names will; for example, a property whose name is the empty string.
3493+ * '' === wp_js_dataset_name( 'data-' );
3494+ * 0 === strlen( wp_js_dataset_name( 'data-' ) );
3495+ *
3496+ * @since 6.9.0
3497+ *
3498+ * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs
3499+ * @see \wp_html_custom_data_attribute_name()
3500+ *
3501+ * @param string $html_attribute_name Raw attribute name as found in the source HTML.
3502+ * @return string|null Transformed `dataset` name, if interpretable as a custom data attribute, else `null`.
3503+ */
3504+ function wp_js_dataset_name ( string $ html_attribute_name ): ?string {
3505+ if ( 0 !== substr_compare ( $ html_attribute_name , 'data- ' , 0 , 5 , true ) ) {
3506+ return null ;
3507+ }
3508+
3509+ $ end = strlen ( $ html_attribute_name );
3510+
3511+ /*
3512+ * If it contains characters which would end the attribute name parsing then
3513+ * something else is wrong and this contains more than just an attribute name.
3514+ */
3515+ if ( ( $ end - 5 ) !== strcspn ( $ html_attribute_name , "=/> \t\f\r\n" , 5 ) ) {
3516+ return null ;
3517+ }
3518+
3519+ /**
3520+ * > For each name in list, for each U+002D HYPHEN-MINUS character (-)
3521+ * > in the name that is followed by an ASCII lower alpha, remove the
3522+ * > U+002D HYPHEN-MINUS character (-) and replace the character that
3523+ * > followed it by the same character converted to ASCII uppercase.
3524+ *
3525+ * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs
3526+ */
3527+ $ custom_name = '' ;
3528+ $ at = 5 ;
3529+ $ was_at = $ at ;
3530+
3531+ while ( $ at < $ end ) {
3532+ $ next_dash_at = strpos ( $ html_attribute_name , '- ' , $ at );
3533+ if ( false === $ next_dash_at || $ next_dash_at === $ end - 1 ) {
3534+ break ;
3535+ }
3536+
3537+ // Transform `-a` to `A`, for example.
3538+ $ c = $ html_attribute_name [ $ next_dash_at + 1 ];
3539+ if ( ( $ c >= 'A ' && $ c <= 'Z ' ) || ( $ c >= 'a ' && $ c <= 'z ' ) ) {
3540+ $ prefix = substr ( $ html_attribute_name , $ was_at , $ next_dash_at - $ was_at );
3541+ $ custom_name .= strtolower ( $ prefix );
3542+ $ custom_name .= strtoupper ( $ c );
3543+ $ at = $ next_dash_at + 2 ;
3544+ $ was_at = $ at ;
3545+ continue ;
3546+ }
3547+
3548+ $ at = $ next_dash_at + 1 ;
3549+ }
3550+
3551+ // If nothing has been added it means there are no dash-letter pairs; return the name as-is.
3552+ return '' === $ custom_name
3553+ ? strtolower ( substr ( $ html_attribute_name , 5 ) )
3554+ : ( $ custom_name . strtolower ( substr ( $ html_attribute_name , $ was_at ) ) );
3555+ }
3556+
3557+ /**
3558+ * Returns a corresponding HTML attribute name for the given name,
3559+ * if that name were found in a JS element’s `dataset` property.
3560+ *
3561+ * Example:
3562+ *
3563+ * 'data-post-id' === wp_html_custom_data_attribute_name( 'postId' );
3564+ * 'data--before' === wp_html_custom_data_attribute_name( 'Before' );
3565+ * 'data---one---two---' === wp_html_custom_data_attribute_name( '-One--Two---' );
3566+ *
3567+ * // Not every attribute name will be interpreted as a custom data attribute.
3568+ * null === wp_html_custom_data_attribute_name( '/not-an-attribute/' );
3569+ * null === wp_html_custom_data_attribute_name( 'no spaces' );
3570+ *
3571+ * // Some very surprising names will; for example, a property whose name is the empty string.
3572+ * 'data-' === wp_html_custom_data_attribute_name( '' );
3573+ *
3574+ * @since 6.9.0
3575+ *
3576+ * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs
3577+ * @see \wp_js_dataset_name()
3578+ *
3579+ * @param string $js_dataset_name Name of JS `dataset` property to transform.
3580+ * @return string|null Corresponding name of an HTML custom data attribute for the given dataset name,
3581+ * if possible to represent in HTML, otherwise `null`.
3582+ */
3583+ function wp_html_custom_data_attribute_name ( string $ js_dataset_name ): ?string {
3584+ $ end = strlen ( $ js_dataset_name );
3585+ if ( 0 === $ end ) {
3586+ return 'data- ' ;
3587+ }
3588+
3589+ /*
3590+ * If it contains characters which would end the attribute name parsing then
3591+ * something it’s not possible to represent this in HTML.
3592+ */
3593+ if ( strcspn ( $ js_dataset_name , "=/> \t\f\r\n" ) !== $ end ) {
3594+ return null ;
3595+ }
3596+
3597+ $ html_name = 'data- ' ;
3598+ $ at = 0 ;
3599+ $ was_at = $ at ;
3600+
3601+ while ( $ at < $ end ) {
3602+ $ next_upper_after = strcspn ( $ js_dataset_name , 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , $ at );
3603+ $ next_upper_at = $ at + $ next_upper_after ;
3604+ if ( $ next_upper_at >= $ end ) {
3605+ break ;
3606+ }
3607+
3608+ $ prefix = substr ( $ js_dataset_name , $ was_at , $ next_upper_at - $ was_at );
3609+ $ html_name .= strtolower ( $ prefix );
3610+ $ html_name .= '- ' . strtolower ( $ js_dataset_name [ $ next_upper_at ] );
3611+ $ at = $ next_upper_at + 1 ;
3612+ $ was_at = $ at ;
3613+ }
3614+
3615+ if ( $ was_at < $ end ) {
3616+ $ html_name .= strtolower ( substr ( $ js_dataset_name , $ was_at ) );
3617+ }
3618+
3619+ return $ html_name ;
3620+ }
0 commit comments