# XMLParser.pm - Will parse an XML file and return the DBI result set. # Created by James A. Pattie. Copyright (c) 2001-2002, Xperience, Inc. package DBIWrapper::XMLParser; use strict; use XML::LibXML; use DBIWrapper::ResultSet; use vars qw ($AUTOLOAD @ISA @EXPORT $VERSION); require Exporter; @ISA = qw(Exporter AutoLoader); @EXPORT = qw(); $VERSION = "1.1"; # new (file, string) # required: file - xml file to work with, or string - xml data # in a string to work with. # one of file or string must be specified. If both are specified, # then the string will take precedence. sub new { my $that = shift; my $class = ref($that) || $that; my $self = bless {}, $class; my %args = ( "file" => "", string => "", @_ ); my $errStr = "DBIWrapper::XMLParser->new() - Error:"; if (length $args{file} > 0) { if ($args{file} !~ /^(.*\.xml)$/) { die "$errStr file = '$args{file}' is not a valid file!\n"; } if (! -e $args{file}) { die "$errStr Can not find config file = '$args{file}'! $!\n"; } } elsif (length $args{string} == 0) { die "$errStr You must specify either 'file' or 'string'!\n"; } $self->{resultFile} = (length $args{string} > 0 ? $args{string} : $args{file}); $self->{resultSetVersion} = "1.2"; eval { $self->{resultSetObj} = DBIWrapper::ResultSet->new(); }; if ($@) { die "$errStr $@\n"; } eval { $self->{xmlObj} = XML::LibXML->new(); }; if ($@) { die "$errStr $@\n"; } if (length $args{file} > 0 && length $args{string} == 0) { eval { $self->{xmlDoc} = $self->{xmlObj}->parse_file($self->{resultFile}); }; } else { eval { $self->{xmlDoc} = $self->{xmlObj}->parse_string($self->{resultFile}); }; } if ($@) { die "$errStr $@\n"; } return $self; } sub AUTOLOAD { my $self = shift; my $type = ref($self) || die "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (exists $self->{$name}) { die "Can't access `$name' field in object of class $type"; } if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } sub parse { my $self = shift; my $nodes = undef; my $errStr = "DBIWrapper::XMLParser->parse() - Error:"; # start by validating the version of the XML file. $self->validateVersion; # initiate the data structure. Fill in any default values possible. $self->{resultSetObj}->{version} = $self->{resultSetVersion}; $self->{resultSetObj}->{resultFile} = $self->{resultFile}; # gather the tags! You should only have one!\n"; } # gather all attributes of the tag!\n"; } if ($attribute !~ /^(sql|plug)$/) { die "$errStr '$attribute' is invalid in the tag!\n"; } } $self->{resultSetObj}->{select} = 1; $self->{resultSetObj}->{sql} = $attributes{sql}; $self->{resultSetObj}->{plug} = $attributes{plug}; } # void getStatus(void) # requires: nothing # returns: nothing sub getStatus { my $self = shift; my %args = ( @_ ); my $errStr = "DBIWrapper::XMLParser->getStatus() - Error:"; my @nodes = $self->getNodes(path => "/resultset/status"); if (scalar @nodes == 0) { die "$errStr Your XML file doesn't contain a tag!\n"; } if (scalar @nodes > 1) { die "$errStr You have too many tags! You should only have one!\n"; } # gather all attributes of the tag. my %attributes = $self->getAttributes(node => $nodes[0]); my %encountered = (); foreach my $attribute (keys %attributes) { if (exists $encountered{$attribute}) { die "$errStr You have already defined '$attribute' in the tag!\n"; } if ($attribute !~ /^(result|error)$/) { die "$errStr '$attribute' is invalid in the tag!\n"; } if ($attribute eq "result" && ($attributes{$attribute} !~ /^(Ok|Error)$/)) { die "$errStr $attribute = '$attributes{$attribute}' is invalid in the tag!\n"; } $encountered{$attribute} = 1; } foreach my $required ("result") { if (!exists $encountered{$required}) { die "$errStr '$required' is required in the tag!\n"; } } $self->{resultSetObj}->{result} = $attributes{result}; $self->{resultSetObj}->{error} = (exists $attributes{error} ? $attributes{error} : ""); } # void getRows(void) # requires: nothing # returns: nothing sub getRows { my $self = shift; my %args = ( @_ ); my $errStr = "DBIWrapper::XMLParser->getRows() - Error:"; my @nodes = $self->getNodes(path => "/resultset/rows"); if (scalar @nodes == 0) { die "$errStr Your XML file doesn't contain a tag!\n"; } if (scalar @nodes > 1) { die "$errStr You have too many tags! You should only have one!\n"; } # gather all attributes of the tag. my %attributes = $self->getAttributes(node => $nodes[0]); my %encountered = (); foreach my $attribute (keys %attributes) { if (exists $encountered{$attribute}) { die "$errStr You have already defined '$attribute' in the tag!\n"; } if ($attribute !~ /^(numRows|columns)$/) { die "$errStr '$attribute' is invalid in the tag!\n"; } if ($attribute eq "columns" && ($attributes{$attribute} !~ /^(0|1)$/)) { die "$errStr $attribute = '$attributes{$attribute}' is invalid in the tag!\n"; } if ($attribute eq "numRows" && $attributes{$attribute} !~ /^(\d+)$/) { die "$errStr $attribute = '$attributes{$attribute}' is invalid in the tag!\n"; } $encountered{$attribute} = 1; } foreach my $required ("numRows", "columns") { if (!exists $encountered{$required}) { die "$errStr '$required' is required in the tag!\n"; } } $self->{resultSetObj}->{numRows} = $attributes{numRows}; $self->{resultSetObj}->{columns} = $attributes{columns}; # now gather the rows if ($attributes{numRows} > 0) { if (!$attributes{columns}) { $self->getRowData(node => $nodes[0]); } else { $self->getRowColumns(node => $nodes[0]); } } } # void getRowData(node) # requires: node # returns: nothing # called when columns = 0 sub getRowData { my $self = shift; my %args = ( node => undef, @_ ); my $node = $args{node}; my @rows = (); my $errStr = "DBIWrapper::XMLParser->getRowData() - Error:"; if (!defined $node) { die "$errStr You must define the rows node to work from!\n"; } # gather all 's my @nodes = $self->getNodes(path => "*", context => $node); if (scalar @nodes == 0) { die "$errStr Your XML file doesn't contain any 's but numRows = '$self->{resultSetObj}->{numRows}'!\n"; } for (my $i=0; $i < scalar @nodes; $i++) { # gather all attributes of the tag. my %attributes = $self->getAttributes(node => $nodes[$i]); my @columnNames = (); my %encountered = (); if (scalar(keys %attributes) == 0) { die "$errStr row: $i has 0 attributes defined!\n"; } foreach my $attribute (sort keys %attributes) { if (exists $encountered{$attribute}) { die "$errStr You have already defined '$attribute' in the tag!\n"; } $encountered{$attribute} = 1; push @columnNames, $attribute; } push @rows, \%attributes; $self->{resultSetObj}->{columnNames} = \@columnNames if ($i == 0); $self->{resultSetObj}->{columnNamesHash} = \%encountered if ($i == 0); } $self->{resultSetObj}->{rows} = \@rows; } # void getRowColumns(node) # requires: node # returns: nothing # called when columns = 0 sub getRowColumns { my $self = shift; my %args = ( node => undef, @_ ); my $node = $args{node}; my @rows = (); my $errStr = "DBIWrapper::XMLParser->getRowColumns() - Error:"; if (!defined $node) { die "$errStr You must define the rows node to work from!\n"; } # gather all 's my @nodes = $self->getNodes(path => "*", context => $node); if (scalar @nodes == 0) { die "$errStr Your XML file doesn't contain any 's but numRows = '$self->{resultSetObj}->{numRows}'!\n"; } for (my $i=0; $i < scalar @nodes; $i++) { # gather all 's my @columnNames = (); my %columnNamesHash = (); my %rowValues = (); my @columns = $self->getNodes(path => "*", context => $nodes[$i]); if (scalar @columns == 0) { die "$errStr No 's defined for row $i!\n"; } for (my $j=0; $j < scalar @columns; $j++) { my $column = $columns[$j]; # gather all attributes of the tag. my %attributes = $self->getAttributes(node => $column); my %encountered = (); if (scalar(keys %attributes) == 0) { die "$errStr row: $i, column: $j has 0 attributes defined!\n"; } foreach my $attribute (sort keys %attributes) { if (exists $encountered{$attribute}) { die "$errStr You have already defined '$attribute' in the tag! row: $i, column: $j\n"; } if ($attribute !~ /^(name|value)$/) { die "$errStr '$attribute' is invalid in the tag! row: $i, column: $j\n"; } $encountered{$attribute} = 1; } # make sure we have all the required attributes for this column. foreach my $required ("name", "value") { if (not exists $encountered{$required}) { die "$errStr '$required' is required in the tag! row: $i, column: $j\n"; } } push @columnNames, $attributes{name}; $columnNamesHash{$attributes{name}} = 1; $rowValues{$attributes{name}} = $attributes{value}; } push @rows, \%rowValues; @columnNames = sort @columnNames; $self->{resultSetObj}->{columnNames} = \@columnNames if ($i == 0); $self->{resultSetObj}->{columnNamesHash} = \%columnNamesHash if ($i == 0); } $self->{resultSetObj}->{rows} = \@rows; } 1; __END__ =head1 NAME XMLParser - The XML Configuration Parser Module. =head1 SYNOPSIS use DBIWrapper::XMLParser; my $obj = DBIWrapper::XMLParser->new(file => "config.xml"); my $resultSetObj = $obj->parse; # this is a ResultSet object. =head1 DESCRIPTION XMLParser will parse XML files that have been generated by the DBIWrapper readXML method. See the DBIWrapper::ResultSet man page for the structure of the returned data. =head1 FUNCTIONS scalar new(file, string) Creates a new instance of the DBIWrapper::ResultSet object. file points to the XML Config file to use. If you don't specify a file to work with then you must specify the xml via the string argument. If you specify both, then the string will take precedence. The file must still point to a valid file. DBIWrapper::ResultSet parse(void) Does the actual parsing of the XML file and generates the resulting data object and returns it. string getVersion(void) returns the version value from the parent tag. =head1 VARIABLES resultFile - The xml file name we are working with or the contents of the string of xml passed in. resultSetVersion - The version of the XML file we require. resultSetObj - ResultSet object that represents the xml file. xmlObj - The XML::LibXML object being used to parse the XML File. NOTE: All data fields are accessible by specifying the object and pointing to the data member to be modified on the left-hand side of the assignment. Ex. $obj->variable($newValue); or $value = $obj->variable; =head1 AUTHOR Xperience, Inc. (mailto:admin at pcxperience.com) =head1 SEE ALSO perl(1), DBIWrapper::ResultSet(3) =cut