Question

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.

Was it helpful?

Solution 2

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.

OTHER TIPS

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top