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