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