my $sXML = genRecord($aData); #default options
#or
print genRecord($aData, $hOptions);
####
use strict;
use warnings;
package XML::HoA;
use base 'Exporter';
our @EXPORT_OK=qw(genRecord);
=pod
=head1 SYNOPSIS
# $aData reference to an array storing zero or more
# hash references. Each hash reference represents
# a different record. The key-value pairs of the
# hash store the value assigned to each field in
# the record. The keys are the field names and
# the value for each key is the value assigned to
# that field
#
# $hOptions reference to a hash storing formatting options
# for each record.
#
# key meaning
# ---- -------
# format 6 possible values - see below
# defaults to NAME_EQ_VAL
# record tag name for XML element storing
# each record - defaults to 'record'
# field tag name for the XML element storing
# each field - defaults to 'field'
# name tag name for the XML element storing
# the field name - defaults to 'name'
# value tag name for the XML element storing
# the value assigned to a field
# - defaults to 'value'
# filter reference to a subroutine that filters
# hash keys and determines which get
# included in the record
#
# $iDepth number of tabs to indent the first record
# the "tab" is defined via $XML::HoA::TAB and
# is initially set to " "; $iDepth defaults to 0
my $sXML;
$sXML = genRecord($aData);
$sXML = genRecord($aData, $hOptions, $iDepth);
# Example
my $aData
= [ { lname => 'Krynicky', fname => 'Jenda' }
, { 'Site' => 'PerlMonks', 'Nick' => 'Jenda' }
];
my $hOptions = { format => XML::HoA::NAME_TEXT_VAL_TEXT
, record => 'page'
, name => 'id' };
print genRecord($aData, $hOptions);
=head1 DESCRIPTION
Module for converting arrays of hashes to XML. This module
supports custom names for tags and six different ways of
representing the fields of a record.
You can choose which of the six you want by setting the C
key of options hash (2nd parameter of C. Other
keys of the options hash will be interpreted based on your selected
format.
=head2 Configuring formats
The six format values are:
=head3 XML::HoA::NAME_EQ_VAL
This format converts field values to C, like
this:
You can change the name of the record element using the option
hash key C. For example, if your option hash was
{ format => XML::HoA::NAME_EQ_VAL, record => 'page' }
Then your output would be:
=head3 XML::HoA::NAME_TAG_VAL_ATTR
This format converts each field to an element whose tag is the
the same as the field name. The value is an attribute:
As with C, you can set the C element of the
option hash to change the record tag. You can also use the
C element to change the attribute name. For example if
your option hash was:
{ format => XML::HoA::NAME_EQ_VAL
, record => 'page'
, value=>'input'
}
Then your output would be:
=head3 XML::HoA::NAME_TAG_VAL_TEXT
This format converts each field to an element whose tag is the
the same as the field name. The value is element text:
Krynicky
Jenda
As with C, only the C and C
keys of the option hash will be used.
=head3 XML::HoA::NAME_ATTR_VAL_ATTR
This format has one element for each field. The value is
stored as an attribute of each of these elements:
All of the hash keys may be used to change the name of tags.
The following value of the options hash:
{ format => XML::HoA::NAME_TEXT_VAL_TEXT
, record => 'person'
, field => 'property'
, name => 'ID' #attribute name
, value => 'VALUE' #attribute name
}
would produce the following XML:
=head3 XML::HoA::NAME_ATTR_VAL_TEXT
This format has one element for each field. The value is
stored as the text of each of these elements:
Krynicky
Jenda
This format uses the same option hash keys as
C. TheC key is ignored
since there is no value attribute.
=head3 XML::HoA::NAME_TEXT_VAL_TEXT
This format assigns the field name and field value to separate
elements:
lname
Krynicky
fname
Jenda
All of the hash keys may be used to change the name of tags.
The following value of the options hash:
{ format => XML::HoA::NAME_TEXT_VAL_TEXT
, record => 'person'
, field => 'property'
, name => 'ID'
, value => 'VALUE'
}
Would result in the following XML:
lname
Krynicky
fname
Jenda
=head2 Filtering record data
Sometimes you don't want to print all of the keys in a hash. If
you need to filter out some of the keys you can write a
routine. This routine takes three arguments, in order:
* the current hash key
* the value assigned to the current hash key
* a reference to the hash storing the record data
It returns 1 if the key should be included and 0 otherwise.
The default filter is a no-op: it always returns 1 so that
all keys are selected.
The following example only includes fields whose names are all
lowercase in the XML record:
my $crFilter = sub {
my ($k,$v,$hRecord) = @_;
return $k =~ /^[a-z]+$/;
}
print genRecord($aData, { filter => $crFilter });
=head1 CAVEATS
Tested only via inspection.
=head1 AUTHOR
Elizabeth Grace Frank-Backman
=head1 COPYRIGHT
Copyright (c) 2008- Elizabeth Grace Frank-Backman. All rights
reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=cut
#==================================================================
# SHARED DATA AND CONSTANTS
#==================================================================
our $TAB=" ";
use constant {
NAME_EQ_VAL => 1
, NAME_TAG_VAL_ATTR => 2
, NAME_TAG_VAL_TEXT => 3
, NAME_ATTR_VAL_ATTR => 4
, NAME_ATTR_VAL_TEXT => 5
, NAME_TEXT_VAL_TEXT => 6
};
my $DEFAULT_FORMAT=NAME_EQ_VAL;
#==================================================================
# FUNCTIONS
#==================================================================
sub genEndTag {
my ($sIndent, $sTag, $bAttributes) = @_;
return $bAttributes ? "/>\n": "$sIndent$sTag>\n";
}
#-----------------------------------------------------------
sub genField {
my ($k, $v, $hOptions, $iDepth) = @_;
$hOptions = {} unless defined($hOptions);
$iDepth=0 unless defined($iDepth);
#set up options
my $iFmt = $hOptions->{format};
$iFmt=$DEFAULT_FORMAT unless defined($iFmt);
my $sFieldTag = $hOptions->{field};
$sFieldTag='field' unless defined($sFieldTag);
my $sNameTag = $hOptions->{name};
$sNameTag='name' unless defined($sNameTag);
my $sValueTag = $hOptions->{value};
$sValueTag='value' unless defined($sValueTag);
#generate field XML
my $sXML='';
my $sIndent=$TAB x $iDepth;
if ($iFmt eq NAME_EQ_VAL) {
$sXML .= "$sIndent$k=\"$v\"\n";
} elsif ($iFmt eq NAME_TAG_VAL_ATTR) {
$sXML .= "$sIndent<$k $sValueTag=\"$v\"/>\n";
} elsif ($iFmt eq NAME_TAG_VAL_TEXT) {
$sXML .= "$sIndent<$k>$v$k>\n";
} elsif ($iFmt eq NAME_ATTR_VAL_ATTR) {
$sXML .= "$sIndent<$sFieldTag $sNameTag=\"$k\" "
."$sValueTag=\"$v\"/>\n";
} elsif ($iFmt eq NAME_ATTR_VAL_TEXT) {
$sXML .= "$sIndent<$sFieldTag $sNameTag=\"$k\">"
."$v$sFieldTag>\n";
} elsif ($iFmt eq NAME_TEXT_VAL_TEXT) {
my $sIndentTag= $TAB x ($iDepth + 1);
$sXML .= "$sIndent<$sFieldTag>\n";
$sXML .= "$sIndentTag<$sNameTag>$k$sNameTag>\n";
$sXML .= "$sIndentTag<$sValueTag>$v$sValueTag>\n";
$sXML .= "$sIndent$sFieldTag>\n";
}
return $sXML;
}
#------------------------------------------------------------
sub genRecord {
my ($aData, $hOptions, $iDepth) = @_;
$iDepth=0 unless defined($iDepth);
$hOptions = {} unless defined($hOptions);
#set up options
my $iFmt = $hOptions->{format};
$iFmt=$DEFAULT_FORMAT unless defined($iFmt);
my $sRecordTag = $hOptions->{record};
$sRecordTag='record' unless defined($sRecordTag);
my $crFilter = $hOptions->{filter};
my $sIndent=$TAB x $iDepth;
my $sXML = '';
my $bAttributes = ($iFmt eq NAME_EQ_VAL);
foreach my $hRecord (@$aData) {
$sXML .= genStartTag($sIndent, $sRecordTag, $bAttributes);
while (my ($k, $v) = each(%$hRecord)) {
if (defined($crFilter) && ! &$crFilter($k,$v,$hRecord)) {
next;
}
$sXML .= genField($k, $v, $hOptions, $iDepth+1);
}
$sXML .= genEndTag($sIndent, $sRecordTag, $bAttributes);
}
return $sXML;
}
#------------------------------------------------------------
sub genStartTag {
my ($sIndent, $sTag, $bAttributes) = @_;
return "$sIndent<$sTag" . ($bAttributes ? "\n" : ">\n");
}
#==================================================================
# MODULE INITIALIZATION
#==================================================================
1;
####
use strict;
use warnings;
use XML::HoA qw(genRecord);
my $aData = [ { 'lname' => 'Krynicky'
, 'fname' => 'Jenda'
, PageId => 1, Name => 'Civil name'
}
, { 'Site' => 'PerlMonks'
, 'Nick' => 'Jenda'
, PageId => 2, Name => 'Online identity'
}
];
my $crFilter = sub { my $k = shift;
return $k !~ /^(?:PageId|Name)$/; };
my $hOptions={format=>XML::HoA::NAME_TEXT_VAL_TEXT
, record=>'page'
, name=>'ID'
, filter => $crFilter };
print genRecord($aData, $hOptions);