File Coverage

blib/lib/PITA/XML/SAXDriver.pm
Criterion Covered Total %
statement 209 224 93.3
branch 37 58 63.7
condition 2 2 100.0
subroutine 26 32 81.2
pod 10 10 100.0
total 284 326 87.1


line stmt bran cond sub pod time code
1             package PITA::XML::SAXDriver;
2              
3             =pod
4              
5             =head1 NAME
6              
7             PITA::XML::SAXDriver - Implements a SAX Driver for PITA::XML objects
8              
9             =head1 DESCRIPTION
10              
11             Although you won't need to use it directly, this class provides a
12             "SAX Driver" class that converts a L object into a stream
13             of SAX events (which will mostly likely be written to a file).
14              
15             Please note that this class is incomplete at this time. Although you
16             can create objects, you can't actually run them yet.
17              
18             =head1 METHODS
19              
20             =cut
21              
22 10     10   274 use 5.006;
  10         67  
  10         476  
23 10     10   56 use strict;
  10         20  
  10         366  
24 10     10   58 use Carp ();
  10         18  
  10         185  
25 10     10   55 use Params::Util ':ALL';
  10         18  
  10         3319  
26 10     10   10722 use Class::Autouse 'XML::SAX::Writer';
  10         97380  
  10         83  
27 10     10   3571 use PITA::XML ();
  10         22  
  10         148  
28 10     10   52 use XML::SAX::Base ();
  10         25  
  10         205  
29              
30 10     10   50 use vars qw{$VERSION @ISA};
  10         23  
  10         711  
31             BEGIN {
32 10     10   28 $VERSION = '0.52';
33 10         34928 @ISA = 'XML::SAX::Base';
34             }
35              
36              
37              
38              
39              
40             #####################################################################
41             # Constructor
42              
43             =pod
44              
45             =head2 new
46              
47             # Create a SAX Driver to generate in-memory
48             $driver = PITA::XML::SAXDriver->new();
49            
50             # ... or to stream (write) to a file
51             $driver = PITA::XML::SAXDriver->new( Output => 'filename' );
52            
53             # ... or to send the events to a custom handler
54             $driver = PITA::XML::SAXDriver->new( Handler => $handler );
55              
56             The C constructor creates a new SAX generator for PITA-XML files.
57              
58             It takes a named param of B an XML Handler object, or an
59             C value that is compatible with L.
60              
61             Returns a C object, or dies on error.
62              
63             =cut
64              
65             sub new {
66 6     6 1 14 my $class = shift;
67 6         65 my $self = bless {
68             NamespaceURI => PITA::XML->XMLNS,
69             Prefix => '',
70             @_,
71             }, $class;
72              
73             # Add a default SAX Handler
74 6 50       121 unless ( $self->{Handler} ) {
75             # We are going to create a file writer to anything
76             # that it supports. So we will need an Output param.
77 6 50       27 unless ( $self->{Output} ) {
78 0         0 my $Output = '';
79 0         0 $self->{Output} = \$Output;
80             }
81              
82             # Create the file writer
83 6 50       88 $self->{Handler} = XML::SAX::Writer->new(
84             Output => $self->{Output},
85             ) or Carp::croak("Failed to create XML Writer for Output");
86             }
87              
88             # Check the namespace
89 6 50       64905 unless ( _STRING($self->{NamespaceURI}) ) {
90 0         0 Carp::croak("Invalid NamespaceURI");
91             }
92              
93             # Flag that an xmlns attribute be added
94             # to the first element in the document
95 6         23 $self->{xmlns} = $self->{NamespaceURI};
96              
97 6         28 $self;
98             }
99              
100             =pod
101              
102             =head2 NamespaceURI
103              
104             The C returns the name of the XML namespace being used
105             in the file generation.
106              
107             While PITA is still in development, this should be something like
108             the following, where C<$VERSION> is the L version string.
109              
110             http://ali.as/xml/schema/pita-xml/$VERSION
111              
112             =cut
113              
114             sub NamespaceURI {
115 0     0 1 0 $_[0]->{NamespaceURI};
116             }
117              
118             =pod
119              
120             =head2 Prefix
121              
122             The C returns the name of the XML prefix being used for the output.
123              
124             =cut
125              
126             sub Prefix {
127 0     0 1 0 $_[0]->{Prefix};
128             }
129              
130             =pod
131              
132             =head2 Handler
133              
134             The C returns the SAX Handler object that the SAX events are being
135             sent to. This will be or the SAX Handler object you originally passed
136             in, or a L object pointing at your C value.
137              
138             =cut
139              
140             sub Handler {
141 0     0 1 0 $_[0]->{Handler};
142             }
143              
144             =pod
145              
146             =head2 Output
147              
148             If you did not provide a custom SAX Handler, the C accessor
149             returns the location you are writing the XML output to.
150              
151             If you did not provide a C or C param to the constructor,
152             then this returns a C reference containing the XML as a string.
153              
154             =cut
155              
156             sub Output {
157 0     0 1 0 $_[0]->{Output};
158             }
159              
160              
161              
162              
163              
164             #####################################################################
165             # Main SAX Methods
166              
167             # Prevent use as a SAX Filter or SAX Parser
168             # We only generate SAX events, we don't consume them.
169             #sub start_document {
170             # my $class = ref $_[0] || $_[0];
171             # die "$class is not a SAX Filter or Driver, it cannot recieve events";
172             #}
173              
174             sub parse {
175 6     6 1 13 my $self = shift;
176 6         69 my $root = _INSTANCE(shift, 'PITA::XML::Storable');
177 6 50       23 unless ( $root ) {
178 0         0 Carp::croak("Did not provide a writable root object");
179             }
180              
181             # Attach the xmlns to the first tag
182 6 50       25 if ( $self->{NamespaceURI} ) {
183 6         15 $self->{xmlns} = $self->{NamespaceURI};
184             }
185              
186             # Generate the SAX2 events
187 6         27 $self->start_document( {} );
188 6 100       101 if ( _INSTANCE($root, 'PITA::XML::Report') ) {
    100          
    50          
189 2         8 $self->_parse_report( $root );
190             } elsif ( _INSTANCE($root, 'PITA::XML::Request') ) {
191 1         5 $self->_parse_request( $root );
192             } elsif ( _INSTANCE($root, 'PITA::XML::Guest') ) {
193 3         15 $self->_parse_guest( $root );
194             } else {
195 0         0 die("Support for " . ref($root) . " not implemented");
196             }
197 6         49 $self->end_document( {} );
198              
199 6         471 return 1;
200             }
201              
202             sub start_document {
203 6     6 1 11 my $self = shift;
204              
205             # Do the normal start_document tasks
206 6         50 $self->SUPER::start_document( @_ );
207              
208             # And always put the XML declaration at the start
209 6         2654 $self->xml_decl( {
210             Version => '1.0',
211             Encoding => 'UTF-8',
212             } );
213              
214 6         377 return 1;
215             }
216              
217             # Generate events for the parent PITA::XML::Report object
218             sub _parse_report {
219 2     2   5 my $self = shift;
220 2         4 my $report = shift;
221              
222             # Send the open tag
223 2         8 my $element = $self->_element( 'report' );
224 2         11 $self->start_element( $element );
225              
226             # Iterate over the individual installations
227 2         284 foreach my $install ( $report->installs ) {
228 1         5 $self->_parse_install( $install );
229             }
230              
231             # Send the close tag
232 2         7 $self->end_element($element);
233              
234 2         167 return 1;
235             }
236              
237             # Generate events for a single install
238             sub _parse_install {
239 1     1   2 my $self = shift;
240 1         2 my $install = shift;
241              
242             # Send the open tag
243 1         4 my $element = $self->_element( 'install' );
244 1         4 $self->start_element( $element );
245              
246             # Send the optional configuration tag
247 1         79 $self->_parse_request( $install->request );
248              
249             # Send the optional platform tag
250 1         5 $self->_parse_platform( $install->platform );
251              
252             # Add the command tags
253 1         5 foreach my $command ( $install->commands ) {
254 1         4 $self->_parse_command( $command );
255             }
256              
257             # Add the test tags
258 1         6 foreach my $test ( $install->tests ) {
259 1         5 $self->_parse_test( $test );
260             }
261              
262             # Add the optional analysis tag
263 1         6 my $analysis = $install->analysis;
264 1 50       5 if ( $analysis ) {
265 0         0 $self->_parse_analysis( $analysis );
266             }
267              
268             # Send the close tag
269 1         4 $self->end_element( $element );
270              
271 1         57 return 1;
272             }
273              
274             # Generate events for a request
275             sub _parse_request {
276 2     2   4 my $self = shift;
277 2         6 my $request = shift;
278              
279             # Send the open tag
280 2 100       13 my $attr = $request->id
281             ? { id => $request->id }
282             : { };
283 2         12 my $element = $self->_element( 'request', $attr );
284 2         9 $self->start_element( $element );
285              
286             # Send the main accessors
287 2         255 $self->_accessor_element( $request, 'scheme' );
288 2         314 $self->_accessor_element( $request, 'distname' );
289              
290             # Send the file(s)
291 2         284 $self->_parse_file( $request->file );
292              
293             # Send the optional authority information
294 2 100       10 if ( $request->authority ) {
295 1         3 $self->_accessor_element( $request, 'authority' );
296 1 50       187 if ( $request->authpath ) {
297 1         4 $self->_accessor_element( $request, 'authpath' );
298             }
299             }
300              
301             # Send the close tag
302 2         103 $self->end_element( $element );
303              
304 2         102 return 1;
305             }
306              
307             # Generate events for a guest
308             sub _parse_guest {
309 3     3   7 my $self = shift;
310 3         5 my $guest = shift;
311              
312             # Send the open tag
313 3 100       15 my $attr = $guest->id
314             ? { id => $guest->id }
315             : { };
316 3         17 my $element = $self->_element( 'guest', $attr );
317 3         13 $self->start_element( $element );
318              
319             # Send the main accessors
320 3         394 $self->_accessor_element( $guest, 'driver' );
321              
322             # Iterate over the individual files
323 3         470 foreach my $file ( $guest->files ) {
324 3         11 $self->_parse_file( $file );
325             }
326              
327             # Send each of the config variables
328 3         16 my $config = $guest->config;
329 3         56 foreach my $name ( sort keys %$config ) {
330 6         452 my $el = $self->_element( 'config', { name => $name } );
331 6         24 $self->start_element( $el );
332 6 50       502 defined($config->{$name})
333             ? $self->characters( $config->{$name} )
334             : $self->_undef;
335 6         70 $self->end_element( $el );
336             }
337              
338             # Iterate over the individual platforms
339 3         358 foreach my $platform ( $guest->platforms ) {
340 1         5 $self->_parse_platform( $platform );
341             }
342              
343             # Send the close tag
344 3         11 $self->end_element($element);
345              
346 3         153 return 1;
347             }
348              
349             # Generate events for a file
350             sub _parse_file {
351 5     5   10 my $self = shift;
352 5         16 my $file = shift;
353              
354             # Send the open tag
355 5         13 my $element = $self->_element( 'file' );
356 5         23 $self->start_element( $element );
357              
358             # Send the main accessors
359 5         297 $self->_accessor_element( $file, 'filename' );
360              
361             # Send the optional resource name
362 5 100       710 if ( defined $file->resource ) {
363 1         4 my $el = $self->_element( 'resource' );
364 1         4 $self->start_element( $el );
365 1         40 $self->characters( $file->resource );
366 1         8 $self->end_element( $el );
367             }
368              
369             # Send the optional digest
370 5 50       101 if ( defined $file->digest ) {
371 5         17 my $el = $self->_element( 'digest' );
372 5         19 $self->start_element( $el );
373 5         323 $self->characters( $file->digest->as_string );
374 5         62 $self->end_element( $el );
375             }
376              
377             # Send the close tag
378 5         592 $self->end_element( $element );
379              
380 5         355 return 1;
381             }
382              
383             # Generate events for a platform configuration
384             sub _parse_platform {
385 2     2   6 my $self = shift;
386 2         4 my $platform = shift;
387              
388             # Send the open tag
389 2         13 my $element = $self->_element( 'platform' );
390 2         7 $self->start_element( $element );
391              
392             # Send the scheme
393 2 50       117 if ( $platform->scheme ) {
394 2         6 my $el = $self->_element( 'scheme' );
395 2         7 $self->start_element( $el );
396 2         146 $self->characters( $platform->scheme );
397 2         26 $self->end_element( $el );
398             }
399              
400             # Send the path
401 2 50       214 if ( $platform->path ) {
402 2         6 my $el = $self->_element( 'path' );
403 2         8 $self->start_element( $el );
404 2         112 $self->characters( $platform->path );
405 2         24 $self->end_element( $el );
406             }
407              
408             # Send each of the environment variables
409 2         301 my $env = $platform->env;
410 2         9 foreach my $name ( sort keys %$env ) {
411 2         11 my $el = $self->_element( 'env', { name => $name } );
412 2         9 $self->start_element( $el );
413 2 50       153 defined($env->{$name})
414             ? $self->characters( $env->{$name} )
415             : $self->_undef;
416 2         24 $self->end_element( $el );
417             }
418              
419             # Send each of the config variables
420 2         222 my $config = $platform->config;
421 2         8 foreach my $name ( sort keys %$config ) {
422 2         9 my $el = $self->_element( 'config', { name => $name } );
423 2         8 $self->start_element( $el );
424 2 50       145 defined($config->{$name})
425             ? $self->characters( $config->{$name} )
426             : $self->_undef;
427 2         103 $self->end_element( $el );
428             }
429              
430             # Send the close tag
431 2         101 $self->end_element( $element );
432              
433 2         112 return 1;
434             }
435              
436             sub _parse_command {
437 1     1   3 my $self = shift;
438 1         2 my $command = shift;
439              
440             # Send the open tag
441 1         3 my $element = $self->_element( 'command' );
442 1         4 $self->start_element( $element );
443              
444             # Send the accessors
445 1         53 $self->_accessor_element( $command, 'cmd' );
446 1         196 $self->_accessor_element( $command, 'stdout' );
447 1         140 $self->_accessor_element( $command, 'stderr' );
448              
449             # Send the close tag
450 1         59 $self->end_element( $element );
451              
452 1         57 return 1;
453             }
454              
455             sub _parse_test {
456 1     1   3 my $self = shift;
457 1         2 my $test = shift;
458              
459             # Send the open tag
460 1         7 my $attrs = {
461             language => $test->language,
462             };
463 1 50       6 if ( defined $test->name ) {
464 1         3 $attrs->{name} = $test->name;
465             }
466 1         3 my $element = $self->_element( 'test', $attrs );
467 1         5 $self->start_element( $element );
468              
469             # Send the accessor elements
470 1         91 $self->_accessor_element( $test, 'stdout' );
471 1 50       141 if ( defined $test->stderr ) {
472 1         5 $self->_accessor_element( $test, 'stderr' );
473             }
474 1 50       60 if ( defined $test->exitcode ) {
475 1         4 $self->_accessor_element( $test, 'exitcode' );
476             }
477              
478             # Send the close tag
479 1         113 $self->end_element( $element );
480              
481 1         56 return 1;
482             }
483              
484             sub _parse_analysis {
485 0     0   0 die "CODE INCOMPLETE";
486             }
487              
488             # Specifically send an undef tag pair
489             sub _undef {
490 2     2   4 my $self = shift;
491 2         5 my $el = $self->_element('null');
492 2         6 $self->start_element( $el );
493 2         148 $self->end_element( $el );
494             }
495              
496              
497              
498              
499              
500             #####################################################################
501             # Support Methods
502              
503             # Make sure the first element gets an xmlns attribute
504             sub start_element {
505 59     59 1 74 my $self = shift;
506 59         101 my $element = shift;
507 59         86 my $xmlns = delete $self->{xmlns};
508              
509             # Shortcut for the most the common case
510 59 100       114 unless ( $xmlns ) {
511 53         191 return $self->SUPER::start_element( $element );
512             }
513              
514             # Add the XMLNS Attribute
515 6         35 $element->{Attributes}->{'xmlns'} = {
516             Prefix => '',
517             LocalName => 'xmlns',
518             Name => 'xmlns',
519             Value => $xmlns,
520             };
521              
522             # Pass on to the parent class
523 6         118 $self->SUPER::start_element( $element );
524             }
525              
526             # Strip out the Attributes for the end element
527             sub end_element {
528 59     59 1 151 delete $_[1]->{Attributes};
529 59         207 shift->SUPER::end_element(@_);
530             }
531              
532             sub _element {
533 59     59   81 my $self = shift;
534 59         73 my $LocalName = shift;
535 59   100     269 my $attrs = _HASH(shift) || {};
536              
537             # Localise some variables for speed
538 59         108 my $NamespaceURI = $self->{NamespaceURI};
539 59 50       128 my $Prefix = $self->{Prefix}
540             ? "$self->{Prefix}:"
541             : '';
542              
543             # Convert the attributes to the full version
544 59         97 my %Attributes = ();
545 59 50       135 if ( $attrs->{xmlns} ) {
546             # The xmlns attribute is always first
547 0         0 my $value = delete $attrs->{xmlns};
548 0         0 $Attributes{xmlns} = {
549             Name => 'xmlns',
550             #NamespaceURI => $NamespaceURI,
551             #Prefix => $Prefix,
552             #LocalName => $key,
553             Value => $value,
554             };
555             }
556 59         246 foreach my $key ( sort keys %$attrs ) {
557             #$Attributes{"{$NamespaceURI}$key"} = {
558 14         82 $Attributes{$key} = {
559             Name => $Prefix . $key,
560             #NamespaceURI => $NamespaceURI,
561             #Prefix => $Prefix,
562             #LocalName => $key,
563             Value => $attrs->{$key},
564             };
565             }
566              
567             # Complete the main element
568             return {
569 59         292 Name => $Prefix . $LocalName,
570             #NamespaceURI => $NamespaceURI,
571             #Prefix => $Prefix,
572             #LocalName => $LocalName,
573             Attributes => \%Attributes,
574             };
575             }
576              
577             # Send a matching tag for a known object accessor
578             sub _accessor_element {
579 20     20   56 my $self = shift;
580 20         25 my $object = shift;
581 20         26 my $method = shift;
582 20         87 my $value = $object->$method();
583              
584             # Generate the element and send it
585 20         47 my $el = $self->_element( $method );
586 20         50 $self->start_element( $el );
587 20         1468 $self->characters( $value );
588 20         320 $self->end_element( $el );
589             }
590              
591             # Auto-preparation of the text
592             sub characters {
593 38     38 1 118 my $self = shift;
594              
595             # A { Data => '...' } string
596 38 50       113 if ( _HASH($_[0]) ) {
597 0         0 return $self->SUPER::characters(shift);
598             }
599              
600             # A normal string, by reference
601 38 100       137 if ( _SCALAR0($_[0]) ) {
602 4         7 my $scalar_ref = shift;
603 4         29 return $self->SUPER::characters( {
604             Data => $$scalar_ref,
605             } );
606             }
607              
608             # Must be a normal string
609             $self->SUPER::characters( {
610 34         158 Data => shift,
611             } );
612             }
613              
614             ### Not sure if we escape here.
615             ### Just pass through for now.
616 0     0     sub _escape { $_[1] }
617              
618             1;
619              
620             =pod
621              
622             =head1 SUPPORT
623              
624             Bugs should be reported via the CPAN bug tracker at
625              
626             L
627              
628             For other issues, contact the author.
629              
630             =head1 AUTHOR
631              
632             Adam Kennedy Eadamk@cpan.orgE, L
633              
634             =head1 SEE ALSO
635              
636             L, L
637              
638             The Perl Image-based Testing Architecture (L)
639              
640             =head1 COPYRIGHT
641              
642             Copyright 2005 - 2013 Adam Kennedy.
643              
644             This program is free software; you can redistribute
645             it and/or modify it under the same terms as Perl itself.
646              
647             The full text of the license can be found in the
648             LICENSE file included with this module.
649              
650             =cut