简体   繁体   English

c# XML 序列化:命名空间声明的顺序

[英]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):在我的机器上,我得到以下 xml(我刚刚添加了换行符的一行):

<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.您会看到 xsd 和 xsi 的顺序交换了。 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.我检查了序列化程序的实现,发现顺序是用哈希表确定的,并且没有 XmlSerializerNamespaces 接口来为命名空间实现我自己的序列化程序。

This is the method in XmlSerializationWriter:这是 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?什么会导致哈希图中命名空间的不同顺序?

From msdn:来自 msdn:

The elements are sorted according to the hash value of the key, and each key can exist only once in the collection.元素按照key的hash值进行排序,每个key在集合中只能存在一次。

The hash value for DictionaryEntry (a struct ) is extracted from ValueType.GetHashCode() . DictionaryEntry (一个struct )的哈希值是从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.它可能只是使用默认object实现。

Also from msdn:同样来自 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.哈希码不是永久值。

I've implemented a custom XmlWriter that ensures that the namespaces in the root element are sorted before they're written out:我已经实现了一个自定义的 XmlWriter,以确保根元素中的命名空间在写出之前进行排序:

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

I ended up loading the generated XML document into an XDocument and swapping around the attributes using the "ReplaceAttributes" method:我最终将生成的 XML 文档加载到 XDocument 中并使用“ReplaceAttributes”方法交换属性:

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.它将遵循 IEnumerable 的顺序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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