2010-01-29 26 views
21

¿Hay alguna manera de obtener la posición actual en la secuencia del nodo bajo examen por XmlReader?obteniendo la posición actual de un XmlReader

Me gustaría usar el XmlReader para analizar un documento y guardar la posición de ciertos elementos para poder buscarlos más tarde.

Adición:

Me estoy Xaml generada por un control de WPF. El Xaml no debe cambiar con frecuencia. Hay marcadores de posición en el Xaml donde necesito reemplazar elementos, a veces en bucle. Pensé que podría ser más fácil hacerlo en código que en una transformación (podría estar equivocado al respecto). Mi idea era analizarlo en una estructura de datos simple de lo que necesita ser reemplazado y dónde está, luego usar un StringBuilder para producir el resultado final al copiar trozos de la cadena xaml.

+1

¿Tuvo éxito con esto? Quiero hacer algo similar (calcular desviaciones de flujo de ciertos elementos, luego buscar ese desplazamiento y analizar a partir de allí), y lo mejor que puedo ver hasta ahora es analizar el archivo dos veces para calcular los desplazamientos. – Rob

+0

@Rob Nope. Terminé usando XmlReader/XmlWriter para procesar el documento cada vez. Son lo suficientemente rápidos para mi propósito que puedo renunciar a esta optimización. – dmo

Respuesta

8

Sólo para atajar una sugerencia antes de que está hecho: que podría mantener una referencia a la secuencia subyacente se pasa en XmlReader, y crea una nota de su posición - pero que le dará los resultados incorrectos, como el lector casi seguramente almacenará en búfer su entrada (es decir, leerá los primeros 1024 caracteres o lo que sea, de modo que su primer nodo podría "aparecer" en el carácter 1024).

Si utiliza XmlTextReader en lugar de sólo XmlReader, a continuación, que implementa IXmlLineInfo, lo que significa que puede pedir la LineNumber y LinePosition en cualquier momento - es eso suficiente bueno para usted? (Probablemente deberías marcar HasLineInfo() en primer lugar.)

EDITAR: Acabo de notar que deseas poder buscar ese puesto más adelante ... en ese caso, la información de la línea puede no ser de mucha ayuda. Es genial para encontrar algo en un editor de texto, pero no tan bueno para mover un puntero de archivo. ¿Podría darnos más información sobre lo que está tratando de hacer? Puede haber una mejor manera de abordar el problema.

+0

Para mi prototipo estaba escribiendo el xml a un escritor, limpiándolo y obteniendo la longitud de ese flujo. Funcionó bien, pero quiero pasar a algo más limpio y menos intensivo en memoria. Gracias por el puntero. – dmo

+0

Parece que XmlTextReader implementa IXmlLineInfo. – dmo

+0

@Downvoter: ¿me gustaría comentar? –

3

Tengo el mismo problema y aparentemente no hay una solución simple.

así que decidí manipular dos de sólo lectura FileStream: una para el XmlReader, el otro para obtener la posición de cada línea:

private void ReadXmlWithLineOffset() 
{ 
    string malformedXml = "<test>\n<test2>\r <test3><test4>\r\n<test5>Thi is\r\ra\ntest</test5></test4></test3></test2>"; 
    string fileName = "test.xml"; 
    File.WriteAllText(fileName, malformedXml); 

    XmlTextReader xr = new XmlTextReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)); 
    FileStream fs2 = new FileStream(fileName, FileMode.Open, FileAccess.Read); 

    try 
    { 
     int currentLine = 1; 
     while(xr.Read()) 
     { 
      if (!string.IsNullOrEmpty(xr.Name)) 
      { 
       for (;currentLine < xr.LineNumber; currentLine++) 
        ReadLine(fs2); 
       Console.WriteLine("{0} : LineNum={1}, FileOffset={2}", xr.Name, xr.LineNumber, fs2.Position); 
      } 
     } 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("Exception : " + ex.Message); 
    } 
    finally 
    { 
     xr.Close(); 
     fs2.Dispose(); 
    } 
} 

private void ReadLine(FileStream fs) 
{ 
    int b; 
    while ((b = fs.ReadByte()) >= 0) 
    { 
     if (b == 10) // \n 
      return; 
     if (b == 13) // \r 
     { 
      if (fs.ReadByte() != 10) // if not \r\n, go back one byte 
       fs.Seek(-1, SeekOrigin.Current); 
      return; 
     } 
    }    
} 

Ésta no es la mejor forma de hacer esto, ya que utiliza dos lectores. Para evitar esto, podríamos reescribir un nuevo FileReader compartido entre XmlReader y el contador de líneas. Pero simplemente le da el desplazamiento de la línea que le interesa. Para obtener el desplazamiento exacto de la etiqueta, debemos usar LinePosition, pero esto puede ser complicado debido a la codificación.

8

He trabajado en una solución para esto, y aunque puede no funcionar en todos los escenarios y utiliza la reflexión contra miembros privados de .NET Framework classes, puedo calcular la posición correcta de XmlReader con el método de extensión mostrado abajo.

Su XmlReader debe ser creado a partir de un StreamReader usando un subyacente FileStream (no he probado otros Streams, y pueden funcionar tan bien, siempre que informan de su posición).

detalles que he publicado aquí: http://g-m-a-c.blogspot.com/2013/11/determine-exact-position-of-xmlreader.html

public static class XmlReaderExtensions 
{ 
    private const long DefaultStreamReaderBufferSize = 1024; 

    public static long GetPosition(this XmlReader xr, StreamReader underlyingStreamReader) 
    { 
     // Get the position of the FileStream 
     long fileStreamPos = underlyingStreamReader.BaseStream.Position; 

     // Get current XmlReader state 
     long xmlReaderBufferLength = GetXmlReaderBufferLength(xr); 
     long xmlReaderBufferPos = GetXmlReaderBufferPosition(xr); 

     // Get current StreamReader state 
     long streamReaderBufferLength = GetStreamReaderBufferLength(underlyingStreamReader); 
     int streamReaderBufferPos = GetStreamReaderBufferPos(underlyingStreamReader); 
     long preambleSize = GetStreamReaderPreambleSize(underlyingStreamReader); 

     // Calculate the actual file position 
     long pos = fileStreamPos 
      - (streamReaderBufferLength == DefaultStreamReaderBufferSize ? DefaultStreamReaderBufferSize : 0) 
      - xmlReaderBufferLength 
      + xmlReaderBufferPos + streamReaderBufferPos - preambleSize; 

     return pos; 
    } 

    #region Supporting methods 

    private static PropertyInfo _xmlReaderBufferSizeProperty; 

    private static long GetXmlReaderBufferLength(XmlReader xr) 
    { 
     if (_xmlReaderBufferSizeProperty == null) 
     { 
      _xmlReaderBufferSizeProperty = xr.GetType() 
              .GetProperty("DtdParserProxy_ParsingBufferLength", 
                  BindingFlags.Instance | BindingFlags.NonPublic); 
     } 

     return (int) _xmlReaderBufferSizeProperty.GetValue(xr); 
    } 

    private static PropertyInfo _xmlReaderBufferPositionProperty; 

    private static int GetXmlReaderBufferPosition(XmlReader xr) 
    { 
     if (_xmlReaderBufferPositionProperty == null) 
     { 
      _xmlReaderBufferPositionProperty = xr.GetType() 
               .GetProperty("DtdParserProxy_CurrentPosition", 
                   BindingFlags.Instance | BindingFlags.NonPublic); 
     } 

     return (int) _xmlReaderBufferPositionProperty.GetValue(xr); 
    } 

    private static PropertyInfo _streamReaderPreambleProperty; 

    private static long GetStreamReaderPreambleSize(StreamReader sr) 
    { 
     if (_streamReaderPreambleProperty == null) 
     { 
      _streamReaderPreambleProperty = sr.GetType() 
               .GetProperty("Preamble_Prop", 
                  BindingFlags.Instance | BindingFlags.NonPublic); 
     } 

     return ((byte[]) _streamReaderPreambleProperty.GetValue(sr)).Length; 
    } 

    private static PropertyInfo _streamReaderByteLenProperty; 

    private static long GetStreamReaderBufferLength(StreamReader sr) 
    { 
     if (_streamReaderByteLenProperty == null) 
     { 
      _streamReaderByteLenProperty = sr.GetType() 
              .GetProperty("ByteLen_Prop", 
                  BindingFlags.Instance | BindingFlags.NonPublic); 
     } 

     return (int) _streamReaderByteLenProperty.GetValue(sr); 
    } 

    private static PropertyInfo _streamReaderBufferPositionProperty; 

    private static int GetStreamReaderBufferPos(StreamReader sr) 
    { 
     if (_streamReaderBufferPositionProperty == null) 
     { 
      _streamReaderBufferPositionProperty = sr.GetType() 
                .GetProperty("CharPos_Prop", 
                   BindingFlags.Instance | BindingFlags.NonPublic); 
     } 

     return (int) _streamReaderBufferPositionProperty.GetValue(sr); 
    } 

    #endregion 
} 
+0

En lugar de utilizar la reflexión para PreamableSize, podría hacerlo de una manera más portátil y a prueba de futuro: sr. CurrentEncoding.GetPreamble(). Length. Además, si dicho método va a regresar mucho, ¿te recomiendo que devuelvas Array.LongLength o simplemente te quedes con devolver un int? – kornman00

+0

De hecho, parece que -todas estas propiedades son entradas de 32 bits en .NET – kornman00

+0

Con respecto al bit donde se comprueba: (streamReaderBufferLength == DefaultStreamReaderBufferSize? DefaultStreamReaderBufferSize: 0) - presumiblemente esto se debe a que el autor de la cadena informa incorrectamente (como cero) la posición del buffer de StreamReader para el último bloque de datos? Descubrí que comprobar sr.EndOfStream y, de ser cierto, establecer la posición a la longitud trabajada (esencialmente la misma lógica) - inusualmente, aunque recuperar la propiedad sr.EndOfStream "solucionó" la posición informada a través de la reflexión. Ho hum. Impresionante solución por cierto. – GHC

2

Gracias Geoff por la respuesta.Funcionó perfectamente en Windows 7. Pero de alguna manera con la versión .net 4 en Windows Server 2003 de mscorlib.dll, tuve que cambiar las siguientes 2 funciones para trabajar.

private long GetStreamReaderBufferLength(StreamReader sr) 
    { 
     FieldInfo _streamReaderByteLenField = sr.GetType() 
              .GetField("charLen", 
                 BindingFlags.Instance | BindingFlags.NonPublic); 

     var fValue = (int)_streamReaderByteLenField.GetValue(sr); 

     return fValue; 
    } 

    private int GetStreamReaderBufferPos(StreamReader sr) 
    { 
     FieldInfo _streamReaderBufferPositionField = sr.GetType() 
              .GetField("charPos", 
                 BindingFlags.Instance | BindingFlags.NonPublic); 
     int fvalue = (int)_streamReaderBufferPositionField.GetValue(sr); 

     return fvalue; 
    } 

también underlyingStreamReader en el método GetPosition debe PEEK para avanzar el puntero.

private long GetPosition(XmlReader xr, StreamReader underlyingStreamReader) 
    { 
     long pos = -1; 
     while (pos < 0) 
     { 
      // Get the position of the FileStream 
      underlyingStreamReader.Peek(); 
      long fileStreamPos = underlyingStreamReader.BaseStream.Position; 

      //   long fileStreamPos = GetStreamReaderBasePosition(underlyingStreamReader); 
      // Get current XmlReader state 
      long xmlReaderBufferLength = GetXmlReaderBufferLength(xr); 
      long xmlReaderBufferPos = GetXmlReaderBufferPosition(xr); 

      // Get current StreamReader state 
      long streamReaderBufferLength = GetStreamReaderBufferLength(underlyingStreamReader); 
      long streamReaderBufferPos = GetStreamReaderBufferPos(underlyingStreamReader); 
      long preambleSize = GetStreamReaderPreambleSize(underlyingStreamReader); 


      // Calculate the actual file position 
      pos = fileStreamPos 
       - (streamReaderBufferLength == DefaultStreamReaderBufferSize ? DefaultStreamReaderBufferSize : 0) 
       - xmlReaderBufferLength 
       + xmlReaderBufferPos + streamReaderBufferPos;// -preambleSize; 
     } 
     return pos; 
    } 
+0

Buena información. Y ahí tienes los peligros de acceder a miembros de framework privados en pantalla completa ... :) –

7

Como dice Jon Skeet, XmlTextReader implementos IXmlLineInfo pero XmlTextReader está desfasado desde .NET 2.0 y la pregunta es acerca única XmlReader. me encontré con esta solución:

XmlReader xr = XmlReader.Create(// MSDN recommends to use Create() instead of ctor() 
    new StringReader("<some><xml><string><data>"), 
    someSettings // furthermore, can't set XmlSettings on XmlTextReader 
); 
IXmlLineInfo xli = (IXmlLineInfo)xr; 

while (xr.Read()) 
{ 
    // ... some read actions ... 

    // current position in StringReader can be accessed through 
    int line = xli.LineNumber; 
    int pos = xli.LinePosition; 
} 

P. S. Probado para .NET Compact Framework 3.5, pero debería funcionar para otros también.

+1

solución perfecta; los otros parecen muy intrincados. tal vez en 2010, esto no estaba disponible? –

Cuestiones relacionadas