C# XDocument Load with multiple roots

Darksody picture Darksody · Aug 12, 2013 · Viewed 16.7k times · Source

I have an XML file with no root. I cannot change this. I am trying to parse it, but XDocument.Load won't do it. I have tried to set ConformanceLevel.Fragment, but I still get an exception thrown. Does anyone have a solution to this?

I tried with XmlReader, but things are messed up and can't get it work right. XDocument.Load works great, but if I have a file with multiple roots, it doesn't.

Answer

Ondrej Svejdar picture Ondrej Svejdar · Aug 12, 2013

XmlReader itself does support reading of xml fragment - i.e.

var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment };
using (var reader = XmlReader.Create("fragment.xml", settings))
{
  // you can work with reader just fine
}

However XDocument.Load does not support reading of fragmented xml.

Quick and dirty way is to wrap the nodes under one virtual root before you invoke the XDocument.Parse. Like:

var fragments = File.ReadAllText("fragment.xml");
var myRootedXml = "<root>" + fragments + "</root>";
var doc = XDocument.Parse(myRootedXml);

This approach is limited to small xml files - as you have to read file into memory first; and concatenating large string means moving large objects in memory - which is best avoided.

If performance matters you should be reading nodes into XDocument one-by-one via XmlReader as explained in excellent @Martin-Honnen 's answer (https://stackoverflow.com/a/18203952/2440262)

If you use API that takes for granted that XmlReader iterates over valid xml, and performance matters, you can use joined-stream approach instead:

using (var jointStream = new MultiStream())
using (var openTagStream = new MemoryStream(Encoding.ASCII.GetBytes("<root>"), false))
using (var fileStream = 
  File.Open(@"fragment.xml", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var closeTagStream = new MemoryStream(Encoding.ASCII.GetBytes("</root>"), false))
{
    jointStream.AddStream(openTagStream);
    jointStream.AddStream(fileStream);
    jointStream.AddStream(closeTagStream);
    using (var reader = XmlReader.Create(jointStream))
    {
        // now you can work with reader as if it is reading valid xml
    }
}

MultiStream - see for example https://gist.github.com/svejdo1/b9165192d313ed0129a679c927379685

Note: XDocument loads the whole xml into memory. So don't use it for large files - instead use XmlReader for iteration and load just the crispy bits as XElement via XNode.ReadFrom(...)