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

I am having difficulties trying to figure out how to append the text from one Child Node to that of another before moving to the next parent.

I have tried using a few different XML libraries and am currently testing XPath. That is before I decide to just write my own parser which I would rather not do right now.

The following is a sample of the XML page

<xdoc> <MsgSigs> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln1</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> </MsgSignals> </MsgSig> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln2</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> <Signal> <Description>0_SPN695 Eng. Override </Description> <Key>sig695</Key> <ValueType>1</ValueType> </Signal> </MsgSignals> </MsgSig> </xdoc>

What I am trying to do is loop through the MsgSig nodes and take the key (eg, ln1, ln2) and append this key to the Description. Note: I am not wanting to go into the Signal Node (why I ruled out DOM). The result would look something like this

<xdoc> <MsgSigs> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1 (ln1)</Description> <Key>ln1</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> </MsgSignals> </MsgSig> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1 (ln2)</Description> <Key>ln2</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> <Signal> <Description>0_SPN695 Eng. Override </Description> <Key>sig695</Key> <ValueType>1</ValueType> </Signal> </MsgSignals> </MsgSig> </xdoc>

As I mentioned I am currently using XPath and as a test have made it to here and here is where I sit.

#!/usr/bin/perl -w use XML::XPath; use Data::Dumper; $file = "test.xml"; $xp = XML::XPath->new(filename => $file); @nodes = $xp->findnodes("/xdoc/MsgSigs/MsgSig"); foreach (@nodes) { $key = $_->findvalue('Key'); $descr = $_->findvalue('Description'); $text = $descr . "(" . $key . ")"; # A setValue would be awesome right about now or an # appendtext $_-> print $_->findvalue('Description'), "\n"; } #print out to xml file

Any help would be very much appreciated.

D. Thomas

Replies are listed 'Best First'.
Re: Appending Text of One XML Node to that of the Other
by choroba (Cardinal) on Feb 04, 2016 at 22:56 UTC
    In xsh, you can do the following:
    open file.xml ; for /xdoc/MsgSigs/MsgSig insert text concat(' (', Key, ')') append Description ; save :b ;

    XML::XSH2 is just a wrapper around XML::LibXML.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      Thanks

Re: Appending Text of One XML Node to that of the Other -- XML::Twig
by Discipulus (Canon) on Feb 05, 2016 at 09:22 UTC
    Hello thomasd

    your XML has the </MsgSigs> closing tag missing.. corrected this I as always propose an XML::Twig based solution. Infact navigation methods as summarized here (the site has also good tutorials!) make the life of an XML traveller easier.

    Note also that twig_handlers plus flush make the program very memory efficient: for every xdoc/MsgSigs/MsgSig two child are inspected and the first manipulated, then flush prints all the twig parsed till now and memory is freed.

    #!perl use strict; use warnings; use XML::Twig; my $t= XML::Twig->new(pretty_print => 'indented', twig_handlers=>{ 'xdoc/MsgSigs/MsgSig'=>sub{ my $keytext = $_[1]->first_child('Key')->text; my $descrtext = $_[1]->first_child('Description') +->text; $_[1]->first_child('Description')->set_text($desc +rtext." ($keytext)"); $_[0]->flush; } } ); $/=''; $t->parse(<DATA>); __DATA__ <xdoc> <MsgSigs> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln1</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> </MsgSignals> </MsgSig> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln2</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> <Signal> <Description>0_SPN695 Eng. Override </Description> <Key>sig695</Key> <ValueType>1</ValueType> </Signal> </MsgSignals> </MsgSig> </MsgSigs> </xdoc> ###OUTPUT <xdoc> <MsgSigs> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1 (ln1)</Description> <Key>ln1</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals></MsgSignals> </MsgSig> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1 (ln2)</Description> <Key>ln2</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> <Signal> <Description>0_SPN695 Eng. Override </Description> <Key>sig695</Key> <ValueType>1</ValueType> </Signal> </MsgSignals> </MsgSig> </MsgSigs> </xdoc>

    HtH

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

      Thank-you very much. I hadn't actually gotten around to Twig yet. Very clean, quick and efficient.

Re: Appending Text of One XML Node to that of the Other
by Jenda (Abbot) on Feb 05, 2016 at 22:30 UTC

    To add to the list of solutions, this is what it would look like using XML::Rules:

    use strict; use XML::Rules; my $filter = XML::Rules->new( style => 'filter', rules => { _default => 'raw', 'Key,Description' => 'raw extended', MsgSig => sub { my ($tag,$attrs) = @_; my $key = $attrs->{':Key'}{_content}; if ($key) { $attrs->{':Description'}{_content} .= " ($key)" } return $tag => $attrs; } } ); $filter->filter(\*DATA); __DATA__ <xdoc> <MsgSigs> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln1</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> </MsgSignals> </MsgSig> <MsgSig> <Description>TSC1 - Torque/Speed Cntrl 1</Description> <Key>ln2</Key> <XtdFrame>True</XtdFrame> <NetworkKey>net0</NetworkKey> <MsgSignals> <Signal> <Description>0_SPN695 Eng. Override </Description> <Key>sig695</Key> <ValueType>1</ValueType> </Signal> </MsgSignals> </MsgSig> </MsgSigs> </xdoc>

    It only keeps at most one <MsgSig> tag in memory using plain and simple Perl data structures. The rules specify what to do with the tags. 'raw' means 'just copy over', 'raw extended' means 'copy over, but also give me a shortcut' and the subroutine gets called for each <MsgSig> tag, reads the contents of its <Key> subtag and appends them to the contents of the <Description>subtag.

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

Re: Appending Text of One XML Node to that of the Other
by poj (Abbot) on Feb 06, 2016 at 08:05 UTC

    I wouldn't recommend this but just to show one way it could be done with XML::XPath.

    #!/usr/bin/perl use strict; use XML::XPath; my $file = "test.xml"; my $xp = XML::XPath->new(filename => $file); my $xpath = '//MsgSig'; my $i=1; while ( $xp->exists($xpath."[$i]") ) { my $key = $xp->getNodeText($xpath."[$i]/Key"); my $desc = $xp->getNodeText($xpath."[$i]/Description"); $desc .= " ($key)"; #print "$desc\n"; $xp->setNodeText($xpath."[$i]/Description",$desc); ++$i; } print $xp->getNodeAsXML();
    poj