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.IOException;
007import java.io.InputStream;
008import java.io.InputStreamReader;
009import java.io.StringReader;
010import java.nio.charset.StandardCharsets;
011
012import javax.xml.parsers.ParserConfigurationException;
013import javax.xml.parsers.SAXParserFactory;
014
015import org.openstreetmap.josm.Main;
016import org.openstreetmap.josm.data.osm.ChangesetDataSet;
017import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType;
018import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.tools.CheckParameterUtil;
021import org.openstreetmap.josm.tools.XmlParsingException;
022import org.xml.sax.Attributes;
023import org.xml.sax.InputSource;
024import org.xml.sax.SAXException;
025import org.xml.sax.SAXParseException;
026
027/**
028 * Parser for OSM changeset content.
029 * @since 2688
030 */
031public class OsmChangesetContentParser {
032
033    private InputSource source;
034    private final ChangesetDataSet data = new ChangesetDataSet();
035
036    private class Parser extends AbstractParser {
037
038        /** the current change modification type */
039        private ChangesetDataSet.ChangesetModificationType currentModificationType;
040
041        @Override
042        protected void throwException(String message) throws XmlParsingException {
043            throw new XmlParsingException(message).rememberLocation(locator);
044        }
045
046        protected void throwException(Exception e) throws XmlParsingException {
047            throw new XmlParsingException(e).rememberLocation(locator);
048        }
049
050        @Override
051        public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
052            if (super.doStartElement(qName, atts)) {
053                // done
054                return;
055            }
056            switch (qName) {
057            case "osmChange":
058                // do nothing
059                break;
060            case "create":
061                currentModificationType = ChangesetModificationType.CREATED;
062                break;
063            case "modify":
064                currentModificationType = ChangesetModificationType.UPDATED;
065                break;
066            case "delete":
067                currentModificationType = ChangesetModificationType.DELETED;
068                break;
069            default:
070                Main.warn(tr("Unsupported start element ''{0}'' in changeset content at position ({1},{2}). Skipping.",
071                        qName, locator.getLineNumber(), locator.getColumnNumber()));
072            }
073        }
074
075        @Override
076        public void endElement(String uri, String localName, String qName) throws SAXException {
077            switch (qName) {
078            case "node":
079            case "way":
080            case "relation":
081                if (currentModificationType == null) {
082                    throwException(tr("Illegal document structure. Found node, way, or relation outside of ''create'', ''modify'', or ''delete''."));
083                }
084                data.put(currentPrimitive, currentModificationType);
085                break;
086            case "osmChange":
087                // do nothing
088                break;
089            case "create":
090                currentModificationType = null;
091                break;
092            case "modify":
093                currentModificationType = null;
094                break;
095            case "delete":
096                currentModificationType = null;
097                break;
098            case "tag":
099            case "nd":
100            case "member":
101                // do nothing
102                break;
103            default:
104                Main.warn(tr("Unsupported end element ''{0}'' in changeset content at position ({1},{2}). Skipping.",
105                        qName, locator.getLineNumber(), locator.getColumnNumber()));
106            }
107        }
108
109        @Override
110        public void error(SAXParseException e) throws SAXException {
111            throwException(e);
112        }
113
114        @Override
115        public void fatalError(SAXParseException e) throws SAXException {
116            throwException(e);
117        }
118    }
119
120    /**
121     * Constructs a new {@code OsmChangesetContentParser}.
122     *
123     * @param source the input stream with the changeset content as XML document. Must not be null.
124     * @throws IllegalArgumentException if source is {@code null}.
125     */
126    @SuppressWarnings("resource")
127    public OsmChangesetContentParser(InputStream source) {
128        CheckParameterUtil.ensureParameterNotNull(source, "source");
129        this.source = new InputSource(new InputStreamReader(source, StandardCharsets.UTF_8));
130    }
131
132    /**
133     * Constructs a new {@code OsmChangesetContentParser}.
134     *
135     * @param source the input stream with the changeset content as XML document. Must not be null.
136     * @throws IllegalArgumentException if source is {@code null}.
137     */
138    public OsmChangesetContentParser(String source) {
139        CheckParameterUtil.ensureParameterNotNull(source, "source");
140        this.source = new InputSource(new StringReader(source));
141    }
142
143    /**
144     * Parses the content.
145     *
146     * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null
147     * @return the parsed data
148     * @throws XmlParsingException if something went wrong. Check for chained
149     * exceptions.
150     */
151    public ChangesetDataSet parse(ProgressMonitor progressMonitor) throws XmlParsingException {
152        if (progressMonitor == null) {
153            progressMonitor = NullProgressMonitor.INSTANCE;
154        }
155        try {
156            progressMonitor.beginTask("");
157            progressMonitor.indeterminateSubTask(tr("Parsing changeset content ..."));
158            SAXParserFactory.newInstance().newSAXParser().parse(source, new Parser());
159        } catch(XmlParsingException e) {
160            throw e;
161        } catch (ParserConfigurationException | SAXException | IOException e) {
162            throw new XmlParsingException(e);
163        } finally {
164            progressMonitor.finishTask();
165        }
166        return data;
167    }
168
169    /**
170     * Parses the content from the input source
171     *
172     * @return the parsed data
173     * @throws XmlParsingException if something went wrong. Check for chained
174     * exceptions.
175     */
176    public ChangesetDataSet parse() throws XmlParsingException {
177        return parse(null);
178    }
179}