source: josm/trunk/src/org/openstreetmap/josm/io/OsmWriter.java@ 10079

Last change on this file since 10079 was 10051, checked in by Don-vip, 8 years ago

fix #12675 - fix XML encoding of user names in changeset requests

  • Property svn:eol-style set to native
File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.PrintWriter;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Comparator;
11import java.util.List;
12import java.util.Map.Entry;
13
14import org.openstreetmap.josm.data.DataSource;
15import org.openstreetmap.josm.data.coor.CoordinateFormat;
16import org.openstreetmap.josm.data.coor.LatLon;
17import org.openstreetmap.josm.data.osm.AbstractPrimitive;
18import org.openstreetmap.josm.data.osm.Changeset;
19import org.openstreetmap.josm.data.osm.DataSet;
20import org.openstreetmap.josm.data.osm.INode;
21import org.openstreetmap.josm.data.osm.IPrimitive;
22import org.openstreetmap.josm.data.osm.IRelation;
23import org.openstreetmap.josm.data.osm.IWay;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.Tagged;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
30import org.openstreetmap.josm.gui.layer.OsmDataLayer;
31import org.openstreetmap.josm.tools.date.DateUtils;
32
33/**
34 * Save the dataset into a stream as osm intern xml format. This is not using any
35 * xml library for storing.
36 * @author imi
37 */
38public class OsmWriter extends XmlWriter implements PrimitiveVisitor {
39
40 public static final String DEFAULT_API_VERSION = "0.6";
41
42 private final boolean osmConform;
43 private boolean withBody = true;
44 private boolean isOsmChange;
45 private String version;
46 private Changeset changeset;
47
48 /**
49 * Constructs a new {@code OsmWriter}.
50 * Do not call this directly. Use {@link OsmWriterFactory} instead.
51 * @param out print writer
52 * @param osmConform if {@code true}, prevents modification attributes to be written to the common part
53 * @param version OSM API version (0.6)
54 */
55 protected OsmWriter(PrintWriter out, boolean osmConform, String version) {
56 super(out);
57 this.osmConform = osmConform;
58 this.version = version == null ? DEFAULT_API_VERSION : version;
59 }
60
61 public void setWithBody(boolean wb) {
62 this.withBody = wb;
63 }
64
65 public void setIsOsmChange(boolean isOsmChange) {
66 this.isOsmChange = isOsmChange;
67 }
68
69 public void setChangeset(Changeset cs) {
70 this.changeset = cs;
71 }
72
73 public void setVersion(String v) {
74 this.version = v;
75 }
76
77 public void header() {
78 header(null);
79 }
80
81 public void header(Boolean upload) {
82 out.println("<?xml version='1.0' encoding='UTF-8'?>");
83 out.print("<osm version='");
84 out.print(version);
85 if (upload != null) {
86 out.print("' upload='");
87 out.print(upload);
88 }
89 out.println("' generator='JOSM'>");
90 }
91
92 public void footer() {
93 out.println("</osm>");
94 }
95
96 /**
97 * Sorts {@code -1} &rarr; {@code -infinity}, then {@code +1} &rarr; {@code +infinity}
98 */
99 protected static final Comparator<AbstractPrimitive> byIdComparator = new Comparator<AbstractPrimitive>() {
100 @Override public int compare(AbstractPrimitive o1, AbstractPrimitive o2) {
101 final long i1 = o1.getUniqueId();
102 final long i2 = o2.getUniqueId();
103 if (i1 < 0 && i2 < 0) {
104 return Long.compare(i2, i1);
105 } else {
106 return Long.compare(i1, i2);
107 }
108 }
109 };
110
111 protected <T extends OsmPrimitive> Collection<T> sortById(Collection<T> primitives) {
112 List<T> result = new ArrayList<>(primitives.size());
113 result.addAll(primitives);
114 Collections.sort(result, byIdComparator);
115 return result;
116 }
117
118 public void writeLayer(OsmDataLayer layer) {
119 header(!layer.isUploadDiscouraged());
120 writeDataSources(layer.data);
121 writeContent(layer.data);
122 footer();
123 }
124
125 /**
126 * Writes the contents of the given dataset (nodes, then ways, then relations)
127 * @param ds The dataset to write
128 */
129 public void writeContent(DataSet ds) {
130 writeNodes(ds.getNodes());
131 writeWays(ds.getWays());
132 writeRelations(ds.getRelations());
133 }
134
135 /**
136 * Writes the given nodes sorted by id
137 * @param nodes The nodes to write
138 * @since 5737
139 */
140 public void writeNodes(Collection<Node> nodes) {
141 for (Node n : sortById(nodes)) {
142 if (shouldWrite(n)) {
143 visit(n);
144 }
145 }
146 }
147
148 /**
149 * Writes the given ways sorted by id
150 * @param ways The ways to write
151 * @since 5737
152 */
153 public void writeWays(Collection<Way> ways) {
154 for (Way w : sortById(ways)) {
155 if (shouldWrite(w)) {
156 visit(w);
157 }
158 }
159 }
160
161 /**
162 * Writes the given relations sorted by id
163 * @param relations The relations to write
164 * @since 5737
165 */
166 public void writeRelations(Collection<Relation> relations) {
167 for (Relation r : sortById(relations)) {
168 if (shouldWrite(r)) {
169 visit(r);
170 }
171 }
172 }
173
174 protected boolean shouldWrite(OsmPrimitive osm) {
175 return !osm.isNewOrUndeleted() || !osm.isDeleted();
176 }
177
178 public void writeDataSources(DataSet ds) {
179 for (DataSource s : ds.dataSources) {
180 out.println(" <bounds minlat='"
181 + s.bounds.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES)
182 +"' minlon='"
183 + s.bounds.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES)
184 +"' maxlat='"
185 + s.bounds.getMax().latToString(CoordinateFormat.DECIMAL_DEGREES)
186 +"' maxlon='"
187 + s.bounds.getMax().lonToString(CoordinateFormat.DECIMAL_DEGREES)
188 +"' origin='"+XmlWriter.encode(s.origin)+"' />");
189 }
190 }
191
192 @Override
193 public void visit(INode n) {
194 if (n.isIncomplete()) return;
195 addCommon(n, "node");
196 if (!withBody) {
197 out.println("/>");
198 } else {
199 if (n.getCoor() != null) {
200 out.print(" lat='"+LatLon.cDdHighPecisionFormatter.format(n.getCoor().lat())+
201 "' lon='"+LatLon.cDdHighPecisionFormatter.format(n.getCoor().lon())+'\'');
202 }
203 addTags(n, "node", true);
204 }
205 }
206
207 @Override
208 public void visit(IWay w) {
209 if (w.isIncomplete()) return;
210 addCommon(w, "way");
211 if (!withBody) {
212 out.println("/>");
213 } else {
214 out.println(">");
215 for (int i = 0; i < w.getNodesCount(); ++i) {
216 out.println(" <nd ref='"+w.getNodeId(i) +"' />");
217 }
218 addTags(w, "way", false);
219 }
220 }
221
222 @Override
223 public void visit(IRelation e) {
224 if (e.isIncomplete()) return;
225 addCommon(e, "relation");
226 if (!withBody) {
227 out.println("/>");
228 } else {
229 out.println(">");
230 for (int i = 0; i < e.getMembersCount(); ++i) {
231 out.print(" <member type='");
232 out.print(e.getMemberType(i).getAPIName());
233 out.println("' ref='"+e.getMemberId(i)+"' role='" +
234 XmlWriter.encode(e.getRole(i)) + "' />");
235 }
236 addTags(e, "relation", false);
237 }
238 }
239
240 public void visit(Changeset cs) {
241 out.print(" <changeset id='"+cs.getId()+'\'');
242 if (cs.getUser() != null) {
243 out.print(" user='"+ XmlWriter.encode(cs.getUser().getName()) +'\'');
244 out.print(" uid='"+cs.getUser().getId() +'\'');
245 }
246 if (cs.getCreatedAt() != null) {
247 out.print(" created_at='"+DateUtils.fromDate(cs.getCreatedAt()) +'\'');
248 }
249 if (cs.getClosedAt() != null) {
250 out.print(" closed_at='"+DateUtils.fromDate(cs.getClosedAt()) +'\'');
251 }
252 out.print(" open='"+ (cs.isOpen() ? "true" : "false") +'\'');
253 if (cs.getMin() != null) {
254 out.print(" min_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
255 out.print(" min_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
256 }
257 if (cs.getMax() != null) {
258 out.print(" max_lon='"+ cs.getMin().lonToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
259 out.print(" max_lat='"+ cs.getMin().latToString(CoordinateFormat.DECIMAL_DEGREES) +'\'');
260 }
261 out.println(">");
262 addTags(cs, "changeset", false); // also writes closing </changeset>
263 }
264
265 protected static final Comparator<Entry<String, String>> byKeyComparator = new Comparator<Entry<String, String>>() {
266 @Override
267 public int compare(Entry<String, String> o1, Entry<String, String> o2) {
268 return o1.getKey().compareTo(o2.getKey());
269 }
270 };
271
272 protected void addTags(Tagged osm, String tagname, boolean tagOpen) {
273 if (osm.hasKeys()) {
274 if (tagOpen) {
275 out.println(">");
276 }
277 List<Entry<String, String>> entries = new ArrayList<>(osm.getKeys().entrySet());
278 Collections.sort(entries, byKeyComparator);
279 for (Entry<String, String> e : entries) {
280 out.println(" <tag k='"+ XmlWriter.encode(e.getKey()) +
281 "' v='"+XmlWriter.encode(e.getValue())+ "' />");
282 }
283 out.println(" </" + tagname + '>');
284 } else if (tagOpen) {
285 out.println(" />");
286 } else {
287 out.println(" </" + tagname + '>');
288 }
289 }
290
291 /**
292 * Add the common part as the form of the tag as well as the XML attributes
293 * id, action, user, and visible.
294 * @param osm osm primitive
295 * @param tagname XML tag matching osm primitive (node, way, relation)
296 */
297 protected void addCommon(IPrimitive osm, String tagname) {
298 out.print(" <"+tagname);
299 if (osm.getUniqueId() != 0) {
300 out.print(" id='"+ osm.getUniqueId()+'\'');
301 } else
302 throw new IllegalStateException(tr("Unexpected id 0 for osm primitive found"));
303 if (!isOsmChange) {
304 if (!osmConform) {
305 String action = null;
306 if (osm.isDeleted()) {
307 action = "delete";
308 } else if (osm.isModified()) {
309 action = "modify";
310 }
311 if (action != null) {
312 out.print(" action='"+action+'\'');
313 }
314 }
315 if (!osm.isTimestampEmpty()) {
316 out.print(" timestamp='"+DateUtils.fromTimestamp(osm.getRawTimestamp())+'\'');
317 }
318 // user and visible added with 0.4 API
319 if (osm.getUser() != null) {
320 if (osm.getUser().isLocalUser()) {
321 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+'\'');
322 } else if (osm.getUser().isOsmUser()) {
323 // uid added with 0.6
324 out.print(" uid='"+ osm.getUser().getId()+'\'');
325 out.print(" user='"+XmlWriter.encode(osm.getUser().getName())+'\'');
326 }
327 }
328 out.print(" visible='"+osm.isVisible()+'\'');
329 }
330 if (osm.getVersion() != 0) {
331 out.print(" version='"+osm.getVersion()+'\'');
332 }
333 if (this.changeset != null && this.changeset.getId() != 0) {
334 out.print(" changeset='"+this.changeset.getId()+'\'');
335 } else if (osm.getChangesetId() > 0 && !osm.isNew()) {
336 out.print(" changeset='"+osm.getChangesetId()+'\'');
337 }
338 }
339}
Note: See TracBrowser for help on using the repository browser.