| File: XmlPoke.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// 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> [MSBuildMultiThreadableTask] public class XmlPoke : TaskExtension, IMultiThreadableTask { #region Properties /// <inheritdoc /> public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback; /// <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; } /// <summary> /// Prohibits loading XML with embedded DTD. When true (default), an error is raised if a DTD is present. /// When false, DTDs are ignored. /// </summary> public bool ProhibitDtd { get; set; } = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_11); #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(); AbsolutePath inputPath = default; try { inputPath = TaskEnvironment.GetAbsolutePath(XmlInputPath.ItemSpec); using (FileStream fs = new FileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var xrs = new XmlReaderSettings { DtdProcessing = ProhibitDtd ? DtdProcessing.Prohibit : DtdProcessing.Ignore }; using (XmlReader sr = XmlReader.Create(fs, xrs)) { xmlDoc.Load(sr); } } } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { if (XmlTaskUtility.IsDtdProhibitedException(e, ProhibitDtd, () => XmlTaskUtility.ContainsDtd(inputPath))) { Log.LogErrorWithCodeFromResources("XmlPeekPoke.InputDtdProhibited", nameof(ProhibitDtd), nameof(XmlPoke)); } else { 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(inputPath)) { xmlDoc.Save(stream); } #else xmlDoc.Save(inputPath); #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); } } } }