Search code examples
c#.netxmlrecursionixmlserializable

How to implement IXmlSerializable for recursive tags in XML


I wonder how to implement the method ReadXml of the IXmlSerializable interface when my XML contains recursive tags like in the following example:

    <?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>

The element <section> works like a container for all kind of elements, itself included. I need to translate this structure into objects: a List of elements (objects) that contains other List objects when the element is a Section. So I've created the following Interface and Classes:

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>();
    }
}

The Section class has one more property compared with the interface IWidget from which it inherits, that is the subsection property.

Then I was ready to move my steps starting from this example XmlSerializer serialize generic List of interface

but I really don't understand how to manage the <section> element in my code, that, at the moment, it is nothing more than the signature of the class:

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)
    {

    }

}

Thanks a lot for the advice!


Solution

  • Why are you wanting to implement IXmlSerializable yourself? You can use various attributes to have the framework do this for you. For example, define a base class (given all your implementations are the same):

    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; }
    }
    

    The attributes here specify that type and labelCation are ignored (as they don't exist in the XML). The rest are mapped to XML attributes (e.g. name='abc').

    You can then create your three sub-types:

    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 is the interesting one as it defines the types permitted as child elements with the relevant name mappings. Similarly to this, you'd then define the root element:

    [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>();
    }
    

    You can see in this demo that your XML round-tripped through the serializer is the same, thus proving this works.