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}