Changeset 4413 in josm for trunk


Ignore:
Timestamp:
2011-09-10T10:56:24+02:00 (13 years ago)
Author:
bastiK
Message:

make osm reader more robust - don't mess up xml structure when there are unknown elements (see #6742)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/OsmReader.java

    r4389 r4413  
    55
    66import java.io.InputStream;
     7import java.io.InputStreamReader;
    78import java.text.MessageFormat;
    89import java.util.ArrayList;
     
    1213import java.util.List;
    1314import java.util.Map;
    14 
    15 import javax.xml.parsers.ParserConfigurationException;
    16 import javax.xml.parsers.SAXParserFactory;
     15import java.util.regex.Matcher;
     16import java.util.regex.Pattern;
     17
     18import javax.xml.stream.XMLInputFactory;
     19import javax.xml.stream.XMLStreamReader;
     20import javax.xml.stream.XMLStreamConstants;
     21import javax.xml.stream.XMLStreamException;
     22import javax.xml.stream.Location;
    1723
    1824import org.openstreetmap.josm.data.Bounds;
     
    3743import org.openstreetmap.josm.tools.CheckParameterUtil;
    3844import org.openstreetmap.josm.tools.DateUtils;
    39 import org.xml.sax.Attributes;
    40 import org.xml.sax.InputSource;
    41 import org.xml.sax.Locator;
    42 import org.xml.sax.SAXException;
    43 import org.xml.sax.SAXParseException;
    44 import org.xml.sax.helpers.DefaultHandler;
    4545
    4646/**
    4747 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
    4848 *
     49 * For each xml element, there is a dedicated method.
     50 * The XMLStreamReader cursor points to the start of the element, when the method is
     51 * entered, and it must point to the end of the same element, when it is exited.
    4952 */
    5053public class OsmReader {
    51     /**
    52      * The dataset to add parsed objects to.
    53      */
    54     private DataSet ds = new DataSet();
    55 
    56     /**
    57      * Replies the parsed data set
    58      *
    59      * @return the parsed data set
    60      */
    61     public DataSet getDataSet() {
    62         return ds;
    63     }
    64 
    65     /** the map from external ids to read OsmPrimitives. External ids are
    66      * longs too, but in contrast to internal ids negative values are used
    67      * to identify primitives unknown to the OSM server
    68      */
    69     private Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
    70 
    71     /**
    72      * constructor (for private use only)
    73      *
    74      * @see #parseDataSet(InputStream, DataSet, ProgressMonitor)
    75      * @see #parseDataSetOsm(InputStream, DataSet, ProgressMonitor)
    76      */
    77     private OsmReader() {
    78         externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
    79     }
    8054
    8155    /**
     
    9064
    9165    /**
     66     * The dataset to add parsed objects to.
     67     */
     68    private DataSet ds = new DataSet();
     69
     70    private XMLStreamReader parser;
     71
     72    /** the map from external ids to read OsmPrimitives. External ids are
     73     * longs too, but in contrast to internal ids negative values are used
     74     * to identify primitives unknown to the OSM server
     75     */
     76    private Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
     77
     78    /**
    9279     * Data structure for the remaining way objects
    9380     */
     
    9986    private Map<Long, Collection<RelationMemberData>> relations = new HashMap<Long, Collection<RelationMemberData>>();
    10087
    101     private class Parser extends DefaultHandler {
    102         private Locator locator;
     88    /**
     89     * constructor (for private use only)
     90     *
     91     * @see #parseDataSet(InputStream, DataSet, ProgressMonitor)
     92     */
     93    private OsmReader() {
     94        externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
     95    }
     96
     97    public void setParser(XMLStreamReader parser) {
     98        this.parser = parser;
     99    }
     100
     101    /**
     102     * Replies the parsed data set
     103     *
     104     * @return the parsed data set
     105     */
     106    public DataSet getDataSet() {
     107        return ds;
     108    }
     109
     110    protected void throwException(String msg) throws XMLStreamException {
     111        throw new OsmParsingException(msg, parser.getLocation());
     112    }
     113
     114    public void parse() throws XMLStreamException {
     115        int event = parser.getEventType();
     116        while (true) {
     117            if (event == XMLStreamConstants.START_ELEMENT) {
     118                if (parser.getLocalName().equals("osm") || parser.getLocalName().equals("osmChange")) {
     119                    parseOsm();
     120                } else {
     121                    parseUnkown();
     122                }
     123            } else if (event == XMLStreamConstants.END_ELEMENT) {
     124                return;
     125            }
     126            if (parser.hasNext()) {
     127                event = parser.next();
     128            } else {
     129                break;
     130            }
     131        }
     132        parser.close();
     133    }
     134
     135    private void parseOsm() throws XMLStreamException {
     136        String v = parser.getAttributeValue(null, "version");
     137        if (v == null) {
     138            throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
     139        }
     140        if (!(v.equals("0.5") || v.equals("0.6"))) {
     141            throwException(tr("Unsupported version: {0}", v));
     142        }
     143        String generator = parser.getAttributeValue(null, "generator");
     144        ds.setVersion(v);
     145        while (true) {
     146            int event = parser.next();
     147            if (event == XMLStreamConstants.START_ELEMENT) {
     148                if (parser.getLocalName().equals("bounds")) {
     149                    parseBounds(generator);
     150                } else if (parser.getLocalName().equals("node")) {
     151                    parseNode();
     152                } else if (parser.getLocalName().equals("way")) {
     153                    parseWay();
     154                } else if (parser.getLocalName().equals("relation")) {
     155                    parseRelation();
     156                } else {
     157                    parseUnkown();
     158                }
     159            } else if (event == XMLStreamConstants.END_ELEMENT) {
     160                return;
     161            }
     162        }
     163    }
     164
     165    private void parseBounds(String generator) throws XMLStreamException {
     166        String minlon = parser.getAttributeValue(null, "minlon");
     167        String minlat = parser.getAttributeValue(null, "minlat");
     168        String maxlon = parser.getAttributeValue(null, "maxlon");
     169        String maxlat = parser.getAttributeValue(null, "maxlat");
     170        String origin = parser.getAttributeValue(null, "origin");
     171        if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
     172            if (origin == null) {
     173                origin = generator;
     174            }
     175            Bounds bounds = new Bounds(
     176                    Double.parseDouble(minlat), Double.parseDouble(minlon),
     177                    Double.parseDouble(maxlat), Double.parseDouble(maxlon));
     178            if (bounds.isOutOfTheWorld()) {
     179                Bounds copy = new Bounds(bounds);
     180                bounds.normalize();
     181                System.out.println("Bbox " + copy + " is out of the world, normalized to " + bounds);
     182            }
     183            DataSource src = new DataSource(bounds, origin);
     184            ds.dataSources.add(src);
     185        } else {
     186            throwException(tr(
     187                    "Missing mandatory attributes on element ''bounds''. Got minlon=''{0}'',minlat=''{1}'',maxlon=''{3}'',maxlat=''{4}'', origin=''{5}''.",
     188                    minlon, minlat, maxlon, maxlat, origin
     189            ));
     190        }
     191        jumpToEnd();
     192    }
     193
     194    private void parseNode() throws XMLStreamException {
     195        NodeData nd = new NodeData();
     196        nd.setCoor(new LatLon(Double.parseDouble(parser.getAttributeValue(null, "lat")), Double.parseDouble(parser.getAttributeValue(null, "lon"))));
     197        readCommon(nd);
     198        Node n = new Node(nd.getId(), nd.getVersion());
     199        n.setVisible(nd.isVisible());
     200        n.load(nd);
     201        externalIdMap.put(nd.getPrimitiveId(), n);
     202        while (true) {
     203            int event = parser.next();
     204            if (event == XMLStreamConstants.START_ELEMENT) {
     205                if (parser.getLocalName().equals("tag")) {
     206                    parseTag(n);
     207                } else {
     208                    parseUnkown();
     209                }
     210            } else if (event == XMLStreamConstants.END_ELEMENT) {
     211                return;
     212            }
     213        }
     214    }
     215
     216    private void parseWay() throws XMLStreamException {
     217        WayData wd = new WayData();
     218        readCommon(wd);
     219        Way w = new Way(wd.getId(), wd.getVersion());
     220        w.setVisible(wd.isVisible());
     221        w.load(wd);
     222        externalIdMap.put(wd.getPrimitiveId(), w);
     223
     224        Collection<Long> nodeIds = new ArrayList<Long>();
     225        while (true) {
     226            int event = parser.next();
     227            if (event == XMLStreamConstants.START_ELEMENT) {
     228                if (parser.getLocalName().equals("nd")) {
     229                    nodeIds.add(parseWayNode(w));
     230                } else if (parser.getLocalName().equals("tag")) {
     231                    parseTag(w);
     232                } else {
     233                    parseUnkown();
     234                }
     235            } else if (event == XMLStreamConstants.END_ELEMENT) {
     236                break;
     237            }
     238        }
     239        if (w.isDeleted() && nodeIds.size() > 0) {
     240            System.out.println(tr("Deleted way {0} contains nodes", w.getUniqueId()));
     241            nodeIds = new ArrayList<Long>();
     242        }
     243        ways.put(wd.getUniqueId(), nodeIds);
     244    }
     245
     246    private long parseWayNode(Way w) throws XMLStreamException {
     247        if (parser.getAttributeValue(null, "ref") == null) {
     248            throwException(
     249                    tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", w.getUniqueId())
     250            );
     251        }
     252        long id = getLong("ref");
     253        if (id == 0) {
     254            throwException(
     255                    tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id)
     256            );
     257        }
     258        jumpToEnd();
     259        return id;
     260    }
     261
     262    private void parseRelation() throws XMLStreamException {
     263        RelationData rd = new RelationData();
     264        readCommon(rd);
     265        Relation r = new Relation(rd.getId(), rd.getVersion());
     266        r.setVisible(rd.isVisible());
     267        r.load(rd);
     268        externalIdMap.put(rd.getPrimitiveId(), r);
     269
     270        Collection<RelationMemberData> members = new ArrayList<RelationMemberData>();
     271        while (true) {
     272            int event = parser.next();
     273            if (event == XMLStreamConstants.START_ELEMENT) {
     274                if (parser.getLocalName().equals("member")) {
     275                    members.add(parseRelationMember(r));
     276                } else if (parser.getLocalName().equals("tag")) {
     277                    parseTag(r);
     278                } else {
     279                    parseUnkown();
     280                }
     281            } else if (event == XMLStreamConstants.END_ELEMENT) {
     282                break;
     283            }
     284        }
     285        if (r.isDeleted() && members.size() > 0) {
     286            System.out.println(tr("Deleted relation {0} contains members", r.getUniqueId()));
     287            members = new ArrayList<RelationMemberData>();
     288        }
     289        relations.put(rd.getUniqueId(), members);
     290    }
     291
     292    private RelationMemberData parseRelationMember(Relation r) throws XMLStreamException {
     293        RelationMemberData emd = new RelationMemberData();
     294        String value = parser.getAttributeValue(null, "ref");
     295        if (value == null) {
     296            throwException(tr("Missing attribute ''ref'' on member in relation {0}.",r.getUniqueId()));
     297        }
     298        try {
     299            emd.id = Long.parseLong(value);
     300        } catch(NumberFormatException e) {
     301            throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(r.getUniqueId()),value));
     302        }
     303        value = parser.getAttributeValue(null, "type");
     304        if (value == null) {
     305            throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(emd.id), Long.toString(r.getUniqueId())));
     306        }
     307        try {
     308            emd.type = OsmPrimitiveType.fromApiTypeName(value);
     309        } catch(IllegalArgumentException e) {
     310            throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", Long.toString(emd.id), Long.toString(r.getUniqueId()), value));
     311        }
     312        value = parser.getAttributeValue(null, "role");
     313        emd.role = value;
     314
     315        if (emd.id == 0) {
     316            throwException(tr("Incomplete <member> specification with ref=0"));
     317        }
     318        jumpToEnd();
     319        return emd;
     320    }
     321
     322    private void parseTag(OsmPrimitive osm) throws XMLStreamException {
     323        String key = parser.getAttributeValue(null, "k");
     324        String value = parser.getAttributeValue(null, "v");
     325        if (key == null || value == null) {
     326            throwException(tr("Missing key or value attribute in tag."));
     327        }
     328        osm.put(key.intern(), value.intern());
     329        jumpToEnd();
     330    }
     331
     332    /**
     333     * When cursor is at the start of an element, moves it to the end tag of that element.
     334     * Nested content is skipped.
     335     *
     336     * This is basically the same code as parseUnkown(), except for the warnings, which
     337     * are displayed for inner elements and not at top level.
     338     */
     339    private void jumpToEnd() throws XMLStreamException {
     340        while (true) {
     341            int event = parser.next();
     342            if (event == XMLStreamConstants.START_ELEMENT) {
     343                parseUnkown();
     344            } else if (event == XMLStreamConstants.END_ELEMENT) {
     345                return;
     346            }
     347        }
     348    }
     349
     350    private void parseUnkown() throws XMLStreamException {
     351        parseUnkown(true);
     352    }
     353
     354    private void parseUnkown(boolean printWarning) throws XMLStreamException {
     355        if (printWarning) {
     356            System.out.println(tr("Undefined element ''{0}'' found in input stream. Skipping.", parser.getLocalName()));
     357        }
     358        while (true) {
     359            int event = parser.next();
     360            if (event == XMLStreamConstants.START_ELEMENT) {
     361                parseUnkown(false); /* no more warning for inner elements */
     362            } else if (event == XMLStreamConstants.END_ELEMENT) {
     363                return;
     364            }
     365        }
     366    }
     367
     368    private User createUser(String uid, String name) throws XMLStreamException {
     369        if (uid == null) {
     370            if (name == null)
     371                return null;
     372            return User.createLocalUser(name);
     373        }
     374        try {
     375            long id = Long.parseLong(uid);
     376            return User.createOsmUser(id, name);
     377        } catch(NumberFormatException e) {
     378            throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
     379        }
     380        return null;
     381    }
     382
     383    /**
     384     * Read out the common attributes and put them into current OsmPrimitive.
     385     */
     386    private void readCommon(PrimitiveData current) throws XMLStreamException {
     387        current.setId(getLong("id"));
     388        if (current.getUniqueId() == 0) {
     389            throwException(tr("Illegal object with ID=0."));
     390        }
     391
     392        String time = parser.getAttributeValue(null, "timestamp");
     393        if (time != null && time.length() != 0) {
     394            current.setTimestamp(DateUtils.fromString(time));
     395        }
     396
     397        // user attribute added in 0.4 API
     398        String user = parser.getAttributeValue(null, "user");
     399        // uid attribute added in 0.6 API
     400        String uid = parser.getAttributeValue(null, "uid");
     401        current.setUser(createUser(uid, user));
     402
     403        // visible attribute added in 0.4 API
     404        String visible = parser.getAttributeValue(null, "visible");
     405        if (visible != null) {
     406            current.setVisible(Boolean.parseBoolean(visible));
     407        }
     408
     409        String versionString = parser.getAttributeValue(null, "version");
     410        int version = 0;
     411        if (versionString != null) {
     412            try {
     413                version = Integer.parseInt(versionString);
     414            } catch(NumberFormatException e) {
     415                throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
     416            }
     417            if (ds.getVersion().equals("0.6")){
     418                if (version <= 0 && current.getUniqueId() > 0) {
     419                    throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
     420                } else if (version < 0 && current.getUniqueId() <= 0) {
     421                    System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.6"));
     422                    version = 0;
     423                }
     424            } else if (ds.getVersion().equals("0.5")) {
     425                if (version <= 0 && current.getUniqueId() > 0) {
     426                    System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
     427                    version = 1;
     428                } else if (version < 0 && current.getUniqueId() <= 0) {
     429                    System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.5"));
     430                    version = 0;
     431                }
     432            } else {
     433                // should not happen. API version has been checked before
     434                throwException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion()));
     435            }
     436        } else {
     437            // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
     438            //
     439            if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.6")) {
     440                throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId())));
     441            } else if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
     442                // default version in 0.5 files for existing primitives
     443                System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
     444                version= 1;
     445            } else if (current.getUniqueId() <= 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
     446                // default version in 0.5 files for new primitives, no warning necessary. This is
     447                // (was) legal in API 0.5
     448                version= 0;
     449            }
     450        }
     451        current.setVersion(version);
     452
     453        String action = parser.getAttributeValue(null, "action");
     454        if (action == null) {
     455            // do nothing
     456        } else if (action.equals("delete")) {
     457            current.setDeleted(true);
     458            current.setModified(current.isVisible());
     459        } else if (action.equals("modify")) {
     460            current.setModified(true);
     461        }
     462
     463        String v = parser.getAttributeValue(null, "changeset");
     464        if (v == null) {
     465            current.setChangesetId(0);
     466        } else {
     467            try {
     468                current.setChangesetId(Integer.parseInt(v));
     469            } catch(NumberFormatException e) {
     470                if (current.getUniqueId() <= 0) {
     471                    // for a new primitive we just log a warning
     472                    System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
     473                    current.setChangesetId(0);
     474                } else {
     475                    // for an existing primitive this is a problem
     476                    throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
     477                }
     478            }
     479            if (current.getChangesetId() <=0) {
     480                if (current.getUniqueId() <= 0) {
     481                    // for a new primitive we just log a warning
     482                    System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
     483                    current.setChangesetId(0);
     484                } else {
     485                    // for an existing primitive this is a problem
     486                    throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
     487                }
     488            }
     489        }
     490    }
     491
     492    private long getLong(String name) throws XMLStreamException {
     493        String value = parser.getAttributeValue(null, name);
     494        if (value == null) {
     495            throwException(tr("Missing required attribute ''{0}''.",name));
     496        }
     497        try {
     498            return Long.parseLong(value);
     499        } catch(NumberFormatException e) {
     500            throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.",name, value));
     501        }
     502        return 0; // should not happen
     503    }
     504
     505    private static class OsmParsingException extends XMLStreamException {
     506        public OsmParsingException() {
     507            super();
     508        }
     509
     510        public OsmParsingException(String msg) {
     511            super(msg);
     512        }
     513
     514        public OsmParsingException(String msg, Location location) {
     515            super(msg); /* cannot use super(msg, location) because it messes with the message preventing localization */
     516            this.location = location;
     517        }
     518
     519        public OsmParsingException(String msg, Location location, Throwable th) {
     520            super(msg, th);
     521            this.location = location;
     522        }
     523
     524        public OsmParsingException(String msg, Throwable th) {
     525            super(msg, th);
     526        }
     527
     528        public OsmParsingException(Throwable th) {
     529            super(th);
     530        }
    103531
    104532        @Override
    105         public void setDocumentLocator(Locator locator) {
    106             this.locator = locator;
    107         }
    108 
    109         protected void throwException(String msg) throws OsmDataParsingException{
    110             throw new OsmDataParsingException(msg).rememberLocation(locator);
    111         }
    112         /**
    113          * The current osm primitive to be read.
    114          */
    115         private OsmPrimitive currentPrimitive;
    116         private long currentExternalId;
    117         private String generator;
    118 
    119         @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
    120 
    121             try {
    122                 if (qName.equals("osm") || qName.equals("osmChange")) {
    123                     if (atts == null) {
    124                         throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}.", "version", qName));
    125                     }
    126                     String v = atts.getValue("version");
    127                     if (v == null) {
    128                         throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
    129                     }
    130                     if (!(v.equals("0.5") || v.equals("0.6"))) {
    131                         throwException(tr("Unsupported version: {0}", v));
    132                     }
    133                     // save generator attribute for later use when creating DataSource objects
    134                     generator = atts.getValue("generator");
    135                     ds.setVersion(v);
    136 
    137                 } else if (qName.equals("bounds")) {
    138                     // new style bounds.
    139                     String minlon = atts.getValue("minlon");
    140                     String minlat = atts.getValue("minlat");
    141                     String maxlon = atts.getValue("maxlon");
    142                     String maxlat = atts.getValue("maxlat");
    143                     String origin = atts.getValue("origin");
    144                     if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
    145                         if (origin == null) {
    146                             origin = generator;
    147                         }
    148                         Bounds bounds = new Bounds(
    149                                 Double.parseDouble(minlat), Double.parseDouble(minlon),
    150                                 Double.parseDouble(maxlat), Double.parseDouble(maxlon));
    151                         if (bounds.isOutOfTheWorld()) {
    152                             Bounds copy = new Bounds(bounds);
    153                             bounds.normalize();
    154                             System.out.println("Bbox " + copy + " is out of the world, normalized to " + bounds);
    155                         }
    156                         DataSource src = new DataSource(bounds, origin);
    157                         ds.dataSources.add(src);
    158                     } else {
    159                         throwException(tr(
    160                                 "Missing mandatory attributes on element ''bounds''. Got minlon=''{0}'',minlat=''{1}'',maxlon=''{3}'',maxlat=''{4}'', origin=''{5}''.",
    161                                 minlon, minlat, maxlon, maxlat, origin
    162                         ));
    163                     }
    164 
    165                     // ---- PARSING NODES AND WAYS ----
    166 
    167                 } else if (qName.equals("node")) {
    168                     NodeData nd = new NodeData();
    169                     nd.setCoor(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
    170                     readCommon(atts, nd);
    171                     Node n = new Node(nd.getId(), nd.getVersion());
    172                     n.setVisible(nd.isVisible());
    173                     n.load(nd);
    174                     externalIdMap.put(nd.getPrimitiveId(), n);
    175                     currentPrimitive = n;
    176                     currentExternalId = nd.getUniqueId();
    177                 } else if (qName.equals("way")) {
    178                     WayData wd = new WayData();
    179                     readCommon(atts, wd);
    180                     Way w = new Way(wd.getId(), wd.getVersion());
    181                     w.setVisible(wd.isVisible());
    182                     w.load(wd);
    183                     externalIdMap.put(wd.getPrimitiveId(), w);
    184                     ways.put(wd.getUniqueId(), new ArrayList<Long>());
    185                     currentPrimitive = w;
    186                     currentExternalId = wd.getUniqueId();
    187                 } else if (qName.equals("nd")) {
    188                     Collection<Long> list = ways.get(currentExternalId);
    189                     if (list == null) {
    190                         throwException(
    191                                 tr("Found XML element <nd> not as direct child of element <way>.")
    192                         );
    193                     }
    194                     if (atts.getValue("ref") == null) {
    195                         throwException(
    196                                 tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", currentPrimitive.getUniqueId())
    197                         );
    198                     }
    199                     long id = getLong(atts, "ref");
    200                     if (id == 0) {
    201                         throwException(
    202                                 tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id)
    203                         );
    204                     }
    205                     if (currentPrimitive.isDeleted()) {
    206                         System.out.println(tr("Deleted way {0} contains nodes", currentPrimitive.getUniqueId()));
    207                     } else {
    208                         list.add(id);
    209                     }
    210 
    211                     // ---- PARSING RELATIONS ----
    212 
    213                 } else if (qName.equals("relation")) {
    214                     RelationData rd = new RelationData();
    215                     readCommon(atts, rd);
    216                     Relation r = new Relation(rd.getId(), rd.getVersion());
    217                     r.setVisible(rd.isVisible());
    218                     r.load(rd);
    219                     externalIdMap.put(rd.getPrimitiveId(), r);
    220                     relations.put(rd.getUniqueId(), new LinkedList<RelationMemberData>());
    221                     currentPrimitive = r;
    222                     currentExternalId = rd.getUniqueId();
    223                 } else if (qName.equals("member")) {
    224                     Collection<RelationMemberData> list = relations.get(currentExternalId);
    225                     if (list == null) {
    226                         throwException(
    227                                 tr("Found XML element <member> not as direct child of element <relation>.")
    228                         );
    229                     }
    230                     RelationMemberData emd = new RelationMemberData();
    231                     String value = atts.getValue("ref");
    232                     if (value == null) {
    233                         throwException(tr("Missing attribute ''ref'' on member in relation {0}.",currentPrimitive.getUniqueId()));
    234                     }
    235                     try {
    236                         emd.id = Long.parseLong(value);
    237                     } catch(NumberFormatException e) {
    238                         throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(currentPrimitive.getUniqueId()),value));
    239                     }
    240                     value = atts.getValue("type");
    241                     if (value == null) {
    242                         throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(emd.id), Long.toString(currentPrimitive.getUniqueId())));
    243                     }
    244                     try {
    245                         emd.type = OsmPrimitiveType.fromApiTypeName(value);
    246                     } catch(IllegalArgumentException e) {
    247                         throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", Long.toString(emd.id), Long.toString(currentPrimitive.getUniqueId()), value));
    248                     }
    249                     value = atts.getValue("role");
    250                     emd.role = value;
    251 
    252                     if (emd.id == 0) {
    253                         throwException(tr("Incomplete <member> specification with ref=0"));
    254                     }
    255 
    256                     if (currentPrimitive.isDeleted()) {
    257                         System.out.println(tr("Deleted relation {0} contains members", currentPrimitive.getUniqueId()));
    258                     } else {
    259                         list.add(emd);
    260                     }
    261 
    262                     // ---- PARSING TAGS (applicable to all objects) ----
    263 
    264                 } else if (qName.equals("tag")) {
    265                     String key = atts.getValue("k");
    266                     String value = atts.getValue("v");
    267                     if (key == null || value == null) {
    268                         throwException(tr("Missing key or value attribute in tag."));
    269                     }
    270                     if (currentPrimitive != null) {
    271                         currentPrimitive.put(key.intern(), value.intern());
    272                     }
    273                 } else {
    274                     System.out.println(tr("Undefined element ''{0}'' found in input stream. Skipping.", qName));
    275                 }
    276             } catch (Exception e) {
    277                 throw new SAXParseException(e.getMessage(), locator, e);
    278             }
    279         }
    280 
    281         private double getDouble(Attributes atts, String value) {
    282             return Double.parseDouble(atts.getValue(value));
    283         }
    284 
    285         private User createUser(String uid, String name) throws SAXException {
    286             if (uid == null) {
    287                 if (name == null)
    288                     return null;
    289                 return User.createLocalUser(name);
    290             }
    291             try {
    292                 long id = Long.parseLong(uid);
    293                 return User.createOsmUser(id, name);
    294             } catch(NumberFormatException e) {
    295                 throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
    296             }
    297             return null;
    298         }
    299         /**
    300          * Read out the common attributes from atts and put them into this.current.
    301          */
    302         void readCommon(Attributes atts, PrimitiveData current) throws SAXException {
    303             current.setId(getLong(atts, "id"));
    304             if (current.getUniqueId() == 0) {
    305                 throwException(tr("Illegal object with ID=0."));
    306             }
    307 
    308             String time = atts.getValue("timestamp");
    309             if (time != null && time.length() != 0) {
    310                 current.setTimestamp(DateUtils.fromString(time));
    311             }
    312 
    313             // user attribute added in 0.4 API
    314             String user = atts.getValue("user");
    315             // uid attribute added in 0.6 API
    316             String uid = atts.getValue("uid");
    317             current.setUser(createUser(uid, user));
    318 
    319             // visible attribute added in 0.4 API
    320             String visible = atts.getValue("visible");
    321             if (visible != null) {
    322                 current.setVisible(Boolean.parseBoolean(visible));
    323             }
    324 
    325             String versionString = atts.getValue("version");
    326             int version = 0;
    327             if (versionString != null) {
    328                 try {
    329                     version = Integer.parseInt(versionString);
    330                 } catch(NumberFormatException e) {
    331                     throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
    332                 }
    333                 if (ds.getVersion().equals("0.6")){
    334                     if (version <= 0 && current.getUniqueId() > 0) {
    335                         throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
    336                     } else if (version < 0 && current.getUniqueId() <= 0) {
    337                         System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.6"));
    338                         version = 0;
    339                     }
    340                 } else if (ds.getVersion().equals("0.5")) {
    341                     if (version <= 0 && current.getUniqueId() > 0) {
    342                         System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
    343                         version = 1;
    344                     } else if (version < 0 && current.getUniqueId() <= 0) {
    345                         System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.5"));
    346                         version = 0;
    347                     }
    348                 } else {
    349                     // should not happen. API version has been checked before
    350                     throwException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion()));
    351                 }
    352             } else {
    353                 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
    354                 //
    355                 if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.6")) {
    356                     throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId())));
    357                 } else if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
    358                     // default version in 0.5 files for existing primitives
    359                     System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
    360                     version= 1;
    361                 } else if (current.getUniqueId() <= 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
    362                     // default version in 0.5 files for new primitives, no warning necessary. This is
    363                     // (was) legal in API 0.5
    364                     version= 0;
    365                 }
    366             }
    367             current.setVersion(version);
    368 
    369             String action = atts.getValue("action");
    370             if (action == null) {
    371                 // do nothing
    372             } else if (action.equals("delete")) {
    373                 current.setDeleted(true);
    374                 current.setModified(current.isVisible());
    375             } else if (action.equals("modify")) {
    376                 current.setModified(true);
    377             }
    378 
    379             String v = atts.getValue("changeset");
    380             if (v == null) {
    381                 current.setChangesetId(0);
    382             } else {
    383                 try {
    384                     current.setChangesetId(Integer.parseInt(v));
    385                 } catch(NumberFormatException e) {
    386                     if (current.getUniqueId() <= 0) {
    387                         // for a new primitive we just log a warning
    388                         System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
    389                         current.setChangesetId(0);
    390                     } else {
    391                         // for an existing primitive this is a problem
    392                         throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
    393                     }
    394                 }
    395                 if (current.getChangesetId() <=0) {
    396                     if (current.getUniqueId() <= 0) {
    397                         // for a new primitive we just log a warning
    398                         System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
    399                         current.setChangesetId(0);
    400                     } else {
    401                         // for an existing primitive this is a problem
    402                         throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
    403                     }
    404                 }
    405             }
    406         }
    407 
    408         private long getLong(Attributes atts, String name) throws SAXException {
    409             String value = atts.getValue(name);
    410             if (value == null) {
    411                 throwException(tr("Missing required attribute ''{0}''.",name));
    412             }
    413             try {
    414                 return Long.parseLong(value);
    415             } catch(NumberFormatException e) {
    416                 throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.",name, value));
    417             }
    418             return 0; // should not happen
     533        public String getMessage() {
     534            String msg = super.getMessage();
     535            if (msg == null) {
     536                msg = getClass().getName();
     537            }
     538            if (getLocation() == null)
     539                return msg;
     540            msg = msg + " " + tr("(at line {0}, column {1})", getLocation().getLineNumber(), getLocation().getColumnNumber());
     541            return msg;
     542        }
     543    }
     544
     545    /**
     546     * Processes the parsed nodes after parsing. Just adds them to
     547     * the dataset
     548     *
     549     */
     550    protected void processNodesAfterParsing() {
     551        for (OsmPrimitive primitive: externalIdMap.values()) {
     552            if (primitive instanceof Node) {
     553                this.ds.addPrimitive(primitive);
     554            }
    419555        }
    420556    }
     
    462598
    463599    /**
    464      * Processes the parsed nodes after parsing. Just adds them to
    465      * the dataset
    466      *
    467      */
    468     protected void processNodesAfterParsing() {
    469         for (OsmPrimitive primitive: externalIdMap.values()) {
    470             if (primitive instanceof Node) {
    471                 this.ds.addPrimitive(primitive);
    472             }
    473         }
    474     }
    475 
    476     /**
    477600     * Completes the parsed relations with its members.
    478601     *
     
    562685            progressMonitor.indeterminateSubTask(tr("Parsing OSM data..."));
    563686
    564             InputSource inputSource = new InputSource(UTFInputStreamReader.create(source, "UTF-8"));
    565             SAXParserFactory.newInstance().newSAXParser().parse(inputSource, reader.new Parser());
     687            InputStreamReader ir = UTFInputStreamReader.create(source, "UTF-8");
     688            XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(ir);
     689            reader.setParser(parser);
     690            reader.parse();
    566691            progressMonitor.worked(1);
    567692
     
    579704        } catch(IllegalDataException e) {
    580705            throw e;
    581         } catch(ParserConfigurationException e) {
     706        } catch(OsmParsingException e) {
    582707            throw new IllegalDataException(e.getMessage(), e);
    583         } catch (SAXParseException e) {
    584             throw new IllegalDataException(tr("Line {0} column {1}: ", e.getLineNumber(), e.getColumnNumber()) + e.getMessage(), e);
    585         } catch(SAXException e) {
    586             throw new IllegalDataException(e.getMessage(), e);
     708        } catch(XMLStreamException e) {
     709            String msg = e.getMessage();
     710            Pattern p = Pattern.compile("Message: (.+)");
     711            Matcher m = p.matcher(msg);
     712            if (m.find()) {
     713                msg = m.group(1);
     714            }
     715            if (e.getLocation() != null) {
     716                throw new IllegalDataException(tr("Line {0} column {1}: ", e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e);
     717            } else {
     718                throw new IllegalDataException(msg, e);
     719            }
    587720        } catch(Exception e) {
    588721            throw new IllegalDataException(e);
Note: See TracChangeset for help on using the changeset viewer.