3

I have a class Person with attributes name and address. I display it in a XML. While unmarshalling from XML will it be possible to get line number for name and address separately. I tried using Locator. But it does not provide individual line numbers.

Anand B
  • 2,997
  • 11
  • 34
  • 55

2 Answers2

3

The EclipseLink JAXB (MOXy) and the JAXB reference implementation each have their own @XmlLocation annotations for supporting this use case. This allows you to store the location on the XML element corresponding to the object as an instance of org.xml.sax.Locator. Since I'm the MOXy lead, I will demonstrate using MOXy:

Person

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;

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

    @XmlJavaTypeAdapter(value=StringAdapter.class)
    String name;

    Address address;

    @XmlLocation
    Locator locator;

}

Address

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;

public class Address {

    @XmlJavaTypeAdapter(value=StringAdapter.class)
    private String street;

    @XmlLocation
    Locator locator;

}

StringAdapter

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.eclipse.persistence.oxm.annotations.XmlLocation;
import org.xml.sax.Locator;

public class StringAdapter extends XmlAdapter<StringAdapter.AdaptedString, String> {

    public static class AdaptedString {

        @XmlValue
        public String value;

        @XmlLocation
        @XmlTransient
        Locator locator;

    }

    @Override
    public String unmarshal(AdaptedString v) throws Exception {
        System.out.println(v.value + " " + v.locator.getLineNumber());
        return v.value;
    }

    @Override
    public AdaptedString marshal(String v) throws Exception {
        AdaptedString adaptedString = new AdaptedString();
        adaptedString.value = v;
        return adaptedString;
    }

}

jaxb.properties

To specify 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.

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

Demo

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

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum14455596/input.xml");
        Person person = (Person) unmarshaller.unmarshal(xml);

        System.out.println("Person:  " + person.locator.getLineNumber());
        System.out.println("Address:  " + person.address.locator.getLineNumber());
    }

}

Output

Jane Doe 3
1 A Street 5
Person:  2
Address:  4
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • In the above example, will it be possible to get the line number for Street which is a String value in XML file – Anand B Jan 22 '13 at 11:14
  • @AnandB - Where do you want to store the line number for `Street`? Also out of curiosity why do you want to store all the line numbers? – bdoughan Jan 22 '13 at 11:15
  • @AnandB - I have updated my answer with an `XmlAdapter` approach that may work. Ultimately you may not want to use `String` properties and just use a class like `AdaptedString` directly in your model. – bdoughan Jan 22 '13 at 11:31
  • @AnandB - It has now been added. – bdoughan Jan 22 '13 at 11:44
  • 1
    @BlaiseDoughan: Is there a way to have the classes auto-generated from schema? – Rekin Nov 21 '14 at 08:32
2

You could leverage a StAX StreamReaderDelegate and do something like the following:

Demo

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(Person.class);

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

            @Override
            public String getLocalName() {
                String localName = super.getLocalName();
                if(isStartElement()) {
                    System.out.println(localName + " " + this.getLocation().getLineNumber());
                }
                return localName;
            }

        };

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(xsr);
    }

}

Person

import javax.xml.bind.annotation.*;

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

    private String name;
    private String address;

}

input.xml

<?xml version="1.0" encoding="UTF-8"?>
<person>
    <name>Jane Doe</name>
    <address>1 A Street</address>
</person>

Output

person 2
name 3
address 4
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • How will I be able to achieve the same in case of nested XML files. Will it be possible to have a property like Locator inside individual classes. – Anand B Jan 22 '13 at 10:34
  • @AnandB - That code will output the line number for each start element regardless of how deep the XML is. – bdoughan Jan 22 '13 at 10:35
  • Will it be possible to have a property like Locator inside individual classes. – Anand B Jan 22 '13 at 10:36
  • @AnandB - You just want to store the location for each domain object? – bdoughan Jan 22 '13 at 10:38
  • @AnandB - I have added another answer demonstrating how to do this with the `@XmlLocation` extension. – bdoughan Jan 22 '13 at 10:56