2012-05-18 24 views
5

Estoy tratando de convertir algunos códigos de usar DOM (a través de jDOM) para usar StAX en su lugar. Al mismo tiempo, estoy migrando desde la validación basada en DTD a la validación basada en XSD. Ah, y solo por si acaso, estoy introduciendo JAXB en la ecuación :)StAX y espacios de nombres

De todos modos, como un paso de migración interino, me gustaría permitir que los usuarios sigan proporcionando documentos heredados (aka, utilizando DTD y por lo tanto sin espacio de nombres). Todavía validaré el documento usando XSD, por lo que se ignora el DTD. Esto funciona, excepto que StAX (ni JAXB) parece que no le gusta el documento sin nombre de espacio. Intenté deshabilitar el soporte del espacio de nombres (usando javax.xml.stream.isNamespaceAware), pero eso no tuvo ningún efecto. La adición explícita de xmlns a la raíz del documento solucionó el problema, por lo que estoy bastante seguro de que se trata de un problema de espacio de nombres.

¿Hay alguna manera de usar StAX XMLEventReader para "introducir" un espacio de nombres predeterminado? Algo similar a this approach (que es específico de SAX), pero para StAX ...

¿O alguna otra idea sobre cómo lograr eso?

Un documento de ejemplo se parece a:

<?xml version="1.0"?> 
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 
<hibernate-mapping package="org.hibernate.test.abstractembeddedcomponents.cid"> 
    ... 
</hibernate-mapping> 

El código que estoy utilizando actualmente para leer estos documentos es:

public JaxbRoot unmarshal(InputStream stream, Origin origin) { 
    try { 
     XMLEventReader staxReader = staxFactory().createXMLEventReader(stream); 
     try { 
      return unmarshal(staxReader, origin); 
     } 
     finally { 
      try { 
       staxReader.close(); 
      } 
      catch (Exception ignore) { 
      } 
     } 
    } 
    catch (XMLStreamException e) { 
     throw new MappingException("Unable to create stax reader", e, origin); 
    } 
} 

private XMLInputFactory staxFactory; 

private XMLInputFactory staxFactory() { 
    if (staxFactory == null) { 
     staxFactory = buildStaxFactory(); 
    } 
    return staxFactory; 
} 

@SuppressWarnings({ "UnnecessaryLocalVariable" }) 
private XMLInputFactory buildStaxFactory() { 
    XMLInputFactory staxFactory = XMLInputFactory.newInstance(); 
    // tried with and without, no effect 
    //staxFactory.setProperty("javax.xml.stream.isNamespaceAware", false); 
    return staxFactory; 
} 

@SuppressWarnings({ "unchecked" }) 
private JaxbRoot unmarshal(XMLEventReader staxEventReader, final Origin origin) { 
    XMLEvent event; 
    try { 
     event = staxEventReader.peek(); 
     while (event != null && !event.isStartElement()) { 
      staxEventReader.nextEvent(); 
      event = staxEventReader.peek(); 
     } 
    } 
    catch (Exception e) { 
     throw new MappingException("Error accessing stax stream", e, origin); 
    } 

    if (event == null) { 
     throw new MappingException("Could not locate root element", origin); 
    } 

    final Schema validationSchema; 
    final Class jaxbTarget; 

    final String elementName = event.asStartElement().getName().getLocalPart(); 

    if ("entity-mappings".equals(elementName)) { 
     final Attribute attribute = event.asStartElement().getAttributeByName(ORM_VERSION_ATTRIBUTE_QNAME); 
     final String explicitVersion = attribute == null ? null : attribute.getValue(); 
     validationSchema = validateXml ? resolveSupportedOrmXsd(explicitVersion) : null; 
     jaxbTarget = JaxbEntityMappings.class; 
    } 
    else { 
     validationSchema = validateXml ? hbmSchema() : null; 
     jaxbTarget = JaxbHibernateMapping.class; 
    } 

    final Object target; 
    final ContextProvidingValidationEventHandler handler = new ContextProvidingValidationEventHandler(); 
    try { 
     JAXBContext jaxbContext = JAXBContext.newInstance(jaxbTarget); 
     Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 
     unmarshaller.setSchema(validationSchema); 
     unmarshaller.setEventHandler(handler); 
     target = unmarshaller.unmarshal(staxEventReader); 
    } 
    catch (JAXBException e) { 
     throw new MappingException(...); 
    } 

    return new JaxbRoot(target, origin); 
} 

En mis pruebas, el DTD estar allí o no tiene ningún efecto. Y como he dicho antes, el simple cambio

<hibernate-mapping package="org.hibernate.test.abstractembeddedcomponents.cid"> 

a

<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping" package="org.hibernate.test.abstractembeddedcomponents.cid"> 

correcciones de los errores que veo, que son:

[org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'hibernate-mapping'.] 
    at ... 
Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'hibernate-mapping'. 
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195) 
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131) 
    at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384) 
    at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318) 
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1916) 
    at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:705) 
    at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.startElement(ValidatorHandlerImpl.java:550) 
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.startElement(ValidatingUnmarshaller.java:78) 
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:60) 
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXEventConnector.handleStartElement(StAXEventConnector.java:247) 
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXEventConnector.bridge(StAXEventConnector.java:116) 
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:394) 
    ... 27 more 
+0

¿Qué implementación de StaX? JDK incorporado? Woodstox? ¿Otro? – bmargulies

+0

JDK incorporado, supongo. No estoy haciendo nada especial para configurar otro. –

+1

La gente usa stax para leer documentos sin espacios de nombres todo el tiempo. Necesita mostrarnos algunos códigos y un poco del XML. – bmargulies

Respuesta

6

Esto se puede hacer mediante la aplicación de un filtro que añade una declaración de espacio de nombres predeterminada para el primer evento (es decir, raíz) StartELement. StAX ya proporciona la clase de utilidad EventReaderDelegate, donde los métodos peek() y nextEvent() deben anularse.

Aquí está el código:

import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 

import javax.xml.namespace.QName; 
import javax.xml.stream.XMLEventFactory; 
import javax.xml.stream.XMLEventReader; 
import javax.xml.stream.XMLStreamException; 
import javax.xml.stream.events.StartElement; 
import javax.xml.stream.events.XMLEvent; 
import javax.xml.stream.util.EventReaderDelegate; 

/** 
* Filter adding default namespace declaration to root element. 
*/ 
public class NamespaceAddingEventReader extends EventReaderDelegate { 
    private final XMLEventFactory factory = XMLEventFactory.newInstance(); 
    private final String namespaceURI; 

    private int startElementCount = 0; 

    public NamespaceAddingEventReader(XMLEventReader reader, String namespaceURI) { 
     super(reader); 
     this.namespaceURI = namespaceURI; 
    } 

    /** 
    * Duplicate event with additional namespace declaration. 
    * @param startElement 
    * @return event with namespace 
    */ 
    private StartElement withNamespace(StartElement startElement) { 
     List<Object> namespaces = new ArrayList<Object>(); 
     namespaces.add(factory.createNamespace(namespaceURI)); 
     Iterator<?> originalNamespaces = startElement.getNamespaces(); 
     while (originalNamespaces.hasNext()) { 
      namespaces.add(originalNamespaces.next()); 
     } 
     return factory.createStartElement(
       new QName(namespaceURI, startElement.getName().getLocalPart()), 
       startElement.getAttributes(), 
       namespaces.iterator()); 
    } 

    @Override 
    public XMLEvent nextEvent() throws XMLStreamException { 
     XMLEvent event = super.nextEvent(); 
     if (event.isStartElement()) { 
      if (++startElementCount == 1) { 
       return withNamespace(event.asStartElement()); 
      } 
     } 
     return event; 
    } 

    @Override 
    public XMLEvent peek() throws XMLStreamException { 
     XMLEvent event = super.peek(); 
     if (startElementCount == 0 && event.isStartElement()) { 
      return withNamespace(event.asStartElement()); 
     } else { 
      return event; 
     } 
    } 
} 

Para ver cómo esto se utiliza, vamos a copiar algunos XML sin declaración de espacio de nombres a system.out mediante la API de evento:

StringReader xml = new StringReader("<?xml version='1.0'?><alice>bob</alice>"); 
XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(xml); 
reader = new NamespaceAddingEventReader(reader, "http://foo"); 
XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(System.out); 
writer.add(reader); 
writer.flush(); 

ejecutar el código imprimirá

<?xml version='1.0' encoding='UTF-8'?><alice xmlns="http://foo">bob</alice> 
+0

Terminé teniendo que aplicar el espacio de nombres a todos los elementos, no solo a la raíz. ¡Pero funcionó! Gracias –

Cuestiones relacionadas