简体   繁体   中英

How do I use XmlSerializer to handle different namespace versions?

I am using the .NET XmlSerializer class to deserialize GPX files.

There are two versions of the GPX standard:

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

Also, some GPX files do not specify a default namespace:

  • <gpx> ... </gpx>

My code needs to handle all three cases, but I can't work out how to get XmlSerializer to do it.

I am sure there must be a simple solution because this a common scenario, for example KML has the same issue.

I have done something similar to this a few times before, and this might be of use to you if you only have to deal with a small number of namespaces and you know them all beforehand. Create a simple inheritance hierarchy of classes, and add attributes to the different classes for the different namespaces. See the following code sample. If you run this program it gives the output:

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

Here is the code:

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);
                }
            }
        }
    }
}

Here's the solution I came up with before the other suggestions came through:

  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);
    }
  }

This example accepts any namespace, but you could easily make it filter for a specific list of known namespaces.

Oddly enough you can't solve this nicely. Have a look at the deserialize section in this troubleshooting article. Especially where it states:

Only a few error conditions lead to exceptions during the deserialization process. The most common ones are:
•The name of the root element or its namespace did not match the expected name.
...

The workaround I use for this is to set the first namespace, try/catch the deserialize operation and if it fails because of the namespace I try it with the next one. Only if all namespace options fail do I throw the error.

From a really strict point of view you can argue that this behavior is correct since the type you deserialize to should represent a specific schema/namespace and then it doesn't make sense that it should also be able to read data from another schema/namespace. In practice this is utterly annoying though. File extenstion rarely change when versions change so the only way to tell if a .gpx file is v0 or v1 is to read the xml contents but the xmldeserializer won't unless you tell upfront which version it will be.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM