xref: /freebsd-src/contrib/libxo/encoder/csv/enc_csv.c (revision 773bec086828bf0f1ba663958853823f7a059fb5)
176afb20cSPhil Shafer /*
276afb20cSPhil Shafer  * Copyright (c) 2015, Juniper Networks, Inc.
376afb20cSPhil Shafer  * All rights reserved.
476afb20cSPhil Shafer  * This SOFTWARE is licensed under the LICENSE provided in the
576afb20cSPhil Shafer  * ../Copyright file. By downloading, installing, copying, or otherwise
676afb20cSPhil Shafer  * using the SOFTWARE, you agree to be bound by the terms of that
776afb20cSPhil Shafer  * LICENSE.
876afb20cSPhil Shafer  * Phil Shafer, August 2015
976afb20cSPhil Shafer  */
1076afb20cSPhil Shafer 
1176afb20cSPhil Shafer /*
1276afb20cSPhil Shafer  * CSV encoder generates comma-separated value files for specific
1376afb20cSPhil Shafer  * subsets of data.  This is not (and cannot be) a generalized
1476afb20cSPhil Shafer  * facility, but for specific subsets of data, CSV data can be
1576afb20cSPhil Shafer  * reasonably generated.  For example, the df XML content:
1676afb20cSPhil Shafer  *     <filesystem>
1776afb20cSPhil Shafer  *      <name>procfs</name>
1876afb20cSPhil Shafer  *      <total-blocks>4</total-blocks>
1976afb20cSPhil Shafer  *      <used-blocks>4</used-blocks>
2076afb20cSPhil Shafer  *      <available-blocks>0</available-blocks>
2176afb20cSPhil Shafer  *      <used-percent>100</used-percent>
2276afb20cSPhil Shafer  *      <mounted-on>/proc</mounted-on>
2376afb20cSPhil Shafer  *    </filesystem>
2476afb20cSPhil Shafer  *
2576afb20cSPhil Shafer  * could be represented as:
2676afb20cSPhil Shafer  *
2776afb20cSPhil Shafer  *  #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on
2876afb20cSPhil Shafer  *  procfs,4,4,0,100,/proc
2976afb20cSPhil Shafer  *
3076afb20cSPhil Shafer  * Data is then constrained to be sibling leaf values.  In addition,
3176afb20cSPhil Shafer  * singular leafs can also be matched.  The costs include recording
3276afb20cSPhil Shafer  * the specific leaf names (to ensure consistency) and some
3376afb20cSPhil Shafer  * buffering.
3476afb20cSPhil Shafer  *
3576afb20cSPhil Shafer  * Some escaping is needed for CSV files, following the rules of RFC4180:
3676afb20cSPhil Shafer  *
3776afb20cSPhil Shafer  * - Fields containing a line-break, double-quote or commas should be
3876afb20cSPhil Shafer  *   quoted. (If they are not, the file will likely be impossible to
3976afb20cSPhil Shafer  *   process correctly).
4076afb20cSPhil Shafer  * - A (double) quote character in a field must be represented by two
4176afb20cSPhil Shafer  *   (double) quote characters.
4276afb20cSPhil Shafer  * - Leading and trialing whitespace require fields be quoted.
4376afb20cSPhil Shafer  *
44*5c5819b2SPhil Shafer  * Cheesy, but simple.  The RFC also requires MS-DOS end-of-line,
45*5c5819b2SPhil Shafer  * which we only do with the "dos" option.  Strange that we still live
46*5c5819b2SPhil Shafer  * in a DOS-friendly world, but then again, we make spaceships based
47*5c5819b2SPhil Shafer  * on the horse butts (http://www.astrodigital.org/space/stshorse.html
48*5c5819b2SPhil Shafer  * though the "built by English expatriates” bit is rubbish; better to
49*5c5819b2SPhil Shafer  * say the first engines used in America were built by Englishmen.)
5076afb20cSPhil Shafer  */
5176afb20cSPhil Shafer 
5276afb20cSPhil Shafer #include <string.h>
5376afb20cSPhil Shafer #include <sys/types.h>
5476afb20cSPhil Shafer #include <unistd.h>
5576afb20cSPhil Shafer #include <stdint.h>
5676afb20cSPhil Shafer #include <ctype.h>
5776afb20cSPhil Shafer #include <stdlib.h>
5876afb20cSPhil Shafer #include <limits.h>
5976afb20cSPhil Shafer 
6076afb20cSPhil Shafer #include "xo.h"
6176afb20cSPhil Shafer #include "xo_encoder.h"
6276afb20cSPhil Shafer #include "xo_buf.h"
6376afb20cSPhil Shafer 
6476afb20cSPhil Shafer #ifndef UNUSED
6576afb20cSPhil Shafer #define UNUSED __attribute__ ((__unused__))
6676afb20cSPhil Shafer #endif /* UNUSED */
6776afb20cSPhil Shafer 
6876afb20cSPhil Shafer /*
6976afb20cSPhil Shafer  * The CSV encoder has three moving parts:
7076afb20cSPhil Shafer  *
7176afb20cSPhil Shafer  * - The path holds the path we are matching against
7276afb20cSPhil Shafer  *   - This is given as input via "options" and does not change
7376afb20cSPhil Shafer  *
7476afb20cSPhil Shafer  * - The stack holds the current names of the open elements
7576afb20cSPhil Shafer  *   - The "open" operations push, while the "close" pop
7676afb20cSPhil Shafer  *   - Turns out, at this point, the stack is unused, but I've
7776afb20cSPhil Shafer  *     left "drippings" in the code because I see this as useful
7876afb20cSPhil Shafer  *     for future features (under CSV_STACK_IS_NEEDED).
7976afb20cSPhil Shafer  *
8076afb20cSPhil Shafer  * - The leafs record the current set of leaf
8176afb20cSPhil Shafer  *   - A key from the parent list counts as a leaf (unless CF_NO_KEYS)
8276afb20cSPhil Shafer  *   - Once the path is matched, all other leafs at that level are leafs
8376afb20cSPhil Shafer  *   - Leafs are recorded to get the header comment accurately recorded
8476afb20cSPhil Shafer  *   - Once the first line is emited, the set of leafs _cannot_ change
8576afb20cSPhil Shafer  *
8676afb20cSPhil Shafer  * We use offsets into the buffers, since we know they can be
8776afb20cSPhil Shafer  * realloc'd out from under us, as the size increases.  The 'path'
8876afb20cSPhil Shafer  * is fixed, we allocate it once, so it doesn't need offsets.
8976afb20cSPhil Shafer  */
9076afb20cSPhil Shafer typedef struct path_frame_s {
9176afb20cSPhil Shafer     char *pf_name;	       /* Path member name; points into c_path_buf */
9276afb20cSPhil Shafer     uint32_t pf_flags;	       /* Flags for this path element (PFF_*) */
9376afb20cSPhil Shafer } path_frame_t;
9476afb20cSPhil Shafer 
9576afb20cSPhil Shafer typedef struct stack_frame_s {
9676afb20cSPhil Shafer     ssize_t sf_off;		/* Element name; offset in c_stack_buf */
9776afb20cSPhil Shafer     uint32_t sf_flags;		/* Flags for this frame (SFF_*) */
9876afb20cSPhil Shafer } stack_frame_t;
9976afb20cSPhil Shafer 
10076afb20cSPhil Shafer /* Flags for sf_flags */
10176afb20cSPhil Shafer 
10276afb20cSPhil Shafer typedef struct leaf_s {
10376afb20cSPhil Shafer     ssize_t f_name;		/* Name of leaf; offset in c_name_buf */
10476afb20cSPhil Shafer     ssize_t f_value;		/* Value of leaf; offset in c_value_buf */
10576afb20cSPhil Shafer     uint32_t f_flags;		/* Flags for this value (FF_*)  */
10676afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
10776afb20cSPhil Shafer     ssize_t f_depth;		/* Depth of stack when leaf was recorded */
10876afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
10976afb20cSPhil Shafer } leaf_t;
11076afb20cSPhil Shafer 
11176afb20cSPhil Shafer /* Flags for f_flags */
11276afb20cSPhil Shafer #define LF_KEY		(1<<0)	/* Leaf is a key */
11376afb20cSPhil Shafer #define LF_HAS_VALUE	(1<<1)	/* Value has been set */
11476afb20cSPhil Shafer 
11576afb20cSPhil Shafer typedef struct csv_private_s {
11676afb20cSPhil Shafer     uint32_t c_flags;		/* Flags for this encoder */
11776afb20cSPhil Shafer 
11876afb20cSPhil Shafer     /* The path for which we select leafs */
11976afb20cSPhil Shafer     char *c_path_buf;	    	/* Buffer containing path members */
12076afb20cSPhil Shafer     path_frame_t *c_path;	/* Array of path members */
12176afb20cSPhil Shafer     ssize_t c_path_max;		/* Depth of c_path[] */
12276afb20cSPhil Shafer     ssize_t c_path_cur;		/* Current depth in c_path[] */
12376afb20cSPhil Shafer 
12476afb20cSPhil Shafer     /* A stack of open elements (xo_op_list, xo_op_container) */
12576afb20cSPhil Shafer #if CSV_STACK_IS_NEEDED
12676afb20cSPhil Shafer     xo_buffer_t c_stack_buf;	/* Buffer used for stack content */
12776afb20cSPhil Shafer     stack_frame_t *c_stack;	/* Stack of open tags */
12876afb20cSPhil Shafer     ssize_t c_stack_max;	/* Maximum stack depth */
12976afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
13076afb20cSPhil Shafer     ssize_t c_stack_depth;	/* Current stack depth */
13176afb20cSPhil Shafer 
13276afb20cSPhil Shafer     /* List of leafs we are emitting (to ensure consistency) */
13376afb20cSPhil Shafer     xo_buffer_t c_name_buf;	/* String buffer for leaf names */
13476afb20cSPhil Shafer     xo_buffer_t c_value_buf;	/* String buffer for leaf values */
13576afb20cSPhil Shafer     leaf_t *c_leaf;		/* List of leafs */
13676afb20cSPhil Shafer     ssize_t c_leaf_depth;	/* Current depth of c_leaf[] (next free) */
13776afb20cSPhil Shafer     ssize_t c_leaf_max;		/* Max depth of c_leaf[] */
13876afb20cSPhil Shafer 
13976afb20cSPhil Shafer     xo_buffer_t c_data;		/* Buffer for creating data */
14076afb20cSPhil Shafer } csv_private_t;
14176afb20cSPhil Shafer 
14276afb20cSPhil Shafer #define C_STACK_MAX	32	/* default c_stack_max */
14376afb20cSPhil Shafer #define C_LEAF_MAX	32	/* default c_leaf_max */
14476afb20cSPhil Shafer 
14576afb20cSPhil Shafer /* Flags for this structure */
14676afb20cSPhil Shafer #define CF_HEADER_DONE	(1<<0)	/* Have already written the header */
14776afb20cSPhil Shafer #define CF_NO_HEADER	(1<<1)	/* Do not generate header */
14876afb20cSPhil Shafer #define CF_NO_KEYS	(1<<2)	/* Do not generate excess keys */
14976afb20cSPhil Shafer #define CF_VALUE_ONLY	(1<<3)	/* Only generate the value */
15076afb20cSPhil Shafer 
15176afb20cSPhil Shafer #define CF_DOS_NEWLINE	(1<<4)	/* Generate CR-NL, just like MS-DOS */
15276afb20cSPhil Shafer #define CF_LEAFS_DONE	(1<<5)	/* Leafs are already been recorded */
15376afb20cSPhil Shafer #define CF_NO_QUOTES	(1<<6)	/* Do not generate quotes */
15476afb20cSPhil Shafer #define CF_RECORD_DATA	(1<<7)	/* Record all sibling leafs */
15576afb20cSPhil Shafer 
15676afb20cSPhil Shafer #define CF_DEBUG	(1<<8)	/* Make debug output */
15776afb20cSPhil Shafer #define CF_HAS_PATH	(1<<9)	/* A "path" option was provided */
15876afb20cSPhil Shafer 
15976afb20cSPhil Shafer /*
16076afb20cSPhil Shafer  * A simple debugging print function, similar to psu_dbg.  Controlled by
16176afb20cSPhil Shafer  * the undocumented "debug" option.
16276afb20cSPhil Shafer  */
16376afb20cSPhil Shafer static void
csv_dbg(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * fmt,...)16476afb20cSPhil Shafer csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
16576afb20cSPhil Shafer 	 const char *fmt, ...)
16676afb20cSPhil Shafer {
16776afb20cSPhil Shafer     if (csv == NULL || !(csv->c_flags & CF_DEBUG))
16876afb20cSPhil Shafer 	return;
16976afb20cSPhil Shafer 
17076afb20cSPhil Shafer     va_list vap;
17176afb20cSPhil Shafer 
17276afb20cSPhil Shafer     va_start(vap, fmt);
17376afb20cSPhil Shafer     vfprintf(stderr, fmt, vap);
17476afb20cSPhil Shafer     va_end(vap);
17576afb20cSPhil Shafer }
17676afb20cSPhil Shafer 
17776afb20cSPhil Shafer /*
17876afb20cSPhil Shafer  * Create the private data for this handle, initialize it, and record
17976afb20cSPhil Shafer  * the pointer in the handle.
18076afb20cSPhil Shafer  */
18176afb20cSPhil Shafer static int
csv_create(xo_handle_t * xop)18276afb20cSPhil Shafer csv_create (xo_handle_t *xop)
18376afb20cSPhil Shafer {
18476afb20cSPhil Shafer     csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));
18576afb20cSPhil Shafer     if (csv == NULL)
18676afb20cSPhil Shafer 	return -1;
18776afb20cSPhil Shafer 
18876afb20cSPhil Shafer     bzero(csv, sizeof(*csv));
18976afb20cSPhil Shafer     xo_buf_init(&csv->c_data);
19076afb20cSPhil Shafer     xo_buf_init(&csv->c_name_buf);
19176afb20cSPhil Shafer     xo_buf_init(&csv->c_value_buf);
19276afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
19376afb20cSPhil Shafer     xo_buf_init(&csv->c_stack_buf);
19476afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
19576afb20cSPhil Shafer 
19676afb20cSPhil Shafer     xo_set_private(xop, csv);
19776afb20cSPhil Shafer 
19876afb20cSPhil Shafer     return 0;
19976afb20cSPhil Shafer }
20076afb20cSPhil Shafer 
20176afb20cSPhil Shafer /*
20276afb20cSPhil Shafer  * Clean up and release any data in use by this handle
20376afb20cSPhil Shafer  */
20476afb20cSPhil Shafer static void
csv_destroy(xo_handle_t * xop UNUSED,csv_private_t * csv)20576afb20cSPhil Shafer csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)
20676afb20cSPhil Shafer {
20776afb20cSPhil Shafer     /* Clean up */
20876afb20cSPhil Shafer     xo_buf_cleanup(&csv->c_data);
20976afb20cSPhil Shafer     xo_buf_cleanup(&csv->c_name_buf);
21076afb20cSPhil Shafer     xo_buf_cleanup(&csv->c_value_buf);
21176afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
21276afb20cSPhil Shafer     xo_buf_cleanup(&csv->c_stack_buf);
21376afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
21476afb20cSPhil Shafer 
21576afb20cSPhil Shafer     if (csv->c_leaf)
21676afb20cSPhil Shafer 	xo_free(csv->c_leaf);
21776afb20cSPhil Shafer     if (csv->c_path_buf)
21876afb20cSPhil Shafer 	xo_free(csv->c_path_buf);
21976afb20cSPhil Shafer }
22076afb20cSPhil Shafer 
22176afb20cSPhil Shafer /*
22276afb20cSPhil Shafer  * Return the element name at the top of the path stack.  This is the
22376afb20cSPhil Shafer  * item that we are currently trying to match on.
22476afb20cSPhil Shafer  */
22576afb20cSPhil Shafer static const char *
csv_path_top(csv_private_t * csv,ssize_t delta)22676afb20cSPhil Shafer csv_path_top (csv_private_t *csv, ssize_t delta)
22776afb20cSPhil Shafer {
22876afb20cSPhil Shafer     if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)
22976afb20cSPhil Shafer 	return NULL;
23076afb20cSPhil Shafer 
23176afb20cSPhil Shafer     ssize_t cur = csv->c_path_cur + delta;
23276afb20cSPhil Shafer 
23376afb20cSPhil Shafer     if (cur < 0)
23476afb20cSPhil Shafer 	return NULL;
23576afb20cSPhil Shafer 
23676afb20cSPhil Shafer     return csv->c_path[cur].pf_name;
23776afb20cSPhil Shafer }
23876afb20cSPhil Shafer 
23976afb20cSPhil Shafer /*
24076afb20cSPhil Shafer  * Underimplemented stack functionality
24176afb20cSPhil Shafer  */
24276afb20cSPhil Shafer static inline void
csv_stack_push(csv_private_t * csv UNUSED,const char * name UNUSED)24376afb20cSPhil Shafer csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)
24476afb20cSPhil Shafer {
24576afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
24676afb20cSPhil Shafer     csv->c_stack_depth += 1;
24776afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
24876afb20cSPhil Shafer }
24976afb20cSPhil Shafer 
25076afb20cSPhil Shafer /*
25176afb20cSPhil Shafer  * Underimplemented stack functionality
25276afb20cSPhil Shafer  */
25376afb20cSPhil Shafer static inline void
csv_stack_pop(csv_private_t * csv UNUSED,const char * name UNUSED)25476afb20cSPhil Shafer csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)
25576afb20cSPhil Shafer {
25676afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
25776afb20cSPhil Shafer     csv->c_stack_depth -= 1;
25876afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
25976afb20cSPhil Shafer }
26076afb20cSPhil Shafer 
26176afb20cSPhil Shafer /* Flags for csv_quote_flags */
26276afb20cSPhil Shafer #define QF_NEEDS_QUOTES	(1<<0)		/* Needs to be quoted */
26376afb20cSPhil Shafer #define QF_NEEDS_ESCAPE	(1<<1)		/* Needs to be escaped */
26476afb20cSPhil Shafer 
26576afb20cSPhil Shafer /*
26676afb20cSPhil Shafer  * Determine how much quote processing is needed.  The details of the
26776afb20cSPhil Shafer  * quoting rules are given at the top of this file.  We return a set
26876afb20cSPhil Shafer  * of flags, indicating what's needed.
26976afb20cSPhil Shafer  */
27076afb20cSPhil Shafer static uint32_t
csv_quote_flags(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * value)27176afb20cSPhil Shafer csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
27276afb20cSPhil Shafer 		  const char *value)
27376afb20cSPhil Shafer {
27476afb20cSPhil Shafer     static const char quoted[] = "\n\r\",";
27576afb20cSPhil Shafer     static const char escaped[] = "\"";
27676afb20cSPhil Shafer 
27776afb20cSPhil Shafer     if (csv->c_flags & CF_NO_QUOTES)	/* User doesn't want quotes */
27876afb20cSPhil Shafer 	return 0;
27976afb20cSPhil Shafer 
28076afb20cSPhil Shafer     size_t len = strlen(value);
28176afb20cSPhil Shafer     uint32_t rc = 0;
28276afb20cSPhil Shafer 
28376afb20cSPhil Shafer     if (strcspn(value, quoted) != len)
28476afb20cSPhil Shafer 	rc |= QF_NEEDS_QUOTES;
28576afb20cSPhil Shafer     else if (isspace((int) value[0]))	/* Leading whitespace */
28676afb20cSPhil Shafer 	rc |= QF_NEEDS_QUOTES;
28776afb20cSPhil Shafer     else if (isspace((int) value[len - 1])) /* Trailing whitespace */
28876afb20cSPhil Shafer 	rc |= QF_NEEDS_QUOTES;
28976afb20cSPhil Shafer 
29076afb20cSPhil Shafer     if (strcspn(value, escaped) != len)
29176afb20cSPhil Shafer 	rc |= QF_NEEDS_ESCAPE;
29276afb20cSPhil Shafer 
29376afb20cSPhil Shafer     csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",
29476afb20cSPhil Shafer 	    value, rc, len, strcspn(value, quoted));
29576afb20cSPhil Shafer 
29676afb20cSPhil Shafer     return rc;
29776afb20cSPhil Shafer }
29876afb20cSPhil Shafer 
29976afb20cSPhil Shafer /*
30076afb20cSPhil Shafer  * Escape the string, following the rules in RFC4180
30176afb20cSPhil Shafer  */
30276afb20cSPhil Shafer static void
csv_escape(xo_buffer_t * xbp,const char * value,size_t len)30376afb20cSPhil Shafer csv_escape (xo_buffer_t *xbp, const char *value, size_t len)
30476afb20cSPhil Shafer {
30576afb20cSPhil Shafer     const char *cp, *ep, *np;
30676afb20cSPhil Shafer 
30776afb20cSPhil Shafer     for (cp = value, ep = value + len; cp && cp < ep; cp = np) {
30876afb20cSPhil Shafer 	np = strchr(cp, '"');
30976afb20cSPhil Shafer 	if (np) {
31076afb20cSPhil Shafer 	    np += 1;
31176afb20cSPhil Shafer 	    xo_buf_append(xbp, cp, np - cp);
31276afb20cSPhil Shafer 	    xo_buf_append(xbp, "\"", 1);
31376afb20cSPhil Shafer 	} else
31476afb20cSPhil Shafer 	    xo_buf_append(xbp, cp, ep - cp);
31576afb20cSPhil Shafer     }
31676afb20cSPhil Shafer }
31776afb20cSPhil Shafer 
31876afb20cSPhil Shafer /*
31976afb20cSPhil Shafer  * Append a newline to the buffer, following the settings of the "dos"
32076afb20cSPhil Shafer  * flag.
32176afb20cSPhil Shafer  */
32276afb20cSPhil Shafer static void
csv_append_newline(xo_buffer_t * xbp,csv_private_t * csv)32376afb20cSPhil Shafer csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)
32476afb20cSPhil Shafer {
32576afb20cSPhil Shafer     if (csv->c_flags & CF_DOS_NEWLINE)
32676afb20cSPhil Shafer 	xo_buf_append(xbp, "\r\n", 2);
32776afb20cSPhil Shafer     else
32876afb20cSPhil Shafer 	xo_buf_append(xbp, "\n", 1);
32976afb20cSPhil Shafer }
33076afb20cSPhil Shafer 
33176afb20cSPhil Shafer /*
33276afb20cSPhil Shafer  * Create a 'record' of 'fields' from our recorded leaf values.  If
33376afb20cSPhil Shafer  * this is the first line and "no-header" isn't given, make a record
33476afb20cSPhil Shafer  * containing the leaf names.
33576afb20cSPhil Shafer  */
33676afb20cSPhil Shafer static void
csv_emit_record(xo_handle_t * xop,csv_private_t * csv)33776afb20cSPhil Shafer csv_emit_record (xo_handle_t *xop, csv_private_t *csv)
33876afb20cSPhil Shafer {
33976afb20cSPhil Shafer     csv_dbg(xop, csv, "csv: emit: ...\n");
34076afb20cSPhil Shafer 
34176afb20cSPhil Shafer     ssize_t fnum;
34276afb20cSPhil Shafer     uint32_t quote_flags;
34376afb20cSPhil Shafer     leaf_t *lp;
34476afb20cSPhil Shafer 
34576afb20cSPhil Shafer     /* If we have no data, then don't bother */
34676afb20cSPhil Shafer     if (csv->c_leaf_depth == 0)
34776afb20cSPhil Shafer 	return;
34876afb20cSPhil Shafer 
34976afb20cSPhil Shafer     if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {
35076afb20cSPhil Shafer 	csv->c_flags |= CF_HEADER_DONE;
35176afb20cSPhil Shafer 
35276afb20cSPhil Shafer 	for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
35376afb20cSPhil Shafer 	    lp = &csv->c_leaf[fnum];
35476afb20cSPhil Shafer 	    const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);
35576afb20cSPhil Shafer 
35676afb20cSPhil Shafer 	    if (fnum != 0)
35776afb20cSPhil Shafer 		xo_buf_append(&csv->c_data, ",", 1);
35876afb20cSPhil Shafer 
35976afb20cSPhil Shafer 	    xo_buf_append(&csv->c_data, name, strlen(name));
36076afb20cSPhil Shafer 	}
36176afb20cSPhil Shafer 
36276afb20cSPhil Shafer 	csv_append_newline(&csv->c_data, csv);
36376afb20cSPhil Shafer     }
36476afb20cSPhil Shafer 
36576afb20cSPhil Shafer     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
36676afb20cSPhil Shafer 	lp = &csv->c_leaf[fnum];
36776afb20cSPhil Shafer 	const char *value;
36876afb20cSPhil Shafer 
36976afb20cSPhil Shafer 	if (lp->f_flags & LF_HAS_VALUE) {
37076afb20cSPhil Shafer 	    value = xo_buf_data(&csv->c_value_buf, lp->f_value);
37176afb20cSPhil Shafer 	} else {
37276afb20cSPhil Shafer 	    value = "";
37376afb20cSPhil Shafer 	}
37476afb20cSPhil Shafer 
37576afb20cSPhil Shafer 	quote_flags = csv_quote_flags(xop, csv, value);
37676afb20cSPhil Shafer 
37776afb20cSPhil Shafer 	if (fnum != 0)
37876afb20cSPhil Shafer 	    xo_buf_append(&csv->c_data, ",", 1);
37976afb20cSPhil Shafer 
38076afb20cSPhil Shafer 	if (quote_flags & QF_NEEDS_QUOTES)
38176afb20cSPhil Shafer 	    xo_buf_append(&csv->c_data, "\"", 1);
38276afb20cSPhil Shafer 
38376afb20cSPhil Shafer 	if (quote_flags & QF_NEEDS_ESCAPE)
38476afb20cSPhil Shafer 	    csv_escape(&csv->c_data, value, strlen(value));
38576afb20cSPhil Shafer 	else
38676afb20cSPhil Shafer 	    xo_buf_append(&csv->c_data, value, strlen(value));
38776afb20cSPhil Shafer 
38876afb20cSPhil Shafer 	if (quote_flags & QF_NEEDS_QUOTES)
38976afb20cSPhil Shafer 	    xo_buf_append(&csv->c_data, "\"", 1);
39076afb20cSPhil Shafer     }
39176afb20cSPhil Shafer 
39276afb20cSPhil Shafer     csv_append_newline(&csv->c_data, csv);
39376afb20cSPhil Shafer 
39476afb20cSPhil Shafer     /* We flush if either flush flag is set */
39576afb20cSPhil Shafer     if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))
39676afb20cSPhil Shafer 	xo_flush_h(xop);
39776afb20cSPhil Shafer 
39876afb20cSPhil Shafer     /* Clean out values from leafs */
39976afb20cSPhil Shafer     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
40076afb20cSPhil Shafer 	lp = &csv->c_leaf[fnum];
40176afb20cSPhil Shafer 
40276afb20cSPhil Shafer 	lp->f_flags &= ~LF_HAS_VALUE;
40376afb20cSPhil Shafer 	lp->f_value = 0;
40476afb20cSPhil Shafer     }
40576afb20cSPhil Shafer 
40676afb20cSPhil Shafer     xo_buf_reset(&csv->c_value_buf);
40776afb20cSPhil Shafer 
40876afb20cSPhil Shafer     /*
40976afb20cSPhil Shafer      * Once we emit the first line, our set of leafs is locked and
41076afb20cSPhil Shafer      * cannot be changed.
41176afb20cSPhil Shafer      */
41276afb20cSPhil Shafer     csv->c_flags |= CF_LEAFS_DONE;
41376afb20cSPhil Shafer }
41476afb20cSPhil Shafer 
41576afb20cSPhil Shafer /*
41676afb20cSPhil Shafer  * Open a "level" of hierarchy, either a container or an instance.  Look
41776afb20cSPhil Shafer  * for a match in the path=x/y/z hierarchy, and ignore if not a match.
41876afb20cSPhil Shafer  * If we're at the end of the path, start recording leaf values.
41976afb20cSPhil Shafer  */
42076afb20cSPhil Shafer static int
csv_open_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,int instance)42176afb20cSPhil Shafer csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,
42276afb20cSPhil Shafer 		const char *name, int instance)
42376afb20cSPhil Shafer {
42476afb20cSPhil Shafer     /* An new "open" event means we stop recording */
42576afb20cSPhil Shafer     if (csv->c_flags & CF_RECORD_DATA) {
42676afb20cSPhil Shafer 	csv->c_flags &= ~CF_RECORD_DATA;
42776afb20cSPhil Shafer 	csv_emit_record(xop, csv);
42876afb20cSPhil Shafer 	return 0;
42976afb20cSPhil Shafer     }
43076afb20cSPhil Shafer 
43176afb20cSPhil Shafer     const char *path_top = csv_path_top(csv, 0);
43276afb20cSPhil Shafer 
43376afb20cSPhil Shafer     /* If the top of the stack does not match the name, then ignore */
43476afb20cSPhil Shafer     if (path_top == NULL) {
43576afb20cSPhil Shafer 	if (instance && !(csv->c_flags & CF_HAS_PATH)) {
43676afb20cSPhil Shafer 	    csv_dbg(xop, csv, "csv: recording (no-path) ...\n");
43776afb20cSPhil Shafer 	    csv->c_flags |= CF_RECORD_DATA;
43876afb20cSPhil Shafer 	}
43976afb20cSPhil Shafer 
44076afb20cSPhil Shafer     } else if (xo_streq(path_top, name)) {
44176afb20cSPhil Shafer 	csv->c_path_cur += 1;		/* Advance to next path member */
44276afb20cSPhil Shafer 
44376afb20cSPhil Shafer 	csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,
44476afb20cSPhil Shafer 	       csv->c_path_cur, csv->c_path_max);
44576afb20cSPhil Shafer 
44676afb20cSPhil Shafer 	/* If we're all the way thru the path members, start recording */
44776afb20cSPhil Shafer 	if (csv->c_path_cur == csv->c_path_max) {
44876afb20cSPhil Shafer 	    csv_dbg(xop, csv, "csv: recording ...\n");
44976afb20cSPhil Shafer 	    csv->c_flags |= CF_RECORD_DATA;
45076afb20cSPhil Shafer 	}
45176afb20cSPhil Shafer     }
45276afb20cSPhil Shafer 
45376afb20cSPhil Shafer     /* Push the name on the stack */
45476afb20cSPhil Shafer     csv_stack_push(csv, name);
45576afb20cSPhil Shafer 
45676afb20cSPhil Shafer     return 0;
45776afb20cSPhil Shafer }
45876afb20cSPhil Shafer 
45976afb20cSPhil Shafer /*
46076afb20cSPhil Shafer  * Close a "level", either a container or an instance.
46176afb20cSPhil Shafer  */
46276afb20cSPhil Shafer static int
csv_close_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name)46376afb20cSPhil Shafer csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)
46476afb20cSPhil Shafer {
46576afb20cSPhil Shafer     /* If we're recording, a close triggers an emit */
46676afb20cSPhil Shafer     if (csv->c_flags & CF_RECORD_DATA) {
46776afb20cSPhil Shafer 	csv->c_flags &= ~CF_RECORD_DATA;
46876afb20cSPhil Shafer 	csv_emit_record(xop, csv);
46976afb20cSPhil Shafer     }
47076afb20cSPhil Shafer 
47176afb20cSPhil Shafer     const char *path_top = csv_path_top(csv, -1);
47276afb20cSPhil Shafer     csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,
47376afb20cSPhil Shafer 	   path_top ?: "", csv->c_path_cur);
47476afb20cSPhil Shafer 
47576afb20cSPhil Shafer     /* If the top of the stack does not match the name, then ignore */
47676afb20cSPhil Shafer     if (path_top != NULL && xo_streq(path_top, name)) {
47776afb20cSPhil Shafer 	csv->c_path_cur -= 1;
47876afb20cSPhil Shafer 	return 0;
47976afb20cSPhil Shafer     }
48076afb20cSPhil Shafer 
48176afb20cSPhil Shafer     /* Pop the name off the stack */
48276afb20cSPhil Shafer     csv_stack_pop(csv, name);
48376afb20cSPhil Shafer 
48476afb20cSPhil Shafer     return 0;
48576afb20cSPhil Shafer }
48676afb20cSPhil Shafer 
48776afb20cSPhil Shafer /*
48876afb20cSPhil Shafer  * Return the index of a given leaf in the c_leaf[] array, where we
48976afb20cSPhil Shafer  * record leaf values.  If the leaf is new and we haven't stopped recording
49076afb20cSPhil Shafer  * leafs, then make a new slot for it and record the name.
49176afb20cSPhil Shafer  */
49276afb20cSPhil Shafer static int
csv_leaf_num(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,xo_xff_flags_t flags)49376afb20cSPhil Shafer csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,
49476afb20cSPhil Shafer 	       const char *name, xo_xff_flags_t flags)
49576afb20cSPhil Shafer {
49676afb20cSPhil Shafer     ssize_t fnum;
49776afb20cSPhil Shafer     leaf_t *lp;
49876afb20cSPhil Shafer     xo_buffer_t *xbp = &csv->c_name_buf;
49976afb20cSPhil Shafer 
50076afb20cSPhil Shafer     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
50176afb20cSPhil Shafer 	lp = &csv->c_leaf[fnum];
50276afb20cSPhil Shafer 
50376afb20cSPhil Shafer 	const char *fname = xo_buf_data(xbp, lp->f_name);
50476afb20cSPhil Shafer 	if (xo_streq(fname, name))
50576afb20cSPhil Shafer 	    return fnum;
50676afb20cSPhil Shafer     }
50776afb20cSPhil Shafer 
50876afb20cSPhil Shafer     /* If we're done with adding new leafs, then bail */
50976afb20cSPhil Shafer     if (csv->c_flags & CF_LEAFS_DONE)
51076afb20cSPhil Shafer 	return -1;
51176afb20cSPhil Shafer 
51276afb20cSPhil Shafer     /* This leaf does not exist yet, so we need to create it */
51376afb20cSPhil Shafer     /* Start by checking if there's enough room */
51476afb20cSPhil Shafer     if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {
51576afb20cSPhil Shafer 	/* Out of room; realloc it */
51676afb20cSPhil Shafer 	ssize_t new_max = csv->c_leaf_max * 2;
51776afb20cSPhil Shafer 	if (new_max == 0)
51876afb20cSPhil Shafer 	    new_max = C_LEAF_MAX;
51976afb20cSPhil Shafer 
52076afb20cSPhil Shafer 	lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));
52176afb20cSPhil Shafer 	if (lp == NULL)
52276afb20cSPhil Shafer 	    return -1;			/* No luck; bail */
52376afb20cSPhil Shafer 
52476afb20cSPhil Shafer 	/* Zero out the new portion */
52576afb20cSPhil Shafer 	bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));
52676afb20cSPhil Shafer 
52776afb20cSPhil Shafer 	/* Update csv data */
52876afb20cSPhil Shafer 	csv->c_leaf = lp;
52976afb20cSPhil Shafer 	csv->c_leaf_max = new_max;
53076afb20cSPhil Shafer     }
53176afb20cSPhil Shafer 
53276afb20cSPhil Shafer     lp = &csv->c_leaf[csv->c_leaf_depth++];
53376afb20cSPhil Shafer #ifdef CSV_STACK_IS_NEEDED
53476afb20cSPhil Shafer     lp->f_depth = csv->c_stack_depth;
53576afb20cSPhil Shafer #endif /* CSV_STACK_IS_NEEDED */
53676afb20cSPhil Shafer 
53776afb20cSPhil Shafer     lp->f_name = xo_buf_offset(xbp);
53876afb20cSPhil Shafer 
53976afb20cSPhil Shafer     char *cp = xo_buf_cur(xbp);
54076afb20cSPhil Shafer     xo_buf_append(xbp, name, strlen(name) + 1);
54176afb20cSPhil Shafer 
54276afb20cSPhil Shafer     if (flags & XFF_KEY)
54376afb20cSPhil Shafer 	lp->f_flags |= LF_KEY;
54476afb20cSPhil Shafer 
54576afb20cSPhil Shafer     csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",
54676afb20cSPhil Shafer 	    fnum, name, cp, lp->f_flags);
54776afb20cSPhil Shafer 
54876afb20cSPhil Shafer     return fnum;
54976afb20cSPhil Shafer }
55076afb20cSPhil Shafer 
55176afb20cSPhil Shafer /*
55276afb20cSPhil Shafer  * Record a new value for a leaf
55376afb20cSPhil Shafer  */
55476afb20cSPhil Shafer static void
csv_leaf_set(xo_handle_t * xop UNUSED,csv_private_t * csv,leaf_t * lp,const char * value)55576afb20cSPhil Shafer csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,
55676afb20cSPhil Shafer 	       const char *value)
55776afb20cSPhil Shafer {
55876afb20cSPhil Shafer     xo_buffer_t *xbp = &csv->c_value_buf;
55976afb20cSPhil Shafer 
56076afb20cSPhil Shafer     lp->f_value = xo_buf_offset(xbp);
56176afb20cSPhil Shafer     lp->f_flags |= LF_HAS_VALUE;
56276afb20cSPhil Shafer 
56376afb20cSPhil Shafer     char *cp = xo_buf_cur(xbp);
56476afb20cSPhil Shafer     xo_buf_append(xbp, value, strlen(value) + 1);
56576afb20cSPhil Shafer 
56676afb20cSPhil Shafer     csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",
56776afb20cSPhil Shafer 	    value, cp, lp->f_flags);
56876afb20cSPhil Shafer }
56976afb20cSPhil Shafer 
57076afb20cSPhil Shafer /*
57176afb20cSPhil Shafer  * Record the requested set of leaf names.  The input should be a set
57276afb20cSPhil Shafer  * of leaf names, separated by periods.
57376afb20cSPhil Shafer  */
57476afb20cSPhil Shafer static int
csv_record_leafs(xo_handle_t * xop,csv_private_t * csv,const char * leafs_raw)57576afb20cSPhil Shafer csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)
57676afb20cSPhil Shafer {
57776afb20cSPhil Shafer     char *cp, *ep, *np;
57876afb20cSPhil Shafer     ssize_t len = strlen(leafs_raw);
57976afb20cSPhil Shafer     char *leafs_buf = alloca(len + 1);
58076afb20cSPhil Shafer 
58176afb20cSPhil Shafer     memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */
58276afb20cSPhil Shafer 
58376afb20cSPhil Shafer     for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {
58476afb20cSPhil Shafer 	np = strchr(cp, '.');
58576afb20cSPhil Shafer 	if (np)
58676afb20cSPhil Shafer 	    *np++ = '\0';
58776afb20cSPhil Shafer 
58876afb20cSPhil Shafer 	if (*cp == '\0')		/* Skip empty names */
58976afb20cSPhil Shafer 	    continue;
59076afb20cSPhil Shafer 
59176afb20cSPhil Shafer 	csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);
59276afb20cSPhil Shafer 	csv_leaf_num(xop, csv, cp, 0);
59376afb20cSPhil Shafer     }
59476afb20cSPhil Shafer 
59576afb20cSPhil Shafer     /*
59676afb20cSPhil Shafer      * Since we've been told explicitly what leafs matter, ignore the rest
59776afb20cSPhil Shafer      */
59876afb20cSPhil Shafer     csv->c_flags |= CF_LEAFS_DONE;
59976afb20cSPhil Shafer 
60076afb20cSPhil Shafer     return 0;
60176afb20cSPhil Shafer }
60276afb20cSPhil Shafer 
60376afb20cSPhil Shafer /*
60476afb20cSPhil Shafer  * Record the requested path elements.  The input should be a set of
60576afb20cSPhil Shafer  * container or instances names, separated by slashes.
60676afb20cSPhil Shafer  */
60776afb20cSPhil Shafer static int
csv_record_path(xo_handle_t * xop,csv_private_t * csv,const char * path_raw)60876afb20cSPhil Shafer csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)
60976afb20cSPhil Shafer {
61076afb20cSPhil Shafer     int count;
61176afb20cSPhil Shafer     char *cp, *ep, *np;
61276afb20cSPhil Shafer     ssize_t len = strlen(path_raw);
61376afb20cSPhil Shafer     char *path_buf = xo_realloc(NULL, len + 1);
61476afb20cSPhil Shafer 
61576afb20cSPhil Shafer     memcpy(path_buf, path_raw, len + 1);
61676afb20cSPhil Shafer 
61776afb20cSPhil Shafer     for (cp = path_buf, ep = path_buf + len, count = 2;
61876afb20cSPhil Shafer 	 cp && cp < ep; cp = np) {
61976afb20cSPhil Shafer 	np = strchr(cp, '/');
62076afb20cSPhil Shafer 	if (np) {
62176afb20cSPhil Shafer 	    np += 1;
62276afb20cSPhil Shafer 	    count += 1;
62376afb20cSPhil Shafer 	}
62476afb20cSPhil Shafer     }
62576afb20cSPhil Shafer 
62676afb20cSPhil Shafer     path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);
62776afb20cSPhil Shafer     if (path == NULL) {
62876afb20cSPhil Shafer 	xo_failure(xop, "allocation failure for path '%s'", path_buf);
62976afb20cSPhil Shafer 	return -1;
63076afb20cSPhil Shafer     }
63176afb20cSPhil Shafer 
63276afb20cSPhil Shafer     bzero(path, sizeof(path[0]) * count);
63376afb20cSPhil Shafer 
63476afb20cSPhil Shafer     for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {
63576afb20cSPhil Shafer 	path[count++].pf_name = cp;
63676afb20cSPhil Shafer 
63776afb20cSPhil Shafer 	np = strchr(cp, '/');
63876afb20cSPhil Shafer 	if (np)
63976afb20cSPhil Shafer 	    *np++ = '\0';
64076afb20cSPhil Shafer 	csv_dbg(xop, csv, "path: [%s]\n", cp);
64176afb20cSPhil Shafer     }
64276afb20cSPhil Shafer 
64376afb20cSPhil Shafer     path[count].pf_name = NULL;
64476afb20cSPhil Shafer 
64576afb20cSPhil Shafer     if (csv->c_path)		     /* In case two paths are given */
64676afb20cSPhil Shafer 	xo_free(csv->c_path);
64776afb20cSPhil Shafer     if (csv->c_path_buf)	     /* In case two paths are given */
64876afb20cSPhil Shafer 	xo_free(csv->c_path_buf);
64976afb20cSPhil Shafer 
65076afb20cSPhil Shafer     csv->c_path_buf = path_buf;
65176afb20cSPhil Shafer     csv->c_path = path;
65276afb20cSPhil Shafer     csv->c_path_max = count;
65376afb20cSPhil Shafer     csv->c_path_cur = 0;
65476afb20cSPhil Shafer 
65576afb20cSPhil Shafer     return 0;
65676afb20cSPhil Shafer }
65776afb20cSPhil Shafer 
65876afb20cSPhil Shafer /*
65976afb20cSPhil Shafer  * Extract the option values.  The format is:
660*5c5819b2SPhil Shafer  *    -libxo encoder=csv:kw=val:kw=val:kw=val,pretty
661*5c5819b2SPhil Shafer  *    -libxo encoder=csv+kw=val+kw=val+kw=val,pretty
66276afb20cSPhil Shafer  */
66376afb20cSPhil Shafer static int
csv_options(xo_handle_t * xop,csv_private_t * csv,const char * raw_opts,char opts_char)664*5c5819b2SPhil Shafer csv_options (xo_handle_t *xop, csv_private_t *csv,
665*5c5819b2SPhil Shafer 	     const char *raw_opts, char opts_char)
66676afb20cSPhil Shafer {
66776afb20cSPhil Shafer     ssize_t len = strlen(raw_opts);
66876afb20cSPhil Shafer     char *options = alloca(len + 1);
66976afb20cSPhil Shafer     memcpy(options, raw_opts, len);
67076afb20cSPhil Shafer     options[len] = '\0';
67176afb20cSPhil Shafer 
67276afb20cSPhil Shafer     char *cp, *ep, *np, *vp;
67376afb20cSPhil Shafer     for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {
674*5c5819b2SPhil Shafer 	np = strchr(cp, opts_char);
67576afb20cSPhil Shafer 	if (np)
67676afb20cSPhil Shafer 	    *np++ = '\0';
67776afb20cSPhil Shafer 
67876afb20cSPhil Shafer 	vp = strchr(cp, '=');
67976afb20cSPhil Shafer 	if (vp)
68076afb20cSPhil Shafer 	    *vp++ = '\0';
68176afb20cSPhil Shafer 
68276afb20cSPhil Shafer 	if (xo_streq(cp, "path")) {
68376afb20cSPhil Shafer 	    /* Record the path */
68476afb20cSPhil Shafer 	    if (vp != NULL && csv_record_path(xop, csv, vp))
68576afb20cSPhil Shafer   		return -1;
68676afb20cSPhil Shafer 
68776afb20cSPhil Shafer 	    csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */
68876afb20cSPhil Shafer 
68976afb20cSPhil Shafer 	} else if (xo_streq(cp, "leafs")
69076afb20cSPhil Shafer 		   || xo_streq(cp, "leaf")
69176afb20cSPhil Shafer 		   || xo_streq(cp, "leaves")) {
69276afb20cSPhil Shafer 	    /* Record the leafs */
69376afb20cSPhil Shafer 	    if (vp != NULL && csv_record_leafs(xop, csv, vp))
69476afb20cSPhil Shafer   		return -1;
69576afb20cSPhil Shafer 
69676afb20cSPhil Shafer 	} else if (xo_streq(cp, "no-keys")) {
69776afb20cSPhil Shafer 	    csv->c_flags |= CF_NO_KEYS;
69876afb20cSPhil Shafer 	} else if (xo_streq(cp, "no-header")) {
69976afb20cSPhil Shafer 	    csv->c_flags |= CF_NO_HEADER;
70076afb20cSPhil Shafer 	} else if (xo_streq(cp, "value-only")) {
70176afb20cSPhil Shafer 	    csv->c_flags |= CF_VALUE_ONLY;
70276afb20cSPhil Shafer 	} else if (xo_streq(cp, "dos")) {
70376afb20cSPhil Shafer 	    csv->c_flags |= CF_DOS_NEWLINE;
70476afb20cSPhil Shafer 	} else if (xo_streq(cp, "no-quotes")) {
70576afb20cSPhil Shafer 	    csv->c_flags |= CF_NO_QUOTES;
70676afb20cSPhil Shafer 	} else if (xo_streq(cp, "debug")) {
70776afb20cSPhil Shafer 	    csv->c_flags |= CF_DEBUG;
70876afb20cSPhil Shafer 	} else {
70976afb20cSPhil Shafer 	    xo_warn_hc(xop, -1,
71076afb20cSPhil Shafer 		       "unknown encoder option value: '%s'", cp);
71176afb20cSPhil Shafer 	    return -1;
71276afb20cSPhil Shafer 	}
71376afb20cSPhil Shafer     }
71476afb20cSPhil Shafer 
71576afb20cSPhil Shafer     return 0;
71676afb20cSPhil Shafer }
71776afb20cSPhil Shafer 
71876afb20cSPhil Shafer /*
71976afb20cSPhil Shafer  * Handler for incoming data values.  We just record each leaf name and
72076afb20cSPhil Shafer  * value.  The values are emittd when the instance is closed.
72176afb20cSPhil Shafer  */
72276afb20cSPhil Shafer 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)72376afb20cSPhil Shafer csv_data (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
72476afb20cSPhil Shafer 	  const char *name, const char *value,
72576afb20cSPhil Shafer 	  xo_xof_flags_t flags)
72676afb20cSPhil Shafer {
72776afb20cSPhil Shafer     csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);
72876afb20cSPhil Shafer 
72976afb20cSPhil Shafer     if (!(csv->c_flags & CF_RECORD_DATA))
73076afb20cSPhil Shafer 	return 0;
73176afb20cSPhil Shafer 
73276afb20cSPhil Shafer     /* Find the leaf number */
73376afb20cSPhil Shafer     int fnum = csv_leaf_num(xop, csv, name, flags);
73476afb20cSPhil Shafer     if (fnum < 0)
73576afb20cSPhil Shafer 	return 0;			/* Don't bother recording */
73676afb20cSPhil Shafer 
73776afb20cSPhil Shafer     leaf_t *lp = &csv->c_leaf[fnum];
73876afb20cSPhil Shafer     csv_leaf_set(xop, csv, lp, value);
73976afb20cSPhil Shafer 
74076afb20cSPhil Shafer     return 0;
74176afb20cSPhil Shafer }
74276afb20cSPhil Shafer 
74376afb20cSPhil Shafer /*
74476afb20cSPhil Shafer  * The callback from libxo, passing us operations/events as they
74576afb20cSPhil Shafer  * happen.
74676afb20cSPhil Shafer  */
74776afb20cSPhil Shafer static int
csv_handler(XO_ENCODER_HANDLER_ARGS)74876afb20cSPhil Shafer csv_handler (XO_ENCODER_HANDLER_ARGS)
74976afb20cSPhil Shafer {
75076afb20cSPhil Shafer     int rc = 0;
75176afb20cSPhil Shafer     csv_private_t *csv = private;
75276afb20cSPhil Shafer     xo_buffer_t *xbp = csv ? &csv->c_data : NULL;
75376afb20cSPhil Shafer 
75476afb20cSPhil Shafer     csv_dbg(xop, csv, "op %s: [%s] [%s]\n",  xo_encoder_op_name(op),
75576afb20cSPhil Shafer 	   name ?: "", value ?: "");
75676afb20cSPhil Shafer     fflush(stdout);
75776afb20cSPhil Shafer 
75876afb20cSPhil Shafer     /* If we don't have private data, we're sunk */
75976afb20cSPhil Shafer     if (csv == NULL && op != XO_OP_CREATE)
76076afb20cSPhil Shafer 	return -1;
76176afb20cSPhil Shafer 
76276afb20cSPhil Shafer     switch (op) {
76376afb20cSPhil Shafer     case XO_OP_CREATE:		/* Called when the handle is init'd */
76476afb20cSPhil Shafer 	rc = csv_create(xop);
76576afb20cSPhil Shafer 	break;
76676afb20cSPhil Shafer 
76776afb20cSPhil Shafer     case XO_OP_OPTIONS:
768*5c5819b2SPhil Shafer 	rc = csv_options(xop, csv, value, ':');
769*5c5819b2SPhil Shafer 	break;
770*5c5819b2SPhil Shafer 
771*5c5819b2SPhil Shafer     case XO_OP_OPTIONS_PLUS:
772*5c5819b2SPhil Shafer 	rc = csv_options(xop, csv, value, '+');
77376afb20cSPhil Shafer 	break;
77476afb20cSPhil Shafer 
77576afb20cSPhil Shafer     case XO_OP_OPEN_LIST:
77676afb20cSPhil Shafer     case XO_OP_CLOSE_LIST:
77776afb20cSPhil Shafer 	break;				/* Ignore these ops */
77876afb20cSPhil Shafer 
77976afb20cSPhil Shafer     case XO_OP_OPEN_CONTAINER:
78076afb20cSPhil Shafer     case XO_OP_OPEN_LEAF_LIST:
78176afb20cSPhil Shafer 	rc = csv_open_level(xop, csv, name, 0);
78276afb20cSPhil Shafer 	break;
78376afb20cSPhil Shafer 
78476afb20cSPhil Shafer     case XO_OP_OPEN_INSTANCE:
78576afb20cSPhil Shafer 	rc = csv_open_level(xop, csv, name, 1);
78676afb20cSPhil Shafer 	break;
78776afb20cSPhil Shafer 
78876afb20cSPhil Shafer     case XO_OP_CLOSE_CONTAINER:
78976afb20cSPhil Shafer     case XO_OP_CLOSE_LEAF_LIST:
79076afb20cSPhil Shafer     case XO_OP_CLOSE_INSTANCE:
79176afb20cSPhil Shafer 	rc = csv_close_level(xop, csv, name);
79276afb20cSPhil Shafer 	break;
79376afb20cSPhil Shafer 
79476afb20cSPhil Shafer     case XO_OP_STRING:		   /* Quoted UTF-8 string */
79576afb20cSPhil Shafer     case XO_OP_CONTENT:		   /* Other content */
79676afb20cSPhil Shafer 	rc = csv_data(xop, csv, name, value, flags);
79776afb20cSPhil Shafer 	break;
79876afb20cSPhil Shafer 
79976afb20cSPhil Shafer     case XO_OP_FINISH:		   /* Clean up function */
80076afb20cSPhil Shafer 	break;
80176afb20cSPhil Shafer 
80276afb20cSPhil Shafer     case XO_OP_FLUSH:		   /* Clean up function */
80376afb20cSPhil Shafer 	rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
80476afb20cSPhil Shafer 	if (rc > 0)
80576afb20cSPhil Shafer 	    rc = 0;
80676afb20cSPhil Shafer 
80776afb20cSPhil Shafer 	xo_buf_reset(xbp);
80876afb20cSPhil Shafer 	break;
80976afb20cSPhil Shafer 
81076afb20cSPhil Shafer     case XO_OP_DESTROY:		   /* Clean up function */
81176afb20cSPhil Shafer 	csv_destroy(xop, csv);
81276afb20cSPhil Shafer 	break;
81376afb20cSPhil Shafer 
81476afb20cSPhil Shafer     case XO_OP_ATTRIBUTE:	   /* Attribute name/value */
81576afb20cSPhil Shafer 	break;
81676afb20cSPhil Shafer 
81776afb20cSPhil Shafer     case XO_OP_VERSION:		/* Version string */
81876afb20cSPhil Shafer 	break;
81976afb20cSPhil Shafer     }
82076afb20cSPhil Shafer 
82176afb20cSPhil Shafer     return rc;
82276afb20cSPhil Shafer }
82376afb20cSPhil Shafer 
82476afb20cSPhil Shafer /*
82576afb20cSPhil Shafer  * Callback when our encoder is loaded.
82676afb20cSPhil Shafer  */
82776afb20cSPhil Shafer int
xo_encoder_library_init(XO_ENCODER_INIT_ARGS)82876afb20cSPhil Shafer xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
82976afb20cSPhil Shafer {
83076afb20cSPhil Shafer     arg->xei_handler = csv_handler;
83176afb20cSPhil Shafer     arg->xei_version = XO_ENCODER_VERSION;
83276afb20cSPhil Shafer 
83376afb20cSPhil Shafer     return 0;
83476afb20cSPhil Shafer }
835