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