File Coverage

blib/lib/PRANG/Marshaller.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1              
2             package PRANG::Marshaller;
3             $PRANG::Marshaller::VERSION = '0.19';
4 1     1   1492 use Moose;
  1         2  
  1         7  
5 1     1   4273 use MooseX::Params::Validate;
  1         2  
  1         7  
6 1     1   405 use Moose::Util::TypeConstraints;
  1         21  
  1         9  
7              
8 1     1   2186 use XML::LibXML 1.65;
  0            
  0            
9             use PRANG::Util qw(types_of);
10             use Carp;
11              
12             BEGIN {
13             class_type 'Moose::Meta::Class';
14             class_type "Moose::Meta::Role";
15             class_type "XML::LibXML::Element";
16             class_type "XML::LibXML::Node";
17             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             my $inv = shift;
46             my ( $class ) = pos_validated_list(
47             \@_,
48             { isa => 'Str' },
49             );
50            
51             if ( ref $inv ) {
52             $inv = ref $inv;
53             }
54             $class->can("meta") or do {
55             my $filename = $class;
56             $filename =~ s{::}{/}g;
57             $filename .= ".pm";
58             if ( !$INC{$filename} ) {
59             eval { require $filename };
60             }
61             $class->can("meta") or
62             die "cannot marshall $class; no ->meta";
63             };
64             my $meta = $class->meta;
65             if ($meta->does_role("PRANG::Graph")
66             or
67             $meta->meta->does_role("PRANG::Graph::Meta::Class")
68             )
69             {
70            
71             my $encoding = $class->can('encoding') ? $class->encoding : 'UTF-8';
72             $marshallers{$class} ||= do {
73             $inv->new( class => $class->meta, encoding => $encoding );
74             }
75             }
76             else {
77             die "cannot marshall ".$meta->name
78             ."; not a PRANG Class/Node";
79             }
80             }
81              
82             sub parse {
83             my $self = shift;
84             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             my $parser = XML::LibXML->new;
93             my $dom = (
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             return $self->from_dom(
101             dom => $dom,
102             lax => $lax
103             );
104             }
105              
106             sub from_dom {
107             my $self = shift;
108              
109             my ( $dom, $lax ) = validated_list(
110             \@_,
111             dom => { isa => 'XML::LibXML::Document', },
112             lax => { isa => 'Bool', optional => 1, default => 0 },
113             );
114              
115             my $rootNode = $dom->documentElement;
116            
117             return $self->from_root_node(
118             root_node => $rootNode,
119             lax => $lax,
120             );
121             }
122              
123             sub from_root_node {
124             my $self = shift;
125              
126             my ( $rootNode, $lax ) = validated_list(
127             \@_,
128             root_node => { isa => 'XML::LibXML::Node', },
129             lax => { isa => 'Bool', optional => 1, default => 0 },
130             );
131            
132             my $rootNodeNS = $rootNode->namespaceURI;
133              
134             my $xsi = {};
135             if ( $self->class->isa("Moose::Meta::Role") ) {
136             my @possible = types_of($self->class);
137             my $found;
138             my $root_localname = $rootNode->localname;
139             my @expected;
140             for my $class (@possible) {
141             if ($root_localname eq
142             $class->name->root_element
143             )
144             {
145              
146             # yeah, this is lazy ;-)
147             $self = (ref $self)->get($class->name);
148             $found = 1;
149             last;
150             }
151             else {
152             push @expected, $class->name->root_element;
153             }
154             }
155             if ( !$found ) {
156             die "No type of ".$self->class->name
157             ." that expects '$root_localname' as a root element (expected: @expected)";
158             }
159             }
160             my $expected_ns = $self->class->name->xmlns;
161             if ( $rootNodeNS and $expected_ns ) {
162             if ( $rootNodeNS ne $expected_ns ) {
163             die
164             "Namespace mismatch: expected '$expected_ns', found '$rootNodeNS'";
165             }
166             }
167             if (!defined($rootNode->prefix)
168             and
169             !defined($rootNode->getAttribute("xmlns"))
170             )
171             {
172              
173             # namespace free;
174             $xsi->{""}="";
175             }
176              
177             my $context = PRANG::Graph::Context->new(
178             base => $self,
179             xpath => "",
180             xsi => $xsi,
181             prefix => "",
182             );
183            
184             my $rv = $self->class->marshall_in_element(
185             $rootNode,
186             $context,
187             $lax,
188             );
189             $rv;
190             }
191              
192             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             my $self = shift;
209             my ( $xmlns ) = pos_validated_list(
210             \@_,
211             { isa => 'Str' },
212             );
213            
214             if ( $zok or eval { require Acme::MetaSyntactic; 1 } ) {
215             my $name;
216             do {
217             $zok ||= do {
218             %zok_seen=();
219             if ( defined $zok_theme ) {
220             $zok_theme++;
221             if ( $zok_theme > $#zok_themes ) {
222             $zok_theme = 0;
223             }
224             }
225             else {
226             $zok_theme = int(time() / 86400)
227             % scalar(@zok_themes);
228             }
229             Acme::MetaSyntactic->new(
230             $zok_themes[$zok_theme],
231             );
232             };
233             do {
234             $name = $zok->name;
235             if ($zok_seen{$name}++) {
236             undef($zok);
237             undef($name);
238             goto next_theme;
239             }
240             } while (
241             length($name) > 10
242             or
243             $name !~ m{^[A-Za-z]\w+$}
244             );
245             next_theme:
246             }
247             until ($name);
248             return $name;
249             }
250             else {
251              
252             # revert to a more boring prefix :)
253             $gen_prefix ||= "a";
254             $gen_prefix++;
255             }
256             }
257              
258             sub to_xml_doc {
259             my $self = shift;
260             my ( $item ) = pos_validated_list(
261             \@_,
262             { isa => 'PRANG::Graph' },
263             );
264            
265             my $xmlns = $item->xmlns;
266             my $prefix = "";
267             if ( $item->can("preferred_prefix") ) {
268             $prefix = $item->preferred_prefix;
269             }
270             my $xsi = { $prefix => ($xmlns||"") };
271              
272             # whoops, this is non-reentrant
273             %zok_seen=();
274             undef($gen_prefix);
275             my $doc = XML::LibXML::Document->new(
276             $self->xml_version, $self->encoding,
277             );
278             my $root = $doc->createElement(
279             ($prefix ? "$prefix:" : "" ) .$item->root_element,
280             );
281             if ($xmlns) {
282             $root->setAttribute(
283             "xmlns".($prefix?":$prefix":""),
284             $xmlns,
285             );
286             }
287             $doc->setDocumentElement($root);
288             my $ctx = PRANG::Graph::Context->new(
289             xpath => "/".$root->nodeName,
290             base => $self,
291             prefix => $prefix,
292             xsi => $xsi,
293             );
294             $item->meta->to_libxml( $item, $root, $ctx );
295             $doc;
296             }
297              
298             sub to_xml {
299             my $self = shift;
300             my ( $item, $format ) = pos_validated_list(
301             \@_,
302             { isa => 'PRANG::Graph' },
303             { isa => 'Int', default => 0 },
304             );
305            
306             my $document = $self->to_xml_doc($item);
307             $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