[7451] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.io;
|
---|
| 3 |
|
---|
[7663] | 4 | import java.io.ByteArrayInputStream;
|
---|
[7451] | 5 | import java.io.IOException;
|
---|
| 6 | import java.io.InputStream;
|
---|
[7663] | 7 | import java.nio.charset.StandardCharsets;
|
---|
[7451] | 8 | import java.util.ArrayList;
|
---|
| 9 | import java.util.Date;
|
---|
| 10 | import java.util.List;
|
---|
| 11 |
|
---|
| 12 | import javax.xml.parsers.ParserConfigurationException;
|
---|
| 13 |
|
---|
| 14 | import org.openstreetmap.josm.Main;
|
---|
| 15 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
| 16 | import org.openstreetmap.josm.data.notes.Note;
|
---|
| 17 | import org.openstreetmap.josm.data.notes.NoteComment;
|
---|
| 18 | import org.openstreetmap.josm.data.notes.NoteComment.Action;
|
---|
| 19 | import org.openstreetmap.josm.data.osm.User;
|
---|
[8287] | 20 | import org.openstreetmap.josm.tools.Utils;
|
---|
[8225] | 21 | import org.openstreetmap.josm.tools.date.DateUtils;
|
---|
[7451] | 22 | import org.xml.sax.Attributes;
|
---|
| 23 | import org.xml.sax.InputSource;
|
---|
| 24 | import org.xml.sax.SAXException;
|
---|
| 25 | import org.xml.sax.helpers.DefaultHandler;
|
---|
| 26 |
|
---|
| 27 | /**
|
---|
[7474] | 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.
|
---|
[7451] | 31 | */
|
---|
| 32 | public class NoteReader {
|
---|
| 33 |
|
---|
[9078] | 34 | private final InputSource inputSource;
|
---|
[7451] | 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 | */
|
---|
[9059] | 42 | private enum NoteParseMode {
|
---|
| 43 | API,
|
---|
| 44 | DUMP
|
---|
| 45 | }
|
---|
[7451] | 46 |
|
---|
| 47 | /**
|
---|
[7474] | 48 | * SAX handler to read note information from its XML representation.
|
---|
| 49 | * Reads both API style and planet dump style formats.
|
---|
[7451] | 50 | */
|
---|
[7474] | 51 | private class Parser extends DefaultHandler {
|
---|
| 52 |
|
---|
| 53 | private NoteParseMode parseMode;
|
---|
[9078] | 54 | private final StringBuilder buffer = new StringBuilder();
|
---|
[7451] | 55 | private Note thisNote;
|
---|
[7474] | 56 | private long commentUid;
|
---|
[7451] | 57 | private String commentUsername;
|
---|
| 58 | private Action noteAction;
|
---|
[7474] | 59 | private Date commentCreateDate;
|
---|
[8377] | 60 | private boolean commentIsNew;
|
---|
[7474] | 61 | private List<Note> notes;
|
---|
[8285] | 62 | private String commentText;
|
---|
[7451] | 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) {
|
---|
[7474] | 73 | case "osm":
|
---|
| 74 | parseMode = NoteParseMode.API;
|
---|
[9070] | 75 | notes = new ArrayList<>(100);
|
---|
[7474] | 76 | return;
|
---|
| 77 | case "osm-notes":
|
---|
| 78 | parseMode = NoteParseMode.DUMP;
|
---|
[9070] | 79 | notes = new ArrayList<>(10000);
|
---|
[7474] | 80 | return;
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | if (parseMode == NoteParseMode.API) {
|
---|
[8510] | 84 | if ("note".equals(qName)) {
|
---|
[7474] | 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) {
|
---|
[7451] | 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");
|
---|
[8510] | 102 | if (closedTimeStr == null) { //no closed_at means the note is still open
|
---|
[7451] | 103 | thisNote.setState(Note.State.open);
|
---|
| 104 | } else {
|
---|
| 105 | thisNote.setState(Note.State.closed);
|
---|
[8225] | 106 | thisNote.setClosedAt(DateUtils.fromString(closedTimeStr));
|
---|
[7451] | 107 | }
|
---|
[8225] | 108 | thisNote.setCreatedAt(DateUtils.fromString(attrs.getValue("created_at")));
|
---|
[7451] | 109 | break;
|
---|
| 110 | case "comment":
|
---|
| 111 | String uidStr = attrs.getValue("uid");
|
---|
[8510] | 112 | if (uidStr == null) {
|
---|
[7451] | 113 | commentUid = 0;
|
---|
| 114 | } else {
|
---|
| 115 | commentUid = Long.parseLong(uidStr);
|
---|
| 116 | }
|
---|
| 117 | commentUsername = attrs.getValue("user");
|
---|
| 118 | noteAction = Action.valueOf(attrs.getValue("action"));
|
---|
[8225] | 119 | commentCreateDate = DateUtils.fromString(attrs.getValue("timestamp"));
|
---|
[7451] | 120 | String isNew = attrs.getValue("is_new");
|
---|
[8510] | 121 | if (isNew == null) {
|
---|
[7451] | 122 | commentIsNew = false;
|
---|
| 123 | } else {
|
---|
[8377] | 124 | commentIsNew = Boolean.parseBoolean(isNew);
|
---|
[7451] | 125 | }
|
---|
| 126 | break;
|
---|
| 127 | }
|
---|
| 128 | }
|
---|
| 129 |
|
---|
| 130 | @Override
|
---|
[7474] | 131 | public void endElement(String namespaceURI, String localName, String qName) {
|
---|
[8510] | 132 | if ("note".equals(qName)) {
|
---|
[7474] | 133 | notes.add(thisNote);
|
---|
[7451] | 134 | }
|
---|
[8510] | 135 | if ("comment".equals(qName)) {
|
---|
[7474] | 136 | User commentUser = User.createOsmUser(commentUid, commentUsername);
|
---|
[7732] | 137 | if (commentUid == 0) {
|
---|
| 138 | commentUser = User.getAnonymous();
|
---|
| 139 | }
|
---|
[8510] | 140 | if (parseMode == NoteParseMode.API) {
|
---|
[7474] | 141 | commentIsNew = false;
|
---|
| 142 | }
|
---|
[8510] | 143 | if (parseMode == NoteParseMode.DUMP) {
|
---|
[7474] | 144 | commentText = buffer.toString();
|
---|
| 145 | }
|
---|
| 146 | thisNote.addComment(new NoteComment(commentCreateDate, commentUser, commentText, noteAction, commentIsNew));
|
---|
| 147 | commentUid = 0;
|
---|
| 148 | commentUsername = null;
|
---|
| 149 | commentCreateDate = null;
|
---|
[8377] | 150 | commentIsNew = false;
|
---|
[7474] | 151 | commentText = null;
|
---|
| 152 | }
|
---|
[8510] | 153 | if (parseMode == NoteParseMode.DUMP) {
|
---|
[7474] | 154 | return;
|
---|
| 155 | }
|
---|
[7451] | 156 |
|
---|
[7474] | 157 | //the rest only applies to API mode
|
---|
[7451] | 158 | switch (qName) {
|
---|
| 159 | case "id":
|
---|
[7474] | 160 | thisNote.setId(Long.parseLong(buffer.toString()));
|
---|
[7451] | 161 | break;
|
---|
| 162 | case "status":
|
---|
[7474] | 163 | thisNote.setState(Note.State.valueOf(buffer.toString()));
|
---|
[7451] | 164 | break;
|
---|
| 165 | case "date_created":
|
---|
[8225] | 166 | thisNote.setCreatedAt(DateUtils.fromString(buffer.toString()));
|
---|
[7451] | 167 | break;
|
---|
[7732] | 168 | case "date_closed":
|
---|
[8225] | 169 | thisNote.setClosedAt(DateUtils.fromString(buffer.toString()));
|
---|
[7732] | 170 | break;
|
---|
[7451] | 171 | case "date":
|
---|
[8225] | 172 | commentCreateDate = DateUtils.fromString(buffer.toString());
|
---|
[7451] | 173 | break;
|
---|
| 174 | case "user":
|
---|
[7474] | 175 | commentUsername = buffer.toString();
|
---|
[7451] | 176 | break;
|
---|
| 177 | case "uid":
|
---|
[7474] | 178 | commentUid = Long.parseLong(buffer.toString());
|
---|
[7451] | 179 | break;
|
---|
| 180 | case "text":
|
---|
[7474] | 181 | commentText = buffer.toString();
|
---|
| 182 | buffer.setLength(0);
|
---|
[7451] | 183 | break;
|
---|
[7474] | 184 | case "action":
|
---|
| 185 | noteAction = Action.valueOf(buffer.toString());
|
---|
| 186 | break;
|
---|
| 187 | case "note": //nothing to do for comment or note, already handled above
|
---|
[7451] | 188 | case "comment":
|
---|
| 189 | break;
|
---|
| 190 | }
|
---|
| 191 | }
|
---|
| 192 |
|
---|
| 193 | @Override
|
---|
| 194 | public void endDocument() throws SAXException {
|
---|
| 195 | parsedNotes = notes;
|
---|
| 196 | }
|
---|
| 197 | }
|
---|
| 198 |
|
---|
| 199 | /**
|
---|
| 200 | * Initializes the reader with a given InputStream
|
---|
| 201 | * @param source - InputStream containing Notes XML
|
---|
[8470] | 202 | * @throws IOException if any I/O error occurs
|
---|
[7451] | 203 | */
|
---|
[7474] | 204 | public NoteReader(InputStream source) throws IOException {
|
---|
[7451] | 205 | this.inputSource = new InputSource(source);
|
---|
| 206 | }
|
---|
| 207 |
|
---|
| 208 | /**
|
---|
[7663] | 209 | * Initializes the reader with a string as a source
|
---|
| 210 | * @param source UTF-8 string containing Notes XML to parse
|
---|
[8470] | 211 | * @throws IOException if any I/O error occurs
|
---|
[7663] | 212 | */
|
---|
| 213 | public NoteReader(String source) throws IOException {
|
---|
| 214 | this.inputSource = new InputSource(new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8)));
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | /**
|
---|
[7451] | 218 | * Parses the InputStream given to the constructor and returns
|
---|
| 219 | * the resulting Note objects
|
---|
| 220 | * @return List of Notes parsed from the input data
|
---|
[8470] | 221 | * @throws SAXException if any SAX parsing error occurs
|
---|
| 222 | * @throws IOException if any I/O error occurs
|
---|
[7451] | 223 | */
|
---|
| 224 | public List<Note> parse() throws SAXException, IOException {
|
---|
[7474] | 225 | DefaultHandler parser = new Parser();
|
---|
[7451] | 226 | try {
|
---|
[8347] | 227 | Utils.parseSafeSAX(inputSource, parser);
|
---|
[7451] | 228 | } catch (ParserConfigurationException e) {
|
---|
| 229 | Main.error(e); // broken SAXException chaining
|
---|
| 230 | throw new SAXException(e);
|
---|
| 231 | }
|
---|
| 232 | return parsedNotes;
|
---|
| 233 | }
|
---|
| 234 | }
|
---|