// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. //----------------------------------------------------------------------- // // Returns the value specified by XPath. //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security; using System.Security.Permissions; using System.Text; using System.Xml; using System.Xml.Xsl; using System.Xml.XPath; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Microsoft.Build.Tasks { /// /// A task that returns values as specified by XPath Query /// from an XML file. /// public class XmlPeek : TaskExtension { #region Members /// /// The XML input as a file path. /// private ITaskItem _xmlInputPath; /// /// The XML input as a string. /// private string _xmlContent; /// /// The XPath Query. /// private string _query; /// /// The results that this task will return. /// private ITaskItem[] _result; /// /// The namespaces for XPath query's prefixes. /// private string _namespaces; #endregion #region Properties /// /// The XML input as a file path. /// public ITaskItem XmlInputPath { get { return _xmlInputPath; } set { _xmlInputPath = value; } } /// /// The XML input as a string. /// public string XmlContent { get { return _xmlContent; } set { _xmlContent = value; } } /// /// The XPath Query. /// public string Query { get { if (_query == null) throw new ArgumentNullException ("Query"); return _query; } set { _query = value; } } /// /// The results returned by this task. /// [Output] public ITaskItem[] Result { get { return _result; } } /// /// The namespaces for XPath query's prefixes. /// public string Namespaces { get { return _namespaces; } set { _namespaces = value; } } #endregion internal static bool IsCriticalException (Exception e) { if (e is StackOverflowException || e is OutOfMemoryException || e is AccessViolationException) { return true; } return false; } /// /// Executes the XMLPeek task. /// /// true if transformation succeeds. public override bool Execute () { XmlInput xmlinput; if (_query == null) throw new ArgumentNullException ("Query"); try { xmlinput = new XmlInput (_xmlInputPath, _xmlContent); } catch (Exception e) { if (IsCriticalException (e)) { throw; } Log.LogErrorWithCodeFromResources ("XmlPeek.ArgumentError", e.Message); return false; } XPathDocument xpathdoc; try { // Load the XPath Document using (XmlReader xr = xmlinput.CreateReader ()) { xpathdoc = new XPathDocument (xr); xr.Close (); } } catch (Exception e) { if (IsCriticalException (e)) { throw; } Log.LogErrorWithCodeFromResources ("XmlPeekPoke.InputFileError", _xmlInputPath.ItemSpec, e.Message); return false; } finally { xmlinput.CloseReader (); } XPathNavigator nav = xpathdoc.CreateNavigator (); XPathExpression expr = null; try { // Create the expression from query expr = nav.Compile (_query); } catch (Exception e) { if (IsCriticalException (e)) { throw; } Log.LogErrorWithCodeFromResources ("XmlPeekPoke.XPathError", _query, e.Message); return false; } // Create the namespace manager and parse the input. XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager (nav.NameTable); try { LoadNamespaces (ref xmlNamespaceManager, _namespaces); } catch (Exception e) { if (IsCriticalException (e)) { throw; } Log.LogErrorWithCodeFromResources ("XmlPeek.NamespacesError", e.Message); return false; } try { expr.SetContext (xmlNamespaceManager); } catch (XPathException e) { Log.LogErrorWithCodeFromResources ("XmlPeek.XPathContextError", e.Message); return false; } XPathNodeIterator iter = nav.Select (expr); List peekValues = new List (); while (iter.MoveNext ()) { if (iter.Current.NodeType == XPathNodeType.Attribute || iter.Current.NodeType == XPathNodeType.Text) { peekValues.Add (iter.Current.Value); } else { peekValues.Add (iter.Current.OuterXml); } } _result = new ITaskItem[peekValues.Count]; int i = 0; foreach (string item in peekValues) { _result [i++] = new TaskItem (item); // This can be logged a lot, so low importance Log.LogMessageFromResources (MessageImportance.Low, "XmlPeek.Found", item); } if (_result.Length == 0) { // Logged no more than once per execute of this task Log.LogMessageFromResources ("XmlPeek.NotFound"); } return true; } /// /// Loads the namespaces specified at Namespaces parameter to XmlNSManager. /// /// The namespace manager to load namespaces to. /// The namespaces as XML snippet. private void LoadNamespaces (ref XmlNamespaceManager namespaceManager, string namepaces) { XmlDocument doc = new XmlDocument (); try { XmlReaderSettings settings = new XmlReaderSettings (); settings.DtdProcessing = DtdProcessing.Ignore; using (XmlReader reader = XmlReader.Create (new StringReader ("" + namepaces + ""), settings)) { doc.Load (reader); } } catch (XmlException xe) { throw new ArgumentException ("The specified Namespaces attribute is not a well-formed XML fragment.", xe); } XmlNodeList xnl = doc.SelectNodes ("/Namespaces/*[local-name() = 'Namespace']"); for (int i = 0; i < xnl.Count; i++) { XmlNode xn = xnl [i]; if (xn.Attributes ["Prefix"] == null) { throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Name")); } if (xn.Attributes ["Uri"] == null) { throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Uri")); } namespaceManager.AddNamespace (xn.Attributes ["Prefix"].Value, xn.Attributes ["Uri"].Value); } } /// /// This class prepares XML input from XMLInputPath and XMLContent parameters /// internal class XmlInput { /// /// What XML input type are we at. /// private XmlModes _xmlMode; /// /// This either contains the raw Xml or the path to Xml file. /// private string _data; /// /// Filestream used to read XML. /// private FileStream _fs; /// /// Constructor. /// Only one parameter should be non null or will throw ArgumentException. /// /// The path to XML file or null. /// The raw XML. public XmlInput (ITaskItem xmlInputPath, string xmlContent) { if (xmlInputPath != null && xmlContent != null) { throw new ArgumentException ("Only one of XmlContent or XmlInputPaths arguments can be set."); } else if (xmlInputPath == null && xmlContent == null) { throw new ArgumentException ("One of XmlContent or XmlInputPaths arguments must be set."); } if (xmlInputPath != null) { _xmlMode = XmlModes.XmlFile; _data = xmlInputPath.ItemSpec; } else { _xmlMode = XmlModes.Xml; _data = xmlContent; } } /// /// Possible accepted types of XML input. /// public enum XmlModes { /// /// If the mode is a XML file. /// XmlFile, /// /// If the mode is a raw XML. /// Xml } /// /// Returns the current mode of the XmlInput /// public XmlModes XmlMode { get { return _xmlMode; } } /// /// Creates correct reader based on the input type. /// /// The XmlReader object public XmlReader CreateReader () { if (_xmlMode == XmlModes.XmlFile) { _fs = new FileStream (_data, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return XmlReader.Create (_fs); } else { // xmlModes.Xml return XmlReader.Create (new StringReader (_data)); } } /// /// Closes the reader. /// public void CloseReader () { if (_fs != null) { _fs.Close (); _fs = null; } } } } }