Config::Context::XMLSimple - Use XML-based config files with Config::Context


Config-Context documentation Contained in the Config-Context distribution.

Index


Code Index:

NAME

Top

Config::Context::XMLSimple - Use XML-based config files with Config::Context

SYNOPSIS

Top

    use Config::Context;

    my $config_text = '
        <opt>

          <Location name="/users">
            <title>User Area</title>
          </Location>

          <LocationMatch name="\.*(jpg|gif|png)$">
            <image_file>1</image_file>
          </LocationMatch>

        </opt>
    ';

    my $conf = Config::Context->new(
        string        => $config_text,
        driver        => 'XMLSimple',
        match_sections => [
            {
                name          => 'Location',
                match_type    => 'path',
            },
            {
                name          => 'LocationMatch',
                match_type    => 'regex',
            },
        ],
    );

    my %config = $conf->context('/users/~mary/index.html');

    use Data::Dumper;
    print Dumper(\%config);
    --------
    $VAR1 = {
        'title'         => 'User Area',
        'image_file'    => undef,
    };

    my %config = $conf->context('/users/~biff/images/flaming_logo.gif');
    print Dumper(\%config);
    --------
    $VAR1 = {
        'title'         => 'User Area',
        'image_file'    => 1,
    };




DESCRIPTION

Top

This module uses XML::Simple to parse XML config files for Config::Context. See the Config::Context docs for more information.

DRIVER OPTIONS

Top

By default, it is assumed that the RootName of your configuration files is <opt>. For instance:

    <opt>
     <Location /users>
      <title>Users Area</title>
     </Location>
    <opt>

If you change this to some other element, then you must specify the RootName parameter in driver_options:

    # Change the name of the root block to <Config>..</Config>
    my $conf = Config::Context->new(
        driver => 'XMLSimple',
        driver_options => {
           XMLSimple = > {
               RootName  => 'Config',
           },
        },
    );




DEFAULT OPTIONS

Top

By default the options passed to XML::Simple are:

    KeyAttr    => [],
    ForceArray => \@section_names,

...where @section_names is a list of the sections as defined in match_sections. This makes for consistently formatted configurations that are similar to those generated by the other drivers.

You can change this behaviour by passing a different value to driver_params to new:

    my $conf = Config::Context->new(
        driver => 'XMLSimple',
        driver_options => {
           XMLSimple = > {
               ForceArray  => 1,
           },
        },
    );

INCLUDE FILES

Top

You include XML files within other XML files by using the XInclude syntax. To include a file called other_config.xml you would use:

   <opt>
     <xi:include href="other_config.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
   </opt>

Files included this way are included in the same scope. For instance:

    # config.xml
    <opt>
     <Location /users>
      <title>Users Area</title>
     </Location>
     <xi:include href="other_config.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
    <opt>

    # other_config.xml
    <opt>
     <Location /users>
      <title>Members Area</title>
     </Location>
    </opt>

In this example, the raw config will look like

    {
        'location' => {
            'users' => {
                'title'         => 'Members Area',
            }
        }
    }

And the config matching users will look like:

    {
        'title'         => 'Members Area',
    }




Note that the placement of the <xi:include> tag within a block (e.g. top or bottom) doesn't matter. Contents are merged into the block so that the included file has precedence.

CONSTRUCTOR

Top

new(...)

    my $driver = Config::Context::XMLSimple->new(
        file             => $config_file,
        options          => {
            # ...
        }
    );

or:

    my $driver = Config::Context::XMLSimple->new(
        string           => $config_string,
        options          => {
            # ...
        }
    );

Returns a new driver object, using the provided options.

METHODS

Top

parse()

Returns the data structure for the parsed config.

files()

Returns a list of all the config files read, including any config files included in the main file.

config_modules

Returns the modules required to parse the config. In this case: XML::Simple, XML::SAX and XML::Filter::XInclude.

CAVEATS

Top

Lower Case names not supported with this driver

The lower_case_names option is not supported used with this driver. If you specify it, it will produce a warning.

SEE ALSO

Top

    Config::Context
    CGI::Application::Plugin::Config::Context
    XML::Simple

COPYRIGHT & LICENSE

Top


Config-Context documentation Contained in the Config-Context distribution.
package Config::Context::XMLSimple;

use warnings;
use strict;
use Carp;
use Cwd;

use Hash::Merge ();

# This is a customized subclass of XInclude, which can remember
# the names of all the files it has read in.

my %Included_Files;
{
    package XML::Filter::XInclude::RememberFiles;
    use vars '@ISA';
    @ISA = qw(XML::Filter::XInclude);

    sub _include_xml_document {
        my $self   = shift;
        my ($url)  = @_;
        my $base   = $self->{bases}[-1];
        my $source = URI->new_abs($url, $base);
        $Included_Files{$source->as_string} = 1;

        $self->SUPER::_include_xml_document(@_);
    }
}


sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;
    my %args  = @_;

    Config::Context->_require_prerequisite_modules($class);

    my %driver_opts = %{ $args{'options'}{'XMLSimple'} || {} };

    # ForceArray for all section names
    # we use a regex for this, for case insensitivity
    #    ForceArray => qr/^(?:(?:Location)|(?:LocationMatch))$/i

    my $match_sections = $args{'match_sections'} || [];
    my @force_array    = map { $_->{'name'} } @$match_sections;

    my $self = {};

    if ($args{'lower_case_names'}) {
        carp "Lower Case Names not supported with XML::Simple driver";
    }
    $self->{'root_key'}         = $driver_opts{'RootName'} || 'opt';

    my $simple = XML::Simple->new(ForceArray => \@force_array, %driver_opts);
    my $filter = XML::Filter::XInclude::RememberFiles->new(Handler => $simple);
    my $parser = XML::SAX::ParserFactory->parser(Handler => $filter);

    $self->{'parser'} = $parser;

    if ($args{'string'}) {
        $self->{'string'} = $args{'string'};
    }
    elsif($args{'file'}) {
        $self->{'file'} = $args{'file'};
    }
    else {
        croak __PACKAGE__ . "->new(): one of 'file' or 'string' is required";
    }

    bless $self, $class;
    return $self;

}

sub parse {
    my $self = shift;

    %Included_Files = ();
    my $config;
    my $parser = $self->{'parser'};
    if ($self->{'string'}) {
        $config = $parser->parse_string($self->{'string'});
    }
    elsif($self->{'file'}) {
        $config = $parser->parse_uri($self->{'file'});
    }

    # handle inclusion by recursively merging all keys named 'opt' into
    # the root name space

    my $rootkey = $self->{'root_name'} || 'opt';

    while (grep { $_ eq $rootkey } keys %$config) {
        foreach my $key (keys %$config) {
            if ($key eq $rootkey) {
                 my $sub_config = delete $config->{$key};
                 $config = Hash::Merge::merge($sub_config, $config);
                 last;
            }
        }
    }

    $self->{'included_files'} = \%Included_Files;

    # Include the containing config file itself
    if ($self->{'file'}) {
        $self->{'included_files'}{Cwd::abs_path($self->{'file'})} = 1;
    }

    return %$config if wantarray;
    return $config;
}

sub files {
    my $self = shift;
    $self->{'included_files'} ||= {};
    my @included_files = keys %{$self->{'included_files'}};
    return @included_files if wantarray;
    return \@included_files;
}


sub config_modules {
    return qw(
        XML::Simple
        XML::SAX
        XML::Filter::XInclude
    );
}




1;