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

Last change on this file since 12711 was 12692, checked in by Don-vip, 7 years ago

fix #14704 - properly encode strings

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