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

Last change on this file since 8126 was 7732, checked in by Don-vip, 9 years ago

fix #10766 - Save notes to file (patch by ToeBee)

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