2010-08-04 11 views
7

Actualmente estoy usando SAX (Java) para analizar un puñado de documentos XML diferentes, cada documento representa datos diferentes y tiene estructuras ligeramente diferentes. Por esta razón, cada documento XML es manejado por una clase SAX diferente (subclases DefaultHandler).Uso de SAX para analizar elementos XML comunes

Sin embargo, hay algunas estructuras XML que pueden aparecer en todos estos documentos diferentes. Idealmente, me gustaría decirle al analizador: "Oye, cuando llegues a un elemento complex_node, simplemente usa ComplexNodeHandler para leerlo, y devuélveme el resultado. Si llegas a some_other_node, usa OtherNodeHandler para leerlo y devolverme esa información. resultado".

Sin embargo, no veo una manera obvia de hacerlo.

¿Debo simplemente hacer una clase de controlador monolítico que pueda leer todos los documentos diferentes que tengo (y erradicar la duplicación de código), o hay una forma más inteligente de manejar esto?

+0

¡Espero/seguro que me he perdido una solución dolorosamente obvia! – Dave

+0

es SAX un requisito? ¿Qué hay de usar xpath con DOM, XOM o vtd-xmL? –

+0

Porque SAX es el más rápido y utiliza la menor cantidad de memoria, lo cual es importante en los dispositivos móviles (que no mencioné cuando hice esta pregunta originalmente). – Dave

Respuesta

11

A continuación se muestra una respuesta que hice a una pregunta similar (Skipping nodes with sax). Demuestra cómo intercambiar controladores de contenido en un XMLReader.

En este ejemplo, el intercambio en ContentHandler simplemente ignora todos los eventos hasta que se pierde el control, pero puede adaptar el concepto fácilmente.


Se podría hacer algo como lo siguiente:

import javax.xml.parsers.SAXParser; 
import javax.xml.parsers.SAXParserFactory; 
import org.xml.sax.XMLReader; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     SAXParserFactory spf = SAXParserFactory.newInstance(); 
     SAXParser sp = spf.newSAXParser(); 
     XMLReader xr = sp.getXMLReader(); 
     xr.setContentHandler(new MyContentHandler(xr)); 
     xr.parse("input.xml"); 
    } 
} 

MyContentHandler

Esta clase es responsable de procesar el documento XML. Cuando tocas un nodo que quieres ignorar, puedes intercambiar el IgnoringContentHandler que tragará todos los eventos para ese nodo.

import org.xml.sax.Attributes; 
import org.xml.sax.ContentHandler; 
import org.xml.sax.Locator; 
import org.xml.sax.SAXException; 
import org.xml.sax.XMLReader; 

public class MyContentHandler implements ContentHandler { 

    private XMLReader xmlReader; 

    public MyContentHandler(XMLReader xmlReader) { 
     this.xmlReader = xmlReader; 
    } 

    public void setDocumentLocator(Locator locator) { 
    } 

    public void startDocument() throws SAXException { 
    } 

    public void endDocument() throws SAXException { 
    } 

    public void startPrefixMapping(String prefix, String uri) 
      throws SAXException { 
    } 

    public void endPrefixMapping(String prefix) throws SAXException { 
    } 

    public void startElement(String uri, String localName, String qName, 
      Attributes atts) throws SAXException { 
     if("sodium".equals(qName)) { 
      xmlReader.setContentHandler(new IgnoringContentHandler(xmlReader, this)); 
     } else { 
      System.out.println("START " + qName); 
     } 
    } 

    public void endElement(String uri, String localName, String qName) 
      throws SAXException { 
     System.out.println("END " + qName); 
    } 

    public void characters(char[] ch, int start, int length) 
      throws SAXException { 
     System.out.println(new String(ch, start, length)); 
    } 

    public void ignorableWhitespace(char[] ch, int start, int length) 
      throws SAXException { 
    } 

    public void processingInstruction(String target, String data) 
      throws SAXException { 
    } 

    public void skippedEntity(String name) throws SAXException { 
    } 

} 

IgnoringContentHandler

Cuando el IgnoringContentHandler se hace tragar eventos se pasa el control de nuevo a su ContentHandler principal.

import org.xml.sax.Attributes; 
import org.xml.sax.ContentHandler; 
import org.xml.sax.Locator; 
import org.xml.sax.SAXException; 
import org.xml.sax.XMLReader; 

public class IgnoringContentHandler implements ContentHandler { 

    private int depth = 1; 
    private XMLReader xmlReader; 
    private ContentHandler contentHandler; 

    public IgnoringContentHandler(XMLReader xmlReader, ContentHandler contentHandler) { 
     this.contentHandler = contentHandler; 
     this.xmlReader = xmlReader; 
    } 

    public void setDocumentLocator(Locator locator) { 
    } 

    public void startDocument() throws SAXException { 
    } 

    public void endDocument() throws SAXException { 
    } 

    public void startPrefixMapping(String prefix, String uri) 
      throws SAXException { 
    } 

    public void endPrefixMapping(String prefix) throws SAXException { 
    } 

    public void startElement(String uri, String localName, String qName, 
      Attributes atts) throws SAXException { 
     depth++; 
    } 

    public void endElement(String uri, String localName, String qName) 
      throws SAXException { 
     depth--; 
     if(0 == depth) { 
      xmlReader.setContentHandler(contentHandler); 
     } 
    } 

    public void characters(char[] ch, int start, int length) 
      throws SAXException { 
    } 

    public void ignorableWhitespace(char[] ch, int start, int length) 
      throws SAXException { 
    } 

    public void processingInstruction(String target, String data) 
      throws SAXException { 
    } 

    public void skippedEntity(String name) throws SAXException { 
    } 

} 
+0

Hmm, no me di cuenta de que XMLReader podría cambiarse sobre la marcha de esa manera.Definitivamente parece ser la mejor forma de manejarlo. – Dave

+0

XMLReader fue diseñado para hacer justamente eso, consulte http://download-llnw.oracle.com/javase/6/docs/api/org/xml/sax/XMLReader.html#setContentHandler(org.xml.sax.ContentHandler), hacemos uso de esto en nuestra implementación JAXB MOXy cuando hacemos el procesamiento SAX tenemos un ContentHandler por objeto que se está construyendo. –

+0

@Blaise Doughan Antes que nada, gracias por esta solución es exactamente lo que he estado buscando. Tengo una pregunta, sí. ¿Hay algún pensamiento especial detrás de la evaluación de la profundidad de la estructura para saber cuándo volver al controlador de contenido principal? ¿Hay algún problema al usar el método endDocument() para este propósito? –

0

Puede tener un controlador (ComplexNodeHandler) que maneja solo algunas partes de un documento (complex_node) y pasa todas las demás piezas a otro controlador. El constructor de ComplexNodeHandler tomaría el otro controlador como parámetro. Quiero decir algo como esto:

class ComplexNodeHandler { 

    private ContentHandler handlerForOtherNodes; 

    public ComplexNodeHandler(ContentHandler handlerForOtherNodes) { 
     this.handlerForOtherNodes = handlerForOtherNodes; 
    } 

    ... 

    public startElement(String uri, String localName, String qName, Attributes atts) { 
     if (currently in complex node) { 
      [handle complex node data] 
     } else { 
      // pass the event to the document specific handler 
      handlerForOtherNodes.startElement(uri, localName, qName, atts); 
     } 
    } 

    ... 

} 

Todavía podría haber mejores alternativas ya que no estoy tan familiarizado con SAX. Escribir un controlador de base para las partes comunes y heredarlo también podría funcionar, pero no estoy seguro de si usar heredad aquí es una buena idea.

+0

Consideré esto, pero rápidamente determiné que sería bastante complejo. Tendría que desviar las llamadas no solo de 'startElement', sino de' endElement', 'characters', y de los manejadores de errores. – Dave

Cuestiones relacionadas