Написание компактного XML с XmlDictionaryWriter.CreateBinaryWriter и XmlDictionary
Я хочу записать XML-документ на диск в компактном формате. Для этого я использую метод Net Framework XmlDictionaryWriter.CreateBinaryWriter(Stream stream,IXmlDictionary dictionary)
Этот метод записывает пользовательское компактное двоичное представление XML, которое впоследствии может быть прочитано XmlDictionaryWriter.CreateBinaryReader
, Метод принимает XmlDictionary
которые могут содержать общие строки, так что эти строки не нужно выводить каждый раз. Вместо строки в файле будет напечатан индекс словаря. CreateBinaryReader
Позже можно использовать тот же словарь, чтобы полностью изменить процесс.
Однако словарь, который я передаю, по-видимому, не используется. Рассмотрим этот код:
using System.IO;
using System.Xml;
using System.Xml.Linq;
class Program
{
public static void Main()
{
XmlDictionary dict = new XmlDictionary();
dict.Add("myLongRoot");
dict.Add("myLongAttribute");
dict.Add("myLongValue");
dict.Add("myLongChild");
dict.Add("myLongText");
XDocument xdoc = new XDocument();
xdoc.Add(new XElement("myLongRoot",
new XAttribute("myLongAttribute", "myLongValue"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText"),
new XElement("myLongChild", "myLongText")
));
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
xdoc.WriteTo(writer);
}
}
}
В результате получается следующее (двоичные управляющие символы не показаны)
@
myLongRootmyLongAttribute˜myLongValue@myLongChild™
myLongText@myLongChild™
myLongText@myLongChild™
myLongText
Таким образом, очевидно, XmlDictionary не был использован. Все строки появляются полностью в выводе, даже несколько раз.
Это не проблема, ограниченная XDocument. В приведенном выше минимальном примере я использовал XDocument для демонстрации проблемы, но изначально я наткнулся на это, используя XmlDictionaryWriter в сочетании с DataContractSerializer, так как он обычно используется. Результаты были одинаковыми:
[Serializable]
public class myLongChild
{
public double myLongText = 0;
}
...
using (Stream stream = File.Create("binaryXml.txt"))
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict))
{
var dcs = new DataContractSerializer(typeof(myLongChild));
dcs.WriteObject(writer, new myLongChild());
}
Полученный вывод не использовал мой XmlDictionary.
Как я могу заставить XmlDictionaryWriter использовать поставляемый XmlDictionary?
Или я неправильно понял, как это работает?
с помощью подхода DataContractSerializer я попытался отладить код сетевого фреймворка (визуальная студия / options / debugging / enable net. framework, пошаговое изменение исходного кода). Очевидно, Writer пытается найти каждую из приведенных выше строк в словаре, как и ожидалось. Однако поиск не выполняется в строке 356 файла XmlbinaryWriter.cs по причинам, которые мне не ясны.
Альтернативы, которые я рассмотрел:
Существует перегрузка для XmlDictionaryWriter.CreatebinaryWriter, которая также принимает XmlBinaryWriterSession. Затем автор добавляет любые новые строки, с которыми он сталкивается, в словарь сеанса. Однако я хочу использовать только статический словарь для чтения и записи, который известен заранее.
Я мог бы обернуть все это в
GzipStream
и пусть сжатие позаботится о множественных экземплярах строк. Однако это не будет сжимать первый экземпляр каждой строки и выглядит как неуклюжий обходной путь в целом.
1 ответ
Да, есть недоразумение. XmlDictionaryWriter
в основном используется для сериализации объектов, и это дочерний класс XmlWriter
, XDocument.WriteTo(XmlWriter something)
принимает XmlWriter
в качестве аргумента. Вызов XmlDictionaryWriter.CreateBinaryWriter
создаст экземпляр System.Xml.XmlBinaryNodeWriter
внутренне. Этот класс имеет оба метода для "обычной" записи:
// override of XmlWriter
public override void WriteStartElement(string prefix, string localName)
{
// plain old "xml" for me please
}
и для словарного подхода:
// override of XmlDictionaryWriter
public override void WriteStartElement(string prefix, XmlDictionaryString localName)
{
// I will use dictionary to hash element names to get shorter output
}
Последний в основном используется, если вы сериализуете объект через DataContractSerializer
(обратите внимание на его метод WriteObject
принимает аргумент обоих XmlDictionaryWriter
а также XmlWriter
типа), пока XDocument
занимает всего XmlWriter
,
Что касается твоей проблемы - на твоем месте я бы сделал свой собственный XmlWriter
:
class CustomXmlWriter : XmlWriter
{
private readonly XmlDictionaryWriter _writer;
public CustomXmlWriter(XmlDictionaryWriter writer)
{
_writer = writer;
}
// override XmlWriter methods to use the dictionary-based approach instead
}
ОБНОВЛЕНИЕ (на основе вашего комментария)
Если вы действительно используете DataContractSerializer
у вас есть несколько ошибок в вашем коде.
1) POC классы должны быть украшены [DataContract]
а также [DataMember]
атрибут, сериализованное значение должно быть свойством, а не полем; также установите для пространства имен пустое значение, иначе вам придется иметь дело с пространствами имен в вашем словаре. Подобно:
namespace XmlStuff {
[DataContract(Namespace = "")]
public class myLongChild
{
[DataMember]
public double myLongText { get; set; }
}
[DataContract(Namespace = "")]
public class myLongRoot
{
[DataMember]
public IList<myLongChild> Items { get; set; }
}
}
2) Предоставить также экземпляр сеанса; для нулевого сеанса автор словаря использует default (XmlWriter
-подобная) реализация:
// order matters - add new items only at the bottom
static readonly string[] s_Terms = new string[]
{
"myLongRoot", "myLongChild", "myLongText",
"http://www.w3.org/2001/XMLSchema-instance", "Items"
};
public class CustomXmlBinaryWriterSession : XmlBinaryWriterSession
{
private bool m_Lock;
public void Lock() { m_Lock = true; }
public override bool TryAdd(XmlDictionaryString value, out int key)
{
if (m_Lock)
{
key = -1;
return false;
}
return base.TryAdd(value, out key);
}
}
static void InitializeWriter(out XmlDictionary dict, out XmlBinaryWriterSession session)
{
dict = new XmlDictionary();
var result = new CustomXmlBinaryWriterSession();
var key = 0;
foreach(var term in s_Terms)
{
result.TryAdd(dict.Add(term), out key);
}
result.Lock();
session = result;
}
static void InitializeReader(out XmlDictionary dict, out XmlBinaryReaderSession session)
{
dict = new XmlDictionary();
var result = new XmlBinaryReaderSession();
for (var i = 0; i < s_Terms.Length; i++)
{
result.Add(i, s_Terms[i]);
}
session = result;
}
static void Main(string[] args)
{
XmlDictionary dict;
XmlBinaryWriterSession session;
InitializeWriter(out dict, out session);
var root = new myLongRoot { Items = new List<myLongChild>() };
root.Items.Add(new myLongChild { myLongText = 24 });
root.Items.Add(new myLongChild { myLongText = 25 });
root.Items.Add(new myLongChild { myLongText = 27 });
byte[] buffer;
using (var stream = new MemoryStream())
{
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dict, session))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
dcs.WriteObject(writer, root);
}
buffer = stream.ToArray();
}
XmlBinaryReaderSession readerSession;
InitializeReader(out dict, out readerSession);
using (var stream = new MemoryStream(buffer, false))
{
using (var reader = XmlDictionaryReader.CreateBinaryReader(stream, dict, new XmlDictionaryReaderQuotas(), readerSession))
{
var dcs = new DataContractSerializer(typeof(myLongRoot));
var rootCopy = dcs.ReadObject(reader);
}
}
}