// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Ian McLean (ianm@activestate.com)
// Mitch Denny (mitch.denny@monash.net)
using System;
using System.Globalization;
using System.IO;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
namespace NAnt.Core.Tasks {
///
/// Replaces text in an XML file at the location specified by an XPath
/// expression.
///
///
///
/// The location specified by the XPath expression must exist, it will
/// not create the parent elements for you. However, provided you have
/// a root element you could use a series of the tasks to build the
/// XML file up if necessary.
///
///
///
///
/// Change the server setting in the configuration from testhost.somecompany.com
/// to productionhost.somecompany.com.
///
/// XML file:
///
///
///
///
///
///
///
/// ]]>
///
/// Build fragment:
///
///
/// ]]>
///
///
///
///
/// Modify the noNamespaceSchemaLocation in an XML file.
///
/// XML file:
///
///
///
///
/// ]]>
///
/// Build fragment:
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("xmlpoke")]
public class XmlPokeTask : Task {
#region Private Instance Fields
private FileInfo _xmlFile;
private string _value;
private string _xPathExpression;
private XmlNamespaceCollection _namespaces = new XmlNamespaceCollection();
#endregion Private Instance Fields
#region Public Instance Properties
///
/// The name of the file that contains the XML document that is going
/// to be poked.
///
[TaskAttribute("file", Required=true)]
public FileInfo XmlFile {
get { return _xmlFile; }
set { _xmlFile = value; }
}
///
/// The XPath expression used to select which nodes are to be modified.
///
[TaskAttribute("xpath", Required=true)]
[StringValidator(AllowEmpty=false)]
public string XPath {
get { return _xPathExpression; }
set { _xPathExpression = value; }
}
///
/// The value that replaces the contents of the selected nodes.
///
[TaskAttribute("value", Required=true)]
[StringValidator(AllowEmpty=true)]
public string Value {
get { return _value; }
set { _value = value; }
}
///
/// Namespace definitions to resolve prefixes in the XPath expression.
///
[BuildElementCollection("namespaces", "namespace")]
public XmlNamespaceCollection Namespaces {
get { return _namespaces; }
set { _namespaces = value; }
}
#endregion Public Instance Properties
#region Override implementation of Task
///
/// Executes the XML poke task.
///
protected override void ExecuteTask() {
// ensure the specified xml file exists
if (!XmlFile.Exists) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1154"), XmlFile.FullName), Location);
}
try {
XmlDocument document = LoadDocument(XmlFile.FullName);
XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
foreach (XmlNamespace xmlNamespace in Namespaces) {
if (xmlNamespace.IfDefined && !xmlNamespace.UnlessDefined) {
nsMgr.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
}
}
XmlNodeList nodes = SelectNodes(XPath, document, nsMgr);
// don't bother trying to update any nodes or save the
// file if no nodes were found in the first place.
if (nodes.Count > 0) {
UpdateNodes(nodes, Value);
SaveDocument(document, XmlFile.FullName);
}
} catch (BuildException ex) {
throw ex;
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1159"), XmlFile.FullName),
Location, ex);
}
}
#endregion Override implementation of Task
#region Private Instance Methods
///
/// Loads an XML document from a file on disk.
///
///
/// The file name of the file to load the XML document from.
///
///
/// An containing
/// the document object model representing the file.
///
private XmlDocument LoadDocument(string fileName) {
XmlDocument document = null;
try {
Log(Level.Verbose, "Attempting to load XML document"
+ " in file '{0}'.", fileName);
document = new XmlDocument();
document.Load(fileName);
Log(Level.Verbose, "XML document in file '{0}' loaded"
+ " successfully.", fileName);
return document;
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1158"), fileName), Location,
ex);
}
}
///
/// Given an XML document and an expression, returns a list of nodes
/// which match the expression criteria.
///
///
/// The XPath expression used to select the nodes.
///
///
/// The XML document that is searched.
///
///
/// An to use for resolving namespaces
/// for prefixes in the XPath expression.
///
///
/// An containing references to the nodes
/// that matched the XPath expression.
///
private XmlNodeList SelectNodes(string xpath, XmlDocument document, XmlNamespaceManager nsMgr) {
XmlNodeList nodes = null;
try {
Log(Level.Verbose, "Selecting nodes with XPath"
+ " expression '{0}'.", xpath);
nodes = document.SelectNodes(xpath, nsMgr);
// report back how many we found if any. If not then
// log a message saying we didn't find any.
if (nodes.Count != 0) {
Log(Level.Info, "Found '{0}' nodes matching"
+ " XPath expression '{1}'.", nodes.Count, xpath);
} else {
Log(Level.Warning, "No matching nodes were found"
+ " with XPath expression '{0}'.", xpath);
}
return nodes;
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1161"),
xpath), Location, ex);
}
}
///
/// Given a node list, replaces the XML within those nodes.
///
///
/// The list of nodes to replace the contents of.
///
///
/// The text to replace the contents with.
///
private void UpdateNodes(XmlNodeList nodes, string value) {
Log(Level.Verbose, "Updating nodes with value '{0}'.",
value);
int index = 0;
foreach (XmlNode node in nodes) {
Log(Level.Verbose, "Updating node '{0}'.", index);
node.InnerXml = value;
index ++;
}
Log( Level.Verbose, "Updated all nodes successfully.",
value);
}
///
/// Saves the XML document to a file.
///
/// The XML document to be saved.
/// The file name to save the XML document under.
private void SaveDocument(XmlDocument document, string fileName) {
try {
Log(Level.Verbose, "Attempting to save XML document"
+ " to '{0}'.", fileName);
document.Save(fileName);
Log(Level.Verbose, "XML document successfully saved"
+ " to '{0}'.", fileName);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1162"), fileName),
Location, ex);
}
}
#endregion Private Instance Methods
}
}