//****************************************************************************************************** // XmlExtensions.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/23/2003 - J. Ritchie Carroll // Generated original version of source code. // 01/23/2006 - J. Ritchie Carroll // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.Common). // 01/24/2007 - J. Ritchie Carroll // Added GetDataSet method to convert an XML string of data into a DataSet object. // 12/13/2007 - Darrell Zuercher // Edited code comments. // 09/12/2008 - J. Ritchie Carroll // Converted to C# extension functions. // 08/10/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Data; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.Xsl; namespace GSF.Xml { /// /// Defines extension functions related to Xml elements. /// public static class XmlExtensions { /// Gets an XML node from given path, creating the entire path if it does not exist. /// This overload just allows the start of the given XML document by using its root element. /// An to query. /// A xpath query. /// An corresponding to the xpath query. public static XmlNode GetXmlNode(this XmlDocument xmlDoc, string xpath) => xmlDoc.DocumentElement.GetXmlNode(xpath, out bool _); /// Gets an XML node from given path, creating the entire path if it does not exist. /// /// This overload just allows the start of the given XML document by using its root element. /// Note that the parameter will be set to True if any items were added to /// the tree. /// /// An to query. /// A xpath query. /// A value indicating if items were added to the tree. /// An corresponding to the xpath query. public static XmlNode GetXmlNode(this XmlDocument xmlDoc, string xpath, out bool isDirty) => xmlDoc.DocumentElement.GetXmlNode(xpath, out isDirty); /// Gets an XML node from given path, creating the entire path if it does not exist. /// An parent node to query. /// A xpath query. /// An corresponding to the xpath query. public static XmlNode GetXmlNode(this XmlNode parentNode, string xpath) => parentNode.GetXmlNode(xpath, out bool _); /// Gets an XML node from given path, creating the entire path if it does not exist. /// Note that the parameter will be set to True if any items were added to /// the tree. /// An parent node to query. /// A xpath query. /// A value indicating if items were added to the tree. /// An corresponding to the xpath query. public static XmlNode GetXmlNode(this XmlNode parentNode, string xpath, out bool isDirty) { XmlNode node = null; isDirty = false; // Removes any slash prefixes. while (xpath[0] == '/') xpath = xpath.Substring(1); string[] elements = xpath.Split('/'); if (elements.Length == 1) { XmlNodeList nodes = parentNode.SelectNodes(xpath); if (nodes is not null) { if (nodes.Count == 0) { if (parentNode.OwnerDocument is not null) { node = parentNode.OwnerDocument.CreateElement(xpath); parentNode.AppendChild(node); isDirty = true; } } else { node = nodes[0]; } } } else { foreach (string element in elements) { node = GetXmlNode(parentNode, element); parentNode = node; } } return node; } /// Safely gets or sets an XML node's attribute. /// If you get an attribute that does not exist, null will be returned. /// A name of the value to get. /// A to query. /// A value returned for the attribute's value. public static string GetAttributeValue(this XmlNode node, string name) { if (node?.Attributes is null) return null; XmlAttribute attribute = node.Attributes[name]; return attribute?.Value; } /// Safely sets an XML node's attribute. /// If you assign a value to an attribute that does not exist, the attribute will be created. /// A indicating the node name to use. /// An node to operate on. /// A value to set the node attribute's value to. public static void SetAttributeValue(this XmlNode node, string name, string value) { if (node?.Attributes is null) return; XmlAttribute attribute = node.Attributes[name]; if (attribute is null) { // Add the attribute. if (node.OwnerDocument != null) { attribute = node.OwnerDocument.CreateAttribute(name); attribute.Value = value; node.Attributes.Append(attribute); } } else { // Set the attribute value. attribute.Value = value; node.Attributes.SetNamedItem(attribute); } } /// /// Gets a data set object from an XML data set formatted as a String. /// /// XML data string in standard DataSet format. /// A object. public static DataSet GetDataSet(this string xmlData) { DataSet dataSet = new DataSet(); using (XmlTextReader xmlReader = new XmlTextReader(xmlData, XmlNodeType.Document, null)) { xmlReader.ReadOuterXml(); // Reads the outer XML into the Dataset. dataSet.ReadXml(xmlReader); } return dataSet; } /// /// Transforms an XML document using the given XSL template. /// /// The document to be transformed. /// The template that defines how the data should be transformed. /// The result of the transformation. public static string ApplyXSLTransform(this string document, string transform) { using (StringReader documentReader = new StringReader(document)) using (StringReader transformReader = new StringReader(transform)) using (XmlReader xmlDocumentReader = XmlReader.Create(documentReader)) using (XmlReader xmlTransformReader = XmlReader.Create(transformReader)) using (StringWriter resultWriter = new StringWriter()) { XslCompiledTransform compiler = new XslCompiledTransform(); compiler.Load(xmlTransformReader); compiler.Transform(xmlDocumentReader, null, resultWriter); return resultWriter.ToString(); } } /// /// Transforms all elements in the given document matching the given /// using the given /// function to perform the transformation. /// /// The document to be modified. /// The name of the elements to be transformed. /// The function that defines the transformation. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is a targeted extension for an XML document")] public static void TransformAll(this XDocument document, XName name, Func selector) { document .Descendants(name) .ToList() .ForEach(element => element.ReplaceWith(selector(element))); } /// /// Transforms all elements in the given document matching the given /// using the given /// function to perform the transformation. /// /// The document to be modified. /// The name of the elements to be transformed. /// The function that defines the transformation. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is a targeted extension for an XML document")] public static void TransformAll(this XDocument document, XName name, Func selector) { document .Descendants(name) .Select((Element, Index) => new { Element, Index }) .ToList() .ForEach(obj => obj.Element.ReplaceWith(selector(obj.Element, obj.Index))); } /// /// Formats the contents of an XML element and returns the result. /// /// The elements whose contents are to be formatted. /// The formatted contents of the elements. /// /// The format to be used is determined by the type and spec /// attributes of the element. The type attribute should be the fully /// qualified type name of an type. The spec /// attribute defines the format string to be used when formatting the contents. /// If type or spec are not defined or if type resovles to /// a type that is not , the contents are not formatted /// and are returned as-is. /// public static object Format(this XElement element) { Type elementType = Type.GetType((string)element.Attribute("type"), false); string formatString = (string)element.Attribute("spec"); if (elementType is null || formatString is null) return element.Nodes(); if (!typeof(IFormattable).IsAssignableFrom(elementType)) return element.Nodes(); string value = (string)element; IFormattable formattable = (IFormattable)Convert.ChangeType(value, elementType); return new XText(formattable.ToString(formatString, null)); } /// /// Finds all attributes in an enumeration of items that match the specified . /// /// Set of values to search. /// Attribute name to match. /// Matching sets of items and associated attribute values. /// /// When used in conjunction with the extension, allows for expressions like: /// /// var globals = XDocument.Load(configFile) /// .Descendants("SettingsGroup") /// .WhereAttribute("Type").Is("Globals"); /// /// public static IEnumerable> WhereAttribute(this IEnumerable source, string attributeName) => source.Select(element => new Tuple(element, element.Attribute(attributeName)?.Value)) .Where(tuple => !string.IsNullOrWhiteSpace(tuple.Item2)); /// /// Finds all attributes queried from that match . /// /// Attributes queried from extension. /// Attribute value to match. /// Flag that determines if attribute value match is case sensitive. /// items that match . /// /// When used in conjunction with the extension, allows for expressions like: /// /// var globals = XDocument.Load(configFile) /// .Descendants("SettingsGroup") /// .WhereAttribute("Type").Is("Globals"); /// /// public static IEnumerable Is(this IEnumerable> source, string attributeValue, bool ignoreCase = true) => source.Where(tuple => string.Equals(tuple.Item2, attributeValue, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) .Select(tuple => tuple.Item1); } }