使用XPath在XML中搜索值时减少代码重复
本文关键字:代码 XPath XML 搜索 使用 | 更新日期: 2023-09-27 18:26:55
我正在编写一些代码来解析以下格式的xml文件(为简化起见,已截断)
<?xml version="1.0" encoding="UTF-8"?>
<ship name="Foo">
<base_type>Foo</base_type>
<GFX>fooGFX</GFX>
....
</ship>
我使用了一个由参数和和xpath-to-value的键对组成的字典,并通过查询将所有值加载到各种变量中。然而,我注意到将会有大量的代码重复。事实上,对于我想要检索的每一个值,我都必须编写另一行看起来几乎相同的代码。这是我的代码:
class Ship
{
public Ship()
{
paths = new Dictionary<string, string>();
doc = new XmlDocument();
//Define path to various elements
paths.Add("name", "/ship/@name");
paths.Add("base_type", "/ship/base_type");
paths.Add("GFX", "/ship/GFX");
}
public void LoadFile(string filename)
{// Loads the file and grabs the parameters
doc.Load(filename);
Name = doc.SelectSingleNode(paths["name"]).Value;
Base_type = doc.SelectSingleNode(paths["base_type"]).Value;
GFX = doc.SelectSingleNode(paths["GFX"]).Value;
}
public Dictionary<string, string> paths; //The XPaths to the various elements, define them in constructor
public XmlDocument doc;
public string Name;
public string Base_type;
public string GFX;
}
注意这里的重复:
variable = doc.SelectSingleNode(paths["variable_name"]).value.
将会有更多的变量,所以这一部分将是巨大的。
有什么可以简化的吗?如果这是C++,我可能会尝试指针,但我知道它们不建议在C#中使用,所以有类似的方法吗?
我会寻找一些东西,我可以给出一个变量名和xpath的列表,让代码提取所有值,并在某种循环或其他方式中加载到变量中。我想使用XPaths,因为我预计这个文件的格式可能会定期更改。
有什么想法吗?
提前谢谢。
编辑:我也希望能够修改这些数据并将其保存回来。如果必要的话,我并不反对保存一整棵新树,但如果可能的话,修改数据会很好。我不需要修改的解决方案,但我只需要打开这个选项。
实现对象填充的一个好方法是使用XmlSerializer.Deserialize()。
类似这样的东西:
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;
}
}
更新:OP增加了一个额外的问题:
我还希望能够修改这些数据并将其保存回。我如果必要的话,我不反对保存一整棵新树
只需使用XmlSerializer.Serialize()
方法。
以下是使用它的一个典型示例:
// Create an XmlTextWriter using a FileStream.
Stream fs = new FileStream(filename, FileMode.Create);
XmlWriter writer =
new XmlTextWriter(fs, Encoding.Unicode);
// Serialize using the XmlTextWriter.
serializer.Serialize(writer, yourObject);
writer.Close();
这对你来说可能听起来很奇怪,但如果你想要大量的数据,那么就我个人而言,我会选择自动生成的代码解决方案。
含义:将所有的XML名称->映射到它们的XPath表达式->以显示名称,并具有自动为您生成数据类的功能。
例如:
假设输入文件为CSV格式(或制表符分隔等):
DisplayName、XMLField、XPathExpr
姓名/船名/@Name
Base_type、Base_type、/ship/Base_type
GFX、GFX、/船/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 ...
}
您可以继续并添加序列化、INotifyPropertyChanged支持以及其他您认为合适的装饰。
不同的方法
将使用反射将数据加载到属性中,但为此,您需要为每个数据成员以及数据映射手动创建一个属性。
这里有一个例子:
LoadData(xmlDoc, "Name", "/ship/@name");
LoadData(xmlDoc, "Base_type", "/ship/base_type");
LoadData(xmlDoc, "GFX", "/ship/GFX");
其中LoadData()
类似于:
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);
}
}
}
- 请注意,我忽略了您的
paths
字典,因为我看不到它的任何特殊角色
一种方法是定义自己的自定义属性类型以将属性映射到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
替换为目标对象),将如下所示:
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);
}
}