Xml circular reference

49 views Asked by At

I am trying to produce the following XML using the class structure below.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VirtualDesktopManager>
  <Categories>
    <Category Name="Category 1">
      <Desktops>
        <Desktop Name="Desktop 1">
          <Applications Name="Application 1" />
          <Applications Name="Application 2" />
        </Desktop>
      </Desktops>
    </Category>
  </Categories>
</VirtualDesktopManager>

When executing the code below, I get the exception: System.ArgumentException: 'Cannot insert a node or any ancestor of that node as a child of itself.'. The classes themselves do not have any circular references so I must be doing something wrong.

private static void Main ()
{
    var database = new Database();
    var category = new VirtualDesktopCategory();
    var desktop = new VirtualDesktop();
    var application = new VirtualDesktopApplication();

    category = new VirtualDesktopCategory() { Name = "Cat 1", };
    database.Categories.Add(category);
    desktop = new VirtualDesktop() { Name = "Desktop 1", };
    category.Desktops.Add(desktop);
    application = new VirtualDesktopApplication() { Name = "Application 1", };
    desktop.Applications.Add(application);
    application = new VirtualDesktopApplication() { Name = "Application 2", };
    desktop.Applications.Add(application);

    database.ToXmlDocument().InnerText.Dump();
}

public class Database
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopCategory> Categories { get; private set; } = new();

    public XmlDocument ToXmlDocument()
    {
        var document = new XmlDocument();
        var xml = $@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes"" ?><VirtualDesktopManager></VirtualDesktopManager>";

        document.LoadXml(xml);
        document.DocumentElement?.AppendChild(this.ToXmlElement(document, document.DocumentElement));

        return document;
    }

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategories = document.CreateElement("Categories");

        elementParent.AppendChild(elementCategories);

        foreach (var category in this.Categories)
        {
            // System.ArgumentException: Cannot insert a node or any ancestor of that node as a child of itself.
            elementCategories.AppendChild(category.ToXmlElement(document, elementCategories));
        }

        return elementCategories;
    }
}

public class VirtualDesktopCategory
{
    public string Name { get; set; } = "";
    public List<VirtualDesktop> Desktops { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategory = document.CreateElement("Category");
        var elementDesktops = document.CreateElement("Desktops");

        elementCategory.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementCategory);
        elementCategory.AppendChild(elementDesktops);

        foreach (var desktop in this.Desktops)
        {
            elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));
        }

        return elementCategory;
    }
}

public class VirtualDesktop
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopApplication> Applications { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementDesktop = document.CreateElement("Desktop");
        var elementApplications = document.CreateElement("Applications");

        elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementDesktop);
        elementDesktop.AppendChild(elementApplications);

        foreach (var application in this.Applications)
        {
            elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
        }

        return elementParent;
    }
}

public class VirtualDesktopApplication
{
    public string Name { get; set; } = "";

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementApplication = document.CreateElement("Application");

        elementApplication.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementApplication);

        return elementApplication;
    }
}

public static class Extensions
{
    public static XmlAttribute AppendAttribute(this XmlElement element, XmlDocument document, string name, string value)
    {
        var attribute = document.CreateAttribute(name);

        attribute.Value = value;
        element.Attributes.Append(attribute);

        return attribute;
    }
}

Any pointers would be appreciated.

2

There are 2 answers

2
canton7 On BEST ANSWER
public class VirtualDesktop
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopApplication> Applications { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementDesktop = document.CreateElement("Desktop");
        var elementApplications = document.CreateElement("Applications");

        elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementDesktop);
        elementDesktop.AppendChild(elementApplications);

        foreach (var application in this.Applications)
        {
            elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
        }

        return elementParent;
    }
}

You're returning elementParent, rather than elementDesktop.

You had mis-identified the line throwing the exception. It was actually:

elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));

So, something in desktop.ToXmlElement was the cause. A quick bit of trial-and-error shows that commenting out everything in VirtualDesktop.ToXmlElement doesn't fix it, so it's somewhere between the var elementDesktop = document.CreateElement("Desktop") and return elementParent;... Wait...


You're also serializing the XmlDocument incorrectly. You need to do something like:

using var sr = new StringWriter();
using (var writer = XmlWriter.Create(sr))
{
    database.ToXmlDocument().Save(writer);
}
Console.WriteLine(sr.ToString());

However, this will give you:

<?xml version="1.0" encoding="utf-16" standalone="yes"?>

To fix the encoding, you'll need to do something like this: https://stackoverflow.com/a/1564727/1086121


Working example, with the duplicate lines identified by @EyesShriveledToRaisins commented out.

4
EyesShriveledToRaisins On

Additionally to canton7's answer, look at how you handle the returned node of (some of) your ToXmlElement methods.

Inside those ToXmlElement methods, you add the node to be returned to the given parent element. Then, outside of these methods in the respective foreach loops, you add that node again to the parent element once more.