Search code examples
c#xmlxmlserializer

Can't Read 3rd level of XML


I'm not a XML expert and haven't coded with it for several years. I'm tasked with reading the following XML file into a C# program for processing. I'm able to read about 2 levels - up to the data index="1" counter="0" lines. However I can't read the bit index="1" value="false" lines. Is there something wrong with my classes?

Also, how can I change the class to add the name attribute for the index lines?

I've tried the trick of copying the source XML into an empty C# class and running it with the class it creates. However that still didn't work.

Here is what I'm trying to read:

<?xml version="1.0"?>
-<mibscalar link="http://1.1.1.1/v1/mib/objs/inputPointGroupStatus?type=xml" type="readonly" name="inputPointGroupStatus">
    -<index name="index1">
        -<data index="1">
            -<index name="index2">
                -<data index="1" counter="0">
                    <bit index="1" value="false"/>
                    <bit index="2" value="false"/>
                    <bit index="3" value="false"/>
                    <bit index="4" value="false"/>
                    <bit index="5" value="false"/>
                    <bit index="6" value="false"/>
                    <bit index="7" value="false"/>
                    <bit index="8" value="false"/>
                </data>
                +<data index="2" counter="0">
                +<data index="3" counter="0">
                +<data index="4" counter="0">
                +<data index="5" counter="0">
                +<data index="6" counter="0">
                +<data index="7" counter="0">
                +<data index="8" counter="0">
                +<data index="9" counter="0">
                +<data index="10" counter="0">
                +<data index="11" counter="0">
                +<data index="12" counter="0">
                +<data index="13" counter="0">
                +<data index="14" counter="0">
                +<data index="15" counter="0">
                +<data index="16" counter="0">
            </index>
        </data>
        +<data index="2">
        +<data index="3">
        +<data index="4">
        +<data index="5">
        +<data index="6">
        +<data index="7">
        +<data index="8">
        +<data index="9">
        +<data index="10">
    </index>
</mibscalar>

Here is the code:

    public static AtcLoopsStatusMibData ExecuteGetLoopsStatus(string ip, string command)
    {
        AtcLoopsStatusMibData mib;
        string uri = string.Format(API_FORMAT_STR, ip, command);
        string msg = $"Calling {uri}";
        Xlog.LogVerbose(msg);

        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Timeout = 15 * 1000;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream());

            XmlSerializer serializer = new XmlSerializer(typeof(AtcLoopsStatusMibData));
            mib = (AtcLoopsStatusMibData)serializer.Deserialize(reader);
        }
        catch (Exception ex)
        {
            msg = $"Call to {uri} failed: {ex.Message}";
            Xlog.LogError(msg);
            throw;
        }

        return mib;
    }

Here is the classes:

[XmlRootAttribute("mibscalar")]
public class AtcLoopsStatusMibData
{
    [XmlAttribute("name")]
    public string Name { get; set; }

    [XmlAttribute("link")]
    public string Link { get; set; }

    [XmlArray("index")]
    [XmlArrayItem("data", typeof(Data1MibElement))]
    public List<Data1MibElement> Data1List { get; set; }

    [XmlElement("data")]
    public Data1MibElement Data { get; set; }

    /// <summary>
    /// Default Constructor
    /// </summary>
    public AtcLoopsStatusMibData()
    {
        Data1List = new List<Data1MibElement>();
    }

    /// <summary>
    /// Sets the "Link" property that is required when sending Mib object
    /// to ATC.
    /// </summary>
    /// <param name="ip">IP Address of the ATC</param>
    /// <param name="commandName">Name of the API command call</param>
    public void SetLinkAndName(string ip, string commandName)
    {
        Link = string.Format(HttpHelper.API_FORMAT_STR, ip, commandName);
        Name = commandName;
    }
}

/// <summary>
/// Secondary object inside the MIB returned by the ATC
/// for detector loop status.
/// This object primary
/// holds the values of the request or command.
/// </summary>
[Serializable]
public class Data1MibElement
{
    [XmlAttribute("index")]
    public int Index { get; set; }

    [XmlArray("index")]
    [XmlArrayItem("data", typeof(Data2MibElement))]
    public List<Data2MibElement> Data2List { get; set; }

    [XmlElement("data")]
    public Data2MibElement Data { get; set; }

    /// <summary>
    /// Default Constructor
    /// </summary>
    public Data1MibElement()
    {
        Data2List = new List<Data2MibElement>();
    }

    /// <summary>
    /// Constructor used when building the LoopsStatus MIB object
    /// </summary>
    /// <param name="index">Loop # (1-6) </param>
    /// <param name="counter">Detector loop status value </param>
    public Data1MibElement(int index)
    {
        Index = index;
    }
}

/// <summary>
/// Secondary object inside the MIB returned by the ATC
/// for detector loop status.
/// This object primary
/// holds the values of the request or command.
/// </summary>
[Serializable]
public class Data2MibElement
{
    [XmlAttribute("index")]
    public int Index { get; set; }

    [XmlAttribute("counter")]
    public int Counter { get; set; }

    [XmlArray("bit")]
    public List<BitMibElement> BitList { get; set; }

    /// <summary>
    /// Default Constructor
    /// </summary>
    public Data2MibElement()
    {
        BitList = new List<BitMibElement>();
    }

    /// <summary>
    /// Constructor used when building the LoopsStatus MIB object
    /// </summary>
    /// <param name="index">Loop # (1-6) </param>
    /// <param name="counter">Detector loop status value </param>
    public Data2MibElement(int index, int counter)
    {
        Index = index;
        Counter = counter;
    }
}

/// <summary>
/// Secondary object inside the MIB returned by the ATC
/// for detector loop status.
/// This object primary
/// holds the values of the request or command.
/// </summary>
[Serializable]
public class BitMibElement
{
    [XmlAttribute("index")]
    public int Index { get; set; }

    [XmlAttribute("value")]
    public string Value { get; set; }

    /// <summary>
    /// Default Constructor
    /// </summary>
    public BitMibElement()
    {

    }

    /// <summary>
    /// Constructor used when building the BitMibElement MIB object
    /// </summary>
    /// <param name="index">Loop # (1-6) </param>
    /// <param name="value">Detector loop status value </param>
    public BitMibElement(int index, string value)
    {
        Index = index;
        Value = value;
    }
}

This is what I'm seeing in the debugger and creating as a output XML file:

<?xml version="1.0" encoding="UTF-8"?>
-<mibscalar link="http://1.1.1.1/v1/mib/objs/inputPointGroupStatus?type=xml" name="inputPointGroupStatus" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-<index>
-<data index="1">
-<index>
-<data index="1" counter="0">
<bit/>
</data>
-<data index="2" counter="0">
<bit/>
</data>
-<data index="3" counter="0">
<bit/>
</data>
-<data index="4" counter="0">
<bit/>
</data>
-<data index="5" counter="0">
<bit/>
</data>
-<data index="6" counter="0">
<bit/>
</data>
-<data index="7" counter="0">
<bit/>
</data>
-<data index="8" counter="0">
<bit/>
</data>
-<data index="9" counter="0">
<bit/>
</data>
-<data index="10" counter="0">
<bit/>
</data>
-<data index="11" counter="0">
<bit/>
</data>
-<data index="12" counter="0">
<bit/>
</data>
-<data index="13" counter="0">
<bit/>
</data>
-<data index="14" counter="0">
<bit/>
</data>
-<data index="15" counter="0">
<bit/>
</data>
-<data index="16" counter="0">
<bit/>
</data>
</index>
</data>
+<data index="2">
+<data index="3">
+<data index="4">
+<data index="5">
+<data index="6">
+<data index="7">
+<data index="8">
+<data index="9">
+<data index="10">
</index>
</mibscalar>

Without + and - characters - duplicate the bit section under every data section to fully see the full file. I can do that for you if this webpage will let me insert something that long.

<?xml version="1.0"?>
<mibscalar link="http://1.1.1.1/v1/mib/objs/inputPointGroupStatus?type=xml" type="readonly" name="inputPointGroupStatus">
    <index name="index1">
        <data index="1">
            <index name="index2">
                <data index="1" counter="0">
                    <bit index="1" value="false"/>
                    <bit index="2" value="false"/>
                    <bit index="3" value="false"/>
                    <bit index="4" value="false"/>
                    <bit index="5" value="false"/>
                    <bit index="6" value="false"/>
                    <bit index="7" value="false"/>
                    <bit index="8" value="false"/>
                </data>
                <data index="2" counter="0" />
                <data index="3" counter="0" />
                <data index="4" counter="0" />
                <data index="5" counter="0" />
                <data index="6" counter="0" />
                <data index="7" counter="0" />
                <data index="8" counter="0" />
                <data index="9" counter="0" />
                <data index="10" counter="0" />
                <data index="11" counter="0" />
                <data index="12" counter="0" />
                <data index="13" counter="0" />
                <data index="14" counter="0" />
                <data index="15" counter="0" />
                <data index="16" counter="0" />
            </index>
        </data>
        <data index="2" />
        <data index="3" />
        <data index="4" />
        <data index="5" />
        <data index="6" />
        <data index="7" />
        <data index="8" />
        <data index="9" />
        <data index="10" />
    </index>
</mibscalar>

Solution

  • Tools to generate c# classes from XML such as xsd.exe and https://xmltocsharp.azurewebsites.net/ can be very useful in creating types to consume some given XML, but they have limitations. One limitation that can be encountered is that they do not correctly detect recursive data structures, i.e. types that contain other instances of themselves, either directly or indirectly. And that is what you have here.

    The nesting relationships exhibited by your XML elements are as follows:

    • The root element <mibscalar> has (multiple?) <index> (or <bit>?).
    • <index> has multiple <data>.
    • <data> has multiple <index> or <bit> (or both?).

    This structure can be modeled as follows:

    [XmlRoot(ElementName = "mibscalar")]
    public class Mibscalar
    {
        [XmlAttribute(AttributeName = "link")]
        public string Link { get; set; }
        [XmlAttribute(AttributeName = "type")]
        public string Type { get; set; }
        [XmlAttribute(AttributeName = "name")]
        public string Name { get; set; }
    
        [XmlElement("bit", typeof(Bit))]
        [XmlElement("index", typeof(Index))]
        public List<DataItemBase> Items { get; set; }
    }
    
    public class Data
    {
        [XmlAttribute(AttributeName = "index")]
        public string Index { get; set; }
        [XmlAttribute(AttributeName = "counter")]
        public string Counter { get; set; }
    
        [XmlElement("bit", typeof(Bit))]
        [XmlElement("index", typeof(Index))]
        public List<DataItemBase> Items { get; set; }
    }
    
    public abstract class DataItemBase
    {
    }
    
    public class Index : DataItemBase
    {
        [XmlAttribute(AttributeName = "name")]
        public string Name { get; set; }
    
        [XmlElement(ElementName = "data")]
        public List<Data> Data { get; set; }
    }
    
    public class Bit : DataItemBase
    {
        [XmlAttribute(AttributeName = "index")]
        public string Index { get; set; }
        [XmlAttribute(AttributeName = "value")]
        public string Value { get; set; }
    }
    

    I used https://xmltocsharp.azurewebsites.net/ to generate initial classes, then modified them to correct the parent/child hierarchy.

    Working sample .Net fiddle here: https://dotnetfiddle.net/9yQ7oN.