Patrik
Patrik

Reputation: 94

Parse xml based on attributes using JAXB

I am using JAXB to parse some xml.

<countries>
    <Name language="en">Australia</Name>
    <Name language="se">Australien</Name>
</countries>

If I in my class Countries use

@XmlElement(name = "Name", required = true)
    protected List<Name> name;

everything works. However I would like to only get the attribute where language="en"

So I in my Countries class have

protected String name

not a collection.

Is there a good way to solve this with some annotation for example?

Upvotes: 3

Views: 3519

Answers (1)

bdoughan
bdoughan

Reputation: 149047

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group

Below are two ways you could handle this use case. The first is a little more code but could be done with any JAXB implementation. The second is less code, but requires you use EclipseLink JAXB (MOXy).

OPTION #1 - ANY JAXB (JSR-222) IMPLEMENTATION

Demo

You could use a filtered stream reader to filter out the unwanted elements and have your JAXB implementation unmarshal that.

package forum11586106;

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

public class Demo {

    private static final String LANGUAGE_CODE = "en";

    public static void main(String[] args) throws Exception {
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource("src/forum11586106/input.xml"));
        xsr = xif.createFilteredReader(xsr, new StreamFilter() {

            private boolean isReading = true;

            @Override
            public boolean accept(XMLStreamReader reader) {
                if(reader.isStartElement() && "Name".equals(reader.getLocalName())) {
                    isReading = LANGUAGE_CODE.equals(reader.getAttributeValue("", "language"));
                    return isReading;
                } else if(reader.isEndElement() && !isReading) {
                    isReading = true;
                    return false;
                }

                return true;
            }});

        JAXBContext jc = JAXBContext.newInstance(Countries.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Countries countries = (Countries) unmarshaller.unmarshal(xsr);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(countries, System.out);
    }

}

Countries

package forum11586106;

import javax.xml.bind.annotation.*;

@XmlRootElement
public class Countries {

    private String name;

    @XmlElement(name="Name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

input.xml

With this approach the language attribute is not included in the output:

<countries>
    <Name language="en">Australia</Name>
    <Name language="se">Australien</Name>
</countries>

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<countries>
    <Name>Australia</Name>
</countries>

OPTION #2 - ECLIPSELINK JAXB (MOXy)

We will leverage MOXy's @XmlPath extension to map to the Name element that has a language attribute with value en (see http://blog.bdoughan.com/2011/03/map-to-element-based-on-attribute-value.html).

Countries

package forum11586106;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
public class Countries {

    private String name;

    @XmlPath("Name[@language='en']/text()")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

jaxb.properties

To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

With this approach the element filtering is handled by the @XmlPath mapping, so the runtime portion becomes much simpler. Note how only the standard JAXB runtime APIs are used.

package forum11586106;

import java.io.File;
import javax.xml.bind.*;

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum11586106/input.xml");
        Countries countries = (Countries) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(countries, System.out);
    }

}

input.xml

<countries>
    <Name language="en">Australia</Name>
    <Name language="se">Australien</Name>
</countries>

Output

With this approach the language attribute is included in the output:

<?xml version="1.0" encoding="UTF-8"?>
<countries>
   <Name language="en">Australia</Name>
</countries>

Upvotes: 4

Related Questions