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 }