314 lines
7.8 KiB
C
314 lines
7.8 KiB
C
/* rec2csv.c - rec to csv converter. */
|
|
|
|
/* Copyright (C) 2011-2022 Jose E. Marchesi */
|
|
|
|
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <getopt.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <xalloc.h>
|
|
#include <gettext.h>
|
|
#define _(str) gettext (str)
|
|
|
|
#include <csv.h>
|
|
#include <rec.h>
|
|
#include <recutl.h>
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
char *rec2csv_record_type = NULL;
|
|
rec_fex_t rec2csv_sort_by_fields = NULL;
|
|
char rec2csv_delim = ',';
|
|
|
|
/*
|
|
* Command line options management
|
|
*/
|
|
|
|
enum
|
|
{
|
|
COMMON_ARGS,
|
|
DELIM_ARG,
|
|
RECORD_TYPE_ARG,
|
|
SORT_ARG
|
|
};
|
|
|
|
static const struct option GNU_longOptions[] =
|
|
{
|
|
COMMON_LONG_ARGS,
|
|
{"delim", required_argument, NULL, DELIM_ARG},
|
|
{"type", required_argument, NULL, RECORD_TYPE_ARG},
|
|
{"sort", required_argument, NULL, SORT_ARG},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
/*
|
|
* Functions.
|
|
*/
|
|
|
|
void
|
|
recutl_print_help (void)
|
|
{
|
|
/* TRANSLATORS: --help output, rec2csv synopsis.
|
|
no-wrap */
|
|
printf (_("\
|
|
Usage: rec2csv [OPTIONS]... [REC_FILE]\n"));
|
|
|
|
/* TRANSLATORS: --help output, rec2csv short description.
|
|
no-wrap */
|
|
fputs (_("\
|
|
Convert rec data into csv data.\n"), stdout);
|
|
|
|
puts ("");
|
|
/* TRANSLATORS: --help output, rec2csv options.
|
|
no-wrap */
|
|
fputs (_("\
|
|
-d, --delim=char sets the deliminator (default ',')\n\
|
|
-t, --type=TYPE record set to convert to csv; if this parameter\n\
|
|
is omitted then the default record set is used\n\
|
|
-S, --sort=FIELDS sort the output by the specified fields.\n"),
|
|
stdout);
|
|
|
|
recutl_print_help_common ();
|
|
puts ("");
|
|
recutl_print_help_footer ();
|
|
}
|
|
|
|
static void
|
|
rec2csv_parse_args (int argc,
|
|
char **argv)
|
|
{
|
|
int ret;
|
|
char c;
|
|
|
|
while ((ret = getopt_long (argc,
|
|
argv,
|
|
"t:S:d:",
|
|
GNU_longOptions,
|
|
NULL)) != -1)
|
|
{
|
|
c = ret;
|
|
switch (c)
|
|
{
|
|
COMMON_ARGS_CASES
|
|
case DELIM_ARG:
|
|
case 'd':
|
|
rec2csv_delim = optarg[0];
|
|
break;
|
|
case RECORD_TYPE_ARG:
|
|
case 't':
|
|
rec2csv_record_type = xstrdup (optarg);
|
|
break;
|
|
case SORT_ARG:
|
|
case 'S':
|
|
if (rec2csv_sort_by_fields)
|
|
recutl_fatal (_("only one list of fields can be specified\
|
|
as a sorting criteria.\n"));
|
|
|
|
/* Parse the field name. */
|
|
if (!rec_fex_check (optarg, REC_FEX_CSV))
|
|
recutl_fatal (_("invalid field name list in -S.\n"));
|
|
|
|
rec2csv_sort_by_fields = rec_fex_new (optarg, REC_FEX_CSV);
|
|
if (!rec2csv_sort_by_fields)
|
|
recutl_fatal (_("internal error creating fex.\n"));
|
|
|
|
break;
|
|
default:
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
rec2csv_generate_csv (rec_rset_t rset,
|
|
rec_fex_t fex)
|
|
{
|
|
rec_mset_iterator_t iter;
|
|
rec_fex_elem_t fex_elem;
|
|
rec_record_t record;
|
|
rec_field_t field;
|
|
char *field_name;
|
|
char *tmp;
|
|
size_t i;
|
|
|
|
/* Generate the row with headers. */
|
|
for (i = 0; i < rec_fex_size (fex); i++)
|
|
{
|
|
if (i != 0)
|
|
putc (rec2csv_delim, stdout);
|
|
|
|
fex_elem = rec_fex_get (fex, i);
|
|
field_name = xstrdup (rec_fex_elem_field_name (fex_elem));
|
|
|
|
/* The header is FNAME or FNAME_N where N is the index starting
|
|
at 1. Note that we shall remove the trailing ':', if any. */
|
|
if (field_name[strlen(field_name)-1] == ':')
|
|
field_name[strlen(field_name)-1] = '\0';
|
|
|
|
|
|
if (rec_fex_elem_min (fex_elem) != 0)
|
|
{
|
|
if (asprintf (&tmp, "%s_%d",
|
|
field_name,
|
|
rec_fex_elem_min (fex_elem) + 1) == -1)
|
|
recutl_out_of_memory ();
|
|
}
|
|
else
|
|
{
|
|
if (asprintf (&tmp, "%s", field_name) == -1)
|
|
recutl_out_of_memory ();
|
|
}
|
|
|
|
csv_fwrite (stdout, tmp, strlen(tmp));
|
|
free (field_name);
|
|
free (tmp);
|
|
}
|
|
|
|
putc ('\n', stdout);
|
|
|
|
/* Generate the data rows. */
|
|
iter = rec_mset_iterator (rec_rset_mset (rset));
|
|
while (rec_mset_iterator_next (&iter, MSET_RECORD, (const void**) &record, NULL))
|
|
{
|
|
for (i = 0; i < rec_fex_size (fex); i++)
|
|
{
|
|
if (i != 0)
|
|
putc (rec2csv_delim, stdout);
|
|
|
|
fex_elem = rec_fex_get (fex, i);
|
|
field = rec_record_get_field_by_name (record,
|
|
rec_fex_elem_field_name (fex_elem),
|
|
rec_fex_elem_min (fex_elem));
|
|
if (field)
|
|
csv_fwrite (stdout,
|
|
rec_field_value (field),
|
|
strlen (rec_field_value (field)));
|
|
}
|
|
|
|
putc ('\n', stdout);
|
|
}
|
|
|
|
rec_mset_iterator_free (&iter);
|
|
}
|
|
|
|
static rec_fex_t
|
|
rec2csv_determine_fields (rec_rset_t rset)
|
|
{
|
|
rec_fex_t fields;
|
|
rec_mset_iterator_t iter_rset;
|
|
rec_mset_iterator_t iter_record;
|
|
rec_record_t record;
|
|
rec_field_t field;
|
|
int field_index;
|
|
|
|
fields = rec_fex_new (NULL, REC_FEX_SIMPLE);
|
|
|
|
iter_rset = rec_mset_iterator (rec_rset_mset (rset));
|
|
while (rec_mset_iterator_next (&iter_rset, MSET_RECORD, (const void **) &record, NULL))
|
|
{
|
|
iter_record = rec_mset_iterator (rec_record_mset (record));
|
|
while (rec_mset_iterator_next (&iter_record, MSET_FIELD, (const void **) &field, NULL))
|
|
{
|
|
field_index = rec_record_get_field_index_by_name (record, field);
|
|
|
|
if (!rec_fex_member_p (fields,
|
|
rec_field_name (field),
|
|
field_index, field_index))
|
|
rec_fex_append (fields,
|
|
rec_field_name (field),
|
|
field_index, field_index);
|
|
}
|
|
|
|
rec_mset_iterator_free (&iter_record);
|
|
}
|
|
|
|
rec_mset_iterator_free (&iter_rset);
|
|
|
|
return fields;
|
|
}
|
|
|
|
static bool
|
|
rec2csv_process_data (rec_db_t db)
|
|
{
|
|
bool ret;
|
|
rec_fex_t row_fields;
|
|
size_t i;
|
|
rec_rset_t rset;
|
|
|
|
ret = true;
|
|
|
|
for (i = 0; i < rec_db_size (db); i++)
|
|
{
|
|
rset = rec_db_get_rset (db, i);
|
|
if (((rec2csv_record_type)
|
|
&& rec_rset_type (rset)
|
|
&& (strcmp (rec_rset_type (rset),
|
|
rec2csv_record_type) == 0))
|
|
|| (!rec2csv_record_type
|
|
&& (!rec_rset_type (rset) ||
|
|
(rec_db_size (db) == 1))))
|
|
{
|
|
/* Process this record set. */
|
|
if (!rec_rset_sort (rset, rec2csv_sort_by_fields))
|
|
recutl_out_of_memory ();
|
|
|
|
/* Build the fields that will appear in the row. */
|
|
row_fields = rec2csv_determine_fields (rset);
|
|
|
|
/* Generate the csv data. */
|
|
rec2csv_generate_csv (rset, row_fields);
|
|
|
|
/* Cleanup. */
|
|
rec_fex_destroy (row_fields);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
int res;
|
|
rec_db_t db;
|
|
|
|
res = 0;
|
|
|
|
recutl_init ("rec2csv");
|
|
|
|
/* Parse arguments. */
|
|
rec2csv_parse_args (argc, argv);
|
|
|
|
/* Get the input data. */
|
|
db = recutl_build_db (argc, argv);
|
|
if (!db)
|
|
res = 1;
|
|
else
|
|
{
|
|
/* Process the data. */
|
|
if (!rec2csv_process_data (db))
|
|
res = 1;
|
|
}
|
|
|
|
rec_db_destroy (db);
|
|
|
|
return res;
|
|
}
|