File Coverage

blib/lib/Config/Context/XMLSimple.pm
Criterion Covered Total %
statement 19 74 25.6
branch 0 20 0.0
condition 0 11 0.0
subroutine 7 11 63.6
pod 4 4 100.0
total 30 120 25.0


line stmt bran cond sub pod time code
1             package Config::Context::XMLSimple;
2              
3 14     14   117769 use warnings;
  14         34  
  14         530  
4 14     14   75 use strict;
  14         31  
  14         458  
5 14     14   77 use Carp;
  14         28  
  14         1778  
6 14     14   76 use Cwd;
  14         31  
  14         937  
7              
8 14     14   123 use Hash::Merge ();
  14         24  
  14         1167  
9              
10             =head1 NAME
11              
12             Config::Context::XMLSimple - Use XML-based config files with Config::Context
13              
14             =head1 SYNOPSIS
15              
16             use Config::Context;
17              
18             my $config_text = '
19            
20              
21            
22             User Area
23            
24              
25            
26             1
27            
28              
29            
30             ';
31              
32             my $conf = Config::Context->new(
33             string => $config_text,
34             driver => 'XMLSimple',
35             match_sections => [
36             {
37             name => 'Location',
38             match_type => 'path',
39             },
40             {
41             name => 'LocationMatch',
42             match_type => 'regex',
43             },
44             ],
45             );
46              
47             my %config = $conf->context('/users/~mary/index.html');
48              
49             use Data::Dumper;
50             print Dumper(\%config);
51             --------
52             $VAR1 = {
53             'title' => 'User Area',
54             'image_file' => undef,
55             };
56              
57             my %config = $conf->context('/users/~biff/images/flaming_logo.gif');
58             print Dumper(\%config);
59             --------
60             $VAR1 = {
61             'title' => 'User Area',
62             'image_file' => 1,
63             };
64              
65              
66             =head1 DESCRIPTION
67              
68             This module uses C to parse XML config files for
69             C. See the C docs for more
70             information.
71              
72             =head1 DRIVER OPTIONS
73              
74             By default, it is assumed that the C of your configuration
75             files is C<< >>. For instance:
76              
77            
78            
79             Users Area
80            
81            
82              
83             If you change this to some other element, then you must specify the
84             C parameter in C:
85              
86             # Change the name of the root block to ..
87             my $conf = Config::Context->new(
88             driver => 'XMLSimple',
89             driver_options => {
90             XMLSimple = > {
91             RootName => 'Config',
92             },
93             },
94             );
95              
96              
97             =head1 DEFAULT OPTIONS
98              
99             By default the options passed to C are:
100              
101             KeyAttr => [],
102             ForceArray => \@section_names,
103              
104             ...where @section_names is a list of the sections as defined in C.
105             This makes for consistently formatted configurations that are similar to
106             those generated by the other drivers.
107              
108             You can change this behaviour by passing a different value to
109             C to C:
110              
111             my $conf = Config::Context->new(
112             driver => 'XMLSimple',
113             driver_options => {
114             XMLSimple = > {
115             ForceArray => 1,
116             },
117             },
118             );
119              
120             =head1 INCLUDE FILES
121              
122             You include XML files within other XML files by using the C
123             syntax. To include a file called C you would use:
124              
125            
126            
127            
128              
129             Files included this way are included in the same scope. For instance:
130              
131             # config.xml
132            
133            
134             Users Area
135            
136            
137            
138              
139             # other_config.xml
140            
141            
142             Members Area
143            
144            
145              
146             In this example, the raw config will look like
147              
148             {
149             'location' => {
150             'users' => {
151             'title' => 'Members Area',
152             }
153             }
154             }
155              
156             And the config matching users will look like:
157              
158             {
159             'title' => 'Members Area',
160             }
161              
162              
163             Note that the placement of the C<< >> tag within a block
164             (e.g. top or bottom) doesn't matter. Contents are merged into the block
165             so that the included file has precedence.
166              
167              
168             =cut
169              
170             # This is a customized subclass of XInclude, which can remember
171             # the names of all the files it has read in.
172              
173             my %Included_Files;
174             {
175             package XML::Filter::XInclude::RememberFiles;
176 14     14   73 use vars '@ISA';
  14         28  
  14         11772  
177             @ISA = qw(XML::Filter::XInclude);
178              
179             sub _include_xml_document {
180 0     0   0 my $self = shift;
181 0         0 my ($url) = @_;
182 0         0 my $base = $self->{bases}[-1];
183 0         0 my $source = URI->new_abs($url, $base);
184 0         0 $Included_Files{$source->as_string} = 1;
185              
186 0         0 $self->SUPER::_include_xml_document(@_);
187             }
188             }
189              
190              
191             =head1 CONSTRUCTOR
192              
193             =head2 new(...)
194              
195             my $driver = Config::Context::XMLSimple->new(
196             file => $config_file,
197             options => {
198             # ...
199             }
200             );
201              
202             or:
203              
204             my $driver = Config::Context::XMLSimple->new(
205             string => $config_string,
206             options => {
207             # ...
208             }
209             );
210              
211             Returns a new driver object, using the provided options.
212              
213             =cut
214              
215             sub new {
216 0     0 1 0 my $proto = shift;
217 0   0     0 my $class = ref $proto || $proto;
218 0         0 my %args = @_;
219              
220 0         0 Config::Context->_require_prerequisite_modules($class);
221              
222 0 0       0 my %driver_opts = %{ $args{'options'}{'XMLSimple'} || {} };
  0         0  
223              
224             # ForceArray for all section names
225             # we use a regex for this, for case insensitivity
226             # ForceArray => qr/^(?:(?:Location)|(?:LocationMatch))$/i
227              
228 0   0     0 my $match_sections = $args{'match_sections'} || [];
229 0         0 my @force_array = map { $_->{'name'} } @$match_sections;
  0         0  
230              
231 0         0 my $self = {};
232              
233 0 0       0 if ($args{'lower_case_names'}) {
234 0         0 carp "Lower Case Names not supported with XML::Simple driver";
235             }
236 0   0     0 $self->{'root_key'} = $driver_opts{'RootName'} || 'opt';
237              
238 0         0 my $simple = XML::Simple->new(ForceArray => \@force_array, %driver_opts);
239 0         0 my $filter = XML::Filter::XInclude::RememberFiles->new(Handler => $simple);
240 0         0 my $parser = XML::SAX::ParserFactory->parser(Handler => $filter);
241              
242 0         0 $self->{'parser'} = $parser;
243              
244 0 0       0 if ($args{'string'}) {
    0          
245 0         0 $self->{'string'} = $args{'string'};
246             }
247             elsif($args{'file'}) {
248 0         0 $self->{'file'} = $args{'file'};
249             }
250             else {
251 0         0 croak __PACKAGE__ . "->new(): one of 'file' or 'string' is required";
252             }
253              
254 0         0 bless $self, $class;
255 0         0 return $self;
256              
257             }
258              
259             =head1 METHODS
260              
261             =head2 parse()
262              
263             Returns the data structure for the parsed config.
264              
265             =cut
266              
267             sub parse {
268 0     0 1 0 my $self = shift;
269              
270 0         0 %Included_Files = ();
271 0         0 my $config;
272 0         0 my $parser = $self->{'parser'};
273 0 0       0 if ($self->{'string'}) {
    0          
274 0         0 $config = $parser->parse_string($self->{'string'});
275             }
276             elsif($self->{'file'}) {
277 0         0 $config = $parser->parse_uri($self->{'file'});
278             }
279              
280             # handle inclusion by recursively merging all keys named 'opt' into
281             # the root name space
282              
283 0   0     0 my $rootkey = $self->{'root_name'} || 'opt';
284              
285 0         0 while (grep { $_ eq $rootkey } keys %$config) {
  0         0  
286 0         0 foreach my $key (keys %$config) {
287 0 0       0 if ($key eq $rootkey) {
288 0         0 my $sub_config = delete $config->{$key};
289 0         0 $config = Hash::Merge::merge($sub_config, $config);
290 0         0 last;
291             }
292             }
293             }
294              
295 0         0 $self->{'included_files'} = \%Included_Files;
296              
297             # Include the containing config file itself
298 0 0       0 if ($self->{'file'}) {
299 0         0 $self->{'included_files'}{Cwd::abs_path($self->{'file'})} = 1;
300             }
301              
302 0 0       0 return %$config if wantarray;
303 0         0 return $config;
304             }
305              
306             =head2 files()
307              
308             Returns a list of all the config files read, including any config files
309             included in the main file.
310              
311             =cut
312              
313             sub files {
314 0     0 1 0 my $self = shift;
315 0   0     0 $self->{'included_files'} ||= {};
316 0         0 my @included_files = keys %{$self->{'included_files'}};
  0         0  
317 0 0       0 return @included_files if wantarray;
318 0         0 return \@included_files;
319             }
320              
321              
322             =head2 config_modules
323              
324             Returns the modules required to parse the config. In this case:
325             C, C and C.
326              
327             =cut
328              
329             sub config_modules {
330 14     14 1 1106 return qw(
331             XML::Simple
332             XML::SAX
333             XML::Filter::XInclude
334             );
335             }
336              
337             =head1 CAVEATS
338              
339             =head2 Lower Case names not supported with this driver
340              
341             The C option is not supported used with this driver.
342             If you specify it, it will produce a warning.
343              
344             =head1 SEE ALSO
345              
346             Config::Context
347             CGI::Application::Plugin::Config::Context
348             XML::Simple
349              
350             =head1 COPYRIGHT & LICENSE
351              
352             Copyright 2004-2005 Michael Graham, All Rights Reserved.
353              
354             This program is free software; you can redistribute it and/or modify it
355             under the same terms as Perl itself.
356              
357             =cut
358              
359              
360              
361              
362             1;
363              
364