С# XDocument Загрузка с несколькими корнями

У меня есть XML файл без root. Я не могу это изменить. Я пытаюсь разобрать его, но XDocument.Load этого не сделает. Я попытался установить ConformanceLevel.Fragment, но я все еще получаю исключение. У кого-нибудь есть решение?

Я пробовал с XmlReader, но все испортилось и не может заставить его работать правильно. XDocument.Load отлично работает, но если у меня есть файл с несколькими корнями, это не так.

Ответы

Ответ 1

XmlReader сам поддерживает чтение xml-фрагмента - i.e.

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

Однако XDocument.Load не поддерживает чтение фрагментированного xml.

Быстрый и грязный способ состоит в том, чтобы обернуть узлы под одним виртуальным корнем, прежде чем вы вызовете XDocument.Parse. Как:

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

Этот подход ограничен небольшими файлами xml - так как вам сначала нужно прочитать файл в памяти; и объединение больших строк означает перемещение больших объектов в памяти - что лучше избегать.

Если производительность важна, вы должны читать узлы в XDocument один за другим через XmlReader, как объясняется в ответе превосходного @Мартина-Хоннен (fooobar.com/questions/352251/...)

Если вы используете API, который считает само собой разумеющимся, что XmlReader выполняет итерацию по допустимому xml, а производительность имеет значение, вы можете использовать подход с объединенным потоком:

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 - см., например, https://gist.github.com/svejdo1/b9165192d313ed0129a679c927379685

Примечание: XDocument загружает весь xml в память. Поэтому не используйте его для больших файлов - вместо этого используйте XmlReader для итерации и загружайте только хрустящие биты как XElement через XNode.ReadFrom(...)

Ответ 2

Единственными представлениями дерева данных в платформе .NET, которые могут обрабатывать фрагменты, являются XmlDocumentFragment в реализации .NET DOM, поэтому вам нужно будет создать XmlDocument и фрагмент, например,

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

или XPathDocument, где вы можете создать его с помощью XmlReader с ConformanceLevel, установленным на 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();

Очевидно, что XPathNavigator доступен только для чтения.

Если вы хотите использовать LINQ to XML, я согласен с предложениями о том, что вам нужно создать XElement в качестве обертки. Вместо того, чтобы вставлять строку с содержимым файла, вы могли бы использовать XNode.ReadFrom с XmlReader, например.

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;
        }
    }
}

затем

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

Это может работать лучше и эффективнее, чем читать все в строку.

Ответ 3

Если вы хотите использовать XmlDocument.Load(), вам нужно будет обернуть содержимое в корневой каталог node.

или вы можете попробовать что-то вроде этого...

while (xmlReader.Read())
{
    if (xmlReader.NodeType == XmlNodeType.Element)
    {
        XmlDocument d = new XmlDocument();
        d.CreateElement().InnerText = xmlReader.ReadOuterXml();
     }
}

Ответ 4

XML-документ не может содержать более одного элемента root. Требуется один корневой элемент. Вы можете сделать одно. Получите все элементы fragment и оберните их в корневой элемент и проанализируйте его с помощью XDocument.

Это был бы самый лучший и простой способ, о котором можно было бы подумать.