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