0

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

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

假设我的 XML 文件包含一堆“Person”元素。Person 元素有一些共同的元素,它们将在所有元素中,但有一些 Person 的子元素只能在某些元素中找到。

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

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

但是,一些 Person 元素可能包含额外的子元素,如下所示:

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

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

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

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

提前致谢!

4

3 回答 3

1

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

如果我明白你想要什么,你可以使用字典。我缩短了您的示例以减少强制性项目,但希望您能理解。这是一个简单的迭代子类的 person 类,通过它们的元素名称将它们添加到字典中。

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

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

于 2017-02-01T11:18:34.930 回答
1

我个人认为最好的途径是获得一个 XSD,如果你不能获得它,那么组成一个具有所有可能性的可序列化类,然后引用它。EG:你有两个领域,一个是有时设置的,一个是你从未见过的,但在规范中的某个地方可能会发生这种情况。

所以让我们组成一个假装类:

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

为了序列化/反序列化的目的,让我为这些提供扩展方法:

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 更好,并且您曾经处理过的任何第三方都应该至少有某种类型的规格表或关于他们给您的内容的详细信息。否则你正在失去标准。Xml 不应该通过反射或其他方式动态创建,因为它意味着如果有任何东西强制执行严格的类型(如果有的话)。

于 2017-01-30T23:24:08.527 回答
1

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

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

编辑:

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

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

映射结果

于 2017-01-30T23:30:04.927 回答