summaryrefslogtreecommitdiff
path: root/encoder/csv/enc_csv.c
diff options
context:
space:
mode:
Diffstat (limited to 'encoder/csv/enc_csv.c')
-rw-r--r--encoder/csv/enc_csv.c826
1 files changed, 826 insertions, 0 deletions
diff --git a/encoder/csv/enc_csv.c b/encoder/csv/enc_csv.c
new file mode 100644
index 000000000000..88e93f37cc97
--- /dev/null
+++ b/encoder/csv/enc_csv.c
@@ -0,0 +1,826 @@
+/*
+ * Copyright (c) 2015, Juniper Networks, Inc.
+ * All rights reserved.
+ * This SOFTWARE is licensed under the LICENSE provided in the
+ * ../Copyright file. By downloading, installing, copying, or otherwise
+ * using the SOFTWARE, you agree to be bound by the terms of that
+ * LICENSE.
+ * Phil Shafer, August 2015
+ */
+
+/*
+ * CSV encoder generates comma-separated value files for specific
+ * subsets of data. This is not (and cannot be) a generalized
+ * facility, but for specific subsets of data, CSV data can be
+ * reasonably generated. For example, the df XML content:
+ * <filesystem>
+ * <name>procfs</name>
+ * <total-blocks>4</total-blocks>
+ * <used-blocks>4</used-blocks>
+ * <available-blocks>0</available-blocks>
+ * <used-percent>100</used-percent>
+ * <mounted-on>/proc</mounted-on>
+ * </filesystem>
+ *
+ * could be represented as:
+ *
+ * #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on
+ * procfs,4,4,0,100,/proc
+ *
+ * Data is then constrained to be sibling leaf values. In addition,
+ * singular leafs can also be matched. The costs include recording
+ * the specific leaf names (to ensure consistency) and some
+ * buffering.
+ *
+ * Some escaping is needed for CSV files, following the rules of RFC4180:
+ *
+ * - Fields containing a line-break, double-quote or commas should be
+ * quoted. (If they are not, the file will likely be impossible to
+ * process correctly).
+ * - A (double) quote character in a field must be represented by two
+ * (double) quote characters.
+ * - Leading and trialing whitespace require fields be quoted.
+ *
+ * Cheesy, but simple. The RFC also requires MS-DOS end-of-line, which
+ * we only do with the "dos" option. Strange that we still live in a
+ * DOS-friendly world, but then again, we make spaceships based on the
+ * horse butts (http://www.astrodigital.org/space/stshorse.html).
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "xo.h"
+#include "xo_encoder.h"
+#include "xo_buf.h"
+
+#ifndef UNUSED
+#define UNUSED __attribute__ ((__unused__))
+#endif /* UNUSED */
+
+/*
+ * The CSV encoder has three moving parts:
+ *
+ * - The path holds the path we are matching against
+ * - This is given as input via "options" and does not change
+ *
+ * - The stack holds the current names of the open elements
+ * - The "open" operations push, while the "close" pop
+ * - Turns out, at this point, the stack is unused, but I've
+ * left "drippings" in the code because I see this as useful
+ * for future features (under CSV_STACK_IS_NEEDED).
+ *
+ * - The leafs record the current set of leaf
+ * - A key from the parent list counts as a leaf (unless CF_NO_KEYS)
+ * - Once the path is matched, all other leafs at that level are leafs
+ * - Leafs are recorded to get the header comment accurately recorded
+ * - Once the first line is emited, the set of leafs _cannot_ change
+ *
+ * We use offsets into the buffers, since we know they can be
+ * realloc'd out from under us, as the size increases. The 'path'
+ * is fixed, we allocate it once, so it doesn't need offsets.
+ */
+typedef struct path_frame_s {
+ char *pf_name; /* Path member name; points into c_path_buf */
+ uint32_t pf_flags; /* Flags for this path element (PFF_*) */
+} path_frame_t;
+
+typedef struct stack_frame_s {
+ ssize_t sf_off; /* Element name; offset in c_stack_buf */
+ uint32_t sf_flags; /* Flags for this frame (SFF_*) */
+} stack_frame_t;
+
+/* Flags for sf_flags */
+
+typedef struct leaf_s {
+ ssize_t f_name; /* Name of leaf; offset in c_name_buf */
+ ssize_t f_value; /* Value of leaf; offset in c_value_buf */
+ uint32_t f_flags; /* Flags for this value (FF_*) */
+#ifdef CSV_STACK_IS_NEEDED
+ ssize_t f_depth; /* Depth of stack when leaf was recorded */
+#endif /* CSV_STACK_IS_NEEDED */
+} leaf_t;
+
+/* Flags for f_flags */
+#define LF_KEY (1<<0) /* Leaf is a key */
+#define LF_HAS_VALUE (1<<1) /* Value has been set */
+
+typedef struct csv_private_s {
+ uint32_t c_flags; /* Flags for this encoder */
+
+ /* The path for which we select leafs */
+ char *c_path_buf; /* Buffer containing path members */
+ path_frame_t *c_path; /* Array of path members */
+ ssize_t c_path_max; /* Depth of c_path[] */
+ ssize_t c_path_cur; /* Current depth in c_path[] */
+
+ /* A stack of open elements (xo_op_list, xo_op_container) */
+#if CSV_STACK_IS_NEEDED
+ xo_buffer_t c_stack_buf; /* Buffer used for stack content */
+ stack_frame_t *c_stack; /* Stack of open tags */
+ ssize_t c_stack_max; /* Maximum stack depth */
+#endif /* CSV_STACK_IS_NEEDED */
+ ssize_t c_stack_depth; /* Current stack depth */
+
+ /* List of leafs we are emitting (to ensure consistency) */
+ xo_buffer_t c_name_buf; /* String buffer for leaf names */
+ xo_buffer_t c_value_buf; /* String buffer for leaf values */
+ leaf_t *c_leaf; /* List of leafs */
+ ssize_t c_leaf_depth; /* Current depth of c_leaf[] (next free) */
+ ssize_t c_leaf_max; /* Max depth of c_leaf[] */
+
+ xo_buffer_t c_data; /* Buffer for creating data */
+} csv_private_t;
+
+#define C_STACK_MAX 32 /* default c_stack_max */
+#define C_LEAF_MAX 32 /* default c_leaf_max */
+
+/* Flags for this structure */
+#define CF_HEADER_DONE (1<<0) /* Have already written the header */
+#define CF_NO_HEADER (1<<1) /* Do not generate header */
+#define CF_NO_KEYS (1<<2) /* Do not generate excess keys */
+#define CF_VALUE_ONLY (1<<3) /* Only generate the value */
+
+#define CF_DOS_NEWLINE (1<<4) /* Generate CR-NL, just like MS-DOS */
+#define CF_LEAFS_DONE (1<<5) /* Leafs are already been recorded */
+#define CF_NO_QUOTES (1<<6) /* Do not generate quotes */
+#define CF_RECORD_DATA (1<<7) /* Record all sibling leafs */
+
+#define CF_DEBUG (1<<8) /* Make debug output */
+#define CF_HAS_PATH (1<<9) /* A "path" option was provided */
+
+/*
+ * A simple debugging print function, similar to psu_dbg. Controlled by
+ * the undocumented "debug" option.
+ */
+static void
+csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
+ const char *fmt, ...)
+{
+ if (csv == NULL || !(csv->c_flags & CF_DEBUG))
+ return;
+
+ va_list vap;
+
+ va_start(vap, fmt);
+ vfprintf(stderr, fmt, vap);
+ va_end(vap);
+}
+
+/*
+ * Create the private data for this handle, initialize it, and record
+ * the pointer in the handle.
+ */
+static int
+csv_create (xo_handle_t *xop)
+{
+ csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));
+ if (csv == NULL)
+ return -1;
+
+ bzero(csv, sizeof(*csv));
+ xo_buf_init(&csv->c_data);
+ xo_buf_init(&csv->c_name_buf);
+ xo_buf_init(&csv->c_value_buf);
+#ifdef CSV_STACK_IS_NEEDED
+ xo_buf_init(&csv->c_stack_buf);
+#endif /* CSV_STACK_IS_NEEDED */
+
+ xo_set_private(xop, csv);
+
+ return 0;
+}
+
+/*
+ * Clean up and release any data in use by this handle
+ */
+static void
+csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)
+{
+ /* Clean up */
+ xo_buf_cleanup(&csv->c_data);
+ xo_buf_cleanup(&csv->c_name_buf);
+ xo_buf_cleanup(&csv->c_value_buf);
+#ifdef CSV_STACK_IS_NEEDED
+ xo_buf_cleanup(&csv->c_stack_buf);
+#endif /* CSV_STACK_IS_NEEDED */
+
+ if (csv->c_leaf)
+ xo_free(csv->c_leaf);
+ if (csv->c_path_buf)
+ xo_free(csv->c_path_buf);
+}
+
+/*
+ * Return the element name at the top of the path stack. This is the
+ * item that we are currently trying to match on.
+ */
+static const char *
+csv_path_top (csv_private_t *csv, ssize_t delta)
+{
+ if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)
+ return NULL;
+
+ ssize_t cur = csv->c_path_cur + delta;
+
+ if (cur < 0)
+ return NULL;
+
+ return csv->c_path[cur].pf_name;
+}
+
+/*
+ * Underimplemented stack functionality
+ */
+static inline void
+csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)
+{
+#ifdef CSV_STACK_IS_NEEDED
+ csv->c_stack_depth += 1;
+#endif /* CSV_STACK_IS_NEEDED */
+}
+
+/*
+ * Underimplemented stack functionality
+ */
+static inline void
+csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)
+{
+#ifdef CSV_STACK_IS_NEEDED
+ csv->c_stack_depth -= 1;
+#endif /* CSV_STACK_IS_NEEDED */
+}
+
+/* Flags for csv_quote_flags */
+#define QF_NEEDS_QUOTES (1<<0) /* Needs to be quoted */
+#define QF_NEEDS_ESCAPE (1<<1) /* Needs to be escaped */
+
+/*
+ * Determine how much quote processing is needed. The details of the
+ * quoting rules are given at the top of this file. We return a set
+ * of flags, indicating what's needed.
+ */
+static uint32_t
+csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
+ const char *value)
+{
+ static const char quoted[] = "\n\r\",";
+ static const char escaped[] = "\"";
+
+ if (csv->c_flags & CF_NO_QUOTES) /* User doesn't want quotes */
+ return 0;
+
+ size_t len = strlen(value);
+ uint32_t rc = 0;
+
+ if (strcspn(value, quoted) != len)
+ rc |= QF_NEEDS_QUOTES;
+ else if (isspace((int) value[0])) /* Leading whitespace */
+ rc |= QF_NEEDS_QUOTES;
+ else if (isspace((int) value[len - 1])) /* Trailing whitespace */
+ rc |= QF_NEEDS_QUOTES;
+
+ if (strcspn(value, escaped) != len)
+ rc |= QF_NEEDS_ESCAPE;
+
+ csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",
+ value, rc, len, strcspn(value, quoted));
+
+ return rc;
+}
+
+/*
+ * Escape the string, following the rules in RFC4180
+ */
+static void
+csv_escape (xo_buffer_t *xbp, const char *value, size_t len)
+{
+ const char *cp, *ep, *np;
+
+ for (cp = value, ep = value + len; cp && cp < ep; cp = np) {
+ np = strchr(cp, '"');
+ if (np) {
+ np += 1;
+ xo_buf_append(xbp, cp, np - cp);
+ xo_buf_append(xbp, "\"", 1);
+ } else
+ xo_buf_append(xbp, cp, ep - cp);
+ }
+}
+
+/*
+ * Append a newline to the buffer, following the settings of the "dos"
+ * flag.
+ */
+static void
+csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)
+{
+ if (csv->c_flags & CF_DOS_NEWLINE)
+ xo_buf_append(xbp, "\r\n", 2);
+ else
+ xo_buf_append(xbp, "\n", 1);
+}
+
+/*
+ * Create a 'record' of 'fields' from our recorded leaf values. If
+ * this is the first line and "no-header" isn't given, make a record
+ * containing the leaf names.
+ */
+static void
+csv_emit_record (xo_handle_t *xop, csv_private_t *csv)
+{
+ csv_dbg(xop, csv, "csv: emit: ...\n");
+
+ ssize_t fnum;
+ uint32_t quote_flags;
+ leaf_t *lp;
+
+ /* If we have no data, then don't bother */
+ if (csv->c_leaf_depth == 0)
+ return;
+
+ if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {
+ csv->c_flags |= CF_HEADER_DONE;
+
+ for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
+ lp = &csv->c_leaf[fnum];
+ const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);
+
+ if (fnum != 0)
+ xo_buf_append(&csv->c_data, ",", 1);
+
+ xo_buf_append(&csv->c_data, name, strlen(name));
+ }
+
+ csv_append_newline(&csv->c_data, csv);
+ }
+
+ for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
+ lp = &csv->c_leaf[fnum];
+ const char *value;
+
+ if (lp->f_flags & LF_HAS_VALUE) {
+ value = xo_buf_data(&csv->c_value_buf, lp->f_value);
+ } else {
+ value = "";
+ }
+
+ quote_flags = csv_quote_flags(xop, csv, value);
+
+ if (fnum != 0)
+ xo_buf_append(&csv->c_data, ",", 1);
+
+ if (quote_flags & QF_NEEDS_QUOTES)
+ xo_buf_append(&csv->c_data, "\"", 1);
+
+ if (quote_flags & QF_NEEDS_ESCAPE)
+ csv_escape(&csv->c_data, value, strlen(value));
+ else
+ xo_buf_append(&csv->c_data, value, strlen(value));
+
+ if (quote_flags & QF_NEEDS_QUOTES)
+ xo_buf_append(&csv->c_data, "\"", 1);
+ }
+
+ csv_append_newline(&csv->c_data, csv);
+
+ /* We flush if either flush flag is set */
+ if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))
+ xo_flush_h(xop);
+
+ /* Clean out values from leafs */
+ for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
+ lp = &csv->c_leaf[fnum];
+
+ lp->f_flags &= ~LF_HAS_VALUE;
+ lp->f_value = 0;
+ }
+
+ xo_buf_reset(&csv->c_value_buf);
+
+ /*
+ * Once we emit the first line, our set of leafs is locked and
+ * cannot be changed.
+ */
+ csv->c_flags |= CF_LEAFS_DONE;
+}
+
+/*
+ * Open a "level" of hierarchy, either a container or an instance. Look
+ * for a match in the path=x/y/z hierarchy, and ignore if not a match.
+ * If we're at the end of the path, start recording leaf values.
+ */
+static int
+csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,
+ const char *name, int instance)
+{
+ /* An new "open" event means we stop recording */
+ if (csv->c_flags & CF_RECORD_DATA) {
+ csv->c_flags &= ~CF_RECORD_DATA;
+ csv_emit_record(xop, csv);
+ return 0;
+ }
+
+ const char *path_top = csv_path_top(csv, 0);
+
+ /* If the top of the stack does not match the name, then ignore */
+ if (path_top == NULL) {
+ if (instance && !(csv->c_flags & CF_HAS_PATH)) {
+ csv_dbg(xop, csv, "csv: recording (no-path) ...\n");
+ csv->c_flags |= CF_RECORD_DATA;
+ }
+
+ } else if (xo_streq(path_top, name)) {
+ csv->c_path_cur += 1; /* Advance to next path member */
+
+ csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,
+ csv->c_path_cur, csv->c_path_max);
+
+ /* If we're all the way thru the path members, start recording */
+ if (csv->c_path_cur == csv->c_path_max) {
+ csv_dbg(xop, csv, "csv: recording ...\n");
+ csv->c_flags |= CF_RECORD_DATA;
+ }
+ }
+
+ /* Push the name on the stack */
+ csv_stack_push(csv, name);
+
+ return 0;
+}
+
+/*
+ * Close a "level", either a container or an instance.
+ */
+static int
+csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)
+{
+ /* If we're recording, a close triggers an emit */
+ if (csv->c_flags & CF_RECORD_DATA) {
+ csv->c_flags &= ~CF_RECORD_DATA;
+ csv_emit_record(xop, csv);
+ }
+
+ const char *path_top = csv_path_top(csv, -1);
+ csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,
+ path_top ?: "", csv->c_path_cur);
+
+ /* If the top of the stack does not match the name, then ignore */
+ if (path_top != NULL && xo_streq(path_top, name)) {
+ csv->c_path_cur -= 1;
+ return 0;
+ }
+
+ /* Pop the name off the stack */
+ csv_stack_pop(csv, name);
+
+ return 0;
+}
+
+/*
+ * Return the index of a given leaf in the c_leaf[] array, where we
+ * record leaf values. If the leaf is new and we haven't stopped recording
+ * leafs, then make a new slot for it and record the name.
+ */
+static int
+csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,
+ const char *name, xo_xff_flags_t flags)
+{
+ ssize_t fnum;
+ leaf_t *lp;
+ xo_buffer_t *xbp = &csv->c_name_buf;
+
+ for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
+ lp = &csv->c_leaf[fnum];
+
+ const char *fname = xo_buf_data(xbp, lp->f_name);
+ if (xo_streq(fname, name))
+ return fnum;
+ }
+
+ /* If we're done with adding new leafs, then bail */
+ if (csv->c_flags & CF_LEAFS_DONE)
+ return -1;
+
+ /* This leaf does not exist yet, so we need to create it */
+ /* Start by checking if there's enough room */
+ if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {
+ /* Out of room; realloc it */
+ ssize_t new_max = csv->c_leaf_max * 2;
+ if (new_max == 0)
+ new_max = C_LEAF_MAX;
+
+ lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));
+ if (lp == NULL)
+ return -1; /* No luck; bail */
+
+ /* Zero out the new portion */
+ bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));
+
+ /* Update csv data */
+ csv->c_leaf = lp;
+ csv->c_leaf_max = new_max;
+ }
+
+ lp = &csv->c_leaf[csv->c_leaf_depth++];
+#ifdef CSV_STACK_IS_NEEDED
+ lp->f_depth = csv->c_stack_depth;
+#endif /* CSV_STACK_IS_NEEDED */
+
+ lp->f_name = xo_buf_offset(xbp);
+
+ char *cp = xo_buf_cur(xbp);
+ xo_buf_append(xbp, name, strlen(name) + 1);
+
+ if (flags & XFF_KEY)
+ lp->f_flags |= LF_KEY;
+
+ csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",
+ fnum, name, cp, lp->f_flags);
+
+ return fnum;
+}
+
+/*
+ * Record a new value for a leaf
+ */
+static void
+csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,
+ const char *value)
+{
+ xo_buffer_t *xbp = &csv->c_value_buf;
+
+ lp->f_value = xo_buf_offset(xbp);
+ lp->f_flags |= LF_HAS_VALUE;
+
+ char *cp = xo_buf_cur(xbp);
+ xo_buf_append(xbp, value, strlen(value) + 1);
+
+ csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",
+ value, cp, lp->f_flags);
+}
+
+/*
+ * Record the requested set of leaf names. The input should be a set
+ * of leaf names, separated by periods.
+ */
+static int
+csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)
+{
+ char *cp, *ep, *np;
+ ssize_t len = strlen(leafs_raw);
+ char *leafs_buf = alloca(len + 1);
+
+ memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */
+
+ for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {
+ np = strchr(cp, '.');
+ if (np)
+ *np++ = '\0';
+
+ if (*cp == '\0') /* Skip empty names */
+ continue;
+
+ csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);
+ csv_leaf_num(xop, csv, cp, 0);
+ }
+
+ /*
+ * Since we've been told explicitly what leafs matter, ignore the rest
+ */
+ csv->c_flags |= CF_LEAFS_DONE;
+
+ return 0;
+}
+
+/*
+ * Record the requested path elements. The input should be a set of
+ * container or instances names, separated by slashes.
+ */
+static int
+csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)
+{
+ int count;
+ char *cp, *ep, *np;
+ ssize_t len = strlen(path_raw);
+ char *path_buf = xo_realloc(NULL, len + 1);
+
+ memcpy(path_buf, path_raw, len + 1);
+
+ for (cp = path_buf, ep = path_buf + len, count = 2;
+ cp && cp < ep; cp = np) {
+ np = strchr(cp, '/');
+ if (np) {
+ np += 1;
+ count += 1;
+ }
+ }
+
+ path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);
+ if (path == NULL) {
+ xo_failure(xop, "allocation failure for path '%s'", path_buf);
+ return -1;
+ }
+
+ bzero(path, sizeof(path[0]) * count);
+
+ for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {
+ path[count++].pf_name = cp;
+
+ np = strchr(cp, '/');
+ if (np)
+ *np++ = '\0';
+ csv_dbg(xop, csv, "path: [%s]\n", cp);
+ }
+
+ path[count].pf_name = NULL;
+
+ if (csv->c_path) /* In case two paths are given */
+ xo_free(csv->c_path);
+ if (csv->c_path_buf) /* In case two paths are given */
+ xo_free(csv->c_path_buf);
+
+ csv->c_path_buf = path_buf;
+ csv->c_path = path;
+ csv->c_path_max = count;
+ csv->c_path_cur = 0;
+
+ return 0;
+}
+
+/*
+ * Extract the option values. The format is:
+ * -libxo encoder=csv:kw=val+kw=val+kw=val,pretty,etc
+ */
+static int
+csv_options (xo_handle_t *xop, csv_private_t *csv, const char *raw_opts)
+{
+ ssize_t len = strlen(raw_opts);
+ char *options = alloca(len + 1);
+ memcpy(options, raw_opts, len);
+ options[len] = '\0';
+
+ char *cp, *ep, *np, *vp;
+ for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {
+ np = strchr(cp, '+');
+ if (np)
+ *np++ = '\0';
+
+ vp = strchr(cp, '=');
+ if (vp)
+ *vp++ = '\0';
+
+ if (xo_streq(cp, "path")) {
+ /* Record the path */
+ if (vp != NULL && csv_record_path(xop, csv, vp))
+ return -1;
+
+ csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */
+
+ } else if (xo_streq(cp, "leafs")
+ || xo_streq(cp, "leaf")
+ || xo_streq(cp, "leaves")) {
+ /* Record the leafs */
+ if (vp != NULL && csv_record_leafs(xop, csv, vp))
+ return -1;
+
+ } else if (xo_streq(cp, "no-keys")) {
+ csv->c_flags |= CF_NO_KEYS;
+ } else if (xo_streq(cp, "no-header")) {
+ csv->c_flags |= CF_NO_HEADER;
+ } else if (xo_streq(cp, "value-only")) {
+ csv->c_flags |= CF_VALUE_ONLY;
+ } else if (xo_streq(cp, "dos")) {
+ csv->c_flags |= CF_DOS_NEWLINE;
+ } else if (xo_streq(cp, "no-quotes")) {
+ csv->c_flags |= CF_NO_QUOTES;
+ } else if (xo_streq(cp, "debug")) {
+ csv->c_flags |= CF_DEBUG;
+ } else {
+ xo_warn_hc(xop, -1,
+ "unknown encoder option value: '%s'", cp);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Handler for incoming data values. We just record each leaf name and
+ * value. The values are emittd when the instance is closed.
+ */
+static int
+csv_data (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
+ const char *name, const char *value,
+ xo_xof_flags_t flags)
+{
+ csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);
+
+ if (!(csv->c_flags & CF_RECORD_DATA))
+ return 0;
+
+ /* Find the leaf number */
+ int fnum = csv_leaf_num(xop, csv, name, flags);
+ if (fnum < 0)
+ return 0; /* Don't bother recording */
+
+ leaf_t *lp = &csv->c_leaf[fnum];
+ csv_leaf_set(xop, csv, lp, value);
+
+ return 0;
+}
+
+/*
+ * The callback from libxo, passing us operations/events as they
+ * happen.
+ */
+static int
+csv_handler (XO_ENCODER_HANDLER_ARGS)
+{
+ int rc = 0;
+ csv_private_t *csv = private;
+ xo_buffer_t *xbp = csv ? &csv->c_data : NULL;
+
+ csv_dbg(xop, csv, "op %s: [%s] [%s]\n", xo_encoder_op_name(op),
+ name ?: "", value ?: "");
+ fflush(stdout);
+
+ /* If we don't have private data, we're sunk */
+ if (csv == NULL && op != XO_OP_CREATE)
+ return -1;
+
+ switch (op) {
+ case XO_OP_CREATE: /* Called when the handle is init'd */
+ rc = csv_create(xop);
+ break;
+
+ case XO_OP_OPTIONS:
+ rc = csv_options(xop, csv, value);
+ break;
+
+ case XO_OP_OPEN_LIST:
+ case XO_OP_CLOSE_LIST:
+ break; /* Ignore these ops */
+
+ case XO_OP_OPEN_CONTAINER:
+ case XO_OP_OPEN_LEAF_LIST:
+ rc = csv_open_level(xop, csv, name, 0);
+ break;
+
+ case XO_OP_OPEN_INSTANCE:
+ rc = csv_open_level(xop, csv, name, 1);
+ break;
+
+ case XO_OP_CLOSE_CONTAINER:
+ case XO_OP_CLOSE_LEAF_LIST:
+ case XO_OP_CLOSE_INSTANCE:
+ rc = csv_close_level(xop, csv, name);
+ break;
+
+ case XO_OP_STRING: /* Quoted UTF-8 string */
+ case XO_OP_CONTENT: /* Other content */
+ rc = csv_data(xop, csv, name, value, flags);
+ break;
+
+ case XO_OP_FINISH: /* Clean up function */
+ break;
+
+ case XO_OP_FLUSH: /* Clean up function */
+ rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
+ if (rc > 0)
+ rc = 0;
+
+ xo_buf_reset(xbp);
+ break;
+
+ case XO_OP_DESTROY: /* Clean up function */
+ csv_destroy(xop, csv);
+ break;
+
+ case XO_OP_ATTRIBUTE: /* Attribute name/value */
+ break;
+
+ case XO_OP_VERSION: /* Version string */
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Callback when our encoder is loaded.
+ */
+int
+xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
+{
+ arg->xei_handler = csv_handler;
+ arg->xei_version = XO_ENCODER_VERSION;
+
+ return 0;
+}