С# 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
.
Это был бы самый лучший и простой способ, о котором можно было бы подумать.