24

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.

psubsee2003
  • 8,563
  • 8
  • 61
  • 79
Darksody
  • 481
  • 1
  • 4
  • 15

4 Answers4

21

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(...)

Community
  • 1
  • 1
Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
16

The only in-memory tree representations in the .NET framework that can deal with fragments are the XmlDocumentFragment in .NET's DOM implementation so you would need to create an XmlDocument and a fragment with e.g.

XmlDocument doc = new XmlDocument();
XmlDocumentFragment frag = doc.CreateDocumentFragment();
frag.InnerXml = stringWithXml; // for instance 
                               // frag.InnerXml = File.ReadAllText("fragment.xml");

or is XPathDocument where you can create one using an XmlReader with ConformanceLevel set to Fragment:

XPathDocument doc;
using (XmlReader xr = 
                 XmlReader.Create("fragment.xml", 
                                   new XmlReaderSettings()
                                   {
                                       ConformanceLevel = ConformanceLevel.Fragment
                                    }))
{
  doc = new XPathDocument(xr);
}

// new create XPathNavigator for read out data e.g.
XPathNavigator nav = doc.CreateNavigator();

Obviously XPathNavigator is read-only.

If you want to use LINQ to XML then I agree with the suggestions made that you need to create an XElement as a wrapper. Instead of pulling in a string with the file contents you could however use XNode.ReadFrom with an XmlReader e.g.

public static class MyExtensions
{
    public static IEnumerable<XNode> ParseFragment(XmlReader xr)
    {
        xr.MoveToContent();
        XNode node;
        while (!xr.EOF && (node = XNode.ReadFrom(xr)) != null)
        {
            yield return node;
        }
    }
}

then

XElement root = new XElement("root", 
                             MyExtensions.ParseFragment(XmlReader.Create(
                                 "fragment.xml", 
                                 new XmlReaderSettings() {
                                 ConformanceLevel = ConformanceLevel.Fragment })));

That might work better and more efficiently than reading everything into a string.

Annie
  • 3,090
  • 9
  • 36
  • 74
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
1

If you wanted to use XmlDocument.Load() then you would need to wrap the content in a root node.

or you could try something like this...

while (xmlReader.Read())
{
    if (xmlReader.NodeType == XmlNodeType.Element)
    {
        XmlDocument d = new XmlDocument();
        d.CreateElement().InnerText = xmlReader.ReadOuterXml();
     }
}
Squirrel5853
  • 2,376
  • 1
  • 18
  • 34
0

XML document cannot have more than one root elements. One root element is required. You may do one thing. Get all the fragment elements and wrap them into a root element and parse it with XDocument.

This would be the best and easiest approach that one could think of.

Vivek Jain
  • 3,811
  • 6
  • 30
  • 47