/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* kdc/tdumputil.c - utilities for tab-separated, etc. files */ /* * Copyright (C) 2015 by the Massachusetts Institute of Technology. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "k5-int.h" #include "k5-platform.h" /* for vasprintf */ #include #include #include #include #include #include "tdumputil.h" /* * Structure describing flavor of a tabular output format. * * fieldsep is the field separator * * recordsep is the record/line separator * * quotechar begins and ends a quoted field. If an instance of quotechar * occurs within a quoted field value, it is doubled. * * Values are only quoted if they contain fieldsep, recordsep, or quotechar. */ struct flavor { int fieldsep; /* field separator */ int recordsep; /* record separator */ int quotechar; /* quote character */ }; struct rechandle { FILE *fh; const char *rectype; int do_sep; struct flavor flavor; }; static const struct flavor tabsep = { '\t', /* fieldsep */ '\n', /* recordsep */ '\0' /* quotechar */ }; static const struct flavor csv = { ',', /* fieldsep */ '\n', /* recordsep */ '"' /* quotechar */ }; /* * Double any quote characters present in a quoted field. */ static char * qquote(struct flavor *fl, const char *s) { const char *sp; struct k5buf buf; k5_buf_init_dynamic(&buf); for (sp = s; *sp != '\0'; sp++) { k5_buf_add_len(&buf, sp, 1); if (*sp == fl->quotechar) k5_buf_add_len(&buf, sp, 1); } return buf.data; } /* * Write an optionally quoted field. */ static int writequoted(struct rechandle *h, const char *fmt, va_list ap) { int doquote = 0, ret; char *s = NULL, *qs = NULL; struct flavor fl = h->flavor; assert(fl.quotechar != '\0'); ret = vasprintf(&s, fmt, ap); if (ret < 0) return ret; if (strchr(s, fl.fieldsep) != NULL) doquote = 1; if (strchr(s, fl.recordsep) != NULL) doquote = 1; if (strchr(s, fl.quotechar) != NULL) doquote = 1; if (doquote) { qs = qquote(&fl, s); if (qs == NULL) { ret = -1; goto cleanup; } ret = fprintf(h->fh, "%c%s%c", fl.quotechar, qs, fl.quotechar); } else { ret = fprintf(h->fh, "%s", s); } cleanup: free(s); free(qs); return ret; } /* * Return a rechandle with the requested file handle and rectype. * * rectype must be a valid pointer for the entire lifetime of the rechandle (or * null) */ static struct rechandle * rechandle_common(FILE *fh, const char *rectype) { struct rechandle *h = calloc(1, sizeof(*h)); if (h == NULL) return NULL; h->fh = fh; h->rectype = rectype; h->do_sep = 0; return h; } /* * Return a rechandle for tab-separated output. */ struct rechandle * rechandle_tabsep(FILE *fh, const char *rectype) { struct rechandle *h = rechandle_common(fh, rectype); if (h == NULL) return NULL; h->flavor = tabsep; return h; } /* * Return a rechandle for CSV output. */ struct rechandle * rechandle_csv(FILE *fh, const char *rectype) { struct rechandle *h = rechandle_common(fh, rectype); if (h == NULL) return NULL; h->flavor = csv; return h; } /* * Free a rechandle. */ void rechandle_free(struct rechandle *h) { free(h); } /* * Start a record. This includes writing a record type prefix (rectype) if * specified. */ int startrec(struct rechandle *h) { if (h->rectype == NULL) { h->do_sep = 0; return 0; } h->do_sep = 1; return fputs(h->rectype, h->fh); } /* * Write a single field of a record. This includes writing a separator * character, if appropriate. */ int writefield(struct rechandle *h, const char *fmt, ...) { int ret = 0; va_list ap; struct flavor fl = h->flavor; if (h->do_sep) { ret = fputc(fl.fieldsep, h->fh); if (ret < 0) return ret; } h->do_sep = 1; va_start(ap, fmt); if (fl.quotechar == '\0') ret = vfprintf(h->fh, fmt, ap); else ret = writequoted(h, fmt, ap); va_end(ap); return ret; } /* * Finish a record (line). */ int endrec(struct rechandle *h) { int ret = 0; struct flavor fl = h->flavor; ret = fputc(fl.recordsep, h->fh); h->do_sep = 0; return ret; } /* * Write a header line if h->rectype is null. (If rectype is set, it will be * prefixed to output lines, most likely in a mixed record type output file, so * it doesn't make sense to output a header line in that case.) */ int writeheader(struct rechandle *h, char * const *a) { int ret = 0; char * const *p; if (h->rectype != NULL) return 0; for (p = a; *p != NULL; p++) { ret = writefield(h, "%s", *p); if (ret < 0) return ret; } ret = endrec(h); return ret; }