Hi, there

Recently I mentioned on the NAnt Dev list that I have been working on
XML-handling functionality.  I've got further than I had last time, so
I thought about summarising what I've done here (I'm also happy to
share the code and/or binaries, so if you want either of these to
check it out for yourself, just email me at 
[EMAIL PROTECTED]).  I've put my code into an assembly, tested
it, and am currently contemplating writing some unit tests for it. 
Before I go through that, though, I wanted to get some feedback and a
better idea of what people thought of this.

This is what I have so far:

Functions
=======

bool xml::value-exists (string xmlFile, string xPath)
-------------------------------------------------------------------------
Tests whether a specified xpath query returns a value.  If this gets a
NullReferenceException (usually means an XML value doesn't exist).

string xml::get-value (string xmlFile, string xPath)
-----------------------------------------------------------------------
Gets a value returned by an XPath query.  Throws an error if the node
specified in the xpath is not found.  Duplicates some of <xmlpeek>'s
functionality, but enables you to use ${x} syntax to get an xml value.
 This fits in with the idea that being able to use this functionality
is nicer if all you want is information.  However, it does not support
namespaces like xmlpeek does.

string xml::get-value-without-failing (string xmlFile, string xPath)
-------------------------------------------------------------------------------------------
Gets a value returned by an XPath query.  As above, but does  not an
error if the node specified in the XPath is not found, instead
returning a blank string.

string xml::get-xml (string xmlFile, string xPath)
Gets the InnerXml property of an XML node returned by an XPath query.

int xml::count (string xmlFile, string xPath)
Gets the number of nodes in a document that match the specified XPath query.

Tasks

xml-foreach
=========
Iterates through an XML document.  I thought about making this part of
the ForEach task, but there seemed to be a significant difference in
functionality, especially concerning getting  the details of each item
in the iteration.  For this reason, I decided to make a separate task.
 The syntax is explained below:

Attributes:
========
 - file:      string.  XML file to iterate
 - xpath:  string.  XPath.  The task will essentially iterate through
the list of nodes that match this query.
 - index:  string.  Optional.  A property that can contain the index
of a returned result in the node list.  You can use this to find this
node in the doument again, especially useful for nested loops.  By
default doesn't set any property.

Nested elements:
=============
 - do:  The tasks to run on each iteration

 - xmlpropertybinding:  Runs XPath queries on the result, putting the
returned value in a property.

 - xmlpropertybinding/get

Attributes
-----------------------------------------------
 - xpath: string.  The XPath query to run
 - property: string.  The property to populate with the value returned
by the XPath query.
 - failonempty: bool.  Optional.  Specifies whether or not the script
should fail if the node specified in the XPath cannot be found. 
Default is false.

Example:

Assuming you have the following in a file called test.xml

 <root>
  <data name="bob" age="44">
    <children>
      <child name="tom" age="3"/>
      <child name="dick" age="1"/>
      <child name="harry" age=".5"/>
    </children>
  </data>
  <data name="bill" age="15">
  </data>
  <data name="ted" age="372">
    <children>
      <child name="sarah" age="4"/>
    </children>
  </data>
  <data name="fred" age="23">
    <children>
      <child name="phil" age="5"/>
    </children>
  </data>
</root>

...and you have the following in your build script:

  <xml-foreach file="test.xml" xpath="/root/data" index="i">
    <xmlpropertybinding>
      <get xpath="@name" property="name"/>
      <get xpath="@age" property="age"/>
      <get xpath="children/child/@name " property="child"/>
    </xmlpropertybinding>
    <do>
      <echo message="#${i} ${name} is ${age} years old"/>
      <xml-foreach file="test.xml" xpath="/root/data[${i}]/children/child">
        <xmlpropertybinding>
          <get xpath="@name" property="childname"/>
          <get xpath="@age" property="age"/>
        </xmlpropertybinding>
        <do>
          <echo message="${name}::${childname} is ${age} years old"/>
        </do>
      </xml-foreach>
    </do>
  </xml-foreach>

 ...you'll get this returned:

     [echo] #1 bob is 44 years old
     [echo] bob::tom is 3 years old
     [echo] bob::dick is 1 years old
     [echo] bob::harry is .5 years old
     [echo] #2 bill is 15 years old
     [echo] #3 ted is 372 years old
     [echo] ted::sarah is 4 years old
     [echo] #4 fred is 23 years old
     [echo] fred::phil is 5 years old

xmlpoke2
=======
Like <xmlpoke>, but writes a whole structure, rather than a single
value.  Especially useful if you want to dynamically create an XML
file in your script.  While this is currently possible in NAnt using a
combination of <echo> (or <copy> if you have a template copy of the
file) and <xmlpoke>, it involves a lot of escape characters.  This new
task allows you to dynamically create your xml structure in your build
script and write it all in one go.

Attributes
--------------
 - file:  string.  The xml file to write to.
 - xpath:  string.  Where to write to in the document.  To write at
the root, use "/"
 - create:  bool.  Optional.  Specifies whether the task should create
the file if it can't find it.  Defaults to true.

Nested Elements

 - data:  the XML data to write.  You should put this into a CDATA
element.Example:

   <xmlpoke2 file="${file}" xpath="/" create="true">
    <data>
      <![CDATA[
        <root>
          <person name="mary" age="43">
            <child name="penelope" age="12"/>
          </person>
        </root>
      ]]>
    </data>
  </xmlpoke2>

You can embed properties within the CDATA element as well, so you can do this:

  <property name="penelopysage" value="12"/>

  <xmlpoke2 file="${file}" xpath="/" create="true">
     <data>
       <![CDATA[
         <root>
           <person name="mary" age="43">
             <child name="penelope" age="${penelopysage}"/>
           </person>
         </root>
       ]]>
     </data>
   </xmlpoke2>
or this:
  <xmlpoke2 file="${file}" xpath="/root" create="true">
     <data>
       <![CDATA[
          ${xml::get-xml(file, '/root')}
          <person name="mary" age="43">
             <child name="penelope" age="12"/>
           </person>
      ]]>
     </data>
   </xmlpoke2>

...which would append data to the end of your document.

Neither of these is perfect at the moment, but I wanted to see if
anyone thought they'd be useful.  The <xmlpoke2> task could maybe do
with namespace support (don't know much about that) and I reckon it's
just begging to be merged into the main <xmlpoke> task.  If people
think they could be useful, then I might do some more work on them, or
if you want to look at the source or try them out yourself just email
me.

Cheers

John


-------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc. Do you grep through log files
for problems?  Stop!  Download the new AJAX search engine that makes
searching your log files as easy as surfing the  web.  DOWNLOAD SPLUNK!
http://sel.as-us.falkag.net/sel?cmd=lnk&kid3432&bid#0486&dat1642
_______________________________________________
nant-developers mailing list
nant-developers@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/nant-developers

Reply via email to