Десериализовать комментарии, используя Custom IXmlSerializer - PullRequest
2 голосов
/ 13 апреля 2020

Я пытаюсь сериализовать свое свойство Description в комментарий Xml. Итак, для этого я реализовал IXmlSerializable, а следующий WriteXml дает очень хороший XML.

[Serializable]
public sealed class Setting<T> : SettingBase, IXmlSerializable
{
    public Setting() { }

    public Setting(T value, string description)
    {
        Value = value;
        Description = description;
    }

    public Setting(string command, T value, string description)
        : this(value, description)
    {
        Command = command;
    }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
    }

    public void WriteXml(XmlWriter writer)
    {
        var properties = GetType().GetProperties();
        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
                writer.WriteComment(Description);
            else if (!propertyInfo.CustomAttributes.Any((attr) => attr.AttributeType.Equals(typeof(XmlIgnoreAttribute))))
                writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null)?.ToString());
        }
    }

    [XmlComment, Browsable(false)]
    public string Description { get; set; }

    [XmlElement, Browsable(false)]
    public string Command { get; set; }

    [XmlElement, Browsable(false)]
    public T Value { get; set; }

    [XmlIgnore]
    public override object ValueUntyped { get { return Value; } }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute {}

Однако у меня было много попыток реализовать ReadXml, но я не могу десериализовать комментарий Description.

Как я могу реализовать ReadXml чтобы развенчать мой класс?

1 Ответ

2 голосов
/ 13 апреля 2020

При реализации IXmlSerializable вам необходимо придерживаться правил, изложенных в этого ответа до Правильный способ реализации IXmlSerializable? до Март c Gravell , а также документация:

Для IXmlSerializable.WriteXml(XmlWriter):

Предоставленная вами реализация WriteXml должна выписать XML представление объекта. Фреймворк записывает элемент-обертку и позиционирует писатель XML после его запуска. Ваша реализация может написать свое содержимое, включая дочерние элементы. Затем платформа закрывает элемент-оболочку.

Для IXmlSerializable.ReadXml(XmlReader):

Метод ReadXml должен воссоздать ваш объект, используя информацию, которая был написан методом Write Xml.

Когда этот метод вызывается, читатель помещается в начальный тег, который оборачивает информацию для вашего типа. То есть непосредственно на начальном теге, который указывает начало сериализованного объекта. Когда этот метод возвращается, он должен прочитать весь элемент от начала до конца, включая все его содержимое. В отличие от метода WriteXml, платформа не обрабатывает элемент-оболочку автоматически. Ваша реализация должна сделать это. Несоблюдение этих правил позиционирования может привести к тому, что код будет генерировать непредвиденные исключения во время выполнения или повреждать данные.

Оказывается, очень сложно написать ReadXml(), который правильно обрабатывает крайние случаи, такие как неупорядоченные или неожиданные элементы, отсутствующие или лишние пробелы, пустые элементы и так далее. Таким образом, имеет смысл принять некую платформу синтаксического анализа для правильной итерации по дереву XML, например this from Почему XmlSerializer выдает исключение и вызывает ValidationEvent, когда ошибка проверки схемы происходит внутри IXmlSerializable.Read Xml () и расширяйте ее для обработки узлов комментариев:

Затем измените свой класс, чтобы использовать его следующим образом:

[Serializable]
public sealed class Setting<T> : SettingBase, IXmlSerializable
{
    public Setting() { }

    public Setting(T value, string description)
    {
        Value = value;
        Description = description;
    }

    public Setting(string command, T value, string description)
        : this(value, description)
    {
        Command = command;
    }

    public XmlSchema GetSchema() { return null;}

    public void ReadXml(XmlReader reader)
    {
        XmlSerializationExtensions.ReadIXmlSerializable(reader, r => true,
            r =>
            {
                switch (r.LocalName)
                {
                    case "Command":
                        Command = r.ReadElementContentAsString();
                        break;
                    case "Value":
                        var serializer = XmlSerializerFactory.Create(typeof(T), "Value", reader.NamespaceURI);
                        Value = (T)serializer.Deserialize(r);
                        break;
                }
                return true;
            },
            r => true, r => { Description += r.Value; return true; });
    }

    public void WriteXml(XmlWriter writer)
    {
        XmlSerializationExtensions.WriteIXmlSerializable(writer, w => { },
            w =>
            {
                if (Description != null)
                    w.WriteComment(Description);
                if (Command != null)
                    w.WriteElementString("Command", Command);
                if (Value != null)
                {
                    var serializer = XmlSerializerFactory.Create(typeof(T), "Value", null);
                    serializer.Serialize(w, Value);
                }
            });
    }

    public string Description { get; set; }

    public string Command { get; set; }

    public T Value { get; set; }

    public override object ValueUntyped { get { return Value; } }
}

// ABSTRACT BASE CLASS NOT INCLUDED IN QUESTION, THIS IS JUST A GUESS
[Serializable]
public abstract class SettingBase
{
    public abstract object ValueUntyped { get; }
}

И вы сможете туда и обратно добраться до XML.

Примечания:

  • Поскольку ваш класс запечатан, я заменил использование отражения на прямой доступ к свойствам для сериализации.

  • В вашей версии вы сериализуете T Value в XML, записывая его значение ToString():

    writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null)?.ToString());
    

    Если само значение не является строкой, это может привести к неправильному результату:

    • Numeri c, DateTime, TimeSpan и подобные примитивы будут локализованы . XML примитивы всегда должны быть отформатированы с учетом культурных особенностей.

    • Сложные объекты, такие как string [], которые не переопределяют ToString(), будут отформатированы совершенно неверным образом.


    Чтобы избежать этих проблем, моя версия сериализует значение до XML, создав соответствующий XmlSerializer. Это гарантирует правильность, но может быть медленнее, чем ваша версия. Если здесь важна производительность, вы можете проверить известные типы (например, string) и отформатировать их в XML вручную, используя, например, служебный класс

XmlConvert.

XmlReader.ReadSubtree() используется для того, чтобы XmlReader не был неправильно расположен HandleXmlElement(XmlReader reader).

Демонстрационная скрипка здесь .

...