0

I have this class which represent a node TestCase in my XML :

public class TestCase
{
    [XmlAttribute("name")]
    public string name { get; set; }
    public string version { get; set; }
    public string verdict { get; set; }
    public string objective { get; set; }
    public string criteria { get; set; }
    public string issue { get; set; }
    public string clientcomments { get; set; }
    public string authoritycomments { get; set; }
    public string sdk { get; set; }   

}

I use XmlNode.SelectSingleNode to fetch a specific node in my XML. For info, there are no duplicate nodes (no nodes with the same name attribute) if it matters.

So far, I have this code :

public static TestCase FetchNode(string NodeName, string Path)
    {
        TestCase testcase = new TestCase();
        string[] attr = { "name", "version", "verdict", "objective", "criteria"
                , "issue", "clientcomments", "authoritycomments", "sdk" };
        string[] attrval = { null, null,null,null,null,null,null,null,null};

        XmlDocument doc = new XmlDocument();
        doc.Load(Path);

        XmlNode node = doc.SelectSingleNode("/TestsList/TestCase[@name='" + NodeName + "']");

        for (var i = 0; i == attr.Length - 1;i++)
        {
            attrval[i] = node[attr[i]].InnerText;
        }

        testcase.name = attrval[0];
        testcase.version = attrval[1];
        testcase.verdict = attrval[2];
        testcase.objective = attrval[3];
        testcase.criteria = attrval[4];
        testcase.issue = attrval[5];
        testcase.clientcomments = attrval[6];
        testcase.authoritycomments = attrval[7];
        testcase.sdk = attrval[8];

        return testcase;

    }

However, this code is not scalable at all, if I change my class structure, I would need to change the function because each element of the class are hardcoded in it.

This is a wide request, but how could I write this function so if I add or remove a string in the class definition of TestCase, I don`t have to change the function FetchNode.

Thank you for your time.

Jeph Gagnon
  • 157
  • 3
  • 13

2 Answers2

1

You could use XmlSerializer.Deserialize

Example:

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class TestCase
{
    [XmlAttribute("name")]
    public string name { get; set; }
    public string version { get; set; }
    public string verdict { get; set; }
    public string objective { get; set; }
    public string criteria { get; set; }
    public string issue { get; set; }
    public string clientcomments { get; set; }
    public string authoritycomments { get; set; }
    public string sdk { get; set; }   
}

public class Program
{
    public const string XML = @"
<TestCase name='TicketName'>
    <name>Jon Nameson</name>
    <version>10.1</version>
    <verdict>High</verdict>
</TestCase>
";

    public static void Main()
    {
        var doc = new XmlDocument();
        doc.LoadXml(XML);

        var node = doc.SelectSingleNode("/TestCase");

        var serializer = new XmlSerializer(typeof(TestCase));

        var testcase = serializer.Deserialize(new StringReader(node.OuterXml)) as TestCase;

        Console.WriteLine(testcase.name);
        Console.WriteLine(testcase.version);
        Console.WriteLine(testcase.verdict);
    }
}

DotNetFiddle

Negorath
  • 486
  • 2
  • 5
1

You can deserialize directly from your selected XmlNode by combining XmlSerializer with XmlNodeReader using the following extension method:

public static class XmlNodeExtensions
{
    public static T Deserialize<T>(this XmlNode element, XmlSerializer serializer = null)
    {
        using (var reader = new ProperXmlNodeReader(element))
            return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
    }

    class ProperXmlNodeReader : XmlNodeReader
    {
        // Bug fix from https://stackoverflow.com/questions/30102275/deserialize-object-property-with-stringreader-vs-xmlnodereader
        public ProperXmlNodeReader(XmlNode node)
            : base(node)
        {
        }

        public override string LookupNamespace(string prefix)
        {
            return NameTable.Add(base.LookupNamespace(prefix));
        }
    }
}

This adds an extension method to XmlNode which invokes XmlSerializer to deserialize the selected node to an instance of the generic type T.

Then do:

var testcase = node.Deserialize<TestCase>();

which is identical to:

var testcase = XmlNodeExtensions.Deserialize<TestCase>(node);

In your case the expected root element name of your TestCase class, namely <TestCase>, matches the actual node name. If the node name does not match the expected root element name, you can tell XmlSerializer to expect a different root name by following the instructions in XmlSerializer Performance Issue when Specifying XmlRootAttribute.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks for the detailed response. My c# understanding is not that advanced so I am not sure what exactly the code you've provided me does. Would you mind giving me a brief explanation of the code? Thanks! – Jeph Gagnon Apr 12 '16 at 19:57
  • @JephGagnon - I added a brief explanation, but I'm not sure what you need to know. – dbc Apr 12 '16 at 20:03
  • It was more of a general understanding of what you did exactly. The link you provided gives me more information on that. I marked Negorath response as the answer because I can understand what was done, but I will definitely try this method also (and more importantly, understand it). Thank you. – Jeph Gagnon Apr 12 '16 at 20:13