A little bit of an update.  I've now made it capable of expanding properties (using the Project.ExpandProperties method) that you put into your xml element.  In theory, you could make large chunks of your xml into properties, and tie them together with this task.  However, I think it's more useful to make the values of your xml more dynamic.  I've also tidied up a few other things in the code.  The updated code is below:

        [TaskName("xmlpoke2")]
        public class XmlPoke2 :Task
        {
          protected override void ExecuteTask()
          {
            _xml = this.XmlNode.FirstChild.Value;
            XmlDocument xd = new XmlDocument();
           
            if (System.IO.File.Exists(_xmlFile))
            {
              xd.Load(_xmlFile);
            }
            else if (_create == "true")
            {
              xd.LoadXml(_xml);
              Log(Level.Info, "Can't find file " + _xmlFile + " - creating a new one.");
            }
            else
            {
              throw new NAnt.Core.BuildException("File " + "_xmlFile " + "could not be found, and create was set to false");
            }
           
            if (xd.SelectSingleNode (_xPath) == null)
            {
              throw new NAnt.Core.BuildException("The XPath query returned a null value");
            }
           
            XmlNode xn = xd.SelectSingleNode(_xPath);
            Log(Level.Info, "Setting '" + _xPath + "' in '" + _xmlFile + "'");
           
            if (xn.NodeType != XmlNodeType.Element && xn.NodeType != XmlNodeType.Document )
            {
              throw new NAnt.Core.BuildException("XPath string must return an element");
            }
           
            xn.InnerXml = Project.ExpandProperties(_xml, Location);
            xd.Save(_xmlFile);
          }
         
          [TaskAttribute("file", Required=true)]
          public string XmlFileName {
              get { return _xmlFile; }
              set { _xmlFile = value; }
          }
          [TaskAttribute("create", Required=false)]
          public string CreateIfAbsent {
              get { return _create; }
              set { _create = value; }
          }
          [TaskAttribute("xpath", Required=true)]
          public string XPathQuery {
              get { return _xPath; }
              set { _xPath = value; }
          }
         
          private string _xml;
          private string _xmlFile;
          private string _xPath;
          private string _create = "true";
        }

Regards

John

On 16/01/06, John Ludlow <[EMAIL PROTECTED]> wrote:
In the XmlPeek thread, I mentioned that I was thinking of an XmlPoke task that could easily create a whole xml structure in one go.  I finally got around to doing it, and here it is:

[TaskName("xmlpoke2")]
        public class XmlPoke2 :Task
        {
          protected override void ExecuteTask()
          {
            _xml = this.XmlNode.FirstChild.Value;
            XmlDocument xd = new XmlDocument();
           
            if (System.IO.File.Exists(_xmlFile))
            {
              xd.Load(_xmlFile);
            }
            else if (_create == "true")
            {
              xd.LoadXml(_xml);
            }
            else
            {
              throw new NAnt.Core.BuildException("File " + "_xmlFile " + "could not be found, and create was set to false");
            }
           
            XmlNode xn = xd.SelectSingleNode(_xPath);
           
            if (xn.NodeType != XmlNodeType.Element && xn.NodeType != XmlNodeType.Document)
            {
              throw new NAnt.Core.BuildException("XPath string must return an element");
            }
           
            xn.InnerXml = _xml;
            xd.Save(_xmlFile);
          }
         
          [TaskAttribute("file", Required=true)]
          public string XmlFileName {
              get { return _xmlFile; }
              set { _xmlFile = value; }
          }
          [TaskAttribute("create", Required=false)]
          public string CreateIfAbsent {
              get { return _create; }
              set { _create = value; }
          }
          [TaskAttribute("xpath", Required=true)]
          public string XPathQuery {
              get { return _xPath; }
              set { _xPath = value; }
          }
         
          private string _xml;
          private string _xmlFile;
          private string _xPath;
          private string _create = "true";
        }

I've tested this from within a <script> task.  Note that this is just preliminary code that I chucked together very quickly, so it's probably a bit-rough-and ready, but seems to do the job.

The usage looks something like this:

Put some xml in an existing xml file, or create a new one if it's not already there

  <xmlpoke2 file="test.xml" xpath="/">
    <![CDATA[
      <test>
          <data>hello world</data>
      </test>
    ]]>
  </xmlpoke2>

Put some xml in an existing xml file, or throw an error if it can't be found

  <xmlpoke2 file="test.xml" xpath="/" create="false">
    <![CDATA[
      <test>
          <data>hello world</data>
      </test>
    ]]>
  </xmlpoke2>

Put some XML in a file at a specific node (useful to create a fragment of an xml document if you want to leave the rest of the document alone).

  <xmlpoke2 file="test.xml" xpath="root/secondlevel">
    <![CDATA[
      <test>
          <data>hello world</data>
      </test>
    ]]>
  </xmlpoke2>

 - The value of the xmlpoke2 task XML element should be a CDATA element with your XML in it.  This is to get around NAnt's schema enforcement.
 - The file attribute should be the XML file path.
 - The xpath attribute should be the xpath of where you want the XML placed within the file.
 - The create attribute (optional) specifies whether or not to create the xml file if it's not found.  It defaults to true.

Some other things to consider:

 - It's called xmlpoke2 to avoid clashing with the existing xmlpoke task.  Whether or not this task should be merged with the existing one is a matter for debate.
 - If the XML you want to write has CDATA elements, you'll still need to use XML escaping (&lt;[CDATA[some text]]&lt;) to make this work.  This is because the parser ends the CDATA section at the first ]]> it finds.
 - Not sure if this works well for preprocessors, DOCTYPEs, and so on.
 - If you try to use the xpath attribute to drill down into the document when the nodes you specfied in there don't exist, you'll get a NullReferenceException.  I'll probably make this a better exception at some point.

Next one I need to consider then is a task to iterate xpath results, I guess.  In the meantime, see what you think of the xmlpoke2 task.

Have fun!

John

Reply via email to