xref: /f-stack/tools/libxo/encoder/csv/enc_csv.c (revision d4a07e70)
1*d4a07e70Sfengbojiang /*
2*d4a07e70Sfengbojiang  * Copyright (c) 2015, Juniper Networks, Inc.
3*d4a07e70Sfengbojiang  * All rights reserved.
4*d4a07e70Sfengbojiang  * This SOFTWARE is licensed under the LICENSE provided in the
5*d4a07e70Sfengbojiang  * ../Copyright file. By downloading, installing, copying, or otherwise
6*d4a07e70Sfengbojiang  * using the SOFTWARE, you agree to be bound by the terms of that
7*d4a07e70Sfengbojiang  * LICENSE.
8*d4a07e70Sfengbojiang  * Phil Shafer, August 2015
9*d4a07e70Sfengbojiang  */
10*d4a07e70Sfengbojiang 
11*d4a07e70Sfengbojiang /*
12*d4a07e70Sfengbojiang  * CSV encoder generates comma-separated value files for specific
13*d4a07e70Sfengbojiang  * subsets of data.  This is not (and cannot be) a generalized
14*d4a07e70Sfengbojiang  * facility, but for specific subsets of data, CSV data can be
15*d4a07e70Sfengbojiang  * reasonably generated.  For example, the df XML content:
16*d4a07e70Sfengbojiang  *     <filesystem>
17*d4a07e70Sfengbojiang  *      <name>procfs</name>
18*d4a07e70Sfengbojiang  *      <total-blocks>4</total-blocks>
19*d4a07e70Sfengbojiang  *      <used-blocks>4</used-blocks>
20*d4a07e70Sfengbojiang  *      <available-blocks>0</available-blocks>
21*d4a07e70Sfengbojiang  *      <used-percent>100</used-percent>
22*d4a07e70Sfengbojiang  *      <mounted-on>/proc</mounted-on>
23*d4a07e70Sfengbojiang  *    </filesystem>
24*d4a07e70Sfengbojiang  *
25*d4a07e70Sfengbojiang  * could be represented as:
26*d4a07e70Sfengbojiang  *
27*d4a07e70Sfengbojiang  *  #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on
28*d4a07e70Sfengbojiang  *  procfs,4,4,0,100,/proc
29*d4a07e70Sfengbojiang  *
30*d4a07e70Sfengbojiang  * Data is then constrained to be sibling leaf values.  In addition,
31*d4a07e70Sfengbojiang  * singular leafs can also be matched.  The costs include recording
32*d4a07e70Sfengbojiang  * the specific leaf names (to ensure consistency) and some
33*d4a07e70Sfengbojiang  * buffering.
34*d4a07e70Sfengbojiang  *
35*d4a07e70Sfengbojiang  * Some escaping is needed for CSV files, following the rules of RFC4180:
36*d4a07e70Sfengbojiang  *
37*d4a07e70Sfengbojiang  * - Fields containing a line-break, double-quote or commas should be
38*d4a07e70Sfengbojiang  *   quoted. (If they are not, the file will likely be impossible to
39*d4a07e70Sfengbojiang  *   process correctly).
40*d4a07e70Sfengbojiang  * - A (double) quote character in a field must be represented by two
41*d4a07e70Sfengbojiang  *   (double) quote characters.
42*d4a07e70Sfengbojiang  * - Leading and trialing whitespace require fields be quoted.
43*d4a07e70Sfengbojiang  *
44*d4a07e70Sfengbojiang  * Cheesy, but simple.  The RFC also requires MS-DOS end-of-line,
45*d4a07e70Sfengbojiang  * which we only do with the "dos" option.  Strange that we still live
46*d4a07e70Sfengbojiang  * in a DOS-friendly world, but then again, we make spaceships based
47*d4a07e70Sfengbojiang  * on the horse butts (http://www.astrodigital.org/space/stshorse.html
48*d4a07e70Sfengbojiang  * though the "built by English expatriates” bit is rubbish; better to
49*d4a07e70Sfengbojiang  * say the first engines used in America were built by Englishmen.)
50*d4a07e70Sfengbojiang  */
51*d4a07e70Sfengbojiang 
52*d4a07e70Sfengbojiang #include <string.h>
53*d4a07e70Sfengbojiang #include <sys/types.h>
54*d4a07e70Sfengbojiang #include <unistd.h>
55*d4a07e70Sfengbojiang #include <stdint.h>
56*d4a07e70Sfengbojiang #include <ctype.h>
57*d4a07e70Sfengbojiang #include <stdlib.h>
58*d4a07e70Sfengbojiang #include <limits.h>
59*d4a07e70Sfengbojiang 
60*d4a07e70Sfengbojiang #include "xo.h"
61*d4a07e70Sfengbojiang #include "xo_encoder.h"
62*d4a07e70Sfengbojiang #include "xo_buf.h"
63*d4a07e70Sfengbojiang 
64*d4a07e70Sfengbojiang #ifndef UNUSED
65*d4a07e70Sfengbojiang #define UNUSED __attribute__ ((__unused__))
66*d4a07e70Sfengbojiang #endif /* UNUSED */
67*d4a07e70Sfengbojiang 
68*d4a07e70Sfengbojiang /*
69*d4a07e70Sfengbojiang  * The CSV encoder has three moving parts:
70*d4a07e70Sfengbojiang  *
71*d4a07e70Sfengbojiang  * - The path holds the path we are matching against
72*d4a07e70Sfengbojiang  *   - This is given as input via "options" and does not change
73*d4a07e70Sfengbojiang  *
74*d4a07e70Sfengbojiang  * - The stack holds the current names of the open elements
75*d4a07e70Sfengbojiang  *   - The "open" operations push, while the "close" pop
76*d4a07e70Sfengbojiang  *   - Turns out, at this point, the stack is unused, but I've
77*d4a07e70Sfengbojiang  *     left "drippings" in the code because I see this as useful
78*d4a07e70Sfengbojiang  *     for future features (under CSV_STACK_IS_NEEDED).
79*d4a07e70Sfengbojiang  *
80*d4a07e70Sfengbojiang  * - The leafs record the current set of leaf
81*d4a07e70Sfengbojiang  *   - A key from the parent list counts as a leaf (unless CF_NO_KEYS)
82*d4a07e70Sfengbojiang  *   - Once the path is matched, all other leafs at that level are leafs
83*d4a07e70Sfengbojiang  *   - Leafs are recorded to get the header comment accurately recorded
84*d4a07e70Sfengbojiang  *   - Once the first line is emited, the set of leafs _cannot_ change
85*d4a07e70Sfengbojiang  *
86*d4a07e70Sfengbojiang  * We use offsets into the buffers, since we know they can be
87*d4a07e70Sfengbojiang  * realloc'd out from under us, as the size increases.  The 'path'
88*d4a07e70Sfengbojiang  * is fixed, we allocate it once, so it doesn't need offsets.
89*d4a07e70Sfengbojiang  */
90*d4a07e70Sfengbojiang typedef struct path_frame_s {
91*d4a07e70Sfengbojiang     char *pf_name;	       /* Path member name; points into c_path_buf */
92*d4a07e70Sfengbojiang     uint32_t pf_flags;	       /* Flags for this path element (PFF_*) */
93*d4a07e70Sfengbojiang } path_frame_t;
94*d4a07e70Sfengbojiang 
95*d4a07e70Sfengbojiang typedef struct stack_frame_s {
96*d4a07e70Sfengbojiang     ssize_t sf_off;		/* Element name; offset in c_stack_buf */
97*d4a07e70Sfengbojiang     uint32_t sf_flags;		/* Flags for this frame (SFF_*) */
98*d4a07e70Sfengbojiang } stack_frame_t;
99*d4a07e70Sfengbojiang 
100*d4a07e70Sfengbojiang /* Flags for sf_flags */
101*d4a07e70Sfengbojiang 
102*d4a07e70Sfengbojiang typedef struct leaf_s {
103*d4a07e70Sfengbojiang     ssize_t f_name;		/* Name of leaf; offset in c_name_buf */
104*d4a07e70Sfengbojiang     ssize_t f_value;		/* Value of leaf; offset in c_value_buf */
105*d4a07e70Sfengbojiang     uint32_t f_flags;		/* Flags for this value (FF_*)  */
106*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
107*d4a07e70Sfengbojiang     ssize_t f_depth;		/* Depth of stack when leaf was recorded */
108*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
109*d4a07e70Sfengbojiang } leaf_t;
110*d4a07e70Sfengbojiang 
111*d4a07e70Sfengbojiang /* Flags for f_flags */
112*d4a07e70Sfengbojiang #define LF_KEY		(1<<0)	/* Leaf is a key */
113*d4a07e70Sfengbojiang #define LF_HAS_VALUE	(1<<1)	/* Value has been set */
114*d4a07e70Sfengbojiang 
115*d4a07e70Sfengbojiang typedef struct csv_private_s {
116*d4a07e70Sfengbojiang     uint32_t c_flags;		/* Flags for this encoder */
117*d4a07e70Sfengbojiang 
118*d4a07e70Sfengbojiang     /* The path for which we select leafs */
119*d4a07e70Sfengbojiang     char *c_path_buf;	    	/* Buffer containing path members */
120*d4a07e70Sfengbojiang     path_frame_t *c_path;	/* Array of path members */
121*d4a07e70Sfengbojiang     ssize_t c_path_max;		/* Depth of c_path[] */
122*d4a07e70Sfengbojiang     ssize_t c_path_cur;		/* Current depth in c_path[] */
123*d4a07e70Sfengbojiang 
124*d4a07e70Sfengbojiang     /* A stack of open elements (xo_op_list, xo_op_container) */
125*d4a07e70Sfengbojiang #if CSV_STACK_IS_NEEDED
126*d4a07e70Sfengbojiang     xo_buffer_t c_stack_buf;	/* Buffer used for stack content */
127*d4a07e70Sfengbojiang     stack_frame_t *c_stack;	/* Stack of open tags */
128*d4a07e70Sfengbojiang     ssize_t c_stack_max;	/* Maximum stack depth */
129*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
130*d4a07e70Sfengbojiang     ssize_t c_stack_depth;	/* Current stack depth */
131*d4a07e70Sfengbojiang 
132*d4a07e70Sfengbojiang     /* List of leafs we are emitting (to ensure consistency) */
133*d4a07e70Sfengbojiang     xo_buffer_t c_name_buf;	/* String buffer for leaf names */
134*d4a07e70Sfengbojiang     xo_buffer_t c_value_buf;	/* String buffer for leaf values */
135*d4a07e70Sfengbojiang     leaf_t *c_leaf;		/* List of leafs */
136*d4a07e70Sfengbojiang     ssize_t c_leaf_depth;	/* Current depth of c_leaf[] (next free) */
137*d4a07e70Sfengbojiang     ssize_t c_leaf_max;		/* Max depth of c_leaf[] */
138*d4a07e70Sfengbojiang 
139*d4a07e70Sfengbojiang     xo_buffer_t c_data;		/* Buffer for creating data */
140*d4a07e70Sfengbojiang } csv_private_t;
141*d4a07e70Sfengbojiang 
142*d4a07e70Sfengbojiang #define C_STACK_MAX	32	/* default c_stack_max */
143*d4a07e70Sfengbojiang #define C_LEAF_MAX	32	/* default c_leaf_max */
144*d4a07e70Sfengbojiang 
145*d4a07e70Sfengbojiang /* Flags for this structure */
146*d4a07e70Sfengbojiang #define CF_HEADER_DONE	(1<<0)	/* Have already written the header */
147*d4a07e70Sfengbojiang #define CF_NO_HEADER	(1<<1)	/* Do not generate header */
148*d4a07e70Sfengbojiang #define CF_NO_KEYS	(1<<2)	/* Do not generate excess keys */
149*d4a07e70Sfengbojiang #define CF_VALUE_ONLY	(1<<3)	/* Only generate the value */
150*d4a07e70Sfengbojiang 
151*d4a07e70Sfengbojiang #define CF_DOS_NEWLINE	(1<<4)	/* Generate CR-NL, just like MS-DOS */
152*d4a07e70Sfengbojiang #define CF_LEAFS_DONE	(1<<5)	/* Leafs are already been recorded */
153*d4a07e70Sfengbojiang #define CF_NO_QUOTES	(1<<6)	/* Do not generate quotes */
154*d4a07e70Sfengbojiang #define CF_RECORD_DATA	(1<<7)	/* Record all sibling leafs */
155*d4a07e70Sfengbojiang 
156*d4a07e70Sfengbojiang #define CF_DEBUG	(1<<8)	/* Make debug output */
157*d4a07e70Sfengbojiang #define CF_HAS_PATH	(1<<9)	/* A "path" option was provided */
158*d4a07e70Sfengbojiang 
159*d4a07e70Sfengbojiang /*
160*d4a07e70Sfengbojiang  * A simple debugging print function, similar to psu_dbg.  Controlled by
161*d4a07e70Sfengbojiang  * the undocumented "debug" option.
162*d4a07e70Sfengbojiang  */
163*d4a07e70Sfengbojiang static void
csv_dbg(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * fmt,...)164*d4a07e70Sfengbojiang csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
165*d4a07e70Sfengbojiang 	 const char *fmt, ...)
166*d4a07e70Sfengbojiang {
167*d4a07e70Sfengbojiang     if (csv == NULL || !(csv->c_flags & CF_DEBUG))
168*d4a07e70Sfengbojiang 	return;
169*d4a07e70Sfengbojiang 
170*d4a07e70Sfengbojiang     va_list vap;
171*d4a07e70Sfengbojiang 
172*d4a07e70Sfengbojiang     va_start(vap, fmt);
173*d4a07e70Sfengbojiang     vfprintf(stderr, fmt, vap);
174*d4a07e70Sfengbojiang     va_end(vap);
175*d4a07e70Sfengbojiang }
176*d4a07e70Sfengbojiang 
177*d4a07e70Sfengbojiang /*
178*d4a07e70Sfengbojiang  * Create the private data for this handle, initialize it, and record
179*d4a07e70Sfengbojiang  * the pointer in the handle.
180*d4a07e70Sfengbojiang  */
181*d4a07e70Sfengbojiang static int
csv_create(xo_handle_t * xop)182*d4a07e70Sfengbojiang csv_create (xo_handle_t *xop)
183*d4a07e70Sfengbojiang {
184*d4a07e70Sfengbojiang     csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));
185*d4a07e70Sfengbojiang     if (csv == NULL)
186*d4a07e70Sfengbojiang 	return -1;
187*d4a07e70Sfengbojiang 
188*d4a07e70Sfengbojiang     bzero(csv, sizeof(*csv));
189*d4a07e70Sfengbojiang     xo_buf_init(&csv->c_data);
190*d4a07e70Sfengbojiang     xo_buf_init(&csv->c_name_buf);
191*d4a07e70Sfengbojiang     xo_buf_init(&csv->c_value_buf);
192*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
193*d4a07e70Sfengbojiang     xo_buf_init(&csv->c_stack_buf);
194*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
195*d4a07e70Sfengbojiang 
196*d4a07e70Sfengbojiang     xo_set_private(xop, csv);
197*d4a07e70Sfengbojiang 
198*d4a07e70Sfengbojiang     return 0;
199*d4a07e70Sfengbojiang }
200*d4a07e70Sfengbojiang 
201*d4a07e70Sfengbojiang /*
202*d4a07e70Sfengbojiang  * Clean up and release any data in use by this handle
203*d4a07e70Sfengbojiang  */
204*d4a07e70Sfengbojiang static void
csv_destroy(xo_handle_t * xop UNUSED,csv_private_t * csv)205*d4a07e70Sfengbojiang csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)
206*d4a07e70Sfengbojiang {
207*d4a07e70Sfengbojiang     /* Clean up */
208*d4a07e70Sfengbojiang     xo_buf_cleanup(&csv->c_data);
209*d4a07e70Sfengbojiang     xo_buf_cleanup(&csv->c_name_buf);
210*d4a07e70Sfengbojiang     xo_buf_cleanup(&csv->c_value_buf);
211*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
212*d4a07e70Sfengbojiang     xo_buf_cleanup(&csv->c_stack_buf);
213*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
214*d4a07e70Sfengbojiang 
215*d4a07e70Sfengbojiang     if (csv->c_leaf)
216*d4a07e70Sfengbojiang 	xo_free(csv->c_leaf);
217*d4a07e70Sfengbojiang     if (csv->c_path_buf)
218*d4a07e70Sfengbojiang 	xo_free(csv->c_path_buf);
219*d4a07e70Sfengbojiang }
220*d4a07e70Sfengbojiang 
221*d4a07e70Sfengbojiang /*
222*d4a07e70Sfengbojiang  * Return the element name at the top of the path stack.  This is the
223*d4a07e70Sfengbojiang  * item that we are currently trying to match on.
224*d4a07e70Sfengbojiang  */
225*d4a07e70Sfengbojiang static const char *
csv_path_top(csv_private_t * csv,ssize_t delta)226*d4a07e70Sfengbojiang csv_path_top (csv_private_t *csv, ssize_t delta)
227*d4a07e70Sfengbojiang {
228*d4a07e70Sfengbojiang     if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)
229*d4a07e70Sfengbojiang 	return NULL;
230*d4a07e70Sfengbojiang 
231*d4a07e70Sfengbojiang     ssize_t cur = csv->c_path_cur + delta;
232*d4a07e70Sfengbojiang 
233*d4a07e70Sfengbojiang     if (cur < 0)
234*d4a07e70Sfengbojiang 	return NULL;
235*d4a07e70Sfengbojiang 
236*d4a07e70Sfengbojiang     return csv->c_path[cur].pf_name;
237*d4a07e70Sfengbojiang }
238*d4a07e70Sfengbojiang 
239*d4a07e70Sfengbojiang /*
240*d4a07e70Sfengbojiang  * Underimplemented stack functionality
241*d4a07e70Sfengbojiang  */
242*d4a07e70Sfengbojiang static inline void
csv_stack_push(csv_private_t * csv UNUSED,const char * name UNUSED)243*d4a07e70Sfengbojiang csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)
244*d4a07e70Sfengbojiang {
245*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
246*d4a07e70Sfengbojiang     csv->c_stack_depth += 1;
247*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
248*d4a07e70Sfengbojiang }
249*d4a07e70Sfengbojiang 
250*d4a07e70Sfengbojiang /*
251*d4a07e70Sfengbojiang  * Underimplemented stack functionality
252*d4a07e70Sfengbojiang  */
253*d4a07e70Sfengbojiang static inline void
csv_stack_pop(csv_private_t * csv UNUSED,const char * name UNUSED)254*d4a07e70Sfengbojiang csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)
255*d4a07e70Sfengbojiang {
256*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
257*d4a07e70Sfengbojiang     csv->c_stack_depth -= 1;
258*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
259*d4a07e70Sfengbojiang }
260*d4a07e70Sfengbojiang 
261*d4a07e70Sfengbojiang /* Flags for csv_quote_flags */
262*d4a07e70Sfengbojiang #define QF_NEEDS_QUOTES	(1<<0)		/* Needs to be quoted */
263*d4a07e70Sfengbojiang #define QF_NEEDS_ESCAPE	(1<<1)		/* Needs to be escaped */
264*d4a07e70Sfengbojiang 
265*d4a07e70Sfengbojiang /*
266*d4a07e70Sfengbojiang  * Determine how much quote processing is needed.  The details of the
267*d4a07e70Sfengbojiang  * quoting rules are given at the top of this file.  We return a set
268*d4a07e70Sfengbojiang  * of flags, indicating what's needed.
269*d4a07e70Sfengbojiang  */
270*d4a07e70Sfengbojiang static uint32_t
csv_quote_flags(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * value)271*d4a07e70Sfengbojiang csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
272*d4a07e70Sfengbojiang 		  const char *value)
273*d4a07e70Sfengbojiang {
274*d4a07e70Sfengbojiang     static const char quoted[] = "\n\r\",";
275*d4a07e70Sfengbojiang     static const char escaped[] = "\"";
276*d4a07e70Sfengbojiang 
277*d4a07e70Sfengbojiang     if (csv->c_flags & CF_NO_QUOTES)	/* User doesn't want quotes */
278*d4a07e70Sfengbojiang 	return 0;
279*d4a07e70Sfengbojiang 
280*d4a07e70Sfengbojiang     size_t len = strlen(value);
281*d4a07e70Sfengbojiang     uint32_t rc = 0;
282*d4a07e70Sfengbojiang 
283*d4a07e70Sfengbojiang     if (strcspn(value, quoted) != len)
284*d4a07e70Sfengbojiang 	rc |= QF_NEEDS_QUOTES;
285*d4a07e70Sfengbojiang     else if (isspace((int) value[0]))	/* Leading whitespace */
286*d4a07e70Sfengbojiang 	rc |= QF_NEEDS_QUOTES;
287*d4a07e70Sfengbojiang     else if (isspace((int) value[len - 1])) /* Trailing whitespace */
288*d4a07e70Sfengbojiang 	rc |= QF_NEEDS_QUOTES;
289*d4a07e70Sfengbojiang 
290*d4a07e70Sfengbojiang     if (strcspn(value, escaped) != len)
291*d4a07e70Sfengbojiang 	rc |= QF_NEEDS_ESCAPE;
292*d4a07e70Sfengbojiang 
293*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",
294*d4a07e70Sfengbojiang 	    value, rc, len, strcspn(value, quoted));
295*d4a07e70Sfengbojiang 
296*d4a07e70Sfengbojiang     return rc;
297*d4a07e70Sfengbojiang }
298*d4a07e70Sfengbojiang 
299*d4a07e70Sfengbojiang /*
300*d4a07e70Sfengbojiang  * Escape the string, following the rules in RFC4180
301*d4a07e70Sfengbojiang  */
302*d4a07e70Sfengbojiang static void
csv_escape(xo_buffer_t * xbp,const char * value,size_t len)303*d4a07e70Sfengbojiang csv_escape (xo_buffer_t *xbp, const char *value, size_t len)
304*d4a07e70Sfengbojiang {
305*d4a07e70Sfengbojiang     const char *cp, *ep, *np;
306*d4a07e70Sfengbojiang 
307*d4a07e70Sfengbojiang     for (cp = value, ep = value + len; cp && cp < ep; cp = np) {
308*d4a07e70Sfengbojiang 	np = strchr(cp, '"');
309*d4a07e70Sfengbojiang 	if (np) {
310*d4a07e70Sfengbojiang 	    np += 1;
311*d4a07e70Sfengbojiang 	    xo_buf_append(xbp, cp, np - cp);
312*d4a07e70Sfengbojiang 	    xo_buf_append(xbp, "\"", 1);
313*d4a07e70Sfengbojiang 	} else
314*d4a07e70Sfengbojiang 	    xo_buf_append(xbp, cp, ep - cp);
315*d4a07e70Sfengbojiang     }
316*d4a07e70Sfengbojiang }
317*d4a07e70Sfengbojiang 
318*d4a07e70Sfengbojiang /*
319*d4a07e70Sfengbojiang  * Append a newline to the buffer, following the settings of the "dos"
320*d4a07e70Sfengbojiang  * flag.
321*d4a07e70Sfengbojiang  */
322*d4a07e70Sfengbojiang static void
csv_append_newline(xo_buffer_t * xbp,csv_private_t * csv)323*d4a07e70Sfengbojiang csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)
324*d4a07e70Sfengbojiang {
325*d4a07e70Sfengbojiang     if (csv->c_flags & CF_DOS_NEWLINE)
326*d4a07e70Sfengbojiang 	xo_buf_append(xbp, "\r\n", 2);
327*d4a07e70Sfengbojiang     else
328*d4a07e70Sfengbojiang 	xo_buf_append(xbp, "\n", 1);
329*d4a07e70Sfengbojiang }
330*d4a07e70Sfengbojiang 
331*d4a07e70Sfengbojiang /*
332*d4a07e70Sfengbojiang  * Create a 'record' of 'fields' from our recorded leaf values.  If
333*d4a07e70Sfengbojiang  * this is the first line and "no-header" isn't given, make a record
334*d4a07e70Sfengbojiang  * containing the leaf names.
335*d4a07e70Sfengbojiang  */
336*d4a07e70Sfengbojiang static void
csv_emit_record(xo_handle_t * xop,csv_private_t * csv)337*d4a07e70Sfengbojiang csv_emit_record (xo_handle_t *xop, csv_private_t *csv)
338*d4a07e70Sfengbojiang {
339*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "csv: emit: ...\n");
340*d4a07e70Sfengbojiang 
341*d4a07e70Sfengbojiang     ssize_t fnum;
342*d4a07e70Sfengbojiang     uint32_t quote_flags;
343*d4a07e70Sfengbojiang     leaf_t *lp;
344*d4a07e70Sfengbojiang 
345*d4a07e70Sfengbojiang     /* If we have no data, then don't bother */
346*d4a07e70Sfengbojiang     if (csv->c_leaf_depth == 0)
347*d4a07e70Sfengbojiang 	return;
348*d4a07e70Sfengbojiang 
349*d4a07e70Sfengbojiang     if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {
350*d4a07e70Sfengbojiang 	csv->c_flags |= CF_HEADER_DONE;
351*d4a07e70Sfengbojiang 
352*d4a07e70Sfengbojiang 	for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
353*d4a07e70Sfengbojiang 	    lp = &csv->c_leaf[fnum];
354*d4a07e70Sfengbojiang 	    const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);
355*d4a07e70Sfengbojiang 
356*d4a07e70Sfengbojiang 	    if (fnum != 0)
357*d4a07e70Sfengbojiang 		xo_buf_append(&csv->c_data, ",", 1);
358*d4a07e70Sfengbojiang 
359*d4a07e70Sfengbojiang 	    xo_buf_append(&csv->c_data, name, strlen(name));
360*d4a07e70Sfengbojiang 	}
361*d4a07e70Sfengbojiang 
362*d4a07e70Sfengbojiang 	csv_append_newline(&csv->c_data, csv);
363*d4a07e70Sfengbojiang     }
364*d4a07e70Sfengbojiang 
365*d4a07e70Sfengbojiang     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
366*d4a07e70Sfengbojiang 	lp = &csv->c_leaf[fnum];
367*d4a07e70Sfengbojiang 	const char *value;
368*d4a07e70Sfengbojiang 
369*d4a07e70Sfengbojiang 	if (lp->f_flags & LF_HAS_VALUE) {
370*d4a07e70Sfengbojiang 	    value = xo_buf_data(&csv->c_value_buf, lp->f_value);
371*d4a07e70Sfengbojiang 	} else {
372*d4a07e70Sfengbojiang 	    value = "";
373*d4a07e70Sfengbojiang 	}
374*d4a07e70Sfengbojiang 
375*d4a07e70Sfengbojiang 	quote_flags = csv_quote_flags(xop, csv, value);
376*d4a07e70Sfengbojiang 
377*d4a07e70Sfengbojiang 	if (fnum != 0)
378*d4a07e70Sfengbojiang 	    xo_buf_append(&csv->c_data, ",", 1);
379*d4a07e70Sfengbojiang 
380*d4a07e70Sfengbojiang 	if (quote_flags & QF_NEEDS_QUOTES)
381*d4a07e70Sfengbojiang 	    xo_buf_append(&csv->c_data, "\"", 1);
382*d4a07e70Sfengbojiang 
383*d4a07e70Sfengbojiang 	if (quote_flags & QF_NEEDS_ESCAPE)
384*d4a07e70Sfengbojiang 	    csv_escape(&csv->c_data, value, strlen(value));
385*d4a07e70Sfengbojiang 	else
386*d4a07e70Sfengbojiang 	    xo_buf_append(&csv->c_data, value, strlen(value));
387*d4a07e70Sfengbojiang 
388*d4a07e70Sfengbojiang 	if (quote_flags & QF_NEEDS_QUOTES)
389*d4a07e70Sfengbojiang 	    xo_buf_append(&csv->c_data, "\"", 1);
390*d4a07e70Sfengbojiang     }
391*d4a07e70Sfengbojiang 
392*d4a07e70Sfengbojiang     csv_append_newline(&csv->c_data, csv);
393*d4a07e70Sfengbojiang 
394*d4a07e70Sfengbojiang     /* We flush if either flush flag is set */
395*d4a07e70Sfengbojiang     if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))
396*d4a07e70Sfengbojiang 	xo_flush_h(xop);
397*d4a07e70Sfengbojiang 
398*d4a07e70Sfengbojiang     /* Clean out values from leafs */
399*d4a07e70Sfengbojiang     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
400*d4a07e70Sfengbojiang 	lp = &csv->c_leaf[fnum];
401*d4a07e70Sfengbojiang 
402*d4a07e70Sfengbojiang 	lp->f_flags &= ~LF_HAS_VALUE;
403*d4a07e70Sfengbojiang 	lp->f_value = 0;
404*d4a07e70Sfengbojiang     }
405*d4a07e70Sfengbojiang 
406*d4a07e70Sfengbojiang     xo_buf_reset(&csv->c_value_buf);
407*d4a07e70Sfengbojiang 
408*d4a07e70Sfengbojiang     /*
409*d4a07e70Sfengbojiang      * Once we emit the first line, our set of leafs is locked and
410*d4a07e70Sfengbojiang      * cannot be changed.
411*d4a07e70Sfengbojiang      */
412*d4a07e70Sfengbojiang     csv->c_flags |= CF_LEAFS_DONE;
413*d4a07e70Sfengbojiang }
414*d4a07e70Sfengbojiang 
415*d4a07e70Sfengbojiang /*
416*d4a07e70Sfengbojiang  * Open a "level" of hierarchy, either a container or an instance.  Look
417*d4a07e70Sfengbojiang  * for a match in the path=x/y/z hierarchy, and ignore if not a match.
418*d4a07e70Sfengbojiang  * If we're at the end of the path, start recording leaf values.
419*d4a07e70Sfengbojiang  */
420*d4a07e70Sfengbojiang static int
csv_open_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,int instance)421*d4a07e70Sfengbojiang csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,
422*d4a07e70Sfengbojiang 		const char *name, int instance)
423*d4a07e70Sfengbojiang {
424*d4a07e70Sfengbojiang     /* An new "open" event means we stop recording */
425*d4a07e70Sfengbojiang     if (csv->c_flags & CF_RECORD_DATA) {
426*d4a07e70Sfengbojiang 	csv->c_flags &= ~CF_RECORD_DATA;
427*d4a07e70Sfengbojiang 	csv_emit_record(xop, csv);
428*d4a07e70Sfengbojiang 	return 0;
429*d4a07e70Sfengbojiang     }
430*d4a07e70Sfengbojiang 
431*d4a07e70Sfengbojiang     const char *path_top = csv_path_top(csv, 0);
432*d4a07e70Sfengbojiang 
433*d4a07e70Sfengbojiang     /* If the top of the stack does not match the name, then ignore */
434*d4a07e70Sfengbojiang     if (path_top == NULL) {
435*d4a07e70Sfengbojiang 	if (instance && !(csv->c_flags & CF_HAS_PATH)) {
436*d4a07e70Sfengbojiang 	    csv_dbg(xop, csv, "csv: recording (no-path) ...\n");
437*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_RECORD_DATA;
438*d4a07e70Sfengbojiang 	}
439*d4a07e70Sfengbojiang 
440*d4a07e70Sfengbojiang     } else if (xo_streq(path_top, name)) {
441*d4a07e70Sfengbojiang 	csv->c_path_cur += 1;		/* Advance to next path member */
442*d4a07e70Sfengbojiang 
443*d4a07e70Sfengbojiang 	csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,
444*d4a07e70Sfengbojiang 	       csv->c_path_cur, csv->c_path_max);
445*d4a07e70Sfengbojiang 
446*d4a07e70Sfengbojiang 	/* If we're all the way thru the path members, start recording */
447*d4a07e70Sfengbojiang 	if (csv->c_path_cur == csv->c_path_max) {
448*d4a07e70Sfengbojiang 	    csv_dbg(xop, csv, "csv: recording ...\n");
449*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_RECORD_DATA;
450*d4a07e70Sfengbojiang 	}
451*d4a07e70Sfengbojiang     }
452*d4a07e70Sfengbojiang 
453*d4a07e70Sfengbojiang     /* Push the name on the stack */
454*d4a07e70Sfengbojiang     csv_stack_push(csv, name);
455*d4a07e70Sfengbojiang 
456*d4a07e70Sfengbojiang     return 0;
457*d4a07e70Sfengbojiang }
458*d4a07e70Sfengbojiang 
459*d4a07e70Sfengbojiang /*
460*d4a07e70Sfengbojiang  * Close a "level", either a container or an instance.
461*d4a07e70Sfengbojiang  */
462*d4a07e70Sfengbojiang static int
csv_close_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name)463*d4a07e70Sfengbojiang csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)
464*d4a07e70Sfengbojiang {
465*d4a07e70Sfengbojiang     /* If we're recording, a close triggers an emit */
466*d4a07e70Sfengbojiang     if (csv->c_flags & CF_RECORD_DATA) {
467*d4a07e70Sfengbojiang 	csv->c_flags &= ~CF_RECORD_DATA;
468*d4a07e70Sfengbojiang 	csv_emit_record(xop, csv);
469*d4a07e70Sfengbojiang     }
470*d4a07e70Sfengbojiang 
471*d4a07e70Sfengbojiang     const char *path_top = csv_path_top(csv, -1);
472*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,
473*d4a07e70Sfengbojiang 	   path_top ?: "", csv->c_path_cur);
474*d4a07e70Sfengbojiang 
475*d4a07e70Sfengbojiang     /* If the top of the stack does not match the name, then ignore */
476*d4a07e70Sfengbojiang     if (path_top != NULL && xo_streq(path_top, name)) {
477*d4a07e70Sfengbojiang 	csv->c_path_cur -= 1;
478*d4a07e70Sfengbojiang 	return 0;
479*d4a07e70Sfengbojiang     }
480*d4a07e70Sfengbojiang 
481*d4a07e70Sfengbojiang     /* Pop the name off the stack */
482*d4a07e70Sfengbojiang     csv_stack_pop(csv, name);
483*d4a07e70Sfengbojiang 
484*d4a07e70Sfengbojiang     return 0;
485*d4a07e70Sfengbojiang }
486*d4a07e70Sfengbojiang 
487*d4a07e70Sfengbojiang /*
488*d4a07e70Sfengbojiang  * Return the index of a given leaf in the c_leaf[] array, where we
489*d4a07e70Sfengbojiang  * record leaf values.  If the leaf is new and we haven't stopped recording
490*d4a07e70Sfengbojiang  * leafs, then make a new slot for it and record the name.
491*d4a07e70Sfengbojiang  */
492*d4a07e70Sfengbojiang static int
csv_leaf_num(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,xo_xff_flags_t flags)493*d4a07e70Sfengbojiang csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,
494*d4a07e70Sfengbojiang 	       const char *name, xo_xff_flags_t flags)
495*d4a07e70Sfengbojiang {
496*d4a07e70Sfengbojiang     ssize_t fnum;
497*d4a07e70Sfengbojiang     leaf_t *lp;
498*d4a07e70Sfengbojiang     xo_buffer_t *xbp = &csv->c_name_buf;
499*d4a07e70Sfengbojiang 
500*d4a07e70Sfengbojiang     for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
501*d4a07e70Sfengbojiang 	lp = &csv->c_leaf[fnum];
502*d4a07e70Sfengbojiang 
503*d4a07e70Sfengbojiang 	const char *fname = xo_buf_data(xbp, lp->f_name);
504*d4a07e70Sfengbojiang 	if (xo_streq(fname, name))
505*d4a07e70Sfengbojiang 	    return fnum;
506*d4a07e70Sfengbojiang     }
507*d4a07e70Sfengbojiang 
508*d4a07e70Sfengbojiang     /* If we're done with adding new leafs, then bail */
509*d4a07e70Sfengbojiang     if (csv->c_flags & CF_LEAFS_DONE)
510*d4a07e70Sfengbojiang 	return -1;
511*d4a07e70Sfengbojiang 
512*d4a07e70Sfengbojiang     /* This leaf does not exist yet, so we need to create it */
513*d4a07e70Sfengbojiang     /* Start by checking if there's enough room */
514*d4a07e70Sfengbojiang     if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {
515*d4a07e70Sfengbojiang 	/* Out of room; realloc it */
516*d4a07e70Sfengbojiang 	ssize_t new_max = csv->c_leaf_max * 2;
517*d4a07e70Sfengbojiang 	if (new_max == 0)
518*d4a07e70Sfengbojiang 	    new_max = C_LEAF_MAX;
519*d4a07e70Sfengbojiang 
520*d4a07e70Sfengbojiang 	lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));
521*d4a07e70Sfengbojiang 	if (lp == NULL)
522*d4a07e70Sfengbojiang 	    return -1;			/* No luck; bail */
523*d4a07e70Sfengbojiang 
524*d4a07e70Sfengbojiang 	/* Zero out the new portion */
525*d4a07e70Sfengbojiang 	bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));
526*d4a07e70Sfengbojiang 
527*d4a07e70Sfengbojiang 	/* Update csv data */
528*d4a07e70Sfengbojiang 	csv->c_leaf = lp;
529*d4a07e70Sfengbojiang 	csv->c_leaf_max = new_max;
530*d4a07e70Sfengbojiang     }
531*d4a07e70Sfengbojiang 
532*d4a07e70Sfengbojiang     lp = &csv->c_leaf[csv->c_leaf_depth++];
533*d4a07e70Sfengbojiang #ifdef CSV_STACK_IS_NEEDED
534*d4a07e70Sfengbojiang     lp->f_depth = csv->c_stack_depth;
535*d4a07e70Sfengbojiang #endif /* CSV_STACK_IS_NEEDED */
536*d4a07e70Sfengbojiang 
537*d4a07e70Sfengbojiang     lp->f_name = xo_buf_offset(xbp);
538*d4a07e70Sfengbojiang 
539*d4a07e70Sfengbojiang     char *cp = xo_buf_cur(xbp);
540*d4a07e70Sfengbojiang     xo_buf_append(xbp, name, strlen(name) + 1);
541*d4a07e70Sfengbojiang 
542*d4a07e70Sfengbojiang     if (flags & XFF_KEY)
543*d4a07e70Sfengbojiang 	lp->f_flags |= LF_KEY;
544*d4a07e70Sfengbojiang 
545*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",
546*d4a07e70Sfengbojiang 	    fnum, name, cp, lp->f_flags);
547*d4a07e70Sfengbojiang 
548*d4a07e70Sfengbojiang     return fnum;
549*d4a07e70Sfengbojiang }
550*d4a07e70Sfengbojiang 
551*d4a07e70Sfengbojiang /*
552*d4a07e70Sfengbojiang  * Record a new value for a leaf
553*d4a07e70Sfengbojiang  */
554*d4a07e70Sfengbojiang static void
csv_leaf_set(xo_handle_t * xop UNUSED,csv_private_t * csv,leaf_t * lp,const char * value)555*d4a07e70Sfengbojiang csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,
556*d4a07e70Sfengbojiang 	       const char *value)
557*d4a07e70Sfengbojiang {
558*d4a07e70Sfengbojiang     xo_buffer_t *xbp = &csv->c_value_buf;
559*d4a07e70Sfengbojiang 
560*d4a07e70Sfengbojiang     lp->f_value = xo_buf_offset(xbp);
561*d4a07e70Sfengbojiang     lp->f_flags |= LF_HAS_VALUE;
562*d4a07e70Sfengbojiang 
563*d4a07e70Sfengbojiang     char *cp = xo_buf_cur(xbp);
564*d4a07e70Sfengbojiang     xo_buf_append(xbp, value, strlen(value) + 1);
565*d4a07e70Sfengbojiang 
566*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",
567*d4a07e70Sfengbojiang 	    value, cp, lp->f_flags);
568*d4a07e70Sfengbojiang }
569*d4a07e70Sfengbojiang 
570*d4a07e70Sfengbojiang /*
571*d4a07e70Sfengbojiang  * Record the requested set of leaf names.  The input should be a set
572*d4a07e70Sfengbojiang  * of leaf names, separated by periods.
573*d4a07e70Sfengbojiang  */
574*d4a07e70Sfengbojiang static int
csv_record_leafs(xo_handle_t * xop,csv_private_t * csv,const char * leafs_raw)575*d4a07e70Sfengbojiang csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)
576*d4a07e70Sfengbojiang {
577*d4a07e70Sfengbojiang     char *cp, *ep, *np;
578*d4a07e70Sfengbojiang     ssize_t len = strlen(leafs_raw);
579*d4a07e70Sfengbojiang     char *leafs_buf = alloca(len + 1);
580*d4a07e70Sfengbojiang 
581*d4a07e70Sfengbojiang     memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */
582*d4a07e70Sfengbojiang 
583*d4a07e70Sfengbojiang     for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {
584*d4a07e70Sfengbojiang 	np = strchr(cp, '.');
585*d4a07e70Sfengbojiang 	if (np)
586*d4a07e70Sfengbojiang 	    *np++ = '\0';
587*d4a07e70Sfengbojiang 
588*d4a07e70Sfengbojiang 	if (*cp == '\0')		/* Skip empty names */
589*d4a07e70Sfengbojiang 	    continue;
590*d4a07e70Sfengbojiang 
591*d4a07e70Sfengbojiang 	csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);
592*d4a07e70Sfengbojiang 	csv_leaf_num(xop, csv, cp, 0);
593*d4a07e70Sfengbojiang     }
594*d4a07e70Sfengbojiang 
595*d4a07e70Sfengbojiang     /*
596*d4a07e70Sfengbojiang      * Since we've been told explicitly what leafs matter, ignore the rest
597*d4a07e70Sfengbojiang      */
598*d4a07e70Sfengbojiang     csv->c_flags |= CF_LEAFS_DONE;
599*d4a07e70Sfengbojiang 
600*d4a07e70Sfengbojiang     return 0;
601*d4a07e70Sfengbojiang }
602*d4a07e70Sfengbojiang 
603*d4a07e70Sfengbojiang /*
604*d4a07e70Sfengbojiang  * Record the requested path elements.  The input should be a set of
605*d4a07e70Sfengbojiang  * container or instances names, separated by slashes.
606*d4a07e70Sfengbojiang  */
607*d4a07e70Sfengbojiang static int
csv_record_path(xo_handle_t * xop,csv_private_t * csv,const char * path_raw)608*d4a07e70Sfengbojiang csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)
609*d4a07e70Sfengbojiang {
610*d4a07e70Sfengbojiang     int count;
611*d4a07e70Sfengbojiang     char *cp, *ep, *np;
612*d4a07e70Sfengbojiang     ssize_t len = strlen(path_raw);
613*d4a07e70Sfengbojiang     char *path_buf = xo_realloc(NULL, len + 1);
614*d4a07e70Sfengbojiang 
615*d4a07e70Sfengbojiang     memcpy(path_buf, path_raw, len + 1);
616*d4a07e70Sfengbojiang 
617*d4a07e70Sfengbojiang     for (cp = path_buf, ep = path_buf + len, count = 2;
618*d4a07e70Sfengbojiang 	 cp && cp < ep; cp = np) {
619*d4a07e70Sfengbojiang 	np = strchr(cp, '/');
620*d4a07e70Sfengbojiang 	if (np) {
621*d4a07e70Sfengbojiang 	    np += 1;
622*d4a07e70Sfengbojiang 	    count += 1;
623*d4a07e70Sfengbojiang 	}
624*d4a07e70Sfengbojiang     }
625*d4a07e70Sfengbojiang 
626*d4a07e70Sfengbojiang     path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);
627*d4a07e70Sfengbojiang     if (path == NULL) {
628*d4a07e70Sfengbojiang 	xo_failure(xop, "allocation failure for path '%s'", path_buf);
629*d4a07e70Sfengbojiang 	return -1;
630*d4a07e70Sfengbojiang     }
631*d4a07e70Sfengbojiang 
632*d4a07e70Sfengbojiang     bzero(path, sizeof(path[0]) * count);
633*d4a07e70Sfengbojiang 
634*d4a07e70Sfengbojiang     for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {
635*d4a07e70Sfengbojiang 	path[count++].pf_name = cp;
636*d4a07e70Sfengbojiang 
637*d4a07e70Sfengbojiang 	np = strchr(cp, '/');
638*d4a07e70Sfengbojiang 	if (np)
639*d4a07e70Sfengbojiang 	    *np++ = '\0';
640*d4a07e70Sfengbojiang 	csv_dbg(xop, csv, "path: [%s]\n", cp);
641*d4a07e70Sfengbojiang     }
642*d4a07e70Sfengbojiang 
643*d4a07e70Sfengbojiang     path[count].pf_name = NULL;
644*d4a07e70Sfengbojiang 
645*d4a07e70Sfengbojiang     if (csv->c_path)		     /* In case two paths are given */
646*d4a07e70Sfengbojiang 	xo_free(csv->c_path);
647*d4a07e70Sfengbojiang     if (csv->c_path_buf)	     /* In case two paths are given */
648*d4a07e70Sfengbojiang 	xo_free(csv->c_path_buf);
649*d4a07e70Sfengbojiang 
650*d4a07e70Sfengbojiang     csv->c_path_buf = path_buf;
651*d4a07e70Sfengbojiang     csv->c_path = path;
652*d4a07e70Sfengbojiang     csv->c_path_max = count;
653*d4a07e70Sfengbojiang     csv->c_path_cur = 0;
654*d4a07e70Sfengbojiang 
655*d4a07e70Sfengbojiang     return 0;
656*d4a07e70Sfengbojiang }
657*d4a07e70Sfengbojiang 
658*d4a07e70Sfengbojiang /*
659*d4a07e70Sfengbojiang  * Extract the option values.  The format is:
660*d4a07e70Sfengbojiang  *    -libxo encoder=csv:kw=val:kw=val:kw=val,pretty
661*d4a07e70Sfengbojiang  *    -libxo encoder=csv+kw=val+kw=val+kw=val,pretty
662*d4a07e70Sfengbojiang  */
663*d4a07e70Sfengbojiang static int
csv_options(xo_handle_t * xop,csv_private_t * csv,const char * raw_opts,char opts_char)664*d4a07e70Sfengbojiang csv_options (xo_handle_t *xop, csv_private_t *csv,
665*d4a07e70Sfengbojiang 	     const char *raw_opts, char opts_char)
666*d4a07e70Sfengbojiang {
667*d4a07e70Sfengbojiang     ssize_t len = strlen(raw_opts);
668*d4a07e70Sfengbojiang     char *options = alloca(len + 1);
669*d4a07e70Sfengbojiang     memcpy(options, raw_opts, len);
670*d4a07e70Sfengbojiang     options[len] = '\0';
671*d4a07e70Sfengbojiang 
672*d4a07e70Sfengbojiang     char *cp, *ep, *np, *vp;
673*d4a07e70Sfengbojiang     for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {
674*d4a07e70Sfengbojiang 	np = strchr(cp, opts_char);
675*d4a07e70Sfengbojiang 	if (np)
676*d4a07e70Sfengbojiang 	    *np++ = '\0';
677*d4a07e70Sfengbojiang 
678*d4a07e70Sfengbojiang 	vp = strchr(cp, '=');
679*d4a07e70Sfengbojiang 	if (vp)
680*d4a07e70Sfengbojiang 	    *vp++ = '\0';
681*d4a07e70Sfengbojiang 
682*d4a07e70Sfengbojiang 	if (xo_streq(cp, "path")) {
683*d4a07e70Sfengbojiang 	    /* Record the path */
684*d4a07e70Sfengbojiang 	    if (vp != NULL && csv_record_path(xop, csv, vp))
685*d4a07e70Sfengbojiang   		return -1;
686*d4a07e70Sfengbojiang 
687*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */
688*d4a07e70Sfengbojiang 
689*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "leafs")
690*d4a07e70Sfengbojiang 		   || xo_streq(cp, "leaf")
691*d4a07e70Sfengbojiang 		   || xo_streq(cp, "leaves")) {
692*d4a07e70Sfengbojiang 	    /* Record the leafs */
693*d4a07e70Sfengbojiang 	    if (vp != NULL && csv_record_leafs(xop, csv, vp))
694*d4a07e70Sfengbojiang   		return -1;
695*d4a07e70Sfengbojiang 
696*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "no-keys")) {
697*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_NO_KEYS;
698*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "no-header")) {
699*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_NO_HEADER;
700*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "value-only")) {
701*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_VALUE_ONLY;
702*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "dos")) {
703*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_DOS_NEWLINE;
704*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "no-quotes")) {
705*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_NO_QUOTES;
706*d4a07e70Sfengbojiang 	} else if (xo_streq(cp, "debug")) {
707*d4a07e70Sfengbojiang 	    csv->c_flags |= CF_DEBUG;
708*d4a07e70Sfengbojiang 	} else {
709*d4a07e70Sfengbojiang 	    xo_warn_hc(xop, -1,
710*d4a07e70Sfengbojiang 		       "unknown encoder option value: '%s'", cp);
711*d4a07e70Sfengbojiang 	    return -1;
712*d4a07e70Sfengbojiang 	}
713*d4a07e70Sfengbojiang     }
714*d4a07e70Sfengbojiang 
715*d4a07e70Sfengbojiang     return 0;
716*d4a07e70Sfengbojiang }
717*d4a07e70Sfengbojiang 
718*d4a07e70Sfengbojiang /*
719*d4a07e70Sfengbojiang  * Handler for incoming data values.  We just record each leaf name and
720*d4a07e70Sfengbojiang  * value.  The values are emittd when the instance is closed.
721*d4a07e70Sfengbojiang  */
722*d4a07e70Sfengbojiang 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)723*d4a07e70Sfengbojiang csv_data (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
724*d4a07e70Sfengbojiang 	  const char *name, const char *value,
725*d4a07e70Sfengbojiang 	  xo_xof_flags_t flags)
726*d4a07e70Sfengbojiang {
727*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);
728*d4a07e70Sfengbojiang 
729*d4a07e70Sfengbojiang     if (!(csv->c_flags & CF_RECORD_DATA))
730*d4a07e70Sfengbojiang 	return 0;
731*d4a07e70Sfengbojiang 
732*d4a07e70Sfengbojiang     /* Find the leaf number */
733*d4a07e70Sfengbojiang     int fnum = csv_leaf_num(xop, csv, name, flags);
734*d4a07e70Sfengbojiang     if (fnum < 0)
735*d4a07e70Sfengbojiang 	return 0;			/* Don't bother recording */
736*d4a07e70Sfengbojiang 
737*d4a07e70Sfengbojiang     leaf_t *lp = &csv->c_leaf[fnum];
738*d4a07e70Sfengbojiang     csv_leaf_set(xop, csv, lp, value);
739*d4a07e70Sfengbojiang 
740*d4a07e70Sfengbojiang     return 0;
741*d4a07e70Sfengbojiang }
742*d4a07e70Sfengbojiang 
743*d4a07e70Sfengbojiang /*
744*d4a07e70Sfengbojiang  * The callback from libxo, passing us operations/events as they
745*d4a07e70Sfengbojiang  * happen.
746*d4a07e70Sfengbojiang  */
747*d4a07e70Sfengbojiang static int
csv_handler(XO_ENCODER_HANDLER_ARGS)748*d4a07e70Sfengbojiang csv_handler (XO_ENCODER_HANDLER_ARGS)
749*d4a07e70Sfengbojiang {
750*d4a07e70Sfengbojiang     int rc = 0;
751*d4a07e70Sfengbojiang     csv_private_t *csv = private;
752*d4a07e70Sfengbojiang     xo_buffer_t *xbp = csv ? &csv->c_data : NULL;
753*d4a07e70Sfengbojiang 
754*d4a07e70Sfengbojiang     csv_dbg(xop, csv, "op %s: [%s] [%s]\n",  xo_encoder_op_name(op),
755*d4a07e70Sfengbojiang 	   name ?: "", value ?: "");
756*d4a07e70Sfengbojiang     fflush(stdout);
757*d4a07e70Sfengbojiang 
758*d4a07e70Sfengbojiang     /* If we don't have private data, we're sunk */
759*d4a07e70Sfengbojiang     if (csv == NULL && op != XO_OP_CREATE)
760*d4a07e70Sfengbojiang 	return -1;
761*d4a07e70Sfengbojiang 
762*d4a07e70Sfengbojiang     switch (op) {
763*d4a07e70Sfengbojiang     case XO_OP_CREATE:		/* Called when the handle is init'd */
764*d4a07e70Sfengbojiang 	rc = csv_create(xop);
765*d4a07e70Sfengbojiang 	break;
766*d4a07e70Sfengbojiang 
767*d4a07e70Sfengbojiang     case XO_OP_OPTIONS:
768*d4a07e70Sfengbojiang 	rc = csv_options(xop, csv, value, ':');
769*d4a07e70Sfengbojiang 	break;
770*d4a07e70Sfengbojiang 
771*d4a07e70Sfengbojiang     case XO_OP_OPTIONS_PLUS:
772*d4a07e70Sfengbojiang 	rc = csv_options(xop, csv, value, '+');
773*d4a07e70Sfengbojiang 	break;
774*d4a07e70Sfengbojiang 
775*d4a07e70Sfengbojiang     case XO_OP_OPEN_LIST:
776*d4a07e70Sfengbojiang     case XO_OP_CLOSE_LIST:
777*d4a07e70Sfengbojiang 	break;				/* Ignore these ops */
778*d4a07e70Sfengbojiang 
779*d4a07e70Sfengbojiang     case XO_OP_OPEN_CONTAINER:
780*d4a07e70Sfengbojiang     case XO_OP_OPEN_LEAF_LIST:
781*d4a07e70Sfengbojiang 	rc = csv_open_level(xop, csv, name, 0);
782*d4a07e70Sfengbojiang 	break;
783*d4a07e70Sfengbojiang 
784*d4a07e70Sfengbojiang     case XO_OP_OPEN_INSTANCE:
785*d4a07e70Sfengbojiang 	rc = csv_open_level(xop, csv, name, 1);
786*d4a07e70Sfengbojiang 	break;
787*d4a07e70Sfengbojiang 
788*d4a07e70Sfengbojiang     case XO_OP_CLOSE_CONTAINER:
789*d4a07e70Sfengbojiang     case XO_OP_CLOSE_LEAF_LIST:
790*d4a07e70Sfengbojiang     case XO_OP_CLOSE_INSTANCE:
791*d4a07e70Sfengbojiang 	rc = csv_close_level(xop, csv, name);
792*d4a07e70Sfengbojiang 	break;
793*d4a07e70Sfengbojiang 
794*d4a07e70Sfengbojiang     case XO_OP_STRING:		   /* Quoted UTF-8 string */
795*d4a07e70Sfengbojiang     case XO_OP_CONTENT:		   /* Other content */
796*d4a07e70Sfengbojiang 	rc = csv_data(xop, csv, name, value, flags);
797*d4a07e70Sfengbojiang 	break;
798*d4a07e70Sfengbojiang 
799*d4a07e70Sfengbojiang     case XO_OP_FINISH:		   /* Clean up function */
800*d4a07e70Sfengbojiang 	break;
801*d4a07e70Sfengbojiang 
802*d4a07e70Sfengbojiang     case XO_OP_FLUSH:		   /* Clean up function */
803*d4a07e70Sfengbojiang 	rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
804*d4a07e70Sfengbojiang 	if (rc > 0)
805*d4a07e70Sfengbojiang 	    rc = 0;
806*d4a07e70Sfengbojiang 
807*d4a07e70Sfengbojiang 	xo_buf_reset(xbp);
808*d4a07e70Sfengbojiang 	break;
809*d4a07e70Sfengbojiang 
810*d4a07e70Sfengbojiang     case XO_OP_DESTROY:		   /* Clean up function */
811*d4a07e70Sfengbojiang 	csv_destroy(xop, csv);
812*d4a07e70Sfengbojiang 	break;
813*d4a07e70Sfengbojiang 
814*d4a07e70Sfengbojiang     case XO_OP_ATTRIBUTE:	   /* Attribute name/value */
815*d4a07e70Sfengbojiang 	break;
816*d4a07e70Sfengbojiang 
817*d4a07e70Sfengbojiang     case XO_OP_VERSION:		/* Version string */
818*d4a07e70Sfengbojiang 	break;
819*d4a07e70Sfengbojiang     }
820*d4a07e70Sfengbojiang 
821*d4a07e70Sfengbojiang     return rc;
822*d4a07e70Sfengbojiang }
823*d4a07e70Sfengbojiang 
824*d4a07e70Sfengbojiang /*
825*d4a07e70Sfengbojiang  * Callback when our encoder is loaded.
826*d4a07e70Sfengbojiang  */
827*d4a07e70Sfengbojiang int
xo_encoder_library_init(XO_ENCODER_INIT_ARGS)828*d4a07e70Sfengbojiang xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
829*d4a07e70Sfengbojiang {
830*d4a07e70Sfengbojiang     arg->xei_handler = csv_handler;
831*d4a07e70Sfengbojiang     arg->xei_version = XO_ENCODER_VERSION;
832*d4a07e70Sfengbojiang 
833*d4a07e70Sfengbojiang     return 0;
834*d4a07e70Sfengbojiang }
835