Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
562 views
in Technique[技术] by (71.8m points)

c# - How to deserialize xml elements that have different names, but the same set of attributes to a typed array/collection

This is a part of XML file I'm trying to de-serialize:

<entry>
...
<A:family type="user">
 <A:variationCount>7</A:variationCount>
 <A:part type="user">
    <title>94 LPS</title>
    <Voltage type="custom" typeOfParameter="Electrical Potential" units="V">120 V</Voltage>
    <Unit_Length displayName="Unit Length" type="custom" typeOfParameter="Length" units="mm">540</Unit_Length>
    <Unit_Height displayName="Unit Height" type="custom" typeOfParameter="Length" units="mm">222</Unit_Height>
    <Total_Cooling_Capacity displayName="Total Cooling Capacity" type="custom" typeOfParameter="Power" units="W">1758 W</Total_Cooling_Capacity>
    <Supply_Air_Width displayName="Supply Air Width" type="custom" typeOfParameter="Duct Size" units="mm">400 mm</Supply_Air_Width>
    <Supply_Air_Height displayName="Supply Air Height" type="custom" typeOfParameter="Duct Size" units="mm">150 mm</Supply_Air_Height>
    <Sensible_Cooling_Capacity displayName="Sensible Cooling Capacity" type="custom" typeOfParameter="Power" units="W">1348 W</Sensible_Cooling_Capacity>
    <Return_Air_Width displayName="Return Air Width" type="custom" typeOfParameter="Duct Size" units="mm">475 mm</Return_Air_Width>
    <Number_of_Poles displayName="Number of Poles" type="custom" typeOfParameter="Number of Poles">1</Number_of_Poles>
    <Load_Classification displayName="Load Classification" type="custom" typeOfParameter="Load Classification">Cooling</Load_Classification>
    <CFU_Material displayName="CFU Material" type="custom" typeOfParameter="Material">&lt;By Category&gt;</CFU_Material>
    <C2_Offset_1 displayName="C2 Offset 1" type="custom" typeOfParameter="Length" units="mm">108</C2_Offset_1>
    <C1_Offset_1 displayName="C1 Offset 1" type="custom" typeOfParameter="Length" units="mm">108</C1_Offset_1>
    <Apparent_Load displayName="Apparent Load" type="custom" typeOfParameter="Apparent Power" units="VA">50 VA</Apparent_Load>
 </A:part>
 <A:part type="user">
    ...
 </A:part>
 ...
</A:family>
</entry>

These are classes I'm using to de-serialize it:

[XmlType(AnonymousType = true)]
[XmlRoot("entry", Namespace="http://www.w3.org/2005/Atom")]
public class PartAtom
{
    ...
    [XmlElement("family", Namespace="urn:schemas-autodesk-com:partatom")]
    public Family Family { get; set; }
}

public class Family
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("variationCount")]
    public int VariationCount { get; set; }

    [XmlElement("part")]
    public FamilyType[] Parts { get; set; }
}

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public class FamilyType 
{   
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlArray]
    public Parameter[] Parameters { get; set; }
}

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public struct Parameter
{
    [XmlAttribute("displayName")]
    public string Name { get; set; }

    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlAttribute("typeOfParameter")]
    public string DataType { get; set; }

    [XmlText]
    public string Value { get; set; }

    [XmlAttribute("units")]
    public string Units { get; set; }
}

I want the elements such as Voltage, Units_Length, Unit_Height, ... Apparent_Load etc to be de-serialized as instances of Parameters class. How can I accomplish something like that? Is it even possible?

UPDATE:

The parameters (XML elements below the title) are virtually infinite, so I can't contemplate all of them, so all the algorithms where I have to specify XmlElement names manually are unusable.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you don't want to implement IXmlSerializable on your class, one option would be to add a proxy property of XElement [] elements marked with [XmlAnyElement] and serialize the parameters from and to this list, fixing the names as required.

Assuming your XML has namespace declarations on the root element like the following that were omitted from the question:

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:A="urn:schemas-autodesk-com:partatom">

Then the following should work:

[XmlType(Namespace = "http://www.w3.org/2005/Atom")]
public class FamilyType
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlIgnore]
    public Parameter[] Parameters { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement[] XmlParameters
    {
        get
        {
            return XmlKeyValueListHelper.SerializeAttributeNameValueList(Parameters, "name");
        }
        set
        {
            Parameters = XmlKeyValueListHelper.DeserializeAttributeNameValueList<Parameter>(value, "name");
        }
    }
}

Note that the serializer automatically deserializes the Title and Type properties rather than passing then to the AnyElement array. Then, in Parameter, I changed Name to DisplayName and added an ElementName property to hold the element name:

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public struct Parameter
{
    [XmlAttribute("name")]
    public string ElementName { get; set; } // Added property.

    [XmlAttribute("displayName")]
    public string DisplayName { get; set; } // Changed from Name to DisplayName

    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlAttribute("typeOfParameter")]
    public string DataType { get; set; }

    [XmlText]
    public string Value { get; set; }

    [XmlAttribute("units")]
    public string Units { get; set; }
}

Using the extension and helper methods:

public static class XmlKeyValueListHelper
{
    public static XElement[] SerializeAttributeNameValueList<T>(IEnumerable<T> items, string nameAttributeName)
    {
        if (items == null)
            return null;
        var ns = new XmlSerializerNamespaces();
        ns.Add("", typeof(T).RootXmlElementNamespace());
        var query = items
            .Select(p => p.SerializeToXElement(ns))
            .Select(e =>
            {
                var attr = e.Attribute(nameAttributeName);
                e.Name = e.Name.Namespace + XmlConvert.EncodeLocalName((string)attr);
                attr.Remove();
                return e;
            });
        return query.ToArray();
    }

    public static T[] DeserializeAttributeNameValueList<T>(IEnumerable<XElement> elements, string nameAttributeName)
    {
        if (elements == null)
            return null;
        var query = elements
            .Select(e => new XElement(e)) // Do not modify the input values.
            .Select(e =>
            {
                e.Add(new XAttribute(nameAttributeName, XmlConvert.DecodeName(e.Name.LocalName)));
                e.Name = e.Name.Namespace + typeof(T).RootXmlElementName();
                return e;
            })
            .Select(e => e.Deserialize<T>());
        return query.ToArray();
    }
}

public static class XmlTypeExtensions
{
    public static string RootXmlElementName(this Type type)
    {
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
            return xmlRoot.ElementName;
        return type.Name;
    }

    public static string RootXmlElementNamespace(this Type type)
    {
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
            return xmlRoot.Namespace;
        return string.Empty;
    }
}

public static class XObjectExtensions
{
    static XmlSerializerNamespaces NoStandardXmlNamespaces()
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        return ns;
    }

    public static XElement SerializeToXElement<T>(this T obj)
    {
        return obj.SerializeToXElement(null, NoStandardXmlNamespaces());
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns)
    {
        return obj.SerializeToXElement(null, ns);
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
            (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }

    public static T Deserialize<T>(this XContainer element)
    {
        return element.Deserialize<T>(new XmlSerializer(typeof(T)));
    }

    public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            object result = serializer.Deserialize(reader);
            if (result is T)
                return (T)result;
        }
        return default(T);
    }
}

Still, easier than a custom IXmlSerializable.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...