|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
#nullable disable
namespace Microsoft.Build.Tasks
{
/// <summary>
/// A task that sets values as specified by XPath Query
/// into a XML file.
/// </summary>
public class XmlPoke : TaskExtension
{
#region Properties
/// <summary>
/// The XML input as file path.
/// </summary>
[Required]
public ITaskItem XmlInputPath { get; set; }
/// <summary>
/// The XPath Query.
/// </summary>
[Required]
public string Query { get; set; }
/// <summary>
/// The value to be inserted into the specified location.
/// </summary>
public ITaskItem Value { get; set; }
/// <summary>
/// The namespaces for XPath query's prefixes.
/// </summary>
public string Namespaces { get; set; }
#endregion
/// <summary>
/// Executes the XMLPoke task.
/// </summary>
/// <returns>true if task execution succeeds.</returns>
public override bool Execute()
{
if (Value == null)
{
// When Value is null, it means Value is not set or empty. Here we treat them all as empty.
Value = new TaskItem(string.Empty);
}
// Load the XPath Document
XmlDocument xmlDoc = new XmlDocument();
try
{
using (FileStream fs = new FileStream(XmlInputPath.ItemSpec, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
XmlReaderSettings xrs = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
using (XmlReader sr = XmlReader.Create(fs, xrs))
{
xmlDoc.Load(sr);
}
}
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
Log.LogErrorWithCodeFromResources("XmlPeekPoke.InputFileError", XmlInputPath.ItemSpec, e.Message);
return false;
}
XPathNavigator nav = xmlDoc.CreateNavigator();
XPathExpression expr;
try
{
// Create the expression from query
expr = nav.Compile(Query);
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
Log.LogErrorWithCodeFromResources("XmlPeekPoke.XPathError", Query, e.Message);
return false;
}
// Create the namespace manager and parse the input.
var xmlNamespaceManager = new XmlNamespaceManager(nav.NameTable);
// Arguments parameters
try
{
LoadNamespaces(ref xmlNamespaceManager, Namespaces);
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
Log.LogErrorWithCodeFromResources("XmlPoke.NamespacesError", e.Message);
return false;
}
try
{
expr.SetContext(xmlNamespaceManager);
}
catch (XPathException e)
{
Log.LogErrorWithCodeFromResources("XmlPoke.XPathContextError", e.Message);
return false;
}
XPathNodeIterator iter = nav.Select(expr);
int count = 0;
while (iter.MoveNext())
{
try
{
count++;
iter.Current.InnerXml = Value.ItemSpec;
Log.LogMessageFromResources(MessageImportance.Low, "XmlPoke.Replaced", iter.Current.Name, Value.ItemSpec);
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
Log.LogErrorWithCodeFromResources("XmlPoke.PokeError", Value.ItemSpec, e.Message);
return false;
}
}
Log.LogMessageFromResources(MessageImportance.Normal, "XmlPoke.Count", count);
if (count > 0)
{
#if RUNTIME_TYPE_NETCORE
using (Stream stream = File.Create(XmlInputPath.ItemSpec))
{
xmlDoc.Save(stream);
}
#else
xmlDoc.Save(XmlInputPath.ItemSpec);
#endif
}
return true;
}
/// <summary>
/// Loads the namespaces specified at Namespaces parameter to XmlNSManager.
/// </summary>
/// <param name="namespaceManager">The namespace manager to load namespaces to.</param>
/// <param name="namepaces">The namespaces as XML snippet.</param>
private static void LoadNamespaces(ref XmlNamespaceManager namespaceManager, string namepaces)
{
var doc = new XmlDocument();
try
{
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
using (XmlReader reader = XmlReader.Create(new StringReader("<Namespaces>" + namepaces + "</Namespaces>"), settings))
{
doc.Load(reader);
}
}
catch (XmlException xe)
{
throw new ArgumentException(ResourceUtilities.GetResourceString("XmlPoke.NamespacesParameterNotWellFormed"), xe);
}
XmlNodeList xnl = doc.SelectNodes("/Namespaces/*[local-name() = 'Namespace']");
for (int i = 0; i < xnl?.Count; i++)
{
XmlNode xn = xnl[i];
const string prefixAttr = "Prefix";
XmlAttribute prefix = xn.Attributes?[prefixAttr];
if (prefix == null)
{
throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("XmlPoke.NamespacesParameterNoAttribute", prefixAttr));
}
const string uriAttr = "Uri";
XmlAttribute uri = xn.Attributes[uriAttr];
if (uri == null)
{
throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("XmlPoke.NamespacesParameterNoAttribute", uriAttr));
}
namespaceManager.AddNamespace(prefix.Value, uri.Value);
}
}
}
}
|