【问题标题】:How to deserialize xml elements that have different names, but the same set of attributes to a typed array/collection如何将具有不同名称但具有相同属性集的xml元素反序列化为类型化数组/集合
【发布时间】:2015-09-03 19:47:41
【问题描述】:

这是我试图反序列化的 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 名称的所有算法都无法使用。

【问题讨论】:

  • 只使用这个表单的属性,我不这么认为。 Parameter 需要一个名称,它不能反序列化具有多个名称的元素。每个参数类型都需要一个类。如果必须采用这种形式,请考虑实现IXmlSerializable
  • @Charles Mager > 每个参数类型都需要一个类" 是的,这将是主要问题,但由于这些参数是由用户创建的,我无法考虑所有这些,所以这个策略不行。另一方面,实现IXmlSerializable 真的很烦人,因为默认情况下,如果我离开 XmlAttribute/XmlText 属性,它不会填充“已知”元素,事件。你知道如何避免这种行为吗?我的意思是NameDataTypeUnits 等不会自动填充,大概我也必须从 ReadXml 方法初始化它们:(
  • ReadXml() 中,您可以加载到XElement 并使用嵌套的XmlSerializer 反序列化,如下所示:stackoverflow.com/questions/28769495/…
  • 你的根元素&lt;entry&gt; 必须有几个这样的命名空间,对吧? &lt;entry xmlns="http://www.w3.org/2005/Atom" xmlns:A="urn:schemas-autodesk-com:partatom"&gt;

标签: c# xml serialization deserialization xml-deserialization


【解决方案1】:

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

假设您的 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 更容易。

【讨论】:

    【解决方案2】:

    一种(丑陋的)方法是在反序列化之前修改 XML。只需将所有“Voltage”、“Unit_height”等替换为“YourParameterClassName”即可。然后他们应该都很好地反序列化,我猜你的“typeOfParameter”仍然会为你提供与替换之前节点中最初存在的相同的信息。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 好的,我必须更新我的问题,告诉我没有所有参数(电压、Unit_Height 等)的枚举。实际上它们是无限的。所以正如我已经说过的“我无法考虑所有这些,所以这个策略是行不通的”。
      • 你对xml的结构有控制权吗?
      • 如果“控制”是指将数据写入 XML 的方式 - 不,我没有那种控制。此 XML 由 Autodesk Revit 应用程序生成,大部分参数都是手工制作的。有一种方法可以通过 Revit API 从应用程序内部读取它们,但我需要从“外部”读取它们,所以我必须编写自己的反序列化器。
      • 那么预处理对我来说似乎是最有效的方法。是否可以在反序列化之前将&lt;A:part type="user"&gt;的所有子代重命名为MyFancyElement
      • 这正是@Culme 所建议的。无论如何,part 标签的第一个子元素是title,可能还有其他子元素,因此确定元素是否为参数的唯一方法是它是否具有某些属性。此外,有时displayName 属性为空,因此我必须改用 XMLElement 名称。我最终得到了一个半自动解析解决方案。我将尝试使它更加优雅,并将其发布在这里作为答案。希望有人能提出更好的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-14
      相关资源
      最近更新 更多