Search code examples
c#.netxmllinqxelement

C# - Append an XElement array to XElement


I have a c# application, where I'm doing a data compare of two xml files inside a method called RevisionTree. I return a list of elements(XElement) from this method. From the BuildXml method, call that method and save the list as tree. Next I create an xml root XElement. I then loop over each element from tree and add specified descendants (status, msg, date) to the root element, each one of these are XElement. So i should see an xml doument with root, then a list of repeating xml. However, when i try to save the to the writer i get the following error.

Error

Exception thrown: 'System.InvalidOperationException' in System.Private.Xml.dll
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Private.Xml.dll
Token StartDocument in state Document would result in an invalid XML document.

Code

    {
        IEnumerable<XElement>
        var tree = RevisionTree("C:\\Users\\Owner\\source\\repos\\SvnCore\\SvnCore\\old_logs.xml", "C:\\Users\\Owner\\source\\repos\\SvnCore\\SvnCore\\new_logs.xml");

        using (XmlWriter writer = XmlWriter.Create("C:\\Users\\Owner\\source\\repos\\SvnCore\\SvnCore\\Temp.xml", xmlSettings))
        {
            writer.WriteStartDocument();
            var root = new XElement("root");
            foreach (var node in tree)
            {
                root.Add(new XElement("id", node.FirstAttribute));
                root.Add(node.Descendants("status").FirstOrDefault());
                root.Add(node.Descendants("msg").FirstOrDefault());
                root.Add(node.Descendants("date").FirstOrDefault());
            }
            root.Save(writer); 
            writer.WriteEndElement();
            writer.WriteEndDocument();
        }
        return true; 
    }

enter image description here


Solution

  • XElement.Save produces an entire document on its own -- you need XElement.WriteTo, which does not. So either (simplified):

    var sb = new StringBuilder();
    using (var sw = new StringWriter(sb))
    using (XmlWriter writer = XmlWriter.Create(sw)) {
        var root = new XElement("root");
        root.Add(new XElement("id", "1"));
        root.Save(writer);  // no DocumentStart, no ElementStart
    }
    
    <?xml version="1.0" encoding="utf-16"?><root><id>1</id></root>
    

    or (if you wanted to write multiple elements, or for some other reason want to control the document node yourself):

    using (XmlWriter writer = XmlWriter.Create(sw)) {
        writer.WriteStartDocument();
        writer.WriteStartElement("root");
        var notRoot = new XElement("notRoot");
        notRoot.Add(new XElement("id", "1"));
        notRoot.WriteTo(writer); 
        notRoot.WriteTo(writer);
    }
    
    <?xml version="1.0" encoding="utf-16"?><root><notRoot><id>1</id></notRoot><notRoot><id>1</id></notRoot></root>
    

    Note that I'm omitting the End calls, since the XmlWriter will take care of that implicitly.

    If you aren't doing anything interesting with the xmlSettings, the whole thing is even simpler since XElement.Save has an overload that accepts a file name directly, so you don't need an XmlWriter at all.