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

This is probably dumb, but I'm using XML::Simple to parse a relatively small straightforward document, something like this:
<data> <fields> <field name="foo"> <number>2</number> <required>Y</required> </field> <field name="bar"> <number>1</number> <required>N</required> </field> </fields> </data>
Say I have that file, and I just want to open it up, add ten to each number, then save it again with the new numbers, which module should I use and how?

I'm finding it impossible to do with XML::Simple using what I thought would be the correct method, XMLout() and printing to a file, because it rearranges the data, no matter what fiddling I do with forcearray and keyattr options, for instance it will output as:

<opt> <fields name="field"> <foo required="Y" number="12" /> <bar required="N" number="11" /> </fields> </opt>

Can anyone give me a quick recipe for this? It's late in the day and I'm not thinking straight any more.
--

“Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
M-J D

Replies are listed 'Best First'.
Re: Edit values in XML and Save?
by grantm (Parson) on Mar 05, 2003 at 07:14 UTC

    Well you could try this:

    use strict; use XML::Simple; my $xs = XML::Simple->new( forcearray => 1, keyattr => {}, rootname => 'data' ); my $data = $xs->XMLin('./data.xml'); foreach my $field (@{$data->{fields}->[0]->{field}}) { $field->{number}->[0] += 10; } print $xs->XMLout($data);

    Or this:

    use strict; use XML::Simple; my $data = XMLin( './data.xml', keyattr => {field => 'name'}, forcearray => [ qw(field + number required) ] ); $data->{fields}->{field}->{foo}->{number}->[0] += 10; $data->{fields}->{field}->{bar}->{number}->[0] += 10; print XMLout($data, keyattr => {field => 'name'}, rootname => 'data');

    Update: plus the obligatory link to Does your XML::Simple code pass the strict test?

      grantm, This worked...thx for the help... alincoln
Re: Edit values in XML and Save?
by mirod (Canon) on Mar 05, 2003 at 08:49 UTC

    And of course here is the OXTV (Obligatory XML::Twig Version), using no variable except $_:

    Update: jeffa noticed that this does not work with the CPAN version of XML::Twig, you will need the development version (3.10) to be able to chain the calls to set_content and to print (I changed set_content in this version to return the element). So with older versions you will need to do this:

    $_->set_content( $_->text + 10); $_->print;
    instead of being able to ncely chain method calls. How lame! ;--(

    #!/usr/bin/perl -w use strict; use XML::Twig; XML::Twig->new( twig_roots => { number => sub { $_->set_content( $_->t +ext + 10) ->print; } }, twig_print_outside_roots => 1, ) ->parse( \*DATA); __DATA__ <data> <fields> <field name="foo"> <number>2</number> <required>Y</required> </field> <field name="bar"> <number>1</number> <required>N</required> </field> </fields> </data>
Re: Edit values in XML and Save?
by robartes (Priest) on Mar 05, 2003 at 07:40 UTC
    Try setting the forcearray option - otherwise nested elements get transformed into attributes, as is happening in your example. Here's some code:
    #!/usr/local/bin/perl -w use strict; use XML::Simple; local $/=undef; my $input=<DATA>; my $xml=XMLin($input, 'forcearray' => 1); $xml->{'fields'}->[0]{'field'}{$_}{'number'}->[0] += 10 for keys %{$xm +l->{'fields'}->[0]{'field'}}; print XMLout($xml, 'rootname' => 'data'); __DATA__ <data> <fields> <field name="foo"> <number>2</number> <required>Y</required> </field> <field name="bar"> <number>1</number> <required>N</required> </field> </fields> </data> _END__ <data> <fields name="field"> <bar> <number>11</number> <required>N</required> </bar> <foo> <number>12</number> <required>Y</required> </foo> </fields> </data>

    Update: grantm beat me to the punch :). He's also using better options than me - my code mangles other parts of the XML structure. Better code is (only relevant lines shown):

    my $xml=XMLin($input, 'forcearray' => [('number','required')]); $xml->{'fields'}{'field'}{$_}{'number'}->[0] += 10 for keys %{$xml->{' +fields'}{'field'}}; print XMLout($xml, keyattr => {field => 'name'}, 'rootname' => 'data') +;
    Which actually works :)

    CU
    Robartes-

Re: Edit values in XML and Save?
by benn (Vicar) on Mar 05, 2003 at 16:29 UTC
    ...Or of course, if you're *just* reading in the file, adding 10, then writing out again...
    use File::Slurp; my $s = read_file("test.xml"); $s =~ s/(<number>)(\d+)(</number>)/$1.($2 + 10).$3/eg; write_file("test.xml",$s);
    I know it avoids all the fun and overhead of a *real* XML parser, but isn't that part of the point of making XML human-readable - for quick Perl-hack purposes? <g> Thanks mirod...<g>
      Thanks all of you.

      benn, trust me, that's the way I always want to do it. I'm forcing myself to do it the Proper Way. But, you might need to tweak the regex to take into account optional whitespace, right?
      --

      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D