File Coverage

blib/lib/XML/Filter/Sort/Buffer.pm
Criterion Covered Total %
statement 155 156 99.3
branch 46 50 92.0
condition 5 7 71.4
subroutine 17 17 100.0
pod 0 11 0.0
total 223 241 92.5


line stmt bran cond sub pod time code
1             # $Id: Buffer.pm,v 1.2 2005/04/20 20:04:34 grantm Exp $
2              
3             package XML::Filter::Sort::Buffer;
4              
5 3     3   141207 use strict;
  3         8  
  3         278  
6              
7             ##############################################################################
8             # G L O B A L V A R I A B L E S
9             ##############################################################################
10              
11 3     3   18 use vars qw($VERSION @ISA);
  3         8  
  3         186  
12              
13             $VERSION = '0.91';
14              
15 3     3   17 use constant NODE_TYPE => 0;
  3         7  
  3         194  
16 3     3   21 use constant NODE_DATA => 1;
  3         5  
  3         128  
17 3     3   16 use constant NODE_CONTENT => 2;
  3         5  
  3         8328  
18              
19              
20             ##############################################################################
21             # M E T H O D S
22             ##############################################################################
23              
24             ##############################################################################
25             # Contructor: new()
26             #
27             # Prepare to build a tree and match nodes against patterns to extract sort
28             # key values.
29             #
30              
31             sub new {
32 13     13 0 7510 my $class = shift;
33              
34 13         44 my $self = { @_, };
35 13         40 bless($self, $class);
36              
37              
38             # Prepare to match sort key nodes
39              
40 13   50     61 $self->{Keys} ||= [ [ '.' ] ];
41 13   100     85 $self->{_match_subs} ||= [ $self->compile_matches($self->{Keys}) ];
42              
43 13         21 $self->{_key_values} = [ ('') x @{$self->{Keys}} ];
  13         71  
44              
45 13         32 $self->{_path_name} = [];
46 13         43 $self->{_path_ns} = [];
47 13         27 $self->{_depth} = -1;
48              
49              
50             # Initialise tree building structures
51            
52 13         28 $self->{tree} = [];
53 13         38 $self->{_lists} = [];
54 13         30 $self->{_curr_list} = $self->{tree};
55              
56 13         46 return($self);
57              
58             }
59              
60              
61             ##############################################################################
62             # Class Method: compile_matches()
63             #
64             # Generates a closure to match each of the supplied sort keys. Returns a
65             # list of closures.
66             #
67              
68             sub compile_matches {
69 13     13 0 25 my $class = shift;
70 13         19 my $keys = shift;
71              
72 13         25 my @match_subs = ();
73              
74 13         25 foreach my $i (0..$#{$keys}) {
  13         43  
75 44         68 my $key_num = $i; # local copy for closure
76              
77 44         53 my($pattern, $comparison, $direction) = @{$keys->[$key_num]};
  44         100  
78 44         120 my($path, $attr) = split(/\@/, $pattern);
79 44         125 my $abs = ($path =~ m{^\.});
80              
81 44         147 $path =~ s{^\.?/*}{};
82 44         191 $path =~ s{/*$}{};
83 44         77 my @name_list = ();
84 44         56 my @ns_list = ();
85 44         121 foreach (split(/\//, $path)) {
86 49         193 my($ns, $name) = m/^(?:\{(.*?)\})?(.*)$/;
87 49         91 push @name_list, $name;
88 49         111 push @ns_list, $ns;
89             };
90              
91 44         82 my $required_depth = @name_list;
92              
93 44         60 my($attr_name, $attr_nsname);
94 44 100 66     185 if($attr and $attr =~ m/^(\{.*?\})?(.*)$/ ) {
95 14         33 $attr_name = $2;
96 14 100       39 if($1) {
97 4         7 $attr_nsname = $attr;
98             }
99             }
100              
101             # Closure which matches the path
102              
103             push @match_subs, sub {
104 134     134   153 my $self = shift;
105              
106 134 100       240 if($abs) {
107 58 100       156 return if($self->{_depth} != $required_depth);
108             }
109             else {
110 76 100       188 return if($self->{_depth} < $required_depth);
111             }
112              
113 104         204 foreach my $i (1..$required_depth) {
114 87 100       385 return unless($self->{_path_name}->[-$i] eq $name_list[-$i]);
115 50 100       149 if(defined($ns_list[-$i])) {
116 8 100       31 return unless($self->{_path_ns}->[-$i] eq $ns_list[-$i]);
117             }
118             }
119              
120 63         172 return $self->save_key_value($key_num, $attr_name, $attr_nsname);
121 44         347 };
122              
123             }
124              
125 13         87 return(@match_subs);
126             }
127              
128              
129             ##############################################################################
130             # Method: save_key_value()
131             #
132             # Once a match has been found, the matching closure will call this method to
133             # extract the key value and save it. Returns true to indicate the reference
134             # to the closure can be deleted since there is no need to try and match the
135             # same pattern again.
136             #
137              
138             sub save_key_value {
139 63     63 0 103 my($self, $key_num, $attr_name, $attr_nsname) = @_;
140              
141              
142             # Locate the element whose end event we're processing (ie: the element
143             # which owns the content list we're about to close)
144            
145 63         124 my $node = $self->{_lists}->[-1]->[-1];
146              
147              
148             # Extract the appropriate value
149              
150 63 100       103 if($attr_name) {
151 39         46 my $value = undef;
152 39 100       67 if(defined($attr_nsname)) {
153 8 100       22 if(exists($node->[NODE_DATA]->{Attributes}->{$attr_nsname})) {
154 4         11 $value = $node->[NODE_DATA]->{Attributes}->{$attr_nsname}->{Value};
155             }
156             }
157             else {
158 31         34 foreach my $attr (values %{$node->[NODE_DATA]->{Attributes}}) {
  31         187  
159 19 100       89 if($attr->{LocalName} eq $attr_name) {
160 9         16 $value = $attr->{Value};
161 9         14 last;
162             }
163             }
164             }
165 39 100       142 return unless(defined($value)); # keep looking for elem with rqd attr
166 13         30 $self->{_key_values}->[$key_num] = $value;
167             }
168             else {
169 24         68 $self->{_key_values}->[$key_num] =
170 24         30 $self->text_content(@{$node->[NODE_CONTENT]});
171             }
172              
173 37         117 return(1);
174              
175             }
176              
177              
178             ##############################################################################
179             # Method: text_content()
180             #
181             # Takes a list of nodes and recursively builds up a string containing the
182             # text content.
183             #
184              
185             sub text_content {
186 28     28 0 34 my $self = shift;
187              
188 28         37 my $text = '';
189              
190 28         71 while(@_) {
191 30         37 my $node = shift;
192 30 100       62 if(ref($node)) {
193 4 50       11 if($node->[NODE_TYPE] eq 'e') {
194 4 50       6 if(@{$node->[NODE_CONTENT]}) {
  4         31  
195 4         5 $text .= $self->text_content(@{$node->[NODE_CONTENT]})
  4         13  
196             }
197             }
198             }
199             else {
200 26         73 $text .= $node;
201             }
202             }
203              
204 28         81 return($text);
205            
206             }
207              
208              
209             ##############################################################################
210             # Method: close()
211             #
212             # Called by the buffer manager to signify that the record is complete.
213             #
214              
215             sub close {
216 13     13 0 688 my $self = shift;
217              
218 13         18 my @key_values = @{$self->{_key_values}};
  13         44  
219 13         149 foreach my $key (grep(/^_/, keys(%$self))) {
220 91         172 delete($self->{$key});
221             }
222              
223 13         77 return(@key_values);
224             }
225              
226              
227             ##############################################################################
228             # Method: to_sax()
229             #
230             # Takes a reference to the parent XML::Filter::Sort object and a list of node
231             # structures. Passes each node to the handler as SAX events, recursing into
232             # nodes as required. On initial call, node list will default to top of stored
233             # tree.
234             #
235              
236             sub to_sax {
237 28     28 0 9553 my $self = shift;
238 28         35 my $filter = shift;
239              
240 28 100       73 @_ = @{$self->{tree}} unless(@_);
  10         34  
241              
242 28         69 while(@_) {
243 54         1503 my $node = shift;
244 54 100       103 if(ref($node)) {
245 22 100       67 if($node->[NODE_TYPE] eq 'e') {
    100          
    50          
246 19         93 $filter->start_element($node->[NODE_DATA]);
247 19 100       1756 if(@{$node->[NODE_CONTENT]}) {
  19         58  
248 18         24 $self->to_sax($filter, @{$node->[NODE_CONTENT]})
  18         89  
249             }
250 19         228 $filter->end_element($node->[NODE_DATA]);
251             }
252             elsif($node->[NODE_TYPE] eq 'p') {
253 1         7 $filter->processing_instruction($node->[NODE_DATA]);
254             }
255             elsif($node->[NODE_TYPE] eq 'c') {
256 2         12 $filter->comment($node->[NODE_DATA]);
257             }
258             else {
259 0         0 die "Unhandled node type: '" . $node->[NODE_TYPE] . "'";
260             }
261             }
262             else {
263 32         147 $filter->characters( { Data => $node } );
264             }
265             }
266              
267             }
268              
269              
270             ##############################################################################
271             # SAX handlers to build buffered event tree
272             ##############################################################################
273              
274             sub start_element {
275 43     43 0 83741 my($self, $elem) = @_;
276            
277 43         77 $self->{_depth}++;
278 43 100       118 if($self->{_depth} > 0) {
279 30         35 push @{$self->{_path_name}}, $elem->{LocalName};
  30         76  
280 30 50       37 push @{$self->{_path_ns}},
  30         107  
281             (defined($elem->{NamespaceURI}) ? $elem->{NamespaceURI} : '');
282             }
283              
284 43         71 my $new_list = [];
285 43         296 my $new_node = [ 'e', { %$elem }, $new_list ];
286              
287 43         74 push @{$self->{_curr_list}}, $new_node;
  43         90  
288 43         77 push @{$self->{_lists}}, $self->{_curr_list};
  43         88  
289 43         147 $self->{_curr_list} = $new_list;
290             }
291              
292             sub characters {
293 69     69 0 3167 my($self, $char) = @_;
294 69         85 push @{$self->{_curr_list}}, $char->{Data};
  69         280  
295             }
296              
297             sub comment {
298 2     2 0 246 my($self, $comment) = @_;
299 2         4 push @{$self->{_curr_list}}, [ 'c', { %{$comment} } ];
  2         6  
  2         15  
300             }
301              
302             sub processing_instruction {
303 1     1 0 190 my($self, $pi) = @_;
304 1         1 push @{$self->{_curr_list}}, [ 'p', { %{$pi} } ];
  1         4  
  1         8  
305             }
306              
307             sub end_element {
308 43     43 0 4291 my $self = shift;
309              
310             # Check for matches against sort key patterns
311              
312 43         65 my $i = 0;
313 43         128 while(exists($self->{_match_subs}->[$i])) {
314 134 100       328 if($self->{_match_subs}->[$i]->($self)) {
315 37         45 splice(@{$self->{_match_subs}}, $i, 1); # Delete the match sub
  37         329  
316             }
317             else {
318 97         232 $i++;
319             }
320             }
321              
322 43         65 $self->{_depth}--;
323 43         45 pop @{$self->{_path_name}};
  43         74  
324 43         62 pop @{$self->{_path_ns}};
  43         72  
325              
326 43         48 $self->{_curr_list} = pop @{$self->{_lists}};
  43         140  
327              
328             }
329              
330              
331             1;
332              
333             __END__