Lee un documento xml ‘falso’ (fragmento xml) en un objeto XmlTextReader

[Caso] He revelado un montón de ‘archivos xml‘ con metadatos sobre una gran cantidad de documentos en ellos. Al menos, eso fue lo que pedí. Lo que recibí donde ‘archivos xml’ sin un elemento raíz, están estructurados de esta forma (dejé fuera un montón de elementos):

       

[Problema] Cuando bash leer el archivo en un objeto XmlTextReader, falla y me dice que no hay ningún elemento raíz.

[Solución actual] Por supuesto, puedo leer el archivo como una secuencia, añadir y y escribir la secuencia en un nuevo archivo y leerla en XmlTextReader. Que es exactamente lo que estoy haciendo ahora, pero prefiero no ‘manipular’ los datos originales.

[Solución solicitada] Entiendo que debo usar XmlTextReader para esto, con la opción DocumentFragment. Sin embargo, esto da el error de compiletime:

Se produjo una excepción no controlada de tipo ‘System.Xml.XmlException’ en System.Xml.dll

Información adicional: XmlNodeType DocumentFragment no se admite para el análisis de contenido parcial. Línea 1, posición 1.

[Código defectuoso]

 using System.Diagnostics; using System.Xml; namespace XmlExample { class Program { static void Main(string[] args) { string file = @"C:\test.txt"; XmlTextReader tr = new XmlTextReader(file, XmlNodeType.DocumentFragment, null); while(tr.Read()) Debug.WriteLine("NodeType: {0} NodeName: {1}", tr.NodeType, tr.Name); } } } 

Aunque se puede hacer que XmlReader lea los datos utilizando la opción ConformanceLevel.Fragment como lo demuestra Martijn, parece que a XmlDataDocument no le gusta la idea de tener varios elementos raíz.

Pensé que intentaría un enfoque diferente, como el que está utilizando actualmente, pero sin el archivo intermedio. La mayoría de las bibliotecas XML (XmlDocument, XDocument, XmlDataDocument) pueden tomar un TextReader como entrada, por lo que he implementado uno propio. Se usa como tal:

 var dataDocument = new XmlDataDocument(); dataDocument.Load(new FakeRootStreamReader(File.OpenRead("test.xml"))); 

El código de la clase actual:

 public class FakeRootStreamReader : TextReader { private static readonly char[] _rootStart; private static readonly char[] _rootEnd; private readonly TextReader _innerReader; private int _charsRead; private bool _eof; static FakeRootStreamReader() { _rootStart = "".ToCharArray(); _rootEnd = "".ToCharArray(); } public FakeRootStreamReader(Stream stream) { _innerReader = new StreamReader(stream); } public FakeRootStreamReader(TextReader innerReader) { _innerReader = innerReader; } public override int Read(char[] buffer, int index, int count) { if (!_eof && _charsRead < _rootStart.Length) { // Prepend root element return ReadFake(_rootStart, buffer, index, count); } if (!_eof) { // Normal reading operation int charsRead = _innerReader.Read(buffer, index, count); if (charsRead > 0) return charsRead; // We've reached the end of the Stream _eof = true; _charsRead = 0; } // Append root element end tag at the end of the Stream return ReadFake(_rootEnd, buffer, index, count); } private int ReadFake(char[] source, char[] buffer, int offset, int count) { int length = Math.Min(source.Length - _charsRead, count); Array.Copy(source, _charsRead, buffer, offset, length); _charsRead += length; return length; } } 

La primera llamada a Read(...) devolverá solo el elemento . Las llamadas subsiguientes leen la secuencia de manera normal, hasta que se alcanza el final de la secuencia, luego se imprime la etiqueta final.

El código es un poco … meh … principalmente porque quería manejar algunos casos que nunca sucederían cuando alguien intenta leer la secuencia de menos de 6 caracteres a la vez.

Esto funciona:

 using System.Diagnostics; using System.Xml; namespace XmlExample { class Program { static void Main(string[] args) { string file = @"C:\test.txt"; XmlReaderSettings settings = new XmlReaderSettings(); settings.ConformanceLevel = ConformanceLevel.Fragment; using (XmlReader reader = XmlReader.Create(file, settings)) { while (reader.Read()) Debug.WriteLine("NodeType: {0} NodeName: {1}", reader.NodeType, reader.Name); } } } }