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