http://www.perlmonks.org?node_id=253934

What follows began life as a response to an email enquiry perhaps others might find it useful ...

Hi Jack*

Thanks for your email - I hope I can help you solve your problem while you still have some hair left.

I'd like to start with two observations:

  1. Your confusion is really nothing to do with XML::Simple. You simply aren't completely comfortable with Perl's references yet. Once you've got anonymous data structures and nested hashes "off pat" you'll find that the output of Data::Dumper starts to make sense and you'll find applications in all your Perl code, not just when you're working with XML.

    Mark Jason Dominus has written a quick tutorial on Perl references. It's already installed on your hard disk but you can read it online here. (Also, busunsl has written a tutorial here).

  2. You asked for help transforming this XML:

    <opt> <trans> <idnumber1 status="not contacted" assignee="jack" /> </trans> </opt>

    To this:

    <opt> <trans> <idnumber1 status="not contacted" assignee="jack" /> <idnumber2 status="contacted" assignee="jill" /> </trans> </opt>

    I'm not going to help you do that because (and I'm going to try to break this to you gently ... ) that would be just plain dumb!

In XML, 'things' that are of the same type should be represented using the same tags. Using <idnumber1> for the first thing and <idnumber2> for the seconds is, well, dumb. Instead, since I'm not sure what these 'things' are, I'm going to use <item> - I'd encourage you to use a tag which more accurately describes what they are.

So what I am going to do is show you how to transform this:

<opt> <trans> <item id="1" status="not contacted" assignee="jack" /> </trans> </opt>

To this:

<opt> <trans> <item id="1" status="not contacted" assignee="jack" /> <item id="2" status="contacted" assignee="jill" /> </trans> </opt>

Please bear in mind that XML::Simple does not care about the order of attributes - and you shouldn't either.

As a special bonus (since I have not been very kind about your stated goals and since you asked so nicely) I'm going to show you two ways to do it.

The first sample script reads the XML into $opt:

my $xs = XML::Simple->new( forcearray => [ 'item' ], keyattr => { item => 'id' }, rootname => 'opt', ); my $opt = $xs->XMLin(\*DATA);

(I use the convention of re-using the outermost tag name as the variable name but it is just a convention). I have explicitly enabled array folding, so you can refer to the first (and in fact only) 'item' as:

$opt->{trans}->{item}->{1}

The {item}->{1} syntax can be read as "the <item> element which has an 'id' attribute with a value of '1'". I'm assuming you want to add another element with an id of '2' and that you somehow know that id number 2 has not already been used. So to add an element, we simply create a hashref with the relevant attributes (key => value pairs) and store it in the <item> with id '2':

$opt->{trans}->{item}->{2} = { status => 'contacted', assignee => 'j +ill' };

This is pretty simple (once you know how) but it has a couple of drawbacks. First, as I mentioned, you need to know which id's are free. Second, since array folding is enabled, the <item>'s are stored in a hash, so the order of the elements from XMLout is not guaranteed to match the original order. Anyway, here's a complete script:

#!/usr/bin/perl -w use strict; use XML::Simple; my $xs = XML::Simple->new( forcearray => [ 'item' ], keyattr => { item => 'id' }, rootname => 'opt', ); my $opt = $xs->XMLin(\*DATA); $opt->{trans}->{item}->{2} = { status => 'contacted', assignee => 'jil +l' }; print $xs->XMLout($opt); __DATA__ <opt> <trans> <item id="1" status="not contacted" assignee="jack" /> </trans> </opt>

The second script does not suffer from either of those drawbacks. It disables array folding by setting keyattr => {}. This means the <item>'s are stored in an array rather than a hash (so order is preserved) and the first item is referred to as:

$opt->{trans}->{item}->[0]

(Note the square brackets meaning an array and the first element in an array is at position 0). The whole list of items is:

@{ $opt->{trans}->{item} }

So we can work out a unique ID based on how many items there are (not foolproof but useful for an example) and 'push' another one onto the end of the list like this:

my $id = @{ $opt->{trans}->{item} } + 1; push @{ $opt->{trans}->{item} }, { id => $id, status => 'contacted', + assignee => 'jill' };

Here's the complete script:

#!/usr/bin/perl -w use strict; use XML::Simple; my $xs = XML::Simple->new( forcearray => [ 'item' ], keyattr => { }, rootname => 'opt', ); my $opt = $xs->XMLin(\*DATA); my $id = @{ $opt->{trans}->{item} } + 1; push @{ $opt->{trans}->{item} }, { id => $id, status => 'contacted', a +ssignee => 'jill' }; print $xs->XMLout($opt); __DATA__ <opt> <trans> <item id="1" status="not contacted" assignee="jack" /> </trans> </opt>

For more info on array folding and the keyattr and forcearray options, you might like to look at this article.

Good luck.

Grant

* names changed to protect the innocent