use strict; use IO::File; BEGIN { print "1..189\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(!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); } ############################################################################## # 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; # Try encoding a scalar value my $xml = XMLout("scalar"); ok(1, 1); # XMLout did not crash ok(2, defined($xml)); # and it returned an XML string ok(3, 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); # Encode to $_ for convenience # Confirm it parses back OK ok(4, DataCompare($hashref1, XMLin($_))); ok(5, s/one="1"//); # first key encoded OK ok(6, s/two="II"//); # second key encoded OK ok(7, s/three="..."//); # third key encoded OK ok(8, /^<\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); # Encode to $_ for convenience ok(9, DataCompare($ref, XMLin($_))); ok(10, s{one\s* two\s* three}{}sx); # array elements encoded in correct order ok(11, /^<(\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); ok(12, DataCompare($ref, XMLin($_))); # Parses back OK ok(13, s{\s*}{}s); ok(14, s{\s*}{}s); ok(15, m{^<(\w+)\s+value="555 1234"\s*>\s*\s*$}s); # Now try encoding an anonymous array $ref = [ qw(1 two III) ]; # Expect: # # 1 # two # III # $_ = XMLout($ref); ok(16, DataCompare($ref, XMLin($_))); # Parses back OK ok(17, s{1\s*}{}s); ok(18, s{two\s*}{}s); ok(19, s{III\s*}{}s); ok(20, m{^<(\w+)\s*>\s*\s*$}s); # 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); ok(21, DataCompare($ref, XMLin($_))); # Parses back OK ok(22, s{1\.1\s*}{row}s); ok(23, s{1\.2\s*}{ one}s); ok(24, s{2\.1\s*}{row}s); ok(25, s{2\.2\s*}{ two}s); ok(26, s{\s*row one\s*\s*}{}s); ok(27, s{\s*row two\s*\s*}{}s); ok(28, m{^<(\w+)\s*>\s*\s*$}s); # 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 => []); ok(29, DataCompare($ref, XMLin($_))); # Parses back OK ok(30, s{\s*}{}s); ok(31, s{\s*}{}s); ok(32, s{\s*}{}s); ok(33, s{\s*}{}s); ok(34, s{^<(\w+)\s*>\s*$}{}s); # Try encoding same again with key folding set to non-standard value # Expect: # # # # # $_ = XMLout($ref, keyattr => ['fullname']); $xml = $_; ok(35, DataCompare($ref, XMLin($_, keyattr => ['fullname']))); # Parses back OK ok(36, s{\s*fullname="England"}{uk}s); ok(37, s{\s*capital="London"}{uk}s); ok(38, s{\s*fullname="France"}{fr}s); ok(39, s{\s*capital="Paris"}{fr}s); ok(40, s{\s*fullname="Turkey"}{tk}s); ok(41, s{\s*capital="Istanbul"}{tk}s); ok(42, s{\s*}{}s); ok(43, s{\s*}{}s); ok(44, s{\s*}{}s); ok(45, s{^<(\w+)\s*>\s*$}{}s); # Same again but specify name as scalar rather than array $_ = XMLout($ref, keyattr => 'fullname'); ok(46, $_ eq $xml); # Same result as last time # Same again but specify keyattr as hash rather than array $_ = XMLout($ref, keyattr => { country => 'fullname' }); ok(47, $_ eq $xml); # Same result as last time # Same again but add leading '+' $_ = XMLout($ref, keyattr => { country => '+fullname' }); ok(48, $_ eq $xml); # Same result as last time # and leading '-' $_ = XMLout($ref, keyattr => { country => '-fullname' }); ok(49, $_ eq $xml); # Same result as last time # One more time but with default key folding values # Expect: # # # # # $_ = XMLout($ref); ok(50, DataCompare($ref, XMLin($_))); # Parses back OK ok(51, s{\s*name="England"}{uk}s); ok(52, s{\s*capital="London"}{uk}s); ok(53, s{\s*name="France"}{fr}s); ok(54, s{\s*capital="Paris"}{fr}s); ok(55, s{\s*name="Turkey"}{tk}s); ok(56, s{\s*capital="Istanbul"}{tk}s); ok(57, s{\s*}{}s); ok(58, s{\s*}{}s); ok(59, s{\s*}{}s); ok(60, s{^<(\w+)\s*>\s*$}{}s); # Finally, confirm folding still works with only one nested hash # Expect: # # # $ref = { country => { England => { capital => 'London' } } }; $_ = XMLout($ref); ok(61, DataCompare($ref, XMLin($_, forcearray => 1))); # Parses back OK ok(62, s{\s*name="England"}{uk}s); ok(63, s{\s*capital="London"}{uk}s); ok(64, s{\s*}{}s); #print STDERR "\n$_\n"; ok(65, s{^<(\w+)\s*>\s*$}{}s); # Check that default XML declaration works # # Expect: # # $ref = { one => 1 }; $_ = XMLout($ref, xmldecl => 1); ok(66, DataCompare($ref, XMLin($_))); # Parses back OK ok(67, s{^\Q\E}{}s); ok(68, s{}{}s); ok(69, m{^\s*$}s); # Check that custom XML declaration works # # Expect: # # $_ = XMLout($ref, xmldecl => ""); ok(70, DataCompare($ref, XMLin($_))); # Parses back OK ok(71, s{^\Q\E}{}s); ok(72, s{}{}s); ok(73, m{^\s*$}s); # Check that special characters do get escaped $ref = { a => '', b => '"B"', c => '&C&' }; $_ = XMLout($ref); ok(74, DataCompare($ref, XMLin($_))); # Parses back OK ok(75, s{a="<A>"}{}s); ok(76, s{b=""B""}{}s); ok(77, s{c="&C&"}{}s); ok(78, s{^<(\w+)\s*/>$}{}s); # unless we turn escaping off $_ = XMLout($ref, noescape => 1); ok(79, s{a=""}{}s); ok(80, s{b=""B""}{}s); ok(81, s{c="&C&"}{}s); ok(82, s{^<(\w+)\s*/>$}{}s); # Try encoding a recursive data structure and confirm that it fails $_ = eval { my $ref = { a => '1' }; $ref->{b} = $ref; XMLout($ref); }; ok(83, !defined($_)); ok(84, $@ =~ /recursive data structures not supported/); # Try encoding a blessed reference and confirm that it fails $_ = eval { my $ref = new IO::File; XMLout($ref) }; ok(85, !defined($_)); ok(86, $@ =~ /Can't encode a value of type: /); # Repeat some of the above tests with named root element # Try encoding a scalar value $xml = XMLout("scalar", rootname => 'TOM'); ok(87, defined($xml)); # and it returned an XML string ok(88, XMLin($xml), 'scalar'); # which parses back OK # and contains the expected data ok(89, $xml =~ /^\s*scalar<\/TOM>\s*$/si); # Next try encoding a hash # Expect: # $_ = XMLout($hashref1, rootname => 'DICK'); # Confirm it parses back OK ok(90, DataCompare($hashref1, XMLin($_))); ok(91, s/one="1"//); # first key encoded OK ok(92, s/two="II"//); # second key encoded OK ok(93, s/three="..."//); # third key encoded OK ok(94, /^/); # only expected root element left # Now try encoding a hash with a nested array $ref = {array => [qw(one two three)]}; # Expect: # # one # two # three # $_ = XMLout($ref, rootname => 'LARRY'); # Encode to $_ for convenience ok(95, DataCompare($ref, XMLin($_))); ok(96, s{one\s* two\s* three}{}sx); # array encoded in correct order ok(97, /^<(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'); ok(98, DataCompare($ref, XMLin($_))); # Parses back OK ok(99, s{\s*}{}s); ok(100, s{\s*}{}s); ok(101, m{^<(CURLY)\s+value="555 1234"\s*>\s*\s*$}s); # Now try encoding an anonymous array $ref = [ qw(1 two III) ]; # Expect: # # 1 # two # III # $_ = XMLout($ref, rootname => 'MOE'); ok(102, DataCompare($ref, XMLin($_))); # Parses back OK ok(103, s{1\s*}{}s); ok(104, s{two\s*}{}s); ok(105, s{III\s*}{}s); ok(106, m{^<(MOE)\s*>\s*\s*$}s); # Test again, this time with no root element # Try encoding a scalar value ok(107, XMLout("scalar", rootname => '') =~ /scalar\s+/s); ok(108, XMLout("scalar", rootname => undef) =~ /scalar\s+/s); # Next try encoding a hash # Expect: # 1 # II # ... $_ = XMLout($hashref1, rootname => ''); # Confirm it parses back OK ok(109, DataCompare($hashref1, XMLin("$_"))); ok(110, s/1<\/one>//); # first key encoded OK ok(111, s/II<\/two>//); # second key encoded OK ok(112, s/...<\/three>//); # third key encoded OK ok(113, /^\s*$/); # nothing else left # Now try encoding a nested hash $ref = { value => '555 1234', hash1 => { one => 1 }, hash2 => { two => 2 } }; # Expect: # 555 1234 # # $_ = XMLout($ref, rootname => ''); ok(114, DataCompare($ref, XMLin("$_"))); # Parses back OK ok(115, s{555 1234<\/value>\s*}{}s); ok(116, s{\s*}{}s); ok(117, s{\s*}{}s); ok(118, m{^\s*$}s); # Now try encoding an anonymous array $ref = [ qw(1 two III) ]; # Expect: # 1 # two # III $_ = XMLout($ref, rootname => ''); ok(119, DataCompare($ref, XMLin("$_"))); # Parses back OK ok(120, s{1\s*}{}s); ok(121, s{two\s*}{}s); ok(122, s{III\s*}{}s); ok(123, m{^\s*$}s); # Test option error handling $_ = eval { XMLout($hashref1, searchpath => []) }; # only valid for XMLin() ok(124, !defined($_)); ok(125, $@ =~ /Unrecognised option:/); $_ = eval { XMLout($hashref1, 'bogus') }; ok(126, !defined($_)); ok(127, $@ =~ /Options must be name=>value pairs .odd number supplied./); # Test output to file my $TestFile = 'testoutput.xml'; unlink($TestFile); ok(128, !-e $TestFile); $xml = XMLout($hashref1); XMLout($hashref1, outputfile => $TestFile); ok(129, -e $TestFile); ok(130, ReadFile($TestFile) eq $xml); unlink($TestFile); # Test output to an IO handle ok(131, !-e $TestFile); my $fh = new IO::File; $fh->open(">$TestFile") || die "$!"; XMLout($hashref1, outputfile => $TestFile); $fh->close(); ok(132, -e $TestFile); ok(133, ReadFile($TestFile) eq $xml); unlink($TestFile); # After all that, confirm that the original hashref we supplied has not # been corrupted. ok(134, DataCompare($hashref1, $hashref2)); # Confirm that hash keys with leading '-' are skipped $ref = { 'a' => 'one', '-b' => 'two', '-c' => { 'one' => 1, 'two' => 2 } }; $_ = XMLout($ref, rootname => 'opt'); ok(135, m{^\s*\s*$}s); # 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' }); ok(136, DataCompare($ref, # Parses back OK XMLin($_, forcearray => 1, keyattr => { 'car' => 'license', 'option' => 'pn' }))); ok(137, s{\s*make="GM"}{gm}s); ok(138, s{\s*id="2"}{gm}s); ok(139, s{\s*license="LW1804"}{gm}s); ok(140, s{\s*desc="Steering Wheel"}{opt}s); ok(141, s{\s*pn="9926543-1167"}{opt}s); ok(142, s{\s*key="1"}{opt}s); ok(143, s{\s*\s*\s*}{CAR}s); ok(144, s{\s*make="Ford"}{ford}s); ok(145, s{\s*id="1"}{ford}s); ok(146, s{\s*license="SH6673"}{ford}s); ok(147, s{\s*desc="Electric Windows"}{1}s); ok(148, s{\s*pn="6389733317-12"}{1}s); ok(149, s{\s*key="2"}{1}s); ok(150, s{\s*\s*(\s*){3}}{CAR}s); ok(160, s{^<(\w+)\s*>\s*CAR\s*CAR\s*$}{}s); # Check that empty hashes translate to empty tags $ref = { 'one' => { 'attr1' => 'avalue1', 'nest1' => [ 'nvalue1' ], 'nest2' => {} }, two => {} }; $_ = XMLout($ref); ok(161, s{\s*}{}); ok(162, s{nvalue1\s*}{}); ok(163, s{\s*}{}); ok(164, s{\s*\s*\s*}{}); ok(165, s{\s*}{}); ok(166, m{^\s*<(\w+)\s*>\s*\s*\s*\s*$}); # Check undefined values generate warnings { my $warn = ''; local $SIG{__WARN__} = sub { $warn = $_[0] }; $_ = eval { $ref = { 'tag' => undef }; XMLout($ref); }; ok(167, $warn =~ /Use of uninitialized value/); } # Unless undef is mapped to empty tags $ref = { 'tag' => undef }; $_ = XMLout($ref, suppressempty => undef); ok(168, m{^\s*<(\w*)\s*>\s*\s*\s*$}s); # 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); ok(169, DataCompare($xml1, $xml2)); # Test that items with text content are output correctly # Expect: text $ref = { 'one' => 1, 'content' => 'text' }; $_ = XMLout($ref); ok(170, m{^\s*text\s*$}s); # Even if we change the default value for the 'contentkey' option $ref = { 'one' => 1, 'text_content' => 'text' }; $_ = XMLout($ref, contentkey => 'text_content'); ok(171, m{^\s*text\s*$}s); # Check 'noattr' option $ref = { attr1 => 'value1', attr2 => 'value2', nest => [ qw(one two three) ] }; # Expect: # # # value1 # value2 # one # two # three # # $_ = XMLout($ref, noattr => 1); ok(172, !m{=}s); # No '=' signs anywhere ok(173, DataCompare($ref, XMLin($_))); # Parses back ok ok(174, s{\s*<(attr1)>value1\s*}{NEST}s); # Output meets expectations ok(175, s{\s*<(attr2)>value2\s*}{NEST}s); ok(176, s{\s*<(nest)>one\s*<\1>two\s*<\1>three}{NEST}s); ok(177, s{^<(\w+)\s*>(NEST\s*){3}$}{}s); # 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' ]); ok(178, !m{=}s); # No '=' signs anywhere # Parses back ok ok(179, DataCompare($ref, XMLin($_, keyattr => [ 'word' ]))); ok(180, s{\s*<(dec)>21\s*}{21}s); ok(181, s{\s*<(hex)>0x15\s*}{21}s); ok(182, s{\s*<(word)>twenty one\s*}{21}s); ok(183, s{\s*<(number)>212121\s*}{NUM}s); ok(184, s{\s*<(dec)>32\s*}{32}s); ok(185, s{\s*<(hex)>0x20\s*}{32}s); ok(186, s{\s*<(word)>thirty two\s*}{32}s); ok(187, s{\s*<(number)>323232\s*}{NUM}s); ok(188, s{^<(\w+)\s*>NUMNUM$}{}s); # '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 (1..40) { foreach my $j (1..$i) { $opt1->{TypeA}->{$i}->{Record}->{$j} = { Hex => sprintf("0x%04X", $j) }; $opt1->{TypeB}->{$i}->{Record}->{$j} = { Oct => sprintf("%04o", $j) }; } } $xml = XMLout($opt1, keyattr => { TypeA => 'alpha', TypeB => 'beta', Record => 'id' }); my $opt2 = XMLin($xml, keyattr => { TypeA => 'alpha', TypeB => 'beta', Record => 'id' }, forcearray => 1); ok(189, DataCompare($opt1, $opt2)); exit(0);