Пользовательская сериализация словаря завершается неудачно, когда в xml есть отступ / разрыв строки
Чтобы иметь более чистый XML сериализации словаря, я написал собственный класс, который реализует IXmlSerializable
,
Мой пользовательский класс определяется так:
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
}
1 ответ
Ваша проблема в том, что reader.ReadElementContentAsString()
позиционирует читателя в начале следующего узла, а не в конце текущего узла. Затем ваш последующий безусловный вызов 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 скрипка.