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 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 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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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