1 /*
2 Output only format for Human Readable formats.
3
4 Copyright (C) 2004 Scott Brynen, scott (at) brynen.com
5 Copyright (C) 2002 Robert Lipe, robertlipe+source@gpsbabel.org
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <cstdint>
23 #include <ctime> // for localtime
24
25 #include <QtCore/QString> // for QString, operator!=
26 #include <QtCore/QVector> // for QVector
27 #include <QtCore/Qt> // for CaseInsensitive
28
29 #include "defs.h"
30 #include "formspec.h" // for FsChainFind, kFsGpx
31 #include "gbfile.h" // for gbfprintf, gbfclose, gbfopen, gbfputs, gbfile
32 #include "jeeps/gpsmath.h" // for GPS_Math_WGS84_To_UTM_EN
33 #include "src/core/datetime.h" // for DateTime
34 #include "src/core/xmltag.h" // for xml_findfirst, xml_attribute, xml_tag, fs_xml, xml_findnext
35
36
37 static gbfile* file_out;
38 static short_handle mkshort_handle;
39
40 static char* stylesheet = nullptr;
41 static char* html_encrypt = nullptr;
42 static char* includelogs = nullptr;
43 static char* degformat = nullptr;
44 static char* altunits = nullptr;
45
46 #define MYNAME "HTML"
47
48 static
49 QVector<arglist_t> html_args = {
50 {
51 "stylesheet", &stylesheet,
52 "Path to HTML style sheet", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
53 },
54 {
55 "encrypt", &html_encrypt,
56 "Encrypt hints using ROT13", nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
57 },
58 {
59 "logs", &includelogs,
60 "Include groundspeak logs if present", nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
61 },
62 {
63 "degformat", °format,
64 "Degrees output as 'ddd', 'dmm'(default) or 'dms'", "dmm", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
65 },
66 {
67 "altunits", &altunits,
68 "Units for altitude (f)eet or (m)etres", "m", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
69 },
70 };
71
72
73
74 static void
wr_init(const QString & fname)75 wr_init(const QString& fname)
76 {
77 file_out = gbfopen(fname, "w", MYNAME);
78 mkshort_handle = mkshort_new_handle();
79 }
80
81 static void
wr_deinit()82 wr_deinit()
83 {
84 gbfclose(file_out);
85 mkshort_del_handle(&mkshort_handle);
86 }
87
88 static void
html_disp(const Waypoint * wpt)89 html_disp(const Waypoint* wpt)
90 {
91 int32_t utmz;
92 double utme;
93 double utmn;
94 char utmzc;
95
96
97 GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
98 &utme, &utmn, &utmz, &utmzc);
99
100 gbfprintf(file_out, "\n<a name=\"%s\"><hr></a>\n", CSTR(wpt->shortname));
101 gbfprintf(file_out, "<table width=\"100%%\">\n");
102 gbfprintf(file_out, "<tr><td><p class=\"gpsbabelwaypoint\">%s - ",(global_opts.synthesize_shortnames) ? CSTR(mkshort_from_wpt(mkshort_handle, wpt)) : CSTR(wpt->shortname));
103 char* cout = pretty_deg_format(wpt->latitude, wpt->longitude, degformat[2], " ", 1);
104 gbfprintf(file_out, "%s (%d%c %6.0f %7.0f)", cout, utmz, utmzc, utme, utmn);
105 xfree(cout);
106 if (wpt->altitude != unknown_alt) {
107 gbfprintf(file_out, " alt:%d", (int)((altunits[0]=='f')?METERS_TO_FEET(wpt->altitude):wpt->altitude));
108 }
109 gbfprintf(file_out, "<br>\n");
110 if (wpt->description != wpt->shortname) {
111 if (wpt->HasUrlLink()) {
112 char* d = html_entitize(CSTR(wpt->description));
113 UrlLink link = wpt->GetUrlLink();
114 gbfprintf(file_out, "<a href=\"%s\">%s</a>", CSTR(link.url_), d);
115 xfree(d);
116 } else {
117 gbfprintf(file_out, "%s", CSTR(wpt->description));
118 }
119 if (!wpt->gc_data->placer.isEmpty()) {
120 gbfprintf(file_out, " by %s", CSTR(wpt->gc_data->placer));
121 }
122 }
123 gbfprintf(file_out, "</p></td>\n");
124
125 gbfprintf(file_out, "<td align=\"right\">");
126 if (wpt->gc_data->terr) {
127 gbfprintf(file_out, "<p class=\"gpsbabelcacheinfo\">%d%s / %d%s<br>\n",
128 (int)(wpt->gc_data->diff / 10), (wpt->gc_data->diff%10)?"½":"",
129 (int)(wpt->gc_data->terr / 10), (wpt->gc_data->terr%10)?"½":"");
130 gbfprintf(file_out, "%s / %s</p>",
131 gs_get_cachetype(wpt->gc_data->type),
132 gs_get_container(wpt->gc_data->container));
133 }
134 gbfprintf(file_out, "</td></tr>\n");
135
136
137 gbfprintf(file_out, "<tr><td colspan=\"2\">");
138 if (!wpt->gc_data->desc_short.utfstring.isEmpty()) {
139 char* tmpstr = strip_nastyhtml(wpt->gc_data->desc_short.utfstring);
140 gbfprintf(file_out, "<p class=\"gpsbabeldescshort\">%s</p>\n", tmpstr);
141 xfree(tmpstr);
142 }
143 if (!wpt->gc_data->desc_long.utfstring.isEmpty()) {
144 char* tmpstr = strip_nastyhtml(wpt->gc_data->desc_long.utfstring);
145 gbfprintf(file_out, "<p class=\"gpsbabeldesclong\">%s</p>\n", tmpstr);
146 xfree(tmpstr);
147 }
148 if (!wpt->gc_data->hint.isEmpty()) {
149 QString hint;
150 if (html_encrypt) {
151 hint = rot13(wpt->gc_data->hint);
152 } else {
153 hint = wpt->gc_data->hint;
154 }
155 gbfprintf(file_out, "<p class=\"gpsbabelhint\"><strong>Hint:</strong> %s</p>\n", CSTR(hint));
156 } else if (!wpt->notes.isEmpty() && (wpt->description.isEmpty() || wpt->notes != wpt->description)) {
157 gbfprintf(file_out, "<p class=\"gpsbabelnotes\">%s</p>\n", CSTR(wpt->notes));
158 }
159
160 if (includelogs) {
161 const auto* fs_gpx = reinterpret_cast<fs_xml*>(wpt->fs.FsChainFind(kFsGpx));
162 if (fs_gpx && fs_gpx->tag) {
163 xml_tag* root = fs_gpx->tag;
164 xml_tag* curlog = xml_findfirst(root, "groundspeak:log");
165 while (curlog) {
166 time_t logtime = 0;
167 gbfprintf(file_out, "<p class=\"gpsbabellog\">\n");
168
169 xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type");
170 if (logpart) {
171 gbfprintf(file_out, "<span class=\"gpsbabellogtype\">%s</span> by ", CSTR(logpart->cdata));
172 }
173
174 logpart = xml_findfirst(curlog, "groundspeak:finder");
175 if (logpart) {
176 char* f = html_entitize(CSTR(logpart->cdata));
177 gbfprintf(file_out, "<span class=\"gpsbabellogfinder\">%s</span> on ", f);
178 xfree(f);
179 }
180
181 logpart = xml_findfirst(curlog, "groundspeak:date");
182 if (logpart) {
183 logtime = xml_parse_time(logpart->cdata).toTime_t();
184 struct tm* logtm = localtime(&logtime);
185 if (logtm) {
186 gbfprintf(file_out,
187 "<span class=\"gpsbabellogdate\">%04d-%02d-%02d</span><br>\n",
188 logtm->tm_year+1900,
189 logtm->tm_mon+1,
190 logtm->tm_mday);
191 }
192 }
193
194 logpart = xml_findfirst(curlog, "groundspeak:log_wpt");
195 if (logpart) {
196 double lat = xml_attribute(logpart->attributes, "lat").toDouble();
197 double lon = xml_attribute(logpart->attributes, "lon").toDouble();
198 char* coordstr = pretty_deg_format(lat, lon, degformat[2], " ", 1);
199 gbfprintf(file_out,
200 "<span class=\"gpsbabellogcoords\">%s</span><br>\n",
201 coordstr);
202 xfree(coordstr);
203 }
204
205 logpart = xml_findfirst(curlog, "groundspeak:text");
206 if (logpart) {
207 QString encstr = xml_attribute(logpart->attributes, "encoded");
208 bool encoded = !encstr.startsWith('F', Qt::CaseInsensitive);
209
210 QString s;
211 if (html_encrypt && encoded) {
212 s = rot13(logpart->cdata);
213 } else {
214 s = logpart->cdata;
215 }
216
217 char* t = html_entitize(s);
218 gbfputs(t, file_out);
219 xfree(t);
220 }
221
222 gbfprintf(file_out, "</p>\n");
223 curlog = xml_findnext(root, curlog, "groundspeak:log");
224 }
225 }
226 }
227 gbfprintf(file_out, "</td></tr></table>\n");
228 }
229
230 static void
html_index(const Waypoint * wpt)231 html_index(const Waypoint* wpt)
232 {
233 char* sn = html_entitize(wpt->shortname);
234 char* d = html_entitize(wpt->description);
235
236 gbfprintf(file_out, "<a href=\"#%s\">%s - %s</a><br>\n", sn, sn, d);
237
238 xfree(sn);
239 xfree(d);
240 }
241
242 static void
data_write()243 data_write()
244 {
245 setshort_length(mkshort_handle, 6);
246
247 gbfprintf(file_out, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n");
248 gbfprintf(file_out, "<html>\n");
249 gbfprintf(file_out, "<head>\n");
250 gbfprintf(file_out, " <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n");
251
252 // Don't write this line when running test suite. Actually, we should
253 // probably not write this line at all...
254 if (!gpsbabel_testmode()) {
255 gbfprintf(file_out, " <meta name=\"Generator\" content=\"GPSBabel %s\">\n", gpsbabel_version);
256 }
257 gbfprintf(file_out, " <title>GPSBabel HTML Output</title>\n");
258 if (stylesheet) {
259 gbfprintf(file_out, " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n", stylesheet);
260 } else {
261 gbfprintf(file_out, " <style>\n");
262 gbfprintf(file_out, " p.gpsbabelwaypoint { font-size: 120%%; font-weight: bold }\n");
263 gbfprintf(file_out, " </style>\n");
264 }
265 gbfprintf(file_out, "</head>\n");
266 gbfprintf(file_out, "<body>\n");
267
268 gbfprintf(file_out, "<p class=\"index\">\n");
269 waypt_disp_all(html_index);
270 gbfprintf(file_out, "</p>\n");
271
272 waypt_disp_all(html_disp);
273
274 gbfprintf(file_out, "</body>");
275 gbfprintf(file_out, "</html>");
276
277 }
278
279
280 ff_vecs_t html_vecs = {
281 ff_type_file,
282 { ff_cap_write, ff_cap_none, ff_cap_none },
283 nullptr,
284 wr_init,
285 nullptr,
286 wr_deinit,
287 nullptr,
288 data_write,
289 nullptr,
290 &html_args,
291 CET_CHARSET_UTF8, 0 /* CET-REVIEW */
292 , NULL_POS_OPS,
293 nullptr
294 };
295