use strict;
use warnings;
use Test::More;
plan tests => 201;
##############################################################################
# S U P P O R T R O U T I N E S
##############################################################################
##############################################################################
# Read file and return contents as a scalar.
#
sub ReadFile {
local($/) = undef;
open(_READ_FILE_, $_[0]) || die "open($_[0]): $!";
my $data = <_READ_FILE_>;
close(_READ_FILE_);
return($data);
}
use XML::Simple;
# Confirm error when mandatory parameter missing
$_ = eval {
XMLout();
};
ok(!defined($_), 'call with no args proves fatal');
like($@, qr/XMLout\(\) requires at least one argument/,
'with correct error message');
# Try encoding a scalar value
my $xml = XMLout("scalar");
ok(1, 'XMLout did not crash');
ok(defined($xml), 'and it returned an XML string');
is(XMLin($xml), 'scalar', 'which parses back OK');
# Next try encoding a hash
my $hashref1 = { one => 1, two => 'II', three => '...' };
my $hashref2 = { one => 1, two => 'II', three => '...' };
# Expect:
#
$_ = XMLout($hashref1);
is_deeply(XMLin($_), $hashref1, 'encoded a hash');
ok(s/one="1"//, 'first key encoded OK');
ok(s/two="II"//, 'second key encoded OK');
ok(s/three="..."//, 'third key encoded OK');
like($_, qr/^<\w+\s+\/>/, 'no other attributes encoded');
# Now try encoding a hash with a nested array
my $ref = {array => [qw(one two three)]};
# Expect:
#
# one
# two
# three
#
$_ = XMLout($ref);
is_deeply(XMLin($_), $ref, 'encoded a hash with nested array');
ok(s{one\s*
two\s*
three}{}sx, 'array elements encoded in correct order');
like($_, qr/^<(\w+)\s*>\s*<\/\1>\s*$/s, 'no other spurious encodings');
# Now try encoding a nested hash
$ref = { value => '555 1234',
hash1 => { one => 1 },
hash2 => { two => 2 } };
# Expect:
#
#
#
#
$_ = XMLout($ref);
is_deeply(XMLin($_), $ref, 'encoded nested hashes');
ok(s{\s*}{}s, 'nested hash 1 ok');
ok(s{\s*}{}s, 'nested hash 2 ok');
like($_, qr{^<(\w+)\s+value="555 1234"\s*>\s*\1>\s*$}s, 'whole OK');
# Now try encoding an anonymous array
$ref = [ qw(1 two III) ];
# Expect:
#
# 1
# two
# III
#
$_ = XMLout($ref);
is_deeply(XMLin($_), $ref, 'encoded anonymous array');
like($_, qr{
^<(\w+)\s*>
\s*1
\s*two
\s*III
\s*\1>\s*$}sx, 'output matches expectations');
# Now try encoding a nested anonymous array
$ref = [ [ qw(1.1 1.2) ], [ qw(2.1 2.2) ] ];
# Expect:
#
#
# 1.1
# 1.2
#
#
# 2.1
# 2.2
#
#
$_ = XMLout($ref);
is_deeply(XMLin($_), $ref, 'encoded nested anonymous arrays');
like($_, qr{
<(\w+)\s*>
\s*
\s*1\.1
\s*1\.2
\s*
\s*
\s*2\.1
\s*2\.2
\s*
\s*\1\s*>
}sx, 'output matches expectations');
# Now try encoding a hash of hashes with key folding disabled
$ref = { country => {
England => { capital => 'London' },
France => { capital => 'Paris' },
Turkey => { capital => 'Istanbul' },
}
};
# Expect:
#
#
#
#
#
#
#
$_ = XMLout($ref, keyattr => []);
is_deeply(XMLin($_), $ref, 'encoded hash of hashes with folding disabled');
ok(s{\s*}{}s, 'nested hash 1 ok');
ok(s{\s*}{}s, 'nested hash 2 ok');
ok(s{\s*}{}s, 'nested hash 3 ok');
ok(s{\s*}{}s, 'container hash ok');
ok(s{^<(\w+)\s*>\s*\1>$}{}s, 'document ok');
# Try encoding same again with key folding set to non-standard value
# Expect:
#
#
#
#
#
my $expected = qr{
^<(\w+)\s*>\s*
(
\s*
|\s*
|\s*
){3}
\1>$
}xs;
$xml = XMLout($ref, keyattr => ['fullname']);
is_deeply(XMLin($xml, keyattr => ['fullname']), $ref,
'encoded hash of hashes with explicit folding enabled');
like($xml, $expected, 'output as expected');
# Same again but specify name as scalar rather than array
$xml = XMLout($ref, keyattr => 'fullname');
like($xml, $expected, 'still works when keyattr is scalar');
# Same again but specify keyattr as hash rather than array
$xml = XMLout($ref, keyattr => { country => 'fullname' });
like($xml, $expected, 'still works when keyattr is hash');
# Same again but add leading '+'
$xml = XMLout($ref, keyattr => { country => '+fullname' });
like($xml, $expected, "still works when keyattr is hash with leading '+'");
# and leading '-'
$xml = XMLout($ref, keyattr => { country => '-fullname' });
like($xml, $expected, "still works when keyattr is hash with leading '-'");
# One more time but with default key folding values
# Expect:
#
#
#
#
#
$expected = qr{
^<(\w+)\s*>\s*
(
\s*
|\s*
|\s*
){3}
\1>$
}xs;
$xml = XMLout($ref);
is_deeply(XMLin($xml), $ref,
'encoded hash of hashes with default folding enabled');
like($xml, $expected, "expected output with default keyattr");
# Finally, confirm folding still works with only one nested hash
# Expect:
#
#
#
$ref = { country => { England => { capital => 'London' } } };
$_ = XMLout($ref);
is_deeply(XMLin($_, forcearray => 1), $ref, 'single nested hash unfolded');
ok(s{\s*name="England"}{uk}s, 'attr 1 ok');
ok(s{\s*capital="London"}{uk}s, 'attr 2 ok');
ok(s{\s*}{}s, 'element ok');
ok(s{^<(\w+)\s*>\s*\1>$}{}s, 'document ok');
# Check that default XML declaration works
#
# Expect:
#
#
$ref = { one => 1 };
$_ = XMLout($ref, xmldecl => 1);
is_deeply(XMLin($_), $ref, 'generated doc with XML declaration');
ok(s{^\Q\E}{}s, 'XML declaration OK');
like($_, qr{^\s*}s, 'data OK too');
# Check that custom XML declaration works
#
# Expect:
#
#
$_ = XMLout($ref, xmldecl => "");
is_deeply(XMLin($_), $ref, 'generated doc with custom XML declaration');
ok(s{^\Q\E}{}s, 'XML declaration OK');
like($_, qr{^\s*}s, 'data OK too');
# Check that special characters do get escaped
$ref = { a => '', b => '"B"', c => '&C&' };
$_ = XMLout($ref);
is_deeply(XMLin($_), $ref, 'generated document with escaping');
ok(s{a="<A>"}{}s, 'angle brackets escaped OK');
ok(s{b=""B""}{}s, 'double quotes escaped OK');
ok(s{c="&C&"}{}s, 'ampersands escaped OK');
ok(s{^<(\w+)\s*/>$}{}s, 'data OK too');
# unless we turn escaping off
$ref = { a => '', b => '"B"', c => ['&C&'] };
$_ = XMLout($ref, noescape => 1);
ok(s{a=""}{}s, 'generated unescaped angle brackets');
ok(s{b=""B""}{}s, 'generated unescaped double quotes');
ok(s{&C&}{}s, 'generated unescaped ampersands');
ok(s{^<(\w+)\s*>\s*\1>$}{}s, 'data OK too');
# same again but with a scalar
$xml = XMLout("", noescape => 1);
like($xml, qr{^<(\w+)>\1>}, "Unescaped scalar as expected too");
# Try encoding a circular data structure and confirm that it fails
$_ = eval {
my $ref = { a => '1' };
$ref->{b} = $ref;
XMLout($ref);
};
ok(!defined($_), 'caught circular data structure');
like($@, qr/circular data structures not supported/,
'with correct error message');
# Try encoding a repetitive (but non-circular) data structure and confirm that
# it does not fail
$_ = eval {
my $a = { alpha => 1 };
my $ref = { a => $a, b => $a };
XMLout($ref);
};
ok(defined($_), 'repetitive (non-circular) data structure not fatal');
like($_, qr{^
(
\s*
|
\s*
){2}
\s*
}xs, 'and encodes as expected');
# Try encoding a non array/hash blessed reference and confirm that it fails
$_ = eval { my $ref = bless \*STDERR, 'BogoClass'; XMLout($ref) };
is($_, undef, 'caught blessed non array/hash reference in data structure');
like($@, qr/Can't encode a value of type: /, 'with correct error message');
# Repeat some of the above tests with named root element
# Try encoding a scalar value
$xml = XMLout("scalar", rootname => 'TOM');
ok(defined($xml), 'generated document with named root element');
is(XMLin($xml), 'scalar', 'parsed it back correctly');
like($xml, qr/^\s*scalar<\/TOM>\s*$/si, 'XML as expected');
# Next try encoding a hash
# Expect:
#
$_ = XMLout($hashref1, rootname => 'DICK');
is_deeply(XMLin($_), $hashref1, 'same again but encoded a hash');
ok(s/one="1"//, 'first key encoded OK');
ok(s/two="II"//, 'second key encoded OK');
ok(s/three="..."//, 'third key encoded OK');
like($_, qr/^/, 'XML looks OK');
# Now try encoding a hash with a nested array
$ref = {array => [qw(one two three)]};
# Expect:
#
# one
# two
# three
#
$_ = XMLout($ref, rootname => 'LARRY');
is_deeply(XMLin($_), $ref, 'same again but with array in hash');
ok(s{one\s*
two\s*
three}{}sx, 'array encoded in correct order');
like($_, qr/^<(LARRY)\s*>\s*<\/\1>\s*$/s, 'only expected root element left');
# Now try encoding a nested hash
$ref = { value => '555 1234',
hash1 => { one => 1 },
hash2 => { two => 2 } };
# Expect:
#
#
#
#
$_ = XMLout($ref, rootname => 'CURLY');
is_deeply(XMLin($_), $ref, 'same again but with nested hashes');
ok(s{\s*}{}s, 'hash 1 encoded OK');
ok(s{\s*}{}s, 'hash 2 encoded OK');
like($_, qr{^<(CURLY)\s+value="555 1234"\s*>\s*\1>\s*$}s, 'document OK');
# Now try encoding an anonymous array
$ref = [ qw(1 two III) ];
# Expect:
#
# 1
# two
# III
#
$_ = XMLout($ref, rootname => 'MOE');
is_deeply(XMLin($_), $ref, 'same again but with nested anonymous array');
like($_, qr{
^<(MOE)\s*>
\s*1
\s*two
\s*III
\s*\1>\s*$}sx, 'document OK');
# Test again, this time with no root element
# Try encoding a scalar value
like(XMLout("scalar", rootname => ''), qr/scalar\s+/s,
'encoded scalar with no root element');
like(XMLout("scalar", rootname => undef), qr/scalar\s+/s,
'same again but with rootname = undef');
# Next try encoding a hash
# Expect:
# 1
# II
# ...
$_ = XMLout($hashref1, rootname => '');
is_deeply(XMLin("$_"), $hashref1,
'generated doc with no root element from hash');
ok(s/1<\/one>//, 'first key encoded OK');
ok(s/II<\/two>//, 'second key encoded OK');
ok(s/...<\/three>//, 'third key encoded OK');
like($_, qr/^\s*$/, 'document OK');
# Now try encoding a nested hash
$ref = { value => '555 1234',
hash1 => { one => 1 },
hash2 => { two => 2 } };
# Expect:
# 555 1234
#
#
$_ = XMLout($ref, rootname => '');
is_deeply(XMLin("$_"), $ref,
'generated docucment with no root element from nested hashes');
ok(s{555 1234<\/value>\s*}{}s, 'first element OK');
ok(s{\s*}{}s, 'second element OK');
ok(s{\s*}{}s, 'third element OK');
like($_, qr{^\s*$}s, 'document OK');
# Now try encoding an anonymous array
$ref = [ qw(1 two III) ];
# Expect:
# 1
# two
# III
$_ = XMLout($ref, rootname => '');
is_deeply(XMLin("$_"), $ref,
'generated doc with no root name from array');
like($_, qr{
^\s*1
\s*two
\s*III
\s*$}sx, 'document OK');
# Test option error handling
$_ = eval { XMLout($hashref1, searchpath => []) }; # only valid for XMLin()
ok(!defined($_), 'caught attempt to specify searchpath on XMLout');
like($@, qr/Unrecognised option:/, 'with correct error message');
$_ = eval { XMLout($hashref1, 'bogus') };
ok(!defined($_), 'caught attempt to specify odd number of option args');
like($@, qr/Options must be name=>value pairs \(odd number supplied\)/,
'with correct error message');
# Test output to file
my $TestFile = 'testoutput.xml';
unlink($TestFile);
ok(!-e $TestFile, 'output file does not exist');
$xml = XMLout($hashref1);
eval { XMLout($hashref1, outputfile => $TestFile); };
ok(-e $TestFile, 'created xml output file');
is(ReadFile($TestFile), $xml, 'Contents match expectations');
unlink($TestFile);
# Test output to an IO handle
ok(!-e $TestFile);
eval {
open my $fh, '>', $TestFile or die "$!";
XMLout($hashref1, outputfile => $fh);
$fh->close();
};
ok(-e $TestFile, 'create XML output file via IO::File');
is(ReadFile($TestFile), $xml, 'Contents match expectations');
unlink($TestFile);
# After all that, confirm that the original hashref we supplied has not
# been corrupted.
is_deeply($hashref1, $hashref2, 'original data not corrupted');
# Confirm that hash keys with leading '-' are skipped
$ref = {
'a' => 'one',
'-b' => 'two',
'-c' => {
'one' => 1,
'two' => 2
}
};
$_ = XMLout($ref, rootname => 'opt');
like($_, qr{^\s*\s*$}s, "skipped hashkeys with '-' prefix");
# Try a more complex unfolding with key attributes named in a hash
$ref = {
'car' => {
'LW1804' => {
'option' => {
'9926543-1167' => { 'key' => 1, 'desc' => 'Steering Wheel' }
},
'id' => 2,
'make' => 'GM'
},
'SH6673' => {
'option' => {
'6389733317-12' => { 'key' => 2, 'desc' => 'Electric Windows' },
'3735498158-01' => { 'key' => 3, 'desc' => 'Leather Seats' },
'5776155953-25' => { 'key' => 4, 'desc' => 'Sun Roof' },
},
'id' => 1,
'make' => 'Ford'
}
}
};
# Expect:
#
#
#
#
#
#
#
#
#
#
$_ = XMLout($ref, keyattr => { 'car' => 'license', 'option' => 'pn' });
is_deeply(XMLin($_,
forcearray => 1,
keyattr => { 'car' => 'license', 'option' => 'pn' }
), $ref, 'generated document from complex nested hash with unfolding');
ok(s{\s*make="GM"}{gm}s, 'element 1 attribute 1 OK');
ok(s{\s*id="2"}{gm}s, 'element 1 attribute 2 OK');
ok(s{\s*license="LW1804"}{gm}s, 'element 1 attribute 3 OK');
ok(s{\s*desc="Steering Wheel"}{opt}s, 'element 1.1 attribute 1 OK');
ok(s{\s*pn="9926543-1167"}{opt}s, 'element 1.1 attribute 2 OK');
ok(s{\s*key="1"}{opt}s, 'element 1.1 attribute 3 OK');
ok(s{\s*\s*\s*}{CAR}s,
'elements 1 and 1.1 OK');
ok(s{\s*make="Ford"}{ford}s, 'element 2 attribute 1 OK');
ok(s{\s*id="1"}{ford}s, 'element 2 attribute 2 OK');
ok(s{\s*license="SH6673"}{ford}s, 'element 2 attribute 3 OK');
ok(s{\s*desc="Electric Windows"}{1}s, 'element 2.1 attribute 1 OK');
ok(s{\s*pn="6389733317-12"}{1}s, 'element 2.1 attribute 2 OK');
ok(s{\s*key="2"}{1}s, 'element 2.1 attribute 3 OK');
ok(s{\s*\s*(\s*){3}}{CAR}s, 'element 2 OK');
ok(s{^<(\w+)\s*>\s*CAR\s*CAR\s*\1>$}{}s, 'document OK');
# Check that empty hashes translate to empty tags
$ref = {
'one' => {
'attr1' => 'avalue1',
'nest1' => [ 'nvalue1' ],
'nest2' => {}
},
two => {}
};
$_ = XMLout($ref);
ok(s{\s*}{}, 'nested empty hash OK');
ok(s{nvalue1\s*}{}, 'array OK');
ok(s{\s*}{}, 'scalar OK');
ok(s{\s*\s*\s*}{}, 'nesting OK');
ok(s{\s*}{}, 'empty hash OK');
like($_, qr{^\s*<(\w+)\s*>\s*\s*\s*\1\s*>\s*$}, 'document OK');
# Check undefined values generate warnings
{
local($^W) = 1;
my $warn = '';
local $SIG{__WARN__} = sub { $warn = $_[0] };
$ref = { 'one' => 1, 'two' => undef };
my $expect = qr/^<\w+(\s+one="1"|\s+two=""){2}/;
$_ = XMLout($ref);
like($warn, qr/Use of uninitialized value/,
'caught warning re uninitialised value');
like($_, $expect, 'undef maps to any empty attribute by default');
# unless warnings are disabled
$^W = 0;
$warn = '';
$_ = XMLout($ref);
is($warn, '', 'no warning re uninitialised value if warnings off');
like($_, $expect, 'undef still maps to any empty attribute');
}
# Unless undef is mapped to empty elements
$ref = { 'tag' => undef };
$_ = XMLout($ref, suppressempty => undef);
like($_, qr{^\s*<(\w*)\s*>\s*\s*\1\s*>\s*$}s,
'uninitialiased values successfully mapped to empty elements');
# Set suppressempty to 1 to not output anything for undef
$ref = { 'one' => 1, 'two' => undef };
$_ = XMLout($ref, suppressempty => 1, noattr => 1);
like($_, qr{^\s*<(\w*)\s*>\s*1\s*\1\s*>\s*$}s,
'uninitialiased values successfully skipped');
# Try undef in an array
$ref = { a => [ 'one', undef, 'three' ] };
$_ = XMLout($ref);
like($_,
qr{
^\s*<(\w*)\s*>
\s*one
\s*
\s*three
\s*\1\s*>\s*$
}xs,
'uninitialiased value in array is empty element');
# And again with SuppressEmpty enabled
$_ = XMLout($ref, SuppressEmpty => 1);
like($_,
qr{
^\s*<(\w*)\s*>
\s*one
\s*three
\s*\1\s*>\s*$
}xs,
'uninitialiased value in array is skipped');
# Test the keeproot option
$ref = {
'seq' => {
'name' => 'alpha',
'alpha' => [ 1, 2, 3 ]
}
};
my $xml1 = XMLout($ref, rootname => 'sequence');
my $xml2 = XMLout({ 'sequence' => $ref }, keeproot => 1);
is_deeply($xml1, $xml2, 'keeproot works as expected');
# Test that items with text content are output correctly
# Expect: text
$ref = { 'one' => 1, 'content' => 'text' };
$_ = XMLout($ref);
like($_, qr{^\s*text\s*$}s, 'content keys mapped OK');
# Even if we change the default value for the 'contentkey' option
$ref = { 'one' => 1, 'text_content' => 'text' };
$_ = XMLout($ref, contentkey => 'text_content');
like($_, qr{^\s*text\s*$}s, 'even when name changed');
# and also if we add the '-' prefix
$_ = XMLout($ref, contentkey => '-text_content');
like($_, qr{^\s*text\s*$}s, 'even with "-" prefix');
# Confirm content key works with undef values (and no warnings)
{
$^W = 1;
my $warn = '';
local $SIG{__WARN__} = sub { $warn = $_[0] };
$_ = eval {
$ref = {
column => [
{ name => 'title', content => 'A Title' },
{ name => 'sponsor', content => undef },
],
};
XMLout($ref, suppress_empty => undef, content_key => 'content');
};
ok(!$warn, 'no warnings with suppress_empty => undef');
like($_, qr{^<(\w+)>
\s*A\sTitle
\s*
\s*
\1>$
}sx, "undef does not cause content tags in output"
);
}
# Check 'noattr' option
$ref = {
attr1 => 'value1',
attr2 => 'value2',
nest => [ qw(one two three) ]
};
# Expect:
#
#
# value1
# value2
# one
# two
# three
#
#
$_ = XMLout($ref, noattr => 1);
unlike($_, qr{=}s, 'generated document with no attributes');
is_deeply(XMLin($_), $ref, 'parses ok');
ok(s{\s*<(attr1)>value1\1>\s*}{NEST}s, 'scalar 1 mapped ok');
ok(s{\s*<(attr2)>value2\1>\s*}{NEST}s, 'scalar 2 mapped ok');
ok(s{\s*<(nest)>one\1>\s*<\1>two\1>\s*<\1>three\1>}{NEST}s,
'array mapped ok');
like($_, qr{^<(\w+)\s*>(NEST\s*){3}\1>$}s, 'document OK');
# Check noattr doesn't screw up keyattr
$ref = { number => {
'twenty one' => { dec => 21, hex => '0x15' },
'thirty two' => { dec => 32, hex => '0x20' }
}
};
# Expect:
#
#
#
# 21
# twenty one
# 0x15
#
#
# 32
# thirty two
# 0x20
#
#
#
$_ = XMLout($ref, noattr => 1, keyattr => [ 'word' ]);
unlike($_, qr{=}s, 'same again but with unfolding too');
is_deeply(XMLin($_, keyattr => [ 'word' ]), $ref, 'parsed OK');
ok(s{\s*<(dec)>21\1>\s*}{21}s, 'scalar 1.1 mapped OK');
ok(s{\s*<(hex)>0x15\1>\s*}{21}s, 'scalar 1.2 mapped OK');
ok(s{\s*<(word)>twenty one\1>\s*}{21}s, 'scalar 1.3 mapped OK');
ok(s{\s*<(number)>212121\1>\s*}{NUM}s, 'element 1 OK');
ok(s{\s*<(dec)>32\1>\s*}{32}s, 'scalar 2.1 mapped OK');
ok(s{\s*<(hex)>0x20\1>\s*}{32}s, 'scalar 2.1 mapped OK');
ok(s{\s*<(word)>thirty two\1>\s*}{32}s, 'scalar 2.1 mapped OK');
ok(s{\s*<(number)>323232\1>\s*}{NUM}s, 'element 2 OK');
like($_, qr{^<(\w+)\s*>NUMNUM\1>$}, 'document OK');
# Check grouped tags get ungrouped correctly
$ref = {
prefix => 'before',
dirs => [ '/usr/bin', '/usr/local/bin' ],
suffix => 'after',
};
# Expect:
#
#
# before
#
# /usr/bin
# /usr/local/bin
#
# after
#
#
$@ = '';
$_ = eval { XMLout($ref, grouptags => {dirs => 'dirs'}, noattr => 1); };
ok($@, 'bad GroupTags value was caught');
like("$@", qr{Bad value in GroupTags: 'dirs' => 'dirs'},
'error message looks good');
$@ = '';
$_ = eval { XMLout($ref, grouptags => {dirs => 'dir'}, noattr => 1); };
ok(!$@, 'good GroupTags value caused no error');
ok(s{\s*<(prefix)>before\1>\s*}{ELEM}s, 'prefix OK');
ok(s{\s*<(suffix)>after\1>\s*}{ELEM}s, 'suffix OK');
ok(s{\s*/usr/bin\s*/usr/local/bin\s*}{LIST}s, 'list OK');
ok(s{\s*LIST\s*}{ELEM}s, 'group OK');
like($_, qr{^<(\w+)\s*>ELEMELEMELEM\1>$}, 'document OK');
is_deeply($ref, {
prefix => 'before',
dirs => [ '/usr/bin', '/usr/local/bin' ],
suffix => 'after',
}, 'original ref is not messed with');
# Try again with multiple groupings
$ref = {
dirs => [ '/usr/bin', '/usr/local/bin' ],
terms => [ 'vt100', 'xterm' ],
};
# Expect:
#
#
#
# /usr/bin
# /usr/local/bin
#
#
# vt100
# xterm
#
#
#
$_ = XMLout($ref, grouptags => {dirs => 'dir', terms => 'term'}, noattr => 1);
ok(s{\s*/usr/bin\s*/usr/local/bin\s*}{LIST}s, 'list 1 OK');
ok(s{\s*LIST\s*}{ELEM}s, 'group 1 OK');
ok(s{\s*vt100\s*xterm\s*}{LIST}s, 'list 2 OK');
ok(s{\s*LIST\s*}{ELEM}s, 'group 2 OK');
like($_, qr{^<(\w+)\s*>ELEMELEM\1>$}, 'document OK');
# Confirm unfolding and grouping work together
$ref = {
dirs => {
first => { content => '/usr/bin' },
second => { content => '/usr/local/bin' },
},
};
# Expect:
#
#
#
# /usr/bin
# /usr/local/bin
#
#
#
$_ = XMLout($ref,
grouptags => {dirs => 'dir'}, keyattr => {dir => 'name'},
);
ok(s{\s*/usr/bin\s*}{ITEM}s, 'item 1 OK');
ok(s{\s*/usr/local/bin\s*}{ITEM}s, 'item 2 OK');
ok(s{\s*ITEMITEM\s*}{GROUP}s, 'group OK');
like($_, qr{^<(\w+)\s*>GROUP\1>$}, 'document OK');
# Combine unfolding, grouping and stripped content - watch it fail :-(
$ref = {
dirs => {
first => '/usr/bin',
second => '/usr/local/bin'
},
};
# Expect:
#
#
#
#
#
$_ = XMLout($ref,
grouptags => {dirs => 'dir'}, keyattr => {dir => 'name'},
contentkey => '-content'
);
like($_, qr{
^<(\w+)>\s*
\s*
\s*
\s*
\1>$
}x, 'Failed to unwrap/group stripped content - as expected');
# Check 'NoIndent' option
$ref = {
nest => [ qw(one two three) ]
};
# Expect:
#
# onetwothree
#
$_ = XMLout($ref, NoIndent => 1);
is_deeply(XMLin($_), $ref, 'parses ok');
is($_, 'onetwothree',
'NoIndent worked ok');
# Check 'NoIndent' works with KeyAttr
$ref = {
person => {
bob => { age => 25 },
kate => { age => 22 },
},
};
# Expect:
#
#
#
$_ = XMLout($ref, NoIndent => 1, KeyAttr => {person => 'name'});
is_deeply(XMLin($_), $ref, 'parses ok');
like($_, qr{
(
|
){2}
}sx,
'NoIndent worked ok with KeyAttr');
# Try the 'AttrIndent' option (assume NoSort defaults to off)
$ref = {
beta => '2',
gamma => '3',
alpha => '1',
colours => {
red => '#ff0000',
green => '#00ff00',
}
};
$_ = XMLout($ref, AttrIndent => 1, RootName => 'opt');
is($_, '
', 'AttrIndent seems to work');
# Test the attribute/element sorting algorithm
$xml = q{
};
$ref = XMLin($xml);
$_ = XMLout($ref, RootName => 'opt');
is($_, qq(\n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq(\n),
'sorting by default key attribute works');
# Try again but with specific key fields:
$ref = XMLin($xml, KeyAttr => {test => 'vegetable', box => 'size'});
$_ = XMLout($ref,
RootName => 'opt',
KeyAttr => {test => 'vegetable', box => 'size'}
);
is($_, qq(\n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq( \n) .
qq(\n),
'sorting by specified key attributes works');
# Try again but with no key fields:
$ref = XMLin($xml, KeyAttr => {});
$_ = XMLout($ref, RootName => 'opt', KeyAttr => {});
like($_, qr{^\s*
(
(
\s*
\s*
\s*
)
|(
\s*
\s*
)
){2}
\s*
$}sx, 'sorting with no key attribute works');
# Check that sorting can be disabled
$@ = '';
SKIP: {
eval { require Tie::IxHash };
skip "Tie::IxHash not installed", 1 if $@;
my(%hash1, %hash2);
tie %hash1, 'Tie::IxHash', Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5;
tie %hash2, 'Tie::IxHash', X => { b => 2 }, A => { c => 3 }, Z => { a => 1 },
M => { f => 6 }, K => { e => 4 }, O => { d => 5 };
$hash1{func} = \%hash2;
$_ = XMLout(\%hash1, NoSort => 1, KeyAttr => {func => 'name'});
like($_, qr{
^\s+
\s+
\s+
\s+
\s+
\s+
\s+
\s*$
}sx, 'Suppressing sort worked');
}
# Check ValueAttr => {} can expand the relevant records
$ref = { one => 1, two => 2, six => 6 };
$xml = XMLout($ref, ValueAttr => { one => 'value', six => 'num' });
like($xml, qr{
^
(
\s*
| \s*
){2}
\s*$
}sx, 'Correct attributes inserted when ValueAttr specified'
);
# Try out the NumericEscape option
SKIP: {
skip "Perl 5.6 or better required", 4 unless($] >= 5.006);
$ref = { euro => "\x{20AC}", nbsp => "\x{A0}" };
$xml = XMLout($ref); # Default: no numeric escaping
my $ents = join ',', sort ($xml =~ m{(\d+);}g);
is($ents, '', "No numeric escaping by default");
$xml = XMLout($ref, NumericEscape => 0);
$ents = join ',', sort ($xml =~ m{(\d+);}g);
is($ents, '', "No numeric escaping: explicit");
$xml = XMLout($ref, NumericEscape => 2);
$ents = join ',', sort ($xml =~ m{(\d+);}g);
is($ents, '160,8364', "Level 2 numeric escaping looks good");
$xml = XMLout($ref, NumericEscape => 1);
$ents = join ',', sort ($xml =~ m{(\d+);}g);
is($ents, '8364', "Level 1 numeric escaping looks good");
}
# 'Stress test' with a data structure that maps to several thousand elements.
# Unfold elements with XMLout() and fold them up again with XMLin()
my $opt1 = {};
foreach my $i (0..40) {
foreach my $j (0..$i) {
$opt1->{TypeA}->{$i}->{Record}->{$j} = { Hex => sprintf("0x%04X", $j) };
$opt1->{TypeB}->{$i}->{Record}->{$j} = { Oct => sprintf("%04o", $j) };
$opt1->{List}->[$i]->[$j] = "$i:$j";
}
}
$xml = XMLout($opt1, keyattr => { TypeA => 'alpha', TypeB => 'beta', Record => 'id' });
my $opt2 = XMLin($xml, keyattr => { TypeA => 'alpha', TypeB => 'beta', Record => 'id' }, forcearray => 1);
is_deeply($opt1, $opt2, 'large datastructure mapped to XML and back OK');
exit(0);