// License: GPL. Copyright 2007 by Immanuel Scholz and others
package org.openstreetmap.josm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
import org.openstreetmap.josm.data.osm.history.HistoryNode;
import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
import org.openstreetmap.josm.data.osm.history.HistoryRelation;
import org.openstreetmap.josm.data.osm.history.HistoryWay;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.tools.DateUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Parser for OSM history data.
*
* It is slightly different from {@see OsmReader} because we don't build an internal graph of
* {@see OsmPrimitive}s. We use objects derived from {@see HistoryOsmPrimitive} instead and we
* keep the data in a dedicated {@see HistoryDataSet}.
*
*/
public class OsmHistoryReader {
private final InputStream in;
private final HistoryDataSet data;
private class Parser extends DefaultHandler {
/** the current primitive to be read */
private HistoryOsmPrimitive current;
private Locator locator;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
protected String getCurrentPosition() {
if (locator == null)
return "";
return "(" + locator.getLineNumber() + "," + locator.getColumnNumber() + ")";
}
protected void throwException(String message) throws SAXException {
throw new SAXException(
getCurrentPosition()
+ message
);
}
protected long getMandatoryAttributeLong(Attributes attr, String name) throws SAXException{
String v = attr.getValue(name);
if (v == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", name));
}
Long l = 0l;
try {
l = Long.parseLong(v);
} catch(NumberFormatException e) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v));
}
if (l < 0) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v));
}
return l;
}
protected long getAttributeLong(Attributes attr, String name, long defaultValue) throws SAXException{
String v = attr.getValue(name);
if (v == null)
return defaultValue;
Long l = 0l;
try {
l = Long.parseLong(v);
} catch(NumberFormatException e) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v));
}
if (l < 0) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v));
}
return l;
}
protected Double getMandatoryAttributeDouble(Attributes attr, String name) throws SAXException{
String v = attr.getValue(name);
if (v == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", name));
}
double d = 0.0;
try {
d = Double.parseDouble(v);
} catch(NumberFormatException e) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type double. Got ''{1}''.", name, v));
}
return d;
}
protected String getMandatoryAttributeString(Attributes attr, String name) throws SAXException{
String v = attr.getValue(name);
if (v == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", name));
}
return v;
}
protected String getAttributeString(Attributes attr, String name, String defaultValue) {
String v = attr.getValue(name);
if (v == null)
return defaultValue;
return v;
}
protected boolean getMandatoryAttributeBoolean(Attributes attr, String name) throws SAXException{
String v = attr.getValue(name);
if (v == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", name));
}
if ("true".equals(v)) return true;
if ("false".equals(v)) return false;
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type boolean. Got ''{1}''.", name, v));
// not reached
return false;
}
protected HistoryOsmPrimitive createPrimitive(Attributes atts, OsmPrimitiveType type) throws SAXException {
long id = getMandatoryAttributeLong(atts,"id");
long version = getMandatoryAttributeLong(atts,"version");
long changesetId = getMandatoryAttributeLong(atts,"changeset");
boolean visible= getMandatoryAttributeBoolean(atts, "visible");
long uid = getAttributeLong(atts, "uid",-1);
String user = getAttributeString(atts, "user", tr("<anonymous>"));
String v = getMandatoryAttributeString(atts, "timestamp");
Date timestamp = DateUtils.fromString(v);
HistoryOsmPrimitive primitive = null;
if (type.equals(OsmPrimitiveType.NODE)) {
double lat = getMandatoryAttributeDouble(atts, "lat");
double lon = getMandatoryAttributeDouble(atts, "lon");
primitive = new HistoryNode(
id,version,visible,user,uid,changesetId,timestamp, new LatLon(lat,lon)
);
} else if (type.equals(OsmPrimitiveType.WAY)) {
primitive = new HistoryWay(
id,version,visible,user,uid,changesetId,timestamp
);
}if (type.equals(OsmPrimitiveType.RELATION)) {
primitive = new HistoryRelation(
id,version,visible,user,uid,changesetId,timestamp
);
}
return primitive;
}
protected void startNode(Attributes atts) throws SAXException {
current= createPrimitive(atts, OsmPrimitiveType.NODE);
}
protected void startWay(Attributes atts) throws SAXException {
current= createPrimitive(atts, OsmPrimitiveType.WAY);
}
protected void startRelation(Attributes atts) throws SAXException {
current= createPrimitive(atts, OsmPrimitiveType.RELATION);
}
protected void handleTag(Attributes atts) throws SAXException {
String key= getMandatoryAttributeString(atts, "k");
String value= getMandatoryAttributeString(atts, "v");
current.put(key,value);
}
protected void handleNodeReference(Attributes atts) throws SAXException {
long ref = getMandatoryAttributeLong(atts, "ref");
((HistoryWay)current).addNode(ref);
}
protected void handleMember(Attributes atts) throws SAXException {
long ref = getMandatoryAttributeLong(atts, "ref");
String v = getMandatoryAttributeString(atts, "type");
OsmPrimitiveType type = null;
try {
type = OsmPrimitiveType.fromApiTypeName(v);
} catch(IllegalArgumentException e) {
throwException(tr("Illegal value for mandatory attribute ''{0}'' of type OsmPrimitiveType. Got ''{1}''.", "type", v));
}
String role = getMandatoryAttributeString(atts, "role");
org.openstreetmap.josm.data.osm.history.RelationMember member = new org.openstreetmap.josm.data.osm.history.RelationMember(role, type,ref);
((HistoryRelation)current).addMember(member);
}
@Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
if (qName.equals("node")) {
startNode(atts);
} else if (qName.equals("way")) {
startWay(atts);
} else if (qName.equals("relation")) {
startRelation(atts);
} else if (qName.equals("tag")) {
handleTag(atts);
} else if (qName.equals("nd")) {
handleNodeReference(atts);
} else if (qName.equals("member")) {
handleMember(atts);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("node")
|| qName.equals("way")
|| qName.equals("relation")) {
data.put(current);
}
}
}
public OsmHistoryReader(InputStream source) {
this.in = source;
this.data = new HistoryDataSet();
}
public HistoryDataSet parse(ProgressMonitor progressMonitor) throws SAXException, IOException {
InputSource inputSource = new InputSource(new InputStreamReader(in, "UTF-8"));
progressMonitor.beginTask(tr("Parsing OSM history data ..."));
try {
SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new Parser());
} catch (ParserConfigurationException e1) {
e1.printStackTrace(); // broken SAXException chaining
throw new SAXException(e1);
} finally {
progressMonitor.finishTask();
}
return data;
}
}