Debian lenny version packages
[pkg-perl] / deb-src / libhtml-tree-perl / libhtml-tree-perl-3.23 / lib / HTML / Element / traverse.pm
1
2 # This is a .pm just to (try to) make some CPAN document converters
3 #  convert it happily as part of the dist's documentation tree.
4 package HTML::Element::traverse;
5  # Time-stamp: "2002-11-22 23:53:39 MST"
6 use HTML::Element ();
7 $VERSION = $VERSION = $HTML::Element::VERSION;
8 1;
9
10 __END__
11
12 =head1 NAME
13
14 HTML::Element::traverse - discussion of HTML::Element's traverse method
15
16 =head1 SYNOPSIS
17
18   # $element->traverse is unnecessary and obscure.
19   #   Don't use it in new code.
20
21 =head1 DESCRIPTION
22
23 C<HTML::Element> provides a method C<traverse> that traverses the tree
24 and calls user-specified callbacks for each node, in pre- or
25 post-order.  However, use of the method is quite superfluous: if you
26 want to recursively visit every node in the tree, it's almost always
27 simpler to write a subroutine does just that, than it is to bundle up
28 the pre- and/or post-order code in callbacks for the C<traverse>
29 method.
30
31 =head1 EXAMPLES
32
33 Suppose you want to traverse at/under a node $tree and give elements
34 an 'id' attribute unless they already have one.
35
36 You can use the C<traverse> method:
37
38   {
39     my $counter = 'x0000';
40     $start_node->traverse(
41       [ # Callbacks;
42         # pre-order callback:
43         sub {
44           my $x = $_[0];
45           $x->attr('id', $counter++) unless defined $x->attr('id');
46           return HTML::Element::OK; # keep traversing
47         },
48         # post-order callback:
49         undef
50       ],
51       1, # don't call the callbacks for text nodes
52     );
53   }
54
55 or you can just be simple and clear (and not have to understand the
56 calling format for C<traverse>) by writing a sub that traverses the
57 tree by just calling itself:
58
59   {
60     my $counter = 'x0000';
61     sub give_id {
62       my $x = $_[0];
63       $x->attr('id', $counter++) unless defined $x->attr('id');
64       foreach my $c ($x->content_list) {
65         give_id($c) if ref $c; # ignore text nodes
66       }
67     };
68     give_id($start_node);
69   }
70
71 See, isn't that nice and clear?
72
73 But, if you really need to know:
74
75 =head1 THE TRAVERSE METHOD
76
77 The C<traverse()> method is a general object-method for traversing a
78 tree or subtree and calling user-specified callbacks.  It accepts the
79 following syntaxes:
80
81 =over
82
83 =item $h->traverse(\&callback)
84
85 =item or $h->traverse(\&callback, $ignore_text)
86
87 =item or $h->traverse( [\&pre_callback,\&post_callback] , $ignore_text)
88
89 =back
90
91 These all mean to traverse the element and all of its children.  That
92 is, this method starts at node $h, "pre-order visits" $h, traverses its
93 children, and then will "post-order visit" $h.  "Visiting" means that
94 the callback routine is called, with these arguments:
95
96     $_[0] : the node (element or text segment),
97     $_[1] : a startflag, and
98     $_[2] : the depth
99
100 If the $ignore_text parameter is given and true, then the pre-order
101 call I<will not> be happen for text content.
102
103 The startflag is 1 when we enter a node (i.e., in pre-order calls) and
104 0 when we leave the node (in post-order calls).
105
106 Note, however, that post-order calls don't happen for nodes that are
107 text segments or are elements that are prototypically empty (like "br",
108 "hr", etc.).
109
110 If we visit text nodes (i.e., unless $ignore_text is given and true),
111 then when text nodes are visited, we will also pass two extra
112 arguments to the callback:
113
114     $_[3] : the element that's the parent
115              of this text node
116     $_[4] : the index of this text node
117              in its parent's content list
118
119 Note that you can specify that the pre-order routine can
120 be a different routine from the post-order one:
121
122     $h->traverse( [\&pre_callback,\&post_callback], ...);
123
124 You can also specify that no post-order calls are to be made,
125 by providing a false value as the post-order routine:
126
127     $h->traverse([ \&pre_callback,0 ], ...);
128
129 And similarly for suppressing pre-order callbacks:
130
131     $h->traverse([ 0,\&post_callback ], ...);
132
133 Note that these two syntaxes specify the same operation:
134
135     $h->traverse([\&foo,\&foo], ...);
136     $h->traverse( \&foo       , ...);
137
138 The return values from calls to your pre- or post-order 
139 routines are significant, and are used to control recursion
140 into the tree.
141
142 These are the values you can return, listed in descending order
143 of my estimation of their usefulness:
144
145 =over
146
147 =item HTML::Element::OK, 1, or any other true value
148
149 ...to keep on traversing.
150
151 Note that C<HTML::Element::OK> et
152 al are constants.  So if you're running under C<use strict>
153 (as I hope you are), and you say:
154 C<return HTML::Element::PRUEN>
155 the compiler will flag this as an error (an unallowable
156 bareword, specifically), whereas if you spell PRUNE correctly,
157 the compiler will not complain.
158
159 =item undef, 0, '0', '', or HTML::Element::PRUNE
160
161 ...to block traversing under the current element's content.
162 (This is ignored if received from a post-order callback,
163 since by then the recursion has already happened.)
164 If this is returned by a pre-order callback, no
165 post-order callback for the current node will happen.
166 (Recall that if your callback exits with just C<return;>,
167 it is returning undef -- at least in scalar context, and
168 C<traverse> always calls your callbacks in scalar context.)
169
170 =item HTML::Element::ABORT
171
172 ...to abort the whole traversal immediately.
173 This is often useful when you're looking for just the first
174 node in the tree that meets some criterion of yours.
175
176 =item HTML::Element::PRUNE_UP
177
178 ...to abort continued traversal into this node and its parent
179 node.  No post-order callback for the current or parent
180 node will happen.
181
182 =item HTML::Element::PRUNE_SOFTLY
183
184 Like PRUNE, except that the post-order call for the current
185 node is not blocked.
186
187 =back
188
189 Almost every task to do with extracting information from a tree can be
190 expressed in terms of traverse operations (usually in only one pass,
191 and usually paying attention to only pre-order, or to only
192 post-order), or operations based on traversing. (In fact, many of the
193 other methods in this class are basically calls to traverse() with
194 particular arguments.)
195
196 The source code for HTML::Element and HTML::TreeBuilder contain
197 several examples of the use of the "traverse" method to gather
198 information about the content of trees and subtrees.
199
200 (Note: you should not change the structure of a tree I<while> you are
201 traversing it.)
202
203 [End of documentation for the C<traverse()> method]
204
205 =head2 Traversing with Recursive Anonymous Routines
206
207 Now, if you've been reading
208 I<Structure and Interpretation of Computer Programs> too much, maybe
209 you even want a recursive lambda.  Go ahead:
210
211   {
212     my $counter = 'x0000';
213     my $give_id;
214     $give_id = sub {
215       my $x = $_[0];
216       $x->attr('id', $counter++) unless defined $x->attr('id');
217       foreach my $c ($x->content_list) {
218         $give_id->($c) if ref $c; # ignore text nodes
219       }
220     };
221     $give_id->($start_node);
222     undef $give_id;
223   }
224
225 It's a bit nutty, and it's I<still> more concise than a call to the
226 C<traverse> method!
227
228 It is left as an exercise to the reader to figure out how to do the
229 same thing without using a C<$give_id> symbol at all.
230
231 It is also left as an exercise to the reader to figure out why I
232 undefine C<$give_id>, above; and why I could achieved the same effect
233 with any of:
234
235     $give_id = 'I like pie!';
236    # or...
237     $give_id = [];
238    # or even;
239     $give_id = sub { print "Mmmm pie!\n" };
240
241 But not:
242
243     $give_id = sub { print "I'm $give_id and I like pie!\n" };
244    # nor...
245     $give_id = \$give_id;
246    # nor...
247     $give_id = { 'pie' => \$give_id, 'mode' => 'a la' };
248
249 =head2 Doing Recursive Things Iteratively
250
251 Note that you may at times see an iterative implementation of
252 pre-order traversal, like so:
253
254    {
255      my @to_do = ($tree); # start-node
256      while(@to_do) {
257        my $this = shift @to_do;
258        
259        # "Visit" the node:
260        $this->attr('id', $counter++)
261         unless defined $this->attr('id');
262        
263        unshift @to_do, grep ref $_, $this->content_list;
264         # Put children on the stack -- they'll be visited next
265      }
266    }
267
268 This can I<under certain circumstances> be more efficient than just a
269 normal recursive routine, but at the cost of being rather obscure.  It
270 gains efficiency by avoiding the overhead of function-calling, but
271 since there are several method dispatches however you do it (to
272 C<attr> and C<content_list>), the overhead for a simple function call
273 is insignificant.
274
275 =head2 Pruning and Whatnot
276
277 The C<traverse> method does have the fairly neat features of 
278 the C<ABORT>, C<PRUNE_UP> and C<PRUNE_SOFTLY> signals.  None of these
279 can be implemented I<totally> straightforwardly with recursive
280 routines, but it is quite possible.  C<ABORT>-like behavior can be
281 implemented either with using non-local returning with C<eval>/C<die>:
282
283   my $died_on; # if you need to know where...
284   sub thing {
285     ... visits $_[0]...
286     ... maybe set $died_on to $_[0] and die "ABORT_TRAV" ...
287     ... else call thing($child) for each child...
288     ...any post-order visiting $_[0]...
289   }
290   eval { thing($node) };
291   if($@) {
292     if($@ =~ m<^ABORT_TRAV>) {
293       ...it died (aborted) on $died_on...
294     } else {
295       die $@; # some REAL error happened
296     }
297   }
298
299 or you can just do it with flags:
300
301   my($abort_flag, $died_on);
302   sub thing {
303     ... visits $_[0]...
304     ... maybe set $abort_flag = 1; $died_on = $_[0]; return;
305     foreach my $c ($_[0]->content_list) {
306       thing($c);
307       return if $abort_flag;
308     }
309     ...any post-order visiting $_[0]...
310     return;
311   }
312
313   $abort_flag = $died_on = undef;
314   thing($node);
315   ...if defined $abort_flag, it died on $died_on
316
317 =head1 SEE ALSO
318
319 L<HTML::Element>
320
321 =head1 COPYRIGHT
322
323 Copyright 2000,2001 Sean M. Burke
324
325 =head1 AUTHOR
326
327 Sean M. Burke, E<lt>sburke@cpan.orgE<gt>
328
329 =cut