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

时间:2012-03-04 20:53:12

标签: c# xml xpath dictionary code-duplication

我正在编写一些代码来解析以下格式的xml文件(为简单起见而截断)

<?xml version="1.0" encoding="UTF-8"?>
<ship name="Foo">
 <base_type>Foo</base_type>
 <GFX>fooGFX</GFX>
 ....
</ship>

我正在使用一个由参数和xpath值键组成的字典,并查询它以将所有值加载到各种变量中。但是,我注意到会有大量的代码重复。实际上,对于我想要检索的每个值,我将不得不编写另一行几乎相同的代码。这是我的代码:

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列表的东西,并让代码拉出所有值并将它们加载到变量中的某种循环或其他东西。我想使用XPath,因为我希望这个文件的格式可能会定期更改。

有什么想法吗?

提前致谢。

编辑:我也希望能够修改这些数据并将其保存回来。如果有必要,我不会保存整个新树,但如果可能的话,修改数据会很好。我不需要修改的解决方案,但我只需要打开这个选项。

3 个答案:

答案 0 :(得分:2)

实现对象填充的一个好方法是使用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();

答案 1 :(得分:0)

这对您来说可能听起来很奇怪,但如果您要获取大量数据,那么就个人而言,我会选择自动生成的代码解决方案。

含义:映射所有XML名称 - &gt;他们的XPath表达式 - &gt;显示名称,并为您自动生成数据类。

例如:

假设输入文件是CSV格式(或制表符分隔符等):

<强> DisplayName的,XMLField,XPathExpr
名称,名称/船/ @名称
Base_type,base_type,/船/ 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词典,因为我没有看到任何特殊的角色。

答案 2 :(得分:0)

一种方法是定义自己的custom attribute type,用于将属性映射到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);
    }
}