< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Utilities.XmlHelper
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/XmlHelper.cs
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
96%
Covered lines: 54
Uncovered lines: 2
Coverable lines: 56
Total lines: 189
Line coverage: 96.4%
Branch coverage
95%
Covered branches: 44
Total branches: 46
Branch coverage: 95.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
ToXml(...)100%88100%
IsSimple(...)100%88100%
DictionaryToXml(...)66.66%66100%
EnumerableToXml(...)100%22100%
ObjectToXml(...)100%4480%
ToHashtable(...)100%1818100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/XmlHelper.cs

#LineLine coverage
 1using System.Collections;
 2using System.Reflection;
 3using System.Xml.Linq;
 4
 5namespace Kestrun.Utilities;
 6
 7/// <summary>
 8/// Helpers for converting arbitrary objects into <see cref="XElement"/> instances.
 9/// </summary>
 10public static class XmlHelper
 11{
 112    private static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
 13
 14    /// <summary>
 15    /// Converts an object to an <see cref="XElement"/> with the specified name, handling nulls, primitives, dictionarie
 16    /// </summary>
 17    /// <param name="name">The name of the XML element.</param>
 18    /// <param name="value">The object to convert to XML.</param>
 19    /// <returns>An <see cref="XElement"/> representing the object.</returns>
 20    public static XElement ToXml(string name, object? value)
 21    {
 22        // 1️⃣ null  → <name xsi:nil="true"/>
 3223        if (value is null)
 24        {
 625            return new XElement(name, new XAttribute(xsi + "nil", true));
 26        }
 27
 28        // 2️⃣ Primitive or string → <name>42</name>
 2629        if (IsSimple(value))
 30        {
 2031            return new XElement(name, value);
 32        }
 33
 34        // 3️⃣ IDictionary (generic or non-generic)
 635        if (value is IDictionary dict)
 36        {
 137            return DictionaryToXml(name, dict);
 38        }
 39
 40        // 4️⃣ IEnumerable (lists, arrays, StringValues, etc.)
 541        if (value is IEnumerable enumerable)
 42        {
 143            return EnumerableToXml(name, enumerable);
 44        }
 45
 46        // 5️⃣ Fallback: reflect public instance properties (skip indexers)
 447        return ObjectToXml(name, value);
 48    }
 49
 50    /// <summary>
 51    /// Determines whether the specified object is a simple type (primitive, string, DateTime, Guid, or decimal).
 52    /// </summary>
 53    /// <param name="value">The object to check.</param>
 54    /// <returns><c>true</c> if the object is a simple type; otherwise, <c>false</c>.</returns>
 55    private static bool IsSimple(object value)
 56    {
 2657        var type = value.GetType();
 2658        return type.IsPrimitive || value is string || value is DateTime || value is Guid || value is decimal;
 59    }
 60
 61    /// <summary>
 62    /// Converts a dictionary to an XML element.
 63    /// </summary>
 64    /// <param name="name">The name of the XML element.</param>
 65    /// <param name="dict">The dictionary to convert.</param>
 66    /// <returns>An <see cref="XElement"/> representing the dictionary.</returns>
 67    private static XElement DictionaryToXml(string name, IDictionary dict)
 68    {
 169        var elem = new XElement(name);
 670        foreach (DictionaryEntry entry in dict)
 71        {
 272            var key = entry.Key?.ToString() ?? "Key";
 273            elem.Add(ToXml(key, entry.Value));
 74        }
 175        return elem;
 76    }
 77    /// <summary>
 78    /// Converts an enumerable collection to an XML element.
 79    /// </summary>
 80    /// <param name="name">The name of the XML element.</param>
 81    /// <param name="enumerable">The enumerable collection to convert.</param>
 82    /// <returns>An <see cref="XElement"/> representing the collection.</returns>
 83    private static XElement EnumerableToXml(string name, IEnumerable enumerable)
 84    {
 185        var elem = new XElement(name);
 686        foreach (var item in enumerable)
 87        {
 288            elem.Add(ToXml("Item", item));
 89        }
 190        return elem;
 91    }
 92
 93    /// <summary>
 94    /// Converts an object to an XML element.
 95    /// </summary>
 96    /// <param name="name">The name of the XML element.</param>
 97    /// <param name="value">The object to convert.</param>
 98    /// <returns>An <see cref="XElement"/> representing the object.</returns>
 99    private static XElement ObjectToXml(string name, object value)
 100    {
 4101        var objElem = new XElement(name);
 4102        var type = value.GetType();
 48103        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
 104        {
 20105            if (prop.GetIndexParameters().Length > 0)   // <<—— SKIP INDEXERS
 106            {
 107                continue;
 108            }
 109
 110            object? propVal;
 111            try
 112            {
 20113                propVal = prop.GetValue(value);
 20114            }
 0115            catch
 116            {
 0117                continue; // skip unreadable props
 118            }
 119
 20120            objElem.Add(ToXml(prop.Name, propVal));
 121        }
 122
 4123        return objElem;
 124    }
 125
 126    /// <summary>
 127    /// Converts an <see cref="XElement"/> into a <see cref="Hashtable"/>.
 128    /// Nested elements become nested Hashtables; repeated elements become lists.
 129    /// Attributes are stored as keys prefixed with "@", xsi:nil="true" becomes <c>null</c>.
 130    /// </summary>
 131    /// <param name="element">The XML element to convert.</param>
 132    /// <returns>A Hashtable representation of the XML element.</returns>
 133    public static Hashtable ToHashtable(XElement element)
 134    {
 17135        var table = new Hashtable();
 136
 137        // Handle attributes (as @AttributeName)
 40138        foreach (var attr in element.Attributes())
 139        {
 4140            if (attr.Name.NamespaceName == xsi.NamespaceName && attr.Name.LocalName == "nil" && attr.Value == "true")
 141            {
 2142                return new Hashtable { [element.Name.LocalName] = null! };
 143            }
 2144            table["@" + attr.Name.LocalName] = attr.Value;
 145        }
 146
 147        // If element has no children → treat as value
 15148        if (!element.HasElements)
 149        {
 10150            if (!string.IsNullOrWhiteSpace(element.Value))
 151            {
 9152                table[element.Name.LocalName] = element.Value;
 153            }
 10154            return table;
 155        }
 156
 157        // Otherwise recurse into children
 5158        var childMap = new Hashtable();
 28159        foreach (var child in element.Elements())
 160        {
 9161            var childKey = child.Name.LocalName;
 9162            var childValue = ToHashtable(child);
 163
 9164            if (childMap.ContainsKey(childKey))
 165            {
 166                // Already exists → convert to List (allowing null values)
 3167                if (childMap[childKey] is List<object?> list)
 168                {
 1169                    list.Add(childValue[childKey]);
 170                }
 171                else
 172                {
 2173                    childMap[childKey] = new List<object?>
 2174                    {
 2175                        childMap[childKey],
 2176                        childValue[childKey]
 2177                    };
 178                }
 179            }
 180            else
 181            {
 6182                childMap[childKey] = childValue[childKey];
 183            }
 184        }
 185
 5186        table[element.Name.LocalName] = childMap;
 5187        return table;
 2188    }
 189}