-
Notifications
You must be signed in to change notification settings - Fork 11
WIP: Introduce directive processor #158
Changes from all commits
bd36851
5747eb3
d0aae6f
f124dbf
5893878
51bf340
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?php | ||
/** | ||
* `WP_Directive_Processor` class test. | ||
*/ | ||
require_once __DIR__ . '/../../src/directives/class-wp-directive-processor.php'; | ||
|
||
/** | ||
* @group html-processor | ||
* | ||
* @coversDefaultClass WP_Directive_Processor | ||
*/ | ||
class WP_Directive_Processor_Test extends WP_UnitTestCase { | ||
const HTML = '<div>outside</div><section><div><img>inside</div></section>'; | ||
|
||
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_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 ); | ||
|
||
$tags->next_tag( 'section' ); | ||
$this->assertSame( '<div><img>inside</div>', $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( '<div>outside</div><section>This is the new section content.</section>', $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( '<div>This is the new div content.</div><section><div><img>inside</div></section>', $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( '<div>outside</div><section>This is the even newer section content.</section>', $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( '<div>outside</div><section id="thesection">This is the new section content.</section>', $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( '<div>outside</div><section id="thesection">This is the new section content.</section>', $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( '<div>outside</div><section>This is the new section content.</section>', $tags->get_updated_html() ); | ||
|
||
$this->expectExceptionMessage( 'Invalid bookmark name' ); | ||
$successful_seek = $tags->seek( 'replaced' ); | ||
$this->assertFalse( $successful_seek ); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php | ||
|
||
class WP_Directive_Processor extends WP_HTML_Tag_Processor { | ||
const DIRECTIVE_PREFIX = 'WPX-'; | ||
|
||
public $html; | ||
|
||
public $might_have_directives = true; | ||
|
||
public function __construct( $html ) { | ||
parent::__construct( $html ); | ||
|
||
if ( false === stripos( self::DIRECTIVE_PREFIX, $html ) ) { | ||
$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( 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 ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that we're currently limiting the loop to only visit tags whose name does match. |
||
return true; | ||
} | ||
|
||
$depth--; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public function get_inner_html() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually scratch that, I tried |
||
$this->set_bookmark( 'start' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. correct, though this was not intended to be a correct working implementation. I wanted to keep things terse to explore the idea and not distract the main functionality with all the details |
||
if ( ! $this->next_balanced_closer() ) { | ||
$this->release_bookmark( 'start' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about wrapping this entire function in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not very keen on using error-handling constructs like this as code conveniences. there's an obvious question it raises, "why is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because |
||
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; | ||
} | ||
|
||
public function get_outer_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']->start; | ||
$end = $this->bookmarks['end']->end + 1; | ||
|
||
$this->release_bookmark( 'start' ); | ||
$this->release_bookmark( 'end' ); | ||
|
||
// For consistency with set_outer_html: | ||
$this->next_tag(); | ||
return substr( $this->html, $start, $end - $start ); | ||
} | ||
|
||
public function set_outer_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']->start; | ||
$end = $this->bookmarks['end']->end + 1; | ||
|
||
$this->release_bookmark( 'start' ); | ||
$this->release_bookmark( 'end' ); | ||
|
||
$this->next_tag(); | ||
$this->set_bookmark( 'next' ); | ||
$this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html ); | ||
// updates before the current position are not supported well and we end | ||
// up at an invalid combination of copied bytes and parsed bytes index. | ||
// bookmarks are updated correctly, though, so seek() makes it right again. | ||
$this->seek('next'); | ||
$this->release_bookmark( 'next' ); | ||
return true; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to check for void tags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you. this was not intended to be a correct working implementation so there are definitely more problems with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Carrying over my comment from #169