libxo.c revision 274672
1/* 2 * Copyright (c) 2014, 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, July 2014 9 */ 10 11#include <stdio.h> 12#include <stdlib.h> 13#include <stdint.h> 14#include <stddef.h> 15#include <wchar.h> 16#include <locale.h> 17#include <sys/types.h> 18#include <stdarg.h> 19#include <string.h> 20#include <errno.h> 21#include <limits.h> 22#include <ctype.h> 23#include <wctype.h> 24#include <getopt.h> 25 26#include "xoconfig.h" 27#include "xo.h" 28#include "xoversion.h" 29 30const char xo_version[] = LIBXO_VERSION; 31const char xo_version_extra[] = LIBXO_VERSION_EXTRA; 32 33#ifndef UNUSED 34#define UNUSED __attribute__ ((__unused__)) 35#endif /* UNUSED */ 36 37#define XO_INDENT_BY 2 /* Amount to indent when pretty printing */ 38#define XO_BUFSIZ (8*1024) /* Initial buffer size */ 39#define XO_DEPTH 512 /* Default stack depth */ 40#define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */ 41 42#define XO_FAILURE_NAME "failure" 43 44/* 45 * xo_buffer_t: a memory buffer that can be grown as needed. We 46 * use them for building format strings and output data. 47 */ 48typedef struct xo_buffer_s { 49 char *xb_bufp; /* Buffer memory */ 50 char *xb_curp; /* Current insertion point */ 51 int xb_size; /* Size of buffer */ 52} xo_buffer_t; 53 54/* Flags for the stack frame */ 55typedef unsigned xo_xsf_flags_t; /* XSF_* flags */ 56#define XSF_NOT_FIRST (1<<0) /* Not the first element */ 57#define XSF_LIST (1<<1) /* Frame is a list */ 58#define XSF_INSTANCE (1<<2) /* Frame is an instance */ 59#define XSF_DTRT (1<<3) /* Save the name for DTRT mode */ 60 61/* 62 * xo_stack_t: As we open and close containers and levels, we 63 * create a stack of frames to track them. This is needed for 64 * XOF_WARN and XOF_XPATH. 65 */ 66typedef struct xo_stack_s { 67 xo_xsf_flags_t xs_flags; /* Flags for this frame */ 68 char *xs_name; /* Name (for XPath value) */ 69 char *xs_keys; /* XPath predicate for any key fields */ 70} xo_stack_t; 71 72/* 73 * xo_handle_t: this is the principle data structure for libxo. 74 * It's used as a store for state, options, and content. 75 */ 76struct xo_handle_s { 77 unsigned long xo_flags; /* Flags */ 78 unsigned short xo_style; /* XO_STYLE_* value */ 79 unsigned short xo_indent; /* Indent level (if pretty) */ 80 unsigned short xo_indent_by; /* Indent amount (tab stop) */ 81 xo_write_func_t xo_write; /* Write callback */ 82 xo_close_func_t xo_close; /* Close callback */ 83 xo_formatter_t xo_formatter; /* Custom formating function */ 84 xo_checkpointer_t xo_checkpointer; /* Custom formating support function */ 85 void *xo_opaque; /* Opaque data for write function */ 86 FILE *xo_fp; /* XXX File pointer */ 87 xo_buffer_t xo_data; /* Output data */ 88 xo_buffer_t xo_fmt; /* Work area for building format strings */ 89 xo_buffer_t xo_attrs; /* Work area for building XML attributes */ 90 xo_buffer_t xo_predicate; /* Work area for building XPath predicates */ 91 xo_stack_t *xo_stack; /* Stack pointer */ 92 int xo_depth; /* Depth of stack */ 93 int xo_stack_size; /* Size of the stack */ 94 xo_info_t *xo_info; /* Info fields for all elements */ 95 int xo_info_count; /* Number of info entries */ 96 va_list xo_vap; /* Variable arguments (stdargs) */ 97 char *xo_leading_xpath; /* A leading XPath expression */ 98 mbstate_t xo_mbstate; /* Multi-byte character conversion state */ 99 unsigned xo_anchor_offset; /* Start of anchored text */ 100 unsigned xo_anchor_columns; /* Number of columns since the start anchor */ 101 int xo_anchor_min_width; /* Desired width of anchored text */ 102 unsigned xo_units_offset; /* Start of units insertion point */ 103 unsigned xo_columns; /* Columns emitted during this xo_emit call */ 104}; 105 106/* Flags for formatting functions */ 107typedef unsigned long xo_xff_flags_t; 108#define XFF_COLON (1<<0) /* Append a ":" */ 109#define XFF_COMMA (1<<1) /* Append a "," iff there's more output */ 110#define XFF_WS (1<<2) /* Append a blank */ 111#define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding formats (xml and json) */ 112 113#define XFF_QUOTE (1<<4) /* Force quotes */ 114#define XFF_NOQUOTE (1<<5) /* Force no quotes */ 115#define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display formats (text and html) */ 116#define XFF_KEY (1<<7) /* Field is a key (for XPath) */ 117 118#define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */ 119#define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */ 120#define XFF_BLANK_LINE (1<<10) /* Emit a blank line */ 121#define XFF_NO_OUTPUT (1<<11) /* Do not make any output */ 122 123#define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */ 124#define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */ 125#define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */ 126 127/* 128 * Normal printf has width and precision, which for strings operate as 129 * min and max number of columns. But this depends on the idea that 130 * one byte means one column, which UTF-8 and multi-byte characters 131 * pitches on its ear. It may take 40 bytes of data to populate 14 132 * columns, but we can't go off looking at 40 bytes of data without the 133 * caller's permission for fear/knowledge that we'll generate core files. 134 * 135 * So we make three values, distinguishing between "max column" and 136 * "number of bytes that we will inspect inspect safely" We call the 137 * later "size", and make the format "%[[<min>].[[<size>].<max>]]s". 138 * 139 * Under the "first do no harm" theory, we default "max" to "size". 140 * This is a reasonable assumption for folks that don't grok the 141 * MBS/WCS/UTF-8 world, and while it will be annoying, it will never 142 * be evil. 143 * 144 * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14 145 * columns of output, but will never look at more than 14 bytes of the 146 * input buffer. This is mostly compatible with printf and caller's 147 * expectations. 148 * 149 * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however 150 * many bytes (or until a NUL is seen) are needed to fill 14 columns 151 * of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up 152 * to xx bytes (or until a NUL is seen) in order to fill 14 columns 153 * of output. 154 * 155 * It's fairly amazing how a good idea (handle all languages of the 156 * world) blows such a big hole in the bottom of the fairly weak boat 157 * that is C string handling. The simplicity and completenesss are 158 * sunk in ways we haven't even begun to understand. 159 */ 160 161#define XF_WIDTH_MIN 0 /* Minimal width */ 162#define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */ 163#define XF_WIDTH_MAX 2 /* Maximum width */ 164#define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */ 165 166/* Input and output string encodings */ 167#define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */ 168#define XF_ENC_UTF8 2 /* UTF-8 */ 169#define XF_ENC_LOCALE 3 /* Current locale */ 170 171/* 172 * A place to parse printf-style format flags for each field 173 */ 174typedef struct xo_format_s { 175 unsigned char xf_fc; /* Format character */ 176 unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */ 177 unsigned char xf_skip; /* Skip this field */ 178 unsigned char xf_lflag; /* 'l' (long) */ 179 unsigned char xf_hflag;; /* 'h' (half) */ 180 unsigned char xf_jflag; /* 'j' (intmax_t) */ 181 unsigned char xf_tflag; /* 't' (ptrdiff_t) */ 182 unsigned char xf_zflag; /* 'z' (size_t) */ 183 unsigned char xf_qflag; /* 'q' (quad_t) */ 184 unsigned char xf_seen_minus; /* Seen a minus */ 185 int xf_leading_zero; /* Seen a leading zero (zero fill) */ 186 unsigned xf_dots; /* Seen one or more '.'s */ 187 int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */ 188 unsigned xf_stars; /* Seen one or more '*'s */ 189 unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */ 190} xo_format_t; 191 192/* 193 * We keep a default handle to allow callers to avoid having to 194 * allocate one. Passing NULL to any of our functions will use 195 * this default handle. 196 */ 197static xo_handle_t xo_default_handle; 198static int xo_default_inited; 199static int xo_locale_inited; 200static char *xo_program; 201 202/* 203 * To allow libxo to be used in diverse environment, we allow the 204 * caller to give callbacks for memory allocation. 205 */ 206static xo_realloc_func_t xo_realloc = realloc; 207static xo_free_func_t xo_free = free; 208 209/* Forward declarations */ 210static void 211xo_failure (xo_handle_t *xop, const char *fmt, ...); 212 213static void 214xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 215 const char *name, int nlen, 216 const char *value, int vlen, 217 const char *encoding, int elen); 218 219static void 220xo_anchor_clear (xo_handle_t *xop); 221 222/* 223 * Callback to write data to a FILE pointer 224 */ 225static int 226xo_write_to_file (void *opaque, const char *data) 227{ 228 FILE *fp = (FILE *) opaque; 229 return fprintf(fp, "%s", data); 230} 231 232/* 233 * Callback to close a file 234 */ 235static void 236xo_close_file (void *opaque) 237{ 238 FILE *fp = (FILE *) opaque; 239 fclose(fp); 240} 241 242/* 243 * Initialize the contents of an xo_buffer_t. 244 */ 245static void 246xo_buf_init (xo_buffer_t *xbp) 247{ 248 xbp->xb_size = XO_BUFSIZ; 249 xbp->xb_bufp = xo_realloc(NULL, xbp->xb_size); 250 xbp->xb_curp = xbp->xb_bufp; 251} 252 253/* 254 * Initialize the contents of an xo_buffer_t. 255 */ 256static void 257xo_buf_cleanup (xo_buffer_t *xbp) 258{ 259 if (xbp->xb_bufp) 260 xo_free(xbp->xb_bufp); 261 bzero(xbp, sizeof(*xbp)); 262} 263 264static int 265xo_depth_check (xo_handle_t *xop, int depth) 266{ 267 xo_stack_t *xsp; 268 269 if (depth >= xop->xo_stack_size) { 270 depth += 16; 271 xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth); 272 if (xsp == NULL) { 273 xo_failure(xop, "xo_depth_check: out of memory (%d)", depth); 274 return 0; 275 } 276 277 int count = depth - xop->xo_stack_size; 278 279 bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp)); 280 xop->xo_stack_size = depth; 281 xop->xo_stack = xsp; 282 } 283 284 return 0; 285} 286 287void 288xo_no_setlocale (void) 289{ 290 xo_locale_inited = 1; /* Skip initialization */ 291} 292 293/* 294 * Initialize an xo_handle_t, using both static defaults and 295 * the global settings from the LIBXO_OPTIONS environment 296 * variable. 297 */ 298static void 299xo_init_handle (xo_handle_t *xop) 300{ 301 xop->xo_opaque = stdout; 302 xop->xo_write = xo_write_to_file; 303 304 /* 305 * We need to initialize the locale, which isn't really pretty. 306 * Libraries should depend on their caller to set up the 307 * environment. But we really can't count on the caller to do 308 * this, because well, they won't. Trust me. 309 */ 310 if (!xo_locale_inited) { 311 xo_locale_inited = 1; /* Only do this once */ 312 313 const char *cp = getenv("LC_CTYPE"); 314 if (cp == NULL) 315 cp = getenv("LANG"); 316 if (cp == NULL) 317 cp = getenv("LC_ALL"); 318 if (cp == NULL) 319 cp = "UTF-8"; /* Optimistic? */ 320 (void) setlocale(LC_CTYPE, cp); 321 } 322 323 /* 324 * Initialize only the xo_buffers we know we'll need; the others 325 * can be allocated as needed. 326 */ 327 xo_buf_init(&xop->xo_data); 328 xo_buf_init(&xop->xo_fmt); 329 330 xop->xo_indent_by = XO_INDENT_BY; 331 xo_depth_check(xop, XO_DEPTH); 332 333#if !defined(NO_LIBXO_OPTIONS) 334 if (!(xop->xo_flags & XOF_NO_ENV)) { 335 char *env = getenv("LIBXO_OPTIONS"); 336 if (env) 337 xo_set_options(xop, env); 338 } 339#endif /* NO_GETENV */ 340} 341 342/* 343 * Initialize the default handle. 344 */ 345static void 346xo_default_init (void) 347{ 348 xo_handle_t *xop = &xo_default_handle; 349 350 xo_init_handle(xop); 351 352 xo_default_inited = 1; 353} 354 355/* 356 * Does the buffer have room for the given number of bytes of data? 357 * If not, realloc the buffer to make room. If that fails, we 358 * return 0 to tell the caller they are in trouble. 359 */ 360static int 361xo_buf_has_room (xo_buffer_t *xbp, int len) 362{ 363 if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) { 364 int sz = xbp->xb_size + XO_BUFSIZ; 365 char *bp = xo_realloc(xbp->xb_bufp, sz); 366 if (bp == NULL) { 367 /* 368 * XXX If we wanted to put a stick XOF_ENOMEM on xop, 369 * this would be the place to do it. But we'd need 370 * to churn the code to pass xop in here.... 371 */ 372 return 0; 373 } 374 375 xbp->xb_curp = bp + (xbp->xb_curp - xbp->xb_bufp); 376 xbp->xb_bufp = bp; 377 xbp->xb_size = sz; 378 } 379 380 return 1; 381} 382 383/* 384 * Cheap convenience function to return either the argument, or 385 * the internal handle, after it has been initialized. The usage 386 * is: 387 * xop = xo_default(xop); 388 */ 389static xo_handle_t * 390xo_default (xo_handle_t *xop) 391{ 392 if (xop == NULL) { 393 if (xo_default_inited == 0) 394 xo_default_init(); 395 xop = &xo_default_handle; 396 } 397 398 return xop; 399} 400 401/* 402 * Return the number of spaces we should be indenting. If 403 * we are pretty-printing, theis is indent * indent_by. 404 */ 405static int 406xo_indent (xo_handle_t *xop) 407{ 408 int rc = 0; 409 410 xop = xo_default(xop); 411 412 if (xop->xo_flags & XOF_PRETTY) { 413 rc = xop->xo_indent * xop->xo_indent_by; 414 if (xop->xo_flags & XOF_TOP_EMITTED) 415 rc += xop->xo_indent_by; 416 } 417 418 return rc; 419} 420 421static void 422xo_buf_indent (xo_handle_t *xop, int indent) 423{ 424 xo_buffer_t *xbp = &xop->xo_data; 425 426 if (indent <= 0) 427 indent = xo_indent(xop); 428 429 if (!xo_buf_has_room(xbp, indent)) 430 return; 431 432 memset(xbp->xb_curp, ' ', indent); 433 xbp->xb_curp += indent; 434} 435 436static char xo_xml_amp[] = "&"; 437static char xo_xml_lt[] = "<"; 438static char xo_xml_gt[] = ">"; 439static char xo_xml_quot[] = """; 440 441static int 442xo_escape_xml (xo_buffer_t *xbp, int len, int attr) 443{ 444 int slen; 445 unsigned delta = 0; 446 char *cp, *ep, *ip; 447 const char *sp; 448 449 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 450 /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */ 451 if (*cp == '<') 452 delta += sizeof(xo_xml_lt) - 2; 453 else if (*cp == '>') 454 delta += sizeof(xo_xml_gt) - 2; 455 else if (*cp == '&') 456 delta += sizeof(xo_xml_amp) - 2; 457 else if (attr && *cp == '"') 458 delta += sizeof(xo_xml_quot) - 2; 459 } 460 461 if (delta == 0) /* Nothing to escape; bail */ 462 return len; 463 464 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ 465 return 0; 466 467 ep = xbp->xb_curp; 468 cp = ep + len; 469 ip = cp + delta; 470 do { 471 cp -= 1; 472 ip -= 1; 473 474 if (*cp == '<') 475 sp = xo_xml_lt; 476 else if (*cp == '>') 477 sp = xo_xml_gt; 478 else if (*cp == '&') 479 sp = xo_xml_amp; 480 else if (attr && *cp == '"') 481 sp = xo_xml_quot; 482 else { 483 *ip = *cp; 484 continue; 485 } 486 487 slen = strlen(sp); 488 ip -= slen - 1; 489 memcpy(ip, sp, slen); 490 491 } while (cp > ep && cp != ip); 492 493 return len + delta; 494} 495 496static int 497xo_escape_json (xo_buffer_t *xbp, int len) 498{ 499 unsigned delta = 0; 500 char *cp, *ep, *ip; 501 502 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 503 if (*cp == '\\') 504 delta += 1; 505 else if (*cp == '"') 506 delta += 1; 507 } 508 509 if (delta == 0) /* Nothing to escape; bail */ 510 return len; 511 512 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ 513 return 0; 514 515 ep = xbp->xb_curp; 516 cp = ep + len; 517 ip = cp + delta; 518 do { 519 cp -= 1; 520 ip -= 1; 521 522 if (*cp != '\\' && *cp != '"') { 523 *ip = *cp; 524 continue; 525 } 526 527 *ip-- = *cp; 528 *ip = '\\'; 529 530 } while (cp > ep && cp != ip); 531 532 return len + delta; 533} 534 535/* 536 * Append the given string to the given buffer 537 */ 538static void 539xo_buf_append (xo_buffer_t *xbp, const char *str, int len) 540{ 541 if (!xo_buf_has_room(xbp, len)) 542 return; 543 544 memcpy(xbp->xb_curp, str, len); 545 xbp->xb_curp += len; 546} 547 548static void 549xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, 550 const char *str, int len, xo_xff_flags_t flags) 551{ 552 if (!xo_buf_has_room(xbp, len)) 553 return; 554 555 memcpy(xbp->xb_curp, str, len); 556 557 switch (xop->xo_style) { 558 case XO_STYLE_XML: 559 case XO_STYLE_HTML: 560 len = xo_escape_xml(xbp, len, (flags & XFF_ATTR)); 561 break; 562 563 case XO_STYLE_JSON: 564 len = xo_escape_json(xbp, len); 565 break; 566 } 567 568 xbp->xb_curp += len; 569} 570 571/* 572 * Write the current contents of the data buffer using the handle's 573 * xo_write function. 574 */ 575static void 576xo_write (xo_handle_t *xop) 577{ 578 xo_buffer_t *xbp = &xop->xo_data; 579 580 if (xbp->xb_curp != xbp->xb_bufp) { 581 xo_buf_append(xbp, "", 1); /* Append ending NUL */ 582 xo_anchor_clear(xop); 583 xop->xo_write(xop->xo_opaque, xbp->xb_bufp); 584 xbp->xb_curp = xbp->xb_bufp; 585 } 586 587 /* Turn off the flags that don't survive across writes */ 588 xop->xo_flags &= ~(XOF_UNITS_PENDING); 589} 590 591/* 592 * Format arguments into our buffer. If a custom formatter has been set, 593 * we use that to do the work; otherwise we vsnprintf(). 594 */ 595static int 596xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) 597{ 598 va_list va_local; 599 int rc; 600 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 601 602 va_copy(va_local, vap); 603 604 if (xop->xo_formatter) 605 rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); 606 else 607 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 608 609 if (rc > xbp->xb_size) { 610 if (!xo_buf_has_room(xbp, rc)) { 611 va_end(va_local); 612 return -1; 613 } 614 615 /* 616 * After we call vsnprintf(), the stage of vap is not defined. 617 * We need to copy it before we pass. Then we have to do our 618 * own logic below to move it along. This is because the 619 * implementation can have va_list be a point (bsd) or a 620 * structure (macosx) or anything in between. 621 */ 622 623 va_end(va_local); /* Reset vap to the start */ 624 va_copy(va_local, vap); 625 626 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 627 if (xop->xo_formatter) 628 xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); 629 else 630 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 631 } 632 va_end(va_local); 633 634 return rc; 635} 636 637/* 638 * Print some data thru the handle. 639 */ 640static int 641xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap) 642{ 643 xo_buffer_t *xbp = &xop->xo_data; 644 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 645 int rc; 646 va_list va_local; 647 648 va_copy(va_local, vap); 649 650 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 651 652 if (rc > xbp->xb_size) { 653 if (!xo_buf_has_room(xbp, rc)) { 654 va_end(va_local); 655 return -1; 656 } 657 658 va_end(va_local); /* Reset vap to the start */ 659 va_copy(va_local, vap); 660 661 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 662 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 663 } 664 665 va_end(va_local); 666 667 if (rc > 0) 668 xbp->xb_curp += rc; 669 670 return rc; 671} 672 673static int 674xo_printf (xo_handle_t *xop, const char *fmt, ...) 675{ 676 int rc; 677 va_list vap; 678 679 va_start(vap, fmt); 680 681 rc = xo_printf_v(xop, fmt, vap); 682 683 va_end(vap); 684 return rc; 685} 686 687/* 688 * These next few function are make The Essential UTF-8 Ginsu Knife. 689 * Identify an input and output character, and convert it. 690 */ 691static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; 692 693static int 694xo_is_utf8 (char ch) 695{ 696 return (ch & 0x80); 697} 698 699static int 700xo_utf8_to_wc_len (const char *buf) 701{ 702 unsigned b = (unsigned char) *buf; 703 int len; 704 705 if ((b & 0x80) == 0x0) 706 len = 1; 707 else if ((b & 0xe0) == 0xc0) 708 len = 2; 709 else if ((b & 0xf0) == 0xe0) 710 len = 3; 711 else if ((b & 0xf8) == 0xf0) 712 len = 4; 713 else if ((b & 0xfc) == 0xf8) 714 len = 5; 715 else if ((b & 0xfe) == 0xfc) 716 len = 6; 717 else 718 len = -1; 719 720 return len; 721} 722 723static int 724xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz) 725{ 726 727 unsigned b = (unsigned char) *buf; 728 int len, i; 729 730 len = xo_utf8_to_wc_len(buf); 731 if (len == -1) { 732 xo_failure(xop, "invalid UTF-8 data: %02hhx", b); 733 return -1; 734 } 735 736 if (len > bufsiz) { 737 xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)", 738 b, len, bufsiz); 739 return -1; 740 } 741 742 for (i = 2; i < len; i++) { 743 b = (unsigned char ) buf[i]; 744 if ((b & 0xc0) != 0x80) { 745 xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b); 746 return -1; 747 } 748 } 749 750 return len; 751} 752 753/* 754 * Build a wide character from the input buffer; the number of 755 * bits we pull off the first character is dependent on the length, 756 * but we put 6 bits off all other bytes. 757 */ 758static wchar_t 759xo_utf8_char (const char *buf, int len) 760{ 761 int i; 762 wchar_t wc; 763 const unsigned char *cp = (const unsigned char *) buf; 764 765 wc = *cp & xo_utf8_bits[len]; 766 for (i = 1; i < len; i++) { 767 wc <<= 6; 768 wc |= cp[i] & 0x3f; 769 if ((cp[i] & 0xc0) != 0x80) 770 return (wchar_t) -1; 771 } 772 773 return wc; 774} 775 776/* 777 * Determine the number of bytes needed to encode a wide character. 778 */ 779static int 780xo_utf8_emit_len (wchar_t wc) 781{ 782 int len; 783 784 if ((wc & ((1<<7) - 1)) == wc) /* Simple case */ 785 len = 1; 786 else if ((wc & ((1<<11) - 1)) == wc) 787 len = 2; 788 else if ((wc & ((1<<16) - 1)) == wc) 789 len = 3; 790 else if ((wc & ((1<<21) - 1)) == wc) 791 len = 4; 792 else if ((wc & ((1<<26) - 1)) == wc) 793 len = 5; 794 else 795 len = 6; 796 797 return len; 798} 799 800static void 801xo_utf8_emit_char (char *buf, int len, wchar_t wc) 802{ 803 int i; 804 805 if (len == 1) { /* Simple case */ 806 buf[0] = wc & 0x7f; 807 return; 808 } 809 810 for (i = len - 1; i >= 0; i--) { 811 buf[i] = 0x80 | (wc & 0x3f); 812 wc >>= 6; 813 } 814 815 buf[0] &= xo_utf8_bits[len]; 816 buf[0] |= ~xo_utf8_bits[len] << 1; 817} 818 819static int 820xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp, 821 const char *ibuf, int ilen) 822{ 823 wchar_t wc; 824 int len; 825 826 /* 827 * Build our wide character from the input buffer; the number of 828 * bits we pull off the first character is dependent on the length, 829 * but we put 6 bits off all other bytes. 830 */ 831 wc = xo_utf8_char(ibuf, ilen); 832 if (wc == (wchar_t) -1) { 833 xo_failure(xop, "invalid utf-8 byte sequence"); 834 return 0; 835 } 836 837 if (xop->xo_flags & XOF_NO_LOCALE) { 838 if (!xo_buf_has_room(xbp, ilen)) 839 return 0; 840 841 memcpy(xbp->xb_curp, ibuf, ilen); 842 xbp->xb_curp += ilen; 843 844 } else { 845 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) 846 return 0; 847 848 bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate)); 849 len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); 850 851 if (len <= 0) { 852 xo_failure(xop, "could not convert wide char: %lx", 853 (unsigned long) wc); 854 return 0; 855 } 856 xbp->xb_curp += len; 857 } 858 859 return wcwidth(wc); 860} 861 862static void 863xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp, 864 const char *cp, int len) 865{ 866 const char *sp = cp, *ep = cp + len; 867 unsigned save_off = xbp->xb_bufp - xbp->xb_curp; 868 int slen; 869 int cols = 0; 870 871 for ( ; cp < ep; cp++) { 872 if (!xo_is_utf8(*cp)) { 873 cols += 1; 874 continue; 875 } 876 877 /* 878 * We're looking at a non-ascii UTF-8 character. 879 * First we copy the previous data. 880 * Then we need find the length and validate it. 881 * Then we turn it into a wide string. 882 * Then we turn it into a localized string. 883 * Then we repeat. Isn't i18n fun? 884 */ 885 if (sp != cp) 886 xo_buf_append(xbp, sp, cp - sp); /* Append previous data */ 887 888 slen = xo_buf_utf8_len(xop, cp, ep - cp); 889 if (slen <= 0) { 890 /* Bad data; back it all out */ 891 xbp->xb_curp = xbp->xb_bufp + save_off; 892 return; 893 } 894 895 cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen); 896 897 /* Next time thru, we'll start at the next character */ 898 cp += slen - 1; 899 sp = cp + 1; 900 } 901 902 /* Update column values */ 903 if (xop->xo_flags & XOF_COLUMNS) 904 xop->xo_columns += cols; 905 if (xop->xo_flags & XOF_ANCHOR) 906 xop->xo_anchor_columns += cols; 907 908 /* Before we fall into the basic logic below, we need reset len */ 909 len = ep - sp; 910 if (len != 0) /* Append trailing data */ 911 xo_buf_append(xbp, sp, len); 912} 913 914/* 915 * Append the given string to the given buffer 916 */ 917static void 918xo_data_append (xo_handle_t *xop, const char *str, int len) 919{ 920 xo_buf_append(&xop->xo_data, str, len); 921} 922 923/* 924 * Append the given string to the given buffer 925 */ 926static void 927xo_data_escape (xo_handle_t *xop, const char *str, int len) 928{ 929 xo_buf_escape(xop, &xop->xo_data, str, len, 0); 930} 931 932/* 933 * Generate a warning. Normally, this is a text message written to 934 * standard error. If the XOF_WARN_XML flag is set, then we generate 935 * XMLified content on standard output. 936 */ 937static void 938xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, 939 const char *fmt, va_list vap) 940{ 941 xop = xo_default(xop); 942 if (check_warn && !(xop->xo_flags & XOF_WARN)) 943 return; 944 945 if (fmt == NULL) 946 return; 947 948 int len = strlen(fmt); 949 int plen = xo_program ? strlen(xo_program) : 0; 950 char *newfmt = alloca(len + 2 + plen + 2); /* newline, NUL, and ": " */ 951 952 if (plen) { 953 memcpy(newfmt, xo_program, plen); 954 newfmt[plen++] = ':'; 955 newfmt[plen++] = ' '; 956 } 957 memcpy(newfmt + plen, fmt, len); 958 959 /* Add a newline to the fmt string */ 960 if (!(xop->xo_flags & XOF_WARN_XML)) 961 newfmt[len++ + plen] = '\n'; 962 newfmt[len + plen] = '\0'; 963 964 if (xop->xo_flags & XOF_WARN_XML) { 965 static char err_open[] = "<error>"; 966 static char err_close[] = "</error>"; 967 static char msg_open[] = "<message>"; 968 static char msg_close[] = "</message>"; 969 970 xo_buffer_t *xbp = &xop->xo_data; 971 972 xo_buf_append(xbp, err_open, sizeof(err_open) - 1); 973 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); 974 975 va_list va_local; 976 va_copy(va_local, vap); 977 978 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 979 int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap); 980 if (rc > xbp->xb_size) { 981 if (!xo_buf_has_room(xbp, rc)) { 982 va_end(va_local); 983 return; 984 } 985 986 va_end(vap); /* Reset vap to the start */ 987 va_copy(vap, va_local); 988 989 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 990 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 991 } 992 va_end(va_local); 993 994 rc = xo_escape_xml(xbp, rc, 1); 995 xbp->xb_curp += rc; 996 997 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); 998 xo_buf_append(xbp, err_close, sizeof(err_close) - 1); 999 1000 if (code > 0) { 1001 const char *msg = strerror(code); 1002 if (msg) { 1003 xo_buf_append(xbp, ": ", 2); 1004 xo_buf_append(xbp, msg, strlen(msg)); 1005 } 1006 } 1007 1008 xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */ 1009 xo_write(xop); 1010 1011 } else { 1012 vfprintf(stderr, newfmt, vap); 1013 } 1014} 1015 1016void 1017xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) 1018{ 1019 va_list vap; 1020 1021 va_start(vap, fmt); 1022 xo_warn_hcv(xop, code, 0, fmt, vap); 1023 va_end(vap); 1024} 1025 1026void 1027xo_warn_c (int code, const char *fmt, ...) 1028{ 1029 va_list vap; 1030 1031 va_start(vap, fmt); 1032 xo_warn_hcv(NULL, 0, code, fmt, vap); 1033 va_end(vap); 1034} 1035 1036void 1037xo_warn (const char *fmt, ...) 1038{ 1039 int code = errno; 1040 va_list vap; 1041 1042 va_start(vap, fmt); 1043 xo_warn_hcv(NULL, code, 0, fmt, vap); 1044 va_end(vap); 1045} 1046 1047void 1048xo_warnx (const char *fmt, ...) 1049{ 1050 va_list vap; 1051 1052 va_start(vap, fmt); 1053 xo_warn_hcv(NULL, -1, 0, fmt, vap); 1054 va_end(vap); 1055} 1056 1057void 1058xo_err (int eval, const char *fmt, ...) 1059{ 1060 int code = errno; 1061 va_list vap; 1062 1063 va_start(vap, fmt); 1064 xo_warn_hcv(NULL, code, 0, fmt, vap); 1065 va_end(vap); 1066 xo_finish(); 1067 exit(eval); 1068} 1069 1070void 1071xo_errx (int eval, const char *fmt, ...) 1072{ 1073 va_list vap; 1074 1075 va_start(vap, fmt); 1076 xo_warn_hcv(NULL, -1, 0, fmt, vap); 1077 va_end(vap); 1078 xo_finish(); 1079 exit(eval); 1080} 1081 1082void 1083xo_errc (int eval, int code, const char *fmt, ...) 1084{ 1085 va_list vap; 1086 1087 va_start(vap, fmt); 1088 xo_warn_hcv(NULL, code, 0, fmt, vap); 1089 va_end(vap); 1090 xo_finish(); 1091 exit(eval); 1092} 1093 1094/* 1095 * Generate a warning. Normally, this is a text message written to 1096 * standard error. If the XOF_WARN_XML flag is set, then we generate 1097 * XMLified content on standard output. 1098 */ 1099void 1100xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) 1101{ 1102 static char msg_open[] = "<message>"; 1103 static char msg_close[] = "</message>"; 1104 xo_buffer_t *xbp; 1105 int rc; 1106 va_list va_local; 1107 1108 xop = xo_default(xop); 1109 1110 if (fmt == NULL || *fmt == '\0') 1111 return; 1112 1113 int need_nl = (fmt[strlen(fmt) - 1] != '\n'); 1114 1115 switch (xop->xo_style) { 1116 case XO_STYLE_XML: 1117 xbp = &xop->xo_data; 1118 if (xop->xo_flags & XOF_PRETTY) 1119 xo_buf_indent(xop, xop->xo_indent_by); 1120 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); 1121 1122 va_copy(va_local, vap); 1123 1124 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1125 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 1126 if (rc > xbp->xb_size) { 1127 if (!xo_buf_has_room(xbp, rc)) { 1128 va_end(va_local); 1129 return; 1130 } 1131 1132 va_end(vap); /* Reset vap to the start */ 1133 va_copy(vap, va_local); 1134 1135 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1136 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 1137 } 1138 va_end(va_local); 1139 1140 rc = xo_escape_xml(xbp, rc, 1); 1141 xbp->xb_curp += rc; 1142 1143 if (need_nl && code > 0) { 1144 const char *msg = strerror(code); 1145 if (msg) { 1146 xo_buf_append(xbp, ": ", 2); 1147 xo_buf_append(xbp, msg, strlen(msg)); 1148 } 1149 } 1150 1151 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); 1152 if (need_nl) 1153 xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */ 1154 xo_write(xop); 1155 break; 1156 1157 case XO_STYLE_HTML: 1158 { 1159 char buf[BUFSIZ], *bp = buf, *cp; 1160 int bufsiz = sizeof(buf); 1161 int rc2; 1162 1163 va_copy(va_local, vap); 1164 1165 rc = vsnprintf(bp, bufsiz, fmt, va_local); 1166 if (rc > bufsiz) { 1167 bufsiz = rc + BUFSIZ; 1168 bp = alloca(bufsiz); 1169 va_end(va_local); 1170 va_copy(va_local, vap); 1171 rc = vsnprintf(bp, bufsiz, fmt, va_local); 1172 } 1173 va_end(va_local); 1174 cp = bp + rc; 1175 1176 if (need_nl) { 1177 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n", 1178 (code > 0) ? ": " : "", 1179 (code > 0) ? strerror(code) : ""); 1180 if (rc2 > 0) 1181 rc += rc2; 1182 } 1183 1184 xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0); 1185 } 1186 break; 1187 1188 case XO_STYLE_JSON: 1189 /* No meanings of representing messages in JSON */ 1190 break; 1191 1192 case XO_STYLE_TEXT: 1193 rc = xo_printf_v(xop, fmt, vap); 1194 /* 1195 * XXX need to handle UTF-8 widths 1196 */ 1197 if (rc > 0) { 1198 if (xop->xo_flags & XOF_COLUMNS) 1199 xop->xo_columns += rc; 1200 if (xop->xo_flags & XOF_ANCHOR) 1201 xop->xo_anchor_columns += rc; 1202 } 1203 1204 if (need_nl && code > 0) { 1205 const char *msg = strerror(code); 1206 if (msg) { 1207 xo_printf(xop, ": %s", msg); 1208 } 1209 } 1210 if (need_nl) 1211 xo_printf(xop, "\n"); 1212 1213 break; 1214 } 1215 1216 xo_flush_h(xop); 1217} 1218 1219void 1220xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...) 1221{ 1222 va_list vap; 1223 1224 va_start(vap, fmt); 1225 xo_message_hcv(xop, code, fmt, vap); 1226 va_end(vap); 1227} 1228 1229void 1230xo_message_c (int code, const char *fmt, ...) 1231{ 1232 va_list vap; 1233 1234 va_start(vap, fmt); 1235 xo_message_hcv(NULL, code, fmt, vap); 1236 va_end(vap); 1237} 1238 1239void 1240xo_message (const char *fmt, ...) 1241{ 1242 int code = errno; 1243 va_list vap; 1244 1245 va_start(vap, fmt); 1246 xo_message_hcv(NULL, code, fmt, vap); 1247 va_end(vap); 1248} 1249 1250static void 1251xo_failure (xo_handle_t *xop, const char *fmt, ...) 1252{ 1253 if (!(xop->xo_flags & XOF_WARN)) 1254 return; 1255 1256 va_list vap; 1257 1258 va_start(vap, fmt); 1259 xo_warn_hcv(xop, -1, 1, fmt, vap); 1260 va_end(vap); 1261} 1262 1263/** 1264 * Create a handle for use by later libxo functions. 1265 * 1266 * Note: normal use of libxo does not require a distinct handle, since 1267 * the default handle (used when NULL is passed) generates text on stdout. 1268 * 1269 * @style Style of output desired (XO_STYLE_* value) 1270 * @flags Set of XOF_* flags in use with this handle 1271 */ 1272xo_handle_t * 1273xo_create (xo_style_t style, xo_xof_flags_t flags) 1274{ 1275 xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop)); 1276 1277 if (xop) { 1278 bzero(xop, sizeof(*xop)); 1279 1280 xop->xo_style = style; 1281 xop->xo_flags = flags; 1282 xo_init_handle(xop); 1283 } 1284 1285 return xop; 1286} 1287 1288/** 1289 * Create a handle that will write to the given file. Use 1290 * the XOF_CLOSE_FP flag to have the file closed on xo_destroy(). 1291 * @fp FILE pointer to use 1292 * @style Style of output desired (XO_STYLE_* value) 1293 * @flags Set of XOF_* flags to use with this handle 1294 */ 1295xo_handle_t * 1296xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags) 1297{ 1298 xo_handle_t *xop = xo_create(style, flags); 1299 1300 if (xop) { 1301 xop->xo_opaque = fp; 1302 xop->xo_write = xo_write_to_file; 1303 xop->xo_close = xo_close_file; 1304 } 1305 1306 return xop; 1307} 1308 1309/** 1310 * Release any resources held by the handle. 1311 * @xop XO handle to alter (or NULL for default handle) 1312 */ 1313void 1314xo_destroy (xo_handle_t *xop_arg) 1315{ 1316 xo_handle_t *xop = xo_default(xop_arg); 1317 1318 if (xop->xo_close && (xop->xo_flags & XOF_CLOSE_FP)) 1319 xop->xo_close(xop->xo_opaque); 1320 1321 xo_free(xop->xo_stack); 1322 xo_buf_cleanup(&xop->xo_data); 1323 xo_buf_cleanup(&xop->xo_fmt); 1324 xo_buf_cleanup(&xop->xo_predicate); 1325 xo_buf_cleanup(&xop->xo_attrs); 1326 1327 if (xop_arg == NULL) { 1328 bzero(&xo_default_handle, sizeof(&xo_default_handle)); 1329 xo_default_inited = 0; 1330 } else 1331 xo_free(xop); 1332} 1333 1334/** 1335 * Record a new output style to use for the given handle (or default if 1336 * handle is NULL). This output style will be used for any future output. 1337 * 1338 * @xop XO handle to alter (or NULL for default handle) 1339 * @style new output style (XO_STYLE_*) 1340 */ 1341void 1342xo_set_style (xo_handle_t *xop, xo_style_t style) 1343{ 1344 xop = xo_default(xop); 1345 xop->xo_style = style; 1346} 1347 1348xo_style_t 1349xo_get_style (xo_handle_t *xop) 1350{ 1351 xop = xo_default(xop); 1352 return xop->xo_style; 1353} 1354 1355static int 1356xo_name_to_style (const char *name) 1357{ 1358 if (strcmp(name, "xml") == 0) 1359 return XO_STYLE_XML; 1360 else if (strcmp(name, "json") == 0) 1361 return XO_STYLE_JSON; 1362 else if (strcmp(name, "text") == 0) 1363 return XO_STYLE_TEXT; 1364 else if (strcmp(name, "html") == 0) 1365 return XO_STYLE_HTML; 1366 1367 return -1; 1368} 1369 1370/* 1371 * Convert string name to XOF_* flag value. 1372 * Not all are useful. Or safe. Or sane. 1373 */ 1374static unsigned 1375xo_name_to_flag (const char *name) 1376{ 1377 if (strcmp(name, "pretty") == 0) 1378 return XOF_PRETTY; 1379 if (strcmp(name, "warn") == 0) 1380 return XOF_WARN; 1381 if (strcmp(name, "xpath") == 0) 1382 return XOF_XPATH; 1383 if (strcmp(name, "info") == 0) 1384 return XOF_INFO; 1385 if (strcmp(name, "warn-xml") == 0) 1386 return XOF_WARN_XML; 1387 if (strcmp(name, "columns") == 0) 1388 return XOF_COLUMNS; 1389 if (strcmp(name, "dtrt") == 0) 1390 return XOF_DTRT; 1391 if (strcmp(name, "flush") == 0) 1392 return XOF_FLUSH; 1393 if (strcmp(name, "keys") == 0) 1394 return XOF_KEYS; 1395 if (strcmp(name, "ignore-close") == 0) 1396 return XOF_IGNORE_CLOSE; 1397 if (strcmp(name, "not-first") == 0) 1398 return XOF_NOT_FIRST; 1399 if (strcmp(name, "no-locale") == 0) 1400 return XOF_NO_LOCALE; 1401 if (strcmp(name, "no-top") == 0) 1402 return XOF_NO_TOP; 1403 if (strcmp(name, "units") == 0) 1404 return XOF_UNITS; 1405 if (strcmp(name, "underscores") == 0) 1406 return XOF_UNDERSCORES; 1407 1408 return 0; 1409} 1410 1411int 1412xo_set_style_name (xo_handle_t *xop, const char *name) 1413{ 1414 if (name == NULL) 1415 return -1; 1416 1417 int style = xo_name_to_style(name); 1418 if (style < 0) 1419 return -1; 1420 1421 xo_set_style(xop, style); 1422 return 0; 1423} 1424 1425/* 1426 * Set the options for a handle using a string of options 1427 * passed in. The input is a comma-separated set of names 1428 * and optional values: "xml,pretty,indent=4" 1429 */ 1430int 1431xo_set_options (xo_handle_t *xop, const char *input) 1432{ 1433 char *cp, *ep, *vp, *np, *bp; 1434 int style = -1, new_style, len, rc = 0; 1435 xo_xof_flags_t new_flag; 1436 1437 if (input == NULL) 1438 return 0; 1439 1440 xop = xo_default(xop); 1441 1442 /* 1443 * We support a simpler, old-school style of giving option 1444 * also, using a single character for each option. It's 1445 * ideal for lazy people, such as myself. 1446 */ 1447 if (*input == ':') { 1448 int sz; 1449 1450 for (input++ ; *input; input++) { 1451 switch (*input) { 1452 case 'f': 1453 xop->xo_flags |= XOF_FLUSH; 1454 break; 1455 1456 case 'H': 1457 xop->xo_style = XO_STYLE_HTML; 1458 break; 1459 1460 case 'I': 1461 xop->xo_flags |= XOF_INFO; 1462 break; 1463 1464 case 'i': 1465 sz = strspn(input + 1, "0123456789"); 1466 if (sz > 0) { 1467 xop->xo_indent_by = atoi(input + 1); 1468 input += sz - 1; /* Skip value */ 1469 } 1470 break; 1471 1472 case 'k': 1473 xop->xo_flags |= XOF_KEYS; 1474 break; 1475 1476 case 'J': 1477 xop->xo_style = XO_STYLE_JSON; 1478 break; 1479 1480 case 'P': 1481 xop->xo_flags |= XOF_PRETTY; 1482 break; 1483 1484 case 'T': 1485 xop->xo_style = XO_STYLE_TEXT; 1486 break; 1487 1488 case 'U': 1489 xop->xo_flags |= XOF_UNITS; 1490 break; 1491 1492 case 'u': 1493 xop->xo_flags |= XOF_UNDERSCORES; 1494 break; 1495 1496 case 'W': 1497 xop->xo_flags |= XOF_WARN; 1498 break; 1499 1500 case 'X': 1501 xop->xo_style = XO_STYLE_XML; 1502 break; 1503 1504 case 'x': 1505 xop->xo_flags |= XOF_XPATH; 1506 break; 1507 } 1508 } 1509 return 0; 1510 } 1511 1512 len = strlen(input) + 1; 1513 bp = alloca(len); 1514 memcpy(bp, input, len); 1515 1516 for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) { 1517 np = strchr(cp, ','); 1518 if (np) 1519 *np++ = '\0'; 1520 1521 vp = strchr(cp, '='); 1522 if (vp) 1523 *vp++ = '\0'; 1524 1525 new_style = xo_name_to_style(cp); 1526 if (new_style >= 0) { 1527 if (style >= 0) 1528 xo_warnx("ignoring multiple styles: '%s'", cp); 1529 else 1530 style = new_style; 1531 } else { 1532 new_flag = xo_name_to_flag(cp); 1533 if (new_flag != 0) 1534 xop->xo_flags |= new_flag; 1535 else { 1536 if (strcmp(cp, "indent") == 0) { 1537 xop->xo_indent_by = atoi(vp); 1538 } else { 1539 xo_warnx("unknown option: '%s'", cp); 1540 rc = -1; 1541 } 1542 } 1543 } 1544 } 1545 1546 if (style > 0) 1547 xop->xo_style= style; 1548 1549 return rc; 1550} 1551 1552/** 1553 * Set one or more flags for a given handle (or default if handle is NULL). 1554 * These flags will affect future output. 1555 * 1556 * @xop XO handle to alter (or NULL for default handle) 1557 * @flags Flags to be set (XOF_*) 1558 */ 1559void 1560xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags) 1561{ 1562 xop = xo_default(xop); 1563 1564 xop->xo_flags |= flags; 1565} 1566 1567xo_xof_flags_t 1568xo_get_flags (xo_handle_t *xop) 1569{ 1570 xop = xo_default(xop); 1571 1572 return xop->xo_flags; 1573} 1574 1575/** 1576 * Record a leading prefix for the XPath we generate. This allows the 1577 * generated data to be placed within an XML hierarchy but still have 1578 * accurate XPath expressions. 1579 * 1580 * @xop XO handle to alter (or NULL for default handle) 1581 * @path The XPath expression 1582 */ 1583void 1584xo_set_leading_xpath (xo_handle_t *xop, const char *path) 1585{ 1586 xop = xo_default(xop); 1587 1588 if (xop->xo_leading_xpath) { 1589 xo_free(xop->xo_leading_xpath); 1590 xop->xo_leading_xpath = NULL; 1591 } 1592 1593 if (path == NULL) 1594 return; 1595 1596 int len = strlen(path); 1597 xop->xo_leading_xpath = xo_realloc(NULL, len + 1); 1598 if (xop->xo_leading_xpath) { 1599 memcpy(xop->xo_leading_xpath, path, len + 1); 1600 } 1601} 1602 1603/** 1604 * Record the info data for a set of tags 1605 * 1606 * @xop XO handle to alter (or NULL for default handle) 1607 * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED) 1608 * @count Number of entries in info (or -1 to count them ourselves) 1609 */ 1610void 1611xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count) 1612{ 1613 xop = xo_default(xop); 1614 1615 if (count < 0 && infop) { 1616 xo_info_t *xip; 1617 1618 for (xip = infop, count = 0; xip->xi_name; xip++, count++) 1619 continue; 1620 } 1621 1622 xop->xo_info = infop; 1623 xop->xo_info_count = count; 1624} 1625 1626/** 1627 * Set the formatter callback for a handle. The callback should 1628 * return a newly formatting contents of a formatting instruction, 1629 * meaning the bits inside the braces. 1630 */ 1631void 1632xo_set_formatter (xo_handle_t *xop, xo_formatter_t func, 1633 xo_checkpointer_t cfunc) 1634{ 1635 xop = xo_default(xop); 1636 1637 xop->xo_formatter = func; 1638 xop->xo_checkpointer = cfunc; 1639} 1640 1641/** 1642 * Clear one or more flags for a given handle (or default if handle is NULL). 1643 * These flags will affect future output. 1644 * 1645 * @xop XO handle to alter (or NULL for default handle) 1646 * @flags Flags to be cleared (XOF_*) 1647 */ 1648void 1649xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags) 1650{ 1651 xop = xo_default(xop); 1652 1653 xop->xo_flags &= ~flags; 1654} 1655 1656static void 1657xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED) 1658{ 1659 static char div_open[] = "<div class=\"line\">"; 1660 static char div_open_blank[] = "<div class=\"blank-line\">"; 1661 1662 if (xop->xo_flags & XOF_DIV_OPEN) 1663 return; 1664 1665 if (xop->xo_style != XO_STYLE_HTML) 1666 return; 1667 1668 xop->xo_flags |= XOF_DIV_OPEN; 1669 if (flags & XFF_BLANK_LINE) 1670 xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1); 1671 else 1672 xo_data_append(xop, div_open, sizeof(div_open) - 1); 1673 1674 if (xop->xo_flags & XOF_PRETTY) 1675 xo_data_append(xop, "\n", 1); 1676} 1677 1678static void 1679xo_line_close (xo_handle_t *xop) 1680{ 1681 static char div_close[] = "</div>"; 1682 1683 switch (xop->xo_style) { 1684 case XO_STYLE_HTML: 1685 if (!(xop->xo_flags & XOF_DIV_OPEN)) 1686 xo_line_ensure_open(xop, 0); 1687 1688 xop->xo_flags &= ~XOF_DIV_OPEN; 1689 xo_data_append(xop, div_close, sizeof(div_close) - 1); 1690 1691 if (xop->xo_flags & XOF_PRETTY) 1692 xo_data_append(xop, "\n", 1); 1693 break; 1694 1695 case XO_STYLE_TEXT: 1696 xo_data_append(xop, "\n", 1); 1697 break; 1698 } 1699} 1700 1701static int 1702xo_info_compare (const void *key, const void *data) 1703{ 1704 const char *name = key; 1705 const xo_info_t *xip = data; 1706 1707 return strcmp(name, xip->xi_name); 1708} 1709 1710 1711static xo_info_t * 1712xo_info_find (xo_handle_t *xop, const char *name, int nlen) 1713{ 1714 xo_info_t *xip; 1715 char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */ 1716 1717 memcpy(cp, name, nlen); 1718 cp[nlen] = '\0'; 1719 1720 xip = bsearch(cp, xop->xo_info, xop->xo_info_count, 1721 sizeof(xop->xo_info[0]), xo_info_compare); 1722 return xip; 1723} 1724 1725#define CONVERT(_have, _need) (((_have) << 8) | (_need)) 1726 1727/* 1728 * Check to see that the conversion is safe and sane. 1729 */ 1730static int 1731xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc) 1732{ 1733 switch (CONVERT(have_enc, need_enc)) { 1734 case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8): 1735 case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE): 1736 case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8): 1737 case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE): 1738 case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE): 1739 case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8): 1740 return 0; 1741 1742 default: 1743 xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc); 1744 return 1; 1745 } 1746} 1747 1748static int 1749xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, 1750 xo_xff_flags_t flags, 1751 const wchar_t *wcp, const char *cp, int len, int max, 1752 int need_enc, int have_enc) 1753{ 1754 int cols = 0; 1755 wchar_t wc = 0; 1756 int ilen, olen, width; 1757 int attr = (flags & XFF_ATTR); 1758 const char *sp; 1759 1760 if (len > 0 && !xo_buf_has_room(xbp, len)) 1761 return 0; 1762 1763 for (;;) { 1764 if (len == 0) 1765 break; 1766 1767 if (cp) { 1768 if (*cp == '\0') 1769 break; 1770 if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) { 1771 cp += 1; 1772 len -= 1; 1773 } 1774 } 1775 1776 if (wcp && *wcp == L'\0') 1777 break; 1778 1779 ilen = 0; 1780 1781 switch (have_enc) { 1782 case XF_ENC_WIDE: /* Wide character */ 1783 wc = *wcp++; 1784 ilen = 1; 1785 break; 1786 1787 case XF_ENC_UTF8: /* UTF-8 */ 1788 ilen = xo_utf8_to_wc_len(cp); 1789 if (ilen < 0) { 1790 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp); 1791 return -1; 1792 } 1793 1794 if (len > 0 && len < ilen) { 1795 len = 0; /* Break out of the loop */ 1796 continue; 1797 } 1798 1799 wc = xo_utf8_char(cp, ilen); 1800 if (wc == (wchar_t) -1) { 1801 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d", 1802 *cp, ilen); 1803 return -1; 1804 } 1805 cp += ilen; 1806 break; 1807 1808 case XF_ENC_LOCALE: /* Native locale */ 1809 ilen = (len > 0) ? len : MB_LEN_MAX; 1810 ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate); 1811 if (ilen < 0) { /* Invalid data; skip */ 1812 xo_failure(xop, "invalid mbs char: %02hhx", *cp); 1813 continue; 1814 } 1815 if (ilen == 0) { /* Hit a wide NUL character */ 1816 len = 0; 1817 continue; 1818 } 1819 1820 cp += ilen; 1821 break; 1822 } 1823 1824 /* Reduce len, but not below zero */ 1825 if (len > 0) { 1826 len -= ilen; 1827 if (len < 0) 1828 len = 0; 1829 } 1830 1831 /* 1832 * Find the width-in-columns of this character, which must be done 1833 * in wide characters, since we lack a mbswidth() function. If 1834 * it doesn't fit 1835 */ 1836 width = wcwidth(wc); 1837 if (width < 0) 1838 width = iswcntrl(wc) ? 0 : 1; 1839 1840 if (xop->xo_style == XO_STYLE_TEXT || xop->xo_style == XO_STYLE_HTML) { 1841 if (max > 0 && cols + width > max) 1842 break; 1843 } 1844 1845 switch (need_enc) { 1846 case XF_ENC_UTF8: 1847 1848 /* Output in UTF-8 needs to be escaped, based on the style */ 1849 switch (xop->xo_style) { 1850 case XO_STYLE_XML: 1851 case XO_STYLE_HTML: 1852 if (wc == '<') 1853 sp = xo_xml_lt; 1854 else if (wc == '>') 1855 sp = xo_xml_gt; 1856 else if (wc == '&') 1857 sp = xo_xml_amp; 1858 else if (attr && wc == '"') 1859 sp = xo_xml_quot; 1860 else 1861 break; 1862 1863 int slen = strlen(sp); 1864 if (!xo_buf_has_room(xbp, slen - 1)) 1865 return -1; 1866 1867 memcpy(xbp->xb_curp, sp, slen); 1868 xbp->xb_curp += slen; 1869 goto done_with_encoding; /* Need multi-level 'break' */ 1870 1871 case XO_STYLE_JSON: 1872 if (wc != '\\' && wc != '"') 1873 break; 1874 1875 if (!xo_buf_has_room(xbp, 2)) 1876 return -1; 1877 1878 *xbp->xb_curp++ = '\\'; 1879 *xbp->xb_curp++ = wc & 0x7f; 1880 goto done_with_encoding; 1881 } 1882 1883 olen = xo_utf8_emit_len(wc); 1884 if (olen < 0) { 1885 xo_failure(xop, "ignoring bad length"); 1886 continue; 1887 } 1888 1889 if (!xo_buf_has_room(xbp, olen)) 1890 return -1; 1891 1892 xo_utf8_emit_char(xbp->xb_curp, olen, wc); 1893 xbp->xb_curp += olen; 1894 break; 1895 1896 case XF_ENC_LOCALE: 1897 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) 1898 return -1; 1899 1900 olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); 1901 if (olen <= 0) { 1902 xo_failure(xop, "could not convert wide char: %lx", 1903 (unsigned long) wc); 1904 olen = 1; 1905 width = 1; 1906 *xbp->xb_curp++ = '?'; 1907 } else 1908 xbp->xb_curp += olen; 1909 break; 1910 } 1911 1912 done_with_encoding: 1913 cols += width; 1914 } 1915 1916 return cols; 1917} 1918 1919static int 1920xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags, 1921 xo_format_t *xfp) 1922{ 1923 static char null[] = "(null)"; 1924 1925 char *cp = NULL; 1926 wchar_t *wcp = NULL; 1927 int len, cols = 0, rc = 0; 1928 int off = xbp->xb_curp - xbp->xb_bufp, off2; 1929 int need_enc = (xop->xo_style == XO_STYLE_TEXT) 1930 ? XF_ENC_LOCALE : XF_ENC_UTF8; 1931 1932 if (xo_check_conversion(xop, xfp->xf_enc, need_enc)) 1933 return 0; 1934 1935 len = xfp->xf_width[XF_WIDTH_SIZE]; 1936 1937 if (xfp->xf_enc == XF_ENC_WIDE) { 1938 wcp = va_arg(xop->xo_vap, wchar_t *); 1939 if (xfp->xf_skip) 1940 return 0; 1941 1942 /* 1943 * Dont' deref NULL; use the traditional "(null)" instead 1944 * of the more accurate "who's been a naughty boy, then?". 1945 */ 1946 if (wcp == NULL) { 1947 cp = null; 1948 len = sizeof(null) - 1; 1949 } 1950 1951 } else { 1952 cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */ 1953 if (xfp->xf_skip) 1954 return 0; 1955 1956 /* Echo "Dont' deref NULL" logic */ 1957 if (cp == NULL) { 1958 cp = null; 1959 len = sizeof(null) - 1; 1960 } 1961 1962 /* 1963 * Optimize the most common case, which is "%s". We just 1964 * need to copy the complete string to the output buffer. 1965 */ 1966 if (xfp->xf_enc == need_enc 1967 && xfp->xf_width[XF_WIDTH_MIN] < 0 1968 && xfp->xf_width[XF_WIDTH_SIZE] < 0 1969 && xfp->xf_width[XF_WIDTH_MAX] < 0 1970 && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) { 1971 len = strlen(cp); 1972 xo_buf_escape(xop, xbp, cp, len, flags); 1973 1974 /* 1975 * Our caller expects xb_curp left untouched, so we have 1976 * to reset it and return the number of bytes written to 1977 * the buffer. 1978 */ 1979 off2 = xbp->xb_curp - xbp->xb_bufp; 1980 rc = off2 - off; 1981 xbp->xb_curp = xbp->xb_bufp + off; 1982 1983 return rc; 1984 } 1985 } 1986 1987 cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len, 1988 xfp->xf_width[XF_WIDTH_MAX], 1989 need_enc, xfp->xf_enc); 1990 if (cols < 0) 1991 goto bail; 1992 1993 /* 1994 * xo_buf_append* will move xb_curp, so we save/restore it. 1995 */ 1996 off2 = xbp->xb_curp - xbp->xb_bufp; 1997 rc = off2 - off; 1998 xbp->xb_curp = xbp->xb_bufp + off; 1999 2000 if (cols < xfp->xf_width[XF_WIDTH_MIN]) { 2001 /* 2002 * Find the number of columns needed to display the string. 2003 * If we have the original wide string, we just call wcswidth, 2004 * but if we did the work ourselves, then we need to do it. 2005 */ 2006 int delta = xfp->xf_width[XF_WIDTH_MIN] - cols; 2007 if (!xo_buf_has_room(xbp, delta)) 2008 goto bail; 2009 2010 /* 2011 * If seen_minus, then pad on the right; otherwise move it so 2012 * we can pad on the left. 2013 */ 2014 if (xfp->xf_seen_minus) { 2015 cp = xbp->xb_curp + rc; 2016 } else { 2017 cp = xbp->xb_curp; 2018 memmove(xbp->xb_curp + delta, xbp->xb_curp, rc); 2019 } 2020 2021 /* Set the padding */ 2022 memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta); 2023 rc += delta; 2024 cols += delta; 2025 } 2026 2027 if (xop->xo_flags & XOF_COLUMNS) 2028 xop->xo_columns += cols; 2029 if (xop->xo_flags & XOF_ANCHOR) 2030 xop->xo_anchor_columns += cols; 2031 2032 return rc; 2033 2034 bail: 2035 xbp->xb_curp = xbp->xb_bufp + off; 2036 return 0; 2037} 2038 2039static void 2040xo_data_append_content (xo_handle_t *xop, const char *str, int len) 2041{ 2042 int cols; 2043 int need_enc = (xop->xo_style == XO_STYLE_TEXT) 2044 ? XF_ENC_LOCALE : XF_ENC_UTF8; 2045 2046 cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE, 2047 NULL, str, len, -1, 2048 need_enc, XF_ENC_UTF8); 2049 2050 if (xop->xo_flags & XOF_COLUMNS) 2051 xop->xo_columns += cols; 2052 if (xop->xo_flags & XOF_ANCHOR) 2053 xop->xo_anchor_columns += cols; 2054} 2055 2056static void 2057xo_bump_width (xo_format_t *xfp, int digit) 2058{ 2059 int *ip = &xfp->xf_width[xfp->xf_dots]; 2060 2061 *ip = ((*ip > 0) ? *ip : 0) * 10 + digit; 2062} 2063 2064static int 2065xo_trim_ws (xo_buffer_t *xbp, int len) 2066{ 2067 char *cp, *sp, *ep; 2068 int delta; 2069 2070 /* First trim leading space */ 2071 for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 2072 if (*cp != ' ') 2073 break; 2074 } 2075 2076 delta = cp - sp; 2077 if (delta) { 2078 len -= delta; 2079 memmove(sp, cp, len); 2080 } 2081 2082 /* Then trim off the end */ 2083 for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) { 2084 if (ep[-1] != ' ') 2085 break; 2086 } 2087 2088 delta = sp - ep; 2089 if (delta) { 2090 len -= delta; 2091 cp[len] = '\0'; 2092 } 2093 2094 return len; 2095} 2096 2097static int 2098xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp, 2099 const char *fmt, int flen, xo_xff_flags_t flags) 2100{ 2101 xo_format_t xf; 2102 const char *cp, *ep, *sp, *xp = NULL; 2103 int rc, cols; 2104 int style = (flags & XFF_XML) ? XO_STYLE_XML : xop->xo_style; 2105 unsigned make_output = !(flags & XFF_NO_OUTPUT); 2106 int need_enc = (xop->xo_style == XO_STYLE_TEXT) 2107 ? XF_ENC_LOCALE : XF_ENC_UTF8; 2108 2109 if (xbp == NULL) 2110 xbp = &xop->xo_data; 2111 2112 for (cp = fmt, ep = fmt + flen; cp < ep; cp++) { 2113 if (*cp != '%') { 2114 add_one: 2115 if (xp == NULL) 2116 xp = cp; 2117 2118 if (*cp == '\\' && cp[1] != '\0') 2119 cp += 1; 2120 continue; 2121 2122 } if (cp + 1 < ep && cp[1] == '%') { 2123 cp += 1; 2124 goto add_one; 2125 } 2126 2127 if (xp) { 2128 if (make_output) { 2129 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, 2130 NULL, xp, cp - xp, -1, 2131 need_enc, XF_ENC_UTF8); 2132 if (xop->xo_flags & XOF_COLUMNS) 2133 xop->xo_columns += cols; 2134 if (xop->xo_flags & XOF_ANCHOR) 2135 xop->xo_anchor_columns += cols; 2136 } 2137 2138 xp = NULL; 2139 } 2140 2141 bzero(&xf, sizeof(xf)); 2142 xf.xf_leading_zero = -1; 2143 xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1; 2144 2145 /* 2146 * "%@" starts an XO-specific set of flags: 2147 * @X@ - XML-only field; ignored if style isn't XML 2148 */ 2149 if (cp[1] == '@') { 2150 for (cp += 2; cp < ep; cp++) { 2151 if (*cp == '@') { 2152 break; 2153 } 2154 if (*cp == '*') { 2155 /* 2156 * '*' means there's a "%*.*s" value in vap that 2157 * we want to ignore 2158 */ 2159 if (!(xop->xo_flags & XOF_NO_VA_ARG)) 2160 va_arg(xop->xo_vap, int); 2161 } 2162 } 2163 } 2164 2165 /* Hidden fields are only visible to JSON and XML */ 2166 if (xop->xo_flags & XFF_ENCODE_ONLY) { 2167 if (style != XO_STYLE_XML 2168 && xop->xo_style != XO_STYLE_JSON) 2169 xf.xf_skip = 1; 2170 } else if (xop->xo_flags & XFF_DISPLAY_ONLY) { 2171 if (style != XO_STYLE_TEXT 2172 && xop->xo_style != XO_STYLE_HTML) 2173 xf.xf_skip = 1; 2174 } 2175 2176 if (!make_output) 2177 xf.xf_skip = 1; 2178 2179 /* 2180 * Looking at one piece of a format; find the end and 2181 * call snprintf. Then advance xo_vap on our own. 2182 * 2183 * Note that 'n', 'v', and '$' are not supported. 2184 */ 2185 sp = cp; /* Save start pointer */ 2186 for (cp += 1; cp < ep; cp++) { 2187 if (*cp == 'l') 2188 xf.xf_lflag += 1; 2189 else if (*cp == 'h') 2190 xf.xf_hflag += 1; 2191 else if (*cp == 'j') 2192 xf.xf_jflag += 1; 2193 else if (*cp == 't') 2194 xf.xf_tflag += 1; 2195 else if (*cp == 'z') 2196 xf.xf_zflag += 1; 2197 else if (*cp == 'q') 2198 xf.xf_qflag += 1; 2199 else if (*cp == '.') { 2200 if (++xf.xf_dots >= XF_WIDTH_NUM) { 2201 xo_failure(xop, "Too many dots in format: '%s'", fmt); 2202 return -1; 2203 } 2204 } else if (*cp == '-') 2205 xf.xf_seen_minus = 1; 2206 else if (isdigit((int) *cp)) { 2207 if (xf.xf_leading_zero < 0) 2208 xf.xf_leading_zero = (*cp == '0'); 2209 xo_bump_width(&xf, *cp - '0'); 2210 } else if (*cp == '*') { 2211 xf.xf_stars += 1; 2212 xf.xf_star[xf.xf_dots] = 1; 2213 } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL) 2214 break; 2215 else if (*cp == 'n' || *cp == 'v') { 2216 xo_failure(xop, "unsupported format: '%s'", fmt); 2217 return -1; 2218 } 2219 } 2220 2221 if (cp == ep) 2222 xo_failure(xop, "field format missing format character: %s", 2223 fmt); 2224 2225 xf.xf_fc = *cp; 2226 2227 if (!(xop->xo_flags & XOF_NO_VA_ARG)) { 2228 if (*cp == 's' || *cp == 'S') { 2229 /* Handle "%*.*.*s" */ 2230 int s; 2231 for (s = 0; s < XF_WIDTH_NUM; s++) { 2232 if (xf.xf_star[s]) { 2233 xf.xf_width[s] = va_arg(xop->xo_vap, int); 2234 2235 /* Normalize a negative width value */ 2236 if (xf.xf_width[s] < 0) { 2237 if (s == 0) { 2238 xf.xf_width[0] = -xf.xf_width[0]; 2239 xf.xf_seen_minus = 1; 2240 } else 2241 xf.xf_width[s] = -1; /* Ignore negative values */ 2242 } 2243 } 2244 } 2245 } 2246 } 2247 2248 /* If no max is given, it defaults to size */ 2249 if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0) 2250 xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE]; 2251 2252 if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U') 2253 xf.xf_lflag = 1; 2254 2255 if (!xf.xf_skip) { 2256 xo_buffer_t *fbp = &xop->xo_fmt; 2257 int len = cp - sp + 1; 2258 if (!xo_buf_has_room(fbp, len + 1)) 2259 return -1; 2260 2261 char *newfmt = fbp->xb_curp; 2262 memcpy(newfmt, sp, len); 2263 newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */ 2264 newfmt[len] = '\0'; 2265 2266 /* 2267 * Bad news: our strings are UTF-8, but the stock printf 2268 * functions won't handle field widths for wide characters 2269 * correctly. So we have to handle this ourselves. 2270 */ 2271 if (xop->xo_formatter == NULL 2272 && (xf.xf_fc == 's' || xf.xf_fc == 'S')) { 2273 xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S')) 2274 ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8; 2275 rc = xo_format_string(xop, xbp, flags, &xf); 2276 2277 if ((flags & XFF_TRIM_WS) 2278 && (xop->xo_style == XO_STYLE_XML 2279 || xop->xo_style == XO_STYLE_JSON)) 2280 rc = xo_trim_ws(xbp, rc); 2281 2282 } else { 2283 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap); 2284 2285 /* 2286 * For XML and HTML, we need "&<>" processing; for JSON, 2287 * it's quotes. Text gets nothing. 2288 */ 2289 switch (style) { 2290 case XO_STYLE_XML: 2291 if (flags & XFF_TRIM_WS) 2292 columns = rc = xo_trim_ws(xbp, rc); 2293 /* fall thru */ 2294 case XO_STYLE_HTML: 2295 rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR)); 2296 break; 2297 2298 case XO_STYLE_JSON: 2299 if (flags & XFF_TRIM_WS) 2300 columns = rc = xo_trim_ws(xbp, rc); 2301 rc = xo_escape_json(xbp, rc); 2302 break; 2303 } 2304 2305 /* 2306 * We can assume all the data we've added is ASCII, so 2307 * the columns and bytes are the same. xo_format_string 2308 * handles all the fancy string conversions and updates 2309 * xo_anchor_columns accordingly. 2310 */ 2311 if (xop->xo_flags & XOF_COLUMNS) 2312 xop->xo_columns += columns; 2313 if (xop->xo_flags & XOF_ANCHOR) 2314 xop->xo_anchor_columns += columns; 2315 } 2316 2317 xbp->xb_curp += rc; 2318 } 2319 2320 /* 2321 * Now for the tricky part: we need to move the argument pointer 2322 * along by the amount needed. 2323 */ 2324 if (!(xop->xo_flags & XOF_NO_VA_ARG)) { 2325 2326 if (xf.xf_fc == 's' ||xf.xf_fc == 'S') { 2327 /* 2328 * The 'S' and 's' formats are normally handled in 2329 * xo_format_string, but if we skipped it, then we 2330 * need to pop it. 2331 */ 2332 if (xf.xf_skip) 2333 va_arg(xop->xo_vap, char *); 2334 2335 } else { 2336 int s; 2337 for (s = 0; s < XF_WIDTH_NUM; s++) { 2338 if (xf.xf_star[s]) 2339 va_arg(xop->xo_vap, int); 2340 } 2341 2342 if (strchr("diouxXDOU", xf.xf_fc) != NULL) { 2343 if (xf.xf_hflag > 1) { 2344 va_arg(xop->xo_vap, int); 2345 2346 } else if (xf.xf_hflag > 0) { 2347 va_arg(xop->xo_vap, int); 2348 2349 } else if (xf.xf_lflag > 1) { 2350 va_arg(xop->xo_vap, unsigned long long); 2351 2352 } else if (xf.xf_lflag > 0) { 2353 va_arg(xop->xo_vap, unsigned long); 2354 2355 } else if (xf.xf_jflag > 0) { 2356 va_arg(xop->xo_vap, intmax_t); 2357 2358 } else if (xf.xf_tflag > 0) { 2359 va_arg(xop->xo_vap, ptrdiff_t); 2360 2361 } else if (xf.xf_zflag > 0) { 2362 va_arg(xop->xo_vap, size_t); 2363 2364 } else if (xf.xf_qflag > 0) { 2365 va_arg(xop->xo_vap, quad_t); 2366 2367 } else { 2368 va_arg(xop->xo_vap, int); 2369 } 2370 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL) 2371 if (xf.xf_lflag) 2372 va_arg(xop->xo_vap, long double); 2373 else 2374 va_arg(xop->xo_vap, double); 2375 2376 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag)) 2377 va_arg(xop->xo_vap, wint_t); 2378 2379 else if (xf.xf_fc == 'c') 2380 va_arg(xop->xo_vap, int); 2381 2382 else if (xf.xf_fc == 'p') 2383 va_arg(xop->xo_vap, void *); 2384 } 2385 } 2386 } 2387 2388 if (xp) { 2389 if (make_output) { 2390 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, 2391 NULL, xp, cp - xp, -1, 2392 need_enc, XF_ENC_UTF8); 2393 if (xop->xo_flags & XOF_COLUMNS) 2394 xop->xo_columns += cols; 2395 if (xop->xo_flags & XOF_ANCHOR) 2396 xop->xo_anchor_columns += cols; 2397 } 2398 2399 xp = NULL; 2400 } 2401 2402 return 0; 2403} 2404 2405static char * 2406xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding) 2407{ 2408 char *cp = encoding; 2409 2410 if (cp[0] != '%' || !isdigit((int) cp[1])) 2411 return encoding; 2412 2413 for (cp += 2; *cp; cp++) { 2414 if (!isdigit((int) *cp)) 2415 break; 2416 } 2417 2418 cp -= 1; 2419 *cp = '%'; 2420 2421 return cp; 2422} 2423 2424static void 2425xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 2426 const char *name, int nlen, 2427 const char *value, int vlen, 2428 const char *encoding, int elen) 2429{ 2430 static char div_start[] = "<div class=\""; 2431 static char div_tag[] = "\" data-tag=\""; 2432 static char div_xpath[] = "\" data-xpath=\""; 2433 static char div_key[] = "\" data-key=\"key"; 2434 static char div_end[] = "\">"; 2435 static char div_close[] = "</div>"; 2436 2437 /* 2438 * To build our XPath predicate, we need to save the va_list before 2439 * we format our data, and then restore it before we format the 2440 * xpath expression. 2441 * Display-only keys implies that we've got an encode-only key 2442 * elsewhere, so we don't use them from making predicates. 2443 */ 2444 int need_predidate = 2445 (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY) 2446 && (xop->xo_flags & XOF_XPATH)); 2447 2448 if (need_predidate) { 2449 va_list va_local; 2450 2451 va_copy(va_local, xop->xo_vap); 2452 if (xop->xo_checkpointer) 2453 xop->xo_checkpointer(xop, xop->xo_vap, 0); 2454 2455 /* 2456 * Build an XPath predicate expression to match this key. 2457 * We use the format buffer. 2458 */ 2459 xo_buffer_t *pbp = &xop->xo_predicate; 2460 pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */ 2461 2462 xo_buf_append(pbp, "[", 1); 2463 xo_buf_escape(xop, pbp, name, nlen, 0); 2464 if (xop->xo_flags & XOF_PRETTY) 2465 xo_buf_append(pbp, " = '", 4); 2466 else 2467 xo_buf_append(pbp, "='", 2); 2468 2469 /* The encoding format defaults to the normal format */ 2470 if (encoding == NULL) { 2471 char *enc = alloca(vlen + 1); 2472 memcpy(enc, value, vlen); 2473 enc[vlen] = '\0'; 2474 encoding = xo_fix_encoding(xop, enc); 2475 elen = strlen(encoding); 2476 } 2477 2478 xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR); 2479 2480 xo_buf_append(pbp, "']", 2); 2481 2482 /* Now we record this predicate expression in the stack */ 2483 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 2484 int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0; 2485 int dlen = pbp->xb_curp - pbp->xb_bufp; 2486 2487 char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1); 2488 if (cp) { 2489 memcpy(cp + olen, pbp->xb_bufp, dlen); 2490 cp[olen + dlen] = '\0'; 2491 xsp->xs_keys = cp; 2492 } 2493 2494 /* Now we reset the xo_vap as if we were never here */ 2495 va_end(xop->xo_vap); 2496 va_copy(xop->xo_vap, va_local); 2497 va_end(va_local); 2498 if (xop->xo_checkpointer) 2499 xop->xo_checkpointer(xop, xop->xo_vap, 1); 2500 } 2501 2502 if (flags & XFF_ENCODE_ONLY) { 2503 /* 2504 * Even if this is encode-only, we need to go thru the 2505 * work of formatting it to make sure the args are cleared 2506 * from xo_vap. 2507 */ 2508 xo_format_data(xop, &xop->xo_data, encoding, elen, 2509 flags | XFF_NO_OUTPUT); 2510 return; 2511 } 2512 2513 xo_line_ensure_open(xop, 0); 2514 2515 if (xop->xo_flags & XOF_PRETTY) 2516 xo_buf_indent(xop, xop->xo_indent_by); 2517 2518 xo_data_append(xop, div_start, sizeof(div_start) - 1); 2519 xo_data_append(xop, class, strlen(class)); 2520 2521 if (name) { 2522 xo_data_append(xop, div_tag, sizeof(div_tag) - 1); 2523 xo_data_escape(xop, name, nlen); 2524 2525 /* 2526 * Save the offset at which we'd place units. See xo_format_units. 2527 */ 2528 if (xop->xo_flags & XOF_UNITS) { 2529 xop->xo_flags |= XOF_UNITS_PENDING; 2530 /* 2531 * Note: We need the '+1' here because we know we've not 2532 * added the closing quote. We add one, knowing the quote 2533 * will be added shortly. 2534 */ 2535 xop->xo_units_offset = 2536 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1; 2537 } 2538 } 2539 2540 if (name) { 2541 if (xop->xo_flags & XOF_XPATH) { 2542 int i; 2543 xo_stack_t *xsp; 2544 2545 xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1); 2546 if (xop->xo_leading_xpath) 2547 xo_data_append(xop, xop->xo_leading_xpath, 2548 strlen(xop->xo_leading_xpath)); 2549 2550 for (i = 0; i <= xop->xo_depth; i++) { 2551 xsp = &xop->xo_stack[i]; 2552 if (xsp->xs_name == NULL) 2553 continue; 2554 2555 xo_data_append(xop, "/", 1); 2556 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name)); 2557 if (xsp->xs_keys) { 2558 /* Don't show keys for the key field */ 2559 if (i != xop->xo_depth || !(flags & XFF_KEY)) 2560 xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys)); 2561 } 2562 } 2563 2564 xo_data_append(xop, "/", 1); 2565 xo_data_escape(xop, name, nlen); 2566 } 2567 2568 if ((xop->xo_flags & XOF_INFO) && xop->xo_info) { 2569 static char in_type[] = "\" data-type=\""; 2570 static char in_help[] = "\" data-help=\""; 2571 2572 xo_info_t *xip = xo_info_find(xop, name, nlen); 2573 if (xip) { 2574 if (xip->xi_type) { 2575 xo_data_append(xop, in_type, sizeof(in_type) - 1); 2576 xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type)); 2577 } 2578 if (xip->xi_help) { 2579 xo_data_append(xop, in_help, sizeof(in_help) - 1); 2580 xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help)); 2581 } 2582 } 2583 } 2584 2585 if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) 2586 xo_data_append(xop, div_key, sizeof(div_key) - 1); 2587 } 2588 2589 xo_data_append(xop, div_end, sizeof(div_end) - 1); 2590 2591 xo_format_data(xop, NULL, value, vlen, 0); 2592 2593 xo_data_append(xop, div_close, sizeof(div_close) - 1); 2594 2595 if (xop->xo_flags & XOF_PRETTY) 2596 xo_data_append(xop, "\n", 1); 2597} 2598 2599static void 2600xo_format_text (xo_handle_t *xop, const char *str, int len) 2601{ 2602 switch (xop->xo_style) { 2603 case XO_STYLE_TEXT: 2604 xo_buf_append_locale(xop, &xop->xo_data, str, len); 2605 break; 2606 2607 case XO_STYLE_HTML: 2608 xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0); 2609 break; 2610 } 2611} 2612 2613static void 2614xo_format_title (xo_handle_t *xop, const char *str, int len, 2615 const char *fmt, int flen) 2616{ 2617 static char div_open[] = "<div class=\"title\">"; 2618 static char div_close[] = "</div>"; 2619 2620 switch (xop->xo_style) { 2621 case XO_STYLE_XML: 2622 case XO_STYLE_JSON: 2623 /* 2624 * Even though we don't care about text, we need to do 2625 * enough parsing work to skip over the right bits of xo_vap. 2626 */ 2627 if (len == 0) 2628 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT); 2629 return; 2630 } 2631 2632 xo_buffer_t *xbp = &xop->xo_data; 2633 int start = xbp->xb_curp - xbp->xb_bufp; 2634 int left = xbp->xb_size - start; 2635 int rc; 2636 int need_enc = XF_ENC_LOCALE; 2637 2638 if (xop->xo_style == XO_STYLE_HTML) { 2639 need_enc = XF_ENC_UTF8; 2640 xo_line_ensure_open(xop, 0); 2641 if (xop->xo_flags & XOF_PRETTY) 2642 xo_buf_indent(xop, xop->xo_indent_by); 2643 xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1); 2644 } 2645 2646 start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */ 2647 if (len) { 2648 char *newfmt = alloca(flen + 1); 2649 memcpy(newfmt, fmt, flen); 2650 newfmt[flen] = '\0'; 2651 2652 /* If len is non-zero, the format string apply to the name */ 2653 char *newstr = alloca(len + 1); 2654 memcpy(newstr, str, len); 2655 newstr[len] = '\0'; 2656 2657 if (newstr[len - 1] == 's') { 2658 int cols; 2659 char *bp; 2660 2661 rc = snprintf(NULL, 0, newfmt, newstr); 2662 if (rc > 0) { 2663 /* 2664 * We have to do this the hard way, since we might need 2665 * the columns. 2666 */ 2667 bp = alloca(rc + 1); 2668 rc = snprintf(bp, rc + 1, newfmt, newstr); 2669 cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1, 2670 need_enc, XF_ENC_UTF8); 2671 if (cols > 0) { 2672 if (xop->xo_flags & XOF_COLUMNS) 2673 xop->xo_columns += cols; 2674 if (xop->xo_flags & XOF_ANCHOR) 2675 xop->xo_anchor_columns += cols; 2676 } 2677 } 2678 goto move_along; 2679 2680 } else { 2681 rc = snprintf(xbp->xb_curp, left, newfmt, newstr); 2682 if (rc > left) { 2683 if (!xo_buf_has_room(xbp, rc)) 2684 return; 2685 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 2686 rc = snprintf(xbp->xb_curp, left, newfmt, newstr); 2687 } 2688 2689 if (rc > 0) { 2690 if (xop->xo_flags & XOF_COLUMNS) 2691 xop->xo_columns += rc; 2692 if (xop->xo_flags & XOF_ANCHOR) 2693 xop->xo_anchor_columns += rc; 2694 } 2695 } 2696 2697 } else { 2698 xo_format_data(xop, NULL, fmt, flen, 0); 2699 2700 /* xo_format_data moved curp, so we need to reset it */ 2701 rc = xbp->xb_curp - (xbp->xb_bufp + start); 2702 xbp->xb_curp = xbp->xb_bufp + start; 2703 } 2704 2705 /* If we're styling HTML, then we need to escape it */ 2706 if (xop->xo_style == XO_STYLE_HTML) { 2707 rc = xo_escape_xml(xbp, rc, 0); 2708 } 2709 2710 if (rc > 0) 2711 xbp->xb_curp += rc; 2712 2713 move_along: 2714 if (xop->xo_style == XO_STYLE_HTML) { 2715 xo_data_append(xop, div_close, sizeof(div_close) - 1); 2716 if (xop->xo_flags & XOF_PRETTY) 2717 xo_data_append(xop, "\n", 1); 2718 } 2719} 2720 2721static void 2722xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags) 2723{ 2724 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) { 2725 xo_data_append(xop, ",", 1); 2726 if (!(flags & XFF_LEAF_LIST) && (xop->xo_flags & XOF_PRETTY)) 2727 xo_data_append(xop, "\n", 1); 2728 } else 2729 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 2730} 2731 2732#if 0 2733/* Useful debugging function */ 2734void 2735xo_arg (xo_handle_t *xop); 2736void 2737xo_arg (xo_handle_t *xop) 2738{ 2739 xop = xo_default(xop); 2740 fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned)); 2741} 2742#endif /* 0 */ 2743 2744static void 2745xo_format_value (xo_handle_t *xop, const char *name, int nlen, 2746 const char *format, int flen, 2747 const char *encoding, int elen, xo_xff_flags_t flags) 2748{ 2749 int pretty = (xop->xo_flags & XOF_PRETTY); 2750 int quote; 2751 xo_buffer_t *xbp; 2752 2753 switch (xop->xo_style) { 2754 case XO_STYLE_TEXT: 2755 if (flags & XFF_ENCODE_ONLY) 2756 flags |= XFF_NO_OUTPUT; 2757 xo_format_data(xop, NULL, format, flen, flags); 2758 break; 2759 2760 case XO_STYLE_HTML: 2761 if (flags & XFF_ENCODE_ONLY) 2762 flags |= XFF_NO_OUTPUT; 2763 xo_buf_append_div(xop, "data", flags, name, nlen, 2764 format, flen, encoding, elen); 2765 break; 2766 2767 case XO_STYLE_XML: 2768 /* 2769 * Even though we're not making output, we still need to 2770 * let the formatting code handle the va_arg popping. 2771 */ 2772 if (flags & XFF_DISPLAY_ONLY) { 2773 flags |= XFF_NO_OUTPUT; 2774 xo_format_data(xop, NULL, format, flen, flags); 2775 break; 2776 } 2777 2778 if (encoding) { 2779 format = encoding; 2780 flen = elen; 2781 } else { 2782 char *enc = alloca(flen + 1); 2783 memcpy(enc, format, flen); 2784 enc[flen] = '\0'; 2785 format = xo_fix_encoding(xop, enc); 2786 flen = strlen(format); 2787 } 2788 2789 if (nlen == 0) { 2790 static char missing[] = "missing-field-name"; 2791 xo_failure(xop, "missing field name: %s", format); 2792 name = missing; 2793 nlen = sizeof(missing) - 1; 2794 } 2795 2796 if (pretty) 2797 xo_buf_indent(xop, -1); 2798 xo_data_append(xop, "<", 1); 2799 xo_data_escape(xop, name, nlen); 2800 2801 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { 2802 xo_data_append(xop, xop->xo_attrs.xb_bufp, 2803 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); 2804 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; 2805 } 2806 2807 /* 2808 * We indicate 'key' fields using the 'key' attribute. While 2809 * this is really committing the crime of mixing meta-data with 2810 * data, it's often useful. Especially when format meta-data is 2811 * difficult to come by. 2812 */ 2813 if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) { 2814 static char attr[] = " key=\"key\""; 2815 xo_data_append(xop, attr, sizeof(attr) - 1); 2816 } 2817 2818 /* 2819 * Save the offset at which we'd place units. See xo_format_units. 2820 */ 2821 if (xop->xo_flags & XOF_UNITS) { 2822 xop->xo_flags |= XOF_UNITS_PENDING; 2823 xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp; 2824 } 2825 2826 xo_data_append(xop, ">", 1); 2827 xo_format_data(xop, NULL, format, flen, flags); 2828 xo_data_append(xop, "</", 2); 2829 xo_data_escape(xop, name, nlen); 2830 xo_data_append(xop, ">", 1); 2831 if (pretty) 2832 xo_data_append(xop, "\n", 1); 2833 break; 2834 2835 case XO_STYLE_JSON: 2836 if (flags & XFF_DISPLAY_ONLY) { 2837 flags |= XFF_NO_OUTPUT; 2838 xo_format_data(xop, NULL, format, flen, flags); 2839 break; 2840 } 2841 2842 if (encoding) { 2843 format = encoding; 2844 flen = elen; 2845 } else { 2846 char *enc = alloca(flen + 1); 2847 memcpy(enc, format, flen); 2848 enc[flen] = '\0'; 2849 format = xo_fix_encoding(xop, enc); 2850 flen = strlen(format); 2851 } 2852 2853 int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST); 2854 2855 xo_format_prep(xop, flags); 2856 2857 if (flags & XFF_QUOTE) 2858 quote = 1; 2859 else if (flags & XFF_NOQUOTE) 2860 quote = 0; 2861 else if (flen == 0) { 2862 quote = 0; 2863 format = "true"; /* JSON encodes empty tags as a boolean true */ 2864 flen = 4; 2865 } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL) 2866 quote = 1; 2867 else 2868 quote = 0; 2869 2870 if (nlen == 0) { 2871 static char missing[] = "missing-field-name"; 2872 xo_failure(xop, "missing field name: %s", format); 2873 name = missing; 2874 nlen = sizeof(missing) - 1; 2875 } 2876 2877 if (flags & XFF_LEAF_LIST) { 2878 if (first && pretty) 2879 xo_buf_indent(xop, -1); 2880 } else { 2881 if (pretty) 2882 xo_buf_indent(xop, -1); 2883 xo_data_append(xop, "\"", 1); 2884 2885 xbp = &xop->xo_data; 2886 int off = xbp->xb_curp - xbp->xb_bufp; 2887 2888 xo_data_escape(xop, name, nlen); 2889 2890 if (xop->xo_flags & XOF_UNDERSCORES) { 2891 int now = xbp->xb_curp - xbp->xb_bufp; 2892 for ( ; off < now; off++) 2893 if (xbp->xb_bufp[off] == '-') 2894 xbp->xb_bufp[off] = '_'; 2895 } 2896 xo_data_append(xop, "\":", 2); 2897 } 2898 2899 if (pretty) 2900 xo_data_append(xop, " ", 1); 2901 if (quote) 2902 xo_data_append(xop, "\"", 1); 2903 2904 xo_format_data(xop, NULL, format, flen, flags); 2905 2906 if (quote) 2907 xo_data_append(xop, "\"", 1); 2908 break; 2909 } 2910} 2911 2912static void 2913xo_format_content (xo_handle_t *xop, const char *class_name, 2914 const char *xml_tag, int display_only, 2915 const char *str, int len, const char *fmt, int flen) 2916{ 2917 switch (xop->xo_style) { 2918 case XO_STYLE_TEXT: 2919 if (len) { 2920 xo_data_append_content(xop, str, len); 2921 } else 2922 xo_format_data(xop, NULL, fmt, flen, 0); 2923 break; 2924 2925 case XO_STYLE_HTML: 2926 if (len == 0) { 2927 str = fmt; 2928 len = flen; 2929 } 2930 2931 xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0); 2932 break; 2933 2934 case XO_STYLE_XML: 2935 if (xml_tag) { 2936 if (len == 0) { 2937 str = fmt; 2938 len = flen; 2939 } 2940 2941 xo_open_container_h(xop, xml_tag); 2942 xo_format_value(xop, "message", 7, str, len, NULL, 0, 0); 2943 xo_close_container_h(xop, xml_tag); 2944 2945 } else { 2946 /* 2947 * Even though we don't care about labels, we need to do 2948 * enough parsing work to skip over the right bits of xo_vap. 2949 */ 2950 if (len == 0) 2951 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT); 2952 } 2953 break; 2954 2955 case XO_STYLE_JSON: 2956 /* 2957 * Even though we don't care about labels, we need to do 2958 * enough parsing work to skip over the right bits of xo_vap. 2959 */ 2960 if (display_only) { 2961 if (len == 0) 2962 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT); 2963 break; 2964 } 2965 /* XXX need schem for representing errors in JSON */ 2966 break; 2967 } 2968} 2969 2970static void 2971xo_format_units (xo_handle_t *xop, const char *str, int len, 2972 const char *fmt, int flen) 2973{ 2974 static char units_start_xml[] = " units=\""; 2975 static char units_start_html[] = " data-units=\""; 2976 2977 if (!(xop->xo_flags & XOF_UNITS_PENDING)) { 2978 xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen); 2979 return; 2980 } 2981 2982 xo_buffer_t *xbp = &xop->xo_data; 2983 int start = xop->xo_units_offset; 2984 int stop = xbp->xb_curp - xbp->xb_bufp; 2985 2986 if (xop->xo_style == XO_STYLE_XML) 2987 xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1); 2988 else if (xop->xo_style == XO_STYLE_HTML) 2989 xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1); 2990 else 2991 return; 2992 2993 if (len) 2994 xo_data_append(xop, str, len); 2995 else 2996 xo_format_data(xop, NULL, fmt, flen, 0); 2997 2998 xo_buf_append(xbp, "\"", 1); 2999 3000 int now = xbp->xb_curp - xbp->xb_bufp; 3001 int delta = now - stop; 3002 if (delta < 0) { /* Strange; no output to move */ 3003 xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */ 3004 return; 3005 } 3006 3007 /* 3008 * Now we're in it alright. We've need to insert the unit value 3009 * we just created into the right spot. We make a local copy, 3010 * move it and then insert our copy. We know there's room in the 3011 * buffer, since we're just moving this around. 3012 */ 3013 char *buf = alloca(delta); 3014 3015 memcpy(buf, xbp->xb_bufp + stop, delta); 3016 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); 3017 memmove(xbp->xb_bufp + start, buf, delta); 3018} 3019 3020static int 3021xo_find_width (xo_handle_t *xop, const char *str, int len, 3022 const char *fmt, int flen) 3023{ 3024 long width = 0; 3025 char *bp; 3026 char *cp; 3027 3028 if (len) { 3029 bp = alloca(len + 1); /* Make local NUL-terminated copy of str */ 3030 memcpy(bp, str, len); 3031 bp[len] = '\0'; 3032 3033 width = strtol(bp, &cp, 0); 3034 if (width == LONG_MIN || width == LONG_MAX 3035 || bp == cp || *cp != '\0' ) { 3036 width = 0; 3037 xo_failure(xop, "invalid width for anchor: '%s'", bp); 3038 } 3039 } else if (flen) { 3040 if (flen != 2 || strncmp("%d", fmt, flen) != 0) 3041 xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt); 3042 if (!(xop->xo_flags & XOF_NO_VA_ARG)) 3043 width = va_arg(xop->xo_vap, int); 3044 } 3045 3046 return width; 3047} 3048 3049static void 3050xo_anchor_clear (xo_handle_t *xop) 3051{ 3052 xop->xo_flags &= ~XOF_ANCHOR; 3053 xop->xo_anchor_offset = 0; 3054 xop->xo_anchor_columns = 0; 3055 xop->xo_anchor_min_width = 0; 3056} 3057 3058/* 3059 * An anchor is a marker used to delay field width implications. 3060 * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}". 3061 * We are looking for output like " 1/4/5" 3062 * 3063 * To make this work, we record the anchor and then return to 3064 * format it when the end anchor tag is seen. 3065 */ 3066static void 3067xo_anchor_start (xo_handle_t *xop, const char *str, int len, 3068 const char *fmt, int flen) 3069{ 3070 if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML) 3071 return; 3072 3073 if (xop->xo_flags & XOF_ANCHOR) 3074 xo_failure(xop, "the anchor already recording is discarded"); 3075 3076 xop->xo_flags |= XOF_ANCHOR; 3077 xo_buffer_t *xbp = &xop->xo_data; 3078 xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp; 3079 xop->xo_anchor_columns = 0; 3080 3081 /* 3082 * Now we find the width, if possible. If it's not there, 3083 * we'll get it on the end anchor. 3084 */ 3085 xop->xo_anchor_min_width = xo_find_width(xop, str, len, fmt, flen); 3086} 3087 3088static void 3089xo_anchor_stop (xo_handle_t *xop, const char *str, int len, 3090 const char *fmt, int flen) 3091{ 3092 if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML) 3093 return; 3094 3095 if (!(xop->xo_flags & XOF_ANCHOR)) { 3096 xo_failure(xop, "no start anchor"); 3097 return; 3098 } 3099 3100 xop->xo_flags &= ~XOF_UNITS_PENDING; 3101 3102 int width = xo_find_width(xop, str, len, fmt, flen); 3103 if (width == 0) 3104 width = xop->xo_anchor_min_width; 3105 3106 if (width == 0) /* No width given; nothing to do */ 3107 goto done; 3108 3109 xo_buffer_t *xbp = &xop->xo_data; 3110 int start = xop->xo_anchor_offset; 3111 int stop = xbp->xb_curp - xbp->xb_bufp; 3112 int abswidth = (width > 0) ? width : -width; 3113 int blen = abswidth - xop->xo_anchor_columns; 3114 3115 if (blen <= 0) /* Already over width */ 3116 goto done; 3117 3118 if (abswidth > XO_MAX_ANCHOR_WIDTH) { 3119 xo_failure(xop, "width over %u are not supported", 3120 XO_MAX_ANCHOR_WIDTH); 3121 goto done; 3122 } 3123 3124 /* Make a suitable padding field and emit it */ 3125 char *buf = alloca(blen); 3126 memset(buf, ' ', blen); 3127 xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0); 3128 3129 if (width < 0) /* Already left justified */ 3130 goto done; 3131 3132 int now = xbp->xb_curp - xbp->xb_bufp; 3133 int delta = now - stop; 3134 if (delta < 0) /* Strange; no output to move */ 3135 goto done; 3136 3137 /* 3138 * Now we're in it alright. We've need to insert the padding data 3139 * we just created (which might be an HTML <div> or text) before 3140 * the formatted data. We make a local copy, move it and then 3141 * insert our copy. We know there's room in the buffer, since 3142 * we're just moving this around. 3143 */ 3144 if (delta > blen) 3145 buf = alloca(delta); /* Expand buffer if needed */ 3146 3147 memcpy(buf, xbp->xb_bufp + stop, delta); 3148 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); 3149 memmove(xbp->xb_bufp + start, buf, delta); 3150 3151 done: 3152 xo_anchor_clear(xop); 3153} 3154 3155static int 3156xo_do_emit (xo_handle_t *xop, const char *fmt) 3157{ 3158 int rc = 0; 3159 const char *cp, *sp, *ep, *basep; 3160 char *newp = NULL; 3161 int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0; 3162 3163 xop->xo_columns = 0; /* Always reset it */ 3164 3165 for (cp = fmt; *cp; ) { 3166 if (*cp == '\n') { 3167 xo_line_close(xop); 3168 xo_flush_h(xop); 3169 cp += 1; 3170 continue; 3171 3172 } else if (*cp == '{') { 3173 if (cp[1] == '{') { /* Start of {{escaped braces}} */ 3174 3175 cp += 2; /* Skip over _both_ characters */ 3176 for (sp = cp; *sp; sp++) { 3177 if (*sp == '}' && sp[1] == '}') 3178 break; 3179 } 3180 if (*sp == '\0') { 3181 xo_failure(xop, "missing closing '}}': %s", fmt); 3182 return -1; 3183 } 3184 3185 xo_format_text(xop, cp, sp - cp); 3186 3187 /* Move along the string, but don't run off the end */ 3188 if (*sp == '}' && sp[1] == '}') 3189 sp += 2; 3190 cp = *sp ? sp + 1 : sp; 3191 continue; 3192 } 3193 /* Else fall thru to the code below */ 3194 3195 } else { 3196 /* Normal text */ 3197 for (sp = cp; *sp; sp++) { 3198 if (*sp == '{' || *sp == '\n') 3199 break; 3200 } 3201 xo_format_text(xop, cp, sp - cp); 3202 3203 cp = sp; 3204 continue; 3205 } 3206 3207 basep = cp + 1; 3208 3209 /* 3210 * We are looking at the start of a field definition. The format is: 3211 * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}' 3212 * Modifiers are optional and include the following field types: 3213 * 'D': decoration; something non-text and non-data (colons, commmas) 3214 * 'E': error message 3215 * 'L': label; text preceding data 3216 * 'N': note; text following data 3217 * 'P': padding; whitespace 3218 * 'T': Title, where 'content' is a column title 3219 * 'U': Units, where 'content' is the unit label 3220 * 'V': value, where 'content' is the name of the field (the default) 3221 * 'W': warning message 3222 * '[': start a section of anchored text 3223 * ']': end a section of anchored text 3224 * The following flags are also supported: 3225 * 'c': flag: emit a colon after the label 3226 * 'd': field is only emitted for display formats (text and html) 3227 * 'e': field is only emitted for encoding formats (xml and json) 3228 * 'k': this field is a key, suitable for XPath predicates 3229 * 'l': a leaf-list, a simple list of values 3230 * 'n': no quotes around this field 3231 * 'q': add quotes around this field 3232 * 't': trim whitespace around the value 3233 * 'w': emit a blank after the label 3234 * The print-fmt and encode-fmt strings is the printf-style formating 3235 * for this data. JSON and XML will use the encoding-fmt, if present. 3236 * If the encode-fmt is not provided, it defaults to the print-fmt. 3237 * If the print-fmt is not provided, it defaults to 's'. 3238 */ 3239 unsigned ftype = 0, flags = 0; 3240 const char *content = NULL, *format = NULL, *encoding = NULL; 3241 int clen = 0, flen = 0, elen = 0; 3242 3243 for (sp = basep; sp; sp++) { 3244 if (*sp == ':' || *sp == '/' || *sp == '}') 3245 break; 3246 3247 if (*sp == '\\') { 3248 if (sp[1] == '\0') { 3249 xo_failure(xop, "backslash at the end of string"); 3250 return -1; 3251 } 3252 sp += 1; 3253 continue; 3254 } 3255 3256 switch (*sp) { 3257 case 'D': 3258 case 'E': 3259 case 'L': 3260 case 'N': 3261 case 'P': 3262 case 'T': 3263 case 'U': 3264 case 'V': 3265 case 'W': 3266 case '[': 3267 case ']': 3268 if (ftype != 0) { 3269 xo_failure(xop, "field descriptor uses multiple types: %s", 3270 fmt); 3271 return -1; 3272 } 3273 ftype = *sp; 3274 break; 3275 3276 case 'c': 3277 flags |= XFF_COLON; 3278 break; 3279 3280 case 'd': 3281 flags |= XFF_DISPLAY_ONLY; 3282 break; 3283 3284 case 'e': 3285 flags |= XFF_ENCODE_ONLY; 3286 break; 3287 3288 case 'k': 3289 flags |= XFF_KEY; 3290 break; 3291 3292 case 'l': 3293 flags |= XFF_LEAF_LIST; 3294 break; 3295 3296 case 'n': 3297 flags |= XFF_NOQUOTE; 3298 break; 3299 3300 case 'q': 3301 flags |= XFF_QUOTE; 3302 break; 3303 3304 case 't': 3305 flags |= XFF_TRIM_WS; 3306 break; 3307 3308 case 'w': 3309 flags |= XFF_WS; 3310 break; 3311 3312 default: 3313 xo_failure(xop, "field descriptor uses unknown modifier: %s", 3314 fmt); 3315 /* 3316 * No good answer here; a bad format will likely 3317 * mean a core file. We just return and hope 3318 * the caller notices there's no output, and while 3319 * that seems, well, bad. There's nothing better. 3320 */ 3321 return -1; 3322 } 3323 } 3324 3325 if (*sp == ':') { 3326 for (ep = ++sp; *sp; sp++) { 3327 if (*sp == '}' || *sp == '/') 3328 break; 3329 if (*sp == '\\') { 3330 if (sp[1] == '\0') { 3331 xo_failure(xop, "backslash at the end of string"); 3332 return -1; 3333 } 3334 sp += 1; 3335 continue; 3336 } 3337 } 3338 if (ep != sp) { 3339 clen = sp - ep; 3340 content = ep; 3341 } 3342 } else { 3343 xo_failure(xop, "missing content (':'): %s", fmt); 3344 return -1; 3345 } 3346 3347 if (*sp == '/') { 3348 for (ep = ++sp; *sp; sp++) { 3349 if (*sp == '}' || *sp == '/') 3350 break; 3351 if (*sp == '\\') { 3352 if (sp[1] == '\0') { 3353 xo_failure(xop, "backslash at the end of string"); 3354 return -1; 3355 } 3356 sp += 1; 3357 continue; 3358 } 3359 } 3360 flen = sp - ep; 3361 format = ep; 3362 } 3363 3364 if (*sp == '/') { 3365 for (ep = ++sp; *sp; sp++) { 3366 if (*sp == '}') 3367 break; 3368 } 3369 elen = sp - ep; 3370 encoding = ep; 3371 } 3372 3373 if (*sp == '}') { 3374 sp += 1; 3375 } else { 3376 xo_failure(xop, "missing closing '}': %s", fmt); 3377 return -1; 3378 } 3379 3380 if (format == NULL && ftype != '[' && ftype != ']' ) { 3381 format = "%s"; 3382 flen = 2; 3383 } 3384 3385 if (ftype == 0 || ftype == 'V') 3386 xo_format_value(xop, content, clen, format, flen, 3387 encoding, elen, flags); 3388 else if (ftype == 'D') 3389 xo_format_content(xop, "decoration", NULL, 1, 3390 content, clen, format, flen); 3391 else if (ftype == 'E') 3392 xo_format_content(xop, "error", "error", 0, 3393 content, clen, format, flen); 3394 else if (ftype == 'L') 3395 xo_format_content(xop, "label", NULL, 1, 3396 content, clen, format, flen); 3397 else if (ftype == 'N') 3398 xo_format_content(xop, "note", NULL, 1, 3399 content, clen, format, flen); 3400 else if (ftype == 'P') 3401 xo_format_content(xop, "padding", NULL, 1, 3402 content, clen, format, flen); 3403 else if (ftype == 'T') 3404 xo_format_title(xop, content, clen, format, flen); 3405 else if (ftype == 'U') { 3406 if (flags & XFF_WS) 3407 xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0); 3408 xo_format_units(xop, content, clen, format, flen); 3409 } else if (ftype == 'W') 3410 xo_format_content(xop, "warning", "warning", 0, 3411 content, clen, format, flen); 3412 else if (ftype == '[') 3413 xo_anchor_start(xop, content, clen, format, flen); 3414 else if (ftype == ']') 3415 xo_anchor_stop(xop, content, clen, format, flen); 3416 3417 if (flags & XFF_COLON) 3418 xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0); 3419 if (ftype != 'U' && (flags & XFF_WS)) 3420 xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0); 3421 3422 cp += sp - basep + 1; 3423 if (newp) { 3424 xo_free(newp); 3425 newp = NULL; 3426 } 3427 } 3428 3429 /* If we don't have an anchor, write the text out */ 3430 if (flush && !(xop->xo_flags & XOF_ANCHOR)) 3431 xo_write(xop); 3432 3433 return (rc < 0) ? rc : (int) xop->xo_columns; 3434} 3435 3436int 3437xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap) 3438{ 3439 int rc; 3440 3441 xop = xo_default(xop); 3442 va_copy(xop->xo_vap, vap); 3443 rc = xo_do_emit(xop, fmt); 3444 va_end(xop->xo_vap); 3445 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 3446 3447 return rc; 3448} 3449 3450int 3451xo_emit_h (xo_handle_t *xop, const char *fmt, ...) 3452{ 3453 int rc; 3454 3455 xop = xo_default(xop); 3456 va_start(xop->xo_vap, fmt); 3457 rc = xo_do_emit(xop, fmt); 3458 va_end(xop->xo_vap); 3459 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 3460 3461 return rc; 3462} 3463 3464int 3465xo_emit (const char *fmt, ...) 3466{ 3467 xo_handle_t *xop = xo_default(NULL); 3468 int rc; 3469 3470 va_start(xop->xo_vap, fmt); 3471 rc = xo_do_emit(xop, fmt); 3472 va_end(xop->xo_vap); 3473 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 3474 3475 return rc; 3476} 3477 3478int 3479xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap) 3480{ 3481 const int extra = 5; /* space, equals, quote, quote, and nul */ 3482 xop = xo_default(xop); 3483 3484 if (xop->xo_style != XO_STYLE_XML) 3485 return 0; 3486 3487 int nlen = strlen(name); 3488 xo_buffer_t *xbp = &xop->xo_attrs; 3489 3490 if (!xo_buf_has_room(xbp, nlen + extra)) 3491 return -1; 3492 3493 *xbp->xb_curp++ = ' '; 3494 memcpy(xbp->xb_curp, name, nlen); 3495 xbp->xb_curp += nlen; 3496 *xbp->xb_curp++ = '='; 3497 *xbp->xb_curp++ = '"'; 3498 3499 int rc = xo_vsnprintf(xop, xbp, fmt, vap); 3500 3501 if (rc > 0) { 3502 rc = xo_escape_xml(xbp, rc, 1); 3503 xbp->xb_curp += rc; 3504 } 3505 3506 if (!xo_buf_has_room(xbp, 2)) 3507 return -1; 3508 3509 *xbp->xb_curp++ = '"'; 3510 *xbp->xb_curp = '\0'; 3511 3512 return rc + nlen + extra; 3513} 3514 3515int 3516xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...) 3517{ 3518 int rc; 3519 va_list vap; 3520 3521 va_start(vap, fmt); 3522 rc = xo_attr_hv(xop, name, fmt, vap); 3523 va_end(vap); 3524 3525 return rc; 3526} 3527 3528int 3529xo_attr (const char *name, const char *fmt, ...) 3530{ 3531 int rc; 3532 va_list vap; 3533 3534 va_start(vap, fmt); 3535 rc = xo_attr_hv(NULL, name, fmt, vap); 3536 va_end(vap); 3537 3538 return rc; 3539} 3540 3541static void 3542xo_stack_set_flags (xo_handle_t *xop) 3543{ 3544 if (xop->xo_flags & XOF_NOT_FIRST) { 3545 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3546 3547 xsp->xs_flags |= XSF_NOT_FIRST; 3548 xop->xo_flags &= ~XOF_NOT_FIRST; 3549 } 3550} 3551 3552static void 3553xo_depth_change (xo_handle_t *xop, const char *name, 3554 int delta, int indent, xo_xsf_flags_t flags) 3555{ 3556 if (xop->xo_flags & XOF_DTRT) 3557 flags |= XSF_DTRT; 3558 3559 if (delta >= 0) { /* Push operation */ 3560 if (xo_depth_check(xop, xop->xo_depth + delta)) 3561 return; 3562 3563 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta]; 3564 xsp->xs_flags = flags; 3565 xo_stack_set_flags(xop); 3566 3567 unsigned save = (xop->xo_flags & (XOF_XPATH | XOF_WARN | XOF_DTRT)); 3568 save |= (flags & XSF_DTRT); 3569 3570 if (name && save) { 3571 int len = strlen(name) + 1; 3572 char *cp = xo_realloc(NULL, len); 3573 if (cp) { 3574 memcpy(cp, name, len); 3575 xsp->xs_name = cp; 3576 } 3577 } 3578 3579 } else { /* Pop operation */ 3580 if (xop->xo_depth == 0) { 3581 if (!(xop->xo_flags & XOF_IGNORE_CLOSE)) 3582 xo_failure(xop, "close with empty stack: '%s'", name); 3583 return; 3584 } 3585 3586 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3587 if (xop->xo_flags & XOF_WARN) { 3588 const char *top = xsp->xs_name; 3589 if (top && strcmp(name, top) != 0) { 3590 xo_failure(xop, "incorrect close: '%s' .vs. '%s'", 3591 name, top); 3592 return; 3593 } 3594 if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) { 3595 xo_failure(xop, "list close on list confict: '%s'", 3596 name); 3597 return; 3598 } 3599 if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) { 3600 xo_failure(xop, "list close on instance confict: '%s'", 3601 name); 3602 return; 3603 } 3604 } 3605 3606 if (xsp->xs_name) { 3607 xo_free(xsp->xs_name); 3608 xsp->xs_name = NULL; 3609 } 3610 if (xsp->xs_keys) { 3611 xo_free(xsp->xs_keys); 3612 xsp->xs_keys = NULL; 3613 } 3614 } 3615 3616 xop->xo_depth += delta; /* Record new depth */ 3617 xop->xo_indent += indent; 3618} 3619 3620void 3621xo_set_depth (xo_handle_t *xop, int depth) 3622{ 3623 xop = xo_default(xop); 3624 3625 if (xo_depth_check(xop, depth)) 3626 return; 3627 3628 xop->xo_depth += depth; 3629 xop->xo_indent += depth; 3630} 3631 3632static xo_xsf_flags_t 3633xo_stack_flags (unsigned xflags) 3634{ 3635 if (xflags & XOF_DTRT) 3636 return XSF_DTRT; 3637 return 0; 3638} 3639 3640static int 3641xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) 3642{ 3643 xop = xo_default(xop); 3644 3645 int rc = 0; 3646 const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3647 const char *pre_nl = ""; 3648 3649 if (name == NULL) { 3650 xo_failure(xop, "NULL passed for container name"); 3651 name = XO_FAILURE_NAME; 3652 } 3653 3654 flags |= xop->xo_flags; /* Pick up handle flags */ 3655 3656 switch (xop->xo_style) { 3657 case XO_STYLE_XML: 3658 rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", 3659 name, ppn); 3660 xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); 3661 break; 3662 3663 case XO_STYLE_JSON: 3664 xo_stack_set_flags(xop); 3665 3666 if (!(xop->xo_flags & XOF_NO_TOP)) { 3667 if (!(xop->xo_flags & XOF_TOP_EMITTED)) { 3668 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); 3669 xop->xo_flags |= XOF_TOP_EMITTED; 3670 } 3671 } 3672 3673 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 3674 pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", "; 3675 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3676 3677 rc = xo_printf(xop, "%s%*s\"%s\": {%s", 3678 pre_nl, xo_indent(xop), "", name, ppn); 3679 xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); 3680 break; 3681 3682 case XO_STYLE_HTML: 3683 case XO_STYLE_TEXT: 3684 xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags)); 3685 break; 3686 } 3687 3688 return rc; 3689} 3690 3691int 3692xo_open_container_h (xo_handle_t *xop, const char *name) 3693{ 3694 return xo_open_container_hf(xop, 0, name); 3695} 3696 3697int 3698xo_open_container (const char *name) 3699{ 3700 return xo_open_container_hf(NULL, 0, name); 3701} 3702 3703int 3704xo_open_container_hd (xo_handle_t *xop, const char *name) 3705{ 3706 return xo_open_container_hf(xop, XOF_DTRT, name); 3707} 3708 3709int 3710xo_open_container_d (const char *name) 3711{ 3712 return xo_open_container_hf(NULL, XOF_DTRT, name); 3713} 3714 3715int 3716xo_close_container_h (xo_handle_t *xop, const char *name) 3717{ 3718 xop = xo_default(xop); 3719 3720 int rc = 0; 3721 const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3722 const char *pre_nl = ""; 3723 3724 if (name == NULL) { 3725 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3726 if (!(xsp->xs_flags & XSF_DTRT)) 3727 xo_failure(xop, "missing name without 'dtrt' mode"); 3728 3729 name = xsp->xs_name; 3730 if (name) { 3731 int len = strlen(name) + 1; 3732 /* We need to make a local copy; xo_depth_change will free it */ 3733 char *cp = alloca(len); 3734 memcpy(cp, name, len); 3735 name = cp; 3736 } else 3737 name = XO_FAILURE_NAME; 3738 } 3739 3740 switch (xop->xo_style) { 3741 case XO_STYLE_XML: 3742 xo_depth_change(xop, name, -1, -1, 0); 3743 rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn); 3744 break; 3745 3746 case XO_STYLE_JSON: 3747 pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3748 ppn = (xop->xo_depth <= 1) ? "\n" : ""; 3749 3750 xo_depth_change(xop, name, -1, -1, 0); 3751 rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn); 3752 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3753 break; 3754 3755 case XO_STYLE_HTML: 3756 case XO_STYLE_TEXT: 3757 xo_depth_change(xop, name, -1, 0, 0); 3758 break; 3759 } 3760 3761 return rc; 3762} 3763 3764int 3765xo_close_container (const char *name) 3766{ 3767 return xo_close_container_h(NULL, name); 3768} 3769 3770int 3771xo_close_container_hd (xo_handle_t *xop) 3772{ 3773 return xo_close_container_h(xop, NULL); 3774} 3775 3776int 3777xo_close_container_d (void) 3778{ 3779 return xo_close_container_h(NULL, NULL); 3780} 3781 3782static int 3783xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 3784{ 3785 xop = xo_default(xop); 3786 3787 if (xop->xo_style != XO_STYLE_JSON) 3788 return 0; 3789 3790 int rc = 0; 3791 const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3792 const char *pre_nl = ""; 3793 3794 if (!(xop->xo_flags & XOF_NO_TOP)) { 3795 if (!(xop->xo_flags & XOF_TOP_EMITTED)) { 3796 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); 3797 xop->xo_flags |= XOF_TOP_EMITTED; 3798 } 3799 } 3800 3801 if (name == NULL) { 3802 xo_failure(xop, "NULL passed for list name"); 3803 name = XO_FAILURE_NAME; 3804 } 3805 3806 xo_stack_set_flags(xop); 3807 3808 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 3809 pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", "; 3810 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3811 3812 rc = xo_printf(xop, "%s%*s\"%s\": [%s", 3813 pre_nl, xo_indent(xop), "", name, ppn); 3814 xo_depth_change(xop, name, 1, 1, XSF_LIST | xo_stack_flags(flags)); 3815 3816 return rc; 3817} 3818 3819int 3820xo_open_list_h (xo_handle_t *xop, const char *name UNUSED) 3821{ 3822 return xo_open_list_hf(xop, 0, name); 3823} 3824 3825int 3826xo_open_list (const char *name) 3827{ 3828 return xo_open_list_hf(NULL, 0, name); 3829} 3830 3831int 3832xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED) 3833{ 3834 return xo_open_list_hf(xop, XOF_DTRT, name); 3835} 3836 3837int 3838xo_open_list_d (const char *name) 3839{ 3840 return xo_open_list_hf(NULL, XOF_DTRT, name); 3841} 3842 3843int 3844xo_close_list_h (xo_handle_t *xop, const char *name) 3845{ 3846 int rc = 0; 3847 const char *pre_nl = ""; 3848 3849 xop = xo_default(xop); 3850 3851 if (xop->xo_style != XO_STYLE_JSON) 3852 return 0; 3853 3854 if (name == NULL) { 3855 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3856 if (!(xsp->xs_flags & XSF_DTRT)) 3857 xo_failure(xop, "missing name without 'dtrt' mode"); 3858 3859 name = xsp->xs_name; 3860 if (name) { 3861 int len = strlen(name) + 1; 3862 /* We need to make a local copy; xo_depth_change will free it */ 3863 char *cp = alloca(len); 3864 memcpy(cp, name, len); 3865 name = cp; 3866 } else 3867 name = XO_FAILURE_NAME; 3868 } 3869 3870 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 3871 pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3872 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3873 3874 xo_depth_change(xop, name, -1, -1, XSF_LIST); 3875 rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); 3876 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3877 3878 return rc; 3879} 3880 3881int 3882xo_close_list (const char *name) 3883{ 3884 return xo_close_list_h(NULL, name); 3885} 3886 3887int 3888xo_close_list_hd (xo_handle_t *xop) 3889{ 3890 return xo_close_list_h(xop, NULL); 3891} 3892 3893int 3894xo_close_list_d (void) 3895{ 3896 return xo_close_list_h(NULL, NULL); 3897} 3898 3899static int 3900xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 3901{ 3902 xop = xo_default(xop); 3903 3904 int rc = 0; 3905 const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3906 const char *pre_nl = ""; 3907 3908 flags |= xop->xo_flags; 3909 3910 if (name == NULL) { 3911 xo_failure(xop, "NULL passed for instance name"); 3912 name = XO_FAILURE_NAME; 3913 } 3914 3915 switch (xop->xo_style) { 3916 case XO_STYLE_XML: 3917 rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", name, ppn); 3918 xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); 3919 break; 3920 3921 case XO_STYLE_JSON: 3922 xo_stack_set_flags(xop); 3923 3924 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 3925 pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", "; 3926 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3927 3928 rc = xo_printf(xop, "%s%*s{%s", 3929 pre_nl, xo_indent(xop), "", ppn); 3930 xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); 3931 break; 3932 3933 case XO_STYLE_HTML: 3934 case XO_STYLE_TEXT: 3935 xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags)); 3936 break; 3937 } 3938 3939 return rc; 3940} 3941 3942int 3943xo_open_instance_h (xo_handle_t *xop, const char *name) 3944{ 3945 return xo_open_instance_hf(xop, 0, name); 3946} 3947 3948int 3949xo_open_instance (const char *name) 3950{ 3951 return xo_open_instance_hf(NULL, 0, name); 3952} 3953 3954int 3955xo_open_instance_hd (xo_handle_t *xop, const char *name) 3956{ 3957 return xo_open_instance_hf(xop, XOF_DTRT, name); 3958} 3959 3960int 3961xo_open_instance_d (const char *name) 3962{ 3963 return xo_open_instance_hf(NULL, XOF_DTRT, name); 3964} 3965 3966int 3967xo_close_instance_h (xo_handle_t *xop, const char *name) 3968{ 3969 xop = xo_default(xop); 3970 3971 int rc = 0; 3972 const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3973 const char *pre_nl = ""; 3974 3975 if (name == NULL) { 3976 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3977 if (!(xsp->xs_flags & XSF_DTRT)) 3978 xo_failure(xop, "missing name without 'dtrt' mode"); 3979 3980 name = xsp->xs_name; 3981 if (name) { 3982 int len = strlen(name) + 1; 3983 /* We need to make a local copy; xo_depth_change will free it */ 3984 char *cp = alloca(len); 3985 memcpy(cp, name, len); 3986 name = cp; 3987 } else 3988 name = XO_FAILURE_NAME; 3989 } 3990 3991 switch (xop->xo_style) { 3992 case XO_STYLE_XML: 3993 xo_depth_change(xop, name, -1, -1, 0); 3994 rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn); 3995 break; 3996 3997 case XO_STYLE_JSON: 3998 pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; 3999 4000 xo_depth_change(xop, name, -1, -1, 0); 4001 rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), ""); 4002 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 4003 break; 4004 4005 case XO_STYLE_HTML: 4006 case XO_STYLE_TEXT: 4007 xo_depth_change(xop, name, -1, 0, 0); 4008 break; 4009 } 4010 4011 return rc; 4012} 4013 4014int 4015xo_close_instance (const char *name) 4016{ 4017 return xo_close_instance_h(NULL, name); 4018} 4019 4020int 4021xo_close_instance_hd (xo_handle_t *xop) 4022{ 4023 return xo_close_instance_h(xop, NULL); 4024} 4025 4026int 4027xo_close_instance_d (void) 4028{ 4029 return xo_close_instance_h(NULL, NULL); 4030} 4031 4032void 4033xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func, 4034 xo_close_func_t close_func) 4035{ 4036 xop = xo_default(xop); 4037 4038 xop->xo_opaque = opaque; 4039 xop->xo_write = write_func; 4040 xop->xo_close = close_func; 4041} 4042 4043void 4044xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func) 4045{ 4046 xo_realloc = realloc_func; 4047 xo_free = free_func; 4048} 4049 4050void 4051xo_flush_h (xo_handle_t *xop) 4052{ 4053 static char div_close[] = "</div>"; 4054 4055 xop = xo_default(xop); 4056 4057 switch (xop->xo_style) { 4058 case XO_STYLE_HTML: 4059 if (xop->xo_flags & XOF_DIV_OPEN) { 4060 xop->xo_flags &= ~XOF_DIV_OPEN; 4061 xo_data_append(xop, div_close, sizeof(div_close) - 1); 4062 4063 if (xop->xo_flags & XOF_PRETTY) 4064 xo_data_append(xop, "\n", 1); 4065 } 4066 break; 4067 } 4068 4069 xo_write(xop); 4070} 4071 4072void 4073xo_flush (void) 4074{ 4075 xo_flush_h(NULL); 4076} 4077 4078void 4079xo_finish_h (xo_handle_t *xop) 4080{ 4081 const char *cp = ""; 4082 xop = xo_default(xop); 4083 4084 switch (xop->xo_style) { 4085 case XO_STYLE_JSON: 4086 if (!(xop->xo_flags & XOF_NO_TOP)) { 4087 if (xop->xo_flags & XOF_TOP_EMITTED) 4088 xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */ 4089 else 4090 cp = "{ "; 4091 xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp); 4092 } 4093 break; 4094 } 4095 4096 xo_flush_h(xop); 4097} 4098 4099void 4100xo_finish (void) 4101{ 4102 xo_finish_h(NULL); 4103} 4104 4105/* 4106 * Generate an error message, such as would be displayed on stderr 4107 */ 4108void 4109xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap) 4110{ 4111 xop = xo_default(xop); 4112 4113 /* 4114 * If the format string doesn't end with a newline, we pop 4115 * one on ourselves. 4116 */ 4117 int len = strlen(fmt); 4118 if (len > 0 && fmt[len - 1] != '\n') { 4119 char *newfmt = alloca(len + 2); 4120 memcpy(newfmt, fmt, len); 4121 newfmt[len] = '\n'; 4122 newfmt[len] = '\0'; 4123 fmt = newfmt; 4124 } 4125 4126 switch (xop->xo_style) { 4127 case XO_STYLE_TEXT: 4128 vfprintf(stderr, fmt, vap); 4129 break; 4130 4131 case XO_STYLE_HTML: 4132 va_copy(xop->xo_vap, vap); 4133 4134 xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0); 4135 4136 if (xop->xo_flags & XOF_DIV_OPEN) 4137 xo_line_close(xop); 4138 4139 xo_write(xop); 4140 4141 va_end(xop->xo_vap); 4142 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 4143 break; 4144 4145 case XO_STYLE_XML: 4146 va_copy(xop->xo_vap, vap); 4147 4148 xo_open_container_h(xop, "error"); 4149 xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0); 4150 xo_close_container_h(xop, "error"); 4151 4152 va_end(xop->xo_vap); 4153 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 4154 break; 4155 } 4156} 4157 4158void 4159xo_error_h (xo_handle_t *xop, const char *fmt, ...) 4160{ 4161 va_list vap; 4162 4163 va_start(vap, fmt); 4164 xo_error_hv(xop, fmt, vap); 4165 va_end(vap); 4166} 4167 4168/* 4169 * Generate an error message, such as would be displayed on stderr 4170 */ 4171void 4172xo_error (const char *fmt, ...) 4173{ 4174 va_list vap; 4175 4176 va_start(vap, fmt); 4177 xo_error_hv(NULL, fmt, vap); 4178 va_end(vap); 4179} 4180 4181int 4182xo_parse_args (int argc, char **argv) 4183{ 4184 static char libxo_opt[] = "--libxo"; 4185 char *cp; 4186 int i, save; 4187 4188 /* Save our program name for xo_err and friends */ 4189 xo_program = argv[0]; 4190 cp = strrchr(xo_program, '/'); 4191 if (cp) 4192 xo_program = cp + 1; 4193 4194 for (save = i = 1; i < argc; i++) { 4195 if (argv[i] == NULL 4196 || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) { 4197 if (save != i) 4198 argv[save] = argv[i]; 4199 save += 1; 4200 continue; 4201 } 4202 4203 cp = argv[i] + sizeof(libxo_opt) - 1; 4204 if (*cp == 0) { 4205 cp = argv[++i]; 4206 if (cp == 0) { 4207 xo_warnx("missing libxo option"); 4208 return -1; 4209 } 4210 4211 if (xo_set_options(NULL, cp) < 0) 4212 return -1; 4213 } else if (*cp == ':') { 4214 if (xo_set_options(NULL, cp) < 0) 4215 return -1; 4216 4217 } else if (*cp == '=') { 4218 if (xo_set_options(NULL, ++cp) < 0) 4219 return -1; 4220 4221 } else if (*cp == '-') { 4222 cp += 1; 4223 if (strcmp(cp, "check") == 0) { 4224 exit(XO_HAS_LIBXO); 4225 4226 } else { 4227 xo_warnx("unknown libxo option: '%s'", argv[i]); 4228 return -1; 4229 } 4230 } else { 4231 xo_warnx("unknown libxo option: '%s'", argv[i]); 4232 return -1; 4233 } 4234 } 4235 4236 argv[save] = NULL; 4237 return save; 4238} 4239 4240#ifdef UNIT_TEST 4241int 4242main (int argc, char **argv) 4243{ 4244 static char base_grocery[] = "GRO"; 4245 static char base_hardware[] = "HRD"; 4246 struct item { 4247 const char *i_title; 4248 int i_sold; 4249 int i_instock; 4250 int i_onorder; 4251 const char *i_sku_base; 4252 int i_sku_num; 4253 }; 4254 struct item list[] = { 4255 { "gum&this&that", 1412, 54, 10, base_grocery, 415 }, 4256 { "<rope>", 85, 4, 2, base_hardware, 212 }, 4257 { "ladder", 0, 2, 1, base_hardware, 517 }, 4258 { "\"bolt\"", 4123, 144, 42, base_hardware, 632 }, 4259 { "water\\blue", 17, 14, 2, base_grocery, 2331 }, 4260 { NULL, 0, 0, 0, NULL, 0 } 4261 }; 4262 struct item list2[] = { 4263 { "fish", 1321, 45, 1, base_grocery, 533 }, 4264 { NULL, 0, 0, 0, NULL, 0 } 4265 }; 4266 struct item *ip; 4267 xo_info_t info[] = { 4268 { "in-stock", "number", "Number of items in stock" }, 4269 { "name", "string", "Name of the item" }, 4270 { "on-order", "number", "Number of items on order" }, 4271 { "sku", "string", "Stock Keeping Unit" }, 4272 { "sold", "number", "Number of items sold" }, 4273 { NULL, NULL, NULL }, 4274 }; 4275 int info_count = (sizeof(info) / sizeof(info[0])) - 1; 4276 4277 argc = xo_parse_args(argc, argv); 4278 if (argc < 0) 4279 exit(1); 4280 4281 xo_set_info(NULL, info, info_count); 4282 4283 xo_open_container_h(NULL, "top"); 4284 4285 xo_open_container("data"); 4286 xo_open_list("item"); 4287 4288 xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}" 4289 "{T:On Order/%12s}{T:SKU/%5s}\n"); 4290 4291 for (ip = list; ip->i_title; ip++) { 4292 xo_open_instance("item"); 4293 4294 xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}" 4295 "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n", 4296 ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder, 4297 ip->i_sku_base, ip->i_sku_num); 4298 4299 xo_close_instance("item"); 4300 } 4301 4302 xo_close_list("item"); 4303 xo_close_container("data"); 4304 4305 xo_emit("\n\n"); 4306 4307 xo_open_container("data"); 4308 xo_open_list("item"); 4309 4310 for (ip = list; ip->i_title; ip++) { 4311 xo_open_instance("item"); 4312 4313 xo_attr("fancy", "%s%d", "item", ip - list); 4314 xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title); 4315 xo_emit("{P: }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n", 4316 ip->i_sold, ip->i_sold ? ".0" : "", 44); 4317 xo_emit("{P: }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock); 4318 xo_emit("{P: }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder); 4319 xo_emit("{P: }{L:SKU}: {q:sku/%s-000-%u}\n", 4320 ip->i_sku_base, ip->i_sku_num); 4321 4322 xo_close_instance("item"); 4323 } 4324 4325 xo_close_list("item"); 4326 xo_close_container("data"); 4327 4328 xo_open_container("data"); 4329 xo_open_list("item"); 4330 4331 for (ip = list2; ip->i_title; ip++) { 4332 xo_open_instance("item"); 4333 4334 xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title); 4335 xo_emit("{P: }{L:Total sold}: {n:sold/%u%s}\n", 4336 ip->i_sold, ip->i_sold ? ".0" : ""); 4337 xo_emit("{P: }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock); 4338 xo_emit("{P: }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder); 4339 xo_emit("{P: }{L:SKU}: {q:sku/%s-000-%u}\n", 4340 ip->i_sku_base, ip->i_sku_num); 4341 4342 xo_open_list("month"); 4343 4344 const char *months[] = { "Jan", "Feb", "Mar", NULL }; 4345 int discounts[] = { 10, 20, 25, 0 }; 4346 int i; 4347 for (i = 0; months[i]; i++) { 4348 xo_open_instance("month"); 4349 xo_emit("{P: }" 4350 "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n", 4351 months[i], discounts[i]); 4352 xo_close_instance("month"); 4353 } 4354 4355 xo_close_list("month"); 4356 4357 xo_close_instance("item"); 4358 } 4359 4360 xo_close_list("item"); 4361 xo_close_container("data"); 4362 4363 xo_close_container_h(NULL, "top"); 4364 4365 xo_finish(); 4366 4367 return 0; 4368} 4369#endif /* UNIT_TEST */ 4370