Написание компактного 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);
    }
  }
}    
Другие вопросы по тегам