001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.InputStream;
007    import java.io.InputStreamReader;
008    import java.text.MessageFormat;
009    import java.util.LinkedList;
010    import java.util.List;
011    
012    import javax.xml.parsers.ParserConfigurationException;
013    import javax.xml.parsers.SAXParserFactory;
014    
015    import org.openstreetmap.josm.data.coor.LatLon;
016    import org.openstreetmap.josm.data.osm.Changeset;
017    import org.openstreetmap.josm.data.osm.User;
018    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
019    import org.openstreetmap.josm.tools.DateUtils;
020    import org.xml.sax.Attributes;
021    import org.xml.sax.InputSource;
022    import org.xml.sax.Locator;
023    import org.xml.sax.SAXException;
024    import org.xml.sax.helpers.DefaultHandler;
025    
026    /**
027     * Parser for a list of changesets, encapsulated in an OSM data set structure.
028     * Example:
029     * <pre>
030     * &lt;osm version="0.6" generator="OpenStreetMap server"&gt;
031     *     &lt;changeset id="143" user="guggis" uid="1" created_at="2009-09-08T20:35:39Z" closed_at="2009-09-08T21:36:12Z" open="false" min_lon="7.380925" min_lat="46.9215164" max_lon="7.3984718" max_lat="46.9226502"&gt;
032     *         &lt;tag k="asdfasdf" v="asdfasdf"/&gt;
033     *         &lt;tag k="created_by" v="JOSM/1.5 (UNKNOWN de)"/&gt;
034     *         &lt;tag k="comment" v="1234"/&gt;
035     *     &lt;/changeset&gt;
036     * &lt;/osm&gt;
037     * </pre>
038     *
039     */
040    public class OsmChangesetParser {
041        private List<Changeset> changesets;
042    
043        private OsmChangesetParser() {
044            changesets = new LinkedList<Changeset>();
045        }
046    
047        public List<Changeset> getChangesets() {
048            return changesets;
049        }
050    
051        private class Parser extends DefaultHandler {
052            private Locator locator;
053    
054            @Override
055            public void setDocumentLocator(Locator locator) {
056                this.locator = locator;
057            }
058    
059            protected void throwException(String msg) throws OsmDataParsingException{
060                throw new OsmDataParsingException(msg).rememberLocation(locator);
061            }
062            /**
063             * The current changeset
064             */
065            private Changeset current = null;
066    
067            protected void parseChangesetAttributes(Changeset cs, Attributes atts) throws OsmDataParsingException {
068                // -- id
069                String value = atts.getValue("id");
070                if (value == null) {
071                    throwException(tr("Missing mandatory attribute ''{0}''.", "id"));
072                }
073                int id = 0;
074                try {
075                    id = Integer.parseInt(value);
076                } catch(NumberFormatException e) {
077                    throwException(tr("Illegal value for attribute ''{0}''. Got ''{1}''.", "id", value));
078                }
079                if (id <= 0) {
080                    throwException(tr("Illegal numeric value for attribute ''{0}''. Got ''{1}''.", "id", id));
081                }
082                current.setId(id);
083    
084                // -- user
085                String user = atts.getValue("user");
086                String uid = atts.getValue("uid");
087                current.setUser(createUser(uid, user));
088    
089                // -- created_at
090                value = atts.getValue("created_at");
091                if (value == null) {
092                    current.setCreatedAt(null);
093                } else {
094                    current.setCreatedAt(DateUtils.fromString(value));
095                }
096    
097                // -- closed_at
098                value = atts.getValue("closed_at");
099                if (value == null) {
100                    current.setClosedAt(null);
101                } else {
102                    current.setClosedAt(DateUtils.fromString(value));
103                }
104    
105                //  -- open
106                value = atts.getValue("open");
107                if (value == null) {
108                    throwException(tr("Missing mandatory attribute ''{0}''.", "open"));
109                } else if (value.equals("true")) {
110                    current.setOpen(true);
111                } else if (value.equals("false")) {
112                    current.setOpen(false);
113                } else {
114                    throwException(tr("Illegal boolean value for attribute ''{0}''. Got ''{1}''.", "open", value));
115                }
116    
117                // -- min_lon and min_lat
118                String min_lon = atts.getValue("min_lon");
119                String min_lat = atts.getValue("min_lat");
120                String max_lon = atts.getValue("max_lon");
121                String max_lat = atts.getValue("max_lat");
122                if (min_lon != null && min_lat != null && max_lon != null && max_lat != null) {
123                    double minLon = 0;
124                    try {
125                        minLon = Double.parseDouble(min_lon);
126                    } catch(NumberFormatException e) {
127                        throwException(tr("Illegal value for attribute ''{0}''. Got ''{1}''.", "min_lon", min_lon));
128                    }
129                    double minLat = 0;
130                    try {
131                        minLat = Double.parseDouble(min_lat);
132                    } catch(NumberFormatException e) {
133                        throwException(tr("Illegal value for attribute ''{0}''. Got ''{1}''.", "min_lat", min_lat));
134                    }
135                    current.setMin(new LatLon(minLat, minLon));
136    
137                    // -- max_lon and max_lat
138    
139                    double maxLon = 0;
140                    try {
141                        maxLon = Double.parseDouble(max_lon);
142                    } catch(NumberFormatException e) {
143                        throwException(tr("Illegal value for attribute ''{0}''. Got ''{1}''.", "max_lon", max_lon));
144                    }
145                    double maxLat = 0;
146                    try {
147                        maxLat = Double.parseDouble(max_lat);
148                    } catch(NumberFormatException e) {
149                        throwException(tr("Illegal value for attribute ''{0}''. Got ''{1}''.", "max_lat", max_lat));
150                    }
151                    current.setMax(new LatLon(maxLon, maxLat));
152                }
153            }
154    
155            @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
156                if (qName.equals("osm")) {
157                    if (atts == null) {
158                        throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}.", "version", "osm"));
159                    }
160                    String v = atts.getValue("version");
161                    if (v == null) {
162                        throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
163                    }
164                    if (!(v.equals("0.6"))) {
165                        throwException(tr("Unsupported version: {0}", v));
166                    }
167                } else if (qName.equals("changeset")) {
168                    current = new Changeset();
169                    parseChangesetAttributes(current, atts);
170                } else if (qName.equals("tag")) {
171                    String key = atts.getValue("k");
172                    String value = atts.getValue("v");
173                    current.put(key, value);
174                } else {
175                    throwException(tr("Undefined element ''{0}'' found in input stream. Aborting.", qName));
176                }
177            }
178    
179            @Override
180            public void endElement(String uri, String localName, String qName) throws SAXException {
181                if (qName.equals("changeset")) {
182                    changesets.add(current);
183                }
184            }
185    
186            protected User createUser(String uid, String name) throws OsmDataParsingException {
187                if (uid == null) {
188                    if (name == null)
189                        return null;
190                    return User.createLocalUser(name);
191                }
192                try {
193                    long id = Long.parseLong(uid);
194                    return User.createOsmUser(id, name);
195                } catch(NumberFormatException e) {
196                    throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
197                }
198                return null;
199            }
200        }
201    
202        /**
203         * Parse the given input source and return the list of changesets
204         *
205         * @param source the source input stream
206         * @param progressMonitor  the progress monitor
207         *
208         * @return the list of changesets
209         * @throws IllegalDataException thrown if the an error was found while parsing the data from the source
210         */
211        public static List<Changeset> parse(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
212            OsmChangesetParser parser = new OsmChangesetParser();
213            try {
214                progressMonitor.beginTask("");
215                progressMonitor.indeterminateSubTask(tr("Parsing list of changesets..."));
216                InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
217                SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser.new Parser());
218                return parser.getChangesets();
219            } catch(ParserConfigurationException e) {
220                throw new IllegalDataException(e.getMessage(), e);
221            } catch(SAXException e) {
222                throw new IllegalDataException(e.getMessage(), e);
223            } catch(Exception e) {
224                throw new IllegalDataException(e);
225            } finally {
226                progressMonitor.finishTask();
227            }
228        }
229    }