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