Как реализовать IXmlSerializable для рекурсивных тегов в XML

Интересно, как реализовать метод ReadXml интерфейса IXmlSerializable, когда мой XML содержит рекурсивные теги, как в следующем примере:

    <?xml version='1.0' encoding='utf-8'?>
<dform>
    <label name='label-a' text='A' dbpath='module/label-a'/>
    <textmemo name='textmemo-a' text='' dbpath='module/textmemo-a'/>
    <section name='section-a' text='' dbpath='module/section-a'>
        <textmemo name='textmemo-b' text='' dbpath='module/textmemo-b'/>
    </section>
    <section name='section-b' text='' dbpath='module/section-b'>
        <textmemo name='textmemo-c' text='' dbpath='module/textmemo-c'/>
        <label name='label-c' text='A' dbpath='module/label-c'/>
        <section name='section-c' text='' dbpath='module/section-c'>
            <label name='label-d' text='A' dbpath='module/label-d'/>
        </section>
    </section>
</dform>

Элемент <section> работает как контейнер для всех типов элементов, включая самого себя. Мне нужно преобразовать эту структуру в объекты: список элементов (объектов), который содержит другие объекты списка, когда элемент является разделом. Итак, я создал следующий интерфейс и классы:

public interface IWidget
{       
    String type { get;}
    String name { get; set; }
    String labelCation { get; set; }
    String text { get; set; }
    String dbpath { get; set; }

}

class Textmemo : IWidget
{
    public String type { get; }
    public String name { get; set; }
    public String labelCation { get; set; }
    public String text { get; set; }
    public String dbpath { get; set; }
    public List<IWidget> subsection { get; set; }

    public Textmemo()
    {
        type = "CONTROL";
    }

}



class Label : IWidget
{
    public String type { get; }
    public String name { get; set; }
    public String labelCation { get; set; }
    public String text { get; set; }
    public String dbpath { get; set; }
    public List<IWidget> subsection { get; set; }

    public Label()
    {
        type = "LABEL";
    }
}

class Section : IWidget
{
    public String type { get; }
    public String name { get; set; }
    public String labelCation { get; set; }
    public String text { get; set; }
    public String dbpath { get; set; }
    public List<IWidget> subsection { get; set; }

    public Section()
    {
        type = "SECTION";
        subsection = new List<IWidget>();
    }
}

Класс Section имеет еще одно свойство по сравнению с интерфейсом IWidget, от которого он наследуется, это свойство subsction.

Затем я был готов начать с этого примера XmlSerializer сериализовать общий список интерфейсов

но я действительно не понимаю, как управлять элементом <section> в моем коде, что на данный момент это не что иное, как подпись класса:

public class WidgetsList: List<IWidget>, IXmlSerializable
{
    public WidgetsList() : base() { }

    public System.Xml.Schema.XmlSchema GetSchema() { return null; }

    public void ReadXml(XmlReader reader)
    {

    }

    public void WriteXml(XmlWriter writer)
    {

    }

}

Большое спасибо за совет!


person Ting    schedule 08.03.2018    source источник
comment
Почему вы не позволяете системе делать тяжелую работу?   -  person aybe    schedule 08.03.2018
comment
Я был бы более чем счастлив сделать это; я явно чего-то не понимаю в том, как работает IXmlSerializable, потому что я не могу понять, как какой-либо элемент назначается правильному разделу или подразделу. Мне чего-то не хватает, но я не знаю, что это такое.   -  person Ting    schedule 08.03.2018


Ответы (2)


Почему вы хотите реализовать IXmlSerializable самостоятельно? Вы можете использовать различные атрибуты, чтобы фреймворк делал это за вас. Например, определите базовый класс (учитывая, что все ваши реализации одинаковы):

public abstract class Widget
{
    [XmlIgnore]
    public abstract string type { get;}
    [XmlAttribute]
    public string name { get; set; }
    [XmlIgnore]
    public string labelCation { get; set; }
    [XmlAttribute]
    public string text { get; set; }
    [XmlAttribute]
    public string dbpath { get; set; }
}

Атрибуты здесь указывают, что type и labelCation игнорируются (поскольку они не существуют в XML). Остальные отображаются в атрибуты XML (например, name='abc').

Затем вы можете создать свои три подтипа:

public class Textmemo : Widget
{
    public override string type { get; } = "CONTROL";
}

public class Label : Widget
{
    public override string type { get; } = "LABEL";
}

public class Section : Widget
{
    public override string type { get; } = "SECTION";

    [XmlElement("textmemo", Type=typeof(Textmemo))]
    [XmlElement("label", Type=typeof(Label))]
    [XmlElement("section", Type=typeof(Section))]
    public List<Widget> subsection { get; } = new List<Widget>();
}

Section является интересным, поскольку он определяет типы, разрешенные как дочерние элементы с соответствующими сопоставлениями имен. Аналогично этому, вы должны определить корневой элемент:

[XmlRoot("dform")]
public class DForm
{
    [XmlElement("textmemo", Type=typeof(Textmemo))]
    [XmlElement("label", Type=typeof(Label))]
    [XmlElement("section", Type=typeof(Section))]
    public List<Widget> Widgets { get; } = new List<Widget>();
}

Вы можете увидеть в этой демонстрации, что ваш XML-код, проходящий через сериализатор, совпадает, что доказывает, что это работает.

person Charles Mager    schedule 12.03.2018
comment
Причина, по которой я реализовал IXmlSerializable, заключается в том, что я использовал интерфейс (IWidget), и процесс сериализации не работает с интерфейсами, если вы не управляете им самостоятельно. Конечно, превращение интерфейса в абстрактный класс решает проблему, как вы прекрасно показали с помощью своего кода, но все же, для чистого упражнения в стиле, я хотел бы знать, как реализовать метод ReadXml. А пока я принимаю ваше решение, потому что это наиболее эффективный способ выполнить задачу, и он может помочь другим разработчикам. Большое спасибо за совет! - person Ting; 13.03.2018

Ниже я написал парсер с использованием Xml Linq, который будет работать

using System;
using System.Collections;
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)
        {

            new Widget(FILENAME);
        }
    }
    public class Widget
    {
        public static Widget root = new Widget();

        public String type { get; set; }
        public String name { get; set; }
        public String labelCation { get; set; }
        public String text { get; set; }
        public String dbpath { get; set; }
        public List<Widget> subsection { get; set; }

        public Widget() { }
        public Widget(string filename)
        {
            XDocument doc = XDocument.Load(filename);
            XElement root = doc.Root;
            RecursiveParse(root, Widget.root);

        }
        public static void RecursiveParse(XElement xParent, Widget textParent)
        {
            foreach (XElement child in xParent.Elements())
            {
                string elementName = child.Name.LocalName;

                switch (elementName)
                {
                    case "label" :
                        textParent.name = (string)child.Attribute("name");
                        textParent.labelCation = (string)child.Attribute("text");
                        textParent.dbpath = (string)child.Attribute("dbpath");

                        break;
                    case "section" :
                        if (textParent.subsection == null) textParent.subsection = new List<Widget>();

                        Widget childSection = new Widget();
                        textParent.subsection.Add(childSection);
                        RecursiveParse(child, childSection);

                        break;
                    default :
                        textParent.text = (string)child.Attribute("text");
                        textParent.labelCation = elementName;
                        break;
                }
            }

        }

    }
}
person jdweng    schedule 08.03.2018
comment
Решение должно работать с любыми типами элементов (textmemo, label, treecombo, ..), а не только с textmemo, и я не могу писать новую процедуру каждый раз, когда решаю добавить новый элемент в свой xml. Вот почему я использовал интерфейс; и поскольку XmlSerializer не работает с интерфейсами, я должен реализовать свой собственный IXmlSerializable. - person Ting; 08.03.2018