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", &degformat,
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)?"&frac12;":"",
129               (int)(wpt->gc_data->terr / 10), (wpt->gc_data->terr%10)?"&frac12;":"");
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