File Coverage

blib/lib/Config/XPath.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             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2005-2010 -- leonerd@leonerd.org.uk
5              
6             package Config::XPath;
7              
8 15     15   311616 use strict;
  15         39  
  15         532  
9 15     15   86 use warnings;
  15         28  
  15         413  
10              
11 15     15   83 use Exporter 'import';
  15         44  
  15         1067  
12             our @EXPORT = qw(
13             get_service_config
14              
15             get_config_string
16             get_config_attrs
17             get_config_list
18             get_config_map
19              
20             get_sub_config
21             get_sub_config_list
22              
23             read_default_config
24             );
25              
26             our $VERSION = '0.16';
27              
28 15     15   21928 use XML::XPath;
  0            
  0            
29              
30             use Carp;
31              
32             use Scalar::Util qw( weaken );
33              
34             =head1 NAME
35              
36             C - retrieve configuration data from XML files by using XPath
37              
38             =head1 SYNOPSIS
39              
40             use Config::XPath;
41              
42             my $conf = Config::XPath->new( filename => 'addressbook.xml' );
43              
44             ## Basic data retrieval
45              
46             my $bob_phone = $conf->get_string( '//user[@name="bob"]/@phone' );
47              
48             my %jim_details = $conf->get_attrs( '//user[@name="jim"]' );
49              
50             my @everyone_with_fax = $conf->get_list( '//user[@fax]' );
51             print " $_ has a fax\n" for @everyone_with_fax;
52              
53             my $phone_map = $conf->get_map( '//user', '@name', '@phone' );
54             print " $_ has a phone: $phone_map->{$_}\n" for sort keys %$phone_map;
55              
56             ## Subconfigurations
57              
58             my $james_config = $conf->get_sub( '//user[@name="james"]' );
59             my $james_phone = $james_config->get_string( '@phone' );
60              
61             foreach my $user_config ( $conf->get_sub_list( '//user[@email]' ) ) {
62             my $town = $user_config->get_string( 'address/town' );
63             print "Someone in $town has an email account\n";
64             }
65              
66             =head1 DESCRIPTION
67              
68             This module provides easy access to configuration data stored in an XML file.
69             Configuration is retrieved using XPath keys; various methods exist to
70             convert the result to a variety of convenient forms.
71              
72             If the methods are called as static functions (as opposed to as object
73             methods) then they access data stored in the default configuration file
74             (details given below).
75              
76             =cut
77              
78             =head2 Subconfigurations
79              
80             By default, the XPath context is at the root node of the XML document. If some
81             other context is required, then a subconfiguration object can be used. This is
82             a child C object, built from an XPath query on the parent.
83             Whatever node the query matches becomes the context for the new object. The
84             methods C and C perform this task; the former
85             returning a single child, and the latter returning a list of all matches.
86              
87             =cut
88              
89             =head1 CONSTRUCTOR
90              
91             =head2 $conf = Config::XPath->new( %args )
92              
93             This function returns a new instance of a C object, containing
94             the configuration in the named XML file. If the given file does not exist, or
95             an error occured while reading it, an exception is thrown.
96              
97             The C<%args> hash requires one the following keys to provide the XML source:
98              
99             =over 8
100              
101             =item filename => $file
102              
103             The filename of the XML file to read
104              
105             =item xml => $xml
106              
107             A string containing XML data
108              
109             =item ioref => IO
110              
111             An IO handle reference
112              
113             =back
114              
115             Also may be provided:
116              
117             =over 8
118              
119             =item parser => $parser
120              
121             An C object
122              
123             =back
124              
125             If a parser is not provided, one will be constructed internally.
126              
127             =cut
128              
129             sub new
130             {
131             my $class = shift;
132              
133             my %args;
134              
135             # Cope with now-deprecated constructor form
136             if( @_ == 1 ) {
137             carp 'Use of '.__PACKAGE__.'->new( $file ) is deprecated; use ->new( filename => $file ) instead';
138             %args = ( filename => $_[0] );
139             }
140             else {
141             %args = @_;
142             }
143              
144             my $self = bless {
145             }, $class;
146              
147             my $parser = $self->{parser} = delete $args{parser};
148            
149             if( defined $args{filename} ) {
150             $self->{filename} = $args{filename};
151             $self->_reload_file;
152             }
153             elsif( defined $args{xml} ) {
154             my $xp = XML::XPath->new(
155             xml => $args{xml},
156             defined $parser ? ( parser => $parser ) : (),
157             );
158             croak "Cannot parse string" unless $xp;
159             $self->{xp} = $xp;
160             }
161             elsif( defined $args{ioref} ) {
162             my $xp = XML::XPath->new(
163             ioref => $args{ioref},
164             defined $parser ? ( parser => $parser ) : (),
165             );
166             croak "Cannot parse XML from ioref" unless $xp;
167             $self->{xp} = $xp;
168             }
169             else {
170             croak "Expected 'filename', 'xml', 'parser' or 'ioref' argument";
171             }
172              
173             return $self;
174             }
175              
176             # Internal-only constructor
177             sub newContext
178             {
179             my $class = shift;
180             my ( $parent, $context ) = @_;
181              
182             my $self = {
183             parent => $parent,
184             context => $context
185             };
186              
187             weaken( $self->{parent} );
188              
189             return bless $self, $class;
190             }
191              
192             sub find
193             {
194             my $self = shift;
195             my ( $path, %args ) = @_;
196              
197             my $toplevel = $self;
198             $toplevel = $toplevel->{parent} while !exists $toplevel->{xp};
199              
200             my $xp = $toplevel->{xp};
201              
202             my $context = $args{context} || $self->{context};
203              
204             if ( defined $context ) {
205             return $xp->find( $path, $context );
206             }
207             else {
208             return $xp->find( $path );
209             }
210             }
211              
212             sub get_config_nodes
213             {
214             my $self = shift;
215             my ( $path ) = @_;
216              
217             my $nodeset = $self->find( $path );
218              
219             unless( $nodeset->isa( "XML::XPath::NodeSet" ) ) {
220             croak "Expected result to be a nodeset at '$path'";
221             }
222              
223             return $nodeset->get_nodelist;
224             }
225              
226             sub get_config_node
227             {
228             my $self = shift;
229             my ( $path ) = @_;
230              
231             my @nodes = $self->get_config_nodes( $path );
232              
233             if ( scalar @nodes == 0 ) {
234             croak "No config found at '$path'";
235             }
236              
237             if ( scalar @nodes > 1 ) {
238             croak "Found more than one node at '$path'";
239             }
240              
241             return shift @nodes;
242             }
243              
244             sub get_node_attrs($)
245             # Get a hash of the attributes, putting the node name in "+"
246             {
247             my ( $node ) = @_;
248              
249             my %attrs = ( '+' => $node->getName() );
250              
251             foreach my $attr ( $node->getAttributes() ) {
252             $attrs{$attr->getName} = $attr->getValue;
253             }
254              
255             return \%attrs;
256             }
257              
258             sub convert_string
259             {
260             my $self = shift;
261             my ( $nodeset, $path, %args ) = @_;
262              
263             if( !$nodeset->isa( "XML::XPath::NodeSet" ) ) {
264             return $nodeset->string_value();
265             }
266              
267             my @nodes = $nodeset->get_nodelist;
268             if ( scalar @nodes == 0 ) {
269             return $args{default} if exists $args{default};
270              
271             croak "No config found at '$path'";
272             }
273              
274             if ( scalar @nodes > 1 ) {
275             croak "Found more than one node at '$path'";
276             }
277              
278             my $node = $nodes[0];
279              
280             if ( $node->isa( "XML::XPath::Node::Element" ) ) {
281             my @children = $node->getChildNodes();
282              
283             if( !@children ) {
284             # No child nodes - treat this as an empty string
285             return "";
286             }
287             elsif ( scalar @children == 1 ) {
288             my $child = shift @children;
289              
290             if ( ! $child->isa( "XML::XPath::Node::Text" ) ) {
291             croak "Result is not a plain text value at '$path'";
292             }
293              
294             return $child->string_value();
295             }
296             else {
297             croak "Found more than one child node at '$path'";
298             }
299             }
300             elsif( $node->isa( "XML::XPath::Node::Text" ) ) {
301             return $node->getValue();
302             }
303             elsif( $node->isa( "XML::XPath::Node::Attribute" ) ) {
304             return $node->getValue();
305             }
306             else {
307             my $t = ref( $node );
308             croak "Cannot return string representation of node type $t at '$path'";
309             }
310             }
311              
312             =head1 METHODS
313              
314             =cut
315              
316             =head2 $result = $config->get( $paths, %args )
317              
318             This method retrieves the result of one of more XPath expressions from the XML
319             file. Each expression should give either a text-valued element with no
320             sub-elements, an attribute, or an XPath function that returns a string,
321             integer or boolean value.
322              
323             The C<$paths> argument should contain a data tree of ARRAY and HASH
324             references, whose leaves will be the XPath expressions used. The C<$result>
325             will be returned in a similar tree structure, with the leaves containing the
326             value each expression yielded against the XML config. The C<%args> may contain
327             a C key, which should give default values for these results, also in
328             a similar tree structure.
329              
330             If no suitable node was found matching an XPath expression and no
331             corresponding C value is found, then an exception is thrown. If more
332             than one node is returned, or the returned node is not either a plain-text
333             content containing no child nodes, or an attribute, then an exception is
334             thrown.
335              
336             =over 8
337              
338             =item $paths
339              
340             A tree data structure containing ARRAY and HASH references, and XPath
341             expressions stored in plain scalars.
342              
343             =item %args
344              
345             A hash that may contain extra options to control the operation. Supports the
346             following keys:
347              
348             =over 4
349              
350             =item C
351              
352             Contains a tree in the same structure as the C<$paths>, whose leaf values
353             should be returned instead of the value yielded by the XPath expression, in
354             the case that no nodes match it.
355              
356             =back
357              
358             =back
359              
360             =cut
361              
362             sub get
363             {
364             my $self = shift;
365             my ( $paths, %args ) = @_;
366              
367             my $context = $args{context};
368              
369             if( !ref $paths ) {
370             return $self->get_string( $paths, %args );
371             }
372             elsif( ref $paths eq "ARRAY" ) {
373             my $default = delete $args{default};
374              
375             my @ret;
376              
377             foreach my $index ( 0 .. $#$paths ) {
378             $ret[$index] = $self->get( $paths->[$index], %args,
379             exists $default->[$index] ? (default => $default->[$index]) : ()
380             );
381             }
382              
383             return \@ret;
384             }
385             elsif( ref $paths eq "HASH" ) {
386             my $default = delete $args{default};
387              
388             my %ret;
389              
390             foreach my $key ( keys %$paths ) {
391             $ret{$key} = $self->get( $paths->{$key}, %args,
392             exists $default->{$key} ? (default => $default->{$key}) : ()
393             );
394             }
395              
396             return \%ret;
397             }
398             else {
399             croak "Expected a plain string or ARRAY or HASH reference as path, got " . ( ref $paths ) . " reference instead";
400             }
401             }
402              
403             =head2 $str = $config->get_string( $path, %args )
404              
405             This function is a smaller version of the C method, which only works on a
406             single string path.
407              
408             =over 8
409              
410             =item $path
411              
412             The XPath to the required configuration node
413              
414             =item %args
415              
416             A hash that may contain extra options to control the operation. Supports the
417             following keys:
418              
419             =over 4
420              
421             =item C
422              
423             If no XML node is found matching the path, return this value rather than
424             throwing an exception.
425              
426             =back
427              
428             =back
429              
430             =cut
431              
432             sub get_string
433             {
434             my $self = shift;
435             my ( $path, %args ) = @_;
436              
437             my $nodeset = $self->find( $path, context => $args{context} );
438              
439             return $self->convert_string( $nodeset, $path, %args );
440             }
441              
442             =head2 $attrs = $config->get_attrs( $path )
443              
444             This method retrieves the attributes of a single element in the XML file. The
445             attributes are returned in a hash, along with the name of the element itself,
446             which is returned in a special key named C<'+'>. This name is not valid for an
447             XML attribute, so this key will never clash with an actual value from the XML
448             file.
449              
450             If no suitable node was found matching the XPath query, then an exception is
451             thrown. If more than one node matched, or the returned node is not an
452             element, then an exception is thrown.
453              
454             =over 8
455              
456             =item C>
457              
458             The XPath to the required configuration node
459              
460             =back
461              
462             =cut
463              
464             sub get_attrs
465             {
466             my $self = shift;
467             my ( $path ) = @_;
468              
469             my $node = $self->get_config_node( $path );
470              
471             unless( $node->isa( "XML::XPath::Node::Element" ) ) {
472             croak "Node is not an element at '$path'";
473             }
474              
475             return get_node_attrs( $node );
476             }
477              
478             =head2 @results = $config->get_list( $listpath; $valuepaths, %args )
479              
480             This method obtains a list of nodes matching the C<$listpath> expression. For
481             each node in the list, it obtains the result of the C<$valuepaths> with the
482             XPath context at each node, and returns them all in a list. The C<$valuepaths>
483             argument can be a single string expression, or an ARRAY or HASH tree, as for
484             the C method.
485              
486             If the C<$valuepaths> argument is not supplied, the type of each node
487             determines the value that will be returned. Element nodes return a
488             hashref, identical to that which C returns. Other nodes will
489             return their XPath string value.
490              
491             =over 8
492              
493             =item $listpath
494              
495             The XPath expression to generate the list of nodes.
496              
497             =item $valuepaths
498              
499             Optional. If present, the XPath expression or tree of expressions to generate
500             the results.
501              
502             =item %args
503              
504             A hash that may contain extra options to control the operation. Supports the
505             following keys:
506              
507             =over 4
508              
509             =item C
510              
511             Contains a tree in the same structure as the C<$valuepaths>, whose leaf values
512             should be returned instead of the value yielded by the XPath expression, in
513             the case that no nodes match it.
514              
515             =back
516              
517             =back
518              
519             =cut
520              
521             sub get_list
522             {
523             my $self = shift;
524             my ( $listpath, $valuepaths, %args ) = @_;
525              
526             my @nodes = $self->get_config_nodes( $listpath );
527              
528             my @ret;
529              
530             foreach my $node ( @nodes ) {
531             my $val;
532              
533             if ( defined $valuepaths ) {
534             $val = $self->get( $valuepaths, context => $node, %args );
535             }
536              
537             elsif ( $node->isa( "XML::XPath::Node::Element" ) ) {
538             $val = get_node_attrs( $node );
539             }
540             elsif ( $node->isa( "XML::XPath::Node::Text" ) or $node->isa( "XML::XPath::Node::Attribute" ) ) {
541             $val = $self->convert_string( $node, $listpath );
542             }
543             else {
544             my $t = ref( $node );
545             croak "Cannot return string representation of node type $t at '$listpath'";
546             }
547              
548             push @ret, $val;
549             }
550              
551             return @ret;
552             }
553              
554             =head2 $map = $config->get_map( $listpath, $keypath, $valuepaths, %args )
555              
556             This method obtains a map, returned as a hash, containing one entry for each
557             node returned by the C<$listpath> search, where the key and value are given by
558             the C<$keypath> and C<$valuepaths> within each node. It is not an error for no
559             nodes to match the C<$listpath>.
560              
561             The result of the C<$listpath> query must be a nodeset. The result of the
562             C<$keypath> is used as the hash key for each node, and must be convertable
563             to a string, by the same rules as the C method. The value for
564             each node in the hash will be obtained using the C<$valuepaths>, which can be
565             a plain string, or an ARRAY or HASH tree, as for the C method.
566              
567             The keys obtained by the C<$keypath> should be unique. In the case of
568             duplicates, the last value from the nodeset is used.
569              
570             =over 8
571              
572             =item $listpath
573              
574             The XPath to generate the nodeset
575              
576             =item $keypath
577              
578             The XPath within each node to generate the key
579              
580             =item $valuepaths
581              
582             The XPath expression or tree of expressions within each node to generate the
583             value.
584              
585             =item %args
586              
587             A hash that may contain extra options to control the operation. Supports the
588             following keys:
589              
590             =over 4
591              
592             =item C
593              
594             Contains a tree in the same structure as the C<$valuepaths>, whose leaf values
595             should be returned instead of the value yielded by the XPath expression, in
596             the case that no nodes match it.
597              
598             =back
599              
600             =back
601              
602             =cut
603              
604             sub get_map
605             {
606             my $self = shift;
607             my ( $listpath, $keypath, $valuepaths, %args ) = @_;
608              
609             my @nodes = $self->get_config_nodes( $listpath );
610              
611             my %ret;
612              
613             foreach my $node ( @nodes ) {
614             my $keynode = $self->find( $keypath, context => $node );
615             my $key = $self->convert_string( $keynode, $keypath );
616              
617             my $value = $self->get( $valuepaths, context => $node, %args );
618              
619             $ret{$key} = $value;
620             }
621              
622             return \%ret;
623             }
624              
625             =head2 $subconfig = $config->get_sub( $path )
626              
627             This method constructs a new C object whose context is at the
628             single node selected by the XPath query. The newly constructed child object is
629             then returned.
630              
631             If no suitable node was found matching the XPath query, then an exception of
632             is thrown. If more than one node matched, then an exception is thrown.
633              
634             =over 8
635              
636             =item $path
637              
638             The XPath to the required configuration node
639              
640             =back
641              
642             =cut
643              
644             sub get_sub
645             {
646             my $self = shift;
647             my $class = ref( $self );
648             my ( $path ) = @_;
649              
650             my $node = $self->get_config_node( $path );
651              
652             return $class->newContext( $self, $node );
653             }
654              
655             =head2 @subconfigs = $config->get_sub_list( $path )
656              
657             This method constructs a list of new C objects whose context is
658             at each node selected by the XPath query. The array of newly constructed
659             objects is then returned. Unlike other methods, it is not an error for no
660             nodes to match.
661              
662             =over 8
663              
664             =item $path
665              
666             The XPath for the required configuration
667              
668             =back
669              
670             =cut
671              
672             sub get_sub_list
673             {
674             my $self = shift;
675             my $class = ref( $self );
676             my ( $path ) = @_;
677              
678             my @nodes = $self->get_config_nodes( $path );
679              
680             my @ret;
681              
682             foreach my $node ( @nodes ) {
683             push @ret, $class->newContext( $self, $node );
684             }
685              
686             return @ret;
687             }
688              
689             # Private methods
690             sub _reload_file
691             {
692             my $self = shift;
693              
694             # Recurse down to the toplevel object
695             return $self->{parent}->reload() if exists $self->{parent};
696              
697             my $file = $self->{filename};
698             my $parser = $self->{parser};
699              
700             my $xp = XML::XPath->new(
701             filename => $file,
702             defined $parser ? ( parser => $parser ) : (),
703             );
704              
705             croak "Cannot read config file $file" unless $xp;
706              
707             # If we threw an exception, this line never gets run, so the old {xp} is
708             # preserved. If not, then we know that $xp at least contains valid XML data
709             # so we store it, replacing the old value.
710              
711             $self->{xp} = $xp;
712             }
713              
714             =head1 DEFAULT CONFIG FILE
715              
716             In the case of calling as static functions, the default configuration is
717             accessed. When the module is loaded no default configuration exists, but one
718             can be loaded by calling the C function. This makes
719             programs simpler to write in cases where only one configuration file is used
720             by the program.
721              
722             =cut
723              
724             my $default_config;
725              
726             =head2 read_default_config( $file )
727              
728             This function reads the default configuration file, from the location given.
729             If the file is not found, or an error occurs while reading it, then an
730             exception is thrown.
731              
732             The default configuration is cached, so multiple calls to this function will
733             not result in multiple reads of the file; subsequent requests will be silently
734             ignored, even if a different filename is given.
735              
736             =over 8
737              
738             =item $file
739              
740             The filename of the default configuration to load
741              
742             =back
743              
744             =cut
745              
746             sub read_default_config
747             {
748             my ( $file ) = @_;
749              
750             last if defined $default_config;
751            
752             $default_config = Config::XPath->new( filename => $file );
753             }
754              
755             =head1 FUNCTIONS
756              
757             Each of the following functions is equivalent to a similar method called on
758             the default configuration, as loaded by C.
759              
760             =cut
761              
762             =head2 $str = get_config_string( $path, %args )
763              
764             Equivalent to the C method
765              
766             =cut
767              
768             sub get_config_string
769             {
770             my $self;
771             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
772             carp "Using static function 'get_config_string' as a method is deprecated";
773             $self = shift;
774             }
775             else {
776             croak "No default config loaded for '$_[0]'" unless defined $default_config;
777             $self = $default_config;
778             }
779              
780             $self->get_string( @_ );
781             }
782              
783             =head2 $attrs = get_config_attrs( $path )
784              
785             Equivalent to the C method
786              
787             =cut
788              
789             sub get_config_attrs
790             {
791             my $self;
792             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
793             carp "Using static function 'get_config_attrs' as a method is deprecated";
794             $self = shift;
795             }
796             else {
797             croak "No default config loaded for '$_[0]'" unless defined $default_config;
798             $self = $default_config;
799             }
800              
801             $self->get_attrs( @_ );
802             }
803              
804             =head2 @values = get_config_list( $path )
805              
806             Equivalent to the C method
807              
808             =cut
809              
810             sub get_config_list
811             {
812             my $self;
813             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
814             carp "Using static function 'get_config_list' as a method is deprecated";
815             $self = shift;
816             }
817             else {
818             croak "No default config loaded for '$_[0]'" unless defined $default_config;
819             $self = $default_config;
820             }
821              
822             $self->get_list( @_ );
823             }
824              
825             =head2 $map = get_config_map( $listpath, $keypath, $valuepath )
826              
827             Equivalent to the C method
828              
829             =cut
830              
831             sub get_config_map
832             {
833             my $self;
834             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
835             carp "Using static function 'get_config_map' as a method is deprecated";
836             $self = shift;
837             }
838             else {
839             croak "No default config loaded for '$_[0]'" unless defined $default_config;
840             $self = $default_config;
841             }
842              
843             $self->get_map( @_ );
844             }
845              
846             =head2 $map = get_sub_config( $path )
847              
848             Equivalent to the C method
849              
850             =cut
851              
852             sub get_sub_config
853             {
854             my $self;
855             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
856             carp "Using static function 'get_sub_config' as a method is deprecated";
857             $self = shift;
858             }
859             else {
860             croak "No default config loaded for '$_[0]'" unless defined $default_config;
861             $self = $default_config;
862             }
863              
864             $self->get_sub( @_ );
865             }
866              
867             =head2 $map = get_sub_config_list( $path )
868              
869             Equivalent to the C method
870              
871             =cut
872              
873             sub get_sub_config_list
874             {
875             my $self;
876             if( ref( $_[0] ) && $_[0]->isa( __PACKAGE__ ) ) {
877             carp "Using static function 'get_sub_config_list' as a method is deprecated";
878             $self = shift;
879             }
880             else {
881             croak "No default config loaded for '$_[0]'" unless defined $default_config;
882             $self = $default_config;
883             }
884              
885             $self->get_sub_list( @_ );
886             }
887              
888             # Keep perl happy; keep Britain tidy
889             1;
890              
891             __END__