C# 使用XPath在XML中搜索值时减少代码重复

C# 使用XPath在XML中搜索值时减少代码重复,c#,xml,xpath,dictionary,code-duplication,C#,Xml,Xpath,Dictionary,Code Duplication,我正在编写一些代码来解析以下格式的xml文件(为了简单起见被截断) 将会有更多的变数,所以这一部分将是巨大的 有没有办法简化这一点?如果这是C++,我可能会尝试指针,但我知道它们不适合在C语言中使用,所以有类似的方法吗? 我会寻找一些东西,我可以给出一个变量名和XPath的列表,让代码提取所有的值,并将它们加载到变量中的某种循环或其他形式。我想使用XPath,因为我希望该文件的格式可能会定期更改 有什么想法吗 提前谢谢 编辑:我还希望能够修改此数据并将其保存回去。如果有必要,我并不反对保存整个新

我正在编写一些代码来解析以下格式的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()方便地完成的。