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

时间:2013-12-20 10:41:25

标签: c# xml serialization

我的情况非常奇怪。我像这样序列化我的命名空间:

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

在我的机器上,我得到以下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/">

在构建服务器上,我使用相同的软件:

<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/">

您会看到xsd和xsi的顺序已交换。我检查了序列化程序的实现,发现顺序是用散列表确定的,并且XmlSerializerNamespaces没有接口为命名空间实现我自己的序列化程序。

这是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;
    }

什么可能导致hashmap中命名空间的不同顺序?

3 个答案:

答案 0 :(得分:3)

来自msdn:

  

元素根据键的哈希值排序,并且   每个密钥只能在集合中存在一次。

DictionaryEntry中提取struct(a ValueType.GetHashCode())的哈希值。它可能返回一个不可确定的密钥 - 可能基于底层参考值。您需要进行一些进一步的反射,以确定如何计算哈希值。它可能只是使用默认的object实现。

也来自msdn:

  

哈希码用于高效插入和查找   基于哈希表的集合。哈希码不是   永久价值。

答案 1 :(得分:0)

我实现了一个自定义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);
    }
}

答案 2 :(得分:0)

我最终将生成的 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);

在我的示例中,我按字母顺序对属性进行了排序,但您可以根据需要交换属性。它将遵循 IEnumerable 的顺序。