Using wildcards with @XmlPath to parse similar representations

I have a deep XML structure with a lot of pointless wrappers I’m mapping to a single Java class. There are several different files, where the content and structure differs just a little. Since I want to be able to cast the resulting classes to something that is easily comparable (every single representation contains a name for example) I’m wondering whether or not it is possible to specify wildcards for @XmlPath.

Inheritance was my first thought to (partially) solve the problem, but since there is a wrapping element at the top of the structure that differs I’m not sure how that could be solved.

Example XML structure

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:root xsi:schemaLocation="http://www.example.eu/test ResourceSchema.xsd" xmlns:s="http://www.example.eu/test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <s:resource|s:data|s:container|s:stackoverflow>
        <s:information>
            <s:date>2013-07-04</s:date>
            <s:name>This example does not work</s:name>
        </s:information>
        <s:elements>
            <s:refobj>
                <s:id>1</s:id>
                <s:source>First Source</s:source>
            </s:refobj>
            <s:refobj>
                <s:id>2</s:id>
                <s:source>Second Source</s:source>
            </s:refobj>
            <s:refobj>
                <s:id>5</s:id>
                <s:source>Fifth Source</s:source>
            </s:refobj>
        </s:elements>
    </s:resource|s:data|s:container|s:stackoverflow>
</s:root>

The second element in the XML is obviously not valid, although that’s where the structure differs and can contain pretty much anything. The third-level element information does however exist in every single structure, and it even contains information about which type of element that is represented within the XML.

A quick solution would be to create a class for each of the possible elements and then try/catch through all of them until one succeeds, although that seems like a horrible solution.

What is the proper way to solve an XML related problem like this? I don’t have the possibility to change the structure, and I don’t have access to the schema to tell me which elements that has multiple names.

Answer

MOXy doesn’t currently support wild cards in @XmlPath but below is a way that you could accomplish this with a StreamReaderDelegate.

DOMAIN MODEL

Root

package forum17527941;

import java.util.Date;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlPath("s:stackoverflow/s:information/s:date/text()")
    @XmlSchemaType(name="date")
    private Date date = new Date();

    @XmlPath("s:stackoverflow/s:information/s:name/text()")
    private String name;

}

package-info

@XmlSchema(
    namespace="http://www.example.eu/test",
    xmlns={
        @XmlNs(prefix="s", namespaceURI="http://www.example.eu/test")
    },
    elementFormDefault=XmlNsForm.QUALIFIED)
package forum17527941;

import javax.xml.bind.annotation.*;

DEMO CODE

Demo

package forum17527941;

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.transform.stream.StreamSource;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        XMLInputFactory xif = XMLInputFactory.newFactory();
        StreamSource xml = new StreamSource("src/forum17527941/input.xml");
        XMLStreamReader xsr = xif.createXMLStreamReader(xml);
        xsr = new StreamReaderDelegate(xsr) {

            @Override
            public String getLocalName() {
                String localName = super.getLocalName();
                if("resource".equals(localName) || "data".equals(localName) || "container".equals(localName)) {
                    return "stackoverflow";
                }
                return localName;
            }
        };

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(xsr);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.example.eu/test ResourceSchema.xsd");
        marshaller.marshal(root, System.out);
    }

}

input.xml

With the following input the StreamReaderDelegate will cause the resource element to be reported as stackoverflow which matches are mapping.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:root xsi:schemaLocation="http://www.example.eu/test ResourceSchema.xsd" xmlns:s="http://www.example.eu/test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <s:resource>
        <s:information>
            <s:date>2013-07-04</s:date>
            <s:name>This example does not work</s:name>
        </s:information>
    </s:resource>
</s:root>

Output

The output will match the mapping in the domain model.

<?xml version="1.0" encoding="UTF-8"?>
<s:root xsi:schemaLocation="http://www.example.eu/test ResourceSchema.xsd" xmlns:s="http://www.example.eu/test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <s:stackoverflow>
      <s:information>
         <s:date>2013-07-04</s:date>
         <s:name>This example does not work</s:name>
      </s:information>
   </s:stackoverflow>
</s:root>

FOR MORE INFORMATION

Leave a Reply

Your email address will not be published. Required fields are marked *