Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/wp-includes/html-api/class-wp-html-spec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

class WP_HTML_Spec {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is something I've meant to create in the HTML processor branches I've been working in. the idea is to move utilities which related to HTML-defined terminology into here.

that would be:

  • is_void_element()
  • is_html_element()
  • is_format_element()
  • etc…

is_html_element() will be important for HTML parsing because foreign elements may be self-closing, but HTML elements may not be, and therefore we have to ignore the trailing / in something like an <img /> tag and look for a closing tag if it's not a void element, such as with <div /> (because while it looks like a self-closer it isn't), but <wp-self-closer /> is due to it being a foreign element.

/**
* @see https://html.spec.whatwg.org/#elements-2
*/
public static function is_void_element( $tag_name ) {
switch ( strtoupper( $tag_name ) ) {
case 'AREA':
case 'BASE':
case 'BR':
case 'COL':
case 'EMBED':
case 'HR':
case 'IMG':
case 'INPUT':
case 'LINK':
case 'META':
case 'SOURCE':
case 'TRACK':
case 'WBR':
return true;

default:
return false;
}
}
}
19 changes: 19 additions & 0 deletions src/wp-includes/html-api/class-wp-html.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

class WP_HTML {
public static function make_tag( $tag_name, $attributes = null, $data = '' ) {
Copy link
Contributor

@adamziel adamziel Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a seperate PR I'd love to add make_class_name that would do what classnames() does in React.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class WP_CSS {
	public static function make_class( $class_names ) {
		
	}
}

this is another reason it could be useful to expose the processor rather than returning a string. the need to call ->get_updated_html() on it is the downside.

$panel = WP_HTML::make_tag( 'div', array( 'class' => 'wp-block-group' ) );
if ( $is_fullscreen ) {
	$panel->add_class( 'fullscreen' );
}

Copy link
Contributor

@adamziel adamziel Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the need to call ->get_updated_html() on it is the downside.

Not at all, __toString() is still there!

Here's the three alternatives I can think of, but with more class names:

make_class

$class_name = WP_HTML::make_class( 'wp-block-group', [ 
    'fullscreen' => $is_fullscreen,
    'bold' => $is_bold,
    'large' => $is_large
], $is_modal ? 'is-modal' : 'is-regular' );
WP_HTML::make_tag( 'div', [ 'class' => $class_name ] );

Returning a Tag Processor

$panel = WP_HTML::make_tag( 'div', array( 'class' => 'wp-block-group' ) );
if ( $is_fullscreen ) {
	$panel->add_class( 'fullscreen' );
}
if ( $is_bold ) {
	$panel->add_class( 'bold' );
}
if ( $is_large ) {
	$panel->add_class( 'large' );
}
if ( $is_modal) {
	$panel->add_class( 'is-modal' );
} else {
	$panel->add_class( 'is-regular' );
}

Manual wrapping in a tag processor

$panel = new WP_HTML_Tag_Processor(
    WP_HTML::make_tag( 'div' )
);
// ... The same as above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, let's consider conditional attributes:

Attributes array

$attributes = [
    'src' => $src
    'alt' => $has_alt ? $alt : false,
];
if ( ! $only_aria ) {
    $attributes['aria-title'] = $title;
} else {
    $attributes['title'] = $title;
}
WP_HTML::make_tag( 'div', $attributes );

Returning a Tag Processor

$panel = WP_HTML::make_tag( 'div', array( 'src' => $src ) );
if ( $has_alt ) {
	$panel->set_attribute( 'alt', $alt );
}
if ( $only_aria ) {
	$panel->set_attribute( 'aria-title', $title );
} else {
	$panel->set_attribute( 'title', $title );
}

Manual wrapping in a tag processor

$panel = new WP_HTML_Tag_Processor(
    WP_HTML::make_tag( 'div', array( 'src' => $src ) )
);
// ... The same as above

Copy link
Contributor

@adamziel adamziel Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What stands out:

  • The array approach is more concise and seems to support all possible use-cases
  • The tag processor approach still accepts an array of attributes but then requires using another API to handle conditionals

I'd go for make_class + an array of attributes and let folks create a new tag processor manually if they wish so.

Copy link
Contributor

@adamziel adamziel Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a wild idea:

echo WP_HTML_Builder::create()
  ->add_tag( 'form', [ 'class' => 'main' ] )
  ->add_child( 'input', [ 'type' => 'text', 'placeholder' => 'Your query' ] ) 
  ->add_sibling( 'button', [ 'class' => 'primary' ], 'Submit' )
  ->parent()
  ->add_sibling( 'img', [ 'src' => 'logo.png' ] )
;
/*
<form class="main">
    <input type="text" placeholder="Your query" />
    <button class="primary">Submit</button>
</form>
<img src="logo.png" />
*/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would play nicely with the future HTML Processor:

$p = new WP_HTML_Processor('<h1 title="main">Site title</h1>');
$p->next_tag();
$p->insert_html_after(
    WP_HTML_Builder::create()
      ->add_tag( 'form', [ 'class' => 'main' ] )
      ->add_sibling( 'img', [ 'src' => 'logo.png' ] )
);

$is_void = WP_HTML_Spec::is_void_element( $tag_name );
$html = $is_void ? "<{$tag_name}>" : "<{$tag_name}>{$data}</{$tag_name}>";

$p = new WP_HTML_Tag_Processor( $html );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This surprised me, as I was fully expecting manual stitching of the strings, but I think it makes total sense. Tag Processor handles boolean values, escaping, and probably a few more cases we would otherwise have to reimplement here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered it and it might make sense to construct it. this is more of an exploration of sharing the tag processor for this utility. I think initially I was looking at returning the processor itself, which would make it possible to modify after creating. the thought of that was for conditional attributes, but here that's already possible by setting an attribute to false


if ( is_array( $attributes ) ) {
$p->next_tag();
foreach ( $attributes as $name => $value ) {
$p->set_attribute( $name, $value );
}
}

return $p->get_updated_html();
}
}
2 changes: 2 additions & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@
require ABSPATH . WPINC . '/html-api/class-wp-html-span.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-text-replacement.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-tag-processor.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-spec.php';
require ABSPATH . WPINC . '/html-api/class-wp-html.php';
require ABSPATH . WPINC . '/class-wp-http.php';
require ABSPATH . WPINC . '/class-wp-http-streams.php';
require ABSPATH . WPINC . '/class-wp-http-curl.php';
Expand Down
24 changes: 24 additions & 0 deletions tests/phpunit/tests/html-api/wpHtml.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
/**
* Unit tests covering WP_HTML functionality.
*
* @package WordPress
* @subpackage HTML-API
*/

/**
* @group html-api
*
* @coversDefaultClass WP_HTML
*/
class Tests_HtmlApi_wpHtml extends WP_UnitTestCase {
public function test_make_tag() {
$script = WP_HTML::make_tag(
'script',
array( 'defer' => true, 'nonce' => 'HXhIuUk5lfXb' ),
'console.log("loaded");'
);

$this->assertSame( '<script defer nonce="HXhIuUk5lfXb">console.log("loaded");</script>', $script );
}
}