Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

XML::Simple isn't listening to SuppressEmpty

by fhew (Beadle)
on May 02, 2012 at 14:57 UTC ( [id://968461]=perlquestion: print w/replies, xml ) Need Help??

fhew has asked for the wisdom of the Perl Monks concerning the following question:

I've been using XML::Simple for some very simple XML and until yesterday it was working the way I expected... till I got bitten with the following example:

use XML::Simple; use Data::Dumper; my $x =<<EOF; <stuff name="me"> <obj class="myclass"> <set name="key1">a</set> <set name="key2"></set> </obj> <obj class="myclass"> <set name="key1">a</set> <set name="key2">b</set> </obj> </stuff> EOF $x = XMLin( $x, #SuppressEmpty => '', ForceArray => 1, ContentKey => '-content', ); print Dumper(\$x); $VAR1 = \{ 'obj' => [ { 'class' => 'myclass', 'set' => { 'key2' => {}, 'key1' => { 'content' => 'a' } } }, { 'class' => 'myclass', 'set' => { 'key2' => 'b', 'key1' => 'a' } } ], 'name' => 'me' };

Here I've provided two 'objects', one object has an element that has an empty or value is blank. As a result the hash that's returned is formatted differently than when _all_ attributes are provided. What I expected was the latter like:

'key2' => 'b', 'key1' => 'a'

but where there is an 'empty' attribute everything changes to a hash (and with the 'content' sub-element). That's what I thought the ForceArray and ContentKey was supposed to suppress; and the ContentKey does do it!... but only when all fields are populated.

I thought SuppressEmpty would do it, but it doesn't.
What am I missing?

TIA Fulko

Replies are listed 'Best First'.
Re: XML::Simple isn't listening to SuppressEmpty
by zwon (Abbot) on May 02, 2012 at 17:14 UTC
    SuppressEmpty is about empty elements -- no content, no attributes, like <set></set>. XML::Simple is good when you want quickly turn an XML file into a data structure and you are accept that it is up to XML::Simple what structure it will be, except maybe some minor tweaks. If you have some specific requirements about the data structure, XML::Simple probably not what you want. Have a look on Stepping up from XML::Simple to XML::LibXML.
Re: XML::Simple isn't listening to SuppressEmpty
by Khen1950fx (Canon) on May 03, 2012 at 01:14 UTC

    FWIW, I've experimented with XML::Simple for a couple of hours now, and, as far as I can see, SuppressEmpty doesn't work in this situation. I get the same results with or without it.

    I triple-checked using Data::Serializer::Raw, Data::Dumper::Concise, and XML::Dumper. Also, I used use XML::Simple qw(:strict), and I used 'content' instead of '-content'. Here's what I have so far:
    #!/usr/bin/perl -l use strict; use warnings; use Data::Dumper::Concise; use Data::Serializer::Raw; use XML::Simple qw(:strict); my $file = '/root/Desktop/xml2.xml'; open my $xml, '<', $file or die $!; binmode $xml, ":encoding(UTF-8)"; my $ref = XMLin( $xml, SuppressEmpty => undef, KeyAttr => { set => 'name' }, ForceArray => [ 'set' ], ContentKey => 'content', ); my $xml_raw_serializer = Data::Serializer::Raw->new( serializer => 'XML::Dumper' ); my $xml_data = $xml_raw_serializer->serialize($ref); print Dumper($xml_raw_serializer->deserialize($xml_data)); close $xml;
Re: XML::Simple isn't listening to SuppressEmpty
by grantm (Parson) on May 03, 2012 at 21:12 UTC

    XML::Simple can't do what you want there. The SuppressEmpty option is almost what you want except it won't apply in your case because the element will contain name => 'set' and won't be considered empty. Later on, the array folding will cause that attribute to be deleted but by that stage it's too late for the SuppressEmpty check.

    The issue is that as soon as you start supporting multiple options, the order in which those options are applied begins to matter.

    I'd just use XML::LibXML.

Re: XML::Simple isn't listening to SuppressEmpty
by Anonymous Monk on May 03, 2012 at 03:38 UTC

    Is this what you want ?

    { 'class' => 'myclass', 'set' => { 'key2' => 'b', 'key1' => 'a' } }

    XML::Rules is simpler than XML::Simple, you start with xml2XMLRules, and you tweak it a little

    #!/usr/bin/perl -- #~ 2012-05-02-20:44:05 by Anonymous Monk #~ perltidy -csc -otr -opr -ce -nibc -i=4 use strict; use warnings; use XML::Rules; use Data::Dump qw/ dd /; Main( @ARGV ); exit( 0 ); sub Main { my $t = XML::Rules->new( qw/ stripspaces 8 /, rules => [ 'obj' => 'as array no content', 'stuff' => 'no content', #~ 'set' => 'as array', # original from xml2XMLRules 'set' => sub { #~ $rule->( $tag_name, \%attrs, \@context, \@parent_data, $parser) #~ my ($tagname, $attrHash, $contexArray, $parentDataArray, $parser) = + @_; '%set', { $_[1]->{name} => $_[1]->{_content} }; }, ], ); my $res = $t->parse( MyXml() ); dd $res; } ## end sub Main sub MyXml { q{<stuff name="me"> <obj class="myclass"> <set name="key1">a</set> <set name="key2"></set> </obj> <obj class="myclass"> <set name="key1">a</set> <set name="key2">b</set> </obj> </stuff> }; } ## end sub MyXml __END__ { stuff => { name => "me", obj => [ { class => "myclass", set => { key1 => "a", key2 => unde +f } }, { class => "myclass", set => { key1 => "a", key2 => "b" +} }, ], }, }
Re: XML::Simple isn't listening to SuppressEmpty
by fhew (Beadle) on May 08, 2012 at 15:38 UTC

    All of the comments have either been: "that option doesn't do it", or "use a different module". Nobody managed to suggest either a fix/change for XML::Simple, or a set of options I can use that causes XML::Simple to become 'self-consistent' when dealing with partial content.

    So in the end, I wrote a small post-processing routine to normalize the output from XMLin(). I.e recurse through the structure and a) promote leafs that are empty hashes to empty values.

     { } to  ''

    and b) hashes that contain only the single 'content' entry into simply the value:

     { content => 'xxx' } to  'xxx'

    Now I consistently get what I expected regardless of data content.

    $VAR1 = \{ 'obj' => [ { 'class' => 'myclass', 'set' => { 'key2' => '', 'key1' => 'a' } }, { 'class' => 'myclass', 'set' => { 'key2' => 'b', 'key1' => 'a' } } ], 'name' => 'me' };
    Using the following:
    sub reduce { my ($ref) = @_; if (ref($ref) eq 'REF') { # recurse into references... $$ref = reduce($$ref); } elsif (UNIVERSAL::isa($ref, 'HASH')) { my @keys = keys %$ref; if (scalar (@keys) == 0) { # empty hash becomes an empty string return ''; } elsif (scalar (@keys) == 1 && $keys[0] eq 'content') { $ref = $ref->{$keys[0]}; # 'content'-only becomes the value } else { foreach (keys %$ref) { # recurse into multi-element hashes $ref->{$_} = reduce($ref->{$_}); } } } elsif (UNIVERSAL::isa($ref, 'ARRAY')) { foreach (@$ref) { # recurse into arrays reduce($_); } } return $ref; }

      So in the end, I wrote a small post-processing routine to normalize the output from XMLin().

      Eeeew :) The worst of all options, why so anchored to XML::Simple? Don't you like steriods? XML::Rules is XML::Simple on steriods :)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://968461]
Approved by Corion
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (4)
As of 2024-04-19 04:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found