2

I am working to understand the details of xml deserialization. While I have taken the string I am passing to an online parser that turns Xml into C# data contract class notation, I cannot seem to see what in my Xml or class object code is causing this error. I am sure it is something small and syntactical.

The following exception is raised:

InvalidOperationException: < folder xmlns=' '> was not expected.

When executing code:

string xml =
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
    "<folder name=\"c\">" +
        "<folder name=\"program files\">" +
            "<folder name=\"uninstall information\" />" +
        "</folder>" +
        "<folder name=\"users\" />" +
    "</folder>";

foreach (string name in Folders.FolderNames(xml, 'u'))
    Console.WriteLine(name);

The expected output, to console (in any order):

uninstall information

users

Supporting class code:

[XmlRoot(ElementName = "folder")]
public class Folder
{
    [XmlAttribute(AttributeName = "name")]
    public string Name { get; set; }
}

public class Folders
{        
    public static IEnumerable<string> FolderNames(string xml, char startingLetter)
    {
        List<Folder> folders = null;
        XmlSerializer serializer = new XmlSerializer(typeof(List<Folder>));
        StringReader reader = new StringReader(xml);
        folders = (List<Folder>)serializer.Deserialize(reader);

        List<string> result = new List<string>();
        foreach (Folder folder in folders)
        {
            if (folder.Name.StartsWith(startingLetter.ToString()))
            {
                result.Add(folder.Name);
            }
        }
        reader.Close();
        return result;
    }
}
Community
  • 1
  • 1
Adam Cox
  • 3,341
  • 1
  • 36
  • 46

4 Answers4

2

See my current answer code below. This is a work in progress because Testdome says it is 50% correct. Current answer code passes 2 of Testdome's 4 unit tests. This is bewildering to me since it passes all my unit tests.

Testdome unit test results:

  • Example case: Correct answer

  • All folder names start with starting letter: Correct answer

  • Root folder name starts with starting letter: Wrong answer*

  • Complicated folder structure: Wrong answer*

*Wrong answers above reveals: "Your code returned a wrong answer for the test case. Create your own test cases to figure out where the code goes wrong."

EDIT

I also requested "Show hint", and the Testdome engine presented this:

Hint 1: XDocument provides a convenient LINQ based approach to parsing XML.

This is interesting because it would change my answer completely. With this I don't believe recursive function is required. Using LINQ, it would be possible to just build the resulting list from the deserialized Xml. I will work on that and post back my findings...

My first answer code (using recursion -- WRONG ANSWER -- keep reading):

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

[XmlRoot(ElementName = "folder")]
public class Folder
{
    [XmlAttribute(AttributeName = "name")]
    public string Name { get; set; }

    [XmlElement(ElementName = "folder")]
    public List<Folder> children { get; set; }
}

public class Folders
{
    public static IEnumerable<string> FolderNames(string xml, char startingLetter)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(Folder));
        StringReader reader = new StringReader(xml);
        Folder folders = (Folder)serializer.Deserialize(reader);

        searchFolders(folders, startingLetter);

        reader.Close();
        return searchFolderResults;
    }

    private static List<string> searchFolderResults = new List<string>();

    private static void searchFolders(Folder node, char startingLetter)
    {
        if (node.Name.StartsWith(startingLetter.ToString()))
        {
            searchFolderResults.Add(node.Name);
        }
        foreach (Folder folder in node.children)
            searchFolders(folder, startingLetter);           
    }

    public static void Main(string[] args)
    {
        string xml =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "<folder name=\"c\">" +
            "<folder name=\"program files\">" +
                "<folder name=\"uninstall information\" />" +
                "<folder name=\"cusers3\" />" +
            "</folder>" +
            "<folder name=\"users\">" +
                "<folder name=\"users2\" />" +
            "</folder>" +
        "</folder>";

        foreach (string name in Folders.FolderNames(xml, 'c'))
            Console.WriteLine(name);
    }
}

EDIT

My latest code (using XDocument and LINQ, and another unit test passing):

Testdome unit test results:

  • Example case: Correct answer

  • All folder names start with starting letter: Correct answer

  • Root folder name starts with starting letter: Correct answer

  • Complicated folder structure: Wrong answer*

*Wrong answers above reveals: "Your code returned a wrong answer for the test case. Create your own test cases to figure out where the code goes wrong."

I hit up the "Show hint" again, and now it has a 2nd hint:

Hint 2: The root node of a document may also need to be considered.

So I am off again to keep working until I resolve all the Testdome unit tests. They claim this is a hard C# problem. I suppose...

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;


[XmlRoot(ElementName = "folder")]
public class Folder
{
    [XmlAttribute(AttributeName = "name")]
    public string Name { get; set; }

    [XmlElement(ElementName = "folder")]
    public List<Folder> children { get; set; }
}

public class Folders
{
    public static IEnumerable<string> FolderNames(string xml, char startingLetter)
    {
        return
            from row in XDocument.Parse(xml).Descendants()
            where row.FirstAttribute.Value.StartsWith(startingLetter.ToString())
            select row.FirstAttribute.Value;
    }

    public static void Main(string[] args)
    {
        string xml =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<folder name=\"c\">" +
                "<folder name=\"program files\">" +
                    "<folder name=\"uninstall information\" />" +
                    "<folder name=\"cusers3\" />" +
                "</folder>" +
                "<folder name=\"users\">" +
                    "<folder name=\"users2\" />" +
                "</folder>" +
            "</folder>";

        foreach (string name in Folders.FolderNames(xml, 'c'))
            Console.WriteLine(name);
    }
}

LAST EDIT ( I promise)

The issue with my latest answer above (involving XDocument and LINQ) is that I had used Contains instead of StartsWith, and this failed on a complex test where "cusers3" was not being filtered when searching for "u". After correcting this logic it works flawless in Testdome's test engine. Thanks!!

BTW.. the 2nd hint (see above) was a red herring (not the fishy kind).

Community
  • 1
  • 1
Adam Cox
  • 3,341
  • 1
  • 36
  • 46
0

Try following which has been tested

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<folders name=\"c\">" +
                    "<folder name=\"program files\">" +
                        "<folder name=\"uninstall information\" />" +
                    "</folder>" +
                    "<folder name=\"users\" />" +
                "</folders>";

            List<string> folders =  Folders.FolderNames(xml, 'u').ToList();
        }
    }
    [XmlRoot(ElementName = "folder")]
    public class Folder
    {
        [XmlAttribute(AttributeName = "name")]
        public string Name { get; set; }
    }

    [XmlRoot(ElementName = "folders")]
    public class Folders
    {
        [XmlElement("folder")]
        public List<Folder> folders { get; set; }

        public static IEnumerable<string> FolderNames(string xml, char startingLetter)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Folders));
            StringReader reader = new StringReader(xml);
            Folders folders = (Folders)serializer.Deserialize(reader);

            List<string> result = new List<string>();
            foreach (Folder folder in folders.folders)
            {
                if (folder.Name.StartsWith(startingLetter.ToString()))
                {
                    result.Add(folder.Name);
                }
            }
            reader.Close();
            return result;
        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • 1
    Thanks jdweng. I checked your answer and while it does compile and output a result, the result is incomplete. It outputs "users", and should also output "uninstall information". I think this is closer. I noticed you changed a node name in the xml from "folder" to "folders", and wondering if this is a deviation off track to the solution. I believe there is some recursion required here whereby a "folder" may contain other nodes of name "folder", and so on. Therefore, with recursion, it is possible to iterate through nested nodes of "folder". Thanks. I am still looking at this problem... – Adam Cox Aug 11 '17 at 13:05
  • Serialize method will not do recursion. And your class structures doesn't contain child nodes. For recursive xml see the following posting : https://stackoverflow.com/questions/28976601/recursion-parsing-xml-file-with-attributes-into-treeview-c-sharp – jdweng Aug 11 '17 at 13:23
  • See my answer. It is possible to do recursion with Xml structure, and to spite that my code doesn't pass Testdome's unit test engine checks. Even though it passes all my tests. I ran many tests, and this thing outputs exactly what is expected, but Testdome says it fails. I will post edit to my answer with the Testdome unit tests results.. – Adam Cox Aug 11 '17 at 14:17
0

Another solution using System.Xml.Linq and System.Linq;

using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;

namespace FoldersApp
{

    public class Folders
    {

        public static IEnumerable<string> FolderNames(string xml, char startingLetter)
        {
            return XDocument.Parse(xml).Descendants()
                 .Where(x =>
                 x.Attribute("name") != null &&
                 x.Attribute("name").Value.StartsWith(Char.ToString(startingLetter)))
                 .Select(x => x.Attribute("name").Value);
        }

        public static void Main(string[] args)
        {
            string xml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<folder name=\"c\">" +
                    "<folder name=\"program files\">" +
                        "<folder name=\"uninstall information\" />" +
                    "</folder>" +
                    "<folder name=\"users\" />" +
                "</folder>";

            foreach (string name in Folders.FolderNames(xml, 'u'))
            Console.WriteLine(name);

        }
    }


}
Harry Geo
  • 1,163
  • 3
  • 10
  • 24
0

Here is the complete solution

using System;
using System.Collections.Generic;
using System.Xml;

namespace Folders
{
    class Program
    {
        public static List<string> FolderNames(string xml, char startingLetter)
        {
            var list = new List<string>();
            var doc = new XmlDocument();
            doc.LoadXml(xml);

            FindNodes(list, doc.ChildNodes, startingLetter);

            return list;
        }

        private static void FindNodes(List<string> results, XmlNodeList nodes, char startingLetter)
        {
            foreach (XmlNode item in nodes)
            {
                if (item.Attributes == null)
                    continue;

                if (item.Attributes["name"] == null)
                    continue;

                var folder = item.Attributes["name"];
                if (folder.Value.ToLower().StartsWith(startingLetter.ToString()))
                {
                    results.Add(folder.Value);
                }

                if (!item.HasChildNodes)
                    continue;

                FindNodes(results, item.ChildNodes, startingLetter);
            }
        }

        public static void Main(string[] args)
        {
            string xml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<folder name=\"c\">" +
                    "<folder name=\"program files\">" +
                        "<folder name=\"uninstall information\" />" +
                    "</folder>" +
                    "<folder name=\"users\" />" +
                "</folder>";

            foreach (string name in FolderNames(xml, 'u'))
                Console.WriteLine(name);

            Console.ReadKey();
        }
    }
}
JohnnBlade
  • 4,261
  • 1
  • 21
  • 22