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 "create":
087            case "modify":
088            case "delete":
089                currentModificationType = null;
090                break;
091            case "osmChange":
092            case "tag":
093            case "nd":
094            case "member":
095                // do nothing
096                break;
097            default:
098                Main.warn(tr("Unsupported end element ''{0}'' in changeset content at position ({1},{2}). Skipping.",
099                        qName, locator.getLineNumber(), locator.getColumnNumber()));
100            }
101        }
102
103        @Override
104        public void error(SAXParseException e) throws SAXException {
105            throwException(e);
106        }
107
108        @Override
109        public void fatalError(SAXParseException e) throws SAXException {
110            throwException(e);
111        }
112    }
113
114    /**
115     * Constructs a new {@code OsmChangesetContentParser}.
116     *
117     * @param source the input stream with the changeset content as XML document. Must not be null.
118     * @throws IllegalArgumentException if source is {@code null}.
119     */
120    @SuppressWarnings("resource")
121    public OsmChangesetContentParser(InputStream source) {
122        CheckParameterUtil.ensureParameterNotNull(source, "source");
123        this.source = new InputSource(new InputStreamReader(source, StandardCharsets.UTF_8));
124    }
125
126    /**
127     * Constructs a new {@code OsmChangesetContentParser}.
128     *
129     * @param source the input stream with the changeset content as XML document. Must not be null.
130     * @throws IllegalArgumentException if source is {@code null}.
131     */
132    public OsmChangesetContentParser(String source) {
133        CheckParameterUtil.ensureParameterNotNull(source, "source");
134        this.source = new InputSource(new StringReader(source));
135    }
136
137    /**
138     * Parses the content.
139     *
140     * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null
141     * @return the parsed data
142     * @throws XmlParsingException if something went wrong. Check for chained
143     * exceptions.
144     */
145    public ChangesetDataSet parse(ProgressMonitor progressMonitor) throws XmlParsingException {
146        if (progressMonitor == null) {
147            progressMonitor = NullProgressMonitor.INSTANCE;
148        }
149        try {
150            progressMonitor.beginTask("");
151            progressMonitor.indeterminateSubTask(tr("Parsing changeset content ..."));
152            SAXParserFactory.newInstance().newSAXParser().parse(source, new Parser());
153        } catch(XmlParsingException e) {
154            throw e;
155        } catch (ParserConfigurationException | SAXException | IOException e) {
156            throw new XmlParsingException(e);
157        } finally {
158            progressMonitor.finishTask();
159        }
160        return data;
161    }
162
163    /**
164     * Parses the content from the input source
165     *
166     * @return the parsed data
167     * @throws XmlParsingException if something went wrong. Check for chained
168     * exceptions.
169     */
170    public ChangesetDataSet parse() throws XmlParsingException {
171        return parse(null);
172    }
173}