Search code examples
perlxml-libxml

How to move a node in XML data using Perl


I've tried several different methods for parsing XML documents. I need to find a node in the document with an xattribute, and then move that node with its children to a different parent node.

I've had some success using XML::LibXML for locating the node and being able to iterate through it's children, but I'm stuck in how to move it to a different parent.

Using $node->cloneNode(1) looks promising but I can't find good examples of its use.

This is the original XML data

<config logdir="/var/log/foo/" debugfile="/tmp/foo.debug">
  <old>
    <server name="sahara" osname="solaris" osversion="2.6">
      <address ip="10.0.0.101">Private</address>
      <address ip="10.0.1.101">Private</address>
    </server>
    <server name="gobi" osname="irix" osversion="6.5">
      <address ip="10.0.0.102">Private</address>
    </server>
    <server name="kalahari" osname="linux" osversion="2.0.34">
      <address ip="10.0.0.103">Private</address>
      <address ip="10.0.1.103">Private</address>
    </server>
  </old>
  <new>
  </new>        
</config>

and this is the result I would like

<config logdir="/var/log/foo/" debugfile="/tmp/foo.debug">
  <old>
    <server name="sahara" osname="solaris" osversion="2.6">
      <address ip="10.0.0.101">Private</address>
      <address ip="10.0.1.101">Private</address>
    </server>
    <server name="gobi" osname="irix" osversion="6.5">
      <address ip="10.0.0.102">Private</address>
    </server>
  </old>
  <new>
    <server name="kalahari" osname="linux" osversion="2.0.34">
      <address ip="10.0.0.103">Private</address>
      <address ip="10.0.1.103">Private</address>
    </server>
  </new>        
</config>

Solution

  • Using XML::LibXML:

    use strict;
    use warnings;
    
    use XML::LibXML;
    
    my $xml = XML::LibXML->load_xml( IO => \*DATA );
    
    my ($new) = $xml->findnodes('//new');
    
    for my $linux ( $xml->findnodes('//old/server[@osname="linux"]') ) {
        $linux->unbindNode();         # Remove from Parent (done automatically when added elsewhere without cloning)
        $new->addChild($linux);
    }
    
    print $xml;
    
    __DATA__
    <config logdir="/var/log/foo/" debugfile="/tmp/foo.debug">
        <old>
            <server name="sahara" osname="solaris" osversion="2.6">
                <address ip="10.0.0.101">Private</address>
                <address ip="10.0.1.101">Private</address>
            </server>
            <server name="gobi" osname="irix" osversion="6.5">
                <address ip="10.0.0.102">Private</address>
            </server>
            <server name="kalahari" osname="linux" osversion="2.0.34">
                <address ip="10.0.0.103">Private</address>
                <address ip="10.0.1.103">Private</address>
            </server>
        </old>
        <new>
        </new>      
    </config>
    

    Outputs:

    <?xml version="1.0"?>
    <config logdir="/var/log/foo/" debugfile="/tmp/foo.debug">
        <old>
            <server name="sahara" osname="solaris" osversion="2.6">
                <address ip="10.0.0.101">Private</address>
                <address ip="10.0.1.101">Private</address>
            </server>
            <server name="gobi" osname="irix" osversion="6.5">
                <address ip="10.0.0.102">Private</address>
            </server>
    
        </old>
        <new>
        <server name="kalahari" osname="linux" osversion="2.0.34">
                <address ip="10.0.0.103">Private</address>
                <address ip="10.0.1.103">Private</address>
            </server></new>      
    </config>