001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.io; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.io.IOException; 007 import java.io.InputStream; 008 import java.io.InputStreamReader; 009 import java.io.StringReader; 010 import java.io.UnsupportedEncodingException; 011 import java.util.Date; 012 013 import javax.xml.parsers.ParserConfigurationException; 014 import javax.xml.parsers.SAXParserFactory; 015 016 import org.openstreetmap.josm.data.coor.LatLon; 017 import org.openstreetmap.josm.data.osm.ChangesetDataSet; 018 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 019 import org.openstreetmap.josm.data.osm.RelationMemberData; 020 import org.openstreetmap.josm.data.osm.User; 021 import org.openstreetmap.josm.data.osm.ChangesetDataSet.ChangesetModificationType; 022 import org.openstreetmap.josm.data.osm.history.HistoryNode; 023 import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 024 import org.openstreetmap.josm.data.osm.history.HistoryRelation; 025 import org.openstreetmap.josm.data.osm.history.HistoryWay; 026 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 027 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 028 import org.openstreetmap.josm.tools.CheckParameterUtil; 029 import org.openstreetmap.josm.tools.DateUtils; 030 import org.xml.sax.Attributes; 031 import org.xml.sax.InputSource; 032 import org.xml.sax.Locator; 033 import org.xml.sax.SAXException; 034 import org.xml.sax.SAXParseException; 035 import org.xml.sax.helpers.DefaultHandler; 036 037 /** 038 * Parser for OSM changeset content. 039 * 040 */ 041 public class OsmChangesetContentParser { 042 043 private InputSource source; 044 private ChangesetDataSet data; 045 046 // FIXME: this class has many similarities with OsmHistoryReader.Parser and should be merged 047 private class Parser extends DefaultHandler { 048 049 /** the current primitive to be read */ 050 private HistoryOsmPrimitive currentPrimitive; 051 /** the current change modification type */ 052 private ChangesetDataSet.ChangesetModificationType currentModificationType; 053 054 private Locator locator; 055 056 @Override 057 public void setDocumentLocator(Locator locator) { 058 this.locator = locator; 059 } 060 061 protected void throwException(String message) throws OsmDataParsingException { 062 throw new OsmDataParsingException( 063 message 064 ).rememberLocation(locator); 065 } 066 067 protected void throwException(Exception e) throws OsmDataParsingException { 068 throw new OsmDataParsingException( 069 e 070 ).rememberLocation(locator); 071 } 072 073 protected long getMandatoryAttributeLong(Attributes attr, String name) throws SAXException{ 074 String v = attr.getValue(name); 075 if (v == null) { 076 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 077 } 078 Long l = 0l; 079 try { 080 l = Long.parseLong(v); 081 } catch(NumberFormatException e) { 082 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v)); 083 } 084 if (l < 0) { 085 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v)); 086 } 087 return l; 088 } 089 090 protected Long getAttributeLong(Attributes attr, String name) throws SAXException{ 091 String v = attr.getValue(name); 092 if (v == null) 093 return null; 094 Long l = null; 095 try { 096 l = Long.parseLong(v); 097 } catch(NumberFormatException e) { 098 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v)); 099 } 100 if (l < 0) { 101 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v)); 102 } 103 return l; 104 } 105 106 protected Double getAttributeDouble(Attributes attr, String name) throws SAXException{ 107 String v = attr.getValue(name); 108 if (v == null) { 109 return null; 110 } 111 double d = 0.0; 112 try { 113 d = Double.parseDouble(v); 114 } catch(NumberFormatException e) { 115 throwException(tr("Illegal value for attribute ''{0}'' of type double. Got ''{1}''.", name, v)); 116 } 117 return d; 118 } 119 120 protected String getMandatoryAttributeString(Attributes attr, String name) throws SAXException{ 121 String v = attr.getValue(name); 122 if (v == null) { 123 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 124 } 125 return v; 126 } 127 128 protected boolean getMandatoryAttributeBoolean(Attributes attr, String name) throws SAXException{ 129 String v = attr.getValue(name); 130 if (v == null) { 131 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 132 } 133 if ("true".equals(v)) return true; 134 if ("false".equals(v)) return false; 135 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type boolean. Got ''{1}''.", name, v)); 136 // not reached 137 return false; 138 } 139 140 protected HistoryOsmPrimitive createPrimitive(Attributes atts, OsmPrimitiveType type) throws SAXException { 141 long id = getMandatoryAttributeLong(atts,"id"); 142 long version = getMandatoryAttributeLong(atts,"version"); 143 long changesetId = getMandatoryAttributeLong(atts,"changeset"); 144 boolean visible= getMandatoryAttributeBoolean(atts, "visible"); 145 146 Long uid = getAttributeLong(atts, "uid"); 147 String userStr = atts.getValue("user"); 148 User user; 149 if (userStr != null) { 150 if (uid != null) { 151 user = User.createOsmUser(uid, userStr); 152 } else { 153 user = User.createLocalUser(userStr); 154 } 155 } else { 156 user = User.getAnonymous(); 157 } 158 159 String v = getMandatoryAttributeString(atts, "timestamp"); 160 Date timestamp = DateUtils.fromString(v); 161 HistoryOsmPrimitive primitive = null; 162 if (type.equals(OsmPrimitiveType.NODE)) { 163 Double lat = getAttributeDouble(atts, "lat"); 164 Double lon = getAttributeDouble(atts, "lon"); 165 LatLon coor = (lat != null && lon != null) ? new LatLon(lat,lon) : null; 166 primitive = new HistoryNode( 167 id,version,visible,user,changesetId,timestamp,coor 168 ); 169 170 } else if (type.equals(OsmPrimitiveType.WAY)) { 171 primitive = new HistoryWay( 172 id,version,visible,user,changesetId,timestamp 173 ); 174 }if (type.equals(OsmPrimitiveType.RELATION)) { 175 primitive = new HistoryRelation( 176 id,version,visible,user,changesetId,timestamp 177 ); 178 } 179 return primitive; 180 } 181 182 protected void startNode(Attributes atts) throws SAXException { 183 currentPrimitive= createPrimitive(atts, OsmPrimitiveType.NODE); 184 } 185 186 protected void startWay(Attributes atts) throws SAXException { 187 currentPrimitive= createPrimitive(atts, OsmPrimitiveType.WAY); 188 } 189 protected void startRelation(Attributes atts) throws SAXException { 190 currentPrimitive= createPrimitive(atts, OsmPrimitiveType.RELATION); 191 } 192 193 protected void handleTag(Attributes atts) throws SAXException { 194 String key= getMandatoryAttributeString(atts, "k"); 195 String value= getMandatoryAttributeString(atts, "v"); 196 currentPrimitive.put(key,value); 197 } 198 199 protected void handleNodeReference(Attributes atts) throws SAXException { 200 long ref = getMandatoryAttributeLong(atts, "ref"); 201 ((HistoryWay)currentPrimitive).addNode(ref); 202 } 203 204 protected void handleMember(Attributes atts) throws SAXException { 205 long ref = getMandatoryAttributeLong(atts, "ref"); 206 String v = getMandatoryAttributeString(atts, "type"); 207 OsmPrimitiveType type = null; 208 try { 209 type = OsmPrimitiveType.fromApiTypeName(v); 210 } catch(IllegalArgumentException e) { 211 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type OsmPrimitiveType. Got ''{1}''.", "type", v)); 212 } 213 String role = getMandatoryAttributeString(atts, "role"); 214 RelationMemberData member = new RelationMemberData(role, type,ref); 215 ((HistoryRelation)currentPrimitive).addMember(member); 216 } 217 218 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 219 if (qName.equals("node")) { 220 startNode(atts); 221 } else if (qName.equals("way")) { 222 startWay(atts); 223 } else if (qName.equals("relation")) { 224 startRelation(atts); 225 } else if (qName.equals("tag")) { 226 handleTag(atts); 227 } else if (qName.equals("nd")) { 228 handleNodeReference(atts); 229 } else if (qName.equals("member")) { 230 handleMember(atts); 231 } else if (qName.equals("osmChange")) { 232 // do nothing 233 } else if (qName.equals("create")) { 234 currentModificationType = ChangesetModificationType.CREATED; 235 } else if (qName.equals("modify")) { 236 currentModificationType = ChangesetModificationType.UPDATED; 237 } else if (qName.equals("delete")) { 238 currentModificationType = ChangesetModificationType.DELETED; 239 } else { 240 System.err.println(tr("Warning: unsupported start element ''{0}'' in changeset content at position ({1},{2}). Skipping.", qName, locator.getLineNumber(), locator.getColumnNumber())); 241 } 242 } 243 244 @Override 245 public void endElement(String uri, String localName, String qName) throws SAXException { 246 if (qName.equals("node") 247 || qName.equals("way") 248 || qName.equals("relation")) { 249 if (currentModificationType == null) { 250 throwException(tr("Illegal document structure. Found node, way, or relation outside of ''create'', ''modify'', or ''delete''.")); 251 } 252 data.put(currentPrimitive, currentModificationType); 253 } else if (qName.equals("osmChange")) { 254 // do nothing 255 } else if (qName.equals("create")) { 256 currentModificationType = null; 257 } else if (qName.equals("modify")) { 258 currentModificationType = null; 259 } else if (qName.equals("delete")) { 260 currentModificationType = null; 261 } else if (qName.equals("tag")) { 262 // do nothing 263 } else if (qName.equals("nd")) { 264 // do nothing 265 } else if (qName.equals("member")) { 266 // do nothing 267 } else { 268 System.err.println(tr("Warning: unsupported end element ''{0}'' in changeset content at position ({1},{2}). Skipping.", qName, locator.getLineNumber(), locator.getColumnNumber())); 269 } 270 } 271 272 @Override 273 public void error(SAXParseException e) throws SAXException { 274 throwException(e); 275 } 276 277 @Override 278 public void fatalError(SAXParseException e) throws SAXException { 279 throwException(e); 280 } 281 } 282 283 /** 284 * Create a parser 285 * 286 * @param source the input stream with the changeset content as XML document. Must not be null. 287 * @throws IllegalArgumentException thrown if source is null. 288 */ 289 public OsmChangesetContentParser(InputStream source) throws UnsupportedEncodingException { 290 CheckParameterUtil.ensureParameterNotNull(source, "source"); 291 this.source = new InputSource(new InputStreamReader(source, "UTF-8")); 292 data = new ChangesetDataSet(); 293 } 294 295 public OsmChangesetContentParser(String source) { 296 CheckParameterUtil.ensureParameterNotNull(source, "source"); 297 this.source = new InputSource(new StringReader(source)); 298 data = new ChangesetDataSet(); 299 } 300 301 /** 302 * Parses the content 303 * 304 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} 305 * if null 306 * @return the parsed data 307 * @throws OsmDataParsingException thrown if something went wrong. Check for chained 308 * exceptions. 309 */ 310 public ChangesetDataSet parse(ProgressMonitor progressMonitor) throws OsmDataParsingException { 311 if (progressMonitor == null) { 312 progressMonitor = NullProgressMonitor.INSTANCE; 313 } 314 try { 315 progressMonitor.beginTask(""); 316 progressMonitor.indeterminateSubTask(tr("Parsing changeset content ...")); 317 SAXParserFactory.newInstance().newSAXParser().parse(source, new Parser()); 318 } catch(OsmDataParsingException e){ 319 throw e; 320 } catch (ParserConfigurationException e) { 321 throw new OsmDataParsingException(e); 322 } catch(SAXException e) { 323 throw new OsmDataParsingException(e); 324 } catch(IOException e) { 325 throw new OsmDataParsingException(e); 326 } finally { 327 progressMonitor.finishTask(); 328 } 329 return data; 330 } 331 332 /** 333 * Parses the content from the input source 334 * 335 * @return the parsed data 336 * @throws OsmDataParsingException thrown if something went wrong. Check for chained 337 * exceptions. 338 */ 339 public ChangesetDataSet parse() throws OsmDataParsingException { 340 return parse(null); 341 } 342 }