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