2012-05-18 12 views
5

Ich versuche, etwas Code von der Verwendung von DOM (über jDOM) zu konvertieren, um stattdessen StAX zu verwenden. Gleichzeitig migriere ich von der DTD-basierten Validierung zur XSD-basierten Validierung. Oh, und nur für ein gutes Maß, ich stelle JAXB in die Gleichung :)StAX und Namespaces

Wie auch immer, als Zwischen Migration Schritt möchte ich Benutzern erlauben, noch Legacy-Dokumente (auch mit DTD und daher kein Namespace) zur Verfügung zu stellen. Ich werde das Dokument weiterhin mithilfe von XSD überprüfen, sodass die DTD ignoriert wird. Dies funktioniert, außer dass StAX (oder JAXB) das nicht namespaced Dokument nicht zu mögen scheint. Ich habe versucht, Namespace-Unterstützung (mit javax.xml.stream.isNamespaceAware) zu deaktivieren, aber das hatte keine Auswirkungen. Durch das explizite Hinzufügen von xmlns zum Dokumentenstamm wurde das Problem behoben, so dass ich ziemlich sicher bin, dass es sich um ein Problem mit dem Namespacing handelt.

Gibt es eine Möglichkeit, StAX XMLEventReader zu verwenden, um einen Standardnamespace "einzuführen"? Etwas in Richtung this approach (das ist SAX spezifisch), aber für StAX ...

Oder irgendwelche anderen Ideen, wie man das erreicht?

Ein Beispiel Dokument wie folgt aussieht:

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

Der Code, den ich zur Zeit diese Dokumente zu lesen, bin mit ist:

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

In meinem die DTD testen dort zu sein oder nicht, keine Wirkung hat. Und wie ich schon sagte einfach,

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

zu

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

behebt die Fehler ich sehe, das sind:

[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

Welche Implementierung von StaX? JDK eingebaut? Holzstampfer? Andere? – bmargulies

+0

JDK gebaut, denke ich. Ich mache nichts besonderes, um ein anderes zu konfigurieren. –

+1

Benutzer verwenden stax, um Dokumente ohne Namespaces ständig zu lesen. Sie müssen uns etwas Code und ein wenig XML zeigen. – bmargulies

Antwort

6

Dies kann durch die Implementierung eines Filters erfolgen, die eine hinzufügt Standard-Namespace-Deklaration zum ersten (dh root) StartELement Ereignis. StAX stellt bereits die Dienstklasse EventReaderDelegate bereit, in der die Methoden peek() und nextEvent() überschrieben werden müssen.

Hier ist der Code:

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

Um zu sehen, wie diese genutzt werden, lassen Sie uns einige XML ohne Namespace-Deklaration kopieren Sie die Ereignis-API system.out mit:

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

den Code ausführen, werden gedruckt

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

Ich musste schließlich den Namespace auf alle Elemente anwenden, nicht nur auf die Wurzel. Aber es hat funktioniert! Vielen Dank –