如何将具有不同名称但同一组属性的 xml 元素反序列化为类型化数组/集合

本文关键字:xml 元素 属性 一组 反序列化 数组 类型化 集合 | 更新日期: 2023-09-27 17:56:49

这是我尝试反序列化的XML文件的一部分:

<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>

这些是我用来反序列化它的类:

[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; }
}

我想要电压、Units_Length、Unit_Height等元素,...Apparent_Load等要反序列化为参数类的实例。我怎样才能完成这样的事情?甚至可能吗?

更新:

参数(标题下方的 XML 元素)实际上是无限的,所以我无法考虑所有这些参数,因此我必须手动指定 XmlElement 名称的所有算法都无法使用。

如何将具有不同名称但同一组属性的 xml 元素反序列化为类型化数组/集合

如果您不想在类上实现IXmlSerializable,一种选择是添加标有 [XmlAnyElement] XElement []元素的代理属性,并序列化此列表之间的参数,并根据需要修复名称。

假设您的 XML 在根元素上具有命名空间声明,如下所示,这些问题中省略了这些声明:

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

那么以下内容应该有效:

[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");
        }
    }
}

请注意,序列化程序会自动反序列化 TitleType 属性,而不是传递给 AnyElement 数组。 然后,在 Parameter 中,我将Name更改为 DisplayName 并添加了一个 ElementName 属性来保存元素名称:

[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; }
}

使用扩展和帮助程序方法:

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);
    }
}

不过,比自定义IXmlSerializable容易。

一个解决方案可能是多个指定了不同名称的XmlElementAttribute。像这样:

 [XmlElement("Voltage")]
 [XmlElement("Unit_Height")]
 [XmlElement("Supply_Air_Height")]
 [XmlElement("CFU_Material")]

一种(丑陋的)方法可能是在反序列化 XML 之前修改它。只需将所有"电压","Unit_height"等替换为"您的参数类名称"即可。然后它们都应该很好地反序列化,我猜你的"typeOfParameter"仍然会为你提供替换之前最初存在于节点中的相同信息。