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 }