从 XElements 动态创建列表

Create a List from XElements Dynamically

我正在将一堆 XML 文件读入 XElements 列表(实际上是 IEnumerable)。然后我想将 XElement 列表(这些 XElement 包含一堆子元素)转换为 classes 的列表,以便我可以更轻松地对数据进行后续操作。

现在如果我提前知道XElements的结构,这就很容易了;我只是创建一个模仿 XElement 结构的 class 并用 XElement 内容填充它的实例。但这里有一个警告;我的 XML 文件元素结构大部分相似,但可能存在结构不同的奇数元素。为了更好地说明情况,让我举个例子。

假设我的 XML 文件包含一堆 'Person' 元素。 Person 元素有一些公共元素将出现在所有元素中,但是 Person 的一些子元素只能在某些元素中找到。

例如,所有 Person 元素都有这些强制子元素:

  <Person>
    <Name/>
    <Age/>
    <City/>
    <Country/>
  </Person>

但是,某些 Person 元素可能包含其他子元素,如下所示:

  <Person>
    <Name/>
    <Age/>
    <City/>
    <Country/>
    <EyeColor/>
    <Profession/>
  </Person>

更糟糕的是,这些子元素也可能具有大部分相似的结构,偶尔会有所不同。

那么有没有一种方法可以让我在一个循环中遍历这些 XElements,并将它们放入一个以某种方式动态创建的实例中,比如说,基于元素名称或类似的东西?我可以创建一个包含所有必需元素的 class 并为奇怪的新元素留下一些额外的成员变量,但这并不理想,原因有二:第一,这会浪费 space,第二,我的 class 中的子元素可能比我的额外变量更多。

所以我正在寻找一种动态创建 class 实例以适应 XElement 结构的方法。换句话说,我真的很想模仿元素结构一直到最深层次。

提前致谢!

我个人认为最好的方法是获得一个 XSD,如果您无法获得它,则构建一个具有所有可能性的可序列化 class,然后引用它。 EG:您有两个字段,其中一个有时设置,一个您从未见过设置,但规范中可能会发生某处。

所以让我们来伪装一下class:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace GenericTesting.Models
{
  [Serializable()]
  public class Location
  {                                                                                
    [XmlAttribute()]
    public int Id { get; set; }
    [XmlAttribute()]
    public double PercentUsed { get; set; }
    [XmlElement]
    public string ExtraGarbage { get; set; }
    [XmlText]
    public string UsedOnceInTheUniverse { get; set; }
  }
}

为了 serializing/deserializing 的目的,让我为那些扩展方法:

using System.IO;        
using System.Xml;
using System.Xml.Serialization;

namespace GenericTesting
{                                   
  static class ExtensionHelper
  { 
    public static string SerializeToXml<T>(this T valueToSerialize)
    {
      dynamic ns = new XmlSerializerNamespaces();
      ns.Add("", "");
      StringWriter sw = new StringWriter();

      using (XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { OmitXmlDeclaration = true }))
      {
        dynamic xmler = new XmlSerializer(valueToSerialize.GetType());
        xmler.Serialize(writer, valueToSerialize, ns);
      }

      return sw.ToString();
    }

    public static T DeserializeXml<T>(this string xmlToDeserialize)
    {
      dynamic serializer = new XmlSerializer(typeof(T));

      using (TextReader reader = new StringReader(xmlToDeserialize))
      {
        return (T)serializer.Deserialize(reader);
      }
    }
  }
}

以及控制台应用程序中的一个简单主入口点:

static void Main(string[] args)
{
  var locations = new List<Location>
    {
      new Location { Id = 1, PercentUsed = 0.5, ExtraGarbage = "really important I'm sure"},
      new Location { Id = 2, PercentUsed = 0.6},
      new Location { Id = 3, PercentUsed = 0.7},
    };

  var serialized = locations.SerializeToXml();

  var deserialized = serialized.DeserializeXml<List<Location>>();

  Console.ReadLine();
}

我知道这不完全是您的要求,但我个人认为 XML 和您打交道的任何第三方都应该至少具有某种类型的规格 sheet 或他们给你的详细信息。否则你正在失去标准。 Xml 不应通过反射或其他方式动态创建,因为如果有的话,它的意思是强制严格键入。

如果您只想枚举 <Person> 的任何子元素并且 xml 相对较小 你可以使用 linq to xml

var listOfElementChildNames = XDocument.Parse(xml).Element("Person")
                                                  .Elements()
                                                  .Select(e => e.Name)
                                                  .ToList();

编辑:

而不是 select .Select(e => e.Name) 我们可以映射到任何 class:

public class Person
{
    public string Name {get;set;}
    public int Age {get;set;}
    public string City {get;set;}
}

var xml = @"<Person>
        <Name>John</Name>
        <Age>25</Age>
        <City>New York</City>
      </Person>";

var people = XDocument.Parse(xml).Elements("Person")
     .Select(p => new Person 
        { 
          Name = p.Element("Name").Value, 
          Age = int.Parse(p.Element("Age").Value),
          City = p.Element("City").Value 
        }).ToList();

首先让我为 VB 道歉,但这就是我所做的。

如果我明白你想要什么,你可以使用字典。我缩短了您的示例以减少强制性项目,但希望您明白了。这是 class 简单地迭代子项并将它们按元素名称添加到字典中的人。

Public Class Person

    Private _dict As New Dictionary(Of String, XElement)
    Public Sub New(persEL As XElement)
        'if the class is intended to modify the original XML
        'use this declaration. 
        Dim aPers As XElement = persEL
        'if the original XML will go away during the class lifetime
        'use this declaration. 
        'Dim aPers As XElement =New XElement( persEL)

        For Each el As XElement In aPers.Elements
            Me._dict.Add(el.Name.LocalName, el)
        Next
    End Sub

    'mandatory children are done like this
    Public Property Name() As String
        Get
            Return Me._dict("Name").Value
        End Get
        Set(ByVal value As String)
            Me._dict("Name").Value = value
        End Set
    End Property

    Public Property Age() As Integer
        Get
            Return CInt(Me._dict("Age").Value)
        End Get
        Set(ByVal value As Integer)
            Me._dict("Age").Value = value.ToString
        End Set
    End Property
    'end mandatory children

    Public Property OtherChildren(key As String) As String
        Get
            Return Me._dict(key).Value
        End Get
        Set(ByVal value As String)
            Me._dict(key).Value = value
        End Set
    End Property

    Public Function HasChild(key As String) As Boolean
        Return Me._dict.ContainsKey(key)
    End Function
End Class

这是一个简单的测试,看看它是如何工作的

    Dim onePersXE As XElement = <Person>
                                    <Name>C</Name>
                                    <Age>22</Age>
                                    <Opt1>optional C1</Opt1>
                                    <Opt2>optional C2</Opt2>
                                </Person>

    Dim onePers As New Person(onePersXE)
    onePers.Name = "new name"
    onePers.Age = 42
    onePers.OtherChildren("Opt1") = "new opt1 value"
    onePers.OtherChildren("Opt2") = "opt 2 has new value"

如您所见,有两个必需元素,在本例中有两个可选子元素。

这是另一个展示人们如何工作的例子

    Dim persons As XElement
    persons = <persons>
                  <Person>
                      <Name>A</Name>
                      <Age>32</Age>
                  </Person>
                  <Person>
                      <Name>B</Name>
                      <Age>42</Age>
                      <Opt1>optional B1</Opt1>
                      <Opt2>optional B2</Opt2>
                  </Person>
              </persons>


    Dim persList As New List(Of Person)
    For Each el As XElement In persons.Elements
        persList.Add(New Person(el))
    Next

希望这至少能给你一些想法。