当 xml 有 indentation/line 中断时,字典的自定义序列化失败
Custom serialization of a dictionary fails when xml has indentation/line break
为了使字典序列化更清晰 XML,我编写了一个自定义 class,它实现了 IXmlSerializable
.
我的自定义class是这样定义的:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
while (reader.Read())
{
if(reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
var content = reader.ReadElementContentAsString();
this.Add(tag, content);
}
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (string key in this.Keys)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, key);
writer.WriteString(this[key]);
writer.WriteEndElement();
}
}
}
我的代码适用于这个 XML 片段:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<MyData Id="1">some content</MyData>
<MyData Id="2">some other content</MyData>
</MyCollection>
但是,当我缩小 XML 时,我的代码抛出异常:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><MyData Id="1">some content </MyData><MyData Id="2">some other content</MyData></MyCollection>
例外情况是:
System.InvalidOperationException: The ReadElementContentAsString method is not supported on node type EndElement
它是在调用 ReadElementContentAsString
时抛出的。
如何修复我的代码?
我可以使用 :
重现问题
var xml = @"<MyCollection xmlns=""http://schemas.datacontract.org/2004/07/MyProject"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><MyData Id=""1"">some content </MyData><MyData Id=""2"">some other content</MyData></MyCollection>";
var raw = Encoding.UTF8.GetBytes(xml);
var serializer = new DataContractSerializer(typeof(MyCollection));
using (var ms = new MemoryStream(raw))
{
var result = serializer.ReadObject(ms); // Exception throws here
}
您的问题是 reader.ReadElementContentAsString()
positions the reader at the beginning of the next node, not the end of the current node. Then, your subsequent unconditional call to reader.Read()
消耗了下一个节点。当该节点是空白时,不会造成任何伤害,但当该节点是一个元素时,该元素将被跳过。
您的 MyCollection
的以下版本修复了此问题:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
using (var subReader = reader.ReadSubtree())
{
XmlKeyValueListHelper.ReadKeyValueXml(subReader, this);
}
// Consume the EndElement also (or move past the current element if reader.IsEmptyElement).
reader.Read();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlKeyValueListHelper.WriteKeyValueXml(writer, this);
}
}
public static class XmlKeyValueListHelper
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public static void WriteKeyValueXml(System.Xml.XmlWriter writer, ICollection<KeyValuePair<string, string>> collection)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, pair.Key);
writer.WriteString(pair.Value);
writer.WriteEndElement();
}
}
public static void ReadKeyValueXml(System.Xml.XmlReader reader, ICollection<KeyValuePair<string, string>> collection)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
string content;
if (reader.IsEmptyElement)
{
content = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
content = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string, string>(tag, content));
}
else
{
// For instance a comment.
reader.Skip();
}
}
// Move past the end of the list element
reader.ReadEndElement();
}
}
一些注意事项:
通过使用 XmlReader.ReadSubtree()
,我确保 ReadXml()
不会读取超过 MyCollection
元素的末尾,从而破坏未来的元素——这是一个容易犯的错误实施 IXmlSerializable
.
时
通过检查 reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName
我忽略了意外类型的节点,例如评论。
工作.Net fiddle。
为了使字典序列化更清晰 XML,我编写了一个自定义 class,它实现了 IXmlSerializable
.
我的自定义class是这样定义的:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
while (reader.Read())
{
if(reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
var content = reader.ReadElementContentAsString();
this.Add(tag, content);
}
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (string key in this.Keys)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, key);
writer.WriteString(this[key]);
writer.WriteEndElement();
}
}
}
我的代码适用于这个 XML 片段:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<MyData Id="1">some content</MyData>
<MyData Id="2">some other content</MyData>
</MyCollection>
但是,当我缩小 XML 时,我的代码抛出异常:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><MyData Id="1">some content </MyData><MyData Id="2">some other content</MyData></MyCollection>
例外情况是:
System.InvalidOperationException: The ReadElementContentAsString method is not supported on node type EndElement
它是在调用 ReadElementContentAsString
时抛出的。
如何修复我的代码?
我可以使用 :
重现问题var xml = @"<MyCollection xmlns=""http://schemas.datacontract.org/2004/07/MyProject"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><MyData Id=""1"">some content </MyData><MyData Id=""2"">some other content</MyData></MyCollection>";
var raw = Encoding.UTF8.GetBytes(xml);
var serializer = new DataContractSerializer(typeof(MyCollection));
using (var ms = new MemoryStream(raw))
{
var result = serializer.ReadObject(ms); // Exception throws here
}
您的问题是 reader.ReadElementContentAsString()
positions the reader at the beginning of the next node, not the end of the current node. Then, your subsequent unconditional call to reader.Read()
消耗了下一个节点。当该节点是空白时,不会造成任何伤害,但当该节点是一个元素时,该元素将被跳过。
您的 MyCollection
的以下版本修复了此问题:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
using (var subReader = reader.ReadSubtree())
{
XmlKeyValueListHelper.ReadKeyValueXml(subReader, this);
}
// Consume the EndElement also (or move past the current element if reader.IsEmptyElement).
reader.Read();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlKeyValueListHelper.WriteKeyValueXml(writer, this);
}
}
public static class XmlKeyValueListHelper
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public static void WriteKeyValueXml(System.Xml.XmlWriter writer, ICollection<KeyValuePair<string, string>> collection)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, pair.Key);
writer.WriteString(pair.Value);
writer.WriteEndElement();
}
}
public static void ReadKeyValueXml(System.Xml.XmlReader reader, ICollection<KeyValuePair<string, string>> collection)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
string content;
if (reader.IsEmptyElement)
{
content = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
content = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string, string>(tag, content));
}
else
{
// For instance a comment.
reader.Skip();
}
}
// Move past the end of the list element
reader.ReadEndElement();
}
}
一些注意事项:
通过使用
XmlReader.ReadSubtree()
,我确保ReadXml()
不会读取超过MyCollection
元素的末尾,从而破坏未来的元素——这是一个容易犯的错误实施IXmlSerializable
. 时
通过检查
reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName
我忽略了意外类型的节点,例如评论。
工作.Net fiddle。