Skip to content

Instantly share code, notes, and snippets.

@nathan130200
Created August 26, 2024 20:41
Show Gist options
  • Save nathan130200/a0e0d00096c1492178e1865fe321d3ea to your computer and use it in GitHub Desktop.
Save nathan130200/a0e0d00096c1492178e1865fe321d3ea to your computer and use it in GitHub Desktop.
C# helper methods & types to manage XML Linq Elements in .NET
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Jabber;
public static partial class Xml
{
[GeneratedRegex(@"(?<tagName>.*)\[@xmlns\=\'(?<namespaceURI>.*)\'\]")]
internal static partial Regex GetElementByNamespace();
[GeneratedRegex(@"(?<tagName>.*)\[(?<fromEnd>\^)?(?<elementIndex>[0-9+]+)\]")]
internal static partial Regex GetElementByIndex();
[GeneratedRegex(@"(?<tagName>.*)\[@(?<attrName>.*)=['""](?<attrVal>.*)['""]\]")]
internal static partial Regex GetElementByAttribute();
public static XElement Root(this XElement child)
{
while (child.Parent != null)
child = child.Parent;
return child;
}
public static XElement? XPathQueryForElement(this XElement element, string expression)
{
if (expression.StartsWith('/'))
{
element = element.Root();
expression = expression[1..];
}
var args = expression.Split('/');
if (args.Length == 0)
return null;
XElement? temp = element;
foreach (var arg in args)
{
// indicate we have "complex" filter here.
if (arg.Contains('[') && arg.Contains(']'))
{
// find by namespace filter: [@xmlns=value]
if (GetElementByNamespace().TryMatch(arg, out var xmlnsMatch))
{
string tagName = xmlnsMatch.Groups["tagName"].Value;
string namespaceURI = xmlnsMatch.Groups["namespaceURI"].Value;
temp = temp.Elements().FirstOrDefault(x =>
{
return x.GetTagName() == tagName
&& x.GetNamespace() == namespaceURI;
});
if (temp != null)
continue;
}
//find by: element[index]
if (GetElementByIndex().TryMatch(arg, out var indexMatch))
{
var tagName = indexMatch.Groups["tagName"].Value;
var elementIndex = int.Parse(indexMatch.Groups["elementIndex"].Value);
// using new Index operator syntax from C#. if char '^' is found at start, indicate the index is from end.
var isFromEnd = indexMatch.Groups["fromEnd"].Value == "^";
var allElements = temp.Elements();
var count = allElements.Count();
// should we clamp index from count?
// or leave LINQ thrown exception if out of range?
// .Count() is expensive method call?
temp = allElements.ElementAtOrDefault(isFromEnd
? Index.FromEnd(elementIndex % count)
: Index.FromStart(elementIndex % count));
if (temp != null)
continue;
}
// find by: [@attribute=value]
if (GetElementByAttribute().TryMatch(arg, out var byAttributeMatch))
{
var tagName = byAttributeMatch.Groups["tagName"].Value;
var attrName = byAttributeMatch.Groups["attrName"].Value;
var attrVal = byAttributeMatch.Groups["attrVal"].Value;
var sub = temp.Elements().FirstOrDefault(x =>
{
var attr = x.Attributes().FirstOrDefault(a =>
a.GetName() == attrName && a.Value == attrVal
);
return attr != null;
});
if (sub != null)
{
temp = sub;
continue;
}
}
// not found, abort
return null;
}
else
{
// find just by qualified XML element name.
temp = temp.Elements().FirstOrDefault(x => x.GetTagName() == arg);
if (temp == null)
break;
}
}
return temp;
}
// A simple hacky to execute regex match inplace.
static bool TryMatch(this Regex r, string term, [NotNullWhen(true)] out Match? result)
{
try
{
result = r.Match(term);
return result.Success;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
result = null;
return false;
}
}
// A simple hacky to get qualified name of the XML element (used in our XPath query parser)
public static string GetTagName(this XElement element)
{
ArgumentNullException.ThrowIfNull(element);
var localName = element.Name.LocalName;
var namespaceURI = element.Name.Namespace;
var prefix = element.GetPrefixOfNamespace(namespaceURI);
if (prefix == null)
return localName;
return string.Concat(prefix, ':', localName);
}
// A simple hacky to get qualified name of the XML attribute (used in our XPath query parser)
public static string GetName(this XAttribute attribute)
{
ArgumentNullException.ThrowIfNull(attribute);
var localName = attribute.Name.LocalName;
var namespaceURI = attribute.Name.Namespace;
if (namespaceURI == XNamespace.Xml)
return $"xml:{localName}";
else if (namespaceURI == XNamespace.Xmlns)
return $"xmlns:{localName}";
else
{
var prefix = attribute.Parent?.GetPrefixOfNamespace(namespaceURI);
if (prefix == null)
return localName;
return string.Concat(prefix, ':', localName);
}
}
// Simple way to wrap namespace from XNamespace to our own custom Namespace type.
// I need to do this shit because XNamespace don't have implicit operators for conversion between XNamespace + string,
// so if i need to set XNamespace as attribute i need MANUALLY call XNamespace.NamespaceName EVERY FUCKING TIME to get namespace URI.
public static Namespace GetNamespace(this XElement element, string? prefix = default)
{
if (prefix != null)
{
var result = element.GetNamespaceOfPrefix(prefix);
if (result == null)
return Namespace.None;
return result;
}
var rawPrefix = element.GetPrefix();
if (rawPrefix != null)
return element.GetDefaultNamespace();
var ns = element.Name.Namespace;
if (ns == null || ns == XNamespace.None)
return Namespace.None;
return ns;
}
// Fast way to get start tag from XElement (useful in XMPP / stream start)
public static string StartTag(this XElement element)
{
ArgumentNullException.ThrowIfNull(element);
var sb = new StringBuilder($"<{element.GetTagName()}");
foreach (var attr in element.Attributes())
sb.Append($" {attr.GetName()}=\"{attr.Value}\"");
return sb.Append('>').ToString();
}
// Fast way to get end tag from XElement (useful in XMPP / stream end)
public static string EndTag(this XElement element)
{
ArgumentNullException.ThrowIfNull(element);
return string.Concat("</", element.GetTagName(), '>');
}
public static void SetAttribute(this XElement element, string name, object value, string? namespaceURI = default)
{
var attributeName = BuildXName(element, name, namespaceURI, false);
var attr = element.Attribute(attributeName);
if (value == null)
attr?.Remove();
else
{
if (attr != null)
attr.SetValue(value);
else
{
element.Add(new XAttribute(attributeName, value));
}
}
}
public static string? GetAttribute(this XElement element, string name, string? defaultValue = default, string? namespaceURI = default)
=> element.Attribute(BuildXName(element, name, namespaceURI))?.Value ?? defaultValue;
// Dynamic way to parse attribute values with fallback value if attribute is absent or cannot be parsed.
[return: NotNullIfNotNull(nameof(defaultValue))]
public static T GetAttribute<T>(this XElement element, string name, T defaultValue = default!, string? namespaceURI = default) where T : IParsable<T>
{
var str = element.GetAttribute(name, namespaceURI: namespaceURI);
// An hacky way to fast parse bool from string/int form.
if (typeof(T) == typeof(bool))
{
object? temp = default;
if (str == "1" || str == "true")
temp = true;
else if (str == "0" || str == "false")
temp = false;
if (temp != null)
return (T)temp;
}
if (T.TryParse(str, CultureInfo.InvariantCulture, out var result))
return result;
return defaultValue;
}
// Dynamic way to parse attribute values as nullable primitives.
public static T? GetAttribute<T>(this XElement element, string name, string? namespaceURI = default) where T : struct, IParsable<T>
{
var str = element.GetAttribute(name, namespaceURI: namespaceURI);
// Yet again the hacky way to fast parse bool from string/int form.
if (typeof(T) == typeof(bool))
{
object? temp = default;
if (str == "1" || str == "true")
temp = true;
else if (str == "0" || str == "false")
temp = false;
if (temp != null)
return (T)temp;
}
if (T.TryParse(str, CultureInfo.InvariantCulture, out var result))
return result;
return default;
}
// Helper method to extract namespace prefix from this element.
public static string? GetPrefix(this XElement element)
=> element.GetPrefixOfNamespace(element.Name.Namespace);
// To be honest this is a terrible way to inherit XML namespaces between parent and children XML elements. But will work for now.
static XName BuildXName(XElement parent, string qualifiedName, string? namespaceURI = default, bool inherited = true)
{
var ofs = qualifiedName.IndexOf(':');
if (ofs == -1)
{
if (namespaceURI == null)
{
// When this namespace can inherits? Who decide this?
if (inherited)
return parent.Name.Namespace + qualifiedName;
return parent.GetDefaultNamespace() + qualifiedName;
}
return (XNamespace)namespaceURI + qualifiedName;
}
else
{
var prefix = qualifiedName[0..ofs];
var localName = qualifiedName[(ofs + 1)..];
if (prefix == parent.GetPrefix())
return parent.Name.Namespace + localName;
else
{
// Thanks .NET for screw up with XML Namespace declarations in XElement.
if (prefix == "xml")
return XNamespace.Xml + localName;
else if (prefix == "xmlns")
return XNamespace.Xmlns + localName;
else
{
if (namespaceURI != null)
return (XNamespace)namespaceURI + localName;
var ns = parent.GetNamespaceOfPrefix(prefix);
if (ns != null)
return ns + localName;
// Should inherit namespace too? Or leave as empty? Thanks again .NET for this mess!
return localName;
}
}
}
}
public static XElement C(this XElement parent, string tagName, bool inheritNamespace = true)
{
var result = new XElement(BuildXName(parent, tagName, inherited: inheritNamespace));
parent.Add(result);
return result;
}
public static XElement C(this XElement parent, XName name)
{
var result = new XElement(name);
parent.Add(result);
return result;
}
public static XElement C(this XElement parent, string tagName, string? namespaceURI)
{
var result = new XElement(BuildXName(parent, tagName, namespaceURI, false));
parent.Add(result);
return result;
}
public static XElement? GetChild(this XElement element, string tagName, string? namespaceURI = default)
{
return element.Element(BuildXName(element, tagName, namespaceURI));
}
public static bool GetChild(this XElement element, string tagName, [NotNullWhen(true)] out XElement? result, string? namespaceURI = default)
{
result = element.Element(BuildXName(element, tagName, namespaceURI));
return result != null;
}
public static IEnumerable<XElement> GetChildren(this XElement element, string tagName, string? namespaceURI = default)
{
return element.Elements(BuildXName(element, tagName, namespaceURI));
}
}
public readonly struct Namespace
{
private readonly string _value;
private readonly XNamespace _namespaceName;
Namespace(XNamespace other)
{
_value = other.NamespaceName;
_namespaceName = other;
}
Namespace(string value)
{
_value = value;
_namespaceName = XNamespace.Get(value);
}
public XAttribute GetNamespaceDeclaration()
=> new("xmlns", _value);
public XAttribute GetNamespaceDeclaration(string prefix)
=> new(Xmlns + prefix, _value);
// ------------------------------------------------------------------------------------------------------------------------ //
public static readonly Namespace None = new(XNamespace.None);
public static readonly Namespace Xml = new(XNamespace.Xml);
public static readonly Namespace Xmlns = new(XNamespace.Xmlns);
// ------------------------------------------------------------------------------------------------------------------------ //
public static readonly Namespace Stream = new("http://etherx.jabber.org/streams");
public static readonly Namespace Client = new("jabber:client");
public static readonly Namespace Server = new("jabber:server");
public static class Component
{
public static readonly Namespace Connect = new("jabber:component:connect");
public static readonly Namespace Accept = new("jabber:component:accept");
}
public static readonly Namespace CryOnline = new("urn:cryonline:k01");
// ------------------------------------------------------------------------------------------------------------------------ //
public override int GetHashCode() => _namespaceName.GetHashCode();
public override string ToString() => _value;
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Namespace other
&& _value.Equals(other._value, StringComparison.Ordinal);
}
// ------------------------------------------------------------------------------------------------------------------------ //
public static implicit operator string(Namespace self) => self._value;
public static implicit operator Namespace(string str) => new(str);
public static implicit operator Namespace(XNamespace ns) => new(ns);
// ------------------------------------------------------------------------------------------------------------------------ //
public static XName operator +(Namespace self, string other)
{
return self._namespaceName + other;
}
public static bool operator ==(Namespace left, Namespace right)
{
return left.Equals(right);
}
public static bool operator !=(Namespace left, Namespace right)
{
return !(left == right);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment