[英]c# XML Serialization: Order of namespace declarations
我有一个非常奇怪的情况。 我像这样序列化我的命名空间:
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;
}
什么会导致哈希图中命名空间的不同顺序?
来自 msdn:
元素按照key的hash值进行排序,每个key在集合中只能存在一次。
DictionaryEntry
(一个struct
)的哈希值是从ValueType.GetHashCode()
提取的。 它很可能会返回一个无法确定的键 - 可能基于基础参考值。 您需要做一些进一步的思考,以确定哈希是如何计算的。 它可能只是使用默认object
实现。
同样来自 msdn:
哈希码用于在基于哈希表的集合中进行高效的插入和查找。 哈希码不是永久值。
我已经实现了一个自定义的 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);
}
}
我最终将生成的 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 的顺序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.