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.XmlParsingException; 021import org.openstreetmap.josm.tools.XmlUtils; 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 Parser(boolean useAnonymousUser) { 038 this.useAnonymousUser = useAnonymousUser; 039 } 040 041 /** the current change modification type */ 042 private ChangesetDataSet.ChangesetModificationType currentModificationType; 043 044 @Override 045 protected void throwException(String message) throws XmlParsingException { 046 throw new XmlParsingException(message).rememberLocation(locator); 047 } 048 049 @Override 050 protected void throwException(String message, Exception e) throws XmlParsingException { 051 throw new XmlParsingException(message, e).rememberLocation(locator); 052 } 053 054 @Override 055 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 056 if (super.doStartElement(qName, atts)) { 057 // done 058 return; 059 } 060 switch (qName) { 061 case "osmChange": 062 // do nothing 063 break; 064 case "create": 065 currentModificationType = ChangesetModificationType.CREATED; 066 break; 067 case "modify": 068 currentModificationType = ChangesetModificationType.UPDATED; 069 break; 070 case "delete": 071 currentModificationType = ChangesetModificationType.DELETED; 072 break; 073 default: 074 Logging.warn(tr("Unsupported start element ''{0}'' in changeset content at position ({1},{2}). Skipping.", 075 qName, locator.getLineNumber(), locator.getColumnNumber())); 076 } 077 } 078 079 @Override 080 public void endElement(String uri, String localName, String qName) throws SAXException { 081 switch (qName) { 082 case "node": 083 case "way": 084 case "relation": 085 if (currentModificationType == null) { 086 // CHECKSTYLE.OFF: LineLength 087 throwException(tr("Illegal document structure. Found node, way, or relation outside of ''create'', ''modify'', or ''delete''.")); 088 // CHECKSTYLE.ON: LineLength 089 } 090 data.put(currentPrimitive, currentModificationType); 091 break; 092 case "create": 093 case "modify": 094 case "delete": 095 currentModificationType = null; 096 break; 097 case "osmChange": 098 case "tag": 099 case "nd": 100 case "member": 101 // do nothing 102 break; 103 default: 104 Logging.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(null, e); 112 } 113 114 @Override 115 public void fatalError(SAXParseException e) throws SAXException { 116 throwException(null, 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 public OsmChangesetContentParser(InputStream source) { 127 CheckParameterUtil.ensureParameterNotNull(source, "source"); 128 this.source = new InputSource(new InputStreamReader(source, StandardCharsets.UTF_8)); 129 } 130 131 /** 132 * Constructs a new {@code OsmChangesetContentParser}. 133 * 134 * @param source the input stream with the changeset content as XML document. Must not be null. 135 * @throws IllegalArgumentException if source is {@code null}. 136 */ 137 public OsmChangesetContentParser(String source) { 138 CheckParameterUtil.ensureParameterNotNull(source, "source"); 139 this.source = new InputSource(new StringReader(source)); 140 } 141 142 /** 143 * Parses the content. 144 * 145 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null 146 * @return the parsed data 147 * @throws XmlParsingException if something went wrong. Check for chained 148 * exceptions. 149 */ 150 public ChangesetDataSet parse(ProgressMonitor progressMonitor) throws XmlParsingException { 151 return parse(progressMonitor, false); 152 } 153 154 /** 155 * Parses the content. 156 * 157 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null 158 * @param useAnonymousUser if true, replace all user information with the anonymous user 159 * @return the parsed data 160 * @throws XmlParsingException if something went wrong. Check for chained 161 * exceptions. 162 * @since 14946 163 */ 164 public ChangesetDataSet parse(ProgressMonitor progressMonitor, boolean useAnonymousUser) throws XmlParsingException { 165 if (progressMonitor == null) { 166 progressMonitor = NullProgressMonitor.INSTANCE; 167 } 168 try { 169 progressMonitor.beginTask(""); 170 progressMonitor.indeterminateSubTask(tr("Parsing changeset content ...")); 171 XmlUtils.parseSafeSAX(source, new Parser(useAnonymousUser)); 172 } catch (XmlParsingException e) { 173 throw e; 174 } catch (ParserConfigurationException | SAXException | IOException e) { 175 throw new XmlParsingException(e); 176 } finally { 177 progressMonitor.finishTask(); 178 } 179 return data; 180 } 181 182 /** 183 * Parses the content from the input source 184 * 185 * @return the parsed data 186 * @throws XmlParsingException if something went wrong. Check for chained 187 * exceptions. 188 */ 189 public ChangesetDataSet parse() throws XmlParsingException { 190 return parse(null, false); 191 } 192}