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}