1

I am trying to create an unmarshaller that will work for the following XML files:

<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd 
  xml:lang="en">
  [...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd" 
  xmlns:configuration="http://eclipse.org/rmf/pror/toolextensions/1.0"
  xmlns:id="http://pror.org/presentation/id"
  xmlns:xhtml="http://www.w3.org/1999/xhtml">
  [...]
</REQ-IF>
<?xml version="1.0" encoding="UTF-8"?>
  <REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  xmlns:doors="http://www.ibm.com/rdm/doors/REQIF/xmlns/1.0"
  xmlns:reqif="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  xmlns:reqif-common="http://www.prostep.org/reqif"
  xmlns:reqif-xhtml="http://www.w3.org/1999/xhtml"
  xmlns:rm="http://www.ibm.com/rm"
  xmlns:rm-reqif="http://www.ibm.com/rm/reqif"
  xmlns:xhtml="http://www.w3.org/1999/xhtml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  [...]
</REQ-IF>

All those files are structurally the same and are based of the same top-level namespace, but also contain a variety of variable sub-level namespaces and other "things" (which by my understanding should be attributes, but are not), which need to be saved in the system.

Thus far, I have managed to get to the point where this much is saved:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd">
  [...]
</REQ-IF>

however, my intended result would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<REQ-IF xmlns="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd 
  xml:lang="en">
  [...]
</REQ-IF>

So the top-level namespace is saved, but the sub-level namespaces and other "things" are lost in the import/export process. This is bad.

How can I save those other sub-namespaces and other "things", considering that they are dynamically generated?

Basically, what I want to say is "save all these extra attributes in any way you like while parsing the XML, and once you export the XML again, re-write them exactly as they were".

Kira Resari
  • 1,718
  • 4
  • 19
  • 50
  • So your use case is to read an XML using JAXB, perform some work with it and serialize it back the same way it was in the beginning? Should structure of the incoming message change during the processing? – Daniil Feb 20 '20 at 08:24
  • @Daniil Effectively, I'm working with ReqIF documents here, which are pretty complex. The structure of the file itself should say the same, but I want to be able to attribute values deeper down in the file structure. The root elements and its attributes should stay the same. – Kira Resari Feb 20 '20 at 08:35
  • Will e.g. solution proposed in this SO answer https://stackoverflow.com/a/7736235/2792888 be enough or you need dynamically save namespace declarations for each concrete XML? – Daniil Feb 20 '20 at 08:38
  • 1
    JAXB doesn't do that. You need finer control over the XML interpretation/generation. Consider JDOM 2 – kumesana Feb 20 '20 at 08:48
  • @Danill : I already checked that answer, and regrettably it does not satisfy the requirements of my problem. As you said, I need to be able to dynamically read out the namespace declarations for each XML. Also, I do have a package-info.java with `namespace="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd", xmlns={ @XmlNs(prefix="xsi", namespaceURI="http://www.w3.org/2001/XMLSchema-instance"), }`. However, even with that file in place (without it, I get an `unexpected element`-error), the `xmlns:xsi`-namespace is not parsed. – Kira Resari Feb 20 '20 at 08:49
  • @kumesana : Thanks, I'll accept that as the answer I've been looking for. I've now set up the parsing in JDOM2, and am very pleased with the control it gives me over this. – Kira Resari Feb 21 '20 at 07:31

2 Answers2

1

If your main use case is to read ReqIF files, consider that there is an open source implementation of a ReqIF (de)serializer at https://www.eclipse.org/rmf/

0

Unfortunately it seems that JAXB alone is not capable of manage all the namespace prefixes dynamically and you need to combine it with another parsing mechanism.

I would try to implement something like this (only rough implementation, details below):

public class MyXmlHandler {

    XMLInputFactory xif = XMLInputFactory.newInstance();

    XMLOutputFactory xof = XMLOutputFactory.newInstance();

    XMLEventFactory xef = XMLEventFactory.newInstance();

    /**
     * Retrieve XMLEvent for root element
     */
    public StartElement getStartElement(String source) throws XMLStreamException {
        XMLEvent event;
        XMLEventReader reader = xif.createXMLEventReader(new StringReader(source));
        while (reader.hasNext()) {
            event = reader.nextEvent();
            if (event.isStartElement()) {
                return event.asStartElement();
            }
            // alternativery you can retrieve here also QNames for first level child elements 
            // and return all this data in some synthetic wrapper class
        }
        return null; // alternatively throw an exception
    }

    /**
     * Write root element, than some content from JAXB elements, than end element
     */
    public void write(
        Marshaller marshaller, 
        Writer writer, 
        StartElement root, 
        List<JAXBElement> elements
    ) throws JAXBException, XMLStreamException {
        XMLEventWriter xew = xof.createXMLEventWriter(writer);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
        xew.add(root);
        for(JAXBElement element : elements) {
            marshaller.marshal(element, xew);
        }
        xew.add(xef.createEndElement(root.getName(), root.getNamespaces()));
        xew.close();
    }

}

And use it like this:

// create JAXB context and unmarshaller
JAXBContext ctx = JAXBContext.newInstance(RootClass.class);
Unmarshaller unmarshaller = ctx.createUnmarshaller();

// unmarshall XML
JAXBElement<RootClass> element = unmarshaller.unmarshal(source, RootClass.class);
RootClass rootValue = element.getValue();

// extract root element data from XML
StartElement root = handler.getStartElement(data);

// perform some business logic

// create marshaller
Marshaller marshaller = ctx.createMarshaller();

// create list of JAXBElements for root children
List<JAXBElement> elements = new ArrayList<>();
QName qname = ... // construct qualified name or retrieve it from saved structure
// very schematic, names of the child elements depend on your implementation
elements.add(new JAXBElement(qname , rootValue.getChild().getClass(), rootValue.getChild()));
handler.write(marshaller, writer, root, elements);

When preserving root element data, you can save also QNames for its children in some wrapper class. So far I see the REQ-IF structure contains header, core content and tool extensions. You could save QNames for all of them and then use them for constructing JAXB elements during marshalling process.

Daniil
  • 913
  • 8
  • 19