Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# XML Serialization: Order of namespace declarations

I have a very odd situation. I serialize my namespaces like this:

var namespaces = new XmlSerializerNamespaces();
namespaces.Add("xsd", "http://www.w3.org/2001/XMLSchema");
namespaces.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");

serializer.Serialize(writer, config, namespaces);

On my machine I get the following xml (one line I just added linebreaks):

<SystemConfiguration 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns="http://www.schaeffler.com/sara/systemconfiguration/">

On the buildserver I get with the same software this:

<SystemConfiguration 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns="http://www.schaeffler.com/sara/systemconfiguration/">

You see the order of xsd and xsi is swapped. I checked the implementation of the serializer and saw that the order is determined with a hashtable and there is not interface for XmlSerializerNamespaces to implement my own serializer for the namespaces.

This is the method in XmlSerializationWriter:

protected void WriteNamespaceDeclarations(XmlSerializerNamespaces xmlns)
    {
      if (xmlns != null)
      {
        foreach (DictionaryEntry dictionaryEntry in xmlns.Namespaces)
        {
          string localName = (string) dictionaryEntry.Key;
          string ns = (string) dictionaryEntry.Value;
          if (this.namespaces != null)
          {
            string str = this.namespaces.Namespaces[(object) localName] as string;
            if (str != null && str != ns)
              throw new InvalidOperationException(Res.GetString("XmlDuplicateNs", (object) localName, (object) ns));
          }
          string str1 = ns == null || ns.Length == 0 ? (string) null : this.Writer.LookupPrefix(ns);
          if (str1 == null || str1 != localName)
            this.WriteAttribute("xmlns", localName, (string) null, ns);
        }
      }
      this.namespaces = (XmlSerializerNamespaces) null;
    }

What can cause the different order of the namespaces within the hashmap?

like image 233
schoetbi Avatar asked Dec 20 '13 10:12

schoetbi


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.


3 Answers

I ended up loading the generated XML document into an XDocument and swapping around the attributes using the "ReplaceAttributes" method:

var xDoc = XDocument.Load(ms);
    
var originalAttributes = xDoc.Root.Attributes();
var orderedAttributes = originalAttributes.OrderBy(a => a.Name.ToString());

xDoc.Root.ReplaceAttributes(orderedAttributes);

In my example I ordered the attributes alphabetically but you can swap the attributes around the way you want. It will follow the order of the IEnumerable.

like image 178
breez Avatar answered Oct 26 '22 23:10

breez


I've implemented a custom XmlWriter that ensures that the namespaces in the root element are sorted before they're written out:

/// <summary>
/// XmlWriter that ensures the namespace declarations in the root element are always sorted.
/// </summary>
class SortedNamespaceXmlWriter : XmlWriter
{
    private readonly XmlWriter _baseWriter;
    private readonly List<(string prefix, string uri)> _namespaces = new List<(string prefix, string uri)>();
    private int _elementIndex;
    private string _nsPrefix;
    private bool _inXmlNsAttribute;


    public SortedNamespaceXmlWriter(XmlWriter baseWriter) => _baseWriter = baseWriter;


    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        _elementIndex++;
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        if (prefix == "xmlns")
        {
            _inXmlNsAttribute = true;
            _nsPrefix = localName;
        }
        else
        {
            FlushRootElementAttributesIfNeeded();
            _baseWriter.WriteStartAttribute(prefix, localName, ns);
        }
    }

    public override void WriteEndAttribute()
    {
        if (_inXmlNsAttribute)
            _inXmlNsAttribute = false;
        else
            _baseWriter.WriteEndAttribute();
    }

    public override void WriteString(string text)
    {
        if (_inXmlNsAttribute)
            _namespaces.Add((_nsPrefix, text));
        else
            _baseWriter.WriteString(text);
    }

    private void FlushRootElementAttributesIfNeeded()
    {
        if (_elementIndex != 1 || _namespaces.Count == 0)
            return;

        _namespaces.Sort((a, b) => StringComparer.Ordinal.Compare(a.prefix, b.prefix));

        foreach (var (prefix, uri) in _namespaces)
        {
            _baseWriter.WriteStartAttribute("xmlns", prefix, null);
            _baseWriter.WriteString(uri);
            _baseWriter.WriteEndAttribute();
        }

        _namespaces.Clear();
    }


    public override WriteState WriteState => _baseWriter.WriteState;

    public override void Flush() => _baseWriter.Flush();

    public override string LookupPrefix(string ns) => _baseWriter.LookupPrefix(ns);

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset) => _baseWriter.WriteDocType(name, pubid, sysid, subset);

    public override void WriteEndDocument() => _baseWriter.WriteEndDocument();

    public override void WriteEndElement()
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteRaw(string data)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteRaw(data);
    }

    public override void WriteStartDocument() => _baseWriter.WriteStartDocument();

    public override void WriteStartDocument(bool standalone) => _baseWriter.WriteStartDocument();

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteWhitespace(ws);
    }
}
like image 25
John Hall Avatar answered Oct 26 '22 21:10

John Hall


From msdn:

The elements are sorted according to the hash value of the key, and each key can exist only once in the collection.

The hash value for DictionaryEntry (a struct) is extracted from ValueType.GetHashCode(). It is likely returning an in-determinable key - potentially based upon the underlying reference value. You would need to do some further reflection to find out for certain how the hash is being calculated. It may just be using the default object implementation.

Also from msdn:

A hash code is intended for efficient insertion and lookup in collections that are based on a hash table. A hash code is not a permanent value.

like image 23
Gusdor Avatar answered Oct 26 '22 21:10

Gusdor