File Coverage

blib/lib/PRANG/Graph/Element.pm
Criterion Covered Total %
statement 118 151 78.1
branch 63 90 70.0
condition 37 58 63.7
subroutine 11 11 100.0
pod 0 5 0.0
total 229 315 72.7


line stmt bran cond sub pod time code
1              
2             package PRANG::Graph::Element;
3             $PRANG::Graph::Element::VERSION = '0.21';
4 11     11   2099 use 5.010;
  11         38  
5 11     11   76 use Moose;
  11         26  
  11         80  
6 11     11   75124 use MooseX::Params::Validate;
  11         37  
  11         104  
7 11     11   5202 use Moose::Util::TypeConstraints;
  11         34  
  11         108  
8 11     11   29193 use XML::LibXML;
  11         302196  
  11         98  
9              
10             BEGIN {
11 11     11   2203 class_type "XML::LibXML::Node";
12 11         1562 class_type "XML::LibXML::Element";
13             }
14              
15             has 'xmlns' =>
16             is => "ro",
17             isa => "Str",
18             predicate => "has_xmlns",
19             ;
20              
21             has 'nodeName' =>
22             is => "ro",
23             isa => "Str",
24             required => 1,
25             ;
26              
27             has 'nodeClass' =>
28             is => "ro",
29             isa => "Str",
30             predicate => "has_nodeClass",
31             ;
32              
33             has 'nodeName_attr' =>
34             is => "rw",
35             isa => "Str",
36             predicate => "has_nodeName_attr",
37             ;
38              
39             has 'xmlns_attr' =>
40             is => "rw",
41             isa => "Str",
42             predicate => "has_xmlns_attr",
43             ;
44              
45             has 'attrName' =>
46             is => "ro",
47             isa => "Str",
48             required => 1,
49             ;
50              
51             has 'contents' =>
52             is => "rw",
53             isa => "PRANG::Graph::Node",
54             predicate => "has_contents",
55             ;
56              
57             sub node_ok {
58 1230     1230 0 2088 my $self = shift;
59 1230         4827 my ( $node, $ctx ) = pos_validated_list(
60             \@_,
61             { isa => 'XML::LibXML::Node' },
62             { isa => 'PRANG::Graph::Context' },
63             );
64            
65 1230 100       257818 return unless $node->nodeType == XML_ELEMENT_NODE;
66 1125         1911 my $got_xmlns;
67              
68 1125 100 100     33495 if ($self->has_xmlns
      100        
      100        
69             or
70             ($node->prefix||"") ne ($ctx->prefix||"")
71             )
72 351   100     1646 { my $prefix = $node->prefix//"";
73 351         8562 $got_xmlns = $ctx->xsi->{$prefix};
74 351 100       873 if ( !defined $got_xmlns ) {
75 12 50       91 $got_xmlns = $node->getAttribute(
76             "xmlns".(length $prefix?":$prefix":"")
77             );
78             }
79 351   100     8721 my $wanted_xmlns = ($self->xmlns||"");
80 351 100 100     1499 if ($got_xmlns
      100        
81             and $wanted_xmlns ne "*"
82             and
83             $got_xmlns ne $wanted_xmlns
84             )
85 77         308 { return;
86             }
87             }
88 1048         2569 my ($ret_nodeName, $ret_xmlns) = ("", "");
89 1048         25491 my $wanted_nodeName = $self->nodeName;
90 1048 100 100     6027 if ($wanted_nodeName ne "*"
91             and $wanted_nodeName ne $node->localname
92             )
93 326         1455 { return;
94             }
95 722 100       22464 if ( $self->has_nodeName_attr ) {
96 86         314 $ret_nodeName = $node->localname;
97             }
98 722 100       20730 if ( $self->has_xmlns_attr ) {
99 63         112 $ret_xmlns = $got_xmlns;
100             }
101 722 100       1718 if (wantarray) {
102 371         1380 return ($ret_nodeName, $ret_xmlns);
103             }
104             else {
105 351         1429 return $ret_nodeName;
106             }
107             }
108              
109             sub accept {
110 371     371 0 710 my $self = shift;
111 371         1992 my ( $node, $ctx, $lax ) = pos_validated_list(
112             \@_,
113             { isa => 'XML::LibXML::Node' },
114             { isa => 'PRANG::Graph::Context' },
115             { isa => 'Bool', optional => 1 },
116             );
117            
118 371         109485 my ($ret_nodeName, $xmlns) = $self->node_ok($node, $ctx);
119 371 50       898 if ( !defined $ret_nodeName ) {
120              
121             # ok, not right, so figure out what we did want, in
122             # the context of the incoming document.
123 0   0     0 my $wanted_xmlns = ($self->xmlns||"");
124 0         0 my $wanted_prefix = $ctx->get_prefix($wanted_xmlns);
125              
126 0         0 my $nodeName = $self->nodeName;
127 0 0       0 if ( $wanted_prefix ne "" ) {
128 0         0 $nodeName = "$wanted_prefix:$nodeName";
129 0 0       0 if ( $ctx->prefix_new($wanted_prefix) ) {
130 0         0 $nodeName .= " xmlns:$wanted_prefix="
131             ."\"$wanted_xmlns\"";
132             }
133             }
134             $ctx->exception(
135 0         0 "invalid element; expected '$nodeName'",
136             $node, 1,
137             );
138             }
139 371 100       1053 undef($ret_nodeName) if !length($ret_nodeName);
140 371 100       10751 if ( $self->has_nodeClass ) {
141              
142             # general nested XML support
143 189         4624 my $marshaller = $ctx->base->get($self->nodeClass);
144 189 50       1692 my $value = (
145             $marshaller
146             ? $marshaller->marshall_in_element(
147             $node,
148             $ctx,
149             $lax,
150             )
151             : $node
152             );
153 171         24818 $ctx->element_ok(1);
154 171         4476 return ($self->attrName => $value, $ret_nodeName, $xmlns);
155             }
156             else {
157              
158             # XML data types
159 182 100       5193 my $type = $self->has_contents
160             ?
161             "XML data"
162             : "presence-only";
163 182 100       911 if ($node->hasAttributes) {
164 2         12 $ctx->exception(
165             "Superfluous attributes on $type node",
166             $node
167             );
168             }
169 180 100       5115 if ( $self->has_contents ) {
170              
171             # simple types, eg Int, Str
172             my (@childNodes) = grep {
173 173   33     667 !( $_->isa("XML::LibXML::Comment")
  107         2620  
174             or
175             $_->isa("XML::LibXML::Text")
176             and $_->data =~ /\A\s+\Z/
177             )
178             } $node->childNodes;
179              
180 173 50       1024 if ( @childNodes > 1 ) {
181              
182             # we could maybe merge CDATA nodes...
183 0         0 $ctx->exception(
184             "Too many child nodes for $type node",
185             $node,
186             );
187             }
188 173         285 my $value;
189 173 100       354 if ( !@childNodes ) {
190 66         134 $value = "";
191             }
192             else {
193 107         2910 (undef, $value) = $self->contents->accept(
194             $childNodes[0],
195             $ctx,
196             $lax,
197             );
198             }
199 173         4716 $ctx->element_ok(1);
200 173         4456 return ($self->attrName => $value, $ret_nodeName, $xmlns);
201             }
202             else {
203              
204             # boolean
205 7 100       86 if ( $node->hasChildNodes ) {
206 1         6 $ctx->exception(
207             "Superfluous child nodes on $type node",
208             $node,
209             );
210             }
211 6         186 $ctx->element_ok(1);
212 6         163 return ($self->attrName => 1, $ret_nodeName, $xmlns);
213             }
214             }
215             }
216              
217             sub complete {
218 16     16 0 51 my $self = shift;
219 16         92 my ( $ctx ) = pos_validated_list(
220             \@_,
221             { isa => 'PRANG::Graph::Context' },
222             );
223            
224 16         3723 $ctx->element_ok;
225             }
226              
227             sub expected {
228 11     11 0 33 my $self = shift;
229 11         46 my ( $ctx ) = pos_validated_list(
230             \@_,
231             { isa => 'PRANG::Graph::Context' },
232             );
233            
234 11         1985 my $prefix = "";
235 11         312 my $nodename = $self->nodeName;
236 11 50       314 if ( $self->has_xmlns ) {
237 0   0     0 my $xmlns = eval { $self->nodeClass->xmlns } ||
238             $self->xmlns;
239 0 0       0 if ( $prefix = $ctx->rxsi->{$xmlns} ) {
240 0         0 $prefix .= ":";
241             }
242             else {
243 0         0 $prefix = $ctx->get_prefix($xmlns);
244 0         0 $nodename .= " xmlns:$prefix='$xmlns'";
245 0         0 $prefix .= ":";
246             }
247             }
248 11 50       360 return "<$prefix$nodename".(
    100          
249             $self->has_nodeClass
250             ?"..."
251             :
252             $self->has_contents?"":"/"
253             )
254             .">";
255             }
256              
257             sub output {
258 224     224 0 435 my $self = shift;
259            
260             # First 3 args positional, rest are named
261             # Because we're making 2 validation calls, we have to use different cache keys
262 224         1258 my ( $item, $node, $ctx ) = pos_validated_list(
263             [@_[0..2]],
264             { isa => 'Object' },
265             { isa => 'XML::LibXML::Element' },
266             { isa => 'PRANG::Graph::Context' },
267             MX_PARAMS_VALIDATE_CACHE_KEY => 'element-output-positional',
268             );
269            
270 224         66486 my ( $value, $slot, $name, $xmlns ) = validated_list(
271             [@_[3..$#_]],
272             value => { isa => 'Item', optional => 1 },
273             slot => { isa => 'Int', optional => 1 },
274             name => { isa => 'Str', optional => 1 },
275             xmlns => { isa => 'Str', optional => 1 },
276             MX_PARAMS_VALIDATE_CACHE_KEY => 'element-output-named',
277             );
278            
279            
280 224   100     46946 $value //= do {
281 65         1989 my $accessor = $self->attrName;
282 65         502 $item->$accessor;
283             };
284 224 50 66     1411 if ( ref $value and ref $value eq "ARRAY" and defined $slot ) {
      33        
285 0         0 $value = $value->[$slot];
286             }
287 224   66     612 $name //= do {
288 171 100       5696 if ( $self->has_nodeName_attr ) {
289 4         104 my $attr = $self->nodeName_attr;
290 4         95 $item->$attr;
291             }
292             else {
293 167         4174 $self->nodeName;
294             }
295             };
296 224   100     807 $xmlns //= do {
297 206 50       6184 if ( $self->has_xmlns_attr ) {
298 0         0 my $attr = $self->xmlns_attr;
299 0         0 $item->$attr;
300             }
301             else {
302 206   100     5002 $self->xmlns // "";
303             }
304             };
305 224 100       454 if ( ref $name ) {
306 4         10 $name = $name->[$slot];
307             }
308 224 50       561 if ( ref $xmlns ) {
309 0         0 $xmlns = $xmlns->[$slot];
310             }
311              
312 224         350 my $nn;
313 224         1307 my $doc = $node->ownerDocument;
314 224         357 my $newctx;
315 224 50       533 if ( length $name ) {
316 224         357 my ($prefix, $new_prefix);
317 224         753 $ctx = $ctx->next_ctx( $xmlns, $name, $value );
318 224         6246 $prefix = $ctx->prefix;
319 224 100       849 my $new_nodeName = ($prefix ? "$prefix:" : "") . $name;
320 224         1609 $nn = $doc->createElement($new_nodeName);
321 224 100       672 if ( $ctx->prefix_new($prefix) ) {
322 15 50       120 $nn->setAttribute(
323             "xmlns".($prefix?":$prefix":""),
324             $xmlns,
325             );
326             }
327 224         2698 $node->appendChild($nn);
328              
329             # now proceed with contents...
330 224 100 66     737 if ( my $class = $self->nodeClass ) {
    100          
331 104         207 my $m;
332 104 100       291 if ( !defined $value ) {
    100          
    50          
    50          
333 1         6 $ctx->exception("required element not set");
334             }
335 103         540 elsif ( eval { $value->isa($class) }) {
336 101         2494 $m = $ctx->base->get($class);
337             }
338 2         26 elsif ( eval{$value->isa("XML::LibXML::Element")} ) {
339 0 0 0     0 if ($value->localname eq $nn->localname
      0        
      0        
340             and
341             ($value->namespaceURI||"") eq
342             ($xmlns||"")
343             )
344 0         0 { my $nn2 = $value->cloneNode(1);
345 0         0 $node->appendChild($nn2);
346 0         0 $node->removeChild($nn);
347             }
348             else {
349              
350             # it's just not safe to set
351             # the nodeName after the fact,
352             # so copy the children across.
353 0         0 for my $att ( $value->attributes ) {
354 0 0       0 next if $att->isa("XML::LibXML::Namespace");
355 0         0 $nn->setAttribute(
356             $att->localname,
357             $att->value,
358             );
359             }
360 0         0 for my $child ( $value->childNodes ) {
361 0         0 my $nn2 = $child->cloneNode(1);
362 0         0 $nn->appendChild($nn2);
363             }
364             }
365 0         0 $m = "ok";
366             }
367             elsif ( blessed $value ) {
368              
369             # this actually indicates a type
370             # error. currently it is required for
371             # the Whatever mapping.
372 2         13 $m = PRANG::Marshaller->get(ref $value);
373             }
374              
375 103 50 33     953 if ( $m and blessed $m ) {
    0          
376 103 50       325 $ctx->exception(
377             "tried to serialize unblessed value $value"
378             )
379             if !blessed $value;
380 103         473 $m->to_libxml($value, $nn, $ctx);
381             }
382             elsif ($m) {
383              
384             # allow value-based code above to drop through
385             }
386             else {
387 0         0 $ctx->exception("no marshaller for '$value'");
388             }
389             }
390             elsif ( $self->has_contents and defined $value ) {
391 115         510 my $tn = $self->createTextNode($doc, $value);
392 115         844 $nn->appendChild($tn);
393             }
394             }
395             else {
396 0           $nn = $self->createTextNode($doc, $value);
397 0           $node->appendChild($nn);
398             }
399             }
400              
401             with 'PRANG::Graph::Node';
402              
403             1;
404              
405             __END__
406              
407             =head1 NAME
408              
409             PRANG::Graph::Element - accept a particular type of element
410              
411             =head1 SYNOPSIS
412              
413             See L<PRANG::Graph::Meta::Element> source and
414             L<PRANG::Graph::Node> for examples and information.
415              
416             =head1 DESCRIPTION
417              
418             This graph node specifies that the XML graph at this point must accept
419             a particular type of element.
420              
421             If the element only has only simple types (eg Str, Bool), it will not
422             have one of these objects in its graph.
423              
424             Along with L<PRANG::Graph::Text>, this graph node is the only type
425             which may actually consume input XML nodes or emit them on output.
426             The other node types merely change the state in the
427             L<PRANG::Graph::Context> object.
428              
429             =head1 ATTRIBUTES
430              
431             =over
432              
433             =item B<Str xmlns>
434              
435             If set, then the XML namespace of this element is expected to be the
436             value passed (or absent). This is generally not set if the namespace
437             of this portion of the graph is the same as the parent class.
438              
439             =item B<nodeName>
440              
441             This map is used for emitting and generating error messages. Also, if
442             set to C<*> it has special meaning when parsing. Specifies the name
443             of the node.
444              
445             =item B<nodeName_attr>
446              
447             If set, instances have an attribute which stores the name of the XML
448             element.
449              
450             =item B<Str nodeClass>
451              
452             This specifies the next type of element; during parsing and emitting,
453             recursion to the meta-object of this class occurs.
454              
455             This will be undefined if the attribute has C<Bool> type; node
456             presence is true and absence is false.
457              
458             =item B<attrName>
459              
460             Used when emitting; specifies the method to call to retrieve the item
461             to be output. Also used when parsing, to return the Moose attribute
462             slot for construction.
463              
464             =back
465              
466             =head1 SEE ALSO
467              
468             L<PRANG::Graph::Meta::Class>, L<PRANG::Graph::Meta::Element>,
469             L<PRANG::Graph::Context>, L<PRANG::Graph::Node>
470              
471             =head1 AUTHOR AND LICENCE
472              
473             Development commissioned by NZ Registry Services, and carried out by
474             Catalyst IT - L<http://www.catalyst.net.nz/>
475              
476             Copyright 2009, 2010, NZ Registry Services. This module is licensed
477             under the Artistic License v2.0, which permits relicensing under other
478             Free Software licenses.
479              
480             =cut
481