Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

First post of the year and hopefully this is something useful. I think it is.

I'm currently doing a major automation-overhaul to our projects, trying to streamline everything. Part of this involves doing automated deployments of the projects to a location (say a file or web server) where a QA person can come along later and with the click of a button they can just launch an installer for any build of an application. This is very much structured like the JetBrains Nightly Build page you see here, but 100% automated for all projects using NAnt.

A lofty goal? Perhaps.

Anywho, the time to update the builds and page has come and I went hunting to see how I could do it (without having to write a silly little console app or something). To start with (and I'm not done here, but this works and is a model anyone can use) we have a basic XML file:

<?xml version="1.0" encoding="utf-8"?>

<builds>

  <build>

    <date>01/03/2008 15:03:41</date>

    <build>0</build>

  </build>

</builds>

This contains build information for the project and will be transformed using XSLT into something pretty (and useful once I add more attributes).

The challenge is that we want to append an XML node to this file during the deployment process, which is kicked off by CruiseControl.NET. Sounds easy huh. There are a few ways to do this. First, I could write that console app or something and have it update some file. Or maybe it would even write to a database. Or... no, that's getting too complicated. The next thought was to use the ability to write C# code in NAnt scripts, but then that started to get ugly real fast and more maintenance than I wanted.

Then I turned to xmlpoke. This little NAnt task let's you replace a node (or nodes) in an XML file. Trouble is that's what it's designed to do. Replace a node or property. Not append one. After about 15 minutes of Googling (my patience is pretty thin for finding an answer on the 3rd page of Google) I realized xmlpoke wasn't going to be good enough for this. Someone had come up with xmlpoke2 which did exactly what I wanted (appended data to an XML file), but to date it hasn't made it into the core or even NAntContrib.

After looking at the XML file I realized I might be able to use xmlpeek (read some XML from a file) and combine it with xmlpoke (modifying it on the way out) and write it back to the file. Maybe not the most elegant solution, but I think it's pretty nifty and it gets the job done.

First we have our XML file (above) so I created a target in NAnt to handle the update to the XML file:

<target name="publish-report" description="add the version deployed to an existing xml file">

</target>

Step 1 - Use xmlpeek to read in the entire XML node tree containing the current builds:

<!-- read in all the builds for rewriting -->

<property name="xmlnodes" value=""/>

<xmlpeek xpath="//builds" file="c:\autobuild.xml" property="xmlnodes"></xmlpeek>

Step 2 - Modify it by appending a new node with the new build info and saving it into a new property:

<!-- modify the node by adding a new one to it -->

<property name="newnode" value="&lt;build&gt;&lt;date&gt;${datetime::now()}&lt;/date&gt;&lt;build&gt;${CCNetLabel}&lt;/build&gt;&lt;/build&gt;" />

<property name="xmlnodes" value="${xmlnodes}${newnode}" />

Step 3 - Write it back out to the original XML file replacing the entire XML tree using xmlpoke:

<!-- rewrite it back out to the xml file using xmlpeek -->

<xmlpoke file="c:\autobuild.xml" xpath="//builds" value="${xmlnodes}" />

The result. Here's the updated XML file after running NAnt with our target task (and faking out the CCNetLabel that would usually get set by CruiseControl via a command line definition):

tools\nant\nant.exe publish-report -D:CCNetLabel=1

NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)

Copyright (C) 2001-2006 Gerry Shaw

http://nant.sourceforge.net

 

Buildfile: file:///C:/development/common/Library/Common/Common.build

Target framework: Microsoft .NET Framework 2.0

Target(s) specified: publish-report

 

publish-report:

 

[xmlpeek] Peeking at 'c:\autobuild.xml' with XPath expression '//builds'.

[xmlpeek] Found '1' nodes with the XPath expression '//builds'.

[xmlpoke] Found '1' nodes matching XPath expression '//builds'.

 

BUILD SUCCEEDED

 

Total time: 0.2 seconds.

<?xml version="1.0" encoding="utf-8"?>

<builds>

  <build>

    <date>01/03/2008 15:03:41</date>

    <build>0</build>

  </build>

  <build>

    <date>01/03/2008 15:30:07</date>

    <build>1</build>

  </build>

</builds>

Now I have a continuously growing XML file with all my build numbers in them. Of course there's more info to add here like where to get the file and such but the concept works and I think it's a half decent compromise (to having to write my own task or more script). The cool thing is that you can even use it against a file like this:

<?xml version="1.0" encoding="utf-8"?>

<builds>

</builds>

This lets you start from scratch for new projects and start with build 1 (which will come from CruiseControl.NET). If the file didn't exist at all, you could even use the echo task or something like it to create the file, then update it with the task info above. Is it bullet proof? Hardly. It should work though and gives me the automation I want.

Well, I'm done for the day. That was a worthwhile hour to build this. Now I just have to go off and add in all the extra goop and hook it up to our builds.

Enjoy!

Published Thursday, January 03, 2008 3:04 PM by Bil Simser

Comments

# re: Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

Thursday, January 03, 2008 9:52 PM by Tom Opgenorth

peek & poke?  Wow - flashbacks from coding on a Apple ][+.

# re: Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

Thursday, January 17, 2008 5:51 PM by Bryant Sharp

Great example :) Thanks for taking the time to share this.

# re: Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

Sunday, November 14, 2010 1:01 AM by Jeff Martin

Thanks very useful.

# re: Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

Friday, July 08, 2011 9:28 AM by Saravanan Kalirajan

I am using NAnt 0.91 Alph2. I have a problem with the above solution.

If the xml file is empty, the new node is added perfectly. But if it already contains a node, the previous node(s) are converted to plain string(no xml hierarchy) and the new node is appended to that plain string. Any clues?

# re: Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

Tuesday, February 14, 2012 8:22 PM by VCSekhar Parepalli

Hi,

if you don't mind sharing your thoughts on this:

I have properties.xml file with builds-info-xml defined just like yours. I referred it into my default.build file and tried XMLPeek and XMLPoke to increment the versions and it didn't work at all. Nant kept complaining "[xmlpoke] No matching nodes were found with XPath expression..."

I later saw other URL (solepano.blogspot.com/.../problem-with-nants-xmlpoke-task.html)  suggesting to use namespaces to get around this. Even though, I referred same namespace, at the top of each file, in both the default.build and properties.xml file and I tried this suggestion too and didn't see any change.

Could you please throw some light on when to use namespaces and when it is not needed for these 2 tags?

Thanks

VC Sekhar Parepalli

Leave a Comment

(required) 
(required) 
(optional)
(required)