1 /*
2 Access Nokia Landmark Exchange files.
3
4 Copyright (C) 2007 Robert Lipe, robertlipe+source@gpsbabel.org
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 */
21
22
23 /*
24 * Nokia's Landmark Exchange (LMX) format is a straight-forward XML
25 * format. Though they do support a compact binary representation,
26 * we don't implement that at this time in GPSBabel.
27 */
28
29 #include <QtCore/QString> // for QString
30 #include <QtCore/QXmlStreamAttributes> // for QXmlStreamAttributes
31
32 #include "defs.h"
33 #include "gbfile.h" // for gbfputc, gbfprintf, gbfclose, gbfopen, gbfputcstr, gbfputs, gbfile, gbfputuint16
34 #include "xmlgeneric.h" // for cb_cdata, xg_callback, xg_string, cb_end, cb_start, xg_cb_type, xml_deinit, xml_init, xml_read, xg_tag_mapping
35
36
37 static gbfile* ofd;
38 static Waypoint* wpt_tmp;
39 static QString urllink;
40 static QString urllinkt;
41 static char* binary = nullptr;
42
43 #define MYNAME "lmx"
44
45 static
46 QVector<arglist_t> lmx_args = {
47 {
48 "binary", &binary,
49 "Compact binary representation",
50 nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
51 },
52 };
53
54 /*
55 * Writer
56 */
57
58
59 static void
lmx_wr_init(const QString & fname)60 lmx_wr_init(const QString& fname)
61 {
62 ofd = gbfopen(fname, "w", MYNAME);
63 }
64
65 static void
lmx_wr_deinit()66 lmx_wr_deinit()
67 {
68 gbfclose(ofd);
69 }
70
71 static const char*
lmx_stag(int tag)72 lmx_stag(int tag)
73 {
74 switch (tag) {
75 case 0xC5:
76 return "lmx";
77 case 0x46:
78 return "landmarkCollection";
79 case 0x47:
80 return "landmark";
81 case 0x48:
82 return "name";
83 case 0x49:
84 return "description";
85 case 0x4A:
86 return "coordinates";
87 case 0x4B:
88 return "latitude";
89 case 0x4C:
90 return "longitude";
91 case 0x4D:
92 return "altitude";
93 case 0x4E:
94 return "horizontalAccuracy";
95 case 0x4F:
96 return "verticalAccuracy";
97 case 0x50:
98 return "timeStamp";
99 case 0x51:
100 return "coverageRadius";
101 case 0x52:
102 return "category";
103 case 0x53:
104 return "id";
105 case 0x54:
106 return "addressInfo";
107 case 0x55:
108 return "country";
109 case 0x56:
110 return "countryCode";
111 case 0x57:
112 return "state";
113 case 0x58:
114 return "county";
115 case 0x59:
116 return "city";
117 case 0x5A:
118 return "district";
119 case 0x5B:
120 return "postalCode";
121 case 0x5C:
122 return "crossing1";
123 case 0x5D:
124 return "crossing2";
125 case 0x5E:
126 return "street";
127 case 0x5F:
128 return "buildingName";
129 case 0x60:
130 return "buildingFloor";
131 case 0x61:
132 return "buildingZone";
133 case 0x62:
134 return "buildingRoom";
135 case 0x63:
136 return "extension";
137 case 0x64:
138 return "phoneNumber";
139 case 0x65:
140 return "mediaLink";
141 case 0x66:
142 return "mime";
143 case 0x67:
144 return "url";
145 default:
146 return nullptr;
147 }
148 }
149
150 static void
lmx_indent(int count)151 lmx_indent(int count)
152 {
153 for (int i = 0; i<count; i++) {
154 gbfputc('\t', ofd);
155 }
156 }
157
158 static void
lmx_start_tag(int tag,int indent)159 lmx_start_tag(int tag, int indent)
160 {
161 if (binary) {
162 gbfputc(tag, ofd);
163 } else {
164 lmx_indent(indent);
165 gbfprintf(ofd, "<lm:%s>", lmx_stag(tag));
166 }
167 }
168
169 static void
lmx_end_tag(int tag,int indent)170 lmx_end_tag(int tag, int indent)
171 {
172 if (binary) {
173 gbfputc(0x01, ofd);
174 } else {
175 lmx_indent(indent);
176 gbfprintf(ofd, "</lm:%s>\n", lmx_stag(tag));
177 }
178 }
179
180 static void
lmx_write_xml(int tag,const QString & data,int indent)181 lmx_write_xml(int tag, const QString& data, int indent)
182 {
183 lmx_start_tag(tag, indent);
184
185 if (binary) {
186 gbfputc(0x03, ofd); // inline string follows
187 gbfputcstr(CSTR(data), ofd);
188 } else {
189 char* tmp_ent = xml_entitize(CSTR(data));
190 gbfputs(tmp_ent, ofd);
191 xfree(tmp_ent);
192 }
193
194 lmx_end_tag(tag, 0);
195 }
196
197 static void
lmx_print(const Waypoint * wpt)198 lmx_print(const Waypoint* wpt)
199 {
200 /*
201 * Desperation time, try very hard to get a good shortname
202 */
203 QString odesc = wpt->notes;
204 if (odesc.isEmpty()) {
205 odesc = wpt->description;
206 }
207 if (odesc.isEmpty()) {
208 odesc = wpt->shortname;
209 }
210
211 QString oname = global_opts.synthesize_shortnames ? odesc : wpt->shortname;
212
213 lmx_start_tag(0x47, 2); // landmark
214 if (!binary) {
215 gbfputc('\n', ofd);
216 }
217 if (!oname.isEmpty()) {
218 lmx_write_xml(0x48, oname, 3); // name
219 }
220 if (!wpt->description.isEmpty()) {
221 lmx_write_xml(0x49, wpt->description, 3); // description
222 }
223 lmx_start_tag(0x4A, 3); // coordinates
224 if (!binary) {
225 gbfputc('\n', ofd);
226 }
227
228 lmx_write_xml(0x4B, QString::number(wpt->latitude, 'f'), 4); // latitude
229
230 lmx_write_xml(0x4C, QString::number(wpt->longitude, 'f'), 4); // longitude
231
232 if (wpt->altitude && (wpt->altitude != unknown_alt)) {
233 lmx_write_xml(0x4D, QString::number(wpt->altitude, 'f'), 4); // altitude
234 }
235 lmx_end_tag(0x4A, 3); // coordinates
236
237 if (wpt->HasUrlLink()) {
238 lmx_start_tag(0x65, 3); // mediaLink
239 if (!binary) {
240 gbfputc('\n', ofd);
241 }
242 UrlLink link = wpt->GetUrlLink();
243 if (!link.url_link_text_.isEmpty()) {
244 lmx_write_xml(0x48, link.url_link_text_, 4); // name
245 }
246 lmx_write_xml(0x67, link.url_, 4); // url
247 lmx_end_tag(0x65, 3); // mediaLink
248 }
249
250 lmx_end_tag(0x47, 2); // landmark
251 }
252
253
254 static void
lmx_write()255 lmx_write()
256 {
257 if (binary) {
258 gbfputc(0x03, ofd); // WBXML version 1.3
259 gbfputuint16(0x04A4, ofd); // "-//NOKIA//DTD LANDMARKS 1.0//EN"
260 gbfputc(106, ofd); // Charset=UTF-8
261 gbfputc(0x00, ofd); // empty string table
262 gbfputc(0xC5, ofd); // lmx
263 gbfputc(0x05, ofd); // xmlns=http://www.nokia.com/schemas/location/landmarks/
264 gbfputc(0x85, ofd); // 1/0/
265 gbfputc(0x06, ofd); // xmlns:xsi=
266 gbfputc(0x86, ofd); // http://www.w3.org/2001/XMLSchema-instance
267 gbfputc(0x07, ofd); // xsi:schemaLocation=http://www.nokia.com/schemas/location/landmarks/
268 gbfputc(0x85, ofd); // 1/0/
269 gbfputc(0x87, ofd); // whitespace
270 gbfputc(0x88, ofd); // lmx.xsd
271 gbfputc(0x01, ofd); // END lmx attributes
272 } else {
273 gbfprintf(ofd, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
274 gbfprintf(ofd, "<lm:lmx xmlns:lm=\"http://www.nokia.com/schemas/location/landmarks/1/0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.nokia.com/schemas/location/landmarks/1/0/ lmx.xsd\">\n");
275 }
276
277 lmx_start_tag(0x46, 1); // landmarkCollection
278 if (!binary) {
279 gbfputc('\n', ofd);
280 }
281 waypt_disp_all(lmx_print);
282 lmx_end_tag(0x46, 1); // landmarkCollection
283 lmx_end_tag(0xC5, 0); // lmx
284 }
285
286 /*
287 * Reader
288 */
289
290 static xg_callback lmx_lm_start, lmx_lm_end;
291 static xg_callback lmx_lm_name,lmx_lm_desc;
292 static xg_callback lmx_lm_lat, lmx_lm_lon, lmx_lm_alt;
293 static xg_callback lmx_lm_mlink_s, lmx_lm_mlink_e;
294 static xg_callback lmx_lm_link, lmx_lm_linkt;
295
296 static xg_tag_mapping gl_map[] = {
297 #define LM "/lm:lmx/lm:landmarkCollection/lm:landmark"
298 { lmx_lm_start, cb_start, LM },
299 { lmx_lm_end, cb_end, LM },
300 { lmx_lm_name, cb_cdata, LM "/lm:name" },
301 { lmx_lm_desc, cb_cdata, LM "/lm:description" },
302 { lmx_lm_lat, cb_cdata, LM "/lm:coordinates/lm:latitude" },
303 { lmx_lm_lon, cb_cdata, LM "/lm:coordinates/lm:longitude" },
304 { lmx_lm_alt, cb_cdata, LM "/lm:coordinates/lm:altitude" },
305 { lmx_lm_mlink_s, cb_start, LM "/lm:mediaLink" },
306 { lmx_lm_link, cb_cdata, LM "/lm:mediaLink/lm:url" },
307 { lmx_lm_linkt, cb_cdata, LM "/lm:mediaLink/lm:name" },
308 { lmx_lm_mlink_e, cb_end, LM "/lm:mediaLink" },
309 { nullptr, (xg_cb_type)0, nullptr}
310 };
311
312 static void
lmx_rd_init(const QString & fname)313 lmx_rd_init(const QString& fname)
314 {
315 xml_init(fname, gl_map, nullptr);
316 }
317
318 static void
lmx_read()319 lmx_read()
320 {
321 xml_read();
322 }
323
324 static void
lmx_rd_deinit()325 lmx_rd_deinit()
326 {
327 xml_deinit();
328 }
329
330
331
332 static void
lmx_lm_start(xg_string,const QXmlStreamAttributes *)333 lmx_lm_start(xg_string, const QXmlStreamAttributes*)
334 {
335 wpt_tmp = new Waypoint;
336 }
337
338 static void
lmx_lm_end(xg_string,const QXmlStreamAttributes *)339 lmx_lm_end(xg_string, const QXmlStreamAttributes*)
340 {
341 waypt_add(wpt_tmp);
342 }
343
344 static void
lmx_lm_lat(xg_string args,const QXmlStreamAttributes *)345 lmx_lm_lat(xg_string args, const QXmlStreamAttributes*)
346 {
347 wpt_tmp->latitude = args.toDouble();
348 }
349
350 static void
lmx_lm_lon(xg_string args,const QXmlStreamAttributes *)351 lmx_lm_lon(xg_string args, const QXmlStreamAttributes*)
352 {
353 wpt_tmp->longitude = args.toDouble();
354 }
355
356 static void
lmx_lm_alt(xg_string args,const QXmlStreamAttributes *)357 lmx_lm_alt(xg_string args, const QXmlStreamAttributes*)
358 {
359 wpt_tmp->altitude = args.toDouble();
360 }
361
362 static void
lmx_lm_name(xg_string args,const QXmlStreamAttributes *)363 lmx_lm_name(xg_string args, const QXmlStreamAttributes*)
364 {
365 wpt_tmp->shortname = args;
366 }
367
368 static void
lmx_lm_desc(xg_string args,const QXmlStreamAttributes *)369 lmx_lm_desc(xg_string args, const QXmlStreamAttributes*)
370 {
371 wpt_tmp->description = args;
372 }
373
374 static void
lmx_lm_mlink_s(xg_string,const QXmlStreamAttributes *)375 lmx_lm_mlink_s(xg_string, const QXmlStreamAttributes*)
376 {
377 urllink = urllinkt = QString();
378 }
379
380 static void
lmx_lm_link(xg_string args,const QXmlStreamAttributes *)381 lmx_lm_link(xg_string args, const QXmlStreamAttributes*)
382 {
383 urllink = args;
384 }
385
386 static void
lmx_lm_linkt(xg_string args,const QXmlStreamAttributes *)387 lmx_lm_linkt(xg_string args, const QXmlStreamAttributes*)
388 {
389 urllinkt = args;
390 }
391
392 static void
lmx_lm_mlink_e(xg_string,const QXmlStreamAttributes *)393 lmx_lm_mlink_e(xg_string, const QXmlStreamAttributes*)
394 {
395 waypt_add_url(wpt_tmp, urllink, urllinkt);
396 }
397
398
399 ff_vecs_t lmx_vecs = {
400 ff_type_file,
401 {
402 (ff_cap)(ff_cap_read | ff_cap_write), /* waypoints */
403 ff_cap_none, /* tracks */
404 ff_cap_none /* routes */
405 },
406 lmx_rd_init,
407 lmx_wr_init,
408 lmx_rd_deinit,
409 lmx_wr_deinit,
410 lmx_read,
411 lmx_write,
412 nullptr,
413 &lmx_args,
414 CET_CHARSET_UTF8, 0 /* CET-REVIEW */
415 , NULL_POS_OPS,
416 nullptr
417 };
418