perltutorial
grantm
<p>What follows began life as a response to an email enquiry perhaps others might find it useful ...</p>
<p>Hi Jack*</p>
<p>Thanks for your email - I hope I can help you solve your problem while you still have some hair left.</p>
<p>I'd like to start with two observations:</p>
<ol>
<li>
<p>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 [cpan://Data::Dumper] starts to make sense and you'll find applications in all your Perl code, not just when you're working with XML.</p>
<p>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 [http://perldoc.perl.org/perlreftut.html|here]. (Also, [busunsl] has written a tutorial [id://137108|here]).</p>
</li>
<li>
<p>You asked for help transforming this XML:</p>
<code>
<opt>
<trans>
<idnumber1 status="not contacted" assignee="jack" />
</trans>
</opt>
</code>
<p>To this:</p>
<code>
<opt>
<trans>
<idnumber1 status="not contacted" assignee="jack" />
<idnumber2 status="contacted" assignee="jill" />
</trans>
</opt>
</code>
<p>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!</p>
</li>
</ol>
<p>In XML, 'things' that are of the same type should be represented using the same tags. Using &lt;idnumber1&gt; for the first thing and &lt;idnumber2&gt; for the seconds is, well, dumb. Instead, since I'm not sure what these 'things' are, I'm going to use &lt;item&gt; - I'd encourage you to use a tag which more accurately describes what they are.</p>
<p>So what I <i>am</i> going to do is show you how to transform this:</p>
<code>
<opt>
<trans>
<item id="1" status="not contacted" assignee="jack" />
</trans>
</opt>
</code>
<p>To this:</p>
<code>
<opt>
<trans>
<item id="1" status="not contacted" assignee="jack" />
<item id="2" status="contacted" assignee="jill" />
</trans>
</opt>
</code>
<readmore>
<p>Please bear in mind that XML::Simple does not care about the order of attributes - and you shouldn't either.</p>
<p>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 <i>two</i> ways to do it.</p>
<p>The first sample script reads the XML into $opt:</p>
<code>
my $xs = XML::Simple->new(
forcearray => [ 'item' ],
keyattr => { item => 'id' },
rootname => 'opt',
);
my $opt = $xs->XMLin(\*DATA);
</code>
<p>(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:</p>
<code>
$opt->{trans}->{item}->{1}
</code>
<p>The <code>{item}->{1}</code> syntax can be read as "the &lt;item&gt; 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 &lt;item&gt; with id '2':</p>
<code>
$opt->{trans}->{item}->{2} = { status => 'contacted', assignee => 'jill' };
</code>
<p>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 &lt;item&gt;'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:</p>
<code>
#!/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 => 'jill' };
print $xs->XMLout($opt);
__DATA__
<opt>
<trans>
<item id="1" status="not contacted" assignee="jack" />
</trans>
</opt>
</code>
<p>The second script does not suffer from either of those drawbacks. It disables array folding by setting <code>keyattr => {}</code>. This means the &lt;item&gt;'s are stored in an array rather than a hash (so order is preserved) and the first item is referred to as:</p>
<code>
$opt->{trans}->{item}->[0]
</code>
<p>(Note the square brackets meaning an array and the first element in an array is at position 0). The whole list of items is:</p>
<code>
@{ $opt->{trans}->{item} }
</code>
<p>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:</p>
<code>
my $id = @{ $opt->{trans}->{item} } + 1;
push @{ $opt->{trans}->{item} }, { id => $id, status => 'contacted', assignee => 'jill' };
</code>
<p>Here's the complete script:</p>
<code>
#!/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', assignee => 'jill' };
print $xs->XMLout($opt);
__DATA__
<opt>
<trans>
<item id="1" status="not contacted" assignee="jack" />
</trans>
</opt>
</code>
</readmore>
<p>For more info on array folding and the keyattr and forcearray options, you might like to look at
[id://218480|this article].</p>
<p>Good luck.</p>
<p>Grant</p>
<p>* names changed to protect the innocent</p>
<!-- jeffa - added readmore tag -->