1 /*
2     Copyright (C) 2002 Alex Mottram (geo_alexm at cox-internet.com)
3     Copyright (C) 2002-2014 Robert Lipe
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 
19  */
20 
21 #ifndef XCSV_H_INCLUDED_
22 #define XCSV_H_INCLUDED_
23 
24 #include <ctime>
25 #include <utility>                // for move
26 
27 #include <QtCore/QByteArray>      // for QByteArray
28 #include <QtCore/QDateTime>       // for QDateTime
29 #include <QtCore/QList>           // for QList
30 #include <QtCore/QString>         // for QString
31 #include <QtCore/QStringList>     // for QStringList
32 #include <QtCore/QVector>         // for QVector
33 
34 #include "defs.h"
35 #include "format.h"
36 #include "garmin_fs.h"
37 #include "src/core/datetime.h"    // for DateTime
38 #include "src/core/optional.h"    // for optional
39 #include "src/core/textstream.h"  // for TextStream
40 
41 #if CSVFMTS_ENABLED
42 
43 /*
44  * Class describing an xcsv format.
45  */
46 
47 class XcsvStyle
48 {
49 public:
50   /* Types */
51 
52   /* something to map fields to waypts */
53   struct field_map {
54     // We use QByteArrays because consumers want char* data and QByteArrays supply this through constData().
55     // If we used QStrings, then we would have to convert to QByteArrays to get the char* data.
56     // If we use char* then we have to manage memory allocation/deallocation.
57     // TODO: when consumers use QStrings then we can store QStrings instead of QByteArrays.
58     QByteArray key;
59     QByteArray val;
60     QByteArray printfc;
61     int hashed_key{0};
62     unsigned options{0};
63 
64     field_map() = default;
field_mapfield_map65     field_map(QByteArray k, QByteArray v, QByteArray p, int hk) : key{std::move(k)},val{std::move(v)},printfc{std::move(p)},hashed_key{hk} {}
field_mapfield_map66     field_map(QByteArray k, QByteArray v, QByteArray p, int hk, unsigned o) : key{std::move(k)},val{std::move(v)},printfc{
67       std::move(p)},hashed_key{hk},options{o} {}
68   };
69 
70   /* Constants */
71 
72   static constexpr unsigned options_nodelim = 1;
73   static constexpr unsigned options_absolute = 2;
74   static constexpr unsigned options_optional = 4;
75 
76   /* Member Functions */
77 
78   static QString xcsv_get_char_from_constant_table(const QString& key);
79   static XcsvStyle xcsv_read_internal_style(const char* style_buf);
80   static XcsvStyle xcsv_read_style(const char* fname);
81 
82   /* Data Members */
83 
84   /* PROLOGUE from style file */
85   /* header lines for writing at the top of the file. */
86   QStringList prologue;
87 
88   /* EPILOGUE from style file */
89   /* footer lines for writing at the bottom of the file. */
90   QStringList epilogue;
91 
92   /* FIELD_DELIMITER from style file */
93   /* comma, quote, etc... */
94   QString field_delimiter;
95 
96   /* FIELD_ENCLOSER from style file */
97   /* doublequote, etc... */
98   QString field_encloser;
99 
100   /* RECORD_DELIMITER from style file */
101   /* newline, c/r, etc... */
102   QString record_delimiter;
103 
104   /* BADCHARS from style file */
105   /* characters we never write to output */
106   QString badchars;
107 
108   /* IFIELDS from style file */
109   /* input field mapping */
110   QList<field_map> ifields;
111 
112   /* OFIELDS from style file */
113   /* output field mapping */
114   QList<field_map> ofields;
115 
116   /* ENCODING from style file */
117   QString codecname;
118 
119   /* DESCRIPTION from style file */
120   /* for help text */
121   QString description;
122 
123   /* EXTENSION from style file */
124   /* preferred filename extension (for wrappers)*/
125   QString extension;
126 
127   /* FORMAT_TYPE from style file */
128   /* format type for GUI wrappers. */
129   ff_type type{ff_type_file};
130 
131   /* DATUM from style file */
132   QString gps_datum_name;
133 
134   /* DATATYPE from style file */
135   /* can be wptdata, rtedata or trkdata */
136   /* ... or ZERO to keep the old behaviour */
137   gpsdata_type datatype{unknown_gpsdata};
138 
139   /* SHORTLEN from style file */
140   gpsbabel_optional::optional<int> shortlen;
141 
142   /* SHORTWHITE from style file */
143   gpsbabel_optional::optional<int> whitespace_ok;
144 
145 private:
146   /* Types */
147 
148   /* something to map config file constants to chars */
149   struct char_map_t {
150     const QString key;
151     const QString chars;
152   };
153 
154   /* Member Functions */
155 
156   static QString dequote(const QString& in);
157   static void validate_fieldmap(const field_map& fmp, bool is_output);
158   static void xcsv_ifield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc);
159   static void xcsv_ofield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc, unsigned int options);
160   static void xcsv_parse_style_line(XcsvStyle* style, QString line);
161   static XcsvStyle xcsv_parse_style_buff(const char* sbuff);
162 
163   /* Data Members */
164 
165   /* a table of config file constants mapped to chars */
166   static const char_map_t xcsv_char_table[];
167 };
168 
169 class XcsvFormat : public Format
170 {
171 public:
172   /* Member Functions */
get_args()173   QVector<arglist_t>* get_args() override
174   {
175     return &xcsv_args;
176   }
177 
get_type()178   ff_type get_type() const override
179   {
180     return ff_type_internal;
181   }
182 
get_cap()183   QVector<ff_cap> get_cap() const override
184   {
185     return FF_CAP_RW_WPT; /* This is a bit of a lie for now... */
186   }
187 
get_encode()188   QString get_encode() const override
189   {
190     return CET_CHARSET_UTF8;
191   }
192 
get_fixed_encode()193   int get_fixed_encode() const override
194   {
195     return 0;
196   }
197 
198   void rd_init(const QString& fname) override;
199   void read() override;
200   void rd_deinit() override;
201   void wr_init(const QString& fname) override;
202   void write() override;
203   void wr_deinit() override;
204   void wr_position_init(const QString& fname) override;
205   void wr_position(Waypoint* wpt) override;
206   void wr_position_deinit() override;
207 
208   void xcsv_setup_internal_style(const char* style_buf);
209 
210 private:
211   /* Types */
212 
213   class XcsvFile
214   {
215   public:
216     /* Special Member Functions */
217 
XcsvFile()218     XcsvFile() : mkshort_handle(mkshort_new_handle()) {}
219     // delete copy and move constructors and assignment operators.
220     // The defaults are not appropriate, and we haven't implemented proper ones.
221     XcsvFile(const XcsvFile&) = delete;
222     XcsvFile& operator=(const XcsvFile&) = delete;
223     XcsvFile(XcsvFile&&) = delete;
224     XcsvFile& operator=(XcsvFile&&) = delete;
~XcsvFile()225     ~XcsvFile()
226     {
227       if (mkshort_handle != nullptr) {
228         mkshort_del_handle(&mkshort_handle);
229       }
230     }
231 
232     /* Data Members */
233 
234     gpsbabel::TextStream stream;
235     QString fname;
236     int gps_datum_idx{-1};		/* result of GPS_Lookup_Datum_Index */
237     short_handle mkshort_handle{nullptr};
238   };
239 
240   struct xcsv_parse_data {
241     QString rte_name;
242     QString trk_name;
243     bool new_track{false};
244     double utm_northing{0};
245     double utm_easting{0};
246     double utm_zone{0};
247     char utm_zonec{'N'};
248     UrlLink* link_{nullptr};
249     gpsbabel_optional::optional<bool> lat_dir_positive;
250     gpsbabel_optional::optional<bool> lon_dir_positive;
251   };
252 
253   /* Constants */
254 
lat_dir(double a)255   static constexpr char lat_dir(double a)
256   {
257     return a < 0.0 ? 'S' : 'N';
258   }
lon_dir(double a)259   static constexpr char lon_dir(double a)
260   {
261     return a < 0.0 ? 'W' : 'E';
262   }
263 
264   /* convert excel time (days since 1900) to time_t and back again */
excel_to_timet(double a)265   static constexpr double excel_to_timet(double a)
266   {
267     return (a - 25569.0) * 86400.0;
268   }
timet_to_excel(double a)269   static constexpr double timet_to_excel(double a)
270   {
271     return (a / 86400.0) + 25569.0;
272   }
273 
274   static constexpr int gps_datum_wgs84 = 118; // GPS_Lookup_Datum_Index("WGS 84")
275 
276   /* Member Functions */
277 
278   static QDateTime yyyymmdd_to_time(const char* s);
279   static time_t sscanftime(const char* s, const char* format, int gmt);
280   static time_t addhms(const char* s, const char* format);
281   static QString writetime(const char* format, time_t t, bool gmt);
282   static QString writetime(const char* format, const gpsbabel::DateTime& t, bool gmt);
283   static QString writehms(const char* format, time_t t, int gmt);
284   static QString writehms(const char* format, const gpsbabel::DateTime& t, int gmt);
285   static long int time_to_yyyymmdd(const QDateTime& t);
286   static garmin_fs_t* gmsd_init(Waypoint* wpt);
287   static void xcsv_parse_val(const QString& value, Waypoint* wpt, const XcsvStyle::field_map& fmp, xcsv_parse_data* parse_data, int line_no);
288   void xcsv_resetpathlen(const route_head* head);
289   void xcsv_waypt_pr(const Waypoint* wpt);
290   QString xcsv_replace_tokens(const QString& original) const;
291 
292   /* Data Members */
293 
294   XcsvFile* xcsv_file{nullptr};
295   const XcsvStyle* xcsv_style{nullptr};
296   double pathdist = 0;
297   double oldlon = 999;
298   double oldlat = 999;
299 
300   int waypt_out_count = 0;
301   const route_head* csv_track = nullptr;
302   const route_head* csv_route = nullptr;
303 
304   char* styleopt = nullptr;
305   char* snlenopt = nullptr;
306   char* snwhiteopt = nullptr;
307   char* snupperopt = nullptr;
308   char* snuniqueopt = nullptr;
309   char* prefer_shortnames = nullptr;
310   char* xcsv_urlbase = nullptr;
311   char* opt_datum = nullptr;
312 
313   const char* intstylebuf = nullptr;
314 
315   QVector<arglist_t> xcsv_args = {
316     {
317       "style", &styleopt, "Full path to XCSV style file", nullptr,
318       ARGTYPE_FILE | ARGTYPE_REQUIRED, ARG_NOMINMAX, nullptr
319     },
320     {
321       "snlen", &snlenopt, "Max synthesized shortname length", nullptr,
322       ARGTYPE_INT, "1", nullptr, nullptr
323     },
324     {
325       "snwhite", &snwhiteopt, "Allow whitespace synth. shortnames",
326       nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
327     },
328     {
329       "snupper", &snupperopt, "UPPERCASE synth. shortnames",
330       nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
331     },
332     {
333       "snunique", &snuniqueopt, "Make synth. shortnames unique",
334       nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
335     },
336     {
337       "urlbase", &xcsv_urlbase, "Basename prepended to URL on output",
338       nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
339     },
340     {
341       "prefer_shortnames", &prefer_shortnames,
342       "Use shortname instead of description",
343       nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
344     },
345     {
346       "datum", &opt_datum, "GPS datum (def. WGS 84)",
347       nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr
348     },
349   };
350 
351 };
352 
353 #endif // CSVFMTS_ENABLED
354 #endif // XCSV_H_INCLUDED_
355