1

I have XmlBeans classes generated from an XSD. I would like to track line numbers for the persisted objects. Is this possible? I don't care if this information is stored during parsing (xml → beans) or during prettyprinting (beans → xml) because I keep them in synch in my application flow.

I would like start and end line/column numbers, if possible.

I don't care if I have to use some kind of non-standard hack to get to the locator data.

If there is another Java XML framework that can generate classes from an XSD file and support locator data, then I'm willing to switch.

Wouter Lievens
  • 4,019
  • 5
  • 41
  • 66

2 Answers2

2

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

If there is another Java XML framework that can generate classes from an XSD file and support locator data, then I'm willing to switch.

JAXB can generate classes from an XML schema, and below are a couple of ways you could get the location information. For a comparison of JAXB and XMLBeans see:

OPTION #1 - StAX and Unmarshaller.Listener

Demo

package forum10241929;

import java.io.File;
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;

public class Demo {

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

        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource(new File("src/forum10241929/input.xml")));

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setListener(new LocationListener(xsr));
        Customer customer = (Customer) unmarshaller.unmarshal(xsr);
    }

    private static class LocationListener extends Unmarshaller.Listener {

        private XMLStreamReader xsr;

        public LocationListener(XMLStreamReader xsr) {
            this.xsr = xsr;
        }

        @Override
        public void afterUnmarshal(Object target, Object parent) {
            log("End", target);
        }

        @Override
        public void beforeUnmarshal(Object target, Object parent) {
            log("Start", target);
        }

        private void log(String event, Object target) {
            System.out.print(event);
            System.out.print(" ");
            System.out.print(target);
            System.out.print(" [");
            Location location = xsr.getLocation();
            System.out.print(location.getLineNumber());
            System.out.print(",");
            System.out.print(location.getColumnNumber());
            System.out.println("]");
        }

    }

}

input.xml

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

Output

Start forum10241929.Customer@144aa0ce [2,11]
Start forum10241929.Address@19e3cd51 [4,14]
End forum10241929.Address@19e3cd51 [6,15]
End forum10241929.Customer@144aa0ce [7,12]

Customer

package forum10241929;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {

    private String name;
    private Address address;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

Address

package forum10241929;

public class Address {

    private String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

}

OPTION #2 - @XmlLocation

There is a JAXB extension that is supported by both EclipseLink JAXB (MOXy) and the reference implementation called @XmlLocation (below is an Example using MOXy). This will only capture the start location.

package forum10241929;

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

@XmlRootElement
public class Customer {

    private String name;
    private Address address;

    @XmlLocation
    private Locator location;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Thank you for your answer. I will mark this as answered, even though I have since implemented a different solution, based on the answer here: http://stackoverflow.com/questions/4915422/get-line-number-from-xml-node-java Basically, I first parse into a regular DOM tree while preserving location information as user data, and then I feed that DOM tree to XmlBeans. This gives me correct start and end information. – Wouter Lievens Apr 20 '12 at 10:37
1

Just use XmlOptions#setLoadLineNumbers(), for example:

MyDocument.Factory.parse(xmlFile, new XmlOptions().setLoadLineNubmers());

Then to retrieve the line numbers from the xml store, find the nearest XmlLineNumber bookmark.

import org.apache.xmlbeans.*;

public class linenumber
{
    public static void main(String[] args) throws XmlException
    {
        XmlOptions options = new XmlOptions().setLoadLineNumbers();
        XmlObject xobj = XmlObject.Factory.parse("<a>\n<b>test</b>\n<c>test</c>\n</a>", options);

        // let's get the line number for the '<c>' xml object
        XmlObject cobj = xobj.selectPath(".//c")[0];
        System.out.println(cobj.xmlText());

        XmlCursor c = null;
        try
        {
            c = cobj.newCursor();

            // search for XmlLineNumber bookmark
            XmlLineNumber ln =
                (XmlLineNumber) c.getBookmark( XmlLineNumber.class );

            if (ln == null)
                ln = (XmlLineNumber) c.toPrevBookmark( XmlLineNumber.class );

            if (ln != null)
            {
                int line = ln.getLine();
                int column = ln.getColumn();
                int offset = ln.getOffset();

                System.out.println("line=" + line + ", col=" + column + ", offset=" + offset);
            }
        }
        finally
        {
            if (c != null) c.dispose();
        }
    }
}
Kevin Krouse
  • 610
  • 6
  • 9