动态创建XElements列表

时间:2017-01-30 22:57:37

标签: c# xml xelement dynamic-class-creation

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

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

让我们说我的XML文件包含一堆“人物”。元素。 Person元素有一些共同的元素将存在于所有元素中,但是有一些Person的子元素只能在一些元素中找到。

例如,所有Person元素都有这些必需的子元素:

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

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

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

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

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

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

提前致谢!

3 个答案:

答案 0 :(得分: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,因为它意味着如果有什么要强制执行严格的输入。

答案 1 :(得分:1)

如果你想枚举<Person>的任何子元素,xml相对较小 你可以使用linq到xml

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

修改

而不是select .Select(e =&gt; 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();

Mapping result

答案 2 :(得分: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

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