/*
XCSV - X Character Separated Values (.???)
A hopefully not too feeble attempt at parsing whatever separated values
files into the waypoint structure and back out again. This is a config-
file wrapper around csv_util.c.
Copyright (C) 2002 Alex Mottram (geo_alexm at cox-internet.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
*/
#include <ctype.h>
#include "defs.h"
#include "csv_util.h"
#include "jeeps/gpsmath.h"
#if CSVFMTS_ENABLED
#define MYNAME "XCSV"
#define ISSTOKEN(a,b) (strncmp(a,b, strlen(b)) == 0)
static char *styleopt = NULL;
static char *snlenopt = NULL;
static char *snwhiteopt = NULL;
static char *snupperopt = NULL;
static char *snuniqueopt = NULL;
char *prefer_shortnames = NULL;
char *xcsv_urlbase = NULL;
static char *opt_datum;
static const char *intstylebuf = NULL;
static
arglist_t xcsv_args[] = {
{"style", &styleopt, "Full path to XCSV style file", NULL,
ARGTYPE_FILE | ARGTYPE_REQUIRED, ARG_NOMINMAX },
{"snlen", &snlenopt, "Max synthesized shortname length", NULL,
ARGTYPE_INT, "1", NULL},
{"snwhite", &snwhiteopt, "Allow whitespace synth. shortnames",
NULL, ARGTYPE_BOOL, ARG_NOMINMAX},
{"snupper", &snupperopt, "UPPERCASE synth. shortnames",
NULL, ARGTYPE_BOOL, ARG_NOMINMAX},
{"snunique", &snuniqueopt, "Make synth. shortnames unique",
NULL, ARGTYPE_BOOL, ARG_NOMINMAX},
{"urlbase", &xcsv_urlbase, "Basename prepended to URL on output",
NULL, ARGTYPE_STRING, ARG_NOMINMAX},
{"prefer_shortnames", &prefer_shortnames,
"Use shortname instead of description",
NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
{"datum", &opt_datum, "GPS datum (def. WGS 84)",
"WGS 84", ARGTYPE_STRING, ARG_NOMINMAX},
ARG_TERMINATOR
};
/* a table of config file constants mapped to chars */
static
char_map_t xcsv_char_table[] = {
{ "COMMA", "," },
{ "COMMASPACE", ", " },
{ "SINGLEQUOTE", "'" },
{ "DOUBLEQUOTE", "\"" },
{ "COLON", ":" },
{ "SEMICOLON", ";" },
{ "NEWLINE", "\n" },
{ "CR", "\n" },
{ "CRNEWLINE", "\r\n" },
{ "TAB", "\t" },
{ "SPACE", " " },
{ "HASH", "#" },
{ "WHITESPACE", "\\w" },
{ "PIPE", "|" },
{ NULL, NULL }
};
void
xcsv_destroy_style(void)
{
queue *elem, *tmp;
field_map_t *fmp;
ogue_t *ogp;
int internal = 0;
/*
* If this xcsv_file struct came from a file we can free it all.
* If not, we can at least free the queue elements.
*/
/* destroy the prologue */
QUEUE_FOR_EACH(&xcsv_file.prologue, elem, tmp) {
ogp = (ogue_t *)elem;
if (ogp->val)
xfree(ogp->val);
if (elem)
xfree(elem);
}
/* destroy the epilogue */
QUEUE_FOR_EACH(&xcsv_file.epilogue, elem, tmp) {
ogp = (ogue_t *)elem;
if (ogp->val)
xfree(ogp->val);
if (elem)
xfree(elem);
}
/* destroy the ifields */
QUEUE_FOR_EACH(&xcsv_file.ifield, elem, tmp) {
fmp = (field_map_t *) elem;
if (fmp->key)
xfree(fmp->key);
if (fmp->val)
xfree(fmp->val);
if (fmp->printfc)
xfree(fmp->printfc);
if (elem)
xfree(elem);
}
/* destroy the ofields, if they are not re-mapped to ifields. */
if (xcsv_file.ofield != &xcsv_file.ifield) {
QUEUE_FOR_EACH(xcsv_file.ofield, elem, tmp) {
fmp = (field_map_t *) elem;
if (fmp->key)
xfree(fmp->key);
if (fmp->val)
xfree(fmp->val);
if (fmp->printfc)
xfree(fmp->printfc);
if (elem)
xfree(elem);
}
if (xcsv_file.ofield)
xfree(xcsv_file.ofield);
}
/* other alloc'd glory */
if (xcsv_file.field_delimiter)
xfree(xcsv_file.field_delimiter);
if (xcsv_file.record_delimiter)
xfree(xcsv_file.record_delimiter);
if (xcsv_file.badchars)
xfree(xcsv_file.badchars);
if (xcsv_file.description)
xfree(xcsv_file.description);
if (xcsv_file.extension)
xfree(xcsv_file.extension);
if (xcsv_file.mkshort_handle)
mkshort_del_handle(&xcsv_file.mkshort_handle);
/* return everything to zeros */
internal = xcsv_file.is_internal;
memset(&xcsv_file, '\0', sizeof(xcsv_file));
xcsv_file.is_internal = internal;
}
const char *
xcsv_get_char_from_constant_table(char *key)
{
char_map_t *cm = xcsv_char_table;
while ((cm->key) && (strcmp(key, cm->key) != 0)) {
cm++;
}
return (cm->chars);
}
static void
xcsv_parse_style_line(const char *sbuff)
{
int i, linecount = 0;
char *s, *p, *sp;
const char *cp;
char *key, *val, *pfc;
/*
* tokens should be parsed longest to shortest, unless something
* requires a previously set value. This way something like
* SHORT and SHORTNAME don't collide.
*/
/* whack off any comments */
if ((p = strchr(sbuff, '#')) != NULL) {
if ((p > sbuff) && p[-1] == '\\') {
memmove(p-1, p, strlen(p));
p[strlen(p)-1] = '\0';
} else {
*p = '\0';
}
}
if (strlen(sbuff)) {
if (ISSTOKEN(sbuff, "FIELD_DELIMITER")) {
sp = csv_stringtrim(&sbuff[16], "\"", 1);
cp = xcsv_get_char_from_constant_table(sp);
if (cp) {
xcsv_file.field_delimiter = xstrdup(cp);
xfree(sp);
}
else
xcsv_file.field_delimiter = sp;
p = csv_stringtrim(xcsv_file.field_delimiter, " ", 0);
/* field delimiters are always bad characters */
if (0 == strcmp(p, "\\w")) {
char *s = xstrappend(xcsv_file.badchars, " \n\r");
if (xcsv_file.badchars) xfree(xcsv_file.badchars);
xcsv_file.badchars = s;
} else {
xcsv_file.badchars = xstrappend(xcsv_file.badchars, p);
}
xfree(p);
} else
if (ISSTOKEN(sbuff, "RECORD_DELIMITER")) {
sp = csv_stringtrim(&sbuff[17], "\"", 1);
cp = xcsv_get_char_from_constant_table(sp);
if (cp) {
xcsv_file.record_delimiter = xstrdup(cp);
xfree(sp);
}
else
xcsv_file.record_delimiter = sp;
p = csv_stringtrim(xcsv_file.record_delimiter, " ", 0);
/* record delimiters are always bad characters */
if (xcsv_file.badchars) {
xcsv_file.badchars = (char *) xrealloc(xcsv_file.badchars,
strlen(xcsv_file.badchars) +
strlen(p) + 1);
} else {
xcsv_file.badchars = (char *) xcalloc(strlen(p) + 1, 1);
}
strcat(xcsv_file.badchars, p);
xfree(p);
} else
if (ISSTOKEN(sbuff, "FORMAT_TYPE")) {
const char *p;
for (p = &sbuff[11]; *p && isspace(*p); p++) {
;
}
if (ISSTOKEN(p, "INTERNAL")) {
xcsv_file.type = ff_type_internal;
}
/* this is almost inconcievable... */
if (ISSTOKEN(p, "SERIAL")) {
xcsv_file.type = ff_type_serial;
}
} else
if (ISSTOKEN(sbuff, "DESCRIPTION")) {
xcsv_file.description = csv_stringtrim(&sbuff[11],"", 0);
} else
if (ISSTOKEN(sbuff, "EXTENSION")) {
xcsv_file.extension = csv_stringtrim(&sbuff[10],"", 0);
} else
if (ISSTOKEN(sbuff, "SHORTLEN")) {
if (xcsv_file.mkshort_handle)
setshort_length(xcsv_file.mkshort_handle, atoi(&sbuff[9]));
} else
if (ISSTOKEN(sbuff, "SHORTWHITE")) {
if (xcsv_file.mkshort_handle)
setshort_whitespace_ok(xcsv_file.mkshort_handle, atoi(&sbuff[12]));
} else
if (ISSTOKEN(sbuff, "BADCHARS")) {
sp = csv_stringtrim(&sbuff[9], "\"", 1);
cp = xcsv_get_char_from_constant_table(sp);
if (cp) {
p = xstrdup(cp);
xfree(sp);
}
else
p = sp;
if (xcsv_file.badchars) {
xcsv_file.badchars = (char *) xrealloc(xcsv_file.badchars,
strlen(xcsv_file.badchars) +
strlen(p) + 1);
} else {
xcsv_file.badchars = (char *) xcalloc(strlen(p) + 1, 1);
}
strcat(xcsv_file.badchars, p);
xfree(p);
} else
if (ISSTOKEN(sbuff, "PROLOGUE")) {
xcsv_prologue_add(xstrdup(&sbuff[9]));
} else
if (ISSTOKEN(sbuff, "EPILOGUE")) {
xcsv_epilogue_add(xstrdup(&sbuff[9]));
} else
if (ISSTOKEN(sbuff, "ENCODING")) {
p = csv_stringtrim(&sbuff[8], "\"", 1);
cet_convert_init(p, 1);
xfree(p);
} else
if (ISSTOKEN(sbuff, "DATUM")) {
p = csv_stringtrim(&sbuff[8], "\"", 1);
xcsv_file.gps_datum = GPS_Lookup_Datum_Index(p);
is_fatal(xcsv_file.gps_datum < 0, MYNAME ": datum \"%s\" is not supported.", p);
xfree(p);
} else
if (ISSTOKEN(sbuff, "DATATYPE")) {
p = csv_stringtrim(&sbuff[8], "\"", 1);
if (case_ignore_strcmp(p, "TRACK") == 0) {
xcsv_file.datatype = trkdata;
}
else if (case_ignore_strcmp(p, "ROUTE") == 0) {
xcsv_file.datatype = rtedata;
}
else if (case_ignore_strcmp(p, "WAYPOINT") == 0) {
xcsv_file.datatype = wptdata;
}
else {
fatal(MYNAME ": Unknown data type \"%s\"!\n", p);
}
xfree(p);
} else
if (ISSTOKEN(sbuff, "IFIELD")) {
key = val = pfc = NULL;
s = csv_lineparse(&sbuff[6], ",", "", linecount);
i = 0;
while (s) {
switch(i) {
case 0:
/* key */
key = csv_stringtrim(s, "\"", 1);
break;
case 1:
/* default value */
val = csv_stringtrim(s, "\"", 1);
break;
case 2:
/* printf conversion */
pfc = csv_stringtrim(s, "\"", 1);
break;
default:
break;
}
i++;
s = csv_lineparse(NULL, ",", "", linecount);
}
xcsv_ifield_add(key, val, pfc);
} else
/*
* as OFIELDs are implemented as an after-thought, I'll
* leave this as it's own parsing for now. We could
* change the world on ifield vs ofield format later..
*/
if (ISSTOKEN(sbuff, "OFIELD")) {
int options = 0;
key = val = pfc = NULL;
s = csv_lineparse(&sbuff[6], ",", "", linecount);
i = 0;
while (s) {
switch(i) {
case 0:
/* key */
key = csv_stringtrim(s, "\"", 1);
break;
case 1:
/* default value */
val = csv_stringtrim(s, "\"", 1);
break;
case 2:
/* printf conversion */
pfc = csv_stringtrim(s, "\"", 1);
break;
case 3:
/* Any additional options. */
if (strstr(s, "no_delim_before")) {
options |= OPTIONS_NODELIM;
}
if (strstr(s, "absolute")) {
options |= OPTIONS_ABSOLUTE;
}
if (strstr(s, "optional")) {
options |= OPTIONS_OPTIONAL;
}
default:
break;
}
i++;
s = csv_lineparse(NULL, ",", "", linecount);
}
xcsv_ofield_add(key, val, pfc, options);
}
}
}
/*
* A wrapper for xcsv_parse_style_line that reads until it hits
* a terminating null. Makes multiple calls to that function so
* that "ignore to end of line" comments work right.
*/
static void
xcsv_parse_style_buff(const char *sbuff)
{
char ibuf[256];
char *ibufp;
size_t i;
while (*sbuff) {
ibuf[0] = 0;
i = 0;
for (ibufp = ibuf; *sbuff != '\n' && i++ < sizeof(ibuf); ) {
*ibufp++ = *sbuff++;
}
while (*sbuff == '\n' || *sbuff == '\r')
sbuff++;
*ibufp = 0;
xcsv_parse_style_line(ibuf);
}
}
static void
xcsv_read_style(const char *fname)
{
char *sbuff;
gbfile *fp;
xcsv_file_init();
fp = gbfopen(fname, "rb", MYNAME);
while ((sbuff = gbfgetstr(fp))) {
sbuff = lrtrim(sbuff);
xcsv_parse_style_line(sbuff);
} while (!gbfeof(fp));
/* if we have no output fields, use input fields as output fields */
if (xcsv_file.ofield_ct == 0) {
if (xcsv_file.ofield)
xfree(xcsv_file.ofield);
xcsv_file.ofield = &xcsv_file.ifield;
xcsv_file.ofield_ct = xcsv_file.ifield_ct;
}
gbfclose(fp);
}
/*
* Passed a pointer to an internal buffer that would be identical
* to the series of bytes that would be in a style file, we set up
* the xcsv parser and make it ready for general use.
*/
void
xcsv_read_internal_style(const char *style_buf)
{
xcsv_file_init();
xcsv_file.is_internal = 1;
xcsv_parse_style_buff(style_buf);
/* if we have no output fields, use input fields as output fields */
if (xcsv_file.ofield_ct == 0) {
if (xcsv_file.ofield)
xfree(xcsv_file.ofield);
xcsv_file.ofield = &xcsv_file.ifield;
xcsv_file.ofield_ct = xcsv_file.ifield_ct;
}
}
void
xcsv_setup_internal_style(const char *style_buf)
{
xcsv_file_init();
xcsv_destroy_style();
xcsv_file.is_internal = !!style_buf;
intstylebuf = style_buf;
}
static void
xcsv_rd_init(const char *fname)
{
/*
* if we don't have an internal style defined, we need to
* read it from a user-supplied style file, or die trying.
*/
if (xcsv_file.is_internal ) {
xcsv_read_internal_style( intstylebuf );
}
else {
if (!styleopt)
fatal(MYNAME ": XCSV input style not declared. Use ... -i xcsv,style=path/to/file.style\n");
xcsv_read_style(styleopt);
}
if ((xcsv_file.datatype == 0) || (xcsv_file.datatype == wptdata)) {
if (global_opts.masked_objective & (TRKDATAMASK|RTEDATAMASK)) {
warning(MYNAME " attempt to read %s as a track or route, but this format only supports waypoints on read. Reading as waypoints instead.\n", fname);
}
}
xcsv_file.xcsvfp = gbfopen(fname, "r", MYNAME);
xcsv_file.gps_datum = GPS_Lookup_Datum_Index(opt_datum);
is_fatal(xcsv_file.gps_datum < 0, MYNAME ": datum \"%s\" is not supported.", opt_datum);
}
static void
xcsv_rd_deinit(void)
{
gbfclose(xcsv_file.xcsvfp);
xcsv_destroy_style();
}
static void
xcsv_wr_init(const char *fname)
{
/* if we don't have an internal style defined, we need to
* read it from a user-supplied style file, or die trying.
*/
if (xcsv_file.is_internal ) {
xcsv_read_internal_style( intstylebuf );
}
else {
if (!styleopt)
fatal(MYNAME ": XCSV output style not declared. Use ... -o xcsv,style=path/to/file.style\n");
xcsv_read_style(styleopt);
}
xcsv_file.xcsvfp = gbfopen(fname, "w", MYNAME);
xcsv_file.fname = (char *)fname;
/* set mkshort options from the command line */
if (global_opts.synthesize_shortnames) {
if (snlenopt)
setshort_length(xcsv_file.mkshort_handle, atoi(snlenopt));
if (snwhiteopt)
setshort_whitespace_ok(xcsv_file.mkshort_handle, atoi(snwhiteopt));
if (snupperopt)
setshort_mustupper(xcsv_file.mkshort_handle, atoi(snupperopt));
if (snuniqueopt)
setshort_mustuniq(xcsv_file.mkshort_handle, atoi(snuniqueopt));
setshort_badchars(xcsv_file.mkshort_handle, xcsv_file.badchars);
}
xcsv_file.gps_datum = GPS_Lookup_Datum_Index(opt_datum);
is_fatal(xcsv_file.gps_datum < 0, MYNAME ": datum \"%s\" is not supported.", opt_datum);
}
static void
xcsv_wr_position_init(const char *fname)
{
xcsv_wr_init(fname);
}
static void
xcsv_wr_deinit(void)
{
gbfclose(xcsv_file.xcsvfp);
xcsv_destroy_style();
}
static void
xcsv_wr_position_deinit(void)
{
xcsv_wr_deinit();
}
static void
xcsv_wr_position(waypoint *wpt)
{
/* Tweak incoming name if we don't have a fix */
switch(wpt->fix) {
case fix_none:
if (wpt->shortname) {
xfree(wpt->shortname);
}
wpt->shortname = xstrdup("ESTIMATED Position");
break;
default:
break;
}
waypt_add(wpt);
xcsv_data_write();
waypt_del(wpt);
gbfflush(xcsv_file.xcsvfp);
}
ff_vecs_t xcsv_vecs = {
ff_type_internal,
FF_CAP_RW_WPT, /* This is a bit of a lie for now... */
xcsv_rd_init,
xcsv_wr_init,
xcsv_rd_deinit,
xcsv_wr_deinit,
xcsv_data_read,
xcsv_data_write,
NULL,
xcsv_args,
CET_CHARSET_ASCII, 0, /* CET-REVIEW */
{ NULL, NULL, NULL, xcsv_wr_position_init, xcsv_wr_position, xcsv_wr_position_deinit }
};
#else
void xcsv_read_internal_style(const char *style_buf) {}
void xcsv_setup_internal_style(const char *style_buf) {}
#endif //CSVFMTS_ENABLED
syntax highlighted by Code2HTML, v. 0.9.1