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 * <osm version="0.6" generator="OpenStreetMap server"> 031 * <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"> 032 * <tag k="asdfasdf" v="asdfasdf"/> 033 * <tag k="created_by" v="JOSM/1.5 (UNKNOWN de)"/> 034 * <tag k="comment" v="1234"/> 035 * </changeset> 036 * </osm> 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 }