Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XStream: Collapsing XML hierarchy as I parse

I have an XML document (generated by Adobe XFA forms), that contains data like the following:

<Position>
   <PositionBorder>
       <Title/>
       <StartDate/>
       <EndDate/>
   </PositionBorder>
</Position>

Since this file is defined elsewhere, I am not at liberty to change the format of the XML that I get.

In my Java code, I create a Position class that contains the Title, Start and End Dates.

My problem is, when I use XStream to parse the file, it wants a PositionBorder class to hold the title and dates. I want to basically ignore the border and place all of the fields into the Position class.

What I'd really like to do is use something like the convertAnother method to convert the child of the position element. I tried to do just that and it fails, because my PositionConverter gets called for the PositionBorder (when I call convertAnother).

Anyone have any clues how to deal with collapsing the structure of an XML when parsing?

like image 876
Steve Avatar asked Jan 11 '10 22:01

Steve


1 Answers

It's not terribly difficult to do with a custom converter. This is a little bit of a long example, but I hope it's simple enough to get the gist of what you need to do:

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public final class ConverterTest {
    public static void main(String[] args) {
        XStream xstream = new XStream();
        xstream.autodetectAnnotations(true);
        xstream.registerConverter(new PositionConverter());

        final Position position = new Position();
        position.setTitle("The Title");
        position.setStartDate("The Start Date");
        position.setEndDate("The End Date");

        final String xml = xstream.toXML(position);
        System.out.println("Generated XML:");
        System.out.println(xml);

        final Position genPosition = (Position) xstream.fromXML(xml);
        System.out.println("Generated Position:");
        System.out.println("\tTitle: " + genPosition.getTitle());
        System.out.println("\tStart Date: " + genPosition.getStartDate());
        System.out.println("\tEnd Date: " + genPosition.getEndDate());
    }

    @XStreamAlias("Position")
    private static class Position {
        public String getEndDate() {
            return endDate;
        }

        public void setEndDate(String endDate) {
            this.endDate = endDate;
        }

        public String getStartDate() {
            return startDate;
        }

        public void setStartDate(String startDate) {
            this.startDate = startDate;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        private String title;
        private String startDate;
        private String endDate;
    }

    private static class PositionConverter implements Converter {
        public boolean canConvert(Class clazz) {
            return Position.class == clazz;
        }

        public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
            Position position = (Position)value;
            writer.startNode("PositionBorder");

            writer.startNode("Title");
            writer.setValue(position.getTitle());
            writer.endNode();

            writer.startNode("StartDate");
            writer.setValue(position.getStartDate());
            writer.endNode();

            writer.startNode("EndDate");
            writer.setValue(position.getEndDate());
            writer.endNode();

            writer.endNode();
        }

        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            Position position = new Position();
            // move it to <PositionBorder> tag.
            reader.moveDown();
            // now move it to <Title> tag.
            reader.moveDown();
            String title = reader.getValue();
            position.setTitle(title);
            reader.moveUp(); // moves back to <PositionBorder>

            reader.moveDown(); // should move down to <StartDate> tag
            String startDate = reader.getValue();
            position.setStartDate(startDate);
            reader.moveUp(); // move back to <PositionBorder>

            reader.moveDown(); // should move down to <EndDate> tag
            String endDate = reader.getValue();
            position.setEndDate(endDate);
            reader.moveUp(); // move back to <PositionBorder>


            return position;
        }
    }
}

Try running that and see what happens. You'll need to modify it to suit your own types, of course -- I just used strings for all of Position's fields (and I'm sure you're Position class isn't nested, either), but converting from a String to a Date (or whatever) should be rather trivial.

One thing you'll want to keep an eye on (and I might not have gotten it completely right in my example) is matching your reader.moveDown() and reader.moveUp() calls. (And, if you're going to do any marshalling instead of just unmarshalling -- which I don't expect from your question -- you'll want to match your writer.startNode() and writer.endNode() calls as well.) It probably won't cause any problems with this example, but I'm sure it'll raise issues if you're doing anything larger or processing multiple files with the same XStream or Converter instance. Also, if you try reader.moveDown() from an invalid location, you'll get a really ugly exception -- it should be pretty obvious.

I had to play around with the moveUp/moveDown methods a bit to get them in the right places, so I'm sure you'll need to test it and tweak it until you get what you need.

like image 164
MCory Avatar answered Oct 01 '22 16:10

MCory