2012-05-18 16 views
5

Sto provando a convertire del codice dall'utilizzo di DOM (tramite jDOM) per utilizzare invece StAX. Allo stesso tempo sto migrando dalla convalida basata su DTD alla convalida basata su XSD. Oh, e solo per buona misura sto introducendo JAXB nell'equazione :)Stax e spazi dei nomi

In ogni caso, come passaggio di migrazione provvisorio vorrei consentire agli utenti di fornire ancora documenti legacy (ovvero, utilizzando DTD e quindi nessuno spazio dei nomi). Continuerò a convalidare il documento usando XSD, quindi il DTD viene ignorato. Questo funziona eccetto che a StAX (e JAXB) sembra non piacere il documento non assegnato a un nome. Ho provato a disattivare il supporto per lo spazio dei nomi (usando javax.xml.stream.isNamespaceAware), ma ciò non ha avuto alcun effetto. L'aggiunta esplicita di xmlns alla radice del documento ha risolto il problema, quindi sono abbastanza fiducioso che si tratti di un problema di namespacing.

C'è un modo per utilizzare StAX XMLEventReader per "introdurre" uno spazio dei nomi predefinito? Qualcosa sulla falsariga di this approach (che è specifico per SAX), ma per StAX ...

O altre idee su come ottenerlo?

Un documento di esempio appare come:

<?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> 

Il codice Attualmente sto usando per leggere questi documenti è:

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); 
} 

Nel mio test DTD essere lì o non ha alcun effetto. E come ho detto prima, semplicemente cambiando

<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"> 

correzioni dei guasti che vedo, che sono:

[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

Quale implementazione di StaX? Incorporato JDK? Woodstox? Altro? – bmargulies

+0

JDK integrato credo. Non sto facendo nulla di speciale per configurarne un altro. –

+1

Le persone usano stax per leggere documenti senza spazi dei nomi in ogni momento. Devi mostrarci un po 'di codice e un po' di XML. – bmargulies

risposta

6

Questo può essere fatto mediante l'attuazione di un filtro che aggiunge un dichiarazione dello spazio dei nomi predefinita al primo evento (ovvero root) StartELement. StAX fornisce già la classe di utilità EventReaderDelegate, in cui i metodi peek() e nextEvent() devono essere sovrascritti.

Ecco il codice:

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; 
     } 
    } 
} 

vedere come questo viene utilizzato, cerchiamo di copiare alcuni XML senza dichiarazione dello spazio dei nomi per System.out utilizzando l'API 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(); 

L'esecuzione del codice stamperà

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

Alla fine ho dovuto applicare lo spazio dei nomi a tutti gli elementi, non solo a quello di root. Ma ha funzionato! Grazie –