001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.InputStream;
007import java.util.Arrays;
008
009import javax.xml.stream.XMLStreamConstants;
010import javax.xml.stream.XMLStreamException;
011
012import org.openstreetmap.josm.data.coor.LatLon;
013import org.openstreetmap.josm.data.osm.DataSet;
014import org.openstreetmap.josm.data.osm.NoteData;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.gui.progress.ProgressMonitor;
017import org.openstreetmap.josm.tools.Pair;
018import org.openstreetmap.josm.tools.Utils;
019
020/**
021 * Reader for <a href="http://wiki.openstreetmap.org/wiki/OsmChange">OsmChange</a> file format.
022 */
023public class OsmChangeReader extends OsmReader {
024
025    /**
026     * List of possible actions.
027     */
028    private static final String[] ACTIONS = {"create", "modify", "delete"};
029
030    protected final NoteData noteData = new NoteData();
031
032    /**
033     * constructor (for private and subclasses use only)
034     *
035     * @see #parseDataSet(InputStream, ProgressMonitor)
036     */
037    protected OsmChangeReader() {
038        // Restricts visibility
039    }
040
041    @Override
042    protected void parseRoot() throws XMLStreamException {
043        if ("osmChange".equals(parser.getLocalName())) {
044            parseOsmChange();
045        } else {
046            parseUnknown();
047        }
048    }
049
050    private void parseOsmChange() throws XMLStreamException {
051        String v = parser.getAttributeValue(null, "version");
052        if (v == null) {
053            throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
054        }
055        if (!"0.6".equals(v)) {
056            throwException(tr("Unsupported version: {0}", v));
057        }
058        ds.setVersion(v);
059        while (parser.hasNext()) {
060            int event = parser.next();
061            if (event == XMLStreamConstants.START_ELEMENT) {
062                if (Arrays.asList(ACTIONS).contains(parser.getLocalName())) {
063                    parseCommon(parser.getLocalName());
064                } else {
065                    parseUnknown();
066                }
067            } else if (event == XMLStreamConstants.END_ELEMENT) {
068                return;
069            }
070        }
071    }
072
073    private void parseCommon(String action) throws XMLStreamException {
074        while (parser.hasNext()) {
075            int event = parser.next();
076            if (event == XMLStreamConstants.START_ELEMENT) {
077                OsmPrimitive p = null;
078                switch (parser.getLocalName()) {
079                case "node":
080                    p = parseNode();
081                    break;
082                case "way":
083                    p = parseWay();
084                    break;
085                case "relation":
086                    p = parseRelation();
087                    break;
088                case "note":
089                    parseNote();
090                    break;
091                default:
092                    parseUnknown();
093                }
094                if (p != null && action != null) {
095                    if ("modify".equals(action)) {
096                        p.setModified(true);
097                    } else if ("delete".equals(action)) {
098                        p.setDeleted(true);
099                    }
100                }
101            } else if (event == XMLStreamConstants.END_ELEMENT) {
102                return;
103            }
104        }
105    }
106
107    private void parseNote() throws XMLStreamException {
108        LatLon location = NoteReader.parseLatLon(s -> parser.getAttributeValue(null, s));
109        String text = null;
110        while (parser.hasNext()) {
111            int event = parser.next();
112            if (event == XMLStreamConstants.START_ELEMENT) {
113                switch (parser.getLocalName()) {
114                case "comment":
115                    text = parser.getAttributeValue(null, "text");
116                    jumpToEnd();
117                    break;
118                default:
119                    parseUnknown();
120                }
121            } else if (event == XMLStreamConstants.END_ELEMENT) {
122                break;
123            }
124        }
125        if (location != null && !Utils.isEmpty(text)) {
126            noteData.createNote(location, text);
127        }
128    }
129
130    /**
131     * Replies the parsed notes data.
132     * @return the parsed notes data
133     * @since 14101
134     */
135    public final NoteData getNoteData() {
136        return noteData;
137    }
138
139    /**
140     * Parse the given input source and return the dataset.
141     *
142     * @param source the source input stream. Must not be <code>null</code>.
143     * @param progressMonitor  the progress monitor. If <code>null</code>,
144     * {@link org.openstreetmap.josm.gui.progress.NullProgressMonitor#INSTANCE} is assumed
145     *
146     * @return the dataset with the parsed data
147     * @throws IllegalDataException if the an error was found while parsing the data from the source
148     * @throws IllegalArgumentException if source is <code>null</code>
149     */
150    public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
151        return new OsmChangeReader().doParseDataSet(source, progressMonitor);
152    }
153
154    /**
155     * Parse the given input source and return the dataset and notes, if any (OsmAnd extends the osmChange format by adding notes).
156     *
157     * @param source the source input stream. Must not be <code>null</code>.
158     * @param progressMonitor  the progress monitor. If <code>null</code>,
159     * {@link org.openstreetmap.josm.gui.progress.NullProgressMonitor#INSTANCE} is assumed
160     *
161     * @return the dataset with the parsed data
162     * @throws IllegalDataException if the an error was found while parsing the data from the source
163     * @throws IllegalArgumentException if source is <code>null</code>
164     * @since 14101
165     */
166    public static Pair<DataSet, NoteData> parseDataSetAndNotes(InputStream source, ProgressMonitor progressMonitor)
167            throws IllegalDataException {
168        OsmChangeReader osmChangeReader = new OsmChangeReader();
169        osmChangeReader.doParseDataSet(source, progressMonitor);
170        return new Pair<>(osmChangeReader.getDataSet(), osmChangeReader.getNoteData());
171    }
172}