// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Xml; namespace Umbraco.Extensions { /// /// Extension methods for xml objects /// public static class XmlExtensions { public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName) { return attributes.Cast().Any(x => x.Name == attributeName); } /// /// Selects a list of XmlNode matching an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The list of XmlNode matching the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNodeList? SelectNodes(this XmlNode source, string expression, IEnumerable? variables) { var av = variables == null ? null : variables.ToArray(); return SelectNodes(source, expression, av); } /// /// Selects a list of XmlNode matching an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The list of XmlNode matching the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable? variables) { var av = variables == null ? null : variables.ToArray(); return SelectNodes(source, expression, av); } /// /// Selects a list of XmlNode matching an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The list of XmlNode matching the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNodeList? SelectNodes(this XmlNode source, string? expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) return source.SelectNodes(expression ?? ""); var iterator = source.CreateNavigator()?.Select(expression ?? "", variables); return XmlNodeListFactory.CreateNodeList(iterator); } /// /// Selects a list of XmlNode matching an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The list of XmlNode matching the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) return source.SelectNodes(expression); var iterator = source.CreateNavigator()?.Select(expression, variables); return XmlNodeListFactory.CreateNodeList(iterator); } /// /// Selects the first XmlNode that matches an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The first XmlNode that matches the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNode? SelectSingleNode(this XmlNode source, string expression, IEnumerable? variables) { var av = variables == null ? null : variables.ToArray(); return SelectSingleNode(source, expression, av); } /// /// Selects the first XmlNode that matches an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The first XmlNode that matches the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable? variables) { var av = variables == null ? null : variables.ToArray(); return SelectSingleNode(source, expression, av); } /// /// Selects the first XmlNode that matches an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The first XmlNode that matches the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNode? SelectSingleNode(this XmlNode source, string expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) return source.SelectSingleNode(expression); return SelectNodes(source, expression, variables)?.Cast().FirstOrDefault(); } /// /// Selects the first XmlNode that matches an XPath expression. /// /// A source XmlNode. /// An XPath expression. /// A set of XPathVariables. /// The first XmlNode that matches the XPath expression. /// /// If is null, or is empty, or contains only one single /// value which itself is null, then variables are ignored. /// The XPath expression should reference variables as $var. /// public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) return source.SelectSingleNode(expression); return SelectNodes(source, expression, variables).Cast().FirstOrDefault(); } /// /// Converts from an XDocument to an XmlDocument /// /// /// public static XmlDocument ToXmlDocument(this XDocument xDocument) { var xmlDocument = new XmlDocument(); using (var xmlReader = xDocument.CreateReader()) { xmlDocument.Load(xmlReader); } return xmlDocument; } /// /// Converts from an XmlDocument to an XDocument /// /// /// public static XDocument ToXDocument(this XmlDocument xmlDocument) { using (var nodeReader = new XmlNodeReader(xmlDocument)) { nodeReader.MoveToContent(); return XDocument.Load(nodeReader); } } ///// ///// Converts from an XElement to an XmlElement ///// ///// ///// public static XmlNode? ToXmlElement(this XContainer xElement) { var xmlDocument = new XmlDocument(); using (var xmlReader = xElement.CreateReader()) { xmlDocument.Load(xmlReader); } return xmlDocument.DocumentElement; } /// /// Converts from an XmlElement to an XElement /// /// /// public static XElement ToXElement(this XmlNode xmlElement) { using (var nodeReader = new XmlNodeReader(xmlElement)) { nodeReader.MoveToContent(); return XElement.Load(nodeReader); } } public static T? RequiredAttributeValue(this XElement xml, string attributeName) { if (xml == null) { throw new ArgumentNullException(nameof(xml)); } if (xml.HasAttributes == false) { throw new InvalidOperationException($"{attributeName} not found in xml"); } XAttribute? attribute = xml.Attribute(attributeName); if (attribute is null) { throw new InvalidOperationException($"{attributeName} not found in xml"); } Attempt result = attribute.Value.TryConvertTo(); if (result.Success.HasValue && result.Success.Value) { return result.Result; } throw new InvalidOperationException($"{attribute.Value} attribute value cannot be converted to {typeof(T)}"); } public static T? AttributeValue(this XElement xml, string attributeName) { if (xml == null) throw new ArgumentNullException("xml"); if (xml.HasAttributes == false) return default(T); if (xml.Attribute(attributeName) == null) return default(T); var val = xml.Attribute(attributeName)?.Value; var result = val.TryConvertTo(); if (result.Success.HasValue && result.Success.Value) return result.Result; return default(T); } public static T? AttributeValue(this XmlNode xml, string attributeName) { if (xml == null) throw new ArgumentNullException("xml"); if (xml.Attributes == null) return default(T); if (xml.Attributes[attributeName] == null) return default(T); var val = xml.Attributes[attributeName]?.Value; var result = val.TryConvertTo(); if (result.Success.HasValue && result.Success.Value) return result.Result; return default(T); } public static XElement? GetXElement(this XmlNode node) { XDocument xDoc = new XDocument(); using (XmlWriter xmlWriter = xDoc.CreateWriter()) node.WriteTo(xmlWriter); return xDoc.Root; } public static XmlNode? GetXmlNode(this XContainer element) { using (var xmlReader = element.CreateReader()) { var xmlDoc = new XmlDocument(); xmlDoc.Load(xmlReader); return xmlDoc.DocumentElement; } } public static XmlNode? GetXmlNode(this XContainer element, XmlDocument xmlDoc) { var node = element.GetXmlNode(); if (node is not null) { return xmlDoc.ImportNode(node, true); } return null; } // this exists because // new XElement("root", "a\nb").Value is "a\nb" but // .ToString(SaveOptions.*) is "a\r\nb" and cannot figure out how to get rid of "\r" // and when saving data we want nothing to change // this method will produce a string that respects the \r and \n in the data value public static string ToDataString(this XElement xml) { var settings = new XmlWriterSettings { OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.None, Indent = false }; var output = new StringBuilder(); using (var writer = XmlWriter.Create(output, settings)) { xml.WriteTo(writer); } return output.ToString(); } } }