Created
August 24, 2018 02:06
-
-
Save ScottKaye/39aa5f067ae3ba5b1e7e51b7478126ea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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