From beda2c383ae588db8c8a11c5f1e505ef06b27e83 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 21 Feb 2023 15:02:49 -0700 Subject: [PATCH 01/20] WIP: Introduce directive processor The directive processor builds on the HTML Tag Processor to provide additional functionality, assuming normative markup generated from React, assuming that all tag directives contain a balanced closer. This will break in practice, and is probably entirely wrong as-is. The purpose of this commit is to introduce the idea and explore using the code to get a feel for whether it will work. --- src/class-wp-directive-processor.php | 92 ++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/class-wp-directive-processor.php diff --git a/src/class-wp-directive-processor.php b/src/class-wp-directive-processor.php new file mode 100644 index 00000000..1a346efb --- /dev/null +++ b/src/class-wp-directive-processor.php @@ -0,0 +1,92 @@ +might_have_directives = false; + } + } + + public function next_directive() { + if ( false === $this->might_have_directives ) { + return false; + } + + while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + $tag_name = $this->get_tag(); + if ( 0 === stripos( self::DIRECTIVE_PREFIX, $tag_name ) ) { + return true; + } + + $attribute_directives = $this->get_attribute_names_with_prefix( self::DIRECTIVE_PREFIX ); + if ( 0 < count( $attribute_directives ) ) { + return true; + } + } + + return false; + } + + public function next_balanced_closer() { + $depth = 0; + + $tag_name = $this->get_tag(); + while ( $this->next_tag( array( 'tag_name' => $tag_name, 'tag_closers' => 'visit' ) ) ) { + if ( ! $this->is_tag_closer() ) { + $depth++; + continue; + } + + if ( 0 === $depth ) { + return true; + } + + $depth--; + } + + return false; + } + + public function get_inner_html() { + $this->set_bookmark( 'start' ); + if ( ! $this->next_balanced_closer() ) { + $this->release_bookmark( 'start' ); + return false; + } + $this->set_bookmark( 'end' ); + + $start = $this->bookmarks['start']->end + 1; + $end = $this->bookmarks['end']->start; + + $this->release_bookmark( 'start' ); + $this->release_bookmark( 'end' ); + + return substr( $this->html, $start, $end - $start ); + } + + public function set_inner_html( $new_html ) { + $this->set_bookmark( 'start' ); + if ( ! $this->next_balanced_closer() ) { + $this->release_bookmark( 'start' ); + return false; + } + $this->set_bookmark( 'end' ); + + $start = $this->bookmarks['start']->end + 1; + $end = $this->bookmarks['end']->start; + + $this->release_bookmark( 'start' ); + $this->release_bookmark( 'end' ); + + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); + return true; + } +} \ No newline at end of file From 4e4b983a3c5c8c72d267ca5edcce682595ede12a Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 2 Mar 2023 12:13:50 +0100 Subject: [PATCH 02/20] Move to directives/ dir --- src/{ => directives}/class-wp-directive-processor.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{ => directives}/class-wp-directive-processor.php (100%) diff --git a/src/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php similarity index 100% rename from src/class-wp-directive-processor.php rename to src/directives/class-wp-directive-processor.php From f85a7d366842293caca89d133190946b0d3d1fa1 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 2 Mar 2023 12:14:03 +0100 Subject: [PATCH 03/20] Add basic test coverage --- phpunit/directives/wp-directive-processor.php | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 phpunit/directives/wp-directive-processor.php diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php new file mode 100644 index 00000000..83ba643c --- /dev/null +++ b/phpunit/directives/wp-directive-processor.php @@ -0,0 +1,98 @@ +outside
inside
'; + + public function test_get_inner_html_returns_correct_result() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $this->assertSame( '
inside
', $tags->get_inner_html() ); + } + + public function test_set_inner_html_on_void_element_has_no_effect() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'img' ); + $content = $tags->set_inner_html( 'This is the new img content' ); + $this->assertFalse( $content ); + $this->assertSame( self::HTML, $tags->get_updated_html() ); + } + + public function test_set_inner_html_sets_content_correctly() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->set_inner_html( 'This is the new section content.' ); + $this->assertSame( '
outside
This is the new section content.
', $tags->get_updated_html() ); + } + + public function test_set_inner_html_updates_bookmarks_correctly() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'div' ); + $tags->set_bookmark( 'start' ); + $tags->next_tag( 'img' ); + $this->assertSame( 'IMG', $tags->get_tag() ); + $tags->set_bookmark( 'after' ); + $tags->seek( 'start' ); + + $tags->set_inner_html( 'This is the new div content.' ); + $this->assertSame( '
This is the new div content.
inside
', $tags->get_updated_html() ); + $tags->seek( 'after' ); + $this->assertSame( 'IMG', $tags->get_tag() ); + } + + public function test_set_inner_html_subsequent_updates_on_the_same_tag_work() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->set_inner_html( 'This is the new section content.' ); + $tags->set_inner_html( 'This is the even newer section content.' ); + $this->assertSame( '
outside
This is the even newer section content.
', $tags->get_updated_html() ); + } + + public function test_set_inner_html_followed_by_set_attribute_works() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->set_inner_html( 'This is the new section content.' ); + $tags->set_attribute( 'id', 'thesection' ); + $this->assertSame( '
outside
This is the new section content.
', $tags->get_updated_html() ); + } + + public function test_set_inner_html_preceded_by_set_attribute_works() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->set_attribute( 'id', 'thesection' ); + $tags->set_inner_html( 'This is the new section content.' ); + $this->assertSame( '
outside
This is the new section content.
', $tags->get_updated_html() ); + } + + public function test_set_inner_html_invalidates_bookmarks_that_point_to_replaced_content() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->set_bookmark( 'start' ); + $tags->next_tag( 'img' ); + $tags->set_bookmark( 'replaced' ); + $tags->seek( 'start' ); + + $tags->set_inner_html( 'This is the new section content.' ); + $this->assertSame( '
outside
This is the new section content.
', $tags->get_updated_html() ); + + $this->expectExceptionMessage( 'Invalid bookmark name' ); + $successful_seek = $tags->seek( 'replaced' ); + $this->assertFalse( $successful_seek ); + } +} From 9c25acba37a8ed0b6b3b91e8bc023be97869ca18 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 2 Mar 2023 12:21:45 +0100 Subject: [PATCH 04/20] Add basic test for next_balanced_closer --- phpunit/directives/wp-directive-processor.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php index 83ba643c..d391de2e 100644 --- a/phpunit/directives/wp-directive-processor.php +++ b/phpunit/directives/wp-directive-processor.php @@ -12,6 +12,15 @@ class WP_Directive_Processor_Test extends WP_UnitTestCase { const HTML = '
outside
inside
'; + public function test_next_balanced_closer_proceeds_to_correct_tag() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'section' ); + $tags->next_balanced_closer(); + $this->assertSame( 'SECTION', $tags->get_tag() ); + $this->assertTrue( $tags->is_tag_closer() ); + } + public function test_get_inner_html_returns_correct_result() { $tags = new WP_Directive_Processor( self::HTML ); From 3c95937fa45341292ad10655b203522567e071ed Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 2 Mar 2023 12:24:51 +0100 Subject: [PATCH 05/20] More test coverage for next_balanced_tag --- phpunit/directives/wp-directive-processor.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php index d391de2e..8ca135de 100644 --- a/phpunit/directives/wp-directive-processor.php +++ b/phpunit/directives/wp-directive-processor.php @@ -21,6 +21,16 @@ public function test_next_balanced_closer_proceeds_to_correct_tag() { $this->assertTrue( $tags->is_tag_closer() ); } + public function test_next_balanced_closer_proceeds_to_correct_tag_for_nested_tag() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'div' ); + $tags->next_tag( 'div' ); + $tags->next_balanced_closer(); + $this->assertSame( 'DIV', $tags->get_tag() ); + $this->assertTrue( $tags->is_tag_closer() ); + } + public function test_get_inner_html_returns_correct_result() { $tags = new WP_Directive_Processor( self::HTML ); From 52d15d7acbda2d67ce06e83373a32cfc16121bb4 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 6 Mar 2023 17:12:27 +0100 Subject: [PATCH 06/20] TEMP: Use GB v15.3.0 RC2 --- .wp-env.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index f282f93d..47b4bbd9 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,7 @@ { "plugins": [ ".", - "https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip" + "https://github.com/WordPress/gutenberg/releases/download/v15.3.0-rc.2/gutenberg.zip" ], "config": { "SCRIPT_DEBUG": true From 9d7b1e13d47972e3322b37024b1616d6a31c6fcb Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 9 Mar 2023 09:22:23 +0100 Subject: [PATCH 07/20] Revert "TEMP: Use GB v15.3.0 RC2" This reverts commit bbc790fcfe2c8da31ada4793333c2d15138d434f. --- .wp-env.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index 47b4bbd9..f282f93d 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,7 @@ { "plugins": [ ".", - "https://github.com/WordPress/gutenberg/releases/download/v15.3.0-rc.2/gutenberg.zip" + "https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip" ], "config": { "SCRIPT_DEBUG": true From 3964533e6ef8ff0225c0a3aac8799d1df8c27cc4 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 9 Mar 2023 13:08:15 +0100 Subject: [PATCH 08/20] In {g|s}et_inner_html, return to original position --- src/directives/class-wp-directive-processor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 1a346efb..370fb969 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -66,6 +66,7 @@ public function get_inner_html() { $start = $this->bookmarks['start']->end + 1; $end = $this->bookmarks['end']->start; + $this->seek( 'start' ); // Return to original position. $this->release_bookmark( 'start' ); $this->release_bookmark( 'end' ); @@ -83,6 +84,7 @@ public function set_inner_html( $new_html ) { $start = $this->bookmarks['start']->end + 1; $end = $this->bookmarks['end']->start; + $this->seek( 'start' ); // Return to original position. $this->release_bookmark( 'start' ); $this->release_bookmark( 'end' ); From a32cfa53c29bd1cda98b5cfd79c15308765ec05b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 9 Mar 2023 13:11:24 +0100 Subject: [PATCH 09/20] Run get_updated_html in set_inner_html --- src/directives/class-wp-directive-processor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 370fb969..08598aba 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -74,6 +74,8 @@ public function get_inner_html() { } public function set_inner_html( $new_html ) { + $this->get_updated_html(); // Apply potential previous updates. + $this->set_bookmark( 'start' ); if ( ! $this->next_balanced_closer() ) { $this->release_bookmark( 'start' ); From 054c579f97d35414d975f683c3894c080b3045e0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 9 Mar 2023 13:28:40 +0100 Subject: [PATCH 10/20] Skip bookmark invalidation unit test --- phpunit/directives/wp-directive-processor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php index 8ca135de..b06e4430 100644 --- a/phpunit/directives/wp-directive-processor.php +++ b/phpunit/directives/wp-directive-processor.php @@ -99,6 +99,8 @@ public function test_set_inner_html_preceded_by_set_attribute_works() { } public function test_set_inner_html_invalidates_bookmarks_that_point_to_replaced_content() { + $this->markTestSkipped( "This requires on bookmark invalidation, which is only in GB's WP 6.3 compat layer." ); + $tags = new WP_Directive_Processor( self::HTML ); $tags->next_tag( 'section' ); From fc850326aee928a7be9d5abbf9905cf808ec8511 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 12:14:59 +0100 Subject: [PATCH 11/20] Move is_html_void_element into WP_Directive_Processor --- .../class-wp-directive-processor.php | 22 ++++++++++++++++ src/directives/wp-process-directives.php | 26 ++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 08598aba..279b3749 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -93,4 +93,26 @@ public function set_inner_html( $new_html ) { $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); return true; } + + public static function is_html_void_element( $tag_name ) { + switch ( $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; + } + } } \ No newline at end of file diff --git a/src/directives/wp-process-directives.php b/src/directives/wp-process-directives.php index c465d6ba..21ca430a 100644 --- a/src/directives/wp-process-directives.php +++ b/src/directives/wp-process-directives.php @@ -1,6 +1,7 @@ get_tag() ) && + ! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) && ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) ) { $tag_stack[] = array( $tag_name, $attributes ); @@ -56,26 +57,3 @@ function wp_process_directives( $tags, $prefix, $directives ) { return $tags; } -// TODO: Move into `WP_HTML_Tag_Processor` (or `WP_HTML_Processor`). -// See e.g. https://github.com/WordPress/gutenberg/pull/47573. -function is_html_void_element( $tag_name ) { - switch ( $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; - } -} From 1a23ad9c558cdfb031868d2274aa49457fa8e66e Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 12:15:19 +0100 Subject: [PATCH 12/20] Handle void elements in next_balanced_closer --- phpunit/directives/wp-directive-processor.php | 9 +++++++++ src/directives/class-wp-directive-processor.php | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php index b06e4430..563daf82 100644 --- a/phpunit/directives/wp-directive-processor.php +++ b/phpunit/directives/wp-directive-processor.php @@ -12,6 +12,15 @@ class WP_Directive_Processor_Test extends WP_UnitTestCase { const HTML = '
outside
inside
'; + public function test_next_balanced_closer_stays_on_void_tag() { + $tags = new WP_Directive_Processor( self::HTML ); + + $tags->next_tag( 'img' ); + $result = $tags->next_balanced_closer(); + $this->assertSame( 'IMG', $tags->get_tag() ); + $this->assertFalse( $result ); + } + public function test_next_balanced_closer_proceeds_to_correct_tag() { $tags = new WP_Directive_Processor( self::HTML ); diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 279b3749..28ec9910 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -39,6 +39,11 @@ public function next_balanced_closer() { $depth = 0; $tag_name = $this->get_tag(); + + if ( self::is_html_void_element( $tag_name ) ) { + return false; + } + while ( $this->next_tag( array( 'tag_name' => $tag_name, 'tag_closers' => 'visit' ) ) ) { if ( ! $this->is_tag_closer() ) { $depth++; From 351635ece27be4a8aa69f71bd7921902267cf8fa Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 13:39:34 +0100 Subject: [PATCH 13/20] Add PHPDoc --- .../class-wp-directive-processor.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 28ec9910..da0e736d 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -35,6 +35,16 @@ public function next_directive() { return false; } + /** + * Find the matching closing tag for an opening tag. + * + * When called while on an open tag, traverse the HTML until we find + * the matching closing tag, respecting any in-between content, including + * nested tags of the same name. Return false when called on a closing or + * void tag, or if no matching closing tag was found. + * + * @return bool True if a matching closing tag was found. + */ public function next_balanced_closer() { $depth = 0; @@ -60,6 +70,14 @@ public function next_balanced_closer() { return false; } + /** + * Return the content between two balanced tags. + * + * When called on an opening tag, return the HTML content found between + * that opening tag and its matching closing tag. + * + * @return string The content between the current opening and its matching closing tag. + */ public function get_inner_html() { $this->set_bookmark( 'start' ); if ( ! $this->next_balanced_closer() ) { @@ -78,6 +96,15 @@ public function get_inner_html() { return substr( $this->html, $start, $end - $start ); } + /** + * Set the content between two balanced tags. + * + * When called on an opening tag, set the HTML content found between + * that opening tag and its matching closing tag. + * + * @param string $new_html The string to replace the content between the matching tags with. + * @return bool Whether the content was successfully replaced. + */ public function set_inner_html( $new_html ) { $this->get_updated_html(); // Apply potential previous updates. @@ -99,6 +126,14 @@ public function set_inner_html( $new_html ) { return true; } + /** + * Whether a given HTML element is void (e.g.
). + * + * @param string $tag_name The element in question. + * @return bool True if the element is void. + * + * @see https://html.spec.whatwg.org/#elements-2 + */ public static function is_html_void_element( $tag_name ) { switch ( $tag_name ) { case 'AREA': From 90ad44169420b1b0484bbc664d12b8e9a71eb3b7 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 13:47:00 +0100 Subject: [PATCH 14/20] Allow setting prefix --- src/directives/class-wp-directive-processor.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index da0e736d..69646e38 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -1,16 +1,18 @@ prefix = $prefix; + + if ( false === stripos( $this->prefix, $html ) ) { $this->might_have_directives = false; } } @@ -22,11 +24,11 @@ public function next_directive() { while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $this->get_tag(); - if ( 0 === stripos( self::DIRECTIVE_PREFIX, $tag_name ) ) { + if ( 0 === stripos( $this->prefix, $tag_name ) ) { return true; } - $attribute_directives = $this->get_attribute_names_with_prefix( self::DIRECTIVE_PREFIX ); + $attribute_directives = $this->get_attribute_names_with_prefix( $this->prefix ); if ( 0 < count( $attribute_directives ) ) { return true; } From 05fcd77e90a2e436d2a1aaca8d52b1c53ba92ccd Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 13:57:27 +0100 Subject: [PATCH 15/20] Needle <-> haystack --- src/directives/class-wp-directive-processor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 69646e38..3ce073cc 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -12,7 +12,7 @@ public function __construct( $html, $prefix = 'wp-' ) { $this->prefix = $prefix; - if ( false === stripos( $this->prefix, $html ) ) { + if ( false === stripos( $html, $this->prefix ) ) { $this->might_have_directives = false; } } @@ -24,7 +24,7 @@ public function next_directive() { while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $this->get_tag(); - if ( 0 === stripos( $this->prefix, $tag_name ) ) { + if ( 0 === stripos( $tag_name, $this->prefix ) ) { return true; } From 749f11096c495716b5d436ed8d4d069d02b365ae Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 14:00:27 +0100 Subject: [PATCH 16/20] Scrape directive specific stuff for now --- .../class-wp-directive-processor.php | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 3ce073cc..5b9f091d 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -1,42 +1,6 @@ prefix = $prefix; - - if ( false === stripos( $html, $this->prefix ) ) { - $this->might_have_directives = false; - } - } - - public function next_directive() { - if ( false === $this->might_have_directives ) { - return false; - } - - while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - $tag_name = $this->get_tag(); - if ( 0 === stripos( $tag_name, $this->prefix ) ) { - return true; - } - - $attribute_directives = $this->get_attribute_names_with_prefix( $this->prefix ); - if ( 0 < count( $attribute_directives ) ) { - return true; - } - } - - return false; - } - /** * Find the matching closing tag for an opening tag. * From 9b31d7b4069b5591258056f578f699566cd88058 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 14:26:46 +0100 Subject: [PATCH 17/20] set_inner_html: Make sure bookmarks don't collide --- .../class-wp-directive-processor.php | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 5b9f091d..4cbeecfe 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -74,19 +74,31 @@ public function get_inner_html() { public function set_inner_html( $new_html ) { $this->get_updated_html(); // Apply potential previous updates. - $this->set_bookmark( 'start' ); + $i = 0; + while ( array_key_exists( 'start' . $i , $this->bookmarks ) ) { + ++$i; + } + $start_name = 'start' . $i; + + $this->set_bookmark( $start_name ); if ( ! $this->next_balanced_closer() ) { - $this->release_bookmark( 'start' ); + $this->release_bookmark( $start_name ); return false; } - $this->set_bookmark( 'end' ); - $start = $this->bookmarks['start']->end + 1; - $end = $this->bookmarks['end']->start; + $i = 0; + while ( array_key_exists( 'end' . $i , $this->bookmarks ) ) { + ++$i; + } + $end_name = 'end' . $i; + $this->set_bookmark( $end_name ); - $this->seek( 'start' ); // Return to original position. - $this->release_bookmark( 'start' ); - $this->release_bookmark( 'end' ); + $start = $this->bookmarks[ $start_name ]->end + 1; + $end = $this->bookmarks[ $end_name ]->start; + + $this->seek( $start_name ); // Return to original position. + $this->release_bookmark( $start_name ); + $this->release_bookmark( $end_name ); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); return true; From 0a163253477fc9d32a464e4666bc9015b0547508 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 14:29:52 +0100 Subject: [PATCH 18/20] Introduce get_balanced_bookmarks helper --- .../class-wp-directive-processor.php | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 4cbeecfe..9bab5829 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -74,6 +74,29 @@ public function get_inner_html() { public function set_inner_html( $new_html ) { $this->get_updated_html(); // Apply potential previous updates. + $bookmarks = $this->get_balanced_tag_bookmarks(); + if ( ! $bookmarks ) { + return false; + } + list( $start_name, $end_name ) = $bookmarks; + + $start = $this->bookmarks[ $start_name ]->end + 1; + $end = $this->bookmarks[ $end_name ]->start; + + $this->seek( $start_name ); // Return to original position. + $this->release_bookmark( $start_name ); + $this->release_bookmark( $end_name ); + + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); + return true; + } + + /** + * Return a pair of bookmarks for the current opening tag and the matching closing tag. + * + * @return array|false A pair of bookmarks, or false if there's no matching closing tag. + */ + public function get_balanced_tag_bookmarks() { $i = 0; while ( array_key_exists( 'start' . $i , $this->bookmarks ) ) { ++$i; @@ -93,15 +116,7 @@ public function set_inner_html( $new_html ) { $end_name = 'end' . $i; $this->set_bookmark( $end_name ); - $start = $this->bookmarks[ $start_name ]->end + 1; - $end = $this->bookmarks[ $end_name ]->start; - - $this->seek( $start_name ); // Return to original position. - $this->release_bookmark( $start_name ); - $this->release_bookmark( $end_name ); - - $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); - return true; + return array( $start_name, $end_name ); } /** From c738c55d5d82a5fbe59c5f9d1e8421ddaed968c6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 14:31:11 +0100 Subject: [PATCH 19/20] Use get_balanced_tag_bookmarks helper in get_inner_html() --- src/directives/class-wp-directive-processor.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 9bab5829..547fe61b 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -45,19 +45,18 @@ public function next_balanced_closer() { * @return string The content between the current opening and its matching closing tag. */ public function get_inner_html() { - $this->set_bookmark( 'start' ); - if ( ! $this->next_balanced_closer() ) { - $this->release_bookmark( 'start' ); + $bookmarks = $this->get_balanced_tag_bookmarks(); + if ( ! $bookmarks ) { return false; } - $this->set_bookmark( 'end' ); + list( $start_name, $end_name ) = $bookmarks; - $start = $this->bookmarks['start']->end + 1; - $end = $this->bookmarks['end']->start; + $start = $this->bookmarks[ $start_name ]->end + 1; + $end = $this->bookmarks[ $end_name ]->start; - $this->seek( 'start' ); // Return to original position. - $this->release_bookmark( 'start' ); - $this->release_bookmark( 'end' ); + $this->seek( $start_name ); // Return to original position. + $this->release_bookmark( $start_name ); + $this->release_bookmark( $end_name ); return substr( $this->html, $start, $end - $start ); } From eecd268135dcee100c23022d5d50d32235ec9c56 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 14 Mar 2023 14:33:33 +0100 Subject: [PATCH 20/20] Format --- phpunit/directives/wp-directive-processor.php | 2 +- src/directives/class-wp-directive-processor.php | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php index 563daf82..e24e247c 100644 --- a/phpunit/directives/wp-directive-processor.php +++ b/phpunit/directives/wp-directive-processor.php @@ -30,7 +30,7 @@ public function test_next_balanced_closer_proceeds_to_correct_tag() { $this->assertTrue( $tags->is_tag_closer() ); } - public function test_next_balanced_closer_proceeds_to_correct_tag_for_nested_tag() { + public function test_next_balanced_closer_proceeds_to_correct_tag_for_nested_tag() { $tags = new WP_Directive_Processor( self::HTML ); $tags->next_tag( 'div' ); diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php index 547fe61b..d9a18cf0 100644 --- a/src/directives/class-wp-directive-processor.php +++ b/src/directives/class-wp-directive-processor.php @@ -20,7 +20,12 @@ public function next_balanced_closer() { return false; } - while ( $this->next_tag( array( 'tag_name' => $tag_name, 'tag_closers' => 'visit' ) ) ) { + while ( $this->next_tag( + array( + 'tag_name' => $tag_name, + 'tag_closers' => 'visit', + ) + ) ) { if ( ! $this->is_tag_closer() ) { $depth++; continue; @@ -97,7 +102,7 @@ public function set_inner_html( $new_html ) { */ public function get_balanced_tag_bookmarks() { $i = 0; - while ( array_key_exists( 'start' . $i , $this->bookmarks ) ) { + while ( array_key_exists( 'start' . $i, $this->bookmarks ) ) { ++$i; } $start_name = 'start' . $i; @@ -109,7 +114,7 @@ public function get_balanced_tag_bookmarks() { } $i = 0; - while ( array_key_exists( 'end' . $i , $this->bookmarks ) ) { + while ( array_key_exists( 'end' . $i, $this->bookmarks ) ) { ++$i; } $end_name = 'end' . $i; @@ -147,4 +152,4 @@ public static function is_html_void_element( $tag_name ) { return false; } } -} \ No newline at end of file +}