XStream: Collapsing XML hierarchy as I parse

2019-04-29 04:31发布

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?

2条回答
女痞
2楼-- · 2019-04-29 04:50

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.

查看更多
甜甜的少女心
3楼-- · 2019-04-29 04:52

I find this way more easy to use:

@Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        Position mPosition = new Position();
        while (reader.hasMoreChildren()) {

            reader.moveDown();

            String nodeName = reader.getNodeName();

            if ("Title".equalsIgnoreCase(nodeName)) {
                mPosition.setTitle(reader.getValue());
            } else if ("StartDate".equalsIgnoreCase(nodeName)) {
                mPosition.setStartDate(reader.getValue());
            }else if ("attributeexample".equalsIgnoreCase(nodeName)) {
                mPosition.setAttributeExample(reader.getAttribute("attrname"));
            }

            reader.moveUp();
        }

        return mPosition;
    }
查看更多
登录 后发表回答