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.util.Date; 010 011 import javax.xml.parsers.ParserConfigurationException; 012 import javax.xml.parsers.SAXParserFactory; 013 014 import org.openstreetmap.josm.data.coor.LatLon; 015 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 016 import org.openstreetmap.josm.data.osm.RelationMemberData; 017 import org.openstreetmap.josm.data.osm.User; 018 import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 019 import org.openstreetmap.josm.data.osm.history.HistoryNode; 020 import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 021 import org.openstreetmap.josm.data.osm.history.HistoryRelation; 022 import org.openstreetmap.josm.data.osm.history.HistoryWay; 023 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024 import org.openstreetmap.josm.tools.DateUtils; 025 import org.xml.sax.Attributes; 026 import org.xml.sax.InputSource; 027 import org.xml.sax.Locator; 028 import org.xml.sax.SAXException; 029 import org.xml.sax.helpers.DefaultHandler; 030 031 /** 032 * Parser for OSM history data. 033 * 034 * It is slightly different from {@link OsmReader} because we don't build an internal graph of 035 * {@link OsmPrimitive}s. We use objects derived from {@link HistoryOsmPrimitive} instead and we 036 * keep the data in a dedicated {@link HistoryDataSet}. 037 * 038 */ 039 public class OsmHistoryReader { 040 041 private final InputStream in; 042 private final HistoryDataSet data; 043 044 // FIXME: this class has many similarities with OsmChangesetContentParser.Parser and should be merged 045 private class Parser extends DefaultHandler { 046 047 /** the current primitive to be read */ 048 private HistoryOsmPrimitive current; 049 private Locator locator; 050 051 @Override 052 public void setDocumentLocator(Locator locator) { 053 this.locator = locator; 054 } 055 056 protected String getCurrentPosition() { 057 if (locator == null) 058 return ""; 059 return "(" + locator.getLineNumber() + "," + locator.getColumnNumber() + ")"; 060 } 061 062 protected void throwException(String message) throws SAXException { 063 throw new SAXException( 064 getCurrentPosition() 065 + message 066 ); 067 } 068 069 protected long getMandatoryAttributeLong(Attributes attr, String name) throws SAXException{ 070 String v = attr.getValue(name); 071 if (v == null) { 072 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 073 } 074 Long l = 0l; 075 try { 076 l = Long.parseLong(v); 077 } catch(NumberFormatException e) { 078 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v)); 079 } 080 if (l < 0) { 081 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v)); 082 } 083 return l; 084 } 085 086 protected Long getAttributeLong(Attributes attr, String name) throws SAXException { 087 String v = attr.getValue(name); 088 if (v == null) 089 return null; 090 Long l = null; 091 try { 092 l = Long.parseLong(v); 093 } catch(NumberFormatException e) { 094 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v)); 095 } 096 if (l < 0) { 097 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v)); 098 } 099 return l; 100 } 101 102 protected Double getAttributeDouble(Attributes attr, String name) throws SAXException{ 103 String v = attr.getValue(name); 104 if (v == null) { 105 return null; 106 } 107 double d = 0.0; 108 try { 109 d = Double.parseDouble(v); 110 } catch(NumberFormatException e) { 111 throwException(tr("Illegal value for attribute ''{0}'' of type double. Got ''{1}''.", name, v)); 112 } 113 return d; 114 } 115 116 protected String getMandatoryAttributeString(Attributes attr, String name) throws SAXException{ 117 String v = attr.getValue(name); 118 if (v == null) { 119 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 120 } 121 return v; 122 } 123 124 protected boolean getMandatoryAttributeBoolean(Attributes attr, String name) throws SAXException{ 125 String v = attr.getValue(name); 126 if (v == null) { 127 throwException(tr("Missing mandatory attribute ''{0}''.", name)); 128 } 129 if ("true".equals(v)) return true; 130 if ("false".equals(v)) return false; 131 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type boolean. Got ''{1}''.", name, v)); 132 // not reached 133 return false; 134 } 135 136 protected HistoryOsmPrimitive createPrimitive(Attributes atts, OsmPrimitiveType type) throws SAXException { 137 long id = getMandatoryAttributeLong(atts,"id"); 138 long version = getMandatoryAttributeLong(atts,"version"); 139 long changesetId = getMandatoryAttributeLong(atts,"changeset"); 140 boolean visible= getMandatoryAttributeBoolean(atts, "visible"); 141 Long uid = getAttributeLong(atts, "uid"); 142 String userStr = atts.getValue("user"); 143 User user; 144 if (userStr != null) { 145 if (uid != null) { 146 user = User.createOsmUser(uid, userStr); 147 } else { 148 user = User.createLocalUser(userStr); 149 } 150 } else { 151 user = User.getAnonymous(); 152 } 153 String v = getMandatoryAttributeString(atts, "timestamp"); 154 Date timestamp = DateUtils.fromString(v); 155 HistoryOsmPrimitive primitive = null; 156 if (type.equals(OsmPrimitiveType.NODE)) { 157 Double lat = getAttributeDouble(atts, "lat"); 158 Double lon = getAttributeDouble(atts, "lon"); 159 LatLon coord = (lat != null && lon != null) ? new LatLon(lat,lon) : null; 160 primitive = new HistoryNode( 161 id,version,visible,user,changesetId,timestamp,coord 162 ); 163 164 } else if (type.equals(OsmPrimitiveType.WAY)) { 165 primitive = new HistoryWay( 166 id,version,visible,user,changesetId,timestamp 167 ); 168 }if (type.equals(OsmPrimitiveType.RELATION)) { 169 primitive = new HistoryRelation( 170 id,version,visible,user,changesetId,timestamp 171 ); 172 } 173 return primitive; 174 } 175 176 protected void startNode(Attributes atts) throws SAXException { 177 current= createPrimitive(atts, OsmPrimitiveType.NODE); 178 } 179 180 protected void startWay(Attributes atts) throws SAXException { 181 current= createPrimitive(atts, OsmPrimitiveType.WAY); 182 } 183 protected void startRelation(Attributes atts) throws SAXException { 184 current= createPrimitive(atts, OsmPrimitiveType.RELATION); 185 } 186 187 protected void handleTag(Attributes atts) throws SAXException { 188 String key= getMandatoryAttributeString(atts, "k"); 189 String value= getMandatoryAttributeString(atts, "v"); 190 current.put(key,value); 191 } 192 193 protected void handleNodeReference(Attributes atts) throws SAXException { 194 long ref = getMandatoryAttributeLong(atts, "ref"); 195 ((HistoryWay)current).addNode(ref); 196 } 197 198 protected void handleMember(Attributes atts) throws SAXException { 199 long ref = getMandatoryAttributeLong(atts, "ref"); 200 String v = getMandatoryAttributeString(atts, "type"); 201 OsmPrimitiveType type = null; 202 try { 203 type = OsmPrimitiveType.fromApiTypeName(v); 204 } catch(IllegalArgumentException e) { 205 throwException(tr("Illegal value for mandatory attribute ''{0}'' of type OsmPrimitiveType. Got ''{1}''.", "type", v)); 206 } 207 String role = getMandatoryAttributeString(atts, "role"); 208 RelationMemberData member = new RelationMemberData(role, type,ref); 209 ((HistoryRelation)current).addMember(member); 210 } 211 212 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 213 if (qName.equals("node")) { 214 startNode(atts); 215 } else if (qName.equals("way")) { 216 startWay(atts); 217 } else if (qName.equals("relation")) { 218 startRelation(atts); 219 } else if (qName.equals("tag")) { 220 handleTag(atts); 221 } else if (qName.equals("nd")) { 222 handleNodeReference(atts); 223 } else if (qName.equals("member")) { 224 handleMember(atts); 225 } 226 } 227 228 @Override 229 public void endElement(String uri, String localName, String qName) throws SAXException { 230 if (qName.equals("node") 231 || qName.equals("way") 232 || qName.equals("relation")) { 233 data.put(current); 234 } 235 } 236 } 237 238 public OsmHistoryReader(InputStream source) { 239 this.in = source; 240 this.data = new HistoryDataSet(); 241 } 242 243 public HistoryDataSet parse(ProgressMonitor progressMonitor) throws SAXException, IOException { 244 InputSource inputSource = new InputSource(new InputStreamReader(in, "UTF-8")); 245 progressMonitor.beginTask(tr("Parsing OSM history data ...")); 246 try { 247 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new Parser()); 248 } catch (ParserConfigurationException e1) { 249 e1.printStackTrace(); // broken SAXException chaining 250 throw new SAXException(e1); 251 } finally { 252 progressMonitor.finishTask(); 253 } 254 return data; 255 } 256 }