use strict;
use IO::File;
use File::Spec;
# Initialise filenames and check they're there
my $XMLFile = File::Spec->catfile('t', 'test1.xml'); # t/test1.xml
unless(-e $XMLFile) {
print STDERR "test data missing...";
print "1..0\n";
exit 0;
}
print "1..65\n";
my $t = 1;
##############################################################################
# S U P P O R T R O U T I N E S
##############################################################################
##############################################################################
# Print out 'n ok' or 'n not ok' as expected by test harness.
# First arg is test number (n). If only one following arg, it is interpreted
# as true/false value. If two args, equality = true.
#
sub ok {
my($n, $x, $y) = @_;
die "Sequence error got $n expected $t" if($n != $t);
$x = 0 if(@_ > 2 and $x ne $y);
print(($x ? '' : 'not '), 'ok ', $t++, "\n");
}
##############################################################################
# Take two scalar values (may be references) and compare them (recursively
# if necessary) returning 1 if same, 0 if different.
#
sub DataCompare {
my($x, $y) = @_;
my($i);
if(!defined($x)) {
return(1) if(!defined($y));
print STDERR "$t:DataCompare: undef != $y\n";
return(0);
}
if(!ref($x)) {
return(1) if($x eq $y);
print STDERR "$t:DataCompare: $x != $y\n";
return(0);
}
if(ref($x) eq 'ARRAY') {
unless(ref($y) eq 'ARRAY') {
print STDERR "$t:DataCompare: expected arrayref, got: $y\n";
return(0);
}
if(scalar(@$x) != scalar(@$y)) {
print STDERR "$t:DataCompare: expected ", scalar(@$x),
" element(s), got: ", scalar(@$y), "\n";
return(0);
}
for($i = 0; $i < scalar(@$x); $i++) {
DataCompare($x->[$i], $y->[$i]) || return(0);
}
return(1);
}
if(ref($x) eq 'HASH') {
unless(ref($y) eq 'HASH') {
print STDERR "$t:DataCompare: expected hashref, got: $y\n";
return(0);
}
if(scalar(keys(%$x)) != scalar(keys(%$y))) {
print STDERR "$t:DataCompare: expected ", scalar(keys(%$x)),
" key(s), (", join(', ', keys(%$x)),
") got: ", scalar(keys(%$y)), " (", join(', ', keys(%$y)),
")\n";
return(0);
}
foreach $i (keys(%$x)) {
unless(exists($y->{$i})) {
print STDERR "$t:DataCompare: missing hash key - {$i}\n";
return(0);
}
DataCompare($x->{$i}, $y->{$i}) || return(0);
}
return(1);
}
print STDERR "Don't know how to compare: " . ref($x) . "\n";
return(0);
}
##############################################################################
# T E S T R O U T I N E S
##############################################################################
eval "use XML::Simple;";
ok(1, !$@); # Module compiled OK
unless($XML::Simple::VERSION eq '1.08') {
print STDERR "Warning: XML::Simple::VERSION = $XML::Simple::VERSION (expected 1.08)...";
}
# Start by parsing an extremely simple piece of XML
my $opt = XMLin(q());
my $expected = {
name1 => 'value1',
name2 => 'value2',
};
ok(2, 1); # XMLin() didn't crash
ok(3, defined($opt)); # and it returned a value
ok(4, ref($opt) eq 'HASH'); # and a hasref at that
ok(5, DataCompare($opt, $expected));
# Now try a slightly more complex one that returns the same value
$opt = XMLin(q(
value1
value2
));
ok(6, DataCompare($opt, $expected));
# And something else that returns the same (line break included to pick up
# missing /s bug)
$opt = XMLin(q());
ok(7, DataCompare($opt, $expected));
# Try something with two lists of nested values
$opt = XMLin(q(
value1.1
value1.2
value1.3
value2.1
value2.2
value2.3
)
);
ok(8, DataCompare($opt, {
name1 => [ 'value1.1', 'value1.2', 'value1.3' ],
name2 => [ 'value2.1', 'value2.2', 'value2.3' ],
}));
# Now a simple nested hash
$opt = XMLin(q(
)
);
ok(9, DataCompare($opt, {
item => { name1 => 'value1', name2 => 'value2' }
}));
# Now a list of nested hashes
$opt = XMLin(q(
)
);
ok(10, DataCompare($opt, {
item => [
{ name1 => 'value1', name2 => 'value2' },
{ name1 => 'value3', name2 => 'value4' }
]
}));
# Now a list of nested hashes transformed into a hash using default key names
my $string = q(
);
my $target = {
item => {
item1 => { attr1 => 'value1', attr2 => 'value2' },
item2 => { attr1 => 'value3', attr2 => 'value4' }
}
};
$opt = XMLin($string);
ok(11, DataCompare($opt, $target));
# Same thing left as an array by suppressing default key names
$target = {
item => [
{name => 'item1', attr1 => 'value1', attr2 => 'value2' },
{name => 'item2', attr1 => 'value3', attr2 => 'value4' }
]
};
$opt = XMLin($string, keyattr => [] );
ok(12, DataCompare($opt, $target));
# Same again with alternative key suppression
$opt = XMLin($string, keyattr => {} );
ok(13, DataCompare($opt, $target));
# Try the other two default key attribute names
$opt = XMLin(q(
));
ok(14, DataCompare($opt, {
item => {
item1 => { attr1 => 'value1', attr2 => 'value2' },
item2 => { attr1 => 'value3', attr2 => 'value4' }
}
}));
$opt = XMLin(q(
));
ok(15, DataCompare($opt, {
item => {
item1 => { attr1 => 'value1', attr2 => 'value2' },
item2 => { attr1 => 'value3', attr2 => 'value4' }
}
}));
# Similar thing using non-standard key names
my $xml = q(
);
$target = {
item => {
item1 => { attr1 => 'value1', attr2 => 'value2' },
item2 => { attr1 => 'value3', attr2 => 'value4' }
}
};
$opt = XMLin($xml, keyattr => [qw(xname)]);
ok(16, DataCompare($opt, $target));
# And with precise element/key specification
$opt = XMLin($xml, keyattr => { 'item' => 'xname' });
ok(17, DataCompare($opt, $target));
# Same again but with key field further down the list
$opt = XMLin($xml, keyattr => [qw(wibble xname)]);
ok(18, DataCompare($opt, $target));
# Same again but with key field supplied as scalar
$opt = XMLin($xml, keyattr => qw(xname));
ok(19, DataCompare($opt, $target));
# Weird variation, not exactly what we wanted but it is what we expected
# given the current implementation and we don't want to break it accidently
$xml = q(
);
$target = { item => {
'three' => { 'value' => 3 },
'a' => { 'value' => 1, 'id' => 'one' },
'two' => { 'value' => 2 }
}
};
$opt = XMLin($xml);
ok(20, DataCompare($opt, $target));
# Or somewhat more as one might expect
$target = { item => {
'one' => { 'value' => '1', 'name' => 'a' },
'two' => { 'value' => '2' },
'three' => { 'value' => '3' },
}
};
$opt = XMLin($xml, keyattr => { 'item' => 'id' });
ok(21, DataCompare($opt, $target));
# Now a somewhat more complex test of targetting folding
$xml = q(
);
$target = {
'car' => {
'LW1804' => {
'id' => 2,
'make' => 'GM',
'option' => {
'9926543-1167' => { 'key' => 1, 'desc' => 'Steering Wheel' }
}
},
'SH6673' => {
'id' => 1,
'make' => 'Ford',
'option' => {
'6389733317-12' => { 'key' => 1, 'desc' => 'Electric Windows' },
'3735498158-01' => { 'key' => 2, 'desc' => 'Leather Seats' },
'5776155953-25' => { 'key' => 3, 'desc' => 'Sun Roof' }
}
}
}
};
$opt = XMLin($xml, forcearray => 1, keyattr => { 'car' => 'license', 'option' => 'pn' });
ok(22, DataCompare($opt, $target));
# Now try leaving the keys in place
$target = {
'car' => {
'LW1804' => {
'id' => 2,
'make' => 'GM',
'option' => {
'9926543-1167' => { 'key' => 1, 'desc' => 'Steering Wheel',
'-pn' => '9926543-1167' }
},
license => 'LW1804'
},
'SH6673' => {
'id' => 1,
'make' => 'Ford',
'option' => {
'6389733317-12' => { 'key' => 1, 'desc' => 'Electric Windows',
'-pn' => '6389733317-12' },
'3735498158-01' => { 'key' => 2, 'desc' => 'Leather Seats',
'-pn' => '3735498158-01' },
'5776155953-25' => { 'key' => 3, 'desc' => 'Sun Roof',
'-pn' => '5776155953-25' }
},
license => 'SH6673'
}
}
};
$opt = XMLin($xml, forcearray => 1, keyattr => { 'car' => '+license', 'option' => '-pn' });
ok(23, DataCompare($opt, $target));
# Make sure that the root element name is preserved if we ask for it
$target = XMLin("$xml", forcearray => 1,
keyattr => { 'car' => '+license', 'option' => '-pn' });
$opt = XMLin( $xml, forcearray => 1, keeproot => 1,
keyattr => { 'car' => '+license', 'option' => '-pn' });
ok(24, DataCompare($opt, $target));
# confirm that CDATA sections parse correctly
$xml = q{Hello, world!]]>};
$opt = XMLin($xml);
ok(25, DataCompare($opt, {
'cdata' => 'Hello, world!'
}));
$xml = q{one]]>two]]>};
$opt = XMLin($xml);
ok(26, DataCompare($opt, {
'x' => 'onetwo'
}));
# Try parsing a named external file
$opt = eval{ XMLin($XMLFile); };
ok(27, !$@); # XMLin didn't die
print STDERR $@ if($@);
ok(28, DataCompare($opt, {
location => 't/test1.xml'
}));
# Try parsing default external file (scriptname.xml in script directory)
$opt = eval { XMLin(); };
print STDERR $@ if($@);
ok(29, !$@); # XMLin didn't die
ok(30, DataCompare($opt, {
location => 't/1_XMLin.xml'
}));
# Try parsing named file in a directory in the searchpath
$opt = eval {
XMLin('test2.xml', searchpath => [
'dir1', 'dir2', File::Spec->catdir('t', 'subdir')
] );
};
print STDERR $@ if($@);
ok(31, !$@); # XMLin didn't die
ok(32, DataCompare($opt, { location => 't/subdir/test2.xml' }));
# Ensure we get expected result if file does not exist
$opt = eval {
XMLin('bogusfile.xml', searchpath => [qw(. ./t)] ); # should 'die'
};
ok(33, !defined($opt)); # XMLin failed
ok(34, $@ =~ /Could not find bogusfile.xml in/); # with the expected message
# Try parsing from an IO::Handle
my $fh = new IO::File;
$XMLFile = File::Spec->catfile('t', '1_XMLin.xml'); # t/1_XMLin.xml
$fh->open($XMLFile) || die "$!";
$opt = XMLin($fh);
ok(35, 1); # XMLin didn't die
ok(36, $opt->{location}, 't/1_XMLin.xml'); # and it parsed the right file
# Try parsing from STDIN
close(STDIN);
open(STDIN, $XMLFile) || die "$!";
$opt = XMLin('-');
ok(37, $opt->{location}, 't/1_XMLin.xml'); # parsed the right file
# Confirm anonymous array folding works in general
$opt = XMLin(q(
0.00.10.2
1.01.11.2
2.02.12.2
));
ok(38, DataCompare($opt, {
row => [
[ '0.0', '0.1', '0.2' ],
[ '1.0', '1.1', '1.2' ],
[ '2.0', '2.1', '2.2' ]
]
}));
# Confirm anonymous array folding works in special top level case
$opt = XMLin(q{
one
two
three
});
ok(39, DataCompare($opt, [
qw(one two three)
]));
$opt = XMLin(q(
1
2.1
2.2.1
2.2.2
));
ok(40, DataCompare($opt, [
1,
[
'2.1', [ '2.2.1', '2.2.2']
]
]));
# Check for the dreaded 'content' attribute
$xml = q(
- text
);
$opt = XMLin($xml);
ok(41, DataCompare($opt, {
item => {
content => 'text',
attr => 'value'
}
}));
# And check that we can change its name if required
$opt = XMLin($xml, contentkey => 'text_content');
ok(42, DataCompare($opt, {
item => {
text_content => 'text',
attr => 'value'
}
}));
# Check that it doesn't get screwed up by forcearray option
$xml = q(text content);
$opt = XMLin($xml, forcearray => 1);
ok(43, DataCompare($opt, {
'attr' => 'value',
'content' => 'text content'
}));
# Test that we can force all text content to parse to hash values
$xml = q(text1text2);
$opt = XMLin($xml, forcecontent => 1);
ok(44, DataCompare($opt, {
'x' => { 'content' => 'text1' },
'y' => { 'a' => 2, 'content' => 'text2' }
}));
# And that this is compatible with changing the key name
$opt = XMLin($xml, forcecontent => 1, contentkey => '0');
ok(45, DataCompare($opt, {
'x' => { 0 => 'text1' },
'y' => { 'a' => 2, 0 => 'text2' }
}));
# Check that mixed content parses in the weird way we expect
$xml = q(
Text with a bold word
);
ok(46, DataCompare(XMLin($xml), {
'class' => 'mixed',
'content' => [ 'Text with a ', ' word' ],
'b' => 'bold'
}));
# Confirm single nested element rolls up into a scalar attribute value
$string = q(
value
);
$opt = XMLin($string);
ok(47, DataCompare($opt, {
name => 'value'
}));
# Unless 'forcearray' option is specified
$opt = XMLin($string, forcearray => 1);
ok(48, DataCompare($opt, {
name => [ 'value' ]
}));
# Confirm array folding of single nested hash
$string = q(
);
$opt = XMLin($string, forcearray => 1);
ok(49, DataCompare($opt, {
'inner' => { 'one' => { 'value' => 1 } }
}));
# But not without forcearray option specified
$opt = XMLin($string, forcearray => 0);
ok(50, DataCompare($opt, {
'inner' => { 'name' => 'one', 'value' => 1 }
}));
# Test advanced features of forcearray
$xml = q(
i
ii
iii
3
c
);
$opt = XMLin($xml, forcearray => [ 'two' ]);
ok(51, DataCompare($opt, {
'zero' => '0',
'one' => 'i',
'two' => [ 'ii' ],
'three' => [ 'iii', 3, 'c' ]
}));
# Test 'noattr' option
$xml = q(
text
);
$opt = XMLin($xml, noattr => 1);
ok(52, DataCompare($opt, { nest => 'text' }));
# And make sure it doesn't screw up array folding
$xml = q{
- aalpha
- bbeta
- ggamma
};
$opt = XMLin($xml, noattr => 1);
ok(53, DataCompare($opt, {
'item' => {
'a' => { 'value' => 'alpha' },
'b' => { 'value' => 'beta' },
'g' => { 'value' => 'gamma' }
}
}));
# Confirm empty elements parse to empty hashrefs
$xml = q(
bob
);
$opt = XMLin($xml, noattr => 1);
ok(54, DataCompare($opt, {
'name' => 'bob',
'outer' => {
'inner1' => {},
'inner2' => {}
}
}));
# Unless 'suppressempty' is enabled
$opt = XMLin($xml, noattr => 1, suppressempty => 1);
ok(55, DataCompare($opt, { 'name' => 'bob', }));
# Check behaviour when 'suppressempty' is set to to undef;
$opt = XMLin($xml, noattr => 1, suppressempty => undef);
ok(56, DataCompare($opt, {
'name' => 'bob',
'outer' => {
'inner1' => undef,
'inner2' => undef
}
}));
# Check behaviour when 'suppressempty' is set to to empty string;
$opt = XMLin($xml, noattr => 1, suppressempty => '');
ok(57, DataCompare($opt, {
'name' => 'bob',
'outer' => {
'inner1' => '',
'inner2' => ''
}
}));
# Confirm completely empty XML parses to undef with 'suppressempty'
$xml = q(
);
$opt = XMLin($xml, noattr => 1, suppressempty => 1);
ok(58, DataCompare($opt, undef));
# Test that nothing unusual happens with namespaces by default
$xml = q(
12345678
8001-22374-001
);
$opt = XMLin($xml);
ok(59, DataCompare($opt, {
'xmlns' => 'urn:accounts',
'xmlns:eng' => 'urn:engineering',
'invoice_num' => 12345678,
'eng:partnum' => '8001-22374-001'
}));
# Test that we can pass an option in to turn on XML::Parser's namespace mode
$opt = XMLin($xml, parseropts => [ Namespaces => 1 ]);
ok(60, DataCompare($opt, {
'invoice_num' => 12345678,
'partnum' => '8001-22374-001'
}));
# Test option error handling
$_ = eval { XMLin('', rootname => 'fred') }; # not valid for XMLin()
ok(61, !defined($_));
ok(62, $@ =~ /Unrecognised option:/);
$_ = eval { XMLin('', 'searchpath') };
ok(63, !defined($_));
ok(64, $@ =~ /Options must be name=>value pairs .odd number supplied./);
# Now for a 'real world' test, try slurping in an SRT config file
$opt = XMLin(File::Spec->catfile('t', 'srt.xml'), forcearray => 1);
$target = {
'global' => [
{
'proxypswd' => 'bar',
'proxyuser' => 'foo',
'exclude' => [
'/_vt',
'/save\\b',
'\\.bak$',
'\\.\\$\\$\\$$'
],
'httpproxy' => 'http://10.1.1.5:8080/',
'tempdir' => 'C:/Temp'
}
],
'pubpath' => {
'test1' => {
'source' => [
{
'label' => 'web_source',
'root' => 'C:/webshare/web_source'
}
],
'title' => 'web_source -> web_target1',
'package' => {
'images' => { 'dir' => 'wwwroot/images' }
},
'target' => [
{
'label' => 'web_target1',
'root' => 'C:/webshare/web_target1',
'temp' => 'C:/webshare/web_target1/temp'
}
],
'dir' => [ 'wwwroot' ]
},
'test2' => {
'source' => [
{
'label' => 'web_source',
'root' => 'C:/webshare/web_source'
}
],
'title' => 'web_source -> web_target1 & web_target2',
'package' => {
'bios' => { 'dir' => 'wwwroot/staff/bios' },
'images' => { 'dir' => 'wwwroot/images' },
'templates' => { 'dir' => 'wwwroot/templates' }
},
'target' => [
{
'label' => 'web_target1',
'root' => 'C:/webshare/web_target1',
'temp' => 'C:/webshare/web_target1/temp'
},
{
'label' => 'web_target2',
'root' => 'C:/webshare/web_target2',
'temp' => 'C:/webshare/web_target2/temp'
}
],
'dir' => [ 'wwwroot' ]
},
'test3' => {
'source' => [
{
'label' => 'web_source',
'root' => 'C:/webshare/web_source'
}
],
'title' => 'web_source -> web_target1 via HTTP',
'addexclude' => [ '\\.pdf$' ],
'target' => [
{
'label' => 'web_target1',
'root' => 'http://127.0.0.1/cgi-bin/srt_slave.plx',
'noproxy' => 1
}
],
'dir' => [ 'wwwroot' ]
}
}
};
ok(65, DataCompare($target, $opt));
exit(0);