File Coverage

blib/lib/XML/Filter/Sort.pm
Criterion Covered Total %
statement 147 202 72.7
branch 49 92 53.2
condition 7 25 28.0
subroutine 15 20 75.0
pod 9 15 60.0
total 227 354 64.1


line stmt bran cond sub pod time code
1             package XML::Filter::Sort;
2              
3 1     1   115255 use strict;
  1         3  
  1         36  
4 1     1   6 use Carp;
  1         4  
  1         84  
5              
6             require XML::SAX::Base;
7              
8              
9             ##############################################################################
10             # G L O B A L V A R I A B L E S
11             ##############################################################################
12              
13 1     1   7 use vars qw($VERSION @ISA);
  1         3  
  1         68  
14              
15             $VERSION = '1.01';
16              
17             @ISA = qw(XML::SAX::Base);
18              
19 1     1   5 use constant DEFAULT_BUFFER_MANAGER_CLASS => 'XML::Filter::Sort::BufferMgr';
  1         1  
  1         57  
20 1     1   5 use constant DISK_BUFFER_MANAGER_CLASS => 'XML::Filter::Sort::DiskBufferMgr';
  1         2  
  1         3059  
21              
22              
23             ##############################################################################
24             # M E T H O D S
25             ##############################################################################
26              
27             ##############################################################################
28             # Contructor: new()
29             #
30             # Set defaults for required properties and parse 'Keys' value from scalar to
31             # a list of lists if required.
32             #
33              
34             sub new {
35 4     4 0 4002 my $proto = shift;
36              
37 4   33     27 my $class = ref($proto) || $proto;
38 4         32 my $self = $class->SUPER::new(@_);
39              
40 4 100       494 croak "You must set the 'Record' option" unless($self->{Record});
41              
42              
43             # Select memory vs disk buffering (or custom buffering class)
44            
45 3 50       14 if($self->{TempDir}) {
46 0   0     0 $self->{BufferManagerClass} ||= DISK_BUFFER_MANAGER_CLASS;
47             }
48 3 50       11 unless($self->{BufferManagerClass}) {
49 3         8 $self->{BufferManagerClass} = DEFAULT_BUFFER_MANAGER_CLASS;
50             }
51 3         21 my $mod_path = join('/', split(/::/, $self->{BufferManagerClass} . '.pm'));
52 3         1008 require $mod_path;
53              
54              
55             # Organise sort keys into a list of 3-element lists
56            
57 3 100       16 $self->{Keys} = '.' unless($self->{Keys});
58 3 100       12 unless(ref($self->{Keys})) { # parse scalar to a list of lists
59 2         4 my @keys = ();
60 2         14 foreach (split(/[\r\n;]/, $self->{Keys})) {
61 6 100       45 next unless(/\S/);
62 4         16 s/,/ /g;
63 4         37 my @key = /(\S+)/g;
64 4         12 push @keys, \@key;
65             }
66 2         8 $self->{Keys} = \@keys;
67             }
68 3         6 foreach my $key (@{$self->{Keys}}) {
  3         8  
69 7 50       20 croak "Keys must be a list of lists" unless(ref($key));
70 7   100     23 $key->[1] ||= 'alpha';
71 7 50       16 unless(ref($key->[1])) {
72 7 100       26 $key->[1] = ($key->[1] =~ /^n/i ? 'num' : 'alpha');
73             }
74 7 100 100     45 $key->[2] = ($key->[2] && $key->[2] =~ /^d/i ? 'desc' : 'asc');
75             }
76              
77              
78             # Precompile a closure to match each key
79              
80 3 50       37 if($self->{BufferManagerClass}->can('compile_matches')) {
81 3         15 $self->{_match_subs} = [
82             $self->{BufferManagerClass}->compile_matches($self->{Keys})
83             ];
84             }
85              
86              
87             # Build up a list of options to be passed to buffers/buffer managers
88              
89 3 50       11 if($self->{MaxMem}) {
90 0 0       0 if(uc($self->{MaxMem}) =~ /^\s*(\d+)(K|M)?$/) {
91 0         0 $self->{MaxMem} = $1;
92 0 0 0     0 $self->{MaxMem} *= 1024 if($2 and $2 eq 'K');
93 0 0 0     0 $self->{MaxMem} *= 1024 * 1024 if($2 and $2 eq 'M');
94             }
95             else {
96 0         0 croak "Illegal value for 'MaxMem': $self->{MaxMem}";
97             }
98             }
99              
100 3         44 $self->{BufferOpts} = {
101 3   33     5 Keys => [ @{$self->{Keys}} ],
102             _match_subs => $self->{_match_subs},
103             IgnoreCase => $self->{IgnoreCase},
104             NormaliseKeySpace => $self->{NormaliseKeySpace} ||
105             $self->{NormalizeKeySpace},
106             KeyFilterSub => $self->{KeyFilterSub},
107             TempDir => $self->{TempDir},
108             MaxMem => $self->{MaxMem},
109             };
110              
111              
112 3         16 return(bless($self,$class));
113             }
114              
115              
116             ##############################################################################
117             # Method: start_document()
118             #
119             # Initialise handler structures and propagate event.
120             #
121              
122             sub start_document {
123 1     1 1 2166 my $self = shift;
124              
125              
126             # Track path to current element
127              
128 1         7 $self->{path_name} = [];
129 1         3 $self->{path_ns} = [];
130 1         3 $self->{prefixes} = [];
131 1         4 $self->{depth} = 0;
132              
133              
134             # Initialise pattern matching for record elements
135              
136 1         5 my @parts = split(/\//, $self->{Record});
137 1 50       5 if($parts[0] eq '') {
138 0         0 $self->{abs_match} = 1;
139 0         0 shift @parts;
140             }
141             else {
142 1         3 $self->{abs_match} = 0;
143             }
144 1         3 $self->{rec_path_name} = [ ];
145 1         3 $self->{rec_path_ns} = [ ];
146 1         3 foreach (@parts) {
147 1 50       8 if(/^(?:\{(.*?)\})?(.*)$/) {
148 1         14 push @{$self->{rec_path_name}}, $2;
  1         5  
149 1         2 push @{$self->{rec_path_ns}}, $1;
  1         5  
150             }
151             }
152 1         4 $self->{required_depth} = @parts;
153              
154 1         13 $self->SUPER::start_document(@_);
155             }
156              
157              
158             ##############################################################################
159             # Method: start_element()
160             #
161             # Marshalls events either to the default handler or to a record buffer.
162             # Also handles the creation of buffers as record elements are encountered.
163             # Two extra considerations increase complexity: contiguous character events
164             # are being merged; and each 'record' element takes it's leading whitespace
165             # with it.
166             #
167              
168             sub start_element {
169 7     7 1 667 my $self = shift;
170 7         8 my $element = shift;
171              
172              
173 7 100       22 return $self->start_prefixed_element($element) if($self->{passthru});
174              
175             # Add this element's details to the end of the list (for recognising records)
176              
177 4         5 push @{$self->{path_name}}, $element->{LocalName};
  4         9  
178 4 50       6 push @{$self->{path_ns}},
  4         15  
179             (defined($element->{NamespaceURI}) ? $element->{NamespaceURI} : '');
180 4         6 $self->{depth}++;
181              
182              
183             # Do we have a record buffer open?
184              
185 4 50       14 if($self->{buffer}) {
186 0         0 $self->{record_depth}++;
187 0         0 $self->send_characters();
188 0         0 $self->{buffer}->start_element($element);
189 0         0 return;
190             }
191              
192              
193             # Any leading (non-whitespace) text?
194              
195 4 50       11 if($self->{buffered_text}) {
196 0         0 $self->flush_buffers();
197 0         0 $self->send_characters();
198             }
199              
200            
201             # Is this a record?
202              
203 4 100       12 if($self->match_record()) {
204            
205 3         4 $self->{record_depth} = 1;
206              
207 3 100       10 unless($self->{buffer_manager}) {
208 1         9 $self->{buffer_manager} = $self->{BufferManagerClass}->new(
209 1         3 %{$self->{BufferOpts}}
210             );
211             }
212              
213 3         13 $self->{buffer} = $self->{buffer_manager}->new_buffer();
214              
215 3         8 $self->send_characters();
216 3         11 $self->{buffer}->start_element($element);
217 3         6 return;
218             }
219              
220              
221             # Send buffered data and this event to the downstream handler
222              
223 1         6 $self->flush_buffers();
224 1         4 $self->send_characters();
225 1         5 $self->start_prefixed_element($element);
226             }
227              
228              
229             ##############################################################################
230             # Method: end_element()
231             #
232             # Marshalls events either to the default handler or to a record buffer.
233             # Also handles closing the current buffer object as the end of a record is
234             # encountered.
235             #
236              
237             sub end_element {
238 7     7 1 21 my $self = shift;
239 7         7 my $element = shift;
240              
241              
242 7 100       21 return $self->end_prefixed_element($element) if($self->{passthru});
243              
244              
245 4         4 pop @{$self->{path_name}};
  4         7  
246 4         5 pop @{$self->{path_ns}};
  4         8  
247 4         5 $self->{depth}--;
248              
249              
250             # Do we have a record buffer open?
251            
252 4 100       11 if($self->{buffer}) {
253 3         6 $self->send_characters();
254 3         10 $self->{buffer}->end_element($element);
255 3         4 $self->{record_depth}--;
256 3 50       9 if($self->{record_depth} == 0) {
257 3         10 $self->{buffer_manager}->close_buffer($self->{buffer});
258 3         6 delete($self->{buffer});
259             }
260 3         7 return;
261             }
262              
263             # No, then do we have any complete buffered records?
264            
265 1         3 $self->flush_buffers();
266              
267 1         9 $self->send_characters();
268 1         3 $self->end_prefixed_element($element);
269              
270             }
271              
272              
273             ##############################################################################
274             # Method: characters()
275             #
276             # Buffer character events for two reasons:
277             # - to merge contiguous character data (simplifies pattern matching logic)
278             # - to enable 'record' elements to take their leading whitespace with them
279             #
280              
281             sub characters {
282 6     6 1 17 my $self = shift;
283 6         8 my $char = shift;
284              
285 6 100       26 return $self->SUPER::characters($char) if($self->{passthru});
286              
287 3 50       9 unless(exists($self->{char_buffer})) {
288 3         7 $self->{char_buffer} = '';
289 3         5 $self->{buffered_text} = 0;
290             }
291 3         7 $self->{char_buffer} .= $char->{Data};
292 3         13 $self->{buffered_text} |= ($char->{Data} =~ /\S/);
293             }
294              
295              
296             ##############################################################################
297             # Method: ignorable_whitespace()
298             #
299             # Discard ignorable whitespace if required, otherwise send it on as
300             # character events.
301             #
302             # Yes, this is a dirty hack, but it's getting late and I haven't got a
303             # parser that generates them anyway.
304             #
305              
306             sub ignorable_whitespace {
307 0     0 1 0 my $self = shift;
308 0         0 my $char = shift;
309              
310 0 0       0 $self->characters($char) unless($self->{SkipIgnorableWS});
311             }
312              
313              
314             ##############################################################################
315             # Method: start_prefix_mapping()
316             # Method: end_prefix_mapping()
317             #
318             # Suppress these events as they need to remain synchronised with the
319             # start/end_element events (which may be re-ordered). Replacement events are
320             # generated by start/end_prefixed_element().
321             #
322              
323 0     0 1 0 sub start_prefix_mapping { }
324 0     0 1 0 sub end_prefix_mapping { }
325              
326              
327             ##############################################################################
328             # Method: start_prefixed_element()
329             #
330             # Sends a start_element() event to the downstream handler, but re-generates
331             # start_prefix_mapping() events first.
332             #
333              
334             sub start_prefixed_element {
335 4     4 0 7 my $self = shift;
336 4         5 my $elem = shift;
337              
338 4         5 my @prefixes;
339 4         4 foreach my $attr (values %{$elem->{Attributes}}) {
  4         14  
340 0 0 0     0 if($attr->{Name} and $attr->{Name} eq 'xmlns') {
    0 0        
341 0         0 unshift @prefixes, '', $attr->{Value};
342             }
343             elsif($attr->{Prefix} and $attr->{Prefix} eq 'xmlns') {
344 0         0 push @prefixes, $attr->{LocalName}, $attr->{Value};
345             }
346             }
347            
348 4 50       10 if(@prefixes) {
349 0         0 push @{$self->{prefixes}}, [ @prefixes ];
  0         0  
350 0         0 while(@prefixes) {
351 0         0 my $prefix = shift @prefixes;
352 0         0 my $uri = shift @prefixes;
353 0         0 $self->SUPER::start_prefix_mapping({
354             Prefix => $prefix,
355             NamespaceURI => $uri,
356             });
357             }
358             }
359             else {
360 4         5 push @{$self->{prefixes}}, undef;
  4         9  
361             }
362            
363 4         21 $self->SUPER::start_element($elem);
364             }
365              
366              
367             ##############################################################################
368             # Method: end_prefixed_element()
369             #
370             # Sends an end_element() event to the downstream handler, and follows it with
371             # re-generated end_prefix_mapping() events.
372             #
373              
374             sub end_prefixed_element {
375 4     4 0 5 my $self = shift;
376 4         5 my $elem = shift;
377              
378 4         16 $self->SUPER::end_element($elem);
379              
380 4         498 my $prefixes = pop @{$self->{prefixes}};
  4         7  
381              
382 4 50       33 if($prefixes) {
383 0         0 while(@$prefixes) {
384 0         0 my $prefix = shift @$prefixes;
385 0         0 my $uri = shift @$prefixes;
386 0         0 $self->SUPER::end_prefix_mapping({
387             Prefix => $prefix,
388             NamespaceURI => $uri,
389             });
390             }
391             }
392              
393             }
394              
395              
396             ##############################################################################
397             # Method: comment()
398             #
399             # Send comments to buffer if we have one open, otherwise flush any buffered
400             # records before propagating event.
401             #
402              
403             sub comment {
404 0     0 1 0 my $self = shift;
405 0         0 my $comment = shift;
406              
407 0 0       0 return $self->SUPER::comment($comment) if($self->{passthru});
408              
409 0 0       0 if($self->{buffer}) {
410 0         0 $self->send_characters();
411 0         0 $self->{buffer}->comment($comment);
412 0         0 return;
413             }
414              
415 0         0 $self->flush_buffers();
416 0         0 $self->send_characters();
417 0         0 $self->SUPER::comment($comment);
418             }
419              
420              
421             ##############################################################################
422             # Method: processing_instruction()
423             #
424             # Send PIs to downstream handler but flush buffered records & text first.
425             #
426              
427             sub processing_instruction {
428 0     0 1 0 my $self = shift;
429 0         0 my $pi = shift;
430              
431 0 0       0 return $self->SUPER::processing_instruction($pi) if($self->{passthru});
432              
433 0 0       0 if($self->{buffer}) {
434 0         0 $self->send_characters();
435 0         0 $self->{buffer}->processing_instruction($pi);
436 0         0 return;
437             }
438              
439 0         0 $self->flush_buffers();
440 0         0 $self->send_characters();
441 0         0 $self->SUPER::processing_instruction($pi);
442             }
443              
444              
445             ##############################################################################
446             # Method: send_characters()
447             #
448             # Contiguous character events are concatenated into a buffer. This routine
449             # sends the buffer contents to the open buffer if there is one, or the
450             # downstream handler otherwise.
451             #
452              
453             sub send_characters {
454 8     8 0 11 my $self = shift;
455              
456 8 100       24 return unless(exists $self->{char_buffer});
457 3 50       8 if($self->{buffer}) {
458 3         17 $self->{buffer}->characters({Data => $self->{char_buffer}});
459             }
460             else {
461 0         0 $self->SUPER::characters({Data => $self->{char_buffer}});
462             }
463 3         7 delete($self->{char_buffer});
464 3         14 delete($self->{buffered_text});
465             }
466              
467              
468             ##############################################################################
469             # Method: flush_buffers()
470             #
471             # If there are any records buffered, sends them to the downstream handler.
472             #
473              
474             sub flush_buffers {
475 2     2 0 5 my $self = shift;
476              
477 2 100       6 if($self->{buffer_manager}) {
478 1         3 $self->{passthru} = 1;
479 1         6 $self->{buffer_manager}->to_sax($self);
480 1         3 $self->{passthru} = 0;
481 1         22 delete($self->{buffer_manager});
482             }
483             }
484              
485              
486             ##############################################################################
487             # Method: match_record()
488             #
489             # Returns true if the path to the current element matches the 'Record' option
490             # passed to the constructor.
491             #
492              
493             sub match_record {
494 4     4 0 7 my $self = shift;
495              
496              
497 4 50       89 if($self->{abs_match}) {
498 0 0       0 return if($self->{depth} != $self->{required_depth});
499             }
500             else {
501 4 50       15 return if($self->{depth} < $self->{required_depth});
502             }
503              
504 4         9 foreach my $i (1..$self->{required_depth}) {
505 4 100       18 return unless($self->{path_name}->[-$i] eq $self->{rec_path_name}->[-$i]);
506 3 50       15 if(defined($self->{rec_path_ns}->[-$i])) {
507 0 0       0 return unless($self->{path_ns}->[-$i] eq $self->{rec_path_ns}->[-$i]);
508             }
509             }
510              
511 3         10 return(1);
512             }
513              
514              
515             1;
516             __END__