File Coverage

lib/HTML/Object/DOM/Node.pm
Criterion Covered Total %
statement 398 727 54.7
branch 156 534 29.2
condition 84 290 28.9
subroutine 68 100 68.0
pod 64 64 100.0
total 770 1715 44.9


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## HTML Object - ~/lib/HTML/Object/DOM/Node.pm
3             ## Version v0.2.1
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/12/13
7             ## Modified 2022/09/20
8             ## All rights reserved
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package HTML::Object::DOM::Node;
15             BEGIN
16             {
17 29     29   18619 use strict;
  29         104  
  29         1017  
18 29     29   237 use warnings;
  29         120  
  29         1090  
19 29     29   587 use parent qw( HTML::Object::EventTarget );
  29         366  
  29         381  
20 29     29   2060 use vars qw( @EXPORT $XP $VERSION );
  29         122  
  29         2146  
21 29     29   251 use Nice::Try;
  29         81  
  29         498  
22 29     29   64135333 use Want;
  29         82  
  29         4242  
23             use constant {
24 29         11648 DOCUMENT_POSITION_IDENTICAL => 0,
25             DOCUMENT_POSITION_DISCONNECTED => 1,
26             DOCUMENT_POSITION_PRECEDING => 2,
27             DOCUMENT_POSITION_FOLLOWING => 4,
28             DOCUMENT_POSITION_CONTAINS => 8,
29             DOCUMENT_POSITION_CONTAINED_BY => 16,
30             DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC => 32,
31             XML_DEFAULT_NAMESPACE => 'http://www.w3.org/XML/1998/namespace',
32             ELEMENT_NODE => 1,
33             ATTRIBUTE_NODE => 2,
34             TEXT_NODE => 3,
35             CDATA_SECTION_NODE => 4,
36             # Deprecated
37             ENTITY_REFERENCE_NODE => 5,
38             # Deprecated
39             ENTITY_NODE => 6,
40             PROCESSING_INSTRUCTION_NODE => 7,
41             COMMENT_NODE => 8,
42             DOCUMENT_NODE => 9,
43             DOCUMENT_TYPE_NODE => 10,
44             DOCUMENT_FRAGMENT_NODE => 11,
45             NOTATION_NODE => 12,
46             SPACE_NODE => 13,
47 29     29   229 };
  29         168  
48 29     29   4640 our @EXPORT = qw(
49             DOCUMENT_POSITION_IDENTICAL
50             DOCUMENT_POSITION_DISCONNECTED
51             DOCUMENT_POSITION_PRECEDING
52             DOCUMENT_POSITION_FOLLOWING
53             DOCUMENT_POSITION_CONTAINS
54             DOCUMENT_POSITION_CONTAINED_BY
55             DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
56            
57             ELEMENT_NODE
58             ATTRIBUTE_NODE
59             TEXT_NODE
60             CDATA_SECTION_NODE
61             ENTITY_REFERENCE_NODE
62             ENTITY_NODE
63             PROCESSING_INSTRUCTION_NODE
64             COMMENT_NODE
65             DOCUMENT_NODE
66             DOCUMENT_TYPE_NODE
67             DOCUMENT_FRAGMENT_NODE
68             NOTATION_NODE
69             SPACE_NODE
70             );
71             use overload (
72 29         644 '==' => \&isSameNode,
73             'eq' => \&isSameNode,
74 29     29   242 );
  29         78  
75 29         64 our $XP;
76 29         618 our $VERSION = 'v0.2.1';
77             };
78              
79 29     29   192 use strict;
  29         62  
  29         775  
80 29     29   159 use warnings;
  29         61  
  29         92271  
81              
82             sub init
83             {
84 347     347 1 1080 my $self = shift( @_ );
85 347         1161 $self->{_init_strict_use_sub} = 1;
86 347 50       1994 $self->HTML::Object::Element::init( @_ ) || return( $self->pass_error );
87 347         2408 return( $self );
88             }
89              
90             sub appendChild
91             {
92 7     7 1 81 my $self = shift( @_ );
93 7 50       30 return( $self->error({
94             message => sprintf( "At least 1 arguments is required, but only %d provided.", scalar( @_ ) ),
95             class => 'HTML::Object::TypeError',
96             }) ) if( scalar( @_ ) < 1 );
97 7         13 my $new = shift( @_ );
98 7         25 my $new_parent = $new->parent;
99 7         130 my $parent = $self->parent;
100             # We use 'nodes' rather than 'children' so this works well with HTML::Object::DOM::Document
101 7         144 my $nodes = $self->nodes;
102 7 50 33     1131 if( $new_parent &&
    50 33        
    50 0        
    50 66        
    50 66        
    50 66        
    50 33        
    50 33        
      0        
      33        
      33        
      33        
      33        
103             !$self->_is_a( $new_parent => 'HTML::Object::DOM::Document' ) &&
104             !$self->_is_a( $new_parent => 'HTML::Object::DOM::DocumentFragment' ) &&
105             !$self->_is_a( $new_parent => 'HTML::Object::DOM::Element' ) )
106             {
107 0         0 return( $self->error({
108             message => "Node's parent is not an HTML::Object::DOM::Document, HTML::Object::DOM::DocumentFragment or HTML::Object::DOM::Element object.",
109             class => 'HTML::Object::HierarchyRequestError',
110             }) );
111             }
112             # All other conditions below are strictly similar to replaceChild()
113             elsif( !$self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) &&
114             !$self->_is_a( $new => 'HTML::Object::DOM::Declaration' ) &&
115             !$self->_is_a( $new => 'HTML::Object::DOM::Element' ) &&
116             !$self->_is_a( $new => 'HTML::Object::DOM::CharacterData' ) )
117             {
118 0         0 return( $self->error({
119             message => "New node is not an HTML::Object::DOM::DocumentFragment, HTML::Object::DOM::Declaration, HTML::Object::DOM::Element or HTML::Object::DOM::CharacterData object.",
120             class => 'HTML::Object::HierarchyRequestError',
121             }) );
122             }
123             elsif( $self->lineage->has( $new ) )
124             {
125 0         0 return( $self->error({
126             message => "New node provided is an ancestor of the current node.",
127             class => 'HTML::Object::HierarchyRequestError',
128             }) );
129             }
130             elsif( ( $self->_is_a( $new => 'HTML::Object::DOM::Text' ) || $self->_is_a( $new => 'HTML::Object::DOM::Space' ) ) &&
131             $self->_is_a( $new_parent => 'HTML::Object::DOM::Document' ) )
132             {
133 0         0 return( $self->error({
134             message => "New node is a HTML::Object::DOM::Text or HTML::Object::DOM::Space node and its parent is a HTML::Object::DOM::Document node.",
135             class => 'HTML::Object::HierarchyRequestError',
136             }) );
137             }
138             elsif( $self->isa( 'HTML::Object::DOM::Declaration' ) && !$self->_is_a( $parent => 'HTML::Object::DOM::Document' ) )
139             {
140 0         0 return( $self->error({
141             message => "Current node is a DocumentType, but its parent is not an HTML::Object::DOM::Document object.",
142             class => 'HTML::Object::HierarchyRequestError',
143             }) );
144             }
145             elsif( $self->_is_a( $parent => 'HTML::Object::DOM::Document' ) &&
146             $self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) &&
147 0     0   0 ( $new->childElementCount > 1 || $new->children->grep(sub{ $self->_is_a( $_ => 'HTML::Object::DOM::Text' ) })->length ) )
148             {
149 0         0 return( $self->error({
150             message => "Current node parent is a HTML::Object::DOM::Document object and new node is a HTML::Object::DOM::DocumentFragment object that has either more than 1 element or has a HTML::Object::DOM::Text node.",
151             class => 'HTML::Object::HierarchyRequestError',
152             }) );
153             }
154             # This is different from replaceChild()
155             elsif( $self->_is_a( $parent => 'HTML::Object::DOM::Document' ) &&
156             $parent->childElementCount > 0 &&
157             $self->_is_a( $new => 'HTML::Object::DOM::Element' ) )
158             {
159 0         0 return( $self->error({
160             message => "Attempting to replace a child element in a Document with another non HTML-tag element. Document can have only one Element: the HTML-tag element.",
161             class => 'HTML::Object::HierarchyRequestError',
162             }) );
163             }
164             # If the node to append is a doctype and the curent last node is an element, this would put a doctype after an element, which is forbidden
165             elsif( $self->_is_a( $new => 'HTML::Object::DOM::Declaration' ) &&
166             $self->_is_a( $nodes->last, 'HTML::Object::DOM::Element' ) )
167             {
168 0         0 return( $self->error({
169             message => "The last node is an element. Appending the DocumentType would place it after.",
170             class => 'HTML::Object::HierarchyRequestError',
171             }) );
172             }
173              
174 7         1166 $new->detach;
175 7 50       26 my $new_array = $self->new_array( $self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) ? $new->children : $new );
176             $new_array->foreach(sub
177             {
178 7 50   7   102 next if( !$self->_is_a( $_ => 'HTML::Object::DOM::Node' ) );
179 7         218 $_->parent( $self );
180 7         435 });
181 7         217 $nodes->push( $new_array->list );
182 7         139 $self->reset(1);
183 7 50       34 if( $self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) )
184             {
185 0         0 $new->children->reset;
186             }
187 7         239 return( $new );
188             }
189              
190             sub appendNodes
191             {
192 0     0 1 0 my $self = shift( @_ );
193 0         0 my $children = $self->children;
194 0         0 foreach my $this ( @_ )
195             {
196 0 0       0 if( $self->_is_a( $this => 'HTML::Object::DOM::Node' ) )
197             {
198 0         0 $this->parent( $self );
199 0         0 $children->push( $this );
200             }
201             }
202 0         0 return( $self );
203             }
204              
205             # Note: Property
206             # Example: <base href="https://www.example.com/">
207             sub baseURI : lvalue { return( shift->_set_get_callback({
208             get => sub
209             {
210 1     1   571 my $self = shift( @_ );
211 1         10 my $root = $self->root;
212 1 50       6 return( $self->new_null ) if( !$root );
213 1 50       10 return( $self->new_null ) if( !$root->can( 'uri' ) );
214 1 50       8 return( $root->uri ) if( $root->uri );
215 1         991 my $nodes = $self->find( 'base' );
216 1 50       5 return( $self->new_null ) if( $nodes->is_empty );
217 1         22 my $node = $nodes->first;
218 1 50       106 return( $self->new_null ) if( !$node );
219 1 50       12 return( $self->new_null ) if( !$node->attributes->has( 'href' ) );
220 1         687 my $uri = $node->attributes->get( 'href' );
221 1 50 33     643 return( $self->new_null ) if( !defined( $uri ) || !CORE::length( "$uri" ) );
222 1         12 return( $self->_set_get_uri( 'uri', $uri ) );
223             },
224             set => sub
225             {
226 0     0   0 my $self = shift( @_ );
227 0         0 my $uri = shift( @_ );
228 0         0 my $root = $self->root;
229 0 0       0 return( $self->new_null ) if( !$root );
230 0         0 my $nodes = $root->find( 'base' );
231 0         0 my $base;
232 0 0       0 if( $nodes->is_empty )
233             {
234 0   0     0 $base = $root->createElement( 'base' ) || return( $self->error( $root->pass_error ) );
235 0   0     0 my $head = $root->find( 'head' )->first ||
236             return( $self->error( "No base uri can be set, because there is no head element in this document." ) );
237 0         0 $head->appendChild( $base );
238             }
239             else
240             {
241 0         0 $base = $nodes->first;
242             }
243 0 0       0 $base->href( $uri ) || return( $self->error( $base->pass_error ) );
244 0         0 return( $uri );
245             },
246 1     1 1 5206 }, @_ ) ); }
247              
248 5     5 1 1978 sub childNodes { return( shift->children ); }
249              
250             sub cloneNode
251             {
252 1     1 1 566 my $self = shift( @_ );
253 1         11 my $clone = $self->clone;
254 1         13 $clone->parent( undef );
255 1         23 return( $clone );
256             }
257              
258             sub compareDocumentPosition
259             {
260 10     10 1 8483 my $self = shift( @_ );
261 10   50     141 my $elem = shift( @_ ) || return( $self->error( "No element was provided to append." ) );
262 10 50       60 return( $self->error( "Element provided (", overload::StrVal( $elem ), ") is actually not an HTML element." ) ) if( !$self->_is_a( $elem => 'HTML::Object::Element' ) );
263             # 0 - Elements are identical.
264             # -> DOCUMENT_POSITION_IDENTICAL
265             # 1 - No relationship, both nodes are in different documents or different trees in the same document.
266             # -> DOCUMENT_POSITION_DISCONNECTED
267             # 2 - The specified node precedes the current node.
268             # otherNode precedes the node in either a pre-order depth-first traversal of a tree containing both (e.g., as an ancestor or previous sibling or a descendant of a previous sibling or previous sibling of an ancestor) or (if they are disconnected) in an arbitrary but consistent ordering.
269             # -> DOCUMENT_POSITION_PRECEDING
270             # 4 - The specified node follows the current node.
271             # The otherNode follows the node in either a pre-order depth-first traversal of a tree containing both (e.g., as a descendant or following sibling or a descendant of a following sibling or following sibling of an ancestor) or (if they are disconnected) in an arbitrary but consistent ordering.
272             # -> DOCUMENT_POSITION_FOLLOWING
273             # 8 - The otherNode is an ancestor of / contains the current node.
274             # -> DOCUMENT_POSITION_CONTAINS
275             # 16 - The otherNode is a descendant of / contained by the node.
276             # -> DOCUMENT_POSITION_CONTAINED_BY
277             # 32 - The specified node and the current node have no common container node or the two nodes are different attributes of the same node.
278             # -> DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
279            
280             # "If the two nodes being compared are the same node, then no flags are set on the return."
281             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
282 10 100       480 return(0) if( Scalar::Util::refaddr( $self ) eq Scalar::Util::refaddr( $elem ) );
283             # Current object and other element are both attributes of the same element (ownerElement)
284             # "If neither of the two determining node is a child node and nodeType is the same for both determining nodes, then an implementation-dependent order between the determining nodes is returned."
285             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
286 9 100 66     75 if( $self->nodeType == ATTRIBUTE_NODE && $elem->nodeType == ATTRIBUTE_NODE )
287             {
288             # 2 attributes of the same node
289 1 50 33     7 return(32) if( $self->ownerElement && $self->ownerElement eq $elem->ownerElement );
290             }
291            
292 8         40 my $parent = $self->parent;
293 8         198 my $parent2 = $elem->parent;
294             # "If neither of the two determining node is a child node and one determining node has a greater value of nodeType than the other, then the corresponding node precedes the other."
295             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
296 8 0 33     257 if( !$parent && !$parent2 )
297             {
298 0 0       0 return( $self->nodeType < $elem->nodeType ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING );
299             }
300            
301 8         53 my $root = $self->root;
302 8         56 my $root2 = $elem->root;
303             # Both elements are in different documents
304 8 100       54 if( $root ne $root2 )
305             {
306 1         5 return( DOCUMENT_POSITION_DISCONNECTED );
307             }
308            
309 7         23 my $bit = 0;
310 7         52 my $lineage = $self->lineage;
311 7         54 my $lineage2 = $elem->lineage;
312 7         63 my $prev_siblings = $self->left;
313 7         1424 my $next_siblings = $self->right;
314 7         11392 my $seen = {};
315 7         929 my $crawl;
316             $crawl = sub
317             {
318 99     99   437 my $kid = shift( @_ );
319 99         180 my $addr = Scalar::Util::refaddr( $kid );
320 99 50       387 return if( ++$seen->{ $addr } > 1 );
321 99         350 my $children = $kid->children;
322 99         7681 foreach( @$children )
323             {
324 58 100 33     572 if( $_->can( 'eid' ) &&
      66        
      100        
325             defined( $_->eid ) &&
326             defined( $elem->eid ) &&
327             $_->eid eq $elem->eid )
328             {
329 2         10 return( $_ );
330             }
331 56 50       169 if( my $e = $crawl->( $_ ) )
332             {
333 0         0 return( $e );
334             }
335             }
336 97         341 return;
337 7         61 };
338            
339             # Check if our parent is among the other element's parents
340 7         44 my $parent_pos = $lineage2->pos( $parent );
341             # Check if the other element's parent is among our parents
342 7         230 my $parent2_pos = $lineage->pos( $parent2 );
343             # Then check their position, if found
344 7 100 100     206 if( defined( $parent_pos ) && defined( $parent2_pos ) )
    100          
    50          
345             {
346 2 50       17 if( $parent_pos > $parent2_pos )
    50          
347             {
348 0         0 $bit |= DOCUMENT_POSITION_FOLLOWING;
349             }
350             elsif( $parent2_pos > $parent_pos )
351             {
352 0         0 $bit |= DOCUMENT_POSITION_PRECEDING;
353             }
354             else
355             {
356             }
357             }
358             elsif( defined( $parent_pos ) )
359             {
360 4         14 $bit |= DOCUMENT_POSITION_FOLLOWING;
361             }
362             elsif( defined( $parent2_pos ) )
363             {
364 1         4 $bit |= DOCUMENT_POSITION_PRECEDING;
365             }
366             # Otherwise neither our parent or the other's parent is in either lineage
367             else
368             {
369             }
370            
371 7 50 33     45 if( $lineage->intersection( $lineage2 )->is_empty &&
372             $lineage2->intersection( $lineage )->is_empty )
373             {
374 0         0 $bit |= DOCUMENT_POSITION_DISCONNECTED;
375             }
376             else
377             {
378             }
379             # Check for the other node in:
380             # 1) ancestor
381             # 2) previous sibling
382             # 3) descendant of previous sibling
383             # 4) previous sibling of an ancestor
384            
385             # "If one of the nodes being compared contains the other node, then the container precedes the contained node, and reversely the contained node follows the container."
386             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
387 7 100       5966 if( $lineage->has( $elem ) )
    100          
388             {
389 1         49 $bit |= DOCUMENT_POSITION_PRECEDING;
390 1         3 $bit |= DOCUMENT_POSITION_CONTAINS;
391             }
392             # check previous sibling
393             elsif( $prev_siblings->has( $elem ) )
394             {
395 1         82 $bit |= DOCUMENT_POSITION_PRECEDING;
396             }
397             else
398             {
399             # check for descendant of previous sibling
400 5         413 $seen = {};
401 5         21 foreach( @$prev_siblings )
402             {
403 12 100       32 if( my $e = $crawl->( $_ ) )
404             {
405 1         3 $bit |= DOCUMENT_POSITION_PRECEDING;
406 1         3 last;
407             }
408             }
409              
410             # no luck so far. Checking previous sibling of an ancestor
411 5 100       32 if( !( $bit & DOCUMENT_POSITION_PRECEDING ) )
412             {
413             # Go through each ancestor
414 4         14 foreach( @$lineage )
415             {
416             # then get its previous siblings
417 10         432 my $ancestor_siblings = $_->left;
418             # and check if the other element is one of them
419 10 50 33     1344 if( $ancestor_siblings && $ancestor_siblings->has( $elem ) )
420             {
421 0         0 $bit |= DOCUMENT_POSITION_PRECEDING;
422 0         0 last;
423             }
424             }
425             }
426             }
427            
428             # still no luck, check
429             # 1) descendants
430             # 2) following sibling
431             # 3) a descendant of a following sibling
432             # 4) following sibling of an ancestor
433 7         194 $seen = {};
434             # e.g. a child or an attribute of our element:
435             # "when comparing an element against its own attribute or child, the element node precedes its attribute node and its child node, which both follow it."
436             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
437 7 100       31 if( $lineage2->has( $self ) )
    100          
    50          
438             {
439 2         66 $bit |= ( DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING )
440             }
441             # Now check for following siblings
442             elsif( $next_siblings->has( $elem ) )
443             {
444 1         60 $bit |= DOCUMENT_POSITION_FOLLOWING;
445             }
446             # "If one of the nodes being compared contains the other node, then the container precedes the contained node, and reversely the contained node follows the container."
447             # <https://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
448             # We look deeper than our direct child
449             elsif( my $e = $crawl->( $self ) )
450             {
451 0         0 $bit |= ( DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING )
452             }
453             # check for a descendant of a following sibling
454             else
455             {
456 4         20 $seen = {};
457 4         18 foreach( @$next_siblings )
458             {
459 27 100       54 if( my $e = $crawl->( $_ ) )
460             {
461 1         3 $bit |= DOCUMENT_POSITION_FOLLOWING;
462 1         2 last;
463             }
464             }
465            
466             # no luck so far. Checking previous sibling of an ancestor
467 4 100       23 if( !( $bit & DOCUMENT_POSITION_FOLLOWING ) )
468             {
469             # Go through each ancestor
470 2         8 foreach( @$lineage )
471             {
472             # then get its following siblings
473 5         639 my $ancestor_siblings = $_->right;
474             # and check if the other element is one of them
475 5 50 66     5774 if( $ancestor_siblings && $ancestor_siblings->has( $elem ) )
476             {
477 0         0 $bit |= DOCUMENT_POSITION_PRECEDING;
478 0         0 last;
479             }
480             }
481             }
482             }
483 7         37 return( $bit );
484             }
485              
486             sub contains
487             {
488 2     2 1 5060 my $self = shift( @_ );
489 2   50     12 my $elem = shift( @_ ) || return( $self->error( "No element was provided to append." ) );
490 2 50       9 return( $self->error( "Element provided (", overload::StrVal( $elem ), ") is actually not an HTML element." ) ) if( !$self->_is_a( $elem => 'HTML::Object::Element' ) );
491             # Object and comparison object are the same
492 2 50       94 return(1) if( Scalar::Util::refaddr( $self ) eq Scalar::Util::refaddr( $elem ) );
493 2         7 my $found = 0;
494 2         5 my $seen = {};
495 2         4 my $traverse;
496             $traverse = sub
497             {
498 40     40   65 my $e = shift( @_ );
499 40 50 33     581 return if( !defined( $e ) || !CORE::length( $e ) );
500 40         91 my $addr = Scalar::Util::refaddr( $e );
501 40 50       98 return if( CORE::exists( $seen->{ $addr } ) );
502 40         100 $seen->{ $addr }++;
503             $e->children->foreach(sub
504             {
505 40 100       7557 if( $_->eid eq $elem->eid )
506             {
507 2         8 $found++;
508 2         15 return;
509             }
510 38         92 return( $traverse->( $_ ) );
511 40         118 });
512 2         15 };
513 2         8 $traverse->( $self );
514 2         428 return( $found );
515             }
516              
517             # Takes a selector; or
518             # Element object
519             sub find
520             {
521 19     19 1 41177 my $self = shift( @_ );
522 19         42 my $this = shift( @_ );
523 19         92 my $opts = $self->_get_args_as_hash( @_ );
524 19         342 my $results = $self->new_array;
525            
526 19 50 33     443 if( ref( $this ) && $self->_is_object( $this ) && $this->isa( 'HTML::Object::DOM::Element' ) )
      33        
527             {
528 0         0 my $a = $self->new_array( [ $this ] );
529 0         0 my $lookup;
530             $lookup = sub
531             {
532 0     0   0 my $kids = shift( @_ );
533             $kids->foreach(sub
534             {
535 0         0 my $child = shift( @_ );
536             $a->foreach(sub
537             {
538 0         0 my $candidate = shift( @_ );
539 0 0       0 if( $child->eid eq $candidate->eid )
540             {
541 0         0 $results->push( $child );
542             # We've added this child. Move to next child.
543 0         0 return( 1 );
544             }
545 0         0 });
546 0 0       0 if( $child->children->length > 0 )
547             {
548 0         0 $lookup->( $child->children );
549             }
550 0         0 });
551 0         0 };
552             # Wether this is a collection or just an element object, we check our children
553 0         0 $lookup->( $self->children );
554             }
555             # I am expecting an xpath value
556             else
557             {
558 19 0 0     69 if( ref( $this ) &&
      33        
559             (
560             !overload::Overloaded( $this ) ||
561             ( overload::Overloaded( $this ) && !overload::Method( $this, '""' ) )
562             ) )
563             {
564 0         0 return( $self->error( "I was expecting an xpath string, but instead I got '$this'." ) );
565             }
566 19   50     135 my $xpath = $self->_xpath_value( $this, $opts ) || return( $self->pass_error );
567             # $self->children->foreach(sub
568             # {
569             # my $child = shift( @_ );
570             # return(1) if( !$child->isElementNode );
571             # # Propagate debug value
572             # $child->debug( $self->debug );
573             # try
574             # {
575             # my @nodes = $child->findnodes( $xpath );
576             # # $self->messagef( 4, "%d nodes found under child element '$child' whose tag is '%s' -> '%s'", scalar( @nodes ), $child->tag, join( "', '", map( overload::StrVal( $_ ), @nodes ) ) );
577             # $results->push( @nodes );
578             # }
579             # catch( $e )
580             # {
581             # warn( "Error while calling findnodes on element id \"", $_->id, "\" and tag \"", $_->tag, "\": $e\n" );
582             # }
583             # });
584 19 50 33     52 try
  19         30  
  19         24  
  19         83  
  0         0  
  19         27  
  19         46  
  19         40  
585 19     19   33 {
586 19         181 my @nodes = $self->findnodes( $xpath );
587 19         247 $results->push( @nodes );
588             }
589 19 0 50     115 catch( $e )
  19 0 33     186  
  19 0       68  
  19 0       39  
  19 0       25  
  19 0       31  
  19 0       30  
  19 0       81  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  19         71  
  0         0  
  19         37  
  0         0  
  0         0  
  19         70  
  19         71  
  19         53  
  19         58  
  0         0  
  0         0  
  0         0  
  0         0  
590 0     0   0 {
591             # warn( "Error while calling findnodes on element id \"", ( $self->id // '' ), "\" and tag \"", ( $self->tag // '' ), "\": $e\n" );
592 0   0     0 warn( "Error while calling findnodes on element with tag \"", ( $self->tag // '' ), "\": $e\n" );
593 29 0 0 29   248 }
  29 0 0     99  
  29 0 33     130431  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 33     0  
  0 0 33     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  19 0       56  
  0 0       0  
  19 0       719  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  19         79  
  0         0  
  0         0  
  0         0  
  0         0  
  19         76  
594             }
595 19         116 return( $results );
596             }
597              
598             sub find_xpath
599             {
600 0     0 1 0 my( $self, $path ) = @_;
601 0         0 return( $self->xp->find( $path, $self ) );
602             }
603              
604             sub findnodes
605             {
606 19     19 1 55 my( $self, $path ) = @_;
607 19         153 return( $self->xp->findnodes( $path, $self ) );
608             }
609              
610             sub findnodes_as_string
611             {
612 0     0 1 0 my( $self, $path ) = @_;
613 0         0 return( $self->xp->findnodes_as_string( $path, $self ) );
614             }
615              
616             sub findnodes_as_strings
617             {
618 0     0 1 0 my( $self, $path ) = @_;
619 0         0 return( $self->xp->findnodes_as_strings( $path, $self ) );
620             }
621              
622             sub findvalue
623             {
624 0     0 1 0 my( $self, $path ) = @_;
625 0         0 return( $self->xp->findvalue( $path, $self ) );
626             }
627              
628             sub findvalues
629             {
630 0     0 1 0 my( $self, $path ) = @_;
631 0         0 return( $self->xp->findvalues( $path, $self ) );
632             }
633              
634             # Note: Property
635 6     6 1 2044 sub firstChild { return( shift->nodes->first ); }
636              
637             sub getAttributes
638             {
639 36     36 1 77 my $self = shift( @_ );
640 36         91 my $rank = 0;
641             my $a = $self->attributes_sequence->map(sub
642             {
643 32     32   12618 return( $self->new_attribute(
644             name => $_,
645             element => $self,
646             rank => $rank++,
647             value => $self->attributes->get( $_ ),
648             ) );
649 36         221 });
650 36 50       13851 return( wantarray() ? $a->list : $a );
651             }
652              
653             sub getChildNodes
654             {
655 553     553 1 806 my $self = shift( @_ );
656 553         1479 my $nodes = $self->nodes;
657 553 100       32301 return( wantarray() ? $nodes->list : $nodes );
658             }
659              
660 0     0 1 0 sub getElementById { return; }
661              
662             # sub getFirstChild { return; }
663 1     1 1 8 sub getFirstChild { return( shift->nodes->first ); }
664              
665             # sub getLastChild { return; }
666 0     0 1 0 sub getLastChild { return( shift->nodes->last ); }
667              
668 0     0 1 0 sub getName { return; }
669              
670 75     75 1 340 sub getNextSibling { return( shift->nextSibling ); }
671              
672 0     0 1 0 sub getParentNode { return( shift->parent ); }
673              
674 98     98 1 435 sub getPreviousSibling { return( shift->previousSibling ); }
675              
676             sub getRootNode
677             {
678 8     8 1 1882 my $self = shift( @_ );
679             # The parent of root is a HTML::Object::Root
680             # that helps getting the tree to mimic a DOM tree
681             # return( $self->root->getParentNode );
682 8         71 return( $self->root );
683             }
684              
685 1     1 1 6 sub hasChildNodes { return( !shift->nodes->is_empty ); }
686              
687             sub insertAfter
688             {
689 0     0 1 0 my $self = shift( @_ );
690 0   0     0 my $e = shift( @_ ) || return( $self->error( "No node was provided to insert." ) );
691 0 0       0 return( $self->error({
692             message => "Node provided (" . overload::StrVal( $e ) . ") is not an HTML::Object::DOM::Node.",
693             class => 'HTML::Object::TypeError',
694             }) ) if( !$self->_is_a( $e => 'HTML::Object::DOM::Node' ) );
695 0         0 my $refNode = shift( @_ );
696 0         0 my $nodes = $self->nodes;
697 0         0 my $pos;
698 0 0       0 if( !defined( $refNode ) )
699             {
700 0         0 $pos = $nodes->size;
701             }
702             else
703             {
704 0 0       0 return( $self->error({
705             message => "Reference node provided (" . overload::StrVal( $refNode ) . ") is not an HTML::Object::DOM::Node.",
706             class => 'HTML::Object::TypeError',
707             }) ) if( !$self->_is_a( $refNode => 'HTML::Object::DOM::Node' ) );
708 0         0 $pos = $nodes->pos( $refNode );
709 0 0       0 return( $self->error({
710             message => "Reference node provided (" . overload::StrVal( $refNode ) . ") is not among the document nodes.",
711             class => 'HTML::Object::HierarchyRequestError',
712             }) ) if( !defined( $pos ) );
713 0 0       0 return( $self->error( "Somehow, position for the reference node returned $pos, but I expected an integer equal or above 0" ) ) if( $pos < 0 );
714             }
715 0 0       0 my $list = $self->new_array( $self->_is_a( $e => 'HTML::Object::DOM::DocumentFragment' ) ? $e->children : $e );
716 0         0 $nodes->splice( $pos + 1, 0, $list->list );
717             $list->foreach(sub
718             {
719 0     0   0 $_->detach;
720 0         0 $_->parent( $self );
721 0         0 });
722 0         0 $self->reset(1);
723 0 0       0 if( $self->_is_a( $e => 'HTML::Object::DOM::DocumentFragment' ) )
724             {
725 0         0 $e->children->reset;
726             }
727 0         0 return( $e );
728             }
729              
730             sub insertBefore
731             {
732 2     2 1 32 my $self = shift( @_ );
733 2   50     11 my $e = shift( @_ ) || return( $self->error( "No node was provided to insert." ) );
734 2 50       11 return( $self->error({
735             message => "Node provided (" . overload::StrVal( $e ) . ") is not an HTML::Object::DOM::Node.",
736             class => 'HTML::Object::TypeError',
737             }) ) if( !$self->_is_a( $e => 'HTML::Object::DOM::Node' ) );
738 2         70 my $refNode = shift( @_ );
739 2         8 my $nodes = $self->nodes;
740 2         189 my $pos;
741 2 50       11 if( !defined( $refNode ) )
742             {
743 0         0 $pos = $nodes->length; # set the position to size + 1 to make it equivalent to a push()
744             }
745             else
746             {
747 2 50       9 return( $self->error({
748             message => "Reference node provided (" . overload::StrVal( $refNode ) . ") is not an HTML::Object::DOM::Node.",
749             class => 'HTML::Object::TypeError',
750             }) ) if( !$self->_is_a( $refNode => 'HTML::Object::DOM::Node' ) );
751 2         75 $pos = $nodes->pos( $refNode );
752 2 50       57 return( $self->error({
753             message => "Reference node provided (" . overload::StrVal( $refNode ) . ") is not among the document nodes.",
754             class => 'HTML::Object::HierarchyRequestError',
755             }) ) if( !defined( $pos ) );
756 2 50       10 return( $self->error( "Somehow, position for the reference node returned $pos, but I expected an integer equal or above 0" ) ) if( $pos < 0 );
757             }
758 2 50       9 my $list = $self->new_array( $self->_is_a( $e => 'HTML::Object::DOM::DocumentFragment' ) ? $e->children : $e );
759 2         124 $nodes->splice( $pos, 0, $list->list );
760             $list->foreach(sub
761             {
762 2     2   71 $_->detach;
763 2         7 $_->parent( $self );
764 2         101 });
765 2         53 $self->reset(1);
766 2 50       11 if( $self->_is_a( $e => 'HTML::Object::DOM::DocumentFragment' ) )
767             {
768 0         0 $e->children->reset;
769             }
770 2         80 return( $e );
771             }
772              
773 0     0 1 0 sub isAttributeNode { return(0); }
774              
775 0     0 1 0 sub isCommentNode { return(0); }
776              
777             # Note: Property
778             sub isConnected
779             {
780 1     1 1 3 my $self = shift( @_ );
781 1         5 my $root = $self->root;
782 1 50       10 return( $self->false ) if( !$root );
783 1 50       14 return( $root->isa( 'HTML::Object::Document' ) ? $self->true : $self->false );
784             }
785              
786             sub isDefaultNamespace
787             {
788 0     0 1 0 my $self = shift( @_ );
789 0         0 my $uri = shift( @_ );
790 0 0       0 return( $self->error( "No namespace URI was provided to check." ) ) if( !defined( $uri ) );
791 0 0       0 if( $self->tag eq 'svg' )
792             {
793 0         0 return( $self->attributes->get( 'xmlns' ) eq $uri );
794             }
795             else
796             {
797 0         0 return( $uri eq "" );
798             }
799             }
800              
801             sub isEqualNode
802             {
803 1     1 1 19 my $self = shift( @_ );
804 1   50     5 my $e = shift( @_ ) || return( $self->error( "No html element was provided to check for equality." ) );
805 1 50       7 return( $self->error( "Element provided (", overload::StrVal( $e ), ") is not an HTML::Object::Element." ) ) if( !$self->_is_a( $e => 'HTML::Object::Element' ) );
806 1 50       39 return(0) if( $self->nodeType != $e->nodeType );
807 1 50       6 return(0) if( $self->children->length != $e->children->length );
808 1 50       40104 return(0) if( $self->attributes_sequence->join( ',' ) ne $e->attributes_sequence->join( ',' ) );
809 1         613 my $failed = 0;
810             $self->attributes_sequence->foreach(sub
811             {
812 3     3   645 my $v1 = $self->attributes->get( $_ );
813 3         1866 my $v2 = $e->attributes->get( $_ );
814 3 50       1838 if( $v1 ne $v2 )
815             {
816 0         0 $failed++;
817 0         0 return;
818             }
819 1         27 });
820 1 50       28 return(0) if( $failed );
821 1         8 return(1);
822             }
823              
824 26     26 1 158 sub isElementNode { return(0); }
825              
826 0     0 1 0 sub isNamespaceNode { return(0); }
827              
828 0     0 1 0 sub isPINode { return(0); }
829              
830 0     0 1 0 sub isProcessingInstructionNode { return(0); }
831              
832             sub isSameNode
833             {
834 107 50   107 1 21171 return(0) if( !defined( $_[1] ) );
835 107 100       479 if( !$_[0]->_is_a( $_[1] => 'HTML::Object::DOM::Node' ) )
836             {
837 44 50       764 warn( "Value provided ($_[1]) is not a reference.\n" ) if( $_[0]->_warnings_is_enabled );
838 44         1176 return(0);
839             }
840 63         2562 return( Scalar::Util::refaddr( $_[0] ) eq Scalar::Util::refaddr( $_[1] ) );
841             }
842              
843 0     0 1 0 sub isTextNode { return(0); }
844              
845             # Node: Property
846 1     1 1 8 sub lastChild { return( shift->nodes->last ); }
847              
848             sub lookupNamespaceURI
849             {
850 1     1 1 511 my $self = shift( @_ );
851 1   50     9 my $prefix = shift( @_ ) || return( '' );
852 0 0       0 return( XML_DEFAULT_NAMESPACE ) if( lc( $prefix ) eq 'xml' );
853 0         0 return( '' );
854             }
855              
856 1     1 1 5 sub lookupPrefix { return; }
857              
858             sub new_closing
859             {
860 0     0 1 0 my $self = shift( @_ );
861 0 0       0 $self->_load_class( 'HTML::Object::DOM::Closing' ) || return( $self->pass_error );
862 0   0     0 my $e = HTML::Object::DOM::Closing->new( @_ ) ||
863             return( $self->pass_error( HTML::Object::DOM::Closing->error ) );
864 0         0 return( $e );
865             }
866              
867             sub new_comment
868             {
869 0     0 1 0 my $self = shift( @_ );
870 0 0       0 $self->_load_class( 'HTML::Object::DOM::Comment' ) || return( $self->pass_error );
871 0   0     0 my $e = HTML::Object::DOM::Comment->new( @_ ) ||
872             return( $self->pass_error( HTML::Object::DOM::Comment->error ) );
873 0         0 return( $e );
874             }
875              
876             sub new_element
877             {
878 0     0 1 0 my $self = shift( @_ );
879 0 0       0 $self->_load_class( 'HTML::Object::DOM::Element' ) || return( $self->pass_error );
880 0   0     0 my $e = HTML::Object::DOM::Element->new( @_ ) ||
881             return( $self->pass_error( HTML::Object::DOM::Element->error ) );
882 0         0 return( $e );
883             }
884              
885             sub new_parser
886             {
887 0     0 1 0 my $self = shift( @_ );
888 0 0       0 $self->_load_class( 'HTML::Object::DOM' ) || return( $self->pass_error );
889 0   0     0 my $p = HTML::Object::DOM->new( debug => $self->debug ) ||
890             return( $self->pass_error( HTML::Object::DOM->error ) );
891 0         0 return( $p );
892             }
893              
894             sub new_text
895             {
896 0     0 1 0 my $self = shift( @_ );
897 0 0       0 $self->_load_class( 'HTML::Object::DOM::Text' ) || return( $self->pass_error );
898 0   0     0 my $e = HTML::Object::DOM::Text->new( @_ ) ||
899             return( $self->pass_error( HTML::Object::DOM::Text->error ) );
900 0         0 return( $e );
901             }
902              
903             # Note: Property
904 93     93 1 10416 sub nextSibling { return( shift->right->first ); }
905              
906             # Note: Property
907             sub nodeName
908             {
909 64     64 1 31348 my $self = shift( @_ );
910 64   33     245 my $class = ref( $self ) || $self;
911 64         451 my $map =
912             {
913             Comment => '#comment',
914             Document => '#document',
915             DocumentFragment => '#documentFragment',
916             Space => '#space',
917             Text => '#text',
918             };
919 64         363 my $type = [split( /::/, $class )]->[-1];
920 64 100       457 return( CORE::exists( $map->{ $type } ) ? $map->{ $type } : $self->tag );
921             }
922              
923             # For any nodes except for Document, nodes are the same as children
924 540     540 1 1814 sub nodes { return( shift->children( @_ ) ); }
925              
926             # ELEMENT_NODE 1
927             # ATTRIBUTE_NODE 2
928             # TEXT_NODE 3
929             # CDATA_SECTION_NODE 4
930             # PROCESSING_INSTRUCTION_NODE 7
931             # COMMENT_NODE 8
932             # DOCUMENT_NODE 9
933             # DOCUMENT_TYPE_NODE 10
934             # DOCUMENT_FRAGMENT_NODE 11
935             # <https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType>
936             # Note: Property
937             sub nodeType
938             {
939 495     495 1 5227 my $self = shift( @_ );
940 495         1641 my $map =
941             [
942             # Document also inherits from Element, so we test it first
943             'HTML::Object::DOM::Document' => DOCUMENT_NODE,
944             'HTML::Object::DOM::Element' => ELEMENT_NODE,
945             'HTML::Object::DOM::Attribute' => ATTRIBUTE_NODE,
946             'HTML::Object::DOM::Text' => TEXT_NODE,
947             # Nothing for CData (4) or Processing Instruction (7)
948             'HTML::Object::DOM::Comment' => COMMENT_NODE,
949             'HTML::Object::DOM::Declaration' => DOCUMENT_TYPE_NODE,
950             'HTML::Object::DOM::DocumentFragment' => DOCUMENT_FRAGMENT_NODE,
951             # We treat space separately, but the DOM normally treats it as text
952             'HTML::Object::DOM::Space' => SPACE_NODE,
953             ];
954            
955             # return( $map->{ [split( /::/, ( ref( $self ) || $self ) )]->[-1] } );
956 495         1196 for( my $i = 0; $i < scalar( @$map ); $i += 2 )
957             {
958 2466         2901 my $class = $map->[$i];
959 2466         2746 my $const = $map->[$i + 1];
960 2466 100       7687 if( $self->isa( $class ) )
961             {
962 495         1442 return( $const );
963             }
964             }
965 0         0 return;
966             }
967              
968             # Note: Property
969 3     3 1 48 sub nodeValue { return; }
970              
971             sub normalize
972             {
973 1     1 1 4 my $self = shift( @_ );
974 1         3 my $process;
975             $process = sub
976             {
977 1     1   3 my $e = shift( @_ );
978 1         7 my $new = $self->new_array;
979 1         23 my $found = '';
980             $e->children->foreach(sub
981             {
982 4         444 my $kid = shift( @_ );
983             # text
984 4 100       73 if( $kid->nodeType == 3 )
985             {
986 3 100       12 if( $found )
987             {
988             # merge it with previous text element and skip it
989 1         8 $found->value->append( $kid->value->scalar );
990             # next
991 1         716 return(1);
992             }
993             # We found a text element. Save it and merge it with siblings, if any
994             else
995             {
996 2         4 $found = $kid;
997             }
998             }
999             else
1000             {
1001 1         4 $found = '';
1002             }
1003 3         10 $new->push( $kid );
1004 1         8 });
1005             # Set the new children elements array object
1006 1         146 $e->children( $new );
1007 1         7 };
1008 1         5 $process->( $self );
1009 1         298 return( $self );
1010             }
1011              
1012             # Note: Property
1013             sub ownerDocument
1014             {
1015 1     1 1 4 my $self = shift( @_ );
1016 1         6 my $root = $self->root;
1017 1         6 return( $root );
1018             }
1019              
1020 2329     2329 1 3183989 sub parent { return( shift->_set_get_object_without_init( 'parent', 'HTML::Object::DOM::Node', @_ ) ); }
1021              
1022             # Note: Property
1023 10     10 1 544 sub parentNode { return( shift->parent ); }
1024              
1025             # Note: Property
1026             # "Returns an Element that is the parent of this node. If the node has no parent, or if that parent is not an Element, this property returns null."
1027             sub parentElement
1028             {
1029 5     5 1 1155 my $self = shift( @_ );
1030 5         24 my $parent = $self->parent;
1031 5 100 66     146 return( $parent ) if( defined( $parent ) && $self->_is_a( $parent => 'HTML::Object::DOM::Element' ) );
1032 1         17 return;
1033             }
1034              
1035             # Note: Property
1036             # The last element of our siblings, is our first element on our left
1037 103     103 1 1492 sub previousSibling { return( shift->left->last ); }
1038              
1039             sub removeChild
1040             {
1041 2     2 1 2064 my $self = shift( @_ );
1042 2   50     12 my $elem = shift( @_ ) || return( $self->error({
1043             message => "No element was provided to remove.",
1044             class => 'HTML::Object::TypeError'
1045             }) );
1046 2 50       13 return( $self->error({
1047             message => "Element provided (" . overload::StrVal( $elem ) . ") is actually not an HTML element.",
1048             class => 'HTML::Object::TypeError'
1049             }) ) if( !$self->_is_a( $elem => 'HTML::Object::Element' ) );
1050 2         96 my $nodes = $self->nodes;
1051 2         142 my $pos = $nodes->pos( $elem );
1052 2 100       107 return( $self->error({
1053             message => "Node to remove was not found among the current object's children.",
1054             class => 'HTML::Object::NotFoundError',
1055             }) ) if( !defined( $pos ) );
1056 1         9 $nodes->splice( $pos, 1 );
1057 1         116 $elem->parent( undef );
1058             # Remove the closing tag also, if there are any.
1059 1 50       32 if( my $close = $elem->close_tag )
1060             {
1061 1 50       31 if( defined( $pos = $nodes->pos( $close ) ) )
1062             {
1063 0         0 $nodes->splice( $pos, 1 );
1064             }
1065             }
1066 1         31 $self->reset(1);
1067 1         3 return( $elem );
1068             }
1069              
1070             sub replaceChild
1071             {
1072 1     1 1 36 my $self = shift( @_ );
1073 1 50       6 return( $self->error({
1074             message => sprintf( "At least 2 arguments are required, but only %d provided.", scalar( @_ ) ),
1075             class => 'HTML::Object::TypeError',
1076             }) ) if( scalar( @_ ) < 2 );
1077 1         5 my( $new, $old ) = @_;
1078 1         6 my $new_parent = $new->parent;
1079 1         34 my $old_parent = $old->parent;
1080 1         29 my $parent = $self->parent;
1081 1 50 33     48 if( !$parent )
    50 33        
    50 33        
    50 33        
    50 33        
    50 33        
    50 0        
    50 33        
    50 33        
    50 33        
    50 0        
      0        
      33        
1082             {
1083 0         0 return( $self->error({
1084             message => "Current node does not have any parent.",
1085             class => 'HTML::Object::HierarchyRequestError',
1086             }) );
1087             }
1088             elsif( !$old_parent )
1089             {
1090 0         0 return( $self->error({
1091             message => "Old node provided does not have any parent",
1092             class => 'HTML::Object::HierarchyRequestError',
1093             }) );
1094             }
1095             elsif( $old_parent ne $self )
1096             {
1097 0         0 return( $self->error({
1098             message => "Old node parent is not the current node.",
1099             class => 'HTML::Object::NotFoundError',
1100             }) );
1101             }
1102             elsif( !$self->_is_a( $old_parent => 'HTML::Object::DOM::Document' ) &&
1103             !$self->_is_a( $old_parent => 'HTML::Object::DOM::DocumentFragment' ) &&
1104             !$self->_is_a( $old_parent => 'HTML::Object::DOM::Element' ) )
1105             {
1106 0         0 return( $self->error({
1107             message => "Old node's parent is not an HTML::Object::DOM::Document, HTML::Object::DOM::DocumentFragment or HTML::Object::DOM::Element object.",
1108             class => 'HTML::Object::HierarchyRequestError',
1109             }) );
1110             }
1111             elsif( !$self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) &&
1112             !$self->_is_a( $new => 'HTML::Object::DOM::Declaration' ) &&
1113             !$self->_is_a( $new => 'HTML::Object::DOM::Element' ) &&
1114             !$self->_is_a( $new => 'HTML::Object::DOM::CharacterData' ) )
1115             {
1116 0         0 return( $self->error({
1117             message => "New node is not an HTML::Object::DOM::DocumentFragment, HTML::Object::DOM::Declaration, HTML::Object::DOM::Element or HTML::Object::DOM::CharacterData object.",
1118             class => 'HTML::Object::HierarchyRequestError',
1119             }) );
1120             }
1121             elsif( $self->lineage->has( $new ) )
1122             {
1123 0         0 return( $self->error({
1124             message => "New node provided is an ancestor of the current node.",
1125             class => 'HTML::Object::HierarchyRequestError',
1126             }) );
1127             }
1128             elsif( ( $self->_is_a( $new => 'HTML::Object::DOM::Text' ) || $self->_is_a( $new => 'HTML::Object::DOM::Space' ) ) &&
1129             $self->_is_a( $new_parent => 'HTML::Object::DOM::Document' ) )
1130             {
1131 0         0 return( $self->error({
1132             message => "New node is a HTML::Object::DOM::Text or HTML::Object::DOM::Space node and its parent is a HTML::Object::DOM::Document node.",
1133             class => 'HTML::Object::HierarchyRequestError',
1134             }) );
1135             }
1136             elsif( $self->isa( 'HTML::Object::DOM::Declaration' ) && !$self->_is_a( $parent => 'HTML::Object::DOM::Document' ) )
1137             {
1138 0         0 return( $self->error({
1139             message => "Current node is a DocumentType, but its parent is not an HTML::Object::DOM::Document object.",
1140             class => 'HTML::Object::HierarchyRequestError',
1141             }) );
1142             }
1143             elsif( $self->_is_a( $parent => 'HTML::Object::DOM::Document' ) &&
1144             $self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) &&
1145 0     0   0 ( $new->childElementCount > 1 || $new->children->grep(sub{ $self->_is_a( $_ => 'HTML::Object::DOM::Text' ) })->length ) )
1146             {
1147 0         0 return( $self->error({
1148             message => "Current node parent is a HTML::Object::DOM::Document object and new node is a HTML::Object::DOM::DocumentFragment object that has either more than 1 element or has a HTML::Object::DOM::Text node.",
1149             class => 'HTML::Object::HierarchyRequestError',
1150             }) );
1151             }
1152             elsif( $self->_is_a( $parent => 'HTML::Object::DOM::Document' ) &&
1153             $parent->childElementCount > 0 &&
1154             $self->_is_a( $new => 'HTML::Object::DOM::Element' ) &&
1155             # Non-standard addition:
1156             # replacement is not forbidden if the user replace an element that is not an HTML element by an HTML element and there is no HTML element yet or
1157             # the user replace an HTML element by another HTML element
1158             !(
1159             ( $self->_is_a( $old => 'HTML::Object::DOM::Element' ) &&
1160             !$self->_is_a( $old => 'HTML::Object::DOM::Element::HTML' ) &&
1161 0     0   0 $parent->children->grep(sub{ $self->_is_a( $_ => 'HTML::Object::DOM::Element::HTML' ) })->is_empty &&
1162             $self->_is_a( $new => 'HTML::Object::DOM::Element::HTML' ) ) ||
1163             ( $self->_is_a( $old => 'HTML::Object::DOM::Element::HTML' ) &&
1164             $self->_is_a( $new => 'HTML::Object::DOM::Element::HTML' ) )
1165             ) )
1166             {
1167 0         0 return( $self->error({
1168             message => "Attempting to replace a child element in a Document with another non HTML-tag element. Document can have only one Element: the HTML-tag element.",
1169             class => 'HTML::Object::HierarchyRequestError',
1170             }) );
1171             }
1172             elsif( $self->_is_a( $new => 'HTML::Object::DOM::Element' ) &&
1173             $self->_is_a( $old->previousSibling => 'HTML::Object::DOM::Declaration' ) )
1174             {
1175 0         0 return( $self->error({
1176             message => "Attempting to add an Element object before a DocumentType object",
1177             class => 'HTML::Object::HierarchyRequestError',
1178             }) );
1179             }
1180             # We use 'nodes' rather than 'children' so this works well with HTML::Object::DOM::Document
1181 1         310 my $nodes = $self->nodes;
1182 1         66 my $newPos = $nodes->pos( $new );
1183 1         28 my $oldPos = $nodes->pos( $old );
1184 1 50 33     54 if( defined( $newPos ) && !defined( $oldPos ) )
    50          
1185             {
1186 0         0 return( $self->error({
1187             message => "New child already has this parent and old child does not. Please check the order of replaceChild's arguments.",
1188             class => 'HTML::Object::NotFoundError',
1189             }) );
1190             }
1191             elsif( !defined( $oldPos ) )
1192             {
1193 0         0 return( $self->error({
1194             message => "Child to be replaced is not a child of this node",
1195             class => 'HTML::Object::NotFoundError',
1196             }) );
1197             }
1198 1         12 $new->detach;
1199 1 50       5 my $new_array = $self->new_array( $self->_is_a( $new => 'HTML::Object::DOM::DocumentFragment' ) ? $new->children : $new );
1200             $new_array->foreach(sub
1201             {
1202 1 50   1   19 next if( !$self->_is_a( $_ => 'HTML::Object::DOM::Node' ) );
1203 1         31 $_->parent( $self );
1204 1         67 });
1205 1         27 $nodes->splice( $oldPos, 1, $new_array->list );
1206 1         38 $old->parent( undef );
1207 1         25 $self->reset(1);
1208 1         3 return( $old );
1209             }
1210              
1211             sub textContent : lvalue { return( shift->_set_get_callback({
1212             get => sub
1213             {
1214 7     7   4327 my $self = shift( @_ );
1215 7 50       72 return if( $self->isa( 'HTML::Object::DOM::Document' ) );
1216 7 50 33     153 unless( $self->isa( 'HTML::Object::DOM::Comment' ) ||
      33        
1217             $self->isa( 'HTML::Object::DOM::Text' ) ||
1218             $self->isa( 'HTML::Object::DOM::Element' ) )
1219             {
1220 0         0 return;
1221             }
1222 7         63 my $str = $self->as_text;
1223 7         38 return( $str );
1224             },
1225             set => sub
1226             {
1227 9     9   3684 my $self = shift( @_ );
1228 9         20 my $ctx = $_;
1229 9         20 my $arg = shift( @_ );
1230 9 50       78 return if( $self->isa( 'HTML::Object::DOM::Document' ) );
1231 9         22 my $dummy;
1232 9 50 100     162 if( $self->isa( 'HTML::Object::DOM::Comment' ) ||
      66        
1233             $self->isa( 'HTML::Object::DOM::Text' ) ||
1234             $self->isa( 'HTML::Object::DOM::Element' ) )
1235             {
1236 9 100 100     89 if( $self->isa( 'HTML::Object::DOM::Comment' ) ||
1237             $self->isa( 'HTML::Object::DOM::Text' ) )
1238             {
1239 2         13 $self->value( $arg );
1240             }
1241             else
1242             {
1243 7         78 my $e = $self->new_text( value => $arg, parent => $self );
1244 7         62 $self->children->set( $e );
1245             }
1246 9         3740 $self->reset(1);
1247 9         25 $dummy = 1;
1248 9         36 return( $dummy );
1249             }
1250 0         0 return( $dummy );
1251             },
1252 16     16 1 6739 }, @_ ) ); }
1253              
1254             sub trigger
1255             {
1256 2     2 1 1178 my $self = shift( @_ );
1257 2         6 my $type = shift( @_ );
1258 2 50 33     19 return( $self->error({
1259             message => "No event type was provided to trigger.",
1260             class => 'HTML::Object::SyntaxError',
1261             }) ) if( !defined( $type ) || !CORE::length( "$type" ) );
1262 2 50       21 return( $self->error({
1263             message => "Event type provided \"$type\" contains illegal characters. Only alphanuneric, underscore (\"_\") and dash (\"-\") are allowed",
1264             class => 'HTML::Object::TypeError',
1265             }) ) if( $type !~ /^\w[\w\-]*$/ );
1266 2         20 require HTML::Object::Event;
1267 2   50     15 my $evt = HTML::Object::Event->new( $type, @_ ) ||
1268             return( $self->pass_error( HTML::Object::Event->error ) );
1269 2         28 return( $self->dispatchEvent( $evt ) );
1270             }
1271              
1272             sub xp
1273             {
1274 19     19 1 44 my $self = shift( @_ );
1275 19 100       63 unless( $XP )
1276             {
1277 4 50       22 $self->_load_class( 'HTML::Object::XPath' ) || return( $self->pass_error );
1278 4         900 $XP = HTML::Object::XPath->new;
1279             }
1280             # $XP->debug( $self->debug );
1281 19         109 return( $XP );
1282             }
1283              
1284             sub _list_to_nodes
1285             {
1286 0     0   0 my $self = shift( @_ );
1287 0         0 my $list = $self->new_array;
1288 0         0 my $p = $self->new_parser;
1289 0         0 my $prev;
1290 0         0 foreach( @_ )
1291             {
1292 0 0 0     0 if( $self->_is_a( $_ => 'HTML::Object::DOM::Node' ) )
    0 0        
1293             {
1294 0 0       0 if( $self->_is_a( $_ => 'HTML::Object::DOM::DocumentFragment' ) )
1295             {
1296 0         0 my $kids = $_->children->clone;
1297 0         0 $_->children->reset;
1298 0         0 foreach my $kid ( @$kids )
1299             {
1300 0         0 $kid->detach;
1301             }
1302 0         0 $list->push( $kids->list );
1303             }
1304             else
1305             {
1306 0         0 $list->push( $_ );
1307 0         0 $_->detach;
1308             }
1309 0         0 undef( $prev );
1310             }
1311             # HTML string
1312             elsif( !ref( $_ ) || ( ref( $_ ) && overload::Method( $_, '""' ) ) )
1313             {
1314 0 0       0 if( $self->looks_like_html( $_ ) )
1315             {
1316 0   0     0 my $doc = $p->parse_data( $_ ) ||
1317             return( $self->pass_error({ class => 'HTML::Object::TypeError' }) );
1318 0 0       0 $list->push( $doc->children->list ) if( !$doc->children->is_empty );
1319 0         0 undef( $prev );
1320             }
1321             else
1322             {
1323 0 0 0     0 if( defined( $prev ) && $self->_is_a( $prev => 'HTML::Object::DOM::Text' ) )
1324             {
1325 0         0 $prev->value->append( $_ );
1326             }
1327             else
1328             {
1329 0         0 my $e = $self->new_text( value => $_ );
1330 0         0 $list->push( $e );
1331 0         0 $prev = $e;
1332             }
1333             }
1334             }
1335             else
1336             {
1337 0         0 return( $self->error({
1338             message => "Unsupported data provided (" . overload::StrVal( $_ ) . ").",
1339             class => 'HTML::Object::TypeError',
1340             }) );
1341             }
1342             }
1343 0         0 return( $list );
1344             }
1345              
1346             sub _xpath_value
1347             {
1348 19     19   45 my $self = shift( @_ );
1349 19         39 my $this = shift( @_ );
1350 19         55 my $opts = $self->_get_args_as_hash( @_ );
1351 19 50       2272 if( ref( $this ) )
1352             {
1353 0         0 return( $$this );
1354             }
1355             else
1356             {
1357 19 50       84 $self->_load_class( 'HTML::Selector::XPath' ) ||
1358             return( $self->pass_error );
1359 19 50 33     11110 try
  19         36  
  19         31  
  19         93  
  0         0  
  19         33  
  19         65  
  19         46  
1360 19     19   34 {
1361 19         115 return( HTML::Selector::XPath::selector_to_xpath( $this, %$opts ) );
1362             }
1363 19 0 0     103 catch( $e )
  0 0 33     0  
  0 0       0  
  19 0       42  
  19 0       32  
  19 0       31  
  19 0       36  
  19 0       103  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  19         79  
  0         0  
  19         39  
  0         0  
  0         0  
  19         2183  
  19         94  
  19         43  
  19         58  
  0         0  
  0         0  
  0         0  
  0         0  
1364 0     0   0 {
1365 0         0 return( $self->error( "Bad selector \"$this\": $e" ) );
1366 29 0 0 29   253 }
  29 0 0     70  
  29 0 33     4892  
  0 0 33     0  
  0 0 33     0  
  0 0 33     0  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  19 0       65  
  0 0       0  
  19 50       208  
  19 50       133  
  19 50       89  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  19         188  
  0            
  0            
  0            
  0            
  0            
  0            
  0            
1367             }
1368             }
1369              
1370             1;
1371             # NOTE: POD
1372             __END__
1373              
1374             =encoding utf-8
1375              
1376             =head1 NAME
1377              
1378             HTML::Object::DOM::Node - HTML Object DOM Node Class
1379              
1380             =head1 SYNOPSIS
1381              
1382             use HTML::Object::DOM::Node;
1383             my $node = HTML::Object::DOM::Node->new ||
1384             die( HTML::Object::DOM::Node->error, "\n" );
1385              
1386             =head1 VERSION
1387              
1388             v0.2.1
1389              
1390             =head1 DESCRIPTION
1391              
1392             This module implement the properties and methods for HTML DOM nodes. It inherits from L<HTML::Object::EventTarget> and is used by L<HTML::Object::DOM::Element>
1393              
1394             =head1 INHERITANCE
1395              
1396             +-----------------------+ +---------------------------+ +-------------------------+
1397             | HTML::Object::Element | --> | HTML::Object::EventTarget | --> | HTML::Object::DOM::Node |
1398             +-----------------------+ +---------------------------+ +-------------------------+
1399              
1400             =head1 PROPERTIES
1401              
1402             All the following properties can be used as lvalue method as well as regular method. For example with L</baseURI>
1403              
1404             # Get the base uri, if any
1405             my $uri = $e->baseURI;
1406             $e->baseURI = 'https://example.org/some/where';
1407             # or
1408             $e->baseURI( 'https://example.org/some/where' );
1409              
1410             =head2 baseURI
1411              
1412             Normally this is read-only, but in this api, you can set an URI.
1413              
1414             This returns an L<URI> object representing the base URL of the document containing the Node, if any.
1415              
1416             The base URL is determined as follows:
1417              
1418             =over 4
1419              
1420             =item 1. By default, the base URL is the location of the document (as set by L<HTML::Object/parse_url>).
1421              
1422             =item 2. If it is an L<HTML Document|HTML::Object::DOM::Document> and there is a L<<base>|https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base> element in the L<document|HTML::Object::DOM::Document>, the hrefvalue of the first Base element with such an attribute is used instead.
1423              
1424             =item 3. By specifying an uri with L<HTML::Object::DOM::Document/documentURI> or L<HTML::Object::DOM::Document/URL>
1425              
1426             =back
1427              
1428             =head2 childNodes
1429              
1430             Read-only
1431              
1432             This returns an L<array object|Module::Generic::Array> containing all the children of this node (including elements, text and comments). This list being live means that if the children of the Node change, the L<list object|Module::Generic::Array> is automatically updated.
1433              
1434             =head2 firstChild
1435              
1436             Read-only
1437              
1438             This returns an element representing the first direct child element of the element, or C<undef> if the element has no child.
1439              
1440             =head2 isConnected
1441              
1442             Returns a boolean indicating whether or not the element is connected (directly or indirectly) to the context object, i.e. the L<Document object|HTML::Object::Document> in the case of the normal DOM.
1443              
1444             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected>
1445              
1446             =head2 lastChild
1447              
1448             Read-only
1449              
1450             This returns an element representing the last direct child element of the element, or C<undef> if the element has no child.
1451              
1452             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/lastChild>
1453              
1454             =head2 nextSibling
1455              
1456             Read-only
1457              
1458             This returns an element representing the next element in the tree, or C<undef> if there is not such element.
1459              
1460             The next node could also be a L<whitespace|HTML::Object::DOM::Space> or a L<text|HTML::Object::DOM::Text>. If you want to get the next element and not just any node, use L<nextElementSibling|HTML::Object::DOM/nextElementSibling> instead.
1461              
1462             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling>
1463              
1464             =head2 nodeName
1465              
1466             Read-only
1467              
1468             This returns a string containing the name of the element. The structure of the name will differ with the element type. E.g. An L<HTML Element|HTML::Object::Element> will contain the name of the corresponding tag, like 'audio' for an HTML audio element, a L<Text|HTML::Object::Text> element will have the '#text' string, or a L<Document|HTML::Object::Document> element will have the '#document' string.
1469              
1470             For L<HTML element|HTML::Object::DOM::Element>, contrary to the standard specifications, is not the uppercase value of the tag name, but the lowercase value. However, if you really wanted the uppercase value, you could get it quite easily like so:
1471              
1472             $e->nodeName->uc;
1473              
1474             This is because L<HTML::Object::Element/tag> returns a L<scalar object|Module::Generic::Scalar>
1475              
1476             Example:
1477              
1478             This is some html:
1479             <div id="d1">Hello world</div>
1480             <!-- Example of comment -->
1481             Text <span>Text</span>
1482             Text<br/>
1483             <svg height="20" width="20">
1484             <circle cx="10" cy="10" r="5" stroke="black" stroke-width="1" fill="red" />
1485             <hr>
1486             <output id="result">Not calculated yet.</output>
1487              
1488             then, with the script:
1489              
1490             let node = document.getElementsByTagName("body")[0].firstChild;
1491             let result = "Node names are:<br/>";
1492             while (node) {
1493             result += node.nodeName + "<br/>";
1494             node = node.nextSibling
1495             }
1496              
1497             const output = document.getElementById("result");
1498             output.innerHTML = result;
1499              
1500             would produce:
1501              
1502             Node names are:
1503             #text
1504             div
1505             #text
1506             #comment
1507             #text
1508             span
1509             #text
1510             br
1511             #text
1512             svg
1513             hr
1514             #text
1515             output
1516             #text
1517             script
1518              
1519             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName>
1520              
1521             =head2 nodeType
1522              
1523             Read-only
1524              
1525             This returns an integer representing the type of the element. Possible values are:
1526              
1527             =over 4
1528              
1529             =item 1. element node
1530              
1531             =item 2. attribute node
1532              
1533             =item 3. text node
1534              
1535             =item 4. CDATA section node
1536              
1537             =item 5. unused (formerly entity reference node)
1538              
1539             =item 6. unused (formerly entity node)
1540              
1541             =item 7. processing instruction node
1542              
1543             =item 8. comment node
1544              
1545             =item 9. document node
1546              
1547             =item 10. document type node
1548              
1549             =item 11. document fragment node
1550              
1551             =item 12. notation node
1552              
1553             =item 13. space node
1554              
1555             =back
1556              
1557             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType>
1558              
1559             =head2 nodeValue
1560              
1561             This returns or sets the value of the current node.
1562              
1563             For document, element or collection, this returns C<undef> and for attribute, text or comment, this sets or returns the objct value.
1564              
1565             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeValue>
1566              
1567             =head2 ownerDocument
1568              
1569             Read-only
1570              
1571             This returns the L<Document|HTML::Object::Document> that this element belongs to. If the element is itself a document, returns C<undef>.
1572              
1573             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/ownerDocument>
1574              
1575             =head2 parentNode
1576              
1577             Read-only
1578              
1579             This returns an element that is the parent of this element. If there is no such element, like if this element is the top of the tree or if does not participate in a tree, this property returns C<undef>.
1580              
1581             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode>
1582              
1583             =head2 parentElement
1584              
1585             Read-only
1586              
1587             This returns an element that is the parent of this element. If the element has no parent, or if that parent is not an Element, this property returns C<undef>.
1588              
1589             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement>
1590              
1591             =head2 previousSibling
1592              
1593             Read-only
1594              
1595             This returns a element representing the previous element in the tree, or C<undef> if there is not such element.
1596              
1597             The previous node could also be a L<whitespace|HTML::Object::DOM::Space> or a L<text|HTML::Object::DOM::Text>. If you want to get the previous element and not just any node, use L<previousElementSibling|HTML::Object::DOM/previousElementSibling> instead.
1598              
1599             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling>
1600              
1601             =head2 textContent
1602              
1603             Returns / Sets the textual content of an element and all its descendants.
1604              
1605             If this is called on a L<text node|HTML::Object::DOM::Text> or a L<comment node|HTML::Object::DOM::Comment>, it will, instead, set the object value to the textual content provided.
1606              
1607             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent>
1608              
1609             =head1 METHODS
1610              
1611             =head2 addEventListener
1612              
1613             Registers an event handler to a specific event type on the node. This is inherited from L<HTML::Object::EventTarget>
1614              
1615             See L<HTML::Object::EventTarget/addEventListener> for more information.
1616              
1617             =head2 appendChild
1618              
1619             Adds the specified C<child> L<element|HTML::Object::Element> argument as the last child to the current L<element|HTML::Object::Element>. If the argument referenced an existing L<element|HTML::Object::Element> on the DOM tree, the element will be detached from its current position and attached at the new position.
1620              
1621             If the given C<child> is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, the entire contents of the L<DocumentFragment|HTML::Object::DOM::DocumentFragment> are moved into the child list of the specified parent node.
1622              
1623             It returns the element added, except when the C<child> is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, in which case the empty L<DocumentFragment|HTML::Object::DOM::DocumentFragment> is returned.
1624              
1625             It returns C<undef> and sets an C<HTML::Object::HierarchyRequestError> error
1626              
1627             =over 4
1628              
1629             =item * the parent of C<child> is not a L<Document|HTML::Object::DOM::Document>, L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, or an L<Element|HTML::Object::DOM::Element>.
1630              
1631             =item * the insertion of C<child> would lead to a cycle, that is If C<child> is an ancestor of the node.
1632              
1633             =item * C<child> is not a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, a L<DocumentType|HTML::Object::DOM::Declaration>, an L<Element|HTML::Object::DOM::Element>, or a L<CharacterData|HTML::Object::DOM::CharacterData>.
1634              
1635             =item * the current node is a L<Text|HTML::Object::DOM::Text>, and its parent is a L<Document|HTML::Object::DOM::Document>.
1636              
1637             =item * the current node is a L<DocumentType|HTML::Object::DOM::Declaration> and its parent is not a L<Document|HTML::Object::DOM::Document>, as a doctype should always be a direct descendant of a document.
1638              
1639             =item * the parent of the node is a L<Document|HTML::Object::DOM::Document> and C<child> is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment> with more than one L<Element|HTML::Object::DOM::Element> child, or that has a L<Text|HTML::Object::DOM::Text> child.
1640              
1641             =item * the insertion of C<child> would lead to L<Document|HTML::Object::DOM::Document> with more than one L<Element|HTML::Object::DOM::Element> as child.
1642              
1643             =item * the insertion of C<child> would lead to the presence of an L<Element|HTML::Object::DOM::Element> node before a L<DocumentType|HTML::Object::DOM::Declaration> node.
1644              
1645             =back
1646              
1647             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild>
1648              
1649             =head2 appendNodes
1650              
1651             Provided with some nodes, and this will add them to the list of nodes for the current node.
1652              
1653             Returns the current node object.
1654              
1655             =head2 cloneNode
1656              
1657             Clone an element, and optionally, all of its contents. By default, it clones the content of the element.
1658              
1659             To clone a node to insert into a different document, use L<HTML::Object::DOM::Document/importNode> instead.
1660              
1661             Returns the element cloned. The cloned node has no parent and is not part of the document, until it is added to another node that is part of the document, using L</appendChild> or a similar method.
1662              
1663             See also L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode>
1664              
1665             =head2 compareDocumentPosition
1666              
1667             Compares the position of the current element against another element in any other document and returns a bitwise value comprised of one or more of the following constants (that are automatically exported):
1668              
1669             =over 4
1670              
1671             =item * DOCUMENT_POSITION_IDENTICAL (0 or in bits: 000000)
1672              
1673             Elements are identical.
1674              
1675             =item * DOCUMENT_POSITION_DISCONNECTED (1 or in bits: 000001)
1676              
1677             No relationship, both nodes are in different documents or different trees in the same document.
1678              
1679             =item * DOCUMENT_POSITION_PRECEDING (2 or in bits: 000010)
1680              
1681             The specified node precedes the current node.
1682              
1683             =item * DOCUMENT_POSITION_FOLLOWING (4 or in bits: 000100)
1684              
1685             The specified node follows the current node.
1686              
1687             =item * DOCUMENT_POSITION_CONTAINS (8 or in bits: 001000)
1688              
1689             The otherNode is an ancestor of / contains the current node.
1690              
1691             =item * DOCUMENT_POSITION_CONTAINED_BY (16 or in bits: 010000)
1692              
1693             The otherNode is a descendant of / contained by the node.
1694              
1695             =item * DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (32 or in bits: 100000)
1696              
1697             The specified node and the current node have no common container node or the two nodes are different attributes of the same node.
1698              
1699             =back
1700              
1701             use HTML::Object::DOM::Node;
1702             my $head = $doc->head;
1703             my $body = $doc->body;
1704              
1705             if( $head->compareDocumentPosition( $body ) & DOCUMENT_POSITION_FOLLOWING )
1706             {
1707             say( 'Well-formed document' );
1708             }
1709             else
1710             {
1711             say( '<head> is not before <body>' );
1712             }
1713              
1714             For example:
1715              
1716             <div id="writeroot">
1717             <form>
1718             <input id="test" />
1719             </form>
1720             </div>
1721              
1722             my $x = $doc->getElementById('writeroot');
1723             my $y = $doc->getElementById('test');
1724             say( $x->compareDocumentPosition( $y ) ); # 20, i.e. 16 | 4
1725             say( $y->compareDocumentPosition( $x ) ); # 10, i.e. 8 | 2
1726              
1727             Be careful that, since this method does quite a bit of searching among various hierarchies, this method is a bit expensive, especially on large documents.
1728              
1729             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition> and also this L<blog post from John Resig|https://johnresig.com/blog/comparing-document-position/> or L<this one from Peter-Paul Koch|https://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html>
1730              
1731             Also the L<W3C specifications|http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compareDocumentPosition> and L<here|http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition>
1732              
1733             =head2 contains
1734              
1735             Returns true or false value indicating whether or not an element is a descendant of the calling element.
1736              
1737             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/contains>
1738              
1739             =head2 dispatchEvent
1740              
1741             Dispatches an event to this node in the DOM and returns a boolean value that indicates whether no handler canceled the event. This is inherited from L<HTML::Object::EventTarget>
1742            
1743             See L<HTML::Object::EventTarget/dispatchEvent> for more information.
1744              
1745             =head2 find
1746              
1747             Provided with an node object or a selector and this will search throughout the current node hierarchy using the XPath expression provided.
1748              
1749             It returns an L<array object|Module::Generic::Array> of the nodes found.
1750              
1751             =head2 find_xpath
1752              
1753             Provided with an XPath expression and this will perform a search using the current node as the context.
1754              
1755             =head2 findnodes
1756              
1757             Provided with an XPath expression and this will perform a search using the current node as the context.
1758              
1759             =head2 findnodes_as_string
1760              
1761             Provided with an XPath expression and this will perform a search using the current node as the context and return the result as string.
1762              
1763             =head2 findnodes_as_strings
1764              
1765             Provided with an XPath expression and this will perform a search using the current node as the context and return the result as a list of strings.
1766              
1767             =head2 findvalue
1768              
1769             Provided with an XPath expression and this will perform a search using the current node as the context and return the result as the node value.
1770              
1771             =head2 findvalues
1772              
1773             Provided with an XPath expression and this will perform a search using the current node as the context and return the result as a list of node values.
1774              
1775             =head2 getAttributes
1776              
1777             Returns a list of attribute objects for this node in list context or an L<array object|Module::Generic::Array> in scalar context.
1778              
1779             =head2 getChildNodes
1780              
1781             Returns a list of the current child nodes in list context or an L<array object|Module::Generic::Array> in scalar context.
1782              
1783             =head2 getElementById
1784              
1785             Returns an empty list in list context and an empty array reference in scalar context.
1786              
1787             =head2 getFirstChild
1788              
1789             Returns the first child node of this node, if any, or C<undef> if there are none.
1790              
1791             =head2 getLastChild
1792              
1793             Returns the last child node of this node, if any, or C<undef> if there are none.
1794              
1795             =head2 getName
1796              
1797             Returns an C<undef> and this method is superseded in L<HTML::Object::DOM::Element>
1798              
1799             =head2 getNextSibling
1800              
1801             This non-standard method is an alias for the property L</nextSibling>
1802              
1803             =head2 getParentNode
1804              
1805             Returns the current node's parent node, if any.
1806              
1807             =head2 getPreviousSibling
1808              
1809             This non-standard method is an alias for the property L</previousSibling>
1810              
1811             =head2 getRootNode
1812              
1813             Returns the context object's root.
1814              
1815             Under JavaScript, this optionally includes the shadow root if it is available. However a shadow root has no meaning under this perl interface.
1816              
1817             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode>
1818              
1819             =head2 hasChildNodes
1820              
1821             Returns a boolean value indicating whether or not the element has any child elements.
1822              
1823             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/hasChildNodes>
1824              
1825             =head2 insertAfter
1826              
1827             This is a non-standard method since it does not exist in the web API, surprisingly enough.
1828              
1829             This is exactly the same as L</insertBefore> below except it inserts the C<node> after.
1830              
1831             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/insertAfter>
1832              
1833             =head2 insertBefore
1834              
1835             Provided with a C<new> node and an optional C<reference> node and this inserts an element before the reference element as a child of a specified parent element. If the C<reference> node is C<undef>, then C<new> node is inserted at the end of current node's child nodes.
1836              
1837             If the given node already exists in the document, C<insertBefore> moves it from its current position to the new position. This means it will automatically be removed from its existing parent before appending it to the specified new parent.
1838              
1839             This means that a node cannot be in two locations of the document simultaneously.
1840              
1841             If the given child is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, the entire contents of the L<DocumentFragment|HTML::Object::DOM::DocumentFragment> are moved into the child list of the specified parent node.
1842              
1843             Returns the added child (unless C<new> is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, in which case the empty L<DocumentFragment|HTML::Object::DOM::DocumentFragment> is returned).
1844              
1845             Example:
1846              
1847             <div id="parentElement">
1848             <span id="childElement">foo bar</span>
1849             </div>
1850              
1851             # Create a new, plain <span> element
1852             my $sp1 = $doc->createElement( 'span' );
1853              
1854             # Get the reference element
1855             my $sp2 = $doc->getElementById( 'childElement' );
1856             # Get the parent element
1857             my $parentDiv = $sp2->parentNode
1858              
1859             # Insert the new element into before sp2
1860             $parentDiv->insertBefore( $sp1, $sp2 );
1861              
1862             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore>
1863              
1864             =head2 isAttributeNode
1865              
1866             Returns false by default.
1867              
1868             =head2 isCommentNode
1869              
1870             Returns false by default.
1871              
1872             =head2 isDefaultNamespace
1873              
1874             Accepts a namespace URI as an argument and returns a boolean value with a value of true if the namespace is the default namespace on the given element or false if not.
1875              
1876             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/isDefaultNamespace>
1877              
1878             =head2 isElementNode
1879              
1880             Returns false by default.
1881              
1882             =head2 isEqualNode
1883              
1884             Returns a boolean value which indicates whether or not two elements are of the same type and all their defining data points match.
1885              
1886             Two elements are equal when they have the same type, defining characteristics (this would be their ID, number of children, and so forth), its attributes match, and so on. The specific set of data points that must match varies depending on the types of the elements.
1887              
1888             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/isEqualNode>
1889              
1890             =head2 isNamespaceNode
1891              
1892             Returns false by default.
1893              
1894             =head2 isPINode
1895              
1896             Returns false by default.
1897              
1898             =head2 isProcessingInstructionNode
1899              
1900             Returns false by default.
1901              
1902             =head2 isSameNode
1903              
1904             Returns a boolean value indicating whether or not the two elements are the same (that is, they reference the same object).
1905              
1906             Example:
1907              
1908             my $div1 = $doc->createElement('div');
1909             $div1->appendChild( $doc->createTextNode('This is an element.') );
1910             my $div2 = $div1->cloneNode;
1911             say $div1->isSameNode( $div2 ); # false
1912             say $div1->isSameNode( $div1 ); # true
1913              
1914             We can also use with the equality operator:
1915              
1916             say $div1 == $div2; # false
1917             say $div1 eq $div2; # same; false
1918             say $div1 == $div1; # true
1919              
1920             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/isSameNode>
1921              
1922             =head2 isTextNode
1923              
1924             Returns false by default.
1925              
1926             =head2 lookupNamespaceURI
1927              
1928             Accepts a prefix and returns the namespace URI associated with it on the given element if found (and C<undef> if not). Supplying C<undef> for the prefix will return the default namespace.
1929              
1930             This always return an empty string and C<http://www.w3.org/XML/1998/namespace> if the prefix is C<xml>
1931              
1932             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/lookupNamespaceURI>
1933              
1934             =head2 lookupPrefix
1935              
1936             This always returns C<undef>, because this is for XML, which is not supported.
1937              
1938             Returns a string containing the prefix for a given namespace URI, if present, and C<undef> if not.
1939              
1940             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/lookupPrefix>
1941              
1942             =head2 new_closing
1943              
1944             Returns a new L<HTML::Object::DOM::Closing> object, passing it whatever arguments were provided and return the newly instantiated object.
1945              
1946             If an error occurred, this returns C<undef> and sets an L<error|Module::Generic/error>
1947              
1948             =head2 new_comment
1949              
1950             Returns a new L<HTML::Object::DOM::Comment> object, passing it whatever arguments were provided and return the newly instantiated object.
1951              
1952             If an error occurred, this returns C<undef> and sets an L<error|Module::Generic/error>
1953              
1954             =head2 new_element
1955              
1956             Returns a new L<HTML::Object::DOM::Element> object, passing it whatever arguments were provided and return the newly instantiated object.
1957              
1958             If an error occurred, this returns C<undef> and sets an L<error|Module::Generic/error>
1959              
1960             =head2 new_parser
1961              
1962             Returns a new L<HTML::Object::DOM> object, passing it whatever arguments were provided and return the newly instantiated object.
1963              
1964             If an error occurred, this returns C<undef> and sets an L<error|Module::Generic/error>
1965              
1966             =head2 new_text
1967              
1968             Returns a new L<HTML::Object::DOM::Text> object, passing it whatever arguments were provided and return the newly instantiated object.
1969              
1970             If an error occurred, this returns C<undef> and sets an L<error|Module::Generic/error>
1971              
1972             =head2 nodes
1973              
1974             Returns the L<array object|Module::Generic::Array> containing the current node's sub nodes.
1975              
1976             =head2 normalize
1977              
1978             Clean up all the text elements under this element (merge adjacent, remove empty).
1979              
1980             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize>
1981              
1982             =head2 removeChild
1983              
1984             Provided with a child node and this removes the child node from the current element, which must be a child of the current element and returns the removed node.
1985              
1986             A C<HTML::Object::NotFoundError> error is returned if the child is not a child of the node.
1987              
1988             Example:
1989              
1990             <div id="top">
1991             <div id="nested"></div>
1992             </div>
1993              
1994             To remove a specified element when knowing its parent node:
1995              
1996             my $d = $doc->getElementById('top');
1997             my $d_nested = $doc->getElementById('nested');
1998             my $throwawayNode = $d->removeChild( $d_nested );
1999              
2000             To remove a specified element without having to specify its parent node:
2001              
2002             my $node = $doc->getElementById('nested');
2003             if( $node->parentNode )
2004             {
2005             $node->parentNode->removeChild( $node );
2006             }
2007              
2008             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild>
2009              
2010             =head2 removeEventListener
2011              
2012             Removes an event listener from the node. This is inherited from L<HTML::Object::EventTarget>
2013            
2014             See L<HTML::Object::EventTarget/removeEventListener> for more information.
2015              
2016             =head2 replaceChild
2017              
2018             Provided with a C<new> node and and an C<old> node and this will replace the C<old> one by the C<new> one. Note that if the C<new> node is already present somewhere else in the C<DOM>, it is first removed from that position.
2019              
2020             This returns the C<old> node removed.
2021              
2022             For L<nodes|HTML::Object::DOM::Node> that are L<elements|HTML::Object::DOM::Element>, it might be easier to read and use L<HTML::Object::DOM::Element/replaceWith>
2023              
2024             It returns C<undef> and sets an L<HTML::Object::HierarchyRequestError|Module::Generic/error> if:
2025              
2026             =over 4
2027              
2028             =item * the parent of C<old> node is not a L<Document|HTML::Object::DOM::Document>, L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, or an L<Element|HTML::Object::DOM::Element>.
2029              
2030             =item * the replacement of C<old> node by C<new> node would lead to a cycle, that is if C<new> node is an ancestor of the node.
2031              
2032             =item * C<new> is not a L<DocumentFragment|HTML::Object::DOM::DocumentFragment>, a L<DocumentType|HTML::Object::DOM::Declaration>, an L<Element|HTML::Object::DOM::Element>, or a L<CharacterData|HTML::Object::DOM::CharacterData>.
2033              
2034             =item * the current node is a L<Text|HTML::Object::DOM::Text>, and its parent is a L<Document|HTML::Object::DOM::Document>.
2035              
2036             =item * the current node is a L<DocumentType|HTML::Object::DOM::Declaration> and its parent is not a L<Document|HTML::Object::DOM::Document>, as a doctype should always be a direct descendant of a document.
2037              
2038             =item * the parent of the node is a L<Document|HTML::Object::DOM::Document> and C<new> node is a L<DocumentFragment|HTML::Object::DOM::DocumentFragment> with more than one L<Element|HTML::Object::DOM::Element> child, or that has a L<Text|HTML::Object::DOM::Text> child.
2039              
2040             =item * the replacement of C<old> node by C<new> node would lead to L<Document|HTML::Object::DOM::Document> with more than one L<Element|HTML::Object::DOM::Element> as child.
2041              
2042             =item * the replacement of C<old> node by C<new> node would lead to the presence of an L<Element|HTML::Object::DOM::Element> node before a L<DocumentType|HTML::Object::DOM::Declaration> node.
2043              
2044             =back
2045              
2046             It returns an L<HTML::Object::NotFoundError|Module::Generic/error> if the parent of C<old> is not the current node.
2047              
2048             Example:
2049              
2050             <div>
2051             <span id="childSpan">foo bar</span>
2052             </div>
2053              
2054             // Build a reference to the existing node to be replaced
2055             let sp1 = document.getElementById('childSpan');
2056             let parentDiv = sp2.parentNode;
2057             // Create an empty element node without an ID, any attributes, or any content
2058             let sp2 = document.createElement('span');
2059             // Give it an id attribute called 'newSpan'
2060             sp2.id = "newSpan";
2061             // Create some content for the new element.
2062             sp2.appendChild( document.createTextNode('new replacement span element.') );
2063             // Replace existing node sp1 with the new span element sp2
2064             parentDiv.replaceChild(sp2, sp1);
2065              
2066             Result:
2067              
2068             <div>
2069             <span id="newSpan">new replacement span element.</span>
2070             </div>
2071              
2072             See also L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild>
2073              
2074             =head2 trigger
2075              
2076             Provided with an even C<type> and this will instantiate a new L<HTML::Object::Event> object, passing it the C<type> argument, and any other arguments provided. it returns the value returned by L<HTML::Object::EventTarget/dispatchEvent>
2077              
2078             If no event type is provided, it returns a C<HTML::Object::SyntaxError> error.
2079              
2080             If the event type contains illegal characters, it returns a C<HTML::Object::TypeError> error. Accepted characters are alpha-numeric, underscore, and dash ("-").
2081              
2082             =head2 xp
2083              
2084             Returns a L<HTML::Object::XPath> object.
2085              
2086             =head1 CONSTANTS
2087              
2088             =over 4
2089              
2090             =item * DOCUMENT_POSITION_IDENTICAL (0 or in bits: 000000)
2091              
2092             Elements are identical.
2093              
2094             =item * DOCUMENT_POSITION_DISCONNECTED (1 or in bits: 000001)
2095              
2096             No relationship, both nodes are in different documents or different trees in the same document.
2097              
2098             =item * DOCUMENT_POSITION_PRECEDING (2 or in bits: 000010)
2099              
2100             The specified node precedes the current node.
2101              
2102             =item * DOCUMENT_POSITION_FOLLOWING (4 or in bits: 000100)
2103              
2104             The specified node follows the current node.
2105              
2106             =item * DOCUMENT_POSITION_CONTAINS (8 or in bits: 001000)
2107              
2108             The otherNode is an ancestor of / contains the current node.
2109              
2110             =item * DOCUMENT_POSITION_CONTAINED_BY (16 or in bits: 010000)
2111              
2112             The otherNode is a descendant of / contained by the node.
2113              
2114             =item * DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (32 or in bits: 100000)
2115              
2116             The specified node and the current node have no common container node or the two nodes are different attributes of the same node.
2117              
2118             =back
2119              
2120             And also the following constants:
2121              
2122             =over 4
2123              
2124             =item * ELEMENT_NODE (1)
2125              
2126             =item * ATTRIBUTE_NODE (2)
2127              
2128             =item * TEXT_NODE (3)
2129              
2130             =item * CDATA_SECTION_NODE (4)
2131              
2132             =item * ENTITY_REFERENCE_NODE (5)
2133              
2134             =item * ENTITY_NODE (6)
2135              
2136             =item * PROCESSING_INSTRUCTION_NODE (7)
2137              
2138             =item * COMMENT_NODE (8)
2139              
2140             =item * DOCUMENT_NODE (9)
2141              
2142             =item * DOCUMENT_TYPE_NODE (10)
2143              
2144             =item * DOCUMENT_FRAGMENT_NODE (11)
2145              
2146             =item * NOTATION_NODE (12)
2147              
2148             =item * SPACE_NODE (13)
2149              
2150             =back
2151              
2152             =head1 AUTHOR
2153              
2154             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
2155              
2156             =head1 SEE ALSO
2157              
2158             See L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/API/Node>
2159              
2160             =head1 COPYRIGHT & LICENSE
2161              
2162             Copyright(c) 2021 DEGUEST Pte. Ltd.
2163              
2164             All rights reserved
2165              
2166             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
2167              
2168             =cut