// License: GPL. For details, see LICENSE file.
package CommandLine;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.AddCommand;
import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.DeleteCommand;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.NodeData;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.PrimitiveData;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationData;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
import org.openstreetmap.josm.data.osm.User;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.WayData;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.io.UTFInputStreamReader;
import org.openstreetmap.josm.tools.XmlParsingException;
import org.openstreetmap.josm.tools.date.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.SAXParseException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;
final class OsmToCmd {
private final CommandLine parentPlugin;
private final DataSet targetDataSet;
private final LinkedList<Command> cmds = new LinkedList<>();
private final HashMap<PrimitiveId, OsmPrimitive> externalIdMap; // Maps external ids to internal primitives
OsmToCmd(CommandLine parentPlugin, DataSet targetDataSet) {
this.parentPlugin = parentPlugin;
this.targetDataSet = targetDataSet;
externalIdMap = new HashMap<>();
}
public void parseStream(InputStream stream) throws IllegalDataException {
try {
InputSource inputSource = new InputSource(UTFInputStreamReader.create(stream, "UTF-8"));
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
Parser handler = new Parser();
parser.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
parser.parse(inputSource, handler);
} catch (ParserConfigurationException e) {
throw new IllegalDataException(e.getMessage(), e);
} catch (SAXParseException e) {
throw new IllegalDataException(tr("Line {0} column {1}: ", e.getLineNumber(), e.getColumnNumber()) + e.getMessage(), e);
} catch (SAXException e) {
throw new IllegalDataException(e.getMessage(), e);
} catch (Exception e) {
throw new IllegalDataException(e);
}
}
public LinkedList<Command> getCommandList() {
return cmds;
}
private class Parser extends DefaultHandler implements LexicalHandler {
private Locator locator;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
protected void throwException(String msg) throws XmlParsingException {
throw new XmlParsingException(msg).rememberLocation(locator);
}
private OsmPrimitive currentPrimitive;
//private long currentExternalId;
private final List<Node> currentWayNodes = new ArrayList<>();
private final List<RelationMember> currentRelationMembers = new ArrayList<>();
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
try {
if (qName.equals("osm")) {
if (atts == null) {
throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}.", "version", "osm"));
return;
}
String v = atts.getValue("version");
if (v == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
return;
}
if (!(v.equals("0.6"))) {
throwException(tr("Unsupported version: {0}", v));
return;
}
// ---- PARSING NODES AND WAYS ----
} else if (qName.equals("node")) {
Node n = new Node();
NodeData source = new NodeData();
source.setCoor(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
readCommon(atts, source);
Node target = (Node) targetDataSet.getPrimitiveById(source.getUniqueId(), source.getType());
if (target == null || !(source.isModified() || source.isDeleted()))
n.load(source);
else {
n.cloneFrom(target);
n.load(source);
}
currentPrimitive = n;
externalIdMap.put(source.getPrimitiveId(), n);
} else if (qName.equals("way")) {
Way w = new Way();
WayData source = new WayData();
readCommon(atts, source);
Way target = (Way) targetDataSet.getPrimitiveById(source.getUniqueId(), source.getType());
if (target == null || !(source.isModified() || source.isDeleted()))
w.load(source);
else {
w.cloneFrom(target);
w.load(source);
}
currentPrimitive = w;
currentWayNodes.clear();
externalIdMap.put(source.getPrimitiveId(), w);
} else if (qName.equals("nd")) {
if (atts.getValue("ref") == null)
throwException(tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", currentPrimitive.getUniqueId()));
long id = getLong(atts, "ref");
if (id == 0)
throwException(tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id));
Node node = (Node) externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
if (node == null || node.isModified()) {
node = (Node) targetDataSet.getPrimitiveById(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
if (node == null)
throwException(tr("Missing definition of new object with id {0}.", id));
}
currentWayNodes.add(node);
} else if (qName.equals("relation")) { // ---- PARSING RELATIONS ----
Relation r = new Relation();
RelationData source = new RelationData();
readCommon(atts, source);
Relation target = (Relation) targetDataSet.getPrimitiveById(source.getUniqueId(), source.getType());
if (target == null || !(source.isModified() || source.isDeleted()))
r.load(source);
else {
r.cloneFrom(target);
r.load(source);
}
currentPrimitive = r;
currentRelationMembers.clear();
externalIdMap.put(source.getPrimitiveId(), r);
} else if (qName.equals("member")) {
if (atts.getValue("ref") == null)
throwException(tr("Missing mandatory attribute ''{0}'' on <member> of relation {1}.",
"ref", currentPrimitive.getUniqueId()));
long id = getLong(atts, "ref");
if (id == 0)
throwException(tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id));
OsmPrimitiveType type = OsmPrimitiveType.NODE;
String value = atts.getValue("type");
if (value == null) {
throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(id),
Long.toString(currentPrimitive.getUniqueId())));
}
try {
type = OsmPrimitiveType.fromApiTypeName(value);
} catch (IllegalArgumentException e) {
throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.",
Long.toString(id), Long.toString(currentPrimitive.getUniqueId()), value));
}
String role = atts.getValue("role");
OsmPrimitive member = externalIdMap.get(new SimplePrimitiveId(id, type));
if (member == null) {
member = targetDataSet.getPrimitiveById(new SimplePrimitiveId(id, type));
if (member == null)
throwException(tr("Missing definition of new object with id {0}.", id));
}
RelationMember relationMember = new RelationMember(role, member);
currentRelationMembers.add(relationMember);
} else if (qName.equals("tag")) { // ---- PARSING TAGS (applicable to all objects) ----
String key = atts.getValue("k");
String value = atts.getValue("v");
if (key == null || value == null) {
throwException(tr("Missing key or value attribute in tag."));
return;
}
currentPrimitive.put(key.intern(), value.intern());
} else {
Main.warn(tr("Undefined element ''{0}'' found in input stream. Skipping.", qName));
}
} catch (Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
@Override
public void endElement(String namespaceURI, String localName, String qName) {
if (qName.equals("node")) {
if (currentPrimitive.isDeleted()) {
cmds.add(new DeleteCommand(targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId())));
} else if (currentPrimitive.isModified()) {
cmds.add(new ChangeCommand(Main.getLayerManager().getEditLayer(),
targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId()), currentPrimitive));
} else if (currentPrimitive.isNew()) {
cmds.add(new AddCommand(currentPrimitive));
}
} else if (qName.equals("way")) {
((Way) currentPrimitive).setNodes(currentWayNodes);
if (currentPrimitive.isDeleted()) {
cmds.add(new DeleteCommand(targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId())));
} else if (currentPrimitive.isModified()) {
cmds.add(new ChangeCommand(Main.getLayerManager().getEditLayer(),
targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId()), currentPrimitive));
} else if (currentPrimitive.isNew()) {
cmds.add(new AddCommand(currentPrimitive));
}
} else if (qName.equals("relation")) {
((Relation) currentPrimitive).setMembers(currentRelationMembers);
if (currentPrimitive.isDeleted()) {
cmds.add(new DeleteCommand(targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId())));
} else if (currentPrimitive.isModified()) {
cmds.add(new ChangeCommand(Main.getLayerManager().getEditLayer(),
targetDataSet.getPrimitiveById(currentPrimitive.getPrimitiveId()), currentPrimitive));
} else if (currentPrimitive.isNew()) {
cmds.add(new AddCommand(currentPrimitive));
}
}
}
@Override
public void comment(char[] ch, int start, int length) {
parentPlugin.printHistory(String.valueOf(ch));
}
@Override
public void startCDATA() {
}
@Override
public void endCDATA() {
}
@Override
public void startEntity(String name) {
}
@Override
public void endEntity(String name) {
}
@Override
public void startDTD(String name, String publicId, String systemId) {
}
@Override
public void endDTD() {
}
private double getDouble(Attributes atts, String value) {
return Double.parseDouble(atts.getValue(value));
}
private long getLong(Attributes atts, String name) throws SAXException {
String value = atts.getValue(name);
if (value == null) {
throwException(tr("Missing required attribute ''{0}''.", name));
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.", name, value));
}
return 0; // should not happen
}
private User createUser(String uid, String name) throws SAXException {
if (uid == null) {
if (name == null)
return null;
return User.createLocalUser(name);
}
try {
long id = Long.parseLong(uid);
return User.createOsmUser(id, name);
} catch (NumberFormatException e) {
throwException(tr("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
}
return null;
}
void readCommon(Attributes atts, PrimitiveData current) throws SAXException {
current.setId(getLong(atts, "id"));
if (current.getUniqueId() == 0) {
throwException(tr("Illegal object with ID=0."));
}
String time = atts.getValue("timestamp");
if (time != null && time.length() != 0) {
current.setTimestamp(DateUtils.fromString(time));
}
String user = atts.getValue("user");
String uid = atts.getValue("uid");
current.setUser(createUser(uid, user));
String visible = atts.getValue("visible");
if (visible != null) {
current.setVisible(Boolean.parseBoolean(visible));
}
String versionString = atts.getValue("version");
int version = 0;
if (versionString != null) {
try {
version = Integer.parseInt(versionString);
} catch (NumberFormatException e) {
throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.",
Long.toString(current.getUniqueId()), versionString));
}
}
current.setVersion(version);
String action = atts.getValue("action");
if (action == null) {
// do nothing
} else if (action.equals("delete")) {
current.setDeleted(true);
current.setModified(current.isVisible());
} else if (action.equals("modify")) {
current.setModified(true);
}
String v = atts.getValue("changeset");
if (v == null) {
current.setChangesetId(0);
} else {
try {
current.setChangesetId(Integer.parseInt(v));
} catch (NumberFormatException e) {
if (current.getUniqueId() <= 0) {
Main.warn(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
v, current.getUniqueId()));
current.setChangesetId(0);
} else {
throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
}
}
if (current.getChangesetId() <= 0) {
if (current.getUniqueId() <= 0) {
Main.warn(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.",
v, current.getUniqueId()));
current.setChangesetId(0);
} else {
throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
}
}
}
}
}
}