代码之家  ›  专栏  ›  技术社区  ›  user2974830

使用XmlSerializer反序列化XML,其中XmlElement名称不同但内容相同

  •  1
  • user2974830  · 技术社区  · 7 年前

    我想将XML文件反序列化为具有几个子类的类。XML如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <Objects>
        <Group index="1">
            <de>
                <GroupName>ANTRIEB</GroupName>
            </de>
            <en>
                <GroupName>missing translation!</GroupName>
            </en>
            <Level>2</Level>
        </Group>
        <Group index="2">
            <de>
                <GroupName>BREMSEN</GroupName>
            </de>
            <Level>3</Level>
        </Group>
    </Objects>
    

    这就是我想将这些语言标记及其内容反序列化到字典中的原因,该字典使用该语言作为键和内容模型。

    我的模型如下所示:

    [XmlRoot("Objects")]
    public class DeactivationsXml
    {
        [XmlElement("Group")]
        public DeactivationsGroup[] Groups { get; set; }
    }
    
    [Serializable()]
    public class DeactivationsGroup
    {
        [XmlIgnore]
        public Dictionary<string, GroupName> GroupNames { get; set; } = new Dictionary<string, GroupName>();
    
        public int Level { get; set; }
    
        [XmlAttribute]
        public byte index { get; set; }
    }
    
    public class GroupName
    {
        [XmlElement("GroupName")]
        public string Name { get; set; }
    }
    

    我花了很长时间来解决这个问题,但没有找到解决方案。我很确定,仅仅用属性是不可能解决这个问题的。

    对于我的问题,一个好的、可扩展的解决方案会很好,因为XML结构很复杂(同一个问题多次出现,内容不同等等)。 我无法更改XML的结构,所以请不要指出这一点。

    方法

    IXML可序列化

    但这种方法没有奏效,因为您必须手动映射所有属性。

    IExtensibleDataObject

    OnDeserialization

    XmlSerializer不支持该属性,但它将提供我可能需要的功能。

    我想这是目前最好的选择。反序列化完成后是否存在一些回调以实现自动化?

    这是到目前为止的全部代码。

    public void Parse()
    {
        string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + 
    "    <Objects>" + 
    "       <Group index=\"1\">" + 
    "           <de>" + 
    "               <GroupName>ANTRIEB</GroupName>" + 
    "           </de>" + 
    "           <en>" + 
    "               <GroupName>missing translation!</GroupName>" + 
    "           </en>" + 
    "           <Level>2</Level>" + 
    "       </Group>" + 
    "       <Group index=\"2\">" + 
    "           <de>" + 
    "               <GroupName>BREMSEN</GroupName>" + 
    "           </de>" + 
    "           <Level>3</Level>" + 
    "       </Group>" + 
    "    </Objects>";
    
        XmlSerializer serializer = new XmlSerializer(typeof(DeactivationsXml));
    
        using (TextReader fileStream = new StringReader(xml))
        {
            var result = (DeactivationsXml)serializer.Deserialize(fileStream);
        }
    }
    
    [XmlRoot("Objects")]
    public class DeactivationsXml
    {
        [XmlElement("Group")]
        public DeactivationsGroup[] Groups { get; set; }
    }
    
    [Serializable()]
    public class DeactivationsGroup
    {
        [XmlIgnore]
        public Dictionary<string, GroupName> GroupNames { get; set; } = new Dictionary<string, GroupName>();
    
        public int Level { get; set; }
    
        [XmlAttribute]
        public byte index { get; set; }
    }
    
    public class GroupName
    {
        [XmlElement("GroupName")]
        public string Name { get; set; }
    }
    
    3 回复  |  直到 7 年前
        1
  •  2
  •   dbc    6 年前

    this answer 并添加代理 XmlElement [] 属性,标记为 [XmlAnyElement] Dictionary<string, GroupName> 属性,将字典键绑定到元素名称。

    注意,虽然 documentation XmlAnyElementAttribute

    指定成员(返回XmlElement或XmlNode对象数组的字段)包含表示在正在序列化或反序列化的对象中没有相应成员的任何XML元素的对象。

    事实上,该属性也可以应用于属性。因此,不需要(反)序列化回调,因为嵌套序列化可以在代理属性本身的getter和setter内执行。它还可以应用于返回数组的成员 XElement XmlElement 如果您更喜欢新的LINQ而不是XML API。

    在这种方法中,您的 DeactivationsGroup 看起来像:

    [Serializable()]
    public class DeactivationsGroup
    {
        public DeactivationsGroup() { this.GroupNames = new Dictionary<string, GroupName>(); }
    
        [XmlIgnore]
        public Dictionary<string, GroupName> GroupNames { get; set; }
    
        public int Level { get; set; }
    
        [XmlAttribute]
        public byte index { get; set; }
    
        [XmlAnyElement]
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public XElement[] XmlGroupNames
        {
            get
            {
                return GroupNames.SerializeToXElements(null);
            }
            set
            {
                if (value == null || value.Length < 1)
                    return;
                foreach (var pair in value.DeserializeFromXElements<GroupName>())
                {
                    GroupNames.Add(pair.Key, pair.Value);
                }
            }
        }
    }
    

    使用以下扩展方法和类:

    public static class XmlKeyValueListHelper
    {
        const string RootLocalName = "Root";
    
        public static XElement [] SerializeToXElements<T>(this IEnumerable<KeyValuePair<string, T>> dictionary, XNamespace ns)
        {
            if (dictionary == null)
                return null;
            ns = ns ?? "";
            var serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
            var array = dictionary
                .Select(p => new { p.Key, Value = p.Value.SerializeToXElement(serializer, true) })
                // Fix name and remove redundant xmlns= attributes.  XmlWriter will add them back if needed.
                .Select(p => new XElement(ns + p.Key, p.Value.Attributes().Where(a => !a.IsNamespaceDeclaration), p.Value.Elements()))
                .ToArray();
            return array;
        }
    
        public static IEnumerable<KeyValuePair<string, T>> DeserializeFromXElements<T>(this IEnumerable<XElement> elements)
        {
            if (elements == null)
                yield break;
            XmlSerializer serializer = null;
            XNamespace ns = null;
            foreach (var element in elements)
            {
                if (serializer == null || element.Name.Namespace != ns)
                {
                    ns = element.Name.Namespace;
                    serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
                }
                var elementToDeserialize = new XElement(ns + RootLocalName, element.Attributes(), element.Elements());
                yield return new KeyValuePair<string, T>(element.Name.LocalName, elementToDeserialize.Deserialize<T>(serializer));
            }
        }
    
        public static XmlSerializerNamespaces NoStandardXmlNamespaces()
        {
            var ns = new XmlSerializerNamespaces();
            ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
            return ns;
        }
    
        public static XElement SerializeToXElement<T>(this T obj)
        {
            return obj.SerializeToXElement(null, NoStandardXmlNamespaces());
        }
    
        public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns)
        {
            return obj.SerializeToXElement(null, ns);
        }
    
        public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
        {
            return obj.SerializeToXElement(serializer, (omitStandardNamespaces ? NoStandardXmlNamespaces() : null));
        }
    
        public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
        {
            var doc = new XDocument();
            using (var writer = doc.CreateWriter())
                (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
            var element = doc.Root;
            if (element != null)
                element.Remove();
            return element;
        }
    
        public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
        {
            using (var reader = element.CreateReader())
            {
                object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
                return (T)result;
            }
        }
    }
    
    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;
            }
        }
    }
    

    fiddle another 演示XML名称空间和属性的示例。

        2
  •  0
  •   jdweng    7 年前

    尝试使用xml linq进行以下操作

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
                XDocument doc = XDocument.Load(FILENAME);
                DeactivationsGroup.GroupNames = doc.Descendants("Group").Select(x => new {
                    languages = x.Elements().Where(y => y.Element("GroupName") != null).Select(y => new DeactivationsGroup() { 
                       name = (string)y.Element("GroupName"),
                       level = (int)x.Element("Level"),
                       index = byte.Parse((string)x.Attribute("index")),
                       language = y.Name.LocalName
                    })
                }).SelectMany(y => y.languages)
                .GroupBy(x => x.name, y => y)
                .ToDictionary(x => x.Key, y => y.FirstOrDefault());
    
            }
            public class DeactivationsGroup
            {
                public static Dictionary<string, DeactivationsGroup> GroupNames { get; set; }
    
                public string name { get; set; }
                public int level { get; set; }
    
                public byte index { get; set; }
    
                public string language { get; set; }
            }
    
        }
    }
    
        3
  •  0
  •   Alexander Petrov    7 年前

    使用您的类集。

    在序列化程序上设置事件处理程序:

    var serializer = new XmlSerializer(typeof(DeactivationsXml));
    serializer.UnknownElement += Serializer_UnknownElement;
    

    private void Serializer_UnknownElement(object sender, XmlElementEventArgs e)
    {
        var group = (DeactivationsGroup)e.ObjectBeingDeserialized;
        group.GroupNames.Add(e.Element.Name, new GroupName { Name = e.Element.InnerText });
    }
    

    Fiddle.