C# 使用XPath在XML中搜索值时减少代码重复
我正在编写一些代码来解析以下格式的xml文件(为了简单起见被截断) 将会有更多的变数,所以这一部分将是巨大的 有没有办法简化这一点?如果这是C++,我可能会尝试指针,但我知道它们不适合在C语言中使用,所以有类似的方法吗? 我会寻找一些东西,我可以给出一个变量名和XPath的列表,让代码提取所有的值,并将它们加载到变量中的某种循环或其他形式。我想使用XPath,因为我希望该文件的格式可能会定期更改 有什么想法吗 提前谢谢C# 使用XPath在XML中搜索值时减少代码重复,c#,xml,xpath,dictionary,code-duplication,C#,Xml,Xpath,Dictionary,Code Duplication,我正在编写一些代码来解析以下格式的xml文件(为了简单起见被截断) 将会有更多的变数,所以这一部分将是巨大的 有没有办法简化这一点?如果这是C++,我可能会尝试指针,但我知道它们不适合在C语言中使用,所以有类似的方法吗? 我会寻找一些东西,我可以给出一个变量名和XPath的列表,让代码提取所有的值,并将它们加载到变量中的某种循环或其他形式。我想使用XPath,因为我希望该文件的格式可能会定期更改 有什么想法吗 提前谢谢 编辑:我还希望能够修改此数据并将其保存回去。如果有必要,我并不反对保存整个新
编辑:我还希望能够修改此数据并将其保存回去。如果有必要,我并不反对保存整个新树,但是如果可能的话,在适当的位置修改数据会更好。我不需要修改的解决方案,但我只需要打开此选项。这听起来可能有点奇怪,但如果您需要大量数据,那么我个人会选择自动生成代码的解决方案 意思是:将所有XML名称->映射到它们的XPath表达式->以显示名称,并具有自动为您生成数据类的功能 例如:
[MapTo("/ship/@name")]
public string Name { get; set; }
[MapTo("/ship/base_type")]
public string BaseType { get; set; }
假设输入文件为CSV格式(或制表符分隔,等等):
DisplayName,XMLField,XPathExpr
Name,Name,/ship/@Name
基本类型,基本类型,/船舶/基本类型
GFX,GFX,/ship/GFX 现在,您需要一些自动生成代码的过程。为此,您可以开发一个C#实用程序,使用一些脚本语言(如Perl),甚至创建一些XSL翻译 最终结果将类似于:
class AutoGeneratedShipData
{
public AutoGeneratedShipData(XmlDocument xmlDoc)
{
// Code initialization like in your sample
}
public string Name ...
public string Base_type ...
public string GFX ...
}
private void LoadData(XmlDocument xmlDoc, Dictionary<string, string> propertyNameToXPathMap)
{
foreach ( PropertyInfo pi in this.GetType().GetProperties() )
{
// Is the property mapped to an xpath?
if ( propertyNameToXPathMap.ContainsKey(pi.Name) )
{
string sPathExpression = propertyNameToXPathMap[pi.Name];
// Extract the Property's value from XML based on the xpath expr.
string value = xmlDoc.SelectSingleNode(sPathExpression).Value;
// Set this object's property's value
pi.SetValue(this, value, null);
}
}
}
您可以继续并添加序列化、INotifyPropertyChanged支持以及其他您认为合适的装饰
另一种方法
使用反射将数据加载到属性中,但为此,您需要为每个数据成员以及数据映射手动创建属性
下面是一个例子:
LoadData(xmlDoc, "Name", "/ship/@name");
LoadData(xmlDoc, "Base_type", "/ship/base_type");
LoadData(xmlDoc, "GFX", "/ship/GFX");
其中LoadData()
类似于:
class AutoGeneratedShipData
{
public AutoGeneratedShipData(XmlDocument xmlDoc)
{
// Code initialization like in your sample
}
public string Name ...
public string Base_type ...
public string GFX ...
}
private void LoadData(XmlDocument xmlDoc, Dictionary<string, string> propertyNameToXPathMap)
{
foreach ( PropertyInfo pi in this.GetType().GetProperties() )
{
// Is the property mapped to an xpath?
if ( propertyNameToXPathMap.ContainsKey(pi.Name) )
{
string sPathExpression = propertyNameToXPathMap[pi.Name];
// Extract the Property's value from XML based on the xpath expr.
string value = xmlDoc.SelectSingleNode(sPathExpression).Value;
// Set this object's property's value
pi.SetValue(this, value, null);
}
}
}
private void LoadData(XmlDocument xmlDoc,Dictionary propertyNameToXPathMap)
{
foreach(PropertyInfo pi在此.GetType().GetProperties()中)
{
//属性是否映射到xpath?
if(propertyNameToXPathMap.ContainsKey(pi.Name))
{
字符串sPathExpression=propertyNameToXPathMap[pi.Name];
//基于xpath表达式从XML中提取属性值。
字符串值=xmlDoc.SelectSingleNode(sPathExpression).value;
//设置此对象的属性值
pi.SetValue(this,value,null);
}
}
}
- 请注意,我忽略了您的
字典,因为我没有看到它的任何特殊角色路径
namespace TestSerialization
{
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class TestSerialization
{
static void Main(string[] args)
{
string theXml =
@"<ship name='Foo'>
<base_type>Foo</base_type>
<GFX>fooGFX</GFX>
</ship>";
Ship s = Ship.Create(theXml);
// Write out the properties of the object.
Console.Write(s.Name + "\t" + s.GFX);
}
}
[XmlRoot("ship")]
public class Ship
{
public Ship() { }
public static Ship Create(string xmlText)
{
// Create an instance of the XmlSerializer specifying type.
XmlSerializer serializer = new XmlSerializer(typeof(Ship));
StringReader sr = new StringReader(xmlText);
XmlReader xreader = new XmlTextReader(sr);
// Use the Deserialize method to restore the object's state.
return (Ship)serializer.Deserialize(xreader);
}
[XmlAttribute("name")]
public string Name;
[XmlElement("base_type")]
public string Base_type;
public string GFX;
}
}
foreach (var property in this.GetType().GetProperties())
{
var mapToAttribute = property.GetCustomAttributes(typeof(MapToAttribute), false).SingleOrDefault() as MapToAttribute;
if (mapToAttribute != null)
{
property.SetValue(this, doc.SelectSingleNode(mapToAttribute.XPathSelector).Value);
}
}
一种方法是定义自己的属性到XPath选择器的映射,为需要映射到XPath选择器的变量定义自动属性,并使用自定义属性修饰这些属性。例如:
[MapTo("/ship/@name")]
public string Name { get; set; }
[MapTo("/ship/base_type")]
public string BaseType { get; set; }
然后在加载XML文档后,编写一个循环,使用反射迭代这些属性中的每一个,并根据它们关联的XPath选择器设置它们的值。例如,假设自定义属性是这样声明的:
[AttributeUsage(AttributeTargets.Property)]
public class MapToAttribute : Attribute
{
public string XPathSelector { get; private set; }
public MapToAttribute(string xPathSelector)
{
XPathSelector = xPathSelector;
}
}
然后,假设执行映射的循环位于类中保存映射属性的实例方法的某个地方(如果不是,则将this
替换为目标对象),则该循环如下所示:
namespace TestSerialization
{
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class TestSerialization
{
static void Main(string[] args)
{
string theXml =
@"<ship name='Foo'>
<base_type>Foo</base_type>
<GFX>fooGFX</GFX>
</ship>";
Ship s = Ship.Create(theXml);
// Write out the properties of the object.
Console.Write(s.Name + "\t" + s.GFX);
}
}
[XmlRoot("ship")]
public class Ship
{
public Ship() { }
public static Ship Create(string xmlText)
{
// Create an instance of the XmlSerializer specifying type.
XmlSerializer serializer = new XmlSerializer(typeof(Ship));
StringReader sr = new StringReader(xmlText);
XmlReader xreader = new XmlTextReader(sr);
// Use the Deserialize method to restore the object's state.
return (Ship)serializer.Deserialize(xreader);
}
[XmlAttribute("name")]
public string Name;
[XmlElement("base_type")]
public string Base_type;
public string GFX;
}
}
foreach (var property in this.GetType().GetProperties())
{
var mapToAttribute = property.GetCustomAttributes(typeof(MapToAttribute), false).SingleOrDefault() as MapToAttribute;
if (mapToAttribute != null)
{
property.SetValue(this, doc.SelectSingleNode(mapToAttribute.XPathSelector).Value);
}
}
好主意。嗯,所以你建议我写一个程序来自动编写代码,这样我就可以在必要时修改它。你是说没有办法存储变量和XPath的引用列表,并让它按照我的要求检索它?是的,有什么感觉舒服的吗。我添加了更多代码,以防您想在
Ship
类本身上使用反射。然而,就我个人而言,我仍然会将Ship
的状态封装在一个数据类中,这个数据类很容易序列化、通过通信层传递等等。。你的评论完全变了。。我没说那是不可能的。我还添加了一个关于如何使用反射的示例。如果每个类都有一个封闭的XML,那么您还可以为它生成一个XSD模式,并使用Xsd2Code从该模式自动生成一个类。我很想使用模式路径,但我没有VS 2010,只有express edition。关于如何轻松创建模式,有什么建议吗?另外,我想补充一点,我需要能够修改树,并在以后将其保存回去。我将更新这个问题。您可以在预构建步骤中从命令行使用XSD2代码。添加用于创建Xml序列化的标志,您将拥有自动生成的Serialize()/Deserialize()方法(从/到字符串和从/到文件)。您能告诉我这在Xml文件的上下文中是如何工作的吗?@Biosci3c:是的,我用完整的解决方案更新了我的答案。@ssg,是的,我相信是这样。这只需要很少的编码,非常容易使用,功能也非常强大。很抱歉回复太晚,但我真的认为我会走这条路,因为这似乎比疯狂解析要容易得多。但有一点让我困惑,括号内的[XmlRoot(“ship”)]语法,因为我以前从未见过它。你能解释一下吗(例如,它是一个模板、一个类等)?谢谢。@Biosci3c:不客气。是的,请让我知道这在实践中是如何工作的。最后一部分——将倒角保存回去——是使用XmlSerializer类的另一个主要方法——Serialize()方便地完成的。