File Coverage

blib/lib/PRANG/Graph/Context.pm
Criterion Covered Total %
statement 101 108 93.5
branch 37 46 80.4
condition 15 26 57.6
subroutine 18 19 94.7
pod 6 10 60.0
total 177 209 84.6


line stmt bran cond sub pod time code
1              
2             package PRANG::Graph::Context;
3             $PRANG::Graph::Context::VERSION = '0.21';
4 11     11   2050 use 5.010;
  11         61  
5 11     11   63 use Moose;
  11         24  
  11         78  
6 11     11   78266 use MooseX::Params::Validate;
  11         960611  
  11         82  
7 11     11   5715 use Moose::Util::TypeConstraints;
  11         26  
  11         72  
8              
9             BEGIN {
10 11     11   25386 class_type "XML::LibXML::Element";
11             }
12              
13             has 'seq_pos' =>
14             is => "rw",
15             isa => "Int",
16             lazy => 1,
17             default => 1,
18             trigger => sub {
19             my $self = shift;
20             $self->clear_quant;
21             $self->clear_chosen;
22             $self->clear_element_ok;
23             },
24             clearer => "clear_seq_pos",
25             ;
26              
27             sub reset {
28 0     0 0 0 my $self = shift;
29            
30 0         0 $self->clear_seq_pos;
31             }
32              
33             has 'quant_found' =>
34             is => "rw",
35             isa => "Int",
36             lazy => 1,
37             default => 0,
38             clearer => 'clear_quant',
39             trigger => sub {
40             my $self = shift;
41             $self->clear_chosen;
42             $self->clear_element_ok;
43             },
44             ;
45              
46             has 'chosen' =>
47             is => "rw",
48             isa => "Int",
49             clearer => "clear_chosen",
50             trigger => sub {
51             $_[0]->clear_element_ok;
52             }
53             ;
54              
55             has 'element_ok' =>
56             is => "rw",
57             isa => "Bool",
58             clearer => "clear_element_ok",
59             ;
60              
61             # For recursion, we need to know a couple of extra things.
62             has 'base' =>
63             is => "ro",
64             isa => 'PRANG::Marshaller',
65             ;
66              
67             has 'xpath' =>
68             is => "ro",
69             isa => "Str",
70             ;
71              
72             has 'xsi' =>
73             is => "rw",
74             isa => "HashRef",
75             default => sub { {} },
76             ;
77              
78             has 'old_xsi' =>
79             is => "rw",
80             isa => "HashRef",
81             default => sub { {} },
82             ;
83              
84             has 'rxsi' =>
85             is => "rw",
86             isa => "HashRef",
87             lazy => 1,
88             default => sub {
89             my $self = shift;
90             +{ reverse %{ $self->xsi } };
91             },
92             ;
93              
94             has 'xsi_virgin' =>
95             is => "rw",
96             isa => "Bool",
97             default => 1,
98             ;
99              
100             sub thing_xmlns {
101 28     28 0 55 my $thing = shift;
102 28 50       114 return unless blessed $thing;
103 28         66 my $xmlns = shift;
104 28 50       944 if ( $thing->can("preferred_prefix") ) {
    50          
105 0         0 $thing->preferred_prefix($xmlns);
106             }
107             elsif ( $thing->can("xmlns_prefix") ) {
108 0         0 $thing->xmlns_prefix($xmlns);
109             }
110             }
111              
112             sub next_ctx {
113 487     487 1 883 my $self = shift;
114 487         2392 my ( $xmlns, $newnode_name, $thing) = pos_validated_list(
115             \@_,
116             { isa => 'Maybe[Str]' },
117             { isa => 'Maybe[Str]' },
118             { optional => 1 },
119             );
120            
121 487         154399 my $prefix = $self->prefix;
122 487         777 my $new_prefix;
123 487 100       1051 if ($xmlns) {
124 62 100       1541 if ( !exists $self->rxsi->{$xmlns} ) {
125 15         48 $new_prefix = 1;
126 15   33     55 $prefix = thing_xmlns($thing, $xmlns) //
127             $self->base->generate_prefix($xmlns);
128             }
129             else {
130 47         198 $prefix = $self->get_prefix($xmlns);
131             }
132             }
133 487 100 66     2842 my $nodename = (($newnode_name && $prefix) ? "$prefix:" : "") .
      50        
134             ($newnode_name||"text()");
135              
136 487         12437 my $clone = (ref $self)->new(
137             prefix => $prefix,
138             base => $self->base,
139             xpath => $self->xpath."/".$nodename,
140             xsi => $self->xsi,
141             rxsi => $self->rxsi,
142             );
143 487 100       899008 if ($new_prefix) {
144 15         108 $clone->add_xmlns($prefix, $xmlns);
145             }
146 487         1642 $clone;
147             }
148              
149             sub prefix_new {
150 224     224 1 380 my $self = shift;
151 224         951 my ( $prefix) = pos_validated_list(
152             \@_,
153             { isa => 'Str' },
154             );
155            
156 224   66     39740 !$self->xsi_virgin and not exists $self->old_xsi->{$prefix};
157             }
158              
159             # this one is to know if the prefix was different to the parent type.
160             has 'prefix' =>
161             is => "ro",
162             isa => "Str",
163             ;
164              
165 11     11   30568 BEGIN { class_type "XML::LibXML::Node" }
166              
167             sub get_prefix {
168 61     61 1 119 my $self = shift;
169 61         433 my ( $xmlns, $thing, $victim ) = pos_validated_list(
170             \@_,
171             { isa => 'Str' },
172             { isa => 'Object', optional => 1 },
173             { isa => 'XML::LibXML::Element', optional => 1 },
174             );
175            
176 61 100       14894 if ( defined(my $prefix = $self->rxsi->{$xmlns}) ) {
177 48         170 $prefix;
178             }
179             else {
180 13   33     43 my $new_prefix = thing_xmlns($thing, $xmlns)
181             // $self->base->generate_prefix($xmlns);
182 13         53 $self->add_xmlns($new_prefix, $xmlns);
183 13 50       102 if ($victim) {
184 13         112 $victim->setAttribute(
185             "xmlns:".$new_prefix,
186             $xmlns,
187             );
188             }
189 13         656 $new_prefix;
190             }
191             }
192              
193             sub add_xmlns {
194 89     89 1 254 my $self = shift;
195 89         429 my ( $prefix, $xmlns ) = pos_validated_list(
196             \@_,
197             { isa => 'Str' },
198             { isa => 'Str' },
199             );
200            
201 89 100       23762 if ( $self->xsi_virgin ) {
202 44         1132 $self->xsi_virgin(0);
203 44         1091 $self->old_xsi($self->xsi);
204 44         95 $self->xsi({ %{$self->xsi}, $prefix => $xmlns });
  44         1057  
205 44 50       1099 if ( $self->rxsi ) {
206 44         82 $self->rxsi({ %{$self->rxsi}, $xmlns => $prefix });
  44         1042  
207             }
208             }
209             else {
210 45         1129 $self->xsi->{$prefix} = $xmlns;
211 45         1110 $self->rxsi->{$xmlns} = $prefix;
212             }
213             }
214              
215             sub get_xmlns{
216 26     26 1 63 my $self = shift;
217 26         122 my ( $prefix, ) = pos_validated_list(
218             \@_,
219             { isa => 'Str' },
220             );
221            
222 26         4888 $self->xsi->{$prefix};
223             }
224              
225             # this is a very convenient class to put a rich and useful exception
226             # method on; all important methods use it, and it has just the
227             # information to make the error message very useful.
228             sub exception {
229 26     26 1 58 my $self = shift;
230 26         161 my ( $message, $node, $skip_ok ) = pos_validated_list(
231             \@_,
232             { isa => 'Str' },
233             { isa => 'XML::LibXML::Node', optional => 1 },
234             { isa => 'Bool', optional => 1 },
235             );
236            
237            
238 26 100       7797 my $error = PRANG::Graph::Context::Error->new(
    50          
239             ($node ? (node => $node) : ()),
240             message => $message,
241             xpath => $self->xpath,
242             ($skip_ok ? (skip_ok => 1) : ()),
243             );
244 26         32595 die $error;
245             }
246              
247             package PRANG::Graph::Context::Error;
248             $PRANG::Graph::Context::Error::VERSION = '0.21';
249 11     11   27046 use Moose;
  11         37  
  11         83  
250 11     11   75347 use MooseX::Params::Validate;
  11         33  
  11         76  
251              
252             has 'node' =>
253             is => "ro",
254             isa => "XML::LibXML::Node",
255             predicate => "has_node",
256             ;
257              
258             has 'message' =>
259             is => "ro",
260             isa => "Str",
261             ;
262              
263             has 'xpath' =>
264             is => "ro",
265             isa => "Str",
266             ;
267              
268             has 'skip_ok' =>
269             is => "ro",
270             isa => "Bool",
271             ;
272              
273             sub show_node {
274 37     37 0 84 my $self = shift;
275            
276 37 100       1078 return "" unless $self->has_node;
277 22         79 my $extra = "";
278 22         582 my $node = $self->node;
279 22 100       125 if ( $node->isa("XML::LibXML::Element") ) {
    50          
    0          
280 18         171 $extra = " (parsing: <".$node->nodeName;
281 18 100       116 if ( $node->hasAttributes ) {
282             $extra .= join(
283             " ", "",
284             map {
285 4         30 $_->name."='".$_->value."'"
  5         98  
286             } $node->attributes
287             );
288             }
289             my @nodes = grep {
290 18   33     94 !( $_->isa("XML::LibXML::Comment")
  22         385  
291             or
292             $_->isa("XML::LibXML::Text") and $_->data =~ /\A\s+\Z/
293             )
294             } $node->childNodes;
295 18 100 66     222 if (@nodes > 1
    100 100        
    100 66        
296 10         33 and grep { !$_->isa("XML::LibXML::Element") }
297             @nodes
298             )
299 2         5 { $extra .= ">(mixed content)";
300             }
301             elsif (@nodes and $nodes[0]->isa("XML::LibXML::Element")) {
302 6         25 $extra .= "><!-- ".@nodes
303             ." child XML nodes -->";
304             }
305             elsif ( @nodes and $nodes[0]->isa("XML::LibXML::Text") ) {
306 6         20 $extra .= ">(text content)";
307             }
308 18 100       59 if ( @nodes == 0 ) {
309 4         12 $extra .= " />";
310             }
311             else {
312 14         55 $extra .= "</".$node->nodeName.">";
313             }
314 18         70 $extra .= ")";
315             }
316             elsif ( $node->isa("XML::LibXML::Text") ) {
317 4         24 my $val = $node->data;
318 4 100       17 if ( length($val) > 15 ) {
319 2         8 $val = substr($val, 0, 13);
320 2         5 $val .= "...";
321             }
322 4         16 $extra .= " (at text node: '$val')";
323             }
324             elsif ($node) {
325 0         0 my $type = ref $node;
326 0         0 $type =~ s{XML::LibXML::}{};
327 0         0 $extra .= " (bogon? $type node)";
328             }
329 22         171 $extra;
330             }
331              
332             sub build_error {
333 37     37 0 19517 my $self = shift;
334 37         1233 my $message = $self->message;
335 37         105 my $extra = $self->show_node;
336 37         1082 return "$message at ".$self->xpath."$extra\n";
337             }
338              
339             use overload
340 11         126 '""' => \&build_error,
341 11     11   11110 fallback => 1;
  11         25  
342              
343             1;
344              
345             __END__
346              
347             =head1 NAME
348              
349             PRANG::Graph::Context - parse/emit state for Marshalling operations
350              
351             =head1 SYNOPSIS
352              
353             my $context = PRANG::Graph::Context->new(
354             base => PRANG::Marshaller->get($class),
355             xpath => "/nodename",
356             );
357              
358             =head1 DESCRIPTION
359              
360             This is a data class, it basically is like a loop counter for parsing
361             (or emitting). Except instead of walking over a list, it 'walks' over
362             a tree of a certain, bound shape.
363              
364             The shape of the XML Graph at each node is limited to:
365              
366             Seq -> Quant -> Choice -> Element -> ( Text | Null )
367              
368             (any of the above may be absent)
369              
370             There are assumptions that nodes only connect as above, and not just
371             in this class.
372              
373             These state in this object allows the code to remember where it is. A
374             new instance is created for each node which may have children for the
375             parsing efforts for that node.
376              
377             =head1 ATTRIBUTES
378              
379             =over
380              
381             =item B<seq_pos>
382              
383             =item B<quant_found>
384              
385             =item B<chosen>
386              
387             =item B<element_ok>
388              
389             The above four properties are state information for any
390             L<PRANG::Graph::Seq>, L<PRANG::Graph::Quant>, L<PRANG::Graph::Choice>
391             or L<PRANG::Graph::Element> objects which exist in the graph for a
392             given class. As the nodes always connect in a particular order,
393             setting one value will clear all of the values for the settings which
394             follow.
395              
396             =item B<xpath>
397              
398             The XML location of the current node. Used for helpful error messages.
399              
400             =item B<xsi>
401              
402             =item B<rxsi>
403              
404             These attributes contain mappings from XML prefixes to namespace URIs
405             and vice versa. They should not be modified, as they are
406             copy-on-write from the parent Context objects.
407              
408             =item B<old_xsi>
409              
410             The B<xsi> attribute from the parent object. Used for C<prefix_new>
411              
412             =item B<xsi_virgin>
413              
414             Unset the first time a prefix is defined.
415              
416             =back
417              
418             =head1 METHODS
419              
420             This API is probably subject to quite some change. It is mainly
421             provided for assisting understanding with internal code.
422              
423             =head2 B<$ctx-E<gt>exception("message", $node?, $skip_ok?)>
424              
425             Raise a context-sensitive exception via C<die>. The XPath that the
426             current node was constructed with is appended with the nodename of the
427             passed node to provide an XML path for the error.
428              
429             Where parsing or emitting errors happen with one of these objects
430             around, it should always be used for reporting the error. The error
431             is a structured object (of type C<PRANG::Graph::Context::Error>) which
432             knows how to stringify into a readable error message.
433              
434             =head2 B<next_ctx( Maybe[Str] $xmlns, Str $newnode_name, $thing? ) returns PRANG::Graph::Context>
435              
436             This returns a new C<PRANG::Graph::Context> object, for the next level
437             of parsing.
438              
439             =head2 B<get_xmlns( Str $prefix ) returns Str>
440              
441             Returns the XML namespace associated with the passed prefix.
442              
443             =head2 B<get_prefix( Str $xmlns, Object $thing?, XML::LibXML::Element $victim? ) returns Str>
444              
445             Used for emitting. This is an alternative to reading the C<rxsi> hash
446             attribute directly. It returns the prefix for the given namespace URI
447             (C<$xmlns>), and if it is not already defined it will figure out based
448             on the type of C<$thing> what prefix to use, and add XML namespace
449             nodes to the C<$victim> XML namespace node. If the C<$thing> does not
450             specify a default XML namespace prefix, then one is chosen for it.
451              
452             =head2 B<add_xmlns( Str $prefix, Str $xmlns )>
453              
454             Used for parsing. This associates the given prefix with the given XML
455             namespace URI.
456              
457             =head2 B<prefix_new( Str $prefix )>
458              
459             This tells you whether or not the passed prefix was declared with this
460             Context or not. Used for emitting.
461              
462             =head1 SEE ALSO
463              
464             L<PRANG::Graph::Meta::Class>, L<PRANG::Graph::Meta::Attr>,
465             L<PRANG::Graph::Meta::Element>, L<PRANG::Marshaller>,
466              
467             Implementations:
468              
469             L<PRANG::Graph::Seq>, L<PRANG::Graph::Quant>, L<PRANG::Graph::Choice>,
470             L<PRANG::Graph::Element>, L<PRANG::Graph::Text>
471              
472             =head1 AUTHOR AND LICENCE
473              
474             Development commissioned by NZ Registry Services, and carried out by
475             Catalyst IT - L<http://www.catalyst.net.nz/>
476              
477             Copyright 2009, 2010, NZ Registry Services. This module is licensed
478             under the Artistic License v2.0, which permits relicensing under other
479             Free Software licenses.
480              
481             =cut