File Coverage

blib/lib/MDOM/Element.pm
Criterion Covered Total %
statement 59 155 38.0
branch 11 74 14.8
condition 2 15 13.3
subroutine 22 48 45.8
pod 20 22 90.9
total 114 314 36.3


line stmt bran cond sub pod time code
1             package MDOM::Element;
2              
3             =pod
4              
5             =head1 NAME
6              
7             MDOM::Element - The abstract Element class, a base for all source objects
8              
9             =head1 INHERITANCE
10              
11             MDOM::Element is the root of the PDOM tree
12              
13             =head1 DESCRIPTION
14              
15             The abstract C serves as a base class for all source-related
16             objects, from a single whitespace token to an entire document. It provides
17             a basic set of methods to provide a common interface and basic
18             implementations.
19              
20             =head1 METHODS
21              
22             =cut
23              
24 17     17   104 use strict;
  17         35  
  17         611  
25 17     17   95 use Scalar::Util 'refaddr';
  17         36  
  17         2047  
26 17         1382 use Params::Util '_INSTANCE',
27 17     17   17953 '_ARRAY';
  17         60042  
28 17     17   11719 use MDOM::Node ();
  17         64  
  17         447  
29 17     17   13647 use Clone ();
  17         54077  
  17         464  
30 17     17   133 use List::MoreUtils ();
  17         37  
  17         866  
31             use overload 'bool' => sub () { 1 },
32 17         207 '""' => 'content',
33             '==' => '__equals',
34             '!=' => '__nequals',
35             'eq' => '__eq',
36 17     17   99 'ne' => '__ne';
  17         36  
37              
38 17     17   2576 use vars qw{$VERSION $errstr %_PARENT};
  17         37  
  17         1381  
39             BEGIN {
40 17     17   47 $VERSION = '0.006';
41 17         37 $errstr = '';
42              
43             # Master Child -> Parent index
44 17         34860 %_PARENT = ();
45             }
46              
47              
48              
49              
50              
51             #####################################################################
52             # General Properties
53              
54             =pod
55              
56             =head2 significant
57              
58             Because we treat whitespace and other non-code items as Tokens (in order to
59             be able to "round trip" the L back to a file) the
60             C method allows us to distinguish between tokens that form a
61             part of the code, and tokens that aren't significant, such as whitespace,
62             POD, or the portion of a file after (and including) the C<__END__> token.
63              
64             Returns true if the Element is significant, or false it not.
65              
66             =cut
67              
68             ### XS -> MDOM/XS.xs:_MDOM_Element__significant 0.845+
69 314     314 1 704 sub significant { 1 }
70              
71             sub lineno {
72 0     0 0 0 $_[0]->{lineno};
73             }
74              
75             =pod
76              
77             =head2 class
78              
79             The C method is provided as a convenience, and really does nothing
80             more than returning C. However, some people have found that
81             they appreciate the laziness of C<$Foo-Eclass eq 'whatever'>, so I
82             have caved to popular demand and included it.
83              
84             Returns the class of the Element as a string
85              
86             =cut
87              
88 142     142 1 621 sub class { ref($_[0]) }
89              
90             =pod
91              
92             =head2 tokens
93              
94             The C method returns a list of L objects for the
95             Element, essentially getting back that part of the document as if it had
96             not been lexed.
97              
98             This also means there are no Statements and no Structures in the list,
99             just the Token classes.
100              
101             =cut
102              
103 0     0 1 0 sub tokens { $_[0] }
104              
105             =pod
106              
107             =head2 content
108              
109             For B C, the C method will reconstitute the
110             base code for it as a single string. This method is also the method used
111             for overloading stringification. When an Element is used in a double-quoted
112             string for example, this is the method that is called.
113              
114             B
115              
116             You should be aware that because of the way that here-docs are handled, any
117             here-doc content is not included in C, and as such you should
118             B eval or execute the result if it contains any L.
119              
120             The L method C should be used to stringify a PDOM
121             document into something that can be executed as expected.
122              
123             Returns the basic code as a string (excluding here-doc content).
124              
125             =cut
126              
127             ### XS -> MDOM/XS.xs:_MDOM_Element__content 0.900+
128 0     0 1 0 sub content { '' }
129              
130              
131             #####################################################################
132             # Naigation Methods
133              
134             =pod
135              
136             =head2 parent
137              
138             Elements themselves are not intended to contain other Elements, that is
139             left to the L abstract class, a subclass of C.
140             However, all Elements can be contained B a parent Node.
141              
142             If an Element is within a parent Node, the C method returns the
143             Node.
144              
145             =cut
146              
147 27     27 1 284 sub parent { $_PARENT{refaddr $_[0]} }
148              
149             =pod
150              
151             =head2 statement
152              
153             For a C that is contained (at some depth) within a
154             L, the C method will return the first parent
155             Statement object lexically 'above' the Element.
156              
157             Returns a L object, which may be the same Element if the
158             Element is itself a L object.
159              
160             Returns false if the Element is not within a Statement and is not itself
161             a Statement.
162              
163             =cut
164              
165             sub statement {
166 0     0 1 0 my $cursor = shift;
167 0         0 while ( ! _INSTANCE($cursor, 'MDOM::Statement') ) {
168 0 0       0 $cursor = $_PARENT{refaddr $cursor} or return '';
169             }
170 0         0 $cursor;
171             }
172              
173             =pod
174              
175             =head2 top
176              
177             For a C that is contained within a PDOM tree, the C method
178             will return the top-level Node in the tree. Most of the time this should be
179             a L object, however this will not always be so. For example,
180             if a subroutine has been removed from its Document, to be moved to another
181             Document.
182              
183             Returns the top-most PDOM object, which may be the same Element, if it is
184             not within any parent PDOM object.
185              
186             =cut
187              
188             sub top {
189 0     0 1 0 my $cursor = shift;
190 0         0 while ( my $parent = $_PARENT{refaddr $cursor} ) {
191 0         0 $cursor = $parent;
192             }
193 0         0 $cursor;
194             }
195              
196             =pod
197              
198             For an Element that is contained within a L object,
199             the C method will return the top-level Document for the Element.
200              
201             Returns the L for this Element, or false if the Element is not
202             contained within a Document.
203              
204             =cut
205              
206             sub document {
207 0     0 0 0 my $top = shift->top;
208 0 0       0 _INSTANCE($top, 'MDOM::Document') and $top;
209             }
210              
211             =pod
212              
213             =head2 next_sibling
214              
215             All L objects (specifically, our parent Node) contain a number of
216             C objects. The C method returns the C
217             immediately after the current one, or false if there is no next sibling.
218              
219             =cut
220              
221             sub next_sibling {
222 4     4 1 6 my $self = shift;
223 4 50       203 my $parent = $_PARENT{refaddr $self} or return '';
224 4         10 my $key = refaddr $self;
225 4         10 my $elements = $parent->{children};
226             my $position = List::MoreUtils::firstidx {
227 8     8   19 refaddr $_ == $key
228 4         20 } @$elements;
229 4 100       120 $elements->[$position + 1] || '';
230             }
231              
232             =pod
233              
234             =head2 snext_sibling
235              
236             As per the other 's' methods, the C method returns the next
237             B sibling of the C object.
238              
239             Returns a C object, or false if there is no 'next' significant
240             sibling.
241              
242             =cut
243              
244             sub snext_sibling {
245 0     0 1 0 my $self = shift;
246 0 0       0 my $parent = $_PARENT{refaddr $self} or return '';
247 0         0 my $key = refaddr $self;
248 0         0 my $elements = $parent->{children};
249             my $position = List::MoreUtils::firstidx {
250 0     0   0 refaddr $_ == $key
251 0         0 } @$elements;
252 0         0 while ( defined(my $it = $elements->[++$position]) ) {
253 0 0       0 return $it if $it->significant;
254             }
255 0         0 '';
256             }
257              
258             =pod
259              
260             =head2 previous_sibling
261              
262             All L objects (specifically, our parent Node) contain a number of
263             C objects. The C method returns the Element
264             immediately before the current one, or false if there is no 'previous'
265             C object.
266              
267             =cut
268              
269             sub previous_sibling {
270 4     4 1 8 my $self = shift;
271 4 50       229 my $parent = $_PARENT{refaddr $self} or return '';
272 4         10 my $key = refaddr $self;
273 4         9 my $elements = $parent->{children};
274             my $position = List::MoreUtils::firstidx {
275 8     8   21 refaddr $_ == $key
276 4         32 } @$elements;
277 4 100 66     320 $position and $elements->[$position - 1] or '';
278             }
279              
280             =pod
281              
282             =head2 sprevious_sibling
283              
284             As per the other 's' methods, the C method returns
285             the previous B sibling of the C object.
286              
287             Returns a C object, or false if there is no 'previous' significant
288             sibling.
289              
290             =cut
291              
292             sub sprevious_sibling {
293 0     0 1 0 my $self = shift;
294 0 0       0 my $parent = $_PARENT{refaddr $self} or return '';
295 0         0 my $key = refaddr $self;
296 0         0 my $elements = $parent->{children};
297             my $position = List::MoreUtils::firstidx {
298 0     0   0 refaddr $_ == $key
299 0         0 } @$elements;
300 0   0     0 while ( $position-- and defined(my $it = $elements->[$position]) ) {
301 0 0       0 return $it if $it->significant;
302             }
303 0         0 '';
304             }
305              
306             =pod
307              
308             =head2 first_token
309              
310             As a support method for higher-order algorithms that deal specifically with
311             tokens and actual Perl content, the C method finds the first
312             MDOM::Token object within or equal to this one.
313              
314             That is, if called on a L subclass, it will descend until it
315             finds a L. If called on a L object, it will return
316             the same object.
317              
318             Returns a L object, or dies on error (which should be extremely
319             rare and only occur if an illegal empty L exists below the
320             current Element somewhere.
321              
322             =cut
323              
324             sub first_token {
325 0     0 1 0 my $cursor = shift;
326 0         0 while ( $cursor->isa('MDOM::Node') ) {
327 0 0       0 $cursor = $cursor->first_element
328             or die "Found empty MDOM::Node while getting first token";
329             }
330 0         0 $cursor;
331             }
332              
333              
334             =pod
335              
336             =head2 last_token
337              
338             As a support method for higher-order algorithms that deal specifically with
339             tokens and actual Perl content, the C method finds the last
340             MDOM::Token object within or equal to this one.
341              
342             That is, if called on a L subclass, it will descend until it
343             finds a L. If called on a L object, it will return
344             the itself.
345              
346             Returns a L object, or dies on error (which should be extremely
347             rare and only occur if an illegal empty L exists below the
348             current Element somewhere.
349              
350             =cut
351              
352             sub last_token {
353 44     44 1 64 my $cursor = shift;
354 44         148 while ( $cursor->isa('MDOM::Node') ) {
355 82 50       222 $cursor = $cursor->last_element
356             or die "Found empty MDOM::Node while getting first token";
357             }
358 44         156 $cursor;
359             }
360              
361             =pod
362              
363             =head2 next_token
364              
365             As a support method for higher-order algorithms that deal specifically with
366             tokens and actual Perl content, the C method finds the
367             L object that is immediately after the current Element, even if
368             it is not within the same parent L as the one for which the
369             method is being called.
370              
371             Note that this is B defined as a L-specific method,
372             because it can be useful to find the next token that is after, say, a
373             L, although obviously it would be useless to want the
374             next token after a L.
375              
376             Returns a L object, or false if there are no more tokens after
377             the Element.
378              
379             =cut
380              
381             sub next_token {
382 0     0 1 0 my $cursor = shift;
383              
384             # Find the next element, going upwards as needed
385 0         0 while ( 1 ) {
386 0         0 my $element = $cursor->next_sibling;
387 0 0       0 if ( $element ) {
388 0 0       0 return $element if $element->isa('MDOM::Token');
389 0         0 return $element->first_token;
390             }
391 0 0       0 $cursor = $cursor->parent or return '';
392 0 0 0     0 if ( $cursor->isa('MDOM::Structure') and $cursor->finish ) {
393 0         0 return $cursor->finish;
394             }
395             }
396             }
397              
398             =pod
399              
400             =head2 previous_token
401              
402             As a support method for higher-order algorithms that deal specifically with
403             tokens and actual Perl content, the C method finds the
404             L object that is immediately before the current Element, even
405             if it is not within the same parent L as this one.
406              
407             Note that this is not defined as a L-only method, because it can
408             be useful to find the token is before, say, a L, although
409             obviously it would be useless to want the next token before a
410             L.
411              
412             Returns a L object, or false if there are no more tokens before
413             the C.
414              
415             =cut
416              
417             sub previous_token {
418 0     0 1 0 my $cursor = shift;
419              
420             # Find the previous element, going upwards as needed
421 0         0 while ( 1 ) {
422 0         0 my $element = $cursor->previous_sibling;
423 0 0       0 if ( $element ) {
424 0 0       0 return $element if $element->isa('MDOM::Token');
425 0         0 return $element->last_token;
426             }
427 0 0       0 $cursor = $cursor->parent or return '';
428 0 0 0     0 if ( $cursor->isa('MDOM::Structure') and $cursor->start ) {
429 0         0 return $cursor->start;
430             }
431             }
432             }
433              
434              
435              
436              
437              
438             #####################################################################
439             # Manipulation
440              
441             =pod
442              
443             =head2 clone
444              
445             As per the L module, the C method makes a perfect copy of
446             an Element object. In the generic case, the implementation is done using
447             the L module's mechanism itself. In higher-order cases, such as for
448             Nodes, there is more work involved to keep the parent-child links intact.
449              
450             =cut
451              
452             sub clone {
453 1     1 1 38 Clone::clone(shift);
454             }
455              
456             =pod
457              
458             =head2 insert_before @Elements
459              
460             The C method allows you to insert lexical perl content, in
461             the form of C objects, before the calling C. You
462             need to be very careful when modifying perl code, as it's easy to break
463             things.
464              
465             In its initial incarnation, this method allows you to insert a single
466             Element, and will perform some basic checking to prevent you inserting
467             something that would be structurally wrong (in PDOM terms).
468              
469             In future, this method may be enhanced to allow the insertion of multiple
470             Elements, inline-parsed code strings or L objects.
471              
472             Returns true if the Element was inserted, false if it can not be inserted,
473             or C if you do not provide a L object as a parameter.
474              
475             =cut
476              
477             sub __insert_before {
478 0     0   0 my $self = shift;
479 0         0 $self->parent->__insert_before_child( $self, @_ );
480             }
481              
482             =pod
483              
484             =head2 insert_after @Elements
485              
486             The C method allows you to insert lexical perl content, in
487             the form of C objects, after the calling C. You need
488             to be very careful when modifying perl code, as it's easy to break things.
489              
490             In its initial incarnation, this method allows you to insert a single
491             Element, and will perform some basic checking to prevent you inserting
492             something that would be structurally wrong (in PDOM terms).
493              
494             In future, this method may be enhanced to allow the insertion of multiple
495             Elements, inline-parsed code strings or L objects.
496              
497             Returns true if the Element was inserted, false if it can not be inserted,
498             or C if you do not provide a L object as a parameter.
499              
500             =cut
501              
502             sub __insert_after {
503 0     0   0 my $self = shift;
504 0         0 $self->parent->__insert_after_child( $self, @_ );
505             }
506              
507             =pod
508              
509             =head2 remove
510              
511             For a given C, the C method will remove it from its
512             parent B, along with all of its children.
513              
514             Returns the C itself as a convenience, or C if an error
515             occurs while trying to remove the C.
516              
517             =cut
518              
519             sub remove {
520 5     5 1 10 my $self = shift;
521 5 50       17 my $parent = $self->parent or return $self;
522 5         18 $parent->remove_child( $self );
523             }
524              
525             =pod
526              
527             =head2 delete
528              
529             For a given C, the C method will remove it from its
530             parent, immediately deleting the C and all of its children (if it
531             has any).
532              
533             Returns true if the C was successfully deleted, or C if
534             an error occurs while trying to remove the C.
535              
536             =cut
537              
538             sub delete {
539 5 50   5 1 21 $_[0]->remove or return undef;
540 5         25 $_[0]->DESTROY;
541 5         25 1;
542             }
543              
544             =pod
545              
546             =head2 replace $Element
547              
548             Although some higher level class support more exotic forms of replace,
549             at the basic level the C method takes a single C as
550             an argument and replaces the current C with it.
551              
552             To prevent accidental damage to code, in this initial implementation the
553             replacement element B be of the same class (or a subclass) as the
554             one being replaced.
555              
556             =cut
557              
558             sub replace {
559 0 0   0 1 0 my $self = ref $_[0] ? shift : return undef;
560 0 0       0 my $Element = _INSTANCE(shift, ref $self) or return undef;
561 0         0 die "The ->replace method has not yet been implemented";
562             }
563              
564             =pod
565              
566             =head2 location
567              
568             If the Element exists within a L that has
569             indexed the Element locations using C, the
570             C method will return the location of the first character of the
571             Element within the Document.
572              
573             Returns the location as a reference to a three-element array in the form
574             C<[ $line, $rowchar, $col ]>. The values are in a human format, with the
575             first character of the file located at C<[ 1, 1, 1 ]>.
576              
577             The second and third numbers are similar, except that the second is the
578             literal horizontal character, and the third is the visual column, taking
579             into account tabbing.
580              
581             Returns C on error, or if the L object has not been indexed.
582              
583             =cut
584              
585             sub location {
586 0     0 1 0 my $self = shift;
587 0 0       0 unless ( exists $self->{_location} ) {
588             # Are we inside a normal document?
589 0 0       0 my $Document = $self->document or return undef;
590 0 0       0 if ( $Document->isa('MDOM::Document::Fragment') ) {
591             # Because they can't be serialized, document fragments
592             # do not support the concept of location.
593 0         0 return undef;
594             }
595              
596             # Generate the locations. If they need one location, then
597             # the chances are they'll want more, and it's better that
598             # everything is already pre-generated.
599 0 0       0 $Document->index_locations or return undef;
600 0 0       0 unless ( exists $self->{_location} ) {
601             # erm... something went very wrong here
602 0         0 return undef;
603             }
604             }
605              
606             # Return a copy, not the original
607 0         0 return [ @{$self->{_location}} ];
  0         0  
608             }
609              
610             # Although flush_locations is only publically a Document-level method,
611             # we are able to implement it at an Element level, allowing us to
612             # selectively flush only the part of the document that occurs after the
613             # element for which the flush is called.
614             sub _flush_locations {
615 0     0   0 my $self = shift;
616 0 0       0 unless ( $self == $self->top ) {
617 0         0 return $self->top->_flush_locations( $self );
618             }
619              
620             # Get the full list of all Tokens
621 0         0 my @Tokens = $self->tokens;
622              
623             # Optionally allow starting from an arbitrary element (or rather,
624             # the first Token equal-to-or-within an arbitrary element)
625 0 0       0 if ( _INSTANCE($_[0], 'MDOM::Element') ) {
626 0         0 my $start = shift->first_token;
627 0         0 while ( my $Token = shift @Tokens ) {
628 0 0       0 return 1 unless $Token->{_location};
629 0 0       0 next unless refaddr($Token) == refaddr($start);
630              
631             # Found the start. Flush it's location
632 0         0 delete $$Token->{_location};
633 0         0 last;
634             }
635             }
636              
637             # Iterate over any remaining Tokens and flush their location
638 0         0 foreach my $Token ( @Tokens ) {
639 0         0 delete $Token->{_location};
640             }
641              
642 0         0 1;
643             }
644              
645              
646              
647              
648              
649             #####################################################################
650             # XML Compatibility Methods
651              
652             sub _xml_name {
653 0   0 0   0 my $class = ref $_[0] || $_[0];
654 0         0 my $name = lc join( '_', split /::/, $class );
655 0         0 substr($name, 4);
656             }
657              
658             sub _xml_attr {
659 0     0   0 return {};
660             }
661              
662             sub _xml_content {
663 0 0   0   0 defined $_[0]->{content} ? $_[0]->{content} : '';
664             }
665              
666              
667              
668              
669              
670             #####################################################################
671             # Internals
672              
673             # Set the error string
674             sub _error {
675 0     0   0 $errstr = $_[1];
676 0         0 undef;
677             }
678              
679             # Clear the error string
680             sub _clear {
681 0     0   0 $errstr = '';
682 0         0 $_[0];
683             }
684              
685             # Being DESTROYed in this manner, rather than by an explicit
686             # ->delete means our reference count has probably fallen to zero.
687             # Therefore we don't need to remove ourselves from our parent,
688             # just the index ( just in case ).
689             ### XS -> MDOM/XS.xs:_MDOM_Element__DESTROY 0.900+
690 866     866   4986 sub DESTROY { delete $_PARENT{refaddr $_[0]} }
691              
692             # Operator overloads
693 0 0   0   0 sub __equals { ref $_[1] and refaddr($_[0]) == refaddr($_[1]) }
694 0     0   0 sub __nequals { !__equals(@_) }
695             sub __eq {
696 1024 50   1024   8750 my $self = _INSTANCE($_[0], 'MDOM::Element') ? $_[0]->content : $_[0];
697 1024 50       2767 my $other = _INSTANCE($_[1], 'MDOM::Element') ? $_[1]->content : $_[1];
698 1024         4253 $self eq $other;
699             }
700 0     0     sub __ne { !__eq(@_) }
701              
702             1;
703              
704             =pod
705              
706             =head1 TO DO
707              
708             It would be nice if C could be used in an ad-hoc manner. That is,
709             if called on an Element within a Document that has not been indexed, it will
710             do a one-off calculation to find the location. It might be very painful if
711             someone started using it a lot, without remembering to index the document,
712             but it would be handy for things that are only likely to use it once, such
713             as error handlers.
714              
715             =head1 SUPPORT
716              
717             See the L in the main module.
718              
719             =head1 AUTHOR
720              
721             Adam Kennedy Eadamk@cpan.orgE
722              
723             =head1 COPYRIGHT
724              
725             Copyright 2001 - 2006 Adam Kennedy.
726              
727             This program is free software; you can redistribute
728             it and/or modify it under the same terms as Perl itself.
729              
730             The full text of the license can be found in the
731             LICENSE file included with this module.
732              
733             =cut