使用自定义IXmlSerializer反序列化注释

月亮骑士

我正在尝试将我的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对我的课程进行反序列化?

数据库

实施时,IXmlSerializable您需要遵守此答案中所述的规则,正确地实现IXmlSerializable?马克Gravell以及文档:

对于IXmlSerializable.WriteXml(XmlWriter)

WriteXml您提供实现应写出对象的XML表示形式。该框架将编写包装器元素,并在XML编写器启动后对其进行定位。您的实现可以编写其内容,包括子元素。然后,框架关闭包装器元素。

对于IXmlSerializable.ReadXml(XmlReader)

ReadXml方法必须使用WriteXml方法编写的信息来重构您的对象。

调用此方法时,阅读器位于包装您的类型的信息的开始标签上。也就是说,直接在指示序列化对象开始的开始标记上。当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。WriteXml方法不同,框架不会自动处理wrapper元素。您的实现必须这样做。不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据。

编写ReadXml()正确处理边缘情况(例如乱序或意外的元素,缺失或多余的空格,空元素等)的结果非常棘手因此,它是有道理采取某种通过XML树正确地分析框架来迭代,比如这一次为什么XmlSerializer的抛出一个异常,并提出一个ValidationEvent里面的时候IXmlSerializable.ReadXml()发生架构验证错误,并延长它来处理评论节点:

public static class XmlSerializationExtensions
{
    // Adapted from this answer https://stackoverflow.com/a/60498500/3744182
    // To https://stackoverflow.com/questions/60449088/why-does-xmlserializer-throws-an-exception-and-raise-a-validationevent-when-a-sc
    // by handling comments.
    public static void ReadIXmlSerializable(XmlReader reader, Func<XmlReader, bool> handleXmlAttribute, Func<XmlReader, bool> handleXmlElement, Func<XmlReader, bool> handleXmlText, Func<XmlReader, bool> handleXmlComment)
    {
        //https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable.readxml?view=netframework-4.8#remarks
        //When this method is called, the reader is positioned on the start tag that wraps the information for your type. 
        //That is, directly on the start tag that indicates the beginning of a serialized object. 
        //When this method returns, it must have read the entire element from beginning to end, including all of its contents. 
        //Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. 
        //Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
        reader.MoveToContent();
        if (reader.NodeType != XmlNodeType.Element)
            throw new XmlException(string.Format("Invalid NodeType {0}", reader.NodeType));
        if (reader.HasAttributes)
        {
            for (int i = 0; i < reader.AttributeCount; i++)
            {
                reader.MoveToAttribute(i);
                handleXmlAttribute(reader);
            }
            reader.MoveToElement(); // Moves the reader back to the element node.
        }
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }
        reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                using (var subReader = reader.ReadSubtree())
                {
                    subReader.MoveToContent();
                    handleXmlElement(subReader);
                }
                // ReadSubtree() leaves the reader positioned ON the end of the element, so read that also.
                reader.Read();
            }
            else if (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA)
            {
                var type = reader.NodeType;
                handleXmlText(reader);
                // Ensure that the reader was not advanced.
                if (reader.NodeType != type)
                    throw new XmlException(string.Format("handleXmlText incorrectly advanced the reader to a new node {0}", reader.NodeType));
                reader.Read();
            }
            else if (reader.NodeType == XmlNodeType.Comment)
            {
                var type = reader.NodeType;
                handleXmlComment(reader);
                // Ensure that the reader was not advanced.
                if (reader.NodeType != type)
                    throw new XmlException(string.Format("handleXmlComment incorrectly advanced the reader to a new node {0}", reader.NodeType));
                reader.Read();
            }
            else // Whitespace, etc.
            {
                // Skip() leaves the reader positioned AFTER the end of the node.
                reader.Skip();
            }
        }
        // Move past the end of the wrapper element
        reader.ReadEndElement();
    }

    public static void ReadIXmlSerializable(XmlReader reader, Func<XmlReader, bool> handleXmlAttribute, Func<XmlReader, bool> handleXmlElement, Func<XmlReader, bool> handleXmlText)
    {
        ReadIXmlSerializable(reader, handleXmlAttribute, handleXmlElement, handleXmlText, r => true);
    }

    public static void WriteIXmlSerializable(XmlWriter writer, Action<XmlWriter> writeAttributes, Action<XmlWriter> writeNodes)
    {
        //https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable.writexml?view=netframework-4.8#remarks
        //The WriteXml implementation you provide should write out the XML representation of the object. 
        //The framework writes a wrapper element and positions the XML writer after its start. Your implementation may write its contents, including child elements. 
        //The framework then closes the wrapper element.
        writeAttributes(writer);
        writeNodes(writer);
    }
}

public static class XmlSerializerFactory
{
    // To avoid a memory leak the serializer must be cached.
    // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
    // This factory taken from 
    // https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648

    readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
    readonly static object padlock;

    static XmlSerializerFactory()
    {
        padlock = new object();
        cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
    }

    public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
    {
        if (serializedType == null)
            throw new ArgumentNullException();
        if (rootName == null && rootNamespace == null)
            return new XmlSerializer(serializedType);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(serializedType, rootName, rootNamespace);
            if (!cache.TryGetValue(key, out serializer))
                cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
            return serializer;
        }
    }
}

然后修改您的类以使用它,如下所示:

[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可以通过编写其ToString()将其序列化为XML

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

    除非该值本身是字符串,否则可能会产生错误的结果:

    • 数字,DateTimeTimeSpan和类似的原语将被本地化XML原语应始终以文化上不变的方式进行格式化。

    • 诸如string []不能覆盖的复杂对象的ToString()格式将完全不正确。


    为了避免这些问题,我的版本通过构造一个适当的,将值序列化为XML XmlSerializer这样可以保证正确性,但可能比您的版本慢。如果此处的性能很重要,则可以检查已知类型(例如string),并使用实用程序类将其手动格式化为XML XmlConvert

  • XmlReader.ReadSubtree()用于确保XmlReader不会被定位HandleXmlElement(XmlReader reader)

演示在这里摆弄

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章