parse - sax java xml
Analizar una secuencia XML sin elemento raĆz (6)
Necesito analizar un flujo continuo de elementos XML bien formados, a los que solo se me ha dado un objeto java.io.Reader
ya construido. Estos elementos no están encerrados en un elemento raíz, ni están precedidos por un encabezado XML como <?xml version="1.0"?>"
, Pero, por lo demás, son XML válidos.
El uso de la clase org.xml.sax.XMLReader
Java no funciona, porque el Lector XML espera analizar XML bien formado, comenzando con un elemento raíz adjunto. Entonces, simplemente lee el primer elemento de la secuencia, que percibe como la raíz, y falla en la siguiente, con el típico
org.xml.sax.SAXParseException: El marcado en el documento que sigue al elemento raíz debe estar bien formado.
Para los archivos que no contienen un elemento raíz, pero donde tal elemento existe o se puede definir (y se llama, por ejemplo, MyRootElement), se puede hacer algo como lo siguiente:
Strint path = <the full path to the file>;
XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
StringBuilder buffer = new StringBuilder();
buffer.append("<?xml version=/"1.0/"?>/n");
buffer.append("<!DOCTYPE MyRootElement ");
buffer.append("[<!ENTITY data SYSTEM /"file:///");
buffer.append(path);
buffer.append("/">]>/n");
buffer.append("<MyRootElement xmlns:...>/n");
buffer.append("&data;/n");
buffer.append("</MyRootElement>/n");
InputSource source = new InputSource(new StringReader(buffer.toString()));
xmlReader.parse(source);
He probado lo anterior guardando parte de la salida de java.io.Reader
en un archivo y funciona. Sin embargo, este enfoque no es aplicable en mi caso y dicha información adicional (encabezado XML, elemento raíz) no se puede insertar, ya que el objeto java.io.Reader
pasado a mi código ya está construido.
Esencialmente, estoy buscando "análisis XML fragmentado". Entonces, mi pregunta es, ¿se puede hacer, utilizando las API estándar de Java (incluidos los org.sax.xml.*
Y java.xml.*
)?
La respuesta 3 funciona, pero para mí tuve que hacer el paso adicional de crear una fuente de entrada desde el SequenceInputStream.
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler((ContentHandler) this);
// Trying to add root element
Enumeration<InputStream> streams = Collections.enumeration(
Arrays.asList(new InputStream[] {
new ByteArrayInputStream("<TopNode>".getBytes()),
new FileInputStream(xmlFile),//bogus xml
new ByteArrayInputStream("</TopNode>".getBytes()),
}));
InputSource is = new InputSource(seqStream);
xmlReader.parse(is);
Puede crear su propio Reader que delega en el Reader proporcionado, de esta forma:
final Reader reader = <whatever you are getting>;
Reader wrappedReader = new Reader()
{
Reader readerCopy = reader;
String start = "<?xml version=/"1.0/"?><MyRootElement>";
String end = "</MyRootElement>";
int index;
@Override
public void close() throws IOException
{
readerCopy.close();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException
{
// You''ll have to get the logic right here - this is only placeholder code
if (index < start.length())
{
// Copy from start to cbuf
}
int result = readerCopy.read(cbuf, off, len);
if (result == -1) {
// Copy from end
}
index += len;
return result;
}
};
Tendrá que completar la lógica para leer primero desde el start
, luego delegar al lector en el medio y, finalmente, cuando el lector esté vacío, lea desde el end
.
Sin embargo, este enfoque funcionará.
Puede escribir su propia Implementación de Reader que encapsule la instancia de Reader que se le da. Este nuevo Reader debería hacer exactamente lo que está haciendo en su código de ejemplo, proporcionar el encabezado y el elemento raíz, luego los datos del lector subyacente y, al final, la etiqueta raíz de cierre. Al ir de esta manera, puede proporcionar una secuencia XML válida al analizador XML y también puede utilizar el objeto Reader que se pasa a su código.
Puedes envolver tu Reader
en una subclase FilterReader
que implementes para hacer más o menos lo que estás haciendo aquí.
Editar:
Si bien esto es similar a la propuesta de implementar su propio Reader
delegando al objeto Reader
dado por un par de otras respuestas, casi todos los métodos en FilterReader
tendrían que ser anulados, por lo que es posible que no obtenga mucho al usar la superclase.
Una variación interesante de las otras propuestas podría ser implementar un SequencedReader
que envuelva varios objetos de Reader
y pase a la siguiente secuencia cuando se agote uno. Luego, puede pasar un objeto StringReader
con el texto de inicio para la raíz que desea agregar, el Reader
original y otro StringReader
con la etiqueta de cierre.
SequenceInputStream viene al rescate:
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
SAXParser parser = saxFactory.newSAXParser();
parser.parse(
new SequenceInputStream(
Collections.enumeration(Arrays.asList(
new InputStream[] {
new ByteArrayInputStream("<dummy>".getBytes()),
new FileInputStream(file),//bogus xml
new ByteArrayInputStream("</dummy>".getBytes()),
}))
),
new DefaultHandler()
);
Solo inserte el elemento raíz ficticio. La solución más elegante en la que puedo pensar es crear su propio InputStream o Reader que envuelva InputSteam / Reader normal y devuelva el dummy <dummyroot>
cuando llame a read () / readLine () por primera vez y luego devuelva el resultado del flujo de carga útil . Esto debería satisfacer el analizador de SAX.