// 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 } }