Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Here's a class I created to parse XML to create an IList. It seems to me that this is something that will be done a lot in general by a lot of different programs. Is there a more standard way of doing this using .Net and C#

/// <summary>
/// public class CListFromXml {}
/// Created By: Michael Fitzpatrick, 2012 Sept 25
/// Please attribute & send email: samplecode AT mikefitz dot us
/// Create an IList from XML data
/// XML Syntax -
///   <XmlNode Type="MyType" Enabled="(bool)" Iterations="(number)"> 
///     (data definition)
///   </XmlNode>
/// 
///   Type: System.Double by default
///     Type can be Enum using the following syntax:
///       Type="NameSpace.ClassParent.Class+EnumName, ClassParent"
///     See: http://stackoverflow.com/questions/2493215/create-list-of-variable-type
/// 
///   Enabled: true by default, If false: no list is created
/// 
///   Iterations: 1 by default, If 0: no list is created. 
///     If LT 0 then Iterations = Int32.MaxValue
///   
///   (data definition): As CSV list-
///     <CsvString> (item), (item), ...  </CsvString>
///     where:
///       (item) is any value. For arrays item is bracketed using "{}" 
///         just like C# uses for initializers. Example:
///           <MeasProp Type="System.Double[]">
///             {1960, 1970, 1980},
///             {2960, 2970, 2980},
///             {2960}
///         </MeasProp>
///   
///   (data definition): As Center / StepDelta -
///     <LoopSpec> 
///       <Center>(some value)</Center>
///       <StepDelta>(some value)</StepDelta> <!-- OPTIONAL -->
///       <StepsUp>(some integer)</StepsUp> <!-- OPTIONAL -->
///       <StepsDown>(some integer)</StepsDown> <!-- OPTIONAL -->
///     </LoopSpec>
/// </summary>
public class CListFromXml
{
    public readonly IList Data = null;
    public readonly string Name;
    public readonly bool Enabled;
    public readonly Type Type;
    public readonly string TypeName;
    public readonly Int32 Iterations;

    private static bool ReadCsv(Type typ, string csvData, IList lst)
    {
        string[] astr = csvData
            .Split(new[] { ",", "\r\n", "\n", " ", "\t", "}" }
            , StringSplitOptions.RemoveEmptyEntries);
        if (astr.Length == 0)
            return false;
        if (typ == typeof(bool))
            foreach (string s in astr)
                lst.Add(CUtils.BoolParse(s));
        else if (typ.IsEnum)
            foreach (string s in astr)
                lst.Add(Enum.Parse(typ, s));
        else
        {
            try
            {
                foreach (string s in astr)
                {
                    object o = Convert.ChangeType(s, typ);
                    lst.Add(o);
                }
            }
            catch
            {
                throw new NotSupportedException("CListFromXml: Type not supported:"
                    + typ.ToString());
            }
        }
        return true;
    }

    // ============================================================
    /// <summary>
    /// Create a list from XML data
    /// </summary>
    /// <param name="n"></param>
    public CListFromXml(XmlNode n)
    {
        if (!(Enabled = CUtils.IsNodeEnabled(n)))
            return;
        if ((Iterations = CUtils.NodeIterations(n)) == 0)
            return;
        Name = n.Name;

        TypeName = CUtils.AttribValue(n, "type");
        if (TypeName != "")
        {
            Type = Type.GetType(TypeName);
            if (Type == null)
                throw new InvalidDataException("Invalid XML File: "
                    + " the element " + Name
                    + " has an invalid type attribute: " + TypeName);
        }
        else
            Type = typeof(double);

        Type lstType = typeof(List<>).MakeGenericType(Type);
        Data = (IList)Activator.CreateInstance(lstType);
        if (n.FirstChild.ChildNodes.Count == 0)
        {
            if (Type.IsArray)
            {
                // String format for Arrays is { <item>, <item>, ...} , { ...
                string[] astr = n.InnerText.Trim()
                    .Split(new[] { "{"}
                        , StringSplitOptions.RemoveEmptyEntries);
                if (astr.Length > 0)
                {
                    IList lst;
                    Type aryType;
                    try
                    {
                        aryType = Type.GetElementType();
                        Type lstType1 = typeof(List<>).MakeGenericType(aryType);
                        lst = (IList)Activator.CreateInstance(lstType1);
                    }
                    catch (Exception e)
                    {
                        throw new NotSupportedException("CListFromXml: XML Node: " 
                            + Name + ", Type not supported:"
                            + TypeName + "\r\n" + e.ToString());
                    }

                    //Type t1 = Data.GetType().GetGenericArguments()[0];
                    foreach (string s in astr)
                    {
                        if (!ReadCsv(aryType, s, lst ))
                            throw new InvalidDataException("Invalid XML File"
                                + " the element " + Name
                                + " has an invalid value: " + n.InnerText
                                + "\nExpected comma delimited list of numbers.");
                        Array ary = (Array)Activator.CreateInstance(Type, new object[] { lst.Count });
                        lst.CopyTo(ary,0);
                        Data.Add(ary);
                        lst.Clear();
                    }
                }
            }
            else
            {
                // Process as a csv list
                if (!ReadCsv(Type, n.InnerText, Data))
                    throw new InvalidDataException("Invalid XML File"
                        + " the element " + Name
                        + " has an invalid value: " + n.InnerText
                        + "\nExpected comma delimited list of numbers.");

            }
        }
        else
        {
            if (!CUtils.IsTypeNumeric(Type))
                throw new Exception("Error in XML file: '"
                    + "', In element: " + Name
                    + ", Only numeric types are supported for 'Center / Delta' lists");
            ValueType center = CInvalid.Dbl, stepDelta = 0; 
            Int32 stepsUp = 0, stepsDown = 0;
            foreach (XmlNode m in n.ChildNodes)
            {
                switch (m.Name.ToLower())
                {
                case "center":
                    center = (ValueType)Convert.ChangeType(
                        m.InnerText.Trim(), Type); 
                    break;
                case "stepdelta":
                    stepDelta = (ValueType)Convert.ChangeType(
                        m.InnerText.Trim(), Type);
                    break;
                case "stepsup":
                    stepsUp = Int32.Parse(m.InnerText.Trim());
                    break;
                case "stepsdown":
                    stepsDown = Int32.Parse(m.InnerText.Trim());
                    break;
                case "#comment":
                    break;
                default:
                    throw new Exception("Error in XML file: '"
                        + "', In element: " + Name
                        + ", invalid element: " + m.Name);
                }
            }
            Data.Add(center);
            if (!stepDelta.Equals(0) && ((stepsDown > 0) || (stepsUp > 0)))
            {
                double dC = (double)Convert.ChangeType(center, typeof(double));
                double dD = (double)Convert.ChangeType(stepDelta, typeof(double));
                for (Int32 i = 1; i <= stepsDown; i++)
                {
                    double d = dC - (i * dD);
                    Data.Add(Convert.ChangeType(d, Type));
                }
                for (Int32 i = 1; i <= stepsUp; i++)
                {
                    double d = dC + (i * dD);
                    Data.Add(Convert.ChangeType(d, Type));
                }
            }
        }
    } // CListFromXml(IList lst, XmlNode n)
} // CListFromXml{}
share|improve this question
1  
I really don't think deserializing from a variant of CSV inside XML is something that is “done a lot”. Can't you use XML serialization? –  svick Sep 25 '12 at 19:11
    
I had to make the XML as human readable as possible, thus the use of a CSV in the XML. –  Michael Fitzpatrick Sep 25 '12 at 19:21
    
It was my understanding XML was was supposed to be designed for human readability. It certainly isn't designed for compactness –  dreza Nov 16 '12 at 18:21
add comment

1 Answer

Your code is definitely not a standard way of parsing XML in .NET. Usually there are 2 main cases where XML parsing is needed:

  1. You expect a predefined XML structure. In this case all the types of XML nodes are known upfront, so there is no need to specify them inside XML (as you do), and the easiest way to parse such XML is to create classes that correspond to the XML structure and use XmlSerializer to do the work.

    Also I usually create a corresponding XSD that specifies the structure of XML that I expect, and if I don't trust the source of XML I run XSD validation before deserializing the XML into objects. The XML structure that you described (where lists are stored enclosed in curly brackets) can't be validated correctly using XSD.

    There is a variation of this option when some .NET types are not known in advance, but the structure is still fixed, e.g. if you store the information about solution plugins (and the type of plugin is stored in XML). In that case I would either deserialize the XML into objects (leaving extension points as XmlElement's) and then resolve .NET types for plugins, or create a custom IXmlSerializable implementation that loads appropriate types.

  2. You receive an unstructured XML and want to traverse it looking for familiar nodes, patterns, or anything else. In this case proper solution would be to use XDocument or XmlDocument, or even XmlReader if you need maximum performance or expect large documents.

I wouldn't recommend using the code that you provided in production as it tries to use XML in its own unique way, which means you won't get support from existing frameworks, and other clients that may want to generate XML consumed by your service would have to design their own hacks. Also the code itself is quite unreadable due to nested conditions and large methods, consider refactoring it. And it's not recommended to expose class fields as public, even if they are readonly.

share|improve this answer
    
I have very little experience with XML, this is the first time I've ever worked with it. I'll consider your suggestions and look at IXmlSerializable as a better way to implement. –  Michael Fitzpatrick Nov 17 '12 at 22:25
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.