Skip to content

Instantly share code, notes, and snippets.

@ScottKaye
Created August 24, 2018 02:06
Show Gist options
  • Save ScottKaye/39aa5f067ae3ba5b1e7e51b7478126ea to your computer and use it in GitHub Desktop.
Save ScottKaye/39aa5f067ae3ba5b1e7e51b7478126ea to your computer and use it in GitHub Desktop.
void Main()
{
foreach (var file in Directory.GetFiles(@"X:\Scott\Projects\Test XML Files", "*.xml"))
{
var doc = XDocument.Load(file);
var expando = XmlToDict.Convert(doc.Root.Elements(), new LibertyTransformer());
expando.Dump(20);
JsonConvert.SerializeObject(expando, Newtonsoft.Json.Formatting.Indented).Dump();
}
}
public static class XmlToDict
{
public static ExpandoObject Convert(XElement element)
=> Convert(element, new NoopTransformer());
public static ExpandoObject Convert(IEnumerable<XElement> elements)
=> Convert(elements, new NoopTransformer());
public static ExpandoObject Convert(XElement element, ExpandoTransformer transformer)
{
if (transformer == null)
{
throw new ArgumentNullException(nameof(transformer));
}
var expando = new ExpandoObject();
var dict = (IDictionary<string, object>)expando;
dict[transformer.TransformElementName(element.Name.LocalName)] = ConvertChildren(element, transformer);
return expando;
}
public static ExpandoObject Convert(IEnumerable<XElement> elements, ExpandoTransformer transformer)
{
if (transformer == null)
{
throw new ArgumentNullException(nameof(transformer));
}
var expando = new ExpandoObject();
var dict = (IDictionary<string, object>)expando;
foreach (var element in elements)
{
dict[transformer.TransformElementName(element.Name.LocalName)] = ConvertChildren(element, transformer);
}
return expando;
}
private static ExpandoObject ConvertChildren(XElement element, ExpandoTransformer transformer)
{
var expando = new ExpandoObject();
var dict = (IDictionary<string, object>)expando;
// Copy in each attribute of the current element
foreach (var attr in element.Attributes())
{
var attributeName = transformer.TransformAttributeName(GetRawAttributeName(attr));
if (attributeName != null)
{
// Force @ before attribute name to prevent collisions
dict["@" + attributeName] = transformer.TransformValue(attributeName, attr.Value);
}
}
// If the element contains more elements, recursively add each of those
if (element.HasElements)
{
foreach (var innerElement in element.Elements())
{
var innerKey = transformer.TransformElementName(innerElement.Name.LocalName);
if (innerKey != null)
{
// Add every item to an list of ExpandoObjects, to avoid instances where one item would map to a literal value and another maps to a list.
// Now, everything is a list, and each item can contain different attributes/values/whatever!
if (dict.ContainsKey(innerKey))
{
((List<ExpandoObject>)dict[innerKey]).Add(ConvertChildren(innerElement, transformer));
}
else
{
dict[innerKey] = new List<ExpandoObject>
{
ConvertChildren(innerElement, transformer)
};
}
}
}
}
// Add the value directly if one exists
else if (element.Value.Length > 0)
{
dict["Value"] = transformer.TransformValue(element.Name.LocalName, element.Value);
}
return expando;
}
private static string GetRawAttributeName(XAttribute attr)
{
// xmlns attributes are returned as "xmlns", but attribute such as "xmlns:ns" are returned with the W3 namespace URI.
// This is not intuitive, so map it back to the raw value in the XML.
if (attr.IsNamespaceDeclaration)
{
if (string.IsNullOrEmpty(attr.Name.NamespaceName))
{
return attr.Name.LocalName;
}
else
{
return $"xmlns:{attr.Name.LocalName}";
}
}
return attr.Name.LocalName;
}
public abstract class ExpandoTransformer
{
/// <summary>Transform an attribute name. Return null to remove the attribute. Will always be prefixed with "@" to avoid collisions such as <Value Value="attr">tag</Value> serializing only one "Value" item with a content of "tag".</summary>
public abstract string TransformAttributeName(string attributeName);
/// <summary>Transform an element name. Return null to remove the element.</summary>
public abstract string TransformElementName(string key);
/// <summary>Transform an element or attribute value.</summary>
public abstract object TransformValue(string key, string value);
}
/// <summary>Does nothing to its inputs.</summary>
private sealed class NoopTransformer : ExpandoTransformer
{
public override string TransformAttributeName(string attributeName) => attributeName;
public override string TransformElementName(string key) => key;
public override object TransformValue(string key, string value) => value;
}
/// <summary>Simple convention-based value mapper that converts basic values into corresponding basic types.</summary>
public class ConventionValueTransformer : ExpandoTransformer
{
public override string TransformAttributeName(string attributeName) => attributeName;
public override string TransformElementName(string key) => key;
public override object TransformValue(string key, string value)
{
if (key.EndsWith("date", StringComparison.OrdinalIgnoreCase) && DateTime.TryParse(value, out var parsedDate))
{
return parsedDate;
}
if (ulong.TryParse(value, out var parsedLong))
{
return parsedLong;
}
if (decimal.TryParse(value, out var parsedDecimal))
{
return parsedDecimal;
}
return value;
}
}
}
public class LibertyTransformer : XmlToDict.ConventionValueTransformer
{
private static readonly string[] _removeElements = new[]
{
"HTTP_HEADERS"
};
private static readonly string[] _removeAttributes = new[]
{
"xmlns",
"xmlns:les"
};
public override string TransformAttributeName(string attributeName)
{
if (_removeAttributes.Contains(attributeName))
{
return null;
}
return attributeName;
}
public override string TransformElementName(string key)
{
// Normalize request body values into one all-encompassing "Body" value to reduce duplication when indexing
if (key.EndsWith("Rq") || key.EndsWith("Rs"))
{
return "Body";
}
if (_removeElements.Contains(key))
{
return null;
}
return key;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment