如何使用XmlSerializer处理不同的命名空间版本?

时间:2012-03-26 09:36:26

标签: c# xml xml-serialization kml gpx

我正在使用.NET XmlSerializer类来反序列化GPX文件。

GPX标准有两个版本:

  • < gpx xmlns =“http://www.topografix.com/GPX/1/0”> ......< / gpx>
  • < gpx xmlns =“http://www.topografix.com/GPX/1/1”> ......< / gpx>

此外,某些GPX文件未指定默认命名空间:

  • < GPX> ......< / gpx>

我的代码需要处理所有三种情况,但我无法弄清楚如何让XmlSerializer这样做。

我确信必须有一个简单的解决方案,因为这是一个常见的场景,例如KML有同样的问题。

3 个答案:

答案 0 :(得分:5)

我之前做了几次类似的事情,如果你只需要处理少量的命名空间并且事先就知道它们,这可能对你有用。创建一个简单的类继承层次结构,并为不同的名称空间的不同类添加属性。请参阅以下代码示例。如果你运行这个程序,它会给出输出:

Deserialized, type=XmlSerializerExample.GpxV1, data=1
Deserialized, type=XmlSerializerExample.GpxV2, data=2
Deserialized, type=XmlSerializerExample.Gpx, data=3

以下是代码:

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

[XmlRoot("gpx")]
public class Gpx {
        [XmlElement("data")] public int Data;
}

[XmlRoot("gpx", Namespace = "http://www.topografix.com/GPX/1/0")]
public class GpxV1 : Gpx {}

[XmlRoot("gpx", Namespace = "http://www.topografix.com/GPX/1/1")]
public class GpxV2 : Gpx {}

internal class Program {
    private static void Main() {
        var xmlExamples = new[] {
            "<gpx xmlns='http://www.topografix.com/GPX/1/0'><data>1</data></gpx>",
            "<gpx xmlns='http://www.topografix.com/GPX/1/1'><data>2</data></gpx>",
            "<gpx><data>3</data></gpx>",
        };

        var serializers = new[] {
            new XmlSerializer(typeof (Gpx)),
            new XmlSerializer(typeof (GpxV1)),
            new XmlSerializer(typeof (GpxV2)),
        };

        foreach (var xml in xmlExamples) {
            var textReader = new StringReader(xml);
            var xmlReader = XmlReader.Create(textReader);

            foreach (var serializer in serializers) {
                if (serializer.CanDeserialize(xmlReader)) {
                    var gpx = (Gpx)serializer.Deserialize(xmlReader);
                    Console.WriteLine("Deserialized, type={0}, data={1}", gpx.GetType(), gpx.Data);
                }
            }
        }
    }
}

答案 1 :(得分:3)

这是我在提出其他建议之前提出的解决方案:

  var settings = new XmlReaderSettings();
  settings.IgnoreComments = true;
  settings.IgnoreProcessingInstructions = true;
  settings.IgnoreWhitespace = true;
  using (var reader = XmlReader.Create(filePath, settings))
  {
    if (reader.IsStartElement("gpx"))
    {
      string defaultNamespace = reader["xmlns"];
      XmlSerializer serializer = new XmlSerializer(typeof(Gpx), defaultNamespace);
      gpx = (Gpx)serializer.Deserialize(reader);
    }
  }

此示例接受任何名称空间,但您可以轻松地对已知名称空间的特定列表进行过滤。

答案 2 :(得分:1)

奇怪的是,你不能很好地解决这个问题。请查看this问题排查文章中的反序列化部分。特别是它声明:

  

只有少数错误条件会导致异常   反序列化过程。最常见的是:
  •名称   根元素或其名称空间与预期名称不匹配   ...

我使用的解决方法是设置第一个命名空间,尝试/捕获反序列化操作,如果由于命名空间而失败,我会尝试使用下一个命名空间。只有当所有命名空间选项都失败时,我才会抛出错误。

从一个非常严格的角度来看,你可以说这种行为是正确的,因为你反序列化的类型应该代表一个特定的模式/命名空间然后它也应该能够从另一个读取数据是没有意义的架构/命名空间。在实践中,这完全令人讨厌。当版本更改时文件扩展很少改变,因此判断.gpx文件是v0还是v1的唯一方法是读取xml内容,但xmldeserializer不会,除非你事先告诉它将是哪个版本。