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