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