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*\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*\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* }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*$}{}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} $ }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} $ }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*$}{}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*$}{}s, 'data OK too'); # same again but with a scalar $xml = XMLout("", noescape => 1); like($xml, qr{^<(\w+)>}, "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*\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*\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*$}{}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*\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*\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*\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*\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*\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* $ }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\s*}{NEST}s, 'scalar 1 mapped ok'); ok(s{\s*<(attr2)>value2\s*}{NEST}s, 'scalar 2 mapped ok'); ok(s{\s*<(nest)>one\s*<\1>two\s*<\1>three}{NEST}s, 'array mapped ok'); like($_, qr{^<(\w+)\s*>(NEST\s*){3}$}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\s*}{21}s, 'scalar 1.1 mapped OK'); ok(s{\s*<(hex)>0x15\s*}{21}s, 'scalar 1.2 mapped OK'); ok(s{\s*<(word)>twenty one\s*}{21}s, 'scalar 1.3 mapped OK'); ok(s{\s*<(number)>212121\s*}{NUM}s, 'element 1 OK'); ok(s{\s*<(dec)>32\s*}{32}s, 'scalar 2.1 mapped OK'); ok(s{\s*<(hex)>0x20\s*}{32}s, 'scalar 2.1 mapped OK'); ok(s{\s*<(word)>thirty two\s*}{32}s, 'scalar 2.1 mapped OK'); ok(s{\s*<(number)>323232\s*}{NUM}s, 'element 2 OK'); like($_, qr{^<(\w+)\s*>NUMNUM$}, '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\s*}{ELEM}s, 'prefix OK'); ok(s{\s*<(suffix)>after\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$}, '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$}, '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$}, '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* $ }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);