Path: blob/main/contrib/libxo/encoder/csv/enc_csv.c
48254 views
/*1* Copyright (c) 2015, Juniper Networks, Inc.2* All rights reserved.3* This SOFTWARE is licensed under the LICENSE provided in the4* ../Copyright file. By downloading, installing, copying, or otherwise5* using the SOFTWARE, you agree to be bound by the terms of that6* LICENSE.7* Phil Shafer, August 20158*/910/*11* CSV encoder generates comma-separated value files for specific12* subsets of data. This is not (and cannot be) a generalized13* facility, but for specific subsets of data, CSV data can be14* reasonably generated. For example, the df XML content:15* <filesystem>16* <name>procfs</name>17* <total-blocks>4</total-blocks>18* <used-blocks>4</used-blocks>19* <available-blocks>0</available-blocks>20* <used-percent>100</used-percent>21* <mounted-on>/proc</mounted-on>22* </filesystem>23*24* could be represented as:25*26* #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on27* procfs,4,4,0,100,/proc28*29* Data is then constrained to be sibling leaf values. In addition,30* singular leafs can also be matched. The costs include recording31* the specific leaf names (to ensure consistency) and some32* buffering.33*34* Some escaping is needed for CSV files, following the rules of RFC4180:35*36* - Fields containing a line-break, double-quote or commas should be37* quoted. (If they are not, the file will likely be impossible to38* process correctly).39* - A (double) quote character in a field must be represented by two40* (double) quote characters.41* - Leading and trialing whitespace require fields be quoted.42*43* Cheesy, but simple. The RFC also requires MS-DOS end-of-line,44* which we only do with the "dos" option. Strange that we still live45* in a DOS-friendly world, but then again, we make spaceships based46* on the horse butts (http://www.astrodigital.org/space/stshorse.html47* though the "built by English expatriates” bit is rubbish; better to48* say the first engines used in America were built by Englishmen.)49*/5051#include <string.h>52#include <sys/types.h>53#include <unistd.h>54#include <stdint.h>55#include <ctype.h>56#include <stdlib.h>57#include <limits.h>5859#include "xo.h"60#include "xo_encoder.h"61#include "xo_buf.h"6263#ifndef UNUSED64#define UNUSED __attribute__ ((__unused__))65#endif /* UNUSED */6667/*68* The CSV encoder has three moving parts:69*70* - The path holds the path we are matching against71* - This is given as input via "options" and does not change72*73* - The stack holds the current names of the open elements74* - The "open" operations push, while the "close" pop75* - Turns out, at this point, the stack is unused, but I've76* left "drippings" in the code because I see this as useful77* for future features (under CSV_STACK_IS_NEEDED).78*79* - The leafs record the current set of leaf80* - A key from the parent list counts as a leaf (unless CF_NO_KEYS)81* - Once the path is matched, all other leafs at that level are leafs82* - Leafs are recorded to get the header comment accurately recorded83* - Once the first line is emited, the set of leafs _cannot_ change84*85* We use offsets into the buffers, since we know they can be86* realloc'd out from under us, as the size increases. The 'path'87* is fixed, we allocate it once, so it doesn't need offsets.88*/89typedef struct path_frame_s {90char *pf_name; /* Path member name; points into c_path_buf */91uint32_t pf_flags; /* Flags for this path element (PFF_*) */92} path_frame_t;9394typedef struct stack_frame_s {95ssize_t sf_off; /* Element name; offset in c_stack_buf */96uint32_t sf_flags; /* Flags for this frame (SFF_*) */97} stack_frame_t;9899/* Flags for sf_flags */100101typedef struct leaf_s {102ssize_t f_name; /* Name of leaf; offset in c_name_buf */103ssize_t f_value; /* Value of leaf; offset in c_value_buf */104uint32_t f_flags; /* Flags for this value (FF_*) */105#ifdef CSV_STACK_IS_NEEDED106ssize_t f_depth; /* Depth of stack when leaf was recorded */107#endif /* CSV_STACK_IS_NEEDED */108} leaf_t;109110/* Flags for f_flags */111#define LF_KEY (1<<0) /* Leaf is a key */112#define LF_HAS_VALUE (1<<1) /* Value has been set */113114typedef struct csv_private_s {115uint32_t c_flags; /* Flags for this encoder */116117/* The path for which we select leafs */118char *c_path_buf; /* Buffer containing path members */119path_frame_t *c_path; /* Array of path members */120ssize_t c_path_max; /* Depth of c_path[] */121ssize_t c_path_cur; /* Current depth in c_path[] */122123/* A stack of open elements (xo_op_list, xo_op_container) */124#if CSV_STACK_IS_NEEDED125xo_buffer_t c_stack_buf; /* Buffer used for stack content */126stack_frame_t *c_stack; /* Stack of open tags */127ssize_t c_stack_max; /* Maximum stack depth */128#endif /* CSV_STACK_IS_NEEDED */129ssize_t c_stack_depth; /* Current stack depth */130131/* List of leafs we are emitting (to ensure consistency) */132xo_buffer_t c_name_buf; /* String buffer for leaf names */133xo_buffer_t c_value_buf; /* String buffer for leaf values */134leaf_t *c_leaf; /* List of leafs */135ssize_t c_leaf_depth; /* Current depth of c_leaf[] (next free) */136ssize_t c_leaf_max; /* Max depth of c_leaf[] */137138xo_buffer_t c_data; /* Buffer for creating data */139} csv_private_t;140141#define C_STACK_MAX 32 /* default c_stack_max */142#define C_LEAF_MAX 32 /* default c_leaf_max */143144/* Flags for this structure */145#define CF_HEADER_DONE (1<<0) /* Have already written the header */146#define CF_NO_HEADER (1<<1) /* Do not generate header */147#define CF_NO_KEYS (1<<2) /* Do not generate excess keys */148#define CF_VALUE_ONLY (1<<3) /* Only generate the value */149150#define CF_DOS_NEWLINE (1<<4) /* Generate CR-NL, just like MS-DOS */151#define CF_LEAFS_DONE (1<<5) /* Leafs are already been recorded */152#define CF_NO_QUOTES (1<<6) /* Do not generate quotes */153#define CF_RECORD_DATA (1<<7) /* Record all sibling leafs */154155#define CF_DEBUG (1<<8) /* Make debug output */156#define CF_HAS_PATH (1<<9) /* A "path" option was provided */157158/*159* A simple debugging print function, similar to psu_dbg. Controlled by160* the undocumented "debug" option.161*/162static void163csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,164const char *fmt, ...)165{166if (csv == NULL || !(csv->c_flags & CF_DEBUG))167return;168169va_list vap;170171va_start(vap, fmt);172vfprintf(stderr, fmt, vap);173va_end(vap);174}175176/*177* Create the private data for this handle, initialize it, and record178* the pointer in the handle.179*/180static int181csv_create (xo_handle_t *xop)182{183csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));184if (csv == NULL)185return -1;186187bzero(csv, sizeof(*csv));188xo_buf_init(&csv->c_data);189xo_buf_init(&csv->c_name_buf);190xo_buf_init(&csv->c_value_buf);191#ifdef CSV_STACK_IS_NEEDED192xo_buf_init(&csv->c_stack_buf);193#endif /* CSV_STACK_IS_NEEDED */194195xo_set_private(xop, csv);196197return 0;198}199200/*201* Clean up and release any data in use by this handle202*/203static void204csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)205{206/* Clean up */207xo_buf_cleanup(&csv->c_data);208xo_buf_cleanup(&csv->c_name_buf);209xo_buf_cleanup(&csv->c_value_buf);210#ifdef CSV_STACK_IS_NEEDED211xo_buf_cleanup(&csv->c_stack_buf);212#endif /* CSV_STACK_IS_NEEDED */213214if (csv->c_leaf)215xo_free(csv->c_leaf);216if (csv->c_path_buf)217xo_free(csv->c_path_buf);218}219220/*221* Return the element name at the top of the path stack. This is the222* item that we are currently trying to match on.223*/224static const char *225csv_path_top (csv_private_t *csv, ssize_t delta)226{227if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)228return NULL;229230ssize_t cur = csv->c_path_cur + delta;231232if (cur < 0)233return NULL;234235return csv->c_path[cur].pf_name;236}237238/*239* Underimplemented stack functionality240*/241static inline void242csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)243{244#ifdef CSV_STACK_IS_NEEDED245csv->c_stack_depth += 1;246#endif /* CSV_STACK_IS_NEEDED */247}248249/*250* Underimplemented stack functionality251*/252static inline void253csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)254{255#ifdef CSV_STACK_IS_NEEDED256csv->c_stack_depth -= 1;257#endif /* CSV_STACK_IS_NEEDED */258}259260/* Flags for csv_quote_flags */261#define QF_NEEDS_QUOTES (1<<0) /* Needs to be quoted */262#define QF_NEEDS_ESCAPE (1<<1) /* Needs to be escaped */263264/*265* Determine how much quote processing is needed. The details of the266* quoting rules are given at the top of this file. We return a set267* of flags, indicating what's needed.268*/269static uint32_t270csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,271const char *value)272{273static const char quoted[] = "\n\r\",";274static const char escaped[] = "\"";275276if (csv->c_flags & CF_NO_QUOTES) /* User doesn't want quotes */277return 0;278279size_t len = strlen(value);280uint32_t rc = 0;281282if (strcspn(value, quoted) != len)283rc |= QF_NEEDS_QUOTES;284else if (isspace((int) value[0])) /* Leading whitespace */285rc |= QF_NEEDS_QUOTES;286else if (isspace((int) value[len - 1])) /* Trailing whitespace */287rc |= QF_NEEDS_QUOTES;288289if (strcspn(value, escaped) != len)290rc |= QF_NEEDS_ESCAPE;291292csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",293value, rc, len, strcspn(value, quoted));294295return rc;296}297298/*299* Escape the string, following the rules in RFC4180300*/301static void302csv_escape (xo_buffer_t *xbp, const char *value, size_t len)303{304const char *cp, *ep, *np;305306for (cp = value, ep = value + len; cp && cp < ep; cp = np) {307np = strchr(cp, '"');308if (np) {309np += 1;310xo_buf_append(xbp, cp, np - cp);311xo_buf_append(xbp, "\"", 1);312} else313xo_buf_append(xbp, cp, ep - cp);314}315}316317/*318* Append a newline to the buffer, following the settings of the "dos"319* flag.320*/321static void322csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)323{324if (csv->c_flags & CF_DOS_NEWLINE)325xo_buf_append(xbp, "\r\n", 2);326else327xo_buf_append(xbp, "\n", 1);328}329330/*331* Create a 'record' of 'fields' from our recorded leaf values. If332* this is the first line and "no-header" isn't given, make a record333* containing the leaf names.334*/335static void336csv_emit_record (xo_handle_t *xop, csv_private_t *csv)337{338csv_dbg(xop, csv, "csv: emit: ...\n");339340ssize_t fnum;341uint32_t quote_flags;342leaf_t *lp;343344/* If we have no data, then don't bother */345if (csv->c_leaf_depth == 0)346return;347348if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {349csv->c_flags |= CF_HEADER_DONE;350351for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {352lp = &csv->c_leaf[fnum];353const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);354355if (fnum != 0)356xo_buf_append(&csv->c_data, ",", 1);357358xo_buf_append(&csv->c_data, name, strlen(name));359}360361csv_append_newline(&csv->c_data, csv);362}363364for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {365lp = &csv->c_leaf[fnum];366const char *value;367368if (lp->f_flags & LF_HAS_VALUE) {369value = xo_buf_data(&csv->c_value_buf, lp->f_value);370} else {371value = "";372}373374quote_flags = csv_quote_flags(xop, csv, value);375376if (fnum != 0)377xo_buf_append(&csv->c_data, ",", 1);378379if (quote_flags & QF_NEEDS_QUOTES)380xo_buf_append(&csv->c_data, "\"", 1);381382if (quote_flags & QF_NEEDS_ESCAPE)383csv_escape(&csv->c_data, value, strlen(value));384else385xo_buf_append(&csv->c_data, value, strlen(value));386387if (quote_flags & QF_NEEDS_QUOTES)388xo_buf_append(&csv->c_data, "\"", 1);389}390391csv_append_newline(&csv->c_data, csv);392393/* We flush if either flush flag is set */394if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))395xo_flush_h(xop);396397/* Clean out values from leafs */398for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {399lp = &csv->c_leaf[fnum];400401lp->f_flags &= ~LF_HAS_VALUE;402lp->f_value = 0;403}404405xo_buf_reset(&csv->c_value_buf);406407/*408* Once we emit the first line, our set of leafs is locked and409* cannot be changed.410*/411csv->c_flags |= CF_LEAFS_DONE;412}413414/*415* Open a "level" of hierarchy, either a container or an instance. Look416* for a match in the path=x/y/z hierarchy, and ignore if not a match.417* If we're at the end of the path, start recording leaf values.418*/419static int420csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,421const char *name, int instance)422{423/* An new "open" event means we stop recording */424if (csv->c_flags & CF_RECORD_DATA) {425csv->c_flags &= ~CF_RECORD_DATA;426csv_emit_record(xop, csv);427return 0;428}429430const char *path_top = csv_path_top(csv, 0);431432/* If the top of the stack does not match the name, then ignore */433if (path_top == NULL) {434if (instance && !(csv->c_flags & CF_HAS_PATH)) {435csv_dbg(xop, csv, "csv: recording (no-path) ...\n");436csv->c_flags |= CF_RECORD_DATA;437}438439} else if (xo_streq(path_top, name)) {440csv->c_path_cur += 1; /* Advance to next path member */441442csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,443csv->c_path_cur, csv->c_path_max);444445/* If we're all the way thru the path members, start recording */446if (csv->c_path_cur == csv->c_path_max) {447csv_dbg(xop, csv, "csv: recording ...\n");448csv->c_flags |= CF_RECORD_DATA;449}450}451452/* Push the name on the stack */453csv_stack_push(csv, name);454455return 0;456}457458/*459* Close a "level", either a container or an instance.460*/461static int462csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)463{464/* If we're recording, a close triggers an emit */465if (csv->c_flags & CF_RECORD_DATA) {466csv->c_flags &= ~CF_RECORD_DATA;467csv_emit_record(xop, csv);468}469470const char *path_top = csv_path_top(csv, -1);471csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,472path_top ?: "", csv->c_path_cur);473474/* If the top of the stack does not match the name, then ignore */475if (path_top != NULL && xo_streq(path_top, name)) {476csv->c_path_cur -= 1;477return 0;478}479480/* Pop the name off the stack */481csv_stack_pop(csv, name);482483return 0;484}485486/*487* Return the index of a given leaf in the c_leaf[] array, where we488* record leaf values. If the leaf is new and we haven't stopped recording489* leafs, then make a new slot for it and record the name.490*/491static int492csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,493const char *name, xo_xff_flags_t flags)494{495ssize_t fnum;496leaf_t *lp;497xo_buffer_t *xbp = &csv->c_name_buf;498499for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {500lp = &csv->c_leaf[fnum];501502const char *fname = xo_buf_data(xbp, lp->f_name);503if (xo_streq(fname, name))504return fnum;505}506507/* If we're done with adding new leafs, then bail */508if (csv->c_flags & CF_LEAFS_DONE)509return -1;510511/* This leaf does not exist yet, so we need to create it */512/* Start by checking if there's enough room */513if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {514/* Out of room; realloc it */515ssize_t new_max = csv->c_leaf_max * 2;516if (new_max == 0)517new_max = C_LEAF_MAX;518519lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));520if (lp == NULL)521return -1; /* No luck; bail */522523/* Zero out the new portion */524bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));525526/* Update csv data */527csv->c_leaf = lp;528csv->c_leaf_max = new_max;529}530531lp = &csv->c_leaf[csv->c_leaf_depth++];532#ifdef CSV_STACK_IS_NEEDED533lp->f_depth = csv->c_stack_depth;534#endif /* CSV_STACK_IS_NEEDED */535536lp->f_name = xo_buf_offset(xbp);537538char *cp = xo_buf_cur(xbp);539xo_buf_append(xbp, name, strlen(name) + 1);540541if (flags & XFF_KEY)542lp->f_flags |= LF_KEY;543544csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",545fnum, name, cp, lp->f_flags);546547return fnum;548}549550/*551* Record a new value for a leaf552*/553static void554csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,555const char *value)556{557xo_buffer_t *xbp = &csv->c_value_buf;558559lp->f_value = xo_buf_offset(xbp);560lp->f_flags |= LF_HAS_VALUE;561562char *cp = xo_buf_cur(xbp);563xo_buf_append(xbp, value, strlen(value) + 1);564565csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",566value, cp, lp->f_flags);567}568569/*570* Record the requested set of leaf names. The input should be a set571* of leaf names, separated by periods.572*/573static int574csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)575{576char *cp, *ep, *np;577ssize_t len = strlen(leafs_raw);578char *leafs_buf = alloca(len + 1);579580memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */581582for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {583np = strchr(cp, '.');584if (np)585*np++ = '\0';586587if (*cp == '\0') /* Skip empty names */588continue;589590csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);591csv_leaf_num(xop, csv, cp, 0);592}593594/*595* Since we've been told explicitly what leafs matter, ignore the rest596*/597csv->c_flags |= CF_LEAFS_DONE;598599return 0;600}601602/*603* Record the requested path elements. The input should be a set of604* container or instances names, separated by slashes.605*/606static int607csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)608{609int count;610char *cp, *ep, *np;611ssize_t len = strlen(path_raw);612char *path_buf = xo_realloc(NULL, len + 1);613614memcpy(path_buf, path_raw, len + 1);615616for (cp = path_buf, ep = path_buf + len, count = 2;617cp && cp < ep; cp = np) {618np = strchr(cp, '/');619if (np) {620np += 1;621count += 1;622}623}624625path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);626if (path == NULL) {627xo_failure(xop, "allocation failure for path '%s'", path_buf);628return -1;629}630631bzero(path, sizeof(path[0]) * count);632633for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {634path[count++].pf_name = cp;635636np = strchr(cp, '/');637if (np)638*np++ = '\0';639csv_dbg(xop, csv, "path: [%s]\n", cp);640}641642path[count].pf_name = NULL;643644if (csv->c_path) /* In case two paths are given */645xo_free(csv->c_path);646if (csv->c_path_buf) /* In case two paths are given */647xo_free(csv->c_path_buf);648649csv->c_path_buf = path_buf;650csv->c_path = path;651csv->c_path_max = count;652csv->c_path_cur = 0;653654return 0;655}656657/*658* Extract the option values. The format is:659* -libxo encoder=csv:kw=val:kw=val:kw=val,pretty660* -libxo encoder=csv+kw=val+kw=val+kw=val,pretty661*/662static int663csv_options (xo_handle_t *xop, csv_private_t *csv,664const char *raw_opts, char opts_char)665{666ssize_t len = strlen(raw_opts);667char *options = alloca(len + 1);668memcpy(options, raw_opts, len);669options[len] = '\0';670671char *cp, *ep, *np, *vp;672for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {673np = strchr(cp, opts_char);674if (np)675*np++ = '\0';676677vp = strchr(cp, '=');678if (vp)679*vp++ = '\0';680681if (xo_streq(cp, "path")) {682/* Record the path */683if (vp != NULL && csv_record_path(xop, csv, vp))684return -1;685686csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */687688} else if (xo_streq(cp, "leafs")689|| xo_streq(cp, "leaf")690|| xo_streq(cp, "leaves")) {691/* Record the leafs */692if (vp != NULL && csv_record_leafs(xop, csv, vp))693return -1;694695} else if (xo_streq(cp, "no-keys")) {696csv->c_flags |= CF_NO_KEYS;697} else if (xo_streq(cp, "no-header")) {698csv->c_flags |= CF_NO_HEADER;699} else if (xo_streq(cp, "value-only")) {700csv->c_flags |= CF_VALUE_ONLY;701} else if (xo_streq(cp, "dos")) {702csv->c_flags |= CF_DOS_NEWLINE;703} else if (xo_streq(cp, "no-quotes")) {704csv->c_flags |= CF_NO_QUOTES;705} else if (xo_streq(cp, "debug")) {706csv->c_flags |= CF_DEBUG;707} else {708xo_warn_hc(xop, -1,709"unknown encoder option value: '%s'", cp);710return -1;711}712}713714return 0;715}716717/*718* Handler for incoming data values. We just record each leaf name and719* value. The values are emittd when the instance is closed.720*/721static int722csv_data (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,723const char *name, const char *value,724xo_xof_flags_t flags)725{726csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);727728if (!(csv->c_flags & CF_RECORD_DATA))729return 0;730731/* Find the leaf number */732int fnum = csv_leaf_num(xop, csv, name, flags);733if (fnum < 0)734return 0; /* Don't bother recording */735736leaf_t *lp = &csv->c_leaf[fnum];737csv_leaf_set(xop, csv, lp, value);738739return 0;740}741742/*743* The callback from libxo, passing us operations/events as they744* happen.745*/746static int747csv_handler (XO_ENCODER_HANDLER_ARGS)748{749int rc = 0;750csv_private_t *csv = private;751xo_buffer_t *xbp = csv ? &csv->c_data : NULL;752753csv_dbg(xop, csv, "op %s: [%s] [%s]\n", xo_encoder_op_name(op),754name ?: "", value ?: "");755fflush(stdout);756757/* If we don't have private data, we're sunk */758if (csv == NULL && op != XO_OP_CREATE)759return -1;760761switch (op) {762case XO_OP_CREATE: /* Called when the handle is init'd */763rc = csv_create(xop);764break;765766case XO_OP_OPTIONS:767rc = csv_options(xop, csv, value, ':');768break;769770case XO_OP_OPTIONS_PLUS:771rc = csv_options(xop, csv, value, '+');772break;773774case XO_OP_OPEN_LIST:775case XO_OP_CLOSE_LIST:776break; /* Ignore these ops */777778case XO_OP_OPEN_CONTAINER:779case XO_OP_OPEN_LEAF_LIST:780rc = csv_open_level(xop, csv, name, 0);781break;782783case XO_OP_OPEN_INSTANCE:784rc = csv_open_level(xop, csv, name, 1);785break;786787case XO_OP_CLOSE_CONTAINER:788case XO_OP_CLOSE_LEAF_LIST:789case XO_OP_CLOSE_INSTANCE:790rc = csv_close_level(xop, csv, name);791break;792793case XO_OP_STRING: /* Quoted UTF-8 string */794case XO_OP_CONTENT: /* Other content */795rc = csv_data(xop, csv, name, value, flags);796break;797798case XO_OP_FINISH: /* Clean up function */799break;800801case XO_OP_FLUSH: /* Clean up function */802rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);803if (rc > 0)804rc = 0;805806xo_buf_reset(xbp);807break;808809case XO_OP_DESTROY: /* Clean up function */810csv_destroy(xop, csv);811break;812813case XO_OP_ATTRIBUTE: /* Attribute name/value */814break;815816case XO_OP_VERSION: /* Version string */817break;818}819820return rc;821}822823/*824* Callback when our encoder is loaded.825*/826int827xo_encoder_library_init (XO_ENCODER_INIT_ARGS)828{829arg->xei_handler = csv_handler;830arg->xei_version = XO_ENCODER_VERSION;831832return 0;833}834835836