从 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
希望这至少能给你一些想法。
我正在将一堆 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
希望这至少能给你一些想法。