1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
5 // <summary>Returns the value specified by XPath.</summary>
6 //-----------------------------------------------------------------------
9 using System.Collections.Generic;
12 using System.Reflection;
13 using System.Security;
14 using System.Security.Permissions;
18 using System.Xml.XPath;
20 using Microsoft.Build.Framework;
21 using Microsoft.Build.Utilities;
23 namespace Microsoft.Build.Tasks
26 /// A task that returns values as specified by XPath Query
29 public class XmlPeek : TaskExtension
34 /// The XML input as a file path.
36 private ITaskItem _xmlInputPath;
39 /// The XML input as a string.
41 private string _xmlContent;
46 private string _query;
49 /// The results that this task will return.
51 private ITaskItem[] _result;
54 /// The namespaces for XPath query's prefixes.
56 private string _namespaces;
63 /// The XML input as a file path.
65 public ITaskItem XmlInputPath {
71 _xmlInputPath = value;
76 /// The XML input as a string.
78 public string XmlContent {
94 throw new ArgumentNullException ("Query");
104 /// The results returned by this task.
107 public ITaskItem[] Result {
114 /// The namespaces for XPath query's prefixes.
116 public string Namespaces {
129 internal static bool IsCriticalException (Exception e)
131 if (e is StackOverflowException
132 || e is OutOfMemoryException
133 || e is AccessViolationException) {
140 /// Executes the XMLPeek task.
142 /// <returns>true if transformation succeeds.</returns>
143 public override bool Execute ()
147 throw new ArgumentNullException ("Query");
150 xmlinput = new XmlInput (_xmlInputPath, _xmlContent);
151 } catch (Exception e) {
152 if (IsCriticalException (e)) {
156 Log.LogErrorWithCodeFromResources ("XmlPeek.ArgumentError", e.Message);
160 XPathDocument xpathdoc;
162 // Load the XPath Document
163 using (XmlReader xr = xmlinput.CreateReader ()) {
164 xpathdoc = new XPathDocument (xr);
167 } catch (Exception e) {
168 if (IsCriticalException (e)) {
172 Log.LogErrorWithCodeFromResources ("XmlPeekPoke.InputFileError", _xmlInputPath.ItemSpec, e.Message);
175 xmlinput.CloseReader ();
178 XPathNavigator nav = xpathdoc.CreateNavigator ();
179 XPathExpression expr = null;
181 // Create the expression from query
182 expr = nav.Compile (_query);
183 } catch (Exception e) {
184 if (IsCriticalException (e)) {
188 Log.LogErrorWithCodeFromResources ("XmlPeekPoke.XPathError", _query, e.Message);
192 // Create the namespace manager and parse the input.
193 XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager (nav.NameTable);
196 LoadNamespaces (ref xmlNamespaceManager, _namespaces);
197 } catch (Exception e) {
198 if (IsCriticalException (e)) {
202 Log.LogErrorWithCodeFromResources ("XmlPeek.NamespacesError", e.Message);
207 expr.SetContext (xmlNamespaceManager);
208 } catch (XPathException e) {
209 Log.LogErrorWithCodeFromResources ("XmlPeek.XPathContextError", e.Message);
213 XPathNodeIterator iter = nav.Select (expr);
215 List<string> peekValues = new List<string> ();
216 while (iter.MoveNext ()) {
217 if (iter.Current.NodeType == XPathNodeType.Attribute
218 || iter.Current.NodeType == XPathNodeType.Text) {
219 peekValues.Add (iter.Current.Value);
221 peekValues.Add (iter.Current.OuterXml);
225 _result = new ITaskItem[peekValues.Count];
227 foreach (string item in peekValues) {
228 _result [i++] = new TaskItem (item);
230 // This can be logged a lot, so low importance
231 Log.LogMessageFromResources (MessageImportance.Low, "XmlPeek.Found", item);
234 if (_result.Length == 0) {
235 // Logged no more than once per execute of this task
236 Log.LogMessageFromResources ("XmlPeek.NotFound");
243 /// Loads the namespaces specified at Namespaces parameter to XmlNSManager.
245 /// <param name="namespaceManager">The namespace manager to load namespaces to.</param>
246 /// <param name="namepaces">The namespaces as XML snippet.</param>
247 private void LoadNamespaces (ref XmlNamespaceManager namespaceManager, string namepaces)
249 XmlDocument doc = new XmlDocument ();
251 XmlReaderSettings settings = new XmlReaderSettings ();
252 settings.DtdProcessing = DtdProcessing.Ignore;
254 using (XmlReader reader = XmlReader.Create (new StringReader ("<Namespaces>" + namepaces + "</Namespaces>"), settings)) {
257 } catch (XmlException xe) {
258 throw new ArgumentException ("The specified Namespaces attribute is not a well-formed XML fragment.", xe);
261 XmlNodeList xnl = doc.SelectNodes ("/Namespaces/*[local-name() = 'Namespace']");
262 for (int i = 0; i < xnl.Count; i++) {
263 XmlNode xn = xnl [i];
265 if (xn.Attributes ["Prefix"] == null) {
266 throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Name"));
269 if (xn.Attributes ["Uri"] == null) {
270 throw new ArgumentException (string.Format ("The specified Namespaces attribute doesn't have attribute \"{0}\".", "Uri"));
273 namespaceManager.AddNamespace (xn.Attributes ["Prefix"].Value, xn.Attributes ["Uri"].Value);
278 /// This class prepares XML input from XMLInputPath and XMLContent parameters
280 internal class XmlInput
283 /// What XML input type are we at.
285 private XmlModes _xmlMode;
288 /// This either contains the raw Xml or the path to Xml file.
290 private string _data;
293 /// Filestream used to read XML.
295 private FileStream _fs;
299 /// Only one parameter should be non null or will throw ArgumentException.
301 /// <param name="xmlInputPath">The path to XML file or null.</param>
302 /// <param name="xmlContent">The raw XML.</param>
303 public XmlInput (ITaskItem xmlInputPath, string xmlContent)
305 if (xmlInputPath != null && xmlContent != null) {
306 throw new ArgumentException ("Only one of XmlContent or XmlInputPaths arguments can be set.");
307 } else if (xmlInputPath == null && xmlContent == null) {
308 throw new ArgumentException ("One of XmlContent or XmlInputPaths arguments must be set.");
311 if (xmlInputPath != null) {
312 _xmlMode = XmlModes.XmlFile;
313 _data = xmlInputPath.ItemSpec;
315 _xmlMode = XmlModes.Xml;
321 /// Possible accepted types of XML input.
326 /// If the mode is a XML file.
331 /// If the mode is a raw XML.
337 /// Returns the current mode of the XmlInput
339 public XmlModes XmlMode {
346 /// Creates correct reader based on the input type.
348 /// <returns>The XmlReader object</returns>
349 public XmlReader CreateReader ()
351 if (_xmlMode == XmlModes.XmlFile) {
352 _fs = new FileStream (_data, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
353 return XmlReader.Create (_fs);
354 } else { // xmlModes.Xml
355 return XmlReader.Create (new StringReader (_data));
360 /// Closes the reader.
362 public void CloseReader ()