File Coverage

blib/lib/PRANG/Marshaller.pm
Criterion Covered Total %
statement 91 123 73.9
branch 21 46 45.6
condition 10 25 40.0
subroutine 15 15 100.0
pod 0 8 0.0
total 137 217 63.1


line stmt bran cond sub pod time code
1              
2             package PRANG::Marshaller;
3             $PRANG::Marshaller::VERSION = '0.21';
4 11     11   1799 use Moose;
  11         32  
  11         106  
5 11     11   52563 use MooseX::Params::Validate;
  11         35  
  11         87  
6 11     11   5065 use Moose::Util::TypeConstraints;
  11         29  
  11         123  
7              
8 11     11   25133 use XML::LibXML 1.65;
  11         170  
  11         556  
9 11     11   2548 use PRANG::Util qw(types_of);
  11         32  
  11         70  
10 11     11   2572 use Carp;
  11         41  
  11         1008  
11              
12             BEGIN {
13 11     11   124 class_type 'Moose::Meta::Class';
14 11         24116 class_type "Moose::Meta::Role";
15 11         21273 class_type "XML::LibXML::Element";
16 11         1156 class_type "XML::LibXML::Node";
17 11         1082 role_type "PRANG::Graph";
18             }
19              
20             has 'class' =>
21             isa => "Moose::Meta::Class|Moose::Meta::Role",
22             is => "ro",
23             required => 1,
24             handles => [qw(marshall_in_element to_libxml)],
25             trigger => sub {
26             my $self = shift;
27             my $class = shift;
28              
29             if ( !$class->can("marshall_in_element") && ! $class->does_role('PRANG::Graph') ) {
30              
31             $class = $class->name if ref $class;
32             die "Can't marshall $class; didn't 'use PRANG::Graph' ?";
33             }
34             },
35             ;
36            
37             has 'encoding' =>
38             isa => 'Str',
39             is => 'ro',
40             default => 'UTF-8';
41              
42             our %marshallers; # could use MooseX::NaturalKey?
43              
44             sub get {
45 408     408 0 844 my $inv = shift;
46 408         1661 my ( $class ) = pos_validated_list(
47             \@_,
48             { isa => 'Str' },
49             );
50            
51 408 100       59995 if ( ref $inv ) {
52 288         630 $inv = ref $inv;
53             }
54 408 50       3006 $class->can("meta") or do {
55 0         0 my $filename = $class;
56 0         0 $filename =~ s{::}{/}g;
57 0         0 $filename .= ".pm";
58 0 0       0 if ( !$INC{$filename} ) {
59 0         0 eval { require $filename };
  0         0  
60             }
61 0 0       0 $class->can("meta") or
62             die "cannot marshall $class; no ->meta";
63             };
64 408         1638 my $meta = $class->meta;
65 408 50 66     9002 if ($meta->does_role("PRANG::Graph")
66             or
67             $meta->meta->does_role("PRANG::Graph::Meta::Class")
68             )
69             {
70            
71 408 50       127095 my $encoding = $class->can('encoding') ? $class->encoding : 'UTF-8';
72 408   66     2402 $marshallers{$class} ||= do {
73 39         176 $inv->new( class => $class->meta, encoding => $encoding );
74             }
75             }
76             else {
77 0         0 die "cannot marshall ".$meta->name
78             ."; not a PRANG Class/Node";
79             }
80             }
81              
82             sub parse {
83 69     69 0 477 my $self = shift;
84 69         684 my ( $xml, $filename, $fh, $lax ) = validated_list(
85             \@_,
86             xml => { isa => 'Str', optional => 1 },
87             filename => { isa => 'Str', optional => 1 },
88             fh => { isa => 'GlobRef', optional => 1 },
89             lax => { isa => 'Bool', optional => 1, default => 0 },
90             );
91              
92 69         21951 my $parser = XML::LibXML->new;
93 69 50       1368 my $dom = (
    100          
    100          
94             defined $xml ? $parser->parse_string($xml) :
95             defined $filename ? $parser->parse_file($filename) :
96             defined $fh ? $parser->parse_fh($fh) :
97             croak("no input passed to parse")
98             );
99              
100 69         15052 return $self->from_dom(
101             dom => $dom,
102             lax => $lax
103             );
104             }
105              
106             sub from_dom {
107 72     72 0 252 my $self = shift;
108              
109 72         580 my ( $dom, $lax ) = validated_list(
110             \@_,
111             dom => { isa => 'XML::LibXML::Document', },
112             lax => { isa => 'Bool', optional => 1, default => 0 },
113             );
114              
115 72         29810 my $rootNode = $dom->documentElement;
116            
117 72         332 return $self->from_root_node(
118             root_node => $rootNode,
119             lax => $lax,
120             );
121             }
122              
123             sub from_root_node {
124 72     72 0 155 my $self = shift;
125              
126 72         426 my ( $rootNode, $lax ) = validated_list(
127             \@_,
128             root_node => { isa => 'XML::LibXML::Node', },
129             lax => { isa => 'Bool', optional => 1, default => 0 },
130             );
131            
132 72         19613 my $rootNodeNS = $rootNode->namespaceURI;
133              
134 72         186 my $xsi = {};
135 72 100       2053 if ( $self->class->isa("Moose::Meta::Role") ) {
136 7         177 my @possible = types_of($self->class);
137 7         21 my $found;
138 7         48 my $root_localname = $rootNode->localname;
139 7         21 my @expected;
140 7         16 for my $class (@possible) {
141 9 100       55 if ($root_localname eq
142             $class->name->root_element
143             )
144             {
145              
146             # yeah, this is lazy ;-)
147 7         37 $self = (ref $self)->get($class->name);
148 7         88 $found = 1;
149 7         20 last;
150             }
151             else {
152 2         11 push @expected, $class->name->root_element;
153             }
154             }
155 7 50       27 if ( !$found ) {
156 0         0 die "No type of ".$self->class->name
157             ." that expects '$root_localname' as a root element (expected: @expected)";
158             }
159             }
160 72         1741 my $expected_ns = $self->class->name->xmlns;
161 72 50 33     510 if ( $rootNodeNS and $expected_ns ) {
162 0 0       0 if ( $rootNodeNS ne $expected_ns ) {
163 0         0 die
164             "Namespace mismatch: expected '$expected_ns', found '$rootNodeNS'";
165             }
166             }
167 72 50 33     630 if (!defined($rootNode->prefix)
168             and
169             !defined($rootNode->getAttribute("xmlns"))
170             )
171             {
172              
173             # namespace free;
174 72         1676 $xsi->{""}="";
175             }
176              
177 72         363 my $context = PRANG::Graph::Context->new(
178             base => $self,
179             xpath => "",
180             xsi => $xsi,
181             prefix => "",
182             );
183            
184 72         127875 my $rv = $self->class->marshall_in_element(
185             $rootNode,
186             $context,
187             $lax,
188             );
189 58         8429 $rv;
190             }
191              
192 39     39 0 1527 sub xml_version { "1.0" }
193              
194             # nothing to see here ... move along please ...
195             our $zok;
196             our %zok_seen;
197             our @zok_themes = (
198             qw( tmnt octothorpe quantum pokemon hhgg pasta
199             phonetic sins punctuation discworld lotr
200             loremipsum batman tld garbage python pooh
201             norse_mythology )
202             );
203             our $zok_theme;
204              
205             our $gen_prefix;
206              
207             sub generate_prefix {
208 28     28 0 61 my $self = shift;
209 28         118 my ( $xmlns ) = pos_validated_list(
210             \@_,
211             { isa => 'Str' },
212             );
213            
214 28 50 33     4511 if ( $zok or eval { require Acme::MetaSyntactic; 1 } ) {
  28         4071  
  0         0  
215 0         0 my $name;
216 0         0 do {
217 0   0     0 $zok ||= do {
218 0         0 %zok_seen=();
219 0 0       0 if ( defined $zok_theme ) {
220 0         0 $zok_theme++;
221 0 0       0 if ( $zok_theme > $#zok_themes ) {
222 0         0 $zok_theme = 0;
223             }
224             }
225             else {
226 0         0 $zok_theme = int(time() / 86400)
227             % scalar(@zok_themes);
228             }
229 0         0 Acme::MetaSyntactic->new(
230             $zok_themes[$zok_theme],
231             );
232             };
233 0   0     0 do {
234 0         0 $name = $zok->name;
235 0 0       0 if ($zok_seen{$name}++) {
236 0         0 undef($zok);
237 0         0 undef($name);
238 0         0 goto next_theme;
239             }
240             } while (
241             length($name) > 10
242             or
243             $name !~ m{^[A-Za-z]\w+$}
244             );
245             next_theme:
246 0         0 }
247             until ($name);
248 0         0 return $name;
249             }
250             else {
251              
252             # revert to a more boring prefix :)
253 28   100     140 $gen_prefix ||= "a";
254 28         613 $gen_prefix++;
255             }
256             }
257              
258             sub to_xml_doc {
259 39     39 0 111 my $self = shift;
260 39         214 my ( $item ) = pos_validated_list(
261             \@_,
262             { isa => 'PRANG::Graph' },
263             );
264            
265 39         17824 my $xmlns = $item->xmlns;
266 39         169 my $prefix = "";
267 39 50       237 if ( $item->can("preferred_prefix") ) {
268 0         0 $prefix = $item->preferred_prefix;
269             }
270 39   50     267 my $xsi = { $prefix => ($xmlns||"") };
271              
272             # whoops, this is non-reentrant
273 39         103 %zok_seen=();
274 39         86 undef($gen_prefix);
275 39         140 my $doc = XML::LibXML::Document->new(
276             $self->xml_version, $self->encoding,
277             );
278 39 50       238 my $root = $doc->createElement(
279             ($prefix ? "$prefix:" : "" ) .$item->root_element,
280             );
281 39 50       435 if ($xmlns) {
282 0 0       0 $root->setAttribute(
283             "xmlns".($prefix?":$prefix":""),
284             $xmlns,
285             );
286             }
287 39         184 $doc->setDocumentElement($root);
288 39         927 my $ctx = PRANG::Graph::Context->new(
289             xpath => "/".$root->nodeName,
290             base => $self,
291             prefix => $prefix,
292             xsi => $xsi,
293             );
294 39         61889 $item->meta->to_libxml( $item, $root, $ctx );
295 38         1483 $doc;
296             }
297              
298             sub to_xml {
299 39     39 0 96 my $self = shift;
300 39         269 my ( $item, $format ) = pos_validated_list(
301             \@_,
302             { isa => 'PRANG::Graph' },
303             { isa => 'Int', default => 0 },
304             );
305            
306 39         23869 my $document = $self->to_xml_doc($item);
307 38         4450 $document->toString($format);
308             }
309              
310             1;
311              
312             __END__
313              
314             =head1 NAME
315              
316             PRANG::Marshaller - entry point for PRANG XML marshalling machinery
317              
318             =head1 SYNOPSIS
319              
320             my $marshaller = PRANG::Marshaller->get($class_or_role);
321              
322             my $object = $marshaller->parse($xml);
323              
324             my $xml = $marshaller->to_xml($object);
325              
326             =head1 DESCRIPTION
327              
328             The B<PRANG::Marshaller> currently serves two major functions;
329              
330             =over
331              
332             =item 1.
333              
334             A place-holder for role-based marshalling (ie, marshalling documents
335             with multiple root element types)
336              
337             =item 2.
338              
339             A place for document-scoped information on emitting to be held (ie,
340             mapping XML namespace prefixes to URIs and generating namespace
341             prefixes).
342              
343             =back
344              
345             This class is a bit of a stop-gap measure; it started out as the only
346             place where any XML marshalling happened, and gradually parts have
347             been moved into metaclass methods, in packages such as
348             L<PRANG::Graph::Meta::Class>, L<PRANG::Graph::Meta::Element> and
349             L<PRANG::Graph::Node> implementations.
350              
351             =head1 SEE ALSO
352              
353             L<PRANG>, L<PRANG::Graph::Meta::Class>,
354             L<PRANG::Graph::Meta::Element>, L<PRANG::Graph::Node>
355              
356             =head1 AUTHOR AND LICENCE
357              
358             Development commissioned by NZ Registry Services, and carried out by
359             Catalyst IT - L<http://www.catalyst.net.nz/>
360              
361             Copyright 2009, 2010, NZ Registry Services. This module is licensed
362             under the Artistic License v2.0, which permits relicensing under other
363             Free Software licenses.
364              
365             =cut
366