source: josm/trunk/src/org/openstreetmap/josm/io/NoteReader.java@ 7474

Last change on this file since 7474 was 7474, checked in by Don-vip, 10 years ago

fix #10445 - Allow NoteReader to automatically determine XML style being parsed (patch by ToeBee)

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.IOException;
5import java.io.InputStream;
6import java.text.ParseException;
7import java.text.SimpleDateFormat;
8import java.util.ArrayList;
9import java.util.Date;
10import java.util.List;
11import java.util.Locale;
12
13import javax.xml.parsers.ParserConfigurationException;
14import javax.xml.parsers.SAXParserFactory;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.notes.Note;
19import org.openstreetmap.josm.data.notes.NoteComment;
20import org.openstreetmap.josm.data.notes.NoteComment.Action;
21import org.openstreetmap.josm.data.osm.User;
22import org.xml.sax.Attributes;
23import org.xml.sax.InputSource;
24import org.xml.sax.SAXException;
25import org.xml.sax.helpers.DefaultHandler;
26
27/**
28 * Class to read Note objects from their XML representation. It can take
29 * either API style XML which starts with an "osm" tag or a planet dump
30 * style XML which starts with an "osm-notes" tag.
31 */
32public class NoteReader {
33
34 private InputSource inputSource;
35 private List<Note> parsedNotes;
36
37 /**
38 * Notes can be represented in two XML formats. One is returned by the API
39 * while the other is used to generate the notes dump file. The parser
40 * needs to know which one it is handling.
41 */
42 private enum NoteParseMode {API, DUMP}
43
44 /**
45 * SAX handler to read note information from its XML representation.
46 * Reads both API style and planet dump style formats.
47 */
48 private class Parser extends DefaultHandler {
49
50 private final SimpleDateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.ENGLISH);
51 private final SimpleDateFormat NOTE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.ENGLISH);
52
53 private NoteParseMode parseMode;
54 private StringBuffer buffer = new StringBuffer();
55 private Note thisNote;
56 private long commentUid;
57 private String commentUsername;
58 private Action noteAction;
59 private Date commentCreateDate;
60 private Boolean commentIsNew;
61 private List<Note> notes;
62 String commentText;
63
64 @Override
65 public void characters(char[] ch, int start, int length) throws SAXException {
66 buffer.append(ch, start, length);
67 }
68
69 @Override
70 public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
71 buffer.setLength(0);
72 switch(qName) {
73 case "osm":
74 parseMode = NoteParseMode.API;
75 notes = new ArrayList<Note>(100);
76 return;
77 case "osm-notes":
78 parseMode = NoteParseMode.DUMP;
79 notes = new ArrayList<Note>(10000);
80 return;
81 }
82
83 if (parseMode == NoteParseMode.API) {
84 if("note".equals(qName)) {
85 double lat = Double.parseDouble(attrs.getValue("lat"));
86 double lon = Double.parseDouble(attrs.getValue("lon"));
87 LatLon noteLatLon = new LatLon(lat, lon);
88 thisNote = new Note(noteLatLon);
89 }
90 return;
91 }
92
93 //The rest only applies for dump mode
94 switch(qName) {
95 case "note":
96 double lat = Double.parseDouble(attrs.getValue("lat"));
97 double lon = Double.parseDouble(attrs.getValue("lon"));
98 LatLon noteLatLon = new LatLon(lat, lon);
99 thisNote = new Note(noteLatLon);
100 thisNote.setId(Long.parseLong(attrs.getValue("id")));
101 String closedTimeStr = attrs.getValue("closed_at");
102 if(closedTimeStr == null) { //no closed_at means the note is still open
103 thisNote.setState(Note.State.open);
104 } else {
105 thisNote.setState(Note.State.closed);
106 thisNote.setClosedAt(parseDate(ISO8601_FORMAT, closedTimeStr));
107 }
108 thisNote.setCreatedAt(parseDate(ISO8601_FORMAT, attrs.getValue("created_at")));
109 break;
110 case "comment":
111 String uidStr = attrs.getValue("uid");
112 if(uidStr == null) {
113 commentUid = 0;
114 } else {
115 commentUid = Long.parseLong(uidStr);
116 }
117 commentUsername = attrs.getValue("user");
118 noteAction = Action.valueOf(attrs.getValue("action"));
119 commentCreateDate = parseDate(ISO8601_FORMAT, attrs.getValue("timestamp"));
120 String isNew = attrs.getValue("is_new");
121 if(isNew == null) {
122 commentIsNew = false;
123 } else {
124 commentIsNew = Boolean.valueOf(isNew);
125 }
126 break;
127 }
128 }
129
130 @Override
131 public void endElement(String namespaceURI, String localName, String qName) {
132 if("note".equals(qName)) {
133 notes.add(thisNote);
134 }
135 if("comment".equals(qName)) {
136 User commentUser = User.createOsmUser(commentUid, commentUsername);
137 if(parseMode == NoteParseMode.API) {
138 commentIsNew = false;
139 }
140 if(parseMode == NoteParseMode.DUMP) {
141 commentText = buffer.toString();
142 }
143 thisNote.addComment(new NoteComment(commentCreateDate, commentUser, commentText, noteAction, commentIsNew));
144 commentUid = 0;
145 commentUsername = null;
146 commentCreateDate = null;
147 commentIsNew = null;
148 commentText = null;
149 }
150 if(parseMode == NoteParseMode.DUMP) {
151 return;
152 }
153
154 //the rest only applies to API mode
155 switch (qName) {
156 case "id":
157 thisNote.setId(Long.parseLong(buffer.toString()));
158 break;
159 case "status":
160 thisNote.setState(Note.State.valueOf(buffer.toString()));
161 break;
162 case "date_created":
163 thisNote.setCreatedAt(parseDate(NOTE_DATE_FORMAT, buffer.toString()));
164 break;
165 case "date":
166 commentCreateDate = parseDate(NOTE_DATE_FORMAT, buffer.toString());
167 break;
168 case "user":
169 commentUsername = buffer.toString();
170 break;
171 case "uid":
172 commentUid = Long.parseLong(buffer.toString());
173 break;
174 case "text":
175 commentText = buffer.toString();
176 buffer.setLength(0);
177 break;
178 case "action":
179 noteAction = Action.valueOf(buffer.toString());
180 break;
181 case "note": //nothing to do for comment or note, already handled above
182 case "comment":
183 break;
184 }
185 }
186
187 @Override
188 public void endDocument() throws SAXException {
189 Main.info("parsed notes: " + notes.size());
190 parsedNotes = notes;
191 }
192
193 /**
194 * Convenience method to handle the date parsing try/catch. Will return null if
195 * there is a parsing exception. This means whatever generated this XML is in error
196 * and there isn't anything we can do about it.
197 * @param dateStr - String to parse
198 * @return Parsed date, null if parsing fails
199 */
200 private Date parseDate(SimpleDateFormat sdf, String dateStr) {
201 try {
202 return sdf.parse(dateStr);
203 } catch(ParseException e) {
204 Main.error("error parsing date in note parser");
205 return null;
206 }
207 }
208 }
209
210 /**
211 * Initializes the reader with a given InputStream
212 * @param source - InputStream containing Notes XML
213 * @throws IOException
214 */
215 public NoteReader(InputStream source) throws IOException {
216 this.inputSource = new InputSource(source);
217 }
218
219 /**
220 * Parses the InputStream given to the constructor and returns
221 * the resulting Note objects
222 * @return List of Notes parsed from the input data
223 * @throws SAXException
224 * @throws IOException
225 */
226 public List<Note> parse() throws SAXException, IOException {
227 DefaultHandler parser = new Parser();
228 try {
229 SAXParserFactory factory = SAXParserFactory.newInstance();
230 factory.setNamespaceAware(true);
231 factory.newSAXParser().parse(inputSource, parser);
232 } catch (ParserConfigurationException e) {
233 Main.error(e); // broken SAXException chaining
234 throw new SAXException(e);
235 }
236 return parsedNotes;
237 }
238}
Note: See TracBrowser for help on using the repository browser.