libxo.c revision 297894
1/* 2 * Copyright (c) 2014-2015, Juniper Networks, Inc. 3 * All rights reserved. 4 * This SOFTWARE is licensed under the LICENSE provided in the 5 * ../Copyright file. By downloading, installing, copying, or otherwise 6 * using the SOFTWARE, you agree to be bound by the terms of that 7 * LICENSE. 8 * Phil Shafer, July 2014 9 * 10 * This is the implementation of libxo, the formatting library that 11 * generates multiple styles of output from a single code path. 12 * Command line utilities can have their normal text output while 13 * automation tools can see XML or JSON output, and web tools can use 14 * HTML output that encodes the text output annotated with additional 15 * information. Specialized encoders can be built that allow custom 16 * encoding including binary ones like CBOR, thrift, protobufs, etc. 17 * 18 * Full documentation is available in ./doc/libxo.txt or online at: 19 * http://juniper.github.io/libxo/libxo-manual.html 20 * 21 * For first time readers, the core bits of code to start looking at are: 22 * - xo_do_emit() -- the central function of the library 23 * - xo_do_format_field() -- handles formatting a single field 24 * - xo_transiton() -- the state machine that keeps things sane 25 * and of course the "xo_handle_t" data structure, which carries all 26 * configuration and state. 27 */ 28 29#include <stdio.h> 30#include <stdlib.h> 31#include <stdint.h> 32#include <unistd.h> 33#include <stddef.h> 34#include <wchar.h> 35#include <locale.h> 36#include <sys/types.h> 37#include <stdarg.h> 38#include <string.h> 39#include <errno.h> 40#include <limits.h> 41#include <ctype.h> 42#include <wctype.h> 43#include <getopt.h> 44 45#include "xo_config.h" 46#include "xo.h" 47#include "xo_encoder.h" 48#include "xo_buf.h" 49 50/* 51 * We ask wcwidth() to do an impossible job, really. It's supposed to 52 * need to tell us the number of columns consumed to display a unicode 53 * character. It returns that number without any sort of context, but 54 * we know they are characters whose glyph differs based on placement 55 * (end of word, middle of word, etc) and many that affect characters 56 * previously emitted. Without content, it can't hope to tell us. 57 * But it's the only standard tool we've got, so we use it. We would 58 * use wcswidth() but it typically just loops thru adding the results 59 * of wcwidth() calls in an entirely unhelpful way. 60 * 61 * Even then, there are many poor implementations (macosx), so we have 62 * to carry our own. We could have configure.ac test this (with 63 * something like 'assert(wcwidth(0x200d) == 0)'), but it would have 64 * to run a binary, which breaks cross-compilation. Hmm... I could 65 * run this test at init time and make a warning for our dear user. 66 * 67 * Anyhow, it remains a best-effort sort of thing. And it's all made 68 * more hopeless because we assume the display code doing the rendering is 69 * playing by the same rules we are. If it display 0x200d as a square 70 * box or a funky question mark, the output will be hosed. 71 */ 72#ifdef LIBXO_WCWIDTH 73#include "xo_wcwidth.h" 74#else /* LIBXO_WCWIDTH */ 75#define xo_wcwidth(_x) wcwidth(_x) 76#endif /* LIBXO_WCWIDTH */ 77 78#ifdef HAVE_STDIO_EXT_H 79#include <stdio_ext.h> 80#endif /* HAVE_STDIO_EXT_H */ 81 82/* 83 * humanize_number is a great function, unless you don't have it. So 84 * we carry one in our pocket. 85 */ 86#ifdef HAVE_HUMANIZE_NUMBER 87#include <libutil.h> 88#define xo_humanize_number humanize_number 89#else /* HAVE_HUMANIZE_NUMBER */ 90#include "xo_humanize.h" 91#endif /* HAVE_HUMANIZE_NUMBER */ 92 93#ifdef HAVE_GETTEXT 94#include <libintl.h> 95#endif /* HAVE_GETTEXT */ 96 97/* 98 * Three styles of specifying thread-local variables are supported. 99 * configure.ac has the brains to run each possibility thru the 100 * compiler and see what works; we are left to define the THREAD_LOCAL 101 * macro to the right value. Most toolchains (clang, gcc) use 102 * "before", but some (borland) use "after" and I've heard of some 103 * (ms) that use __declspec. Any others out there? 104 */ 105#define THREAD_LOCAL_before 1 106#define THREAD_LOCAL_after 2 107#define THREAD_LOCAL_declspec 3 108 109#ifndef HAVE_THREAD_LOCAL 110#define THREAD_LOCAL(_x) _x 111#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before 112#define THREAD_LOCAL(_x) __thread _x 113#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after 114#define THREAD_LOCAL(_x) _x __thread 115#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec 116#define THREAD_LOCAL(_x) __declspec(_x) 117#else 118#error unknown thread-local setting 119#endif /* HAVE_THREADS_H */ 120 121const char xo_version[] = LIBXO_VERSION; 122const char xo_version_extra[] = LIBXO_VERSION_EXTRA; 123 124#ifndef UNUSED 125#define UNUSED __attribute__ ((__unused__)) 126#endif /* UNUSED */ 127 128#define XO_INDENT_BY 2 /* Amount to indent when pretty printing */ 129#define XO_DEPTH 128 /* Default stack depth */ 130#define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */ 131 132#define XO_FAILURE_NAME "failure" 133 134/* Flags for the stack frame */ 135typedef unsigned xo_xsf_flags_t; /* XSF_* flags */ 136#define XSF_NOT_FIRST (1<<0) /* Not the first element */ 137#define XSF_LIST (1<<1) /* Frame is a list */ 138#define XSF_INSTANCE (1<<2) /* Frame is an instance */ 139#define XSF_DTRT (1<<3) /* Save the name for DTRT mode */ 140 141#define XSF_CONTENT (1<<4) /* Some content has been emitted */ 142#define XSF_EMIT (1<<5) /* Some field has been emitted */ 143#define XSF_EMIT_KEY (1<<6) /* A key has been emitted */ 144#define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */ 145 146/* These are the flags we propagate between markers and their parents */ 147#define XSF_MARKER_FLAGS \ 148 (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST ) 149 150/* 151 * A word about states: We use a finite state machine (FMS) approach 152 * to help remove fragility from the caller's code. Instead of 153 * requiring a specific order of calls, we'll allow the caller more 154 * flexibility and make the library responsible for recovering from 155 * missed steps. The goal is that the library should not be capable 156 * of emitting invalid xml or json, but the developer shouldn't need 157 * to know or understand all the details about these encodings. 158 * 159 * You can think of states as either states or events, since they 160 * function rather like both. None of the XO_CLOSE_* events will 161 * persist as states, since the matching stack frame will be popped. 162 * Same is true of XSS_EMIT, which is an event that asks us to 163 * prep for emitting output fields. 164 */ 165 166/* Stack frame states */ 167typedef unsigned xo_state_t; 168#define XSS_INIT 0 /* Initial stack state */ 169#define XSS_OPEN_CONTAINER 1 170#define XSS_CLOSE_CONTAINER 2 171#define XSS_OPEN_LIST 3 172#define XSS_CLOSE_LIST 4 173#define XSS_OPEN_INSTANCE 5 174#define XSS_CLOSE_INSTANCE 6 175#define XSS_OPEN_LEAF_LIST 7 176#define XSS_CLOSE_LEAF_LIST 8 177#define XSS_DISCARDING 9 /* Discarding data until recovered */ 178#define XSS_MARKER 10 /* xo_open_marker's marker */ 179#define XSS_EMIT 11 /* xo_emit has a leaf field */ 180#define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */ 181#define XSS_FINISH 13 /* xo_finish was called */ 182 183#define XSS_MAX 13 184 185#define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new)) 186 187/* 188 * xo_stack_t: As we open and close containers and levels, we 189 * create a stack of frames to track them. This is needed for 190 * XOF_WARN and XOF_XPATH. 191 */ 192typedef struct xo_stack_s { 193 xo_xsf_flags_t xs_flags; /* Flags for this frame */ 194 xo_state_t xs_state; /* State for this stack frame */ 195 char *xs_name; /* Name (for XPath value) */ 196 char *xs_keys; /* XPath predicate for any key fields */ 197} xo_stack_t; 198 199/* 200 * libxo supports colors and effects, for those who like them. 201 * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_* 202 * ("effects") are bits since we need to maintain state. 203 */ 204#define XO_COL_DEFAULT 0 205#define XO_COL_BLACK 1 206#define XO_COL_RED 2 207#define XO_COL_GREEN 3 208#define XO_COL_YELLOW 4 209#define XO_COL_BLUE 5 210#define XO_COL_MAGENTA 6 211#define XO_COL_CYAN 7 212#define XO_COL_WHITE 8 213 214#define XO_NUM_COLORS 9 215 216/* 217 * Yes, there's no blink. We're civilized. We like users. Blink 218 * isn't something one does to someone you like. Friends don't let 219 * friends use blink. On friends. You know what I mean. Blink is 220 * like, well, it's like bursting into show tunes at a funeral. It's 221 * just not done. Not something anyone wants. And on those rare 222 * instances where it might actually be appropriate, it's still wrong, 223 * since it's likely done by the wrong person for the wrong reason. 224 * Just like blink. And if I implemented blink, I'd be like a funeral 225 * director who adds "Would you like us to burst into show tunes?" on 226 * the list of questions asked while making funeral arrangements. 227 * It's formalizing wrongness in the wrong way. And we're just too 228 * civilized to do that. Hhhmph! 229 */ 230#define XO_EFF_RESET (1<<0) 231#define XO_EFF_NORMAL (1<<1) 232#define XO_EFF_BOLD (1<<2) 233#define XO_EFF_UNDERLINE (1<<3) 234#define XO_EFF_INVERSE (1<<4) 235 236#define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */ 237 238typedef uint8_t xo_effect_t; 239typedef uint8_t xo_color_t; 240typedef struct xo_colors_s { 241 xo_effect_t xoc_effects; /* Current effect set */ 242 xo_color_t xoc_col_fg; /* Foreground color */ 243 xo_color_t xoc_col_bg; /* Background color */ 244} xo_colors_t; 245 246/* 247 * xo_handle_t: this is the principle data structure for libxo. 248 * It's used as a store for state, options, content, and all manor 249 * of other information. 250 */ 251struct xo_handle_s { 252 xo_xof_flags_t xo_flags; /* Flags (XOF_*) from the user*/ 253 xo_xof_flags_t xo_iflags; /* Internal flags (XOIF_*) */ 254 xo_style_t xo_style; /* XO_STYLE_* value */ 255 unsigned short xo_indent; /* Indent level (if pretty) */ 256 unsigned short xo_indent_by; /* Indent amount (tab stop) */ 257 xo_write_func_t xo_write; /* Write callback */ 258 xo_close_func_t xo_close; /* Close callback */ 259 xo_flush_func_t xo_flush; /* Flush callback */ 260 xo_formatter_t xo_formatter; /* Custom formating function */ 261 xo_checkpointer_t xo_checkpointer; /* Custom formating support function */ 262 void *xo_opaque; /* Opaque data for write function */ 263 xo_buffer_t xo_data; /* Output data */ 264 xo_buffer_t xo_fmt; /* Work area for building format strings */ 265 xo_buffer_t xo_attrs; /* Work area for building XML attributes */ 266 xo_buffer_t xo_predicate; /* Work area for building XPath predicates */ 267 xo_stack_t *xo_stack; /* Stack pointer */ 268 int xo_depth; /* Depth of stack */ 269 int xo_stack_size; /* Size of the stack */ 270 xo_info_t *xo_info; /* Info fields for all elements */ 271 int xo_info_count; /* Number of info entries */ 272 va_list xo_vap; /* Variable arguments (stdargs) */ 273 char *xo_leading_xpath; /* A leading XPath expression */ 274 mbstate_t xo_mbstate; /* Multi-byte character conversion state */ 275 unsigned xo_anchor_offset; /* Start of anchored text */ 276 unsigned xo_anchor_columns; /* Number of columns since the start anchor */ 277 int xo_anchor_min_width; /* Desired width of anchored text */ 278 unsigned xo_units_offset; /* Start of units insertion point */ 279 unsigned xo_columns; /* Columns emitted during this xo_emit call */ 280 uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */ 281 uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */ 282 xo_colors_t xo_colors; /* Current color and effect values */ 283 xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */ 284 char *xo_version; /* Version string */ 285 int xo_errno; /* Saved errno for "%m" */ 286 char *xo_gt_domain; /* Gettext domain, suitable for dgettext(3) */ 287 xo_encoder_func_t xo_encoder; /* Encoding function */ 288 void *xo_private; /* Private data for external encoders */ 289}; 290 291/* Flag operations */ 292#define XOF_BIT_ISSET(_flag, _bit) (((_flag) & (_bit)) ? 1 : 0) 293#define XOF_BIT_SET(_flag, _bit) do { (_flag) |= (_bit); } while (0) 294#define XOF_BIT_CLEAR(_flag, _bit) do { (_flag) &= ~(_bit); } while (0) 295 296#define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit) 297#define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit) 298#define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit) 299 300#define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit) 301#define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit) 302#define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit) 303 304/* Internal flags */ 305#define XOIF_REORDER XOF_BIT(0) /* Reordering fields; record field info */ 306#define XOIF_DIV_OPEN XOF_BIT(1) /* A <div> is open */ 307#define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */ 308#define XOIF_ANCHOR XOF_BIT(3) /* An anchor is in place */ 309 310#define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */ 311#define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */ 312 313/* Flags for formatting functions */ 314typedef unsigned long xo_xff_flags_t; 315#define XFF_COLON (1<<0) /* Append a ":" */ 316#define XFF_COMMA (1<<1) /* Append a "," iff there's more output */ 317#define XFF_WS (1<<2) /* Append a blank */ 318#define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding styles (XML, JSON) */ 319 320#define XFF_QUOTE (1<<4) /* Force quotes */ 321#define XFF_NOQUOTE (1<<5) /* Force no quotes */ 322#define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display styles (text, html) */ 323#define XFF_KEY (1<<7) /* Field is a key (for XPath) */ 324 325#define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */ 326#define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */ 327#define XFF_BLANK_LINE (1<<10) /* Emit a blank line */ 328#define XFF_NO_OUTPUT (1<<11) /* Do not make any output */ 329 330#define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */ 331#define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */ 332#define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */ 333#define XFF_HUMANIZE (1<<15) /* Humanize the value (for display styles) */ 334 335#define XFF_HN_SPACE (1<<16) /* Humanize: put space before suffix */ 336#define XFF_HN_DECIMAL (1<<17) /* Humanize: add one decimal place if <10 */ 337#define XFF_HN_1000 (1<<18) /* Humanize: use 1000, not 1024 */ 338#define XFF_GT_FIELD (1<<19) /* Call gettext() on a field */ 339 340#define XFF_GT_PLURAL (1<<20) /* Call dngettext to find plural form */ 341 342/* Flags to turn off when we don't want i18n processing */ 343#define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL) 344 345/* 346 * Normal printf has width and precision, which for strings operate as 347 * min and max number of columns. But this depends on the idea that 348 * one byte means one column, which UTF-8 and multi-byte characters 349 * pitches on its ear. It may take 40 bytes of data to populate 14 350 * columns, but we can't go off looking at 40 bytes of data without the 351 * caller's permission for fear/knowledge that we'll generate core files. 352 * 353 * So we make three values, distinguishing between "max column" and 354 * "number of bytes that we will inspect inspect safely" We call the 355 * later "size", and make the format "%[[<min>].[[<size>].<max>]]s". 356 * 357 * Under the "first do no harm" theory, we default "max" to "size". 358 * This is a reasonable assumption for folks that don't grok the 359 * MBS/WCS/UTF-8 world, and while it will be annoying, it will never 360 * be evil. 361 * 362 * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14 363 * columns of output, but will never look at more than 14 bytes of the 364 * input buffer. This is mostly compatible with printf and caller's 365 * expectations. 366 * 367 * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however 368 * many bytes (or until a NUL is seen) are needed to fill 14 columns 369 * of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up 370 * to xx bytes (or until a NUL is seen) in order to fill 14 columns 371 * of output. 372 * 373 * It's fairly amazing how a good idea (handle all languages of the 374 * world) blows such a big hole in the bottom of the fairly weak boat 375 * that is C string handling. The simplicity and completenesss are 376 * sunk in ways we haven't even begun to understand. 377 */ 378#define XF_WIDTH_MIN 0 /* Minimal width */ 379#define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */ 380#define XF_WIDTH_MAX 2 /* Maximum width */ 381#define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */ 382 383/* Input and output string encodings */ 384#define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */ 385#define XF_ENC_UTF8 2 /* UTF-8 */ 386#define XF_ENC_LOCALE 3 /* Current locale */ 387 388/* 389 * A place to parse printf-style format flags for each field 390 */ 391typedef struct xo_format_s { 392 unsigned char xf_fc; /* Format character */ 393 unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */ 394 unsigned char xf_skip; /* Skip this field */ 395 unsigned char xf_lflag; /* 'l' (long) */ 396 unsigned char xf_hflag;; /* 'h' (half) */ 397 unsigned char xf_jflag; /* 'j' (intmax_t) */ 398 unsigned char xf_tflag; /* 't' (ptrdiff_t) */ 399 unsigned char xf_zflag; /* 'z' (size_t) */ 400 unsigned char xf_qflag; /* 'q' (quad_t) */ 401 unsigned char xf_seen_minus; /* Seen a minus */ 402 int xf_leading_zero; /* Seen a leading zero (zero fill) */ 403 unsigned xf_dots; /* Seen one or more '.'s */ 404 int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */ 405 unsigned xf_stars; /* Seen one or more '*'s */ 406 unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */ 407} xo_format_t; 408 409/* 410 * This structure represents the parsed field information, suitable for 411 * processing by xo_do_emit and anything else that needs to parse fields. 412 * Note that all pointers point to the main format string. 413 * 414 * XXX This is a first step toward compilable or cachable format 415 * strings. We can also cache the results of dgettext when no format 416 * is used, assuming the 'p' modifier has _not_ been set. 417 */ 418typedef struct xo_field_info_s { 419 xo_xff_flags_t xfi_flags; /* Flags for this field */ 420 unsigned xfi_ftype; /* Field type, as character (e.g. 'V') */ 421 const char *xfi_start; /* Start of field in the format string */ 422 const char *xfi_content; /* Field's content */ 423 const char *xfi_format; /* Field's Format */ 424 const char *xfi_encoding; /* Field's encoding format */ 425 const char *xfi_next; /* Next character in format string */ 426 unsigned xfi_len; /* Length of field */ 427 unsigned xfi_clen; /* Content length */ 428 unsigned xfi_flen; /* Format length */ 429 unsigned xfi_elen; /* Encoding length */ 430 unsigned xfi_fnum; /* Field number (if used; 0 otherwise) */ 431 unsigned xfi_renum; /* Reordered number (0 == no renumbering) */ 432} xo_field_info_t; 433 434/* 435 * We keep a 'default' handle to allow callers to avoid having to 436 * allocate one. Passing NULL to any of our functions will use 437 * this default handle. Most functions have a variant that doesn't 438 * require a handle at all, since most output is to stdout, which 439 * the default handle handles handily. 440 */ 441static THREAD_LOCAL(xo_handle_t) xo_default_handle; 442static THREAD_LOCAL(int) xo_default_inited; 443static int xo_locale_inited; 444static const char *xo_program; 445 446/* 447 * To allow libxo to be used in diverse environment, we allow the 448 * caller to give callbacks for memory allocation. 449 */ 450xo_realloc_func_t xo_realloc = realloc; 451xo_free_func_t xo_free = free; 452 453/* Forward declarations */ 454static void 455xo_failure (xo_handle_t *xop, const char *fmt, ...); 456 457static int 458xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, 459 xo_state_t new_state); 460 461static void 462xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 463 const char *name, int nlen, 464 const char *value, int vlen, 465 const char *encoding, int elen); 466 467static void 468xo_anchor_clear (xo_handle_t *xop); 469 470/* 471 * xo_style is used to retrieve the current style. When we're built 472 * for "text only" mode, we use this function to drive the removal 473 * of most of the code in libxo. We return a constant and the compiler 474 * happily removes the non-text code that is not longer executed. This 475 * trims our code nicely without needing to trampel perfectly readable 476 * code with ifdefs. 477 */ 478static inline xo_style_t 479xo_style (xo_handle_t *xop UNUSED) 480{ 481#ifdef LIBXO_TEXT_ONLY 482 return XO_STYLE_TEXT; 483#else /* LIBXO_TEXT_ONLY */ 484 return xop->xo_style; 485#endif /* LIBXO_TEXT_ONLY */ 486} 487 488/* 489 * Callback to write data to a FILE pointer 490 */ 491static int 492xo_write_to_file (void *opaque, const char *data) 493{ 494 FILE *fp = (FILE *) opaque; 495 496 return fprintf(fp, "%s", data); 497} 498 499/* 500 * Callback to close a file 501 */ 502static void 503xo_close_file (void *opaque) 504{ 505 FILE *fp = (FILE *) opaque; 506 507 fclose(fp); 508} 509 510/* 511 * Callback to flush a FILE pointer 512 */ 513static int 514xo_flush_file (void *opaque) 515{ 516 FILE *fp = (FILE *) opaque; 517 518 return fflush(fp); 519} 520 521/* 522 * Use a rotating stock of buffers to make a printable string 523 */ 524#define XO_NUMBUFS 8 525#define XO_SMBUFSZ 128 526 527static const char * 528xo_printable (const char *str) 529{ 530 static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ]; 531 static THREAD_LOCAL(int) bufnum = 0; 532 533 if (str == NULL) 534 return ""; 535 536 if (++bufnum == XO_NUMBUFS) 537 bufnum = 0; 538 539 char *res = bufset[bufnum], *cp, *ep; 540 541 for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) { 542 if (*str == '\n') { 543 *cp++ = '\\'; 544 *cp = 'n'; 545 } else if (*str == '\r') { 546 *cp++ = '\\'; 547 *cp = 'r'; 548 } else if (*str == '\"') { 549 *cp++ = '\\'; 550 *cp = '"'; 551 } else 552 *cp = *str; 553 } 554 555 *cp = '\0'; 556 return res; 557} 558 559static int 560xo_depth_check (xo_handle_t *xop, int depth) 561{ 562 xo_stack_t *xsp; 563 564 if (depth >= xop->xo_stack_size) { 565 depth += XO_DEPTH; /* Extra room */ 566 567 xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth); 568 if (xsp == NULL) { 569 xo_failure(xop, "xo_depth_check: out of memory (%d)", depth); 570 return -1; 571 } 572 573 int count = depth - xop->xo_stack_size; 574 575 bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp)); 576 xop->xo_stack_size = depth; 577 xop->xo_stack = xsp; 578 } 579 580 return 0; 581} 582 583void 584xo_no_setlocale (void) 585{ 586 xo_locale_inited = 1; /* Skip initialization */ 587} 588 589/* 590 * We need to decide if stdout is line buffered (_IOLBF). Lacking a 591 * standard way to decide this (e.g. getlinebuf()), we have configure 592 * look to find __flbf, which glibc supported. If not, we'll rely on 593 * isatty, with the assumption that terminals are the only thing 594 * that's line buffered. We _could_ test for "steam._flags & _IOLBF", 595 * which is all __flbf does, but that's even tackier. Like a 596 * bedazzled Elvis outfit on an ugly lap dog sort of tacky. Not 597 * something we're willing to do. 598 */ 599static int 600xo_is_line_buffered (FILE *stream) 601{ 602#if HAVE___FLBF 603 if (__flbf(stream)) 604 return 1; 605#else /* HAVE___FLBF */ 606 if (isatty(fileno(stream))) 607 return 1; 608#endif /* HAVE___FLBF */ 609 return 0; 610} 611 612/* 613 * Initialize an xo_handle_t, using both static defaults and 614 * the global settings from the LIBXO_OPTIONS environment 615 * variable. 616 */ 617static void 618xo_init_handle (xo_handle_t *xop) 619{ 620 xop->xo_opaque = stdout; 621 xop->xo_write = xo_write_to_file; 622 xop->xo_flush = xo_flush_file; 623 624 if (xo_is_line_buffered(stdout)) 625 XOF_SET(xop, XOF_FLUSH_LINE); 626 627 /* 628 * We only want to do color output on terminals, but we only want 629 * to do this if the user has asked for color. 630 */ 631 if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1)) 632 XOF_SET(xop, XOF_COLOR); 633 634 /* 635 * We need to initialize the locale, which isn't really pretty. 636 * Libraries should depend on their caller to set up the 637 * environment. But we really can't count on the caller to do 638 * this, because well, they won't. Trust me. 639 */ 640 if (!xo_locale_inited) { 641 xo_locale_inited = 1; /* Only do this once */ 642 643 const char *cp = getenv("LC_CTYPE"); 644 if (cp == NULL) 645 cp = getenv("LANG"); 646 if (cp == NULL) 647 cp = getenv("LC_ALL"); 648 if (cp == NULL) 649 cp = "C"; /* Default for C programs */ 650 (void) setlocale(LC_CTYPE, cp); 651 } 652 653 /* 654 * Initialize only the xo_buffers we know we'll need; the others 655 * can be allocated as needed. 656 */ 657 xo_buf_init(&xop->xo_data); 658 xo_buf_init(&xop->xo_fmt); 659 660 if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS)) 661 return; 662 XOIF_SET(xop, XOIF_INIT_IN_PROGRESS); 663 664 xop->xo_indent_by = XO_INDENT_BY; 665 xo_depth_check(xop, XO_DEPTH); 666 667#if !defined(NO_LIBXO_OPTIONS) 668 if (!XOF_ISSET(xop, XOF_NO_ENV)) { 669 char *env = getenv("LIBXO_OPTIONS"); 670 if (env) 671 xo_set_options(xop, env); 672 673 } 674#endif /* NO_GETENV */ 675 676 XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS); 677} 678 679/* 680 * Initialize the default handle. 681 */ 682static void 683xo_default_init (void) 684{ 685 xo_handle_t *xop = &xo_default_handle; 686 687 xo_init_handle(xop); 688 689 xo_default_inited = 1; 690} 691 692/* 693 * Cheap convenience function to return either the argument, or 694 * the internal handle, after it has been initialized. The usage 695 * is: 696 * xop = xo_default(xop); 697 */ 698static xo_handle_t * 699xo_default (xo_handle_t *xop) 700{ 701 if (xop == NULL) { 702 if (xo_default_inited == 0) 703 xo_default_init(); 704 xop = &xo_default_handle; 705 } 706 707 return xop; 708} 709 710/* 711 * Return the number of spaces we should be indenting. If 712 * we are pretty-printing, this is indent * indent_by. 713 */ 714static int 715xo_indent (xo_handle_t *xop) 716{ 717 int rc = 0; 718 719 xop = xo_default(xop); 720 721 if (XOF_ISSET(xop, XOF_PRETTY)) { 722 rc = xop->xo_indent * xop->xo_indent_by; 723 if (XOIF_ISSET(xop, XOIF_TOP_EMITTED)) 724 rc += xop->xo_indent_by; 725 } 726 727 return (rc > 0) ? rc : 0; 728} 729 730static void 731xo_buf_indent (xo_handle_t *xop, int indent) 732{ 733 xo_buffer_t *xbp = &xop->xo_data; 734 735 if (indent <= 0) 736 indent = xo_indent(xop); 737 738 if (!xo_buf_has_room(xbp, indent)) 739 return; 740 741 memset(xbp->xb_curp, ' ', indent); 742 xbp->xb_curp += indent; 743} 744 745static char xo_xml_amp[] = "&"; 746static char xo_xml_lt[] = "<"; 747static char xo_xml_gt[] = ">"; 748static char xo_xml_quot[] = """; 749 750static int 751xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags) 752{ 753 int slen; 754 unsigned delta = 0; 755 char *cp, *ep, *ip; 756 const char *sp; 757 int attr = (flags & XFF_ATTR); 758 759 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 760 /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */ 761 if (*cp == '<') 762 delta += sizeof(xo_xml_lt) - 2; 763 else if (*cp == '>') 764 delta += sizeof(xo_xml_gt) - 2; 765 else if (*cp == '&') 766 delta += sizeof(xo_xml_amp) - 2; 767 else if (attr && *cp == '"') 768 delta += sizeof(xo_xml_quot) - 2; 769 } 770 771 if (delta == 0) /* Nothing to escape; bail */ 772 return len; 773 774 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ 775 return 0; 776 777 ep = xbp->xb_curp; 778 cp = ep + len; 779 ip = cp + delta; 780 do { 781 cp -= 1; 782 ip -= 1; 783 784 if (*cp == '<') 785 sp = xo_xml_lt; 786 else if (*cp == '>') 787 sp = xo_xml_gt; 788 else if (*cp == '&') 789 sp = xo_xml_amp; 790 else if (attr && *cp == '"') 791 sp = xo_xml_quot; 792 else { 793 *ip = *cp; 794 continue; 795 } 796 797 slen = strlen(sp); 798 ip -= slen - 1; 799 memcpy(ip, sp, slen); 800 801 } while (cp > ep && cp != ip); 802 803 return len + delta; 804} 805 806static int 807xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED) 808{ 809 unsigned delta = 0; 810 char *cp, *ep, *ip; 811 812 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 813 if (*cp == '\\' || *cp == '"') 814 delta += 1; 815 else if (*cp == '\n' || *cp == '\r') 816 delta += 1; 817 } 818 819 if (delta == 0) /* Nothing to escape; bail */ 820 return len; 821 822 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ 823 return 0; 824 825 ep = xbp->xb_curp; 826 cp = ep + len; 827 ip = cp + delta; 828 do { 829 cp -= 1; 830 ip -= 1; 831 832 if (*cp == '\\' || *cp == '"') { 833 *ip-- = *cp; 834 *ip = '\\'; 835 } else if (*cp == '\n') { 836 *ip-- = 'n'; 837 *ip = '\\'; 838 } else if (*cp == '\r') { 839 *ip-- = 'r'; 840 *ip = '\\'; 841 } else { 842 *ip = *cp; 843 } 844 845 } while (cp > ep && cp != ip); 846 847 return len + delta; 848} 849 850/* 851 * PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and 852 * ; ']' MUST be escaped. 853 */ 854static int 855xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED) 856{ 857 unsigned delta = 0; 858 char *cp, *ep, *ip; 859 860 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 861 if (*cp == '\\' || *cp == '"' || *cp == ']') 862 delta += 1; 863 } 864 865 if (delta == 0) /* Nothing to escape; bail */ 866 return len; 867 868 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ 869 return 0; 870 871 ep = xbp->xb_curp; 872 cp = ep + len; 873 ip = cp + delta; 874 do { 875 cp -= 1; 876 ip -= 1; 877 878 if (*cp == '\\' || *cp == '"' || *cp == ']') { 879 *ip-- = *cp; 880 *ip = '\\'; 881 } else { 882 *ip = *cp; 883 } 884 885 } while (cp > ep && cp != ip); 886 887 return len + delta; 888} 889 890static void 891xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, 892 const char *str, int len, xo_xff_flags_t flags) 893{ 894 if (!xo_buf_has_room(xbp, len)) 895 return; 896 897 memcpy(xbp->xb_curp, str, len); 898 899 switch (xo_style(xop)) { 900 case XO_STYLE_XML: 901 case XO_STYLE_HTML: 902 len = xo_escape_xml(xbp, len, flags); 903 break; 904 905 case XO_STYLE_JSON: 906 len = xo_escape_json(xbp, len, flags); 907 break; 908 909 case XO_STYLE_SDPARAMS: 910 len = xo_escape_sdparams(xbp, len, flags); 911 break; 912 } 913 914 xbp->xb_curp += len; 915} 916 917/* 918 * Write the current contents of the data buffer using the handle's 919 * xo_write function. 920 */ 921static int 922xo_write (xo_handle_t *xop) 923{ 924 int rc = 0; 925 xo_buffer_t *xbp = &xop->xo_data; 926 927 if (xbp->xb_curp != xbp->xb_bufp) { 928 xo_buf_append(xbp, "", 1); /* Append ending NUL */ 929 xo_anchor_clear(xop); 930 if (xop->xo_write) 931 rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp); 932 xbp->xb_curp = xbp->xb_bufp; 933 } 934 935 /* Turn off the flags that don't survive across writes */ 936 XOIF_CLEAR(xop, XOIF_UNITS_PENDING); 937 938 return rc; 939} 940 941/* 942 * Format arguments into our buffer. If a custom formatter has been set, 943 * we use that to do the work; otherwise we vsnprintf(). 944 */ 945static int 946xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) 947{ 948 va_list va_local; 949 int rc; 950 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 951 952 va_copy(va_local, vap); 953 954 if (xop->xo_formatter) 955 rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); 956 else 957 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 958 959 if (rc >= left) { 960 if (!xo_buf_has_room(xbp, rc)) { 961 va_end(va_local); 962 return -1; 963 } 964 965 /* 966 * After we call vsnprintf(), the stage of vap is not defined. 967 * We need to copy it before we pass. Then we have to do our 968 * own logic below to move it along. This is because the 969 * implementation can have va_list be a pointer (bsd) or a 970 * structure (macosx) or anything in between. 971 */ 972 973 va_end(va_local); /* Reset vap to the start */ 974 va_copy(va_local, vap); 975 976 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 977 if (xop->xo_formatter) 978 rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); 979 else 980 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 981 } 982 va_end(va_local); 983 984 return rc; 985} 986 987/* 988 * Print some data thru the handle. 989 */ 990static int 991xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap) 992{ 993 xo_buffer_t *xbp = &xop->xo_data; 994 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 995 int rc; 996 va_list va_local; 997 998 va_copy(va_local, vap); 999 1000 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 1001 1002 if (rc >= left) { 1003 if (!xo_buf_has_room(xbp, rc)) { 1004 va_end(va_local); 1005 return -1; 1006 } 1007 1008 va_end(va_local); /* Reset vap to the start */ 1009 va_copy(va_local, vap); 1010 1011 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1012 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); 1013 } 1014 1015 va_end(va_local); 1016 1017 if (rc > 0) 1018 xbp->xb_curp += rc; 1019 1020 return rc; 1021} 1022 1023static int 1024xo_printf (xo_handle_t *xop, const char *fmt, ...) 1025{ 1026 int rc; 1027 va_list vap; 1028 1029 va_start(vap, fmt); 1030 1031 rc = xo_printf_v(xop, fmt, vap); 1032 1033 va_end(vap); 1034 return rc; 1035} 1036 1037/* 1038 * These next few function are make The Essential UTF-8 Ginsu Knife. 1039 * Identify an input and output character, and convert it. 1040 */ 1041static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; 1042 1043static int 1044xo_is_utf8 (char ch) 1045{ 1046 return (ch & 0x80); 1047} 1048 1049static int 1050xo_utf8_to_wc_len (const char *buf) 1051{ 1052 unsigned b = (unsigned char) *buf; 1053 int len; 1054 1055 if ((b & 0x80) == 0x0) 1056 len = 1; 1057 else if ((b & 0xe0) == 0xc0) 1058 len = 2; 1059 else if ((b & 0xf0) == 0xe0) 1060 len = 3; 1061 else if ((b & 0xf8) == 0xf0) 1062 len = 4; 1063 else if ((b & 0xfc) == 0xf8) 1064 len = 5; 1065 else if ((b & 0xfe) == 0xfc) 1066 len = 6; 1067 else 1068 len = -1; 1069 1070 return len; 1071} 1072 1073static int 1074xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz) 1075{ 1076 1077 unsigned b = (unsigned char) *buf; 1078 int len, i; 1079 1080 len = xo_utf8_to_wc_len(buf); 1081 if (len == -1) { 1082 xo_failure(xop, "invalid UTF-8 data: %02hhx", b); 1083 return -1; 1084 } 1085 1086 if (len > bufsiz) { 1087 xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)", 1088 b, len, bufsiz); 1089 return -1; 1090 } 1091 1092 for (i = 2; i < len; i++) { 1093 b = (unsigned char ) buf[i]; 1094 if ((b & 0xc0) != 0x80) { 1095 xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b); 1096 return -1; 1097 } 1098 } 1099 1100 return len; 1101} 1102 1103/* 1104 * Build a wide character from the input buffer; the number of 1105 * bits we pull off the first character is dependent on the length, 1106 * but we put 6 bits off all other bytes. 1107 */ 1108static wchar_t 1109xo_utf8_char (const char *buf, int len) 1110{ 1111 int i; 1112 wchar_t wc; 1113 const unsigned char *cp = (const unsigned char *) buf; 1114 1115 wc = *cp & xo_utf8_bits[len]; 1116 for (i = 1; i < len; i++) { 1117 wc <<= 6; 1118 wc |= cp[i] & 0x3f; 1119 if ((cp[i] & 0xc0) != 0x80) 1120 return (wchar_t) -1; 1121 } 1122 1123 return wc; 1124} 1125 1126/* 1127 * Determine the number of bytes needed to encode a wide character. 1128 */ 1129static int 1130xo_utf8_emit_len (wchar_t wc) 1131{ 1132 int len; 1133 1134 if ((wc & ((1<<7) - 1)) == wc) /* Simple case */ 1135 len = 1; 1136 else if ((wc & ((1<<11) - 1)) == wc) 1137 len = 2; 1138 else if ((wc & ((1<<16) - 1)) == wc) 1139 len = 3; 1140 else if ((wc & ((1<<21) - 1)) == wc) 1141 len = 4; 1142 else if ((wc & ((1<<26) - 1)) == wc) 1143 len = 5; 1144 else 1145 len = 6; 1146 1147 return len; 1148} 1149 1150static void 1151xo_utf8_emit_char (char *buf, int len, wchar_t wc) 1152{ 1153 int i; 1154 1155 if (len == 1) { /* Simple case */ 1156 buf[0] = wc & 0x7f; 1157 return; 1158 } 1159 1160 for (i = len - 1; i >= 0; i--) { 1161 buf[i] = 0x80 | (wc & 0x3f); 1162 wc >>= 6; 1163 } 1164 1165 buf[0] &= xo_utf8_bits[len]; 1166 buf[0] |= ~xo_utf8_bits[len] << 1; 1167} 1168 1169static int 1170xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp, 1171 const char *ibuf, int ilen) 1172{ 1173 wchar_t wc; 1174 int len; 1175 1176 /* 1177 * Build our wide character from the input buffer; the number of 1178 * bits we pull off the first character is dependent on the length, 1179 * but we put 6 bits off all other bytes. 1180 */ 1181 wc = xo_utf8_char(ibuf, ilen); 1182 if (wc == (wchar_t) -1) { 1183 xo_failure(xop, "invalid utf-8 byte sequence"); 1184 return 0; 1185 } 1186 1187 if (XOF_ISSET(xop, XOF_NO_LOCALE)) { 1188 if (!xo_buf_has_room(xbp, ilen)) 1189 return 0; 1190 1191 memcpy(xbp->xb_curp, ibuf, ilen); 1192 xbp->xb_curp += ilen; 1193 1194 } else { 1195 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) 1196 return 0; 1197 1198 bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate)); 1199 len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); 1200 1201 if (len <= 0) { 1202 xo_failure(xop, "could not convert wide char: %lx", 1203 (unsigned long) wc); 1204 return 0; 1205 } 1206 xbp->xb_curp += len; 1207 } 1208 1209 return xo_wcwidth(wc); 1210} 1211 1212static void 1213xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp, 1214 const char *cp, int len) 1215{ 1216 const char *sp = cp, *ep = cp + len; 1217 unsigned save_off = xbp->xb_bufp - xbp->xb_curp; 1218 int slen; 1219 int cols = 0; 1220 1221 for ( ; cp < ep; cp++) { 1222 if (!xo_is_utf8(*cp)) { 1223 cols += 1; 1224 continue; 1225 } 1226 1227 /* 1228 * We're looking at a non-ascii UTF-8 character. 1229 * First we copy the previous data. 1230 * Then we need find the length and validate it. 1231 * Then we turn it into a wide string. 1232 * Then we turn it into a localized string. 1233 * Then we repeat. Isn't i18n fun? 1234 */ 1235 if (sp != cp) 1236 xo_buf_append(xbp, sp, cp - sp); /* Append previous data */ 1237 1238 slen = xo_buf_utf8_len(xop, cp, ep - cp); 1239 if (slen <= 0) { 1240 /* Bad data; back it all out */ 1241 xbp->xb_curp = xbp->xb_bufp + save_off; 1242 return; 1243 } 1244 1245 cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen); 1246 1247 /* Next time thru, we'll start at the next character */ 1248 cp += slen - 1; 1249 sp = cp + 1; 1250 } 1251 1252 /* Update column values */ 1253 if (XOF_ISSET(xop, XOF_COLUMNS)) 1254 xop->xo_columns += cols; 1255 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 1256 xop->xo_anchor_columns += cols; 1257 1258 /* Before we fall into the basic logic below, we need reset len */ 1259 len = ep - sp; 1260 if (len != 0) /* Append trailing data */ 1261 xo_buf_append(xbp, sp, len); 1262} 1263 1264/* 1265 * Append the given string to the given buffer, without escaping or 1266 * character set conversion. This is the straight copy to the data 1267 * buffer with no fanciness. 1268 */ 1269static void 1270xo_data_append (xo_handle_t *xop, const char *str, int len) 1271{ 1272 xo_buf_append(&xop->xo_data, str, len); 1273} 1274 1275/* 1276 * Append the given string to the given buffer 1277 */ 1278static void 1279xo_data_escape (xo_handle_t *xop, const char *str, int len) 1280{ 1281 xo_buf_escape(xop, &xop->xo_data, str, len, 0); 1282} 1283 1284/* 1285 * Generate a warning. Normally, this is a text message written to 1286 * standard error. If the XOF_WARN_XML flag is set, then we generate 1287 * XMLified content on standard output. 1288 */ 1289static void 1290xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, 1291 const char *fmt, va_list vap) 1292{ 1293 xop = xo_default(xop); 1294 if (check_warn && !XOF_ISSET(xop, XOF_WARN)) 1295 return; 1296 1297 if (fmt == NULL) 1298 return; 1299 1300 int len = strlen(fmt); 1301 int plen = xo_program ? strlen(xo_program) : 0; 1302 char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */ 1303 1304 if (plen) { 1305 memcpy(newfmt, xo_program, plen); 1306 newfmt[plen++] = ':'; 1307 newfmt[plen++] = ' '; 1308 } 1309 memcpy(newfmt + plen, fmt, len); 1310 newfmt[len + plen] = '\0'; 1311 1312 if (XOF_ISSET(xop, XOF_WARN_XML)) { 1313 static char err_open[] = "<error>"; 1314 static char err_close[] = "</error>"; 1315 static char msg_open[] = "<message>"; 1316 static char msg_close[] = "</message>"; 1317 1318 xo_buffer_t *xbp = &xop->xo_data; 1319 1320 xo_buf_append(xbp, err_open, sizeof(err_open) - 1); 1321 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); 1322 1323 va_list va_local; 1324 va_copy(va_local, vap); 1325 1326 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1327 int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap); 1328 if (rc >= left) { 1329 if (!xo_buf_has_room(xbp, rc)) { 1330 va_end(va_local); 1331 return; 1332 } 1333 1334 va_end(vap); /* Reset vap to the start */ 1335 va_copy(vap, va_local); 1336 1337 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1338 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 1339 } 1340 va_end(va_local); 1341 1342 rc = xo_escape_xml(xbp, rc, 1); 1343 xbp->xb_curp += rc; 1344 1345 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); 1346 xo_buf_append(xbp, err_close, sizeof(err_close) - 1); 1347 1348 if (code >= 0) { 1349 const char *msg = strerror(code); 1350 if (msg) { 1351 xo_buf_append(xbp, ": ", 2); 1352 xo_buf_append(xbp, msg, strlen(msg)); 1353 } 1354 } 1355 1356 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ 1357 (void) xo_write(xop); 1358 1359 } else { 1360 vfprintf(stderr, newfmt, vap); 1361 if (code >= 0) { 1362 const char *msg = strerror(code); 1363 if (msg) 1364 fprintf(stderr, ": %s", msg); 1365 } 1366 fprintf(stderr, "\n"); 1367 } 1368} 1369 1370void 1371xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) 1372{ 1373 va_list vap; 1374 1375 va_start(vap, fmt); 1376 xo_warn_hcv(xop, code, 0, fmt, vap); 1377 va_end(vap); 1378} 1379 1380void 1381xo_warn_c (int code, const char *fmt, ...) 1382{ 1383 va_list vap; 1384 1385 va_start(vap, fmt); 1386 xo_warn_hcv(NULL, code, 0, fmt, vap); 1387 va_end(vap); 1388} 1389 1390void 1391xo_warn (const char *fmt, ...) 1392{ 1393 int code = errno; 1394 va_list vap; 1395 1396 va_start(vap, fmt); 1397 xo_warn_hcv(NULL, code, 0, fmt, vap); 1398 va_end(vap); 1399} 1400 1401void 1402xo_warnx (const char *fmt, ...) 1403{ 1404 va_list vap; 1405 1406 va_start(vap, fmt); 1407 xo_warn_hcv(NULL, -1, 0, fmt, vap); 1408 va_end(vap); 1409} 1410 1411void 1412xo_err (int eval, const char *fmt, ...) 1413{ 1414 int code = errno; 1415 va_list vap; 1416 1417 va_start(vap, fmt); 1418 xo_warn_hcv(NULL, code, 0, fmt, vap); 1419 va_end(vap); 1420 xo_finish(); 1421 exit(eval); 1422} 1423 1424void 1425xo_errx (int eval, const char *fmt, ...) 1426{ 1427 va_list vap; 1428 1429 va_start(vap, fmt); 1430 xo_warn_hcv(NULL, -1, 0, fmt, vap); 1431 va_end(vap); 1432 xo_finish(); 1433 exit(eval); 1434} 1435 1436void 1437xo_errc (int eval, int code, const char *fmt, ...) 1438{ 1439 va_list vap; 1440 1441 va_start(vap, fmt); 1442 xo_warn_hcv(NULL, code, 0, fmt, vap); 1443 va_end(vap); 1444 xo_finish(); 1445 exit(eval); 1446} 1447 1448/* 1449 * Generate a warning. Normally, this is a text message written to 1450 * standard error. If the XOF_WARN_XML flag is set, then we generate 1451 * XMLified content on standard output. 1452 */ 1453void 1454xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) 1455{ 1456 static char msg_open[] = "<message>"; 1457 static char msg_close[] = "</message>"; 1458 xo_buffer_t *xbp; 1459 int rc; 1460 va_list va_local; 1461 1462 xop = xo_default(xop); 1463 1464 if (fmt == NULL || *fmt == '\0') 1465 return; 1466 1467 int need_nl = (fmt[strlen(fmt) - 1] != '\n'); 1468 1469 switch (xo_style(xop)) { 1470 case XO_STYLE_XML: 1471 xbp = &xop->xo_data; 1472 if (XOF_ISSET(xop, XOF_PRETTY)) 1473 xo_buf_indent(xop, xop->xo_indent_by); 1474 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); 1475 1476 va_copy(va_local, vap); 1477 1478 int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1479 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 1480 if (rc >= left) { 1481 if (!xo_buf_has_room(xbp, rc)) { 1482 va_end(va_local); 1483 return; 1484 } 1485 1486 va_end(vap); /* Reset vap to the start */ 1487 va_copy(vap, va_local); 1488 1489 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 1490 rc = vsnprintf(xbp->xb_curp, left, fmt, vap); 1491 } 1492 va_end(va_local); 1493 1494 rc = xo_escape_xml(xbp, rc, 0); 1495 xbp->xb_curp += rc; 1496 1497 if (need_nl && code > 0) { 1498 const char *msg = strerror(code); 1499 if (msg) { 1500 xo_buf_append(xbp, ": ", 2); 1501 xo_buf_append(xbp, msg, strlen(msg)); 1502 } 1503 } 1504 1505 if (need_nl) 1506 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ 1507 1508 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); 1509 1510 if (XOF_ISSET(xop, XOF_PRETTY)) 1511 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ 1512 1513 (void) xo_write(xop); 1514 break; 1515 1516 case XO_STYLE_HTML: 1517 { 1518 char buf[BUFSIZ], *bp = buf, *cp; 1519 int bufsiz = sizeof(buf); 1520 int rc2; 1521 1522 va_copy(va_local, vap); 1523 1524 rc = vsnprintf(bp, bufsiz, fmt, va_local); 1525 if (rc > bufsiz) { 1526 bufsiz = rc + BUFSIZ; 1527 bp = alloca(bufsiz); 1528 va_end(va_local); 1529 va_copy(va_local, vap); 1530 rc = vsnprintf(bp, bufsiz, fmt, va_local); 1531 } 1532 va_end(va_local); 1533 cp = bp + rc; 1534 1535 if (need_nl) { 1536 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n", 1537 (code > 0) ? ": " : "", 1538 (code > 0) ? strerror(code) : ""); 1539 if (rc2 > 0) 1540 rc += rc2; 1541 } 1542 1543 xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0); 1544 } 1545 break; 1546 1547 case XO_STYLE_JSON: 1548 case XO_STYLE_SDPARAMS: 1549 case XO_STYLE_ENCODER: 1550 /* No means of representing messages */ 1551 return; 1552 1553 case XO_STYLE_TEXT: 1554 rc = xo_printf_v(xop, fmt, vap); 1555 /* 1556 * XXX need to handle UTF-8 widths 1557 */ 1558 if (rc > 0) { 1559 if (XOF_ISSET(xop, XOF_COLUMNS)) 1560 xop->xo_columns += rc; 1561 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 1562 xop->xo_anchor_columns += rc; 1563 } 1564 1565 if (need_nl && code > 0) { 1566 const char *msg = strerror(code); 1567 if (msg) { 1568 xo_printf(xop, ": %s", msg); 1569 } 1570 } 1571 if (need_nl) 1572 xo_printf(xop, "\n"); 1573 1574 break; 1575 } 1576 1577 (void) xo_flush_h(xop); 1578} 1579 1580void 1581xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...) 1582{ 1583 va_list vap; 1584 1585 va_start(vap, fmt); 1586 xo_message_hcv(xop, code, fmt, vap); 1587 va_end(vap); 1588} 1589 1590void 1591xo_message_c (int code, const char *fmt, ...) 1592{ 1593 va_list vap; 1594 1595 va_start(vap, fmt); 1596 xo_message_hcv(NULL, code, fmt, vap); 1597 va_end(vap); 1598} 1599 1600void 1601xo_message_e (const char *fmt, ...) 1602{ 1603 int code = errno; 1604 va_list vap; 1605 1606 va_start(vap, fmt); 1607 xo_message_hcv(NULL, code, fmt, vap); 1608 va_end(vap); 1609} 1610 1611void 1612xo_message (const char *fmt, ...) 1613{ 1614 va_list vap; 1615 1616 va_start(vap, fmt); 1617 xo_message_hcv(NULL, 0, fmt, vap); 1618 va_end(vap); 1619} 1620 1621static void 1622xo_failure (xo_handle_t *xop, const char *fmt, ...) 1623{ 1624 if (!XOF_ISSET(xop, XOF_WARN)) 1625 return; 1626 1627 va_list vap; 1628 1629 va_start(vap, fmt); 1630 xo_warn_hcv(xop, -1, 1, fmt, vap); 1631 va_end(vap); 1632} 1633 1634/** 1635 * Create a handle for use by later libxo functions. 1636 * 1637 * Note: normal use of libxo does not require a distinct handle, since 1638 * the default handle (used when NULL is passed) generates text on stdout. 1639 * 1640 * @style Style of output desired (XO_STYLE_* value) 1641 * @flags Set of XOF_* flags in use with this handle 1642 */ 1643xo_handle_t * 1644xo_create (xo_style_t style, xo_xof_flags_t flags) 1645{ 1646 xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop)); 1647 1648 if (xop) { 1649 bzero(xop, sizeof(*xop)); 1650 1651 xop->xo_style = style; 1652 XOF_SET(xop, flags); 1653 xo_init_handle(xop); 1654 xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */ 1655 } 1656 1657 return xop; 1658} 1659 1660/** 1661 * Create a handle that will write to the given file. Use 1662 * the XOF_CLOSE_FP flag to have the file closed on xo_destroy(). 1663 * @fp FILE pointer to use 1664 * @style Style of output desired (XO_STYLE_* value) 1665 * @flags Set of XOF_* flags to use with this handle 1666 */ 1667xo_handle_t * 1668xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags) 1669{ 1670 xo_handle_t *xop = xo_create(style, flags); 1671 1672 if (xop) { 1673 xop->xo_opaque = fp; 1674 xop->xo_write = xo_write_to_file; 1675 xop->xo_close = xo_close_file; 1676 xop->xo_flush = xo_flush_file; 1677 } 1678 1679 return xop; 1680} 1681 1682/** 1683 * Release any resources held by the handle. 1684 * @xop XO handle to alter (or NULL for default handle) 1685 */ 1686void 1687xo_destroy (xo_handle_t *xop_arg) 1688{ 1689 xo_handle_t *xop = xo_default(xop_arg); 1690 1691 xo_flush_h(xop); 1692 1693 if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP)) 1694 xop->xo_close(xop->xo_opaque); 1695 1696 xo_free(xop->xo_stack); 1697 xo_buf_cleanup(&xop->xo_data); 1698 xo_buf_cleanup(&xop->xo_fmt); 1699 xo_buf_cleanup(&xop->xo_predicate); 1700 xo_buf_cleanup(&xop->xo_attrs); 1701 xo_buf_cleanup(&xop->xo_color_buf); 1702 1703 if (xop->xo_version) 1704 xo_free(xop->xo_version); 1705 1706 if (xop_arg == NULL) { 1707 bzero(&xo_default_handle, sizeof(xo_default_handle)); 1708 xo_default_inited = 0; 1709 } else 1710 xo_free(xop); 1711} 1712 1713/** 1714 * Record a new output style to use for the given handle (or default if 1715 * handle is NULL). This output style will be used for any future output. 1716 * 1717 * @xop XO handle to alter (or NULL for default handle) 1718 * @style new output style (XO_STYLE_*) 1719 */ 1720void 1721xo_set_style (xo_handle_t *xop, xo_style_t style) 1722{ 1723 xop = xo_default(xop); 1724 xop->xo_style = style; 1725} 1726 1727xo_style_t 1728xo_get_style (xo_handle_t *xop) 1729{ 1730 xop = xo_default(xop); 1731 return xo_style(xop); 1732} 1733 1734static int 1735xo_name_to_style (const char *name) 1736{ 1737 if (strcmp(name, "xml") == 0) 1738 return XO_STYLE_XML; 1739 else if (strcmp(name, "json") == 0) 1740 return XO_STYLE_JSON; 1741 else if (strcmp(name, "encoder") == 0) 1742 return XO_STYLE_ENCODER; 1743 else if (strcmp(name, "text") == 0) 1744 return XO_STYLE_TEXT; 1745 else if (strcmp(name, "html") == 0) 1746 return XO_STYLE_HTML; 1747 else if (strcmp(name, "sdparams") == 0) 1748 return XO_STYLE_SDPARAMS; 1749 1750 return -1; 1751} 1752 1753/* 1754 * Indicate if the style is an "encoding" one as opposed to a "display" one. 1755 */ 1756static int 1757xo_style_is_encoding (xo_handle_t *xop) 1758{ 1759 if (xo_style(xop) == XO_STYLE_JSON 1760 || xo_style(xop) == XO_STYLE_XML 1761 || xo_style(xop) == XO_STYLE_SDPARAMS 1762 || xo_style(xop) == XO_STYLE_ENCODER) 1763 return 1; 1764 return 0; 1765} 1766 1767/* Simple name-value mapping */ 1768typedef struct xo_mapping_s { 1769 xo_xff_flags_t xm_value; 1770 const char *xm_name; 1771} xo_mapping_t; 1772 1773static xo_xff_flags_t 1774xo_name_lookup (xo_mapping_t *map, const char *value, int len) 1775{ 1776 if (len == 0) 1777 return 0; 1778 1779 if (len < 0) 1780 len = strlen(value); 1781 1782 while (isspace((int) *value)) { 1783 value += 1; 1784 len -= 1; 1785 } 1786 1787 while (isspace((int) value[len])) 1788 len -= 1; 1789 1790 if (*value == '\0') 1791 return 0; 1792 1793 for ( ; map->xm_name; map++) 1794 if (strncmp(map->xm_name, value, len) == 0) 1795 return map->xm_value; 1796 1797 return 0; 1798} 1799 1800#ifdef NOT_NEEDED_YET 1801static const char * 1802xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value) 1803{ 1804 if (value == 0) 1805 return NULL; 1806 1807 for ( ; map->xm_name; map++) 1808 if (map->xm_value == value) 1809 return map->xm_name; 1810 1811 return NULL; 1812} 1813#endif /* NOT_NEEDED_YET */ 1814 1815static xo_mapping_t xo_xof_names[] = { 1816 { XOF_COLOR_ALLOWED, "color" }, 1817 { XOF_COLUMNS, "columns" }, 1818 { XOF_DTRT, "dtrt" }, 1819 { XOF_FLUSH, "flush" }, 1820 { XOF_IGNORE_CLOSE, "ignore-close" }, 1821 { XOF_INFO, "info" }, 1822 { XOF_KEYS, "keys" }, 1823 { XOF_LOG_GETTEXT, "log-gettext" }, 1824 { XOF_LOG_SYSLOG, "log-syslog" }, 1825 { XOF_NO_HUMANIZE, "no-humanize" }, 1826 { XOF_NO_LOCALE, "no-locale" }, 1827 { XOF_NO_TOP, "no-top" }, 1828 { XOF_NOT_FIRST, "not-first" }, 1829 { XOF_PRETTY, "pretty" }, 1830 { XOF_UNDERSCORES, "underscores" }, 1831 { XOF_UNITS, "units" }, 1832 { XOF_WARN, "warn" }, 1833 { XOF_WARN_XML, "warn-xml" }, 1834 { XOF_XPATH, "xpath" }, 1835 { 0, NULL } 1836}; 1837 1838/* 1839 * Convert string name to XOF_* flag value. 1840 * Not all are useful. Or safe. Or sane. 1841 */ 1842static unsigned 1843xo_name_to_flag (const char *name) 1844{ 1845 return (unsigned) xo_name_lookup(xo_xof_names, name, -1); 1846} 1847 1848int 1849xo_set_style_name (xo_handle_t *xop, const char *name) 1850{ 1851 if (name == NULL) 1852 return -1; 1853 1854 int style = xo_name_to_style(name); 1855 if (style < 0) 1856 return -1; 1857 1858 xo_set_style(xop, style); 1859 return 0; 1860} 1861 1862/* 1863 * Set the options for a handle using a string of options 1864 * passed in. The input is a comma-separated set of names 1865 * and optional values: "xml,pretty,indent=4" 1866 */ 1867int 1868xo_set_options (xo_handle_t *xop, const char *input) 1869{ 1870 char *cp, *ep, *vp, *np, *bp; 1871 int style = -1, new_style, len, rc = 0; 1872 xo_xof_flags_t new_flag; 1873 1874 if (input == NULL) 1875 return 0; 1876 1877 xop = xo_default(xop); 1878 1879#ifdef LIBXO_COLOR_ON_BY_DEFAULT 1880 /* If the installer used --enable-color-on-by-default, then we allow it */ 1881 XOF_SET(xop, XOF_COLOR_ALLOWED); 1882#endif /* LIBXO_COLOR_ON_BY_DEFAULT */ 1883 1884 /* 1885 * We support a simpler, old-school style of giving option 1886 * also, using a single character for each option. It's 1887 * ideal for lazy people, such as myself. 1888 */ 1889 if (*input == ':') { 1890 int sz; 1891 1892 for (input++ ; *input; input++) { 1893 switch (*input) { 1894 case 'c': 1895 XOF_SET(xop, XOF_COLOR_ALLOWED); 1896 break; 1897 1898 case 'f': 1899 XOF_SET(xop, XOF_FLUSH); 1900 break; 1901 1902 case 'F': 1903 XOF_SET(xop, XOF_FLUSH_LINE); 1904 break; 1905 1906 case 'g': 1907 XOF_SET(xop, XOF_LOG_GETTEXT); 1908 break; 1909 1910 case 'H': 1911 xop->xo_style = XO_STYLE_HTML; 1912 break; 1913 1914 case 'I': 1915 XOF_SET(xop, XOF_INFO); 1916 break; 1917 1918 case 'i': 1919 sz = strspn(input + 1, "0123456789"); 1920 if (sz > 0) { 1921 xop->xo_indent_by = atoi(input + 1); 1922 input += sz - 1; /* Skip value */ 1923 } 1924 break; 1925 1926 case 'J': 1927 xop->xo_style = XO_STYLE_JSON; 1928 break; 1929 1930 case 'k': 1931 XOF_SET(xop, XOF_KEYS); 1932 break; 1933 1934 case 'n': 1935 XOF_SET(xop, XOF_NO_HUMANIZE); 1936 break; 1937 1938 case 'P': 1939 XOF_SET(xop, XOF_PRETTY); 1940 break; 1941 1942 case 'T': 1943 xop->xo_style = XO_STYLE_TEXT; 1944 break; 1945 1946 case 'U': 1947 XOF_SET(xop, XOF_UNITS); 1948 break; 1949 1950 case 'u': 1951 XOF_SET(xop, XOF_UNDERSCORES); 1952 break; 1953 1954 case 'W': 1955 XOF_SET(xop, XOF_WARN); 1956 break; 1957 1958 case 'X': 1959 xop->xo_style = XO_STYLE_XML; 1960 break; 1961 1962 case 'x': 1963 XOF_SET(xop, XOF_XPATH); 1964 break; 1965 } 1966 } 1967 return 0; 1968 } 1969 1970 len = strlen(input) + 1; 1971 bp = alloca(len); 1972 memcpy(bp, input, len); 1973 1974 for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) { 1975 np = strchr(cp, ','); 1976 if (np) 1977 *np++ = '\0'; 1978 1979 vp = strchr(cp, '='); 1980 if (vp) 1981 *vp++ = '\0'; 1982 1983 if (strcmp("colors", cp) == 0) { 1984 /* XXX Look for colors=red-blue+green-yellow */ 1985 continue; 1986 } 1987 1988 /* 1989 * For options, we don't allow "encoder" since we want to 1990 * handle it explicitly below as "encoder=xxx". 1991 */ 1992 new_style = xo_name_to_style(cp); 1993 if (new_style >= 0 && new_style != XO_STYLE_ENCODER) { 1994 if (style >= 0) 1995 xo_warnx("ignoring multiple styles: '%s'", cp); 1996 else 1997 style = new_style; 1998 } else { 1999 new_flag = xo_name_to_flag(cp); 2000 if (new_flag != 0) 2001 XOF_SET(xop, new_flag); 2002 else { 2003 if (strcmp(cp, "no-color") == 0) { 2004 XOF_CLEAR(xop, XOF_COLOR_ALLOWED); 2005 } else if (strcmp(cp, "indent") == 0) { 2006 if (vp) 2007 xop->xo_indent_by = atoi(vp); 2008 else 2009 xo_failure(xop, "missing value for indent option"); 2010 } else if (strcmp(cp, "encoder") == 0) { 2011 if (vp == NULL) 2012 xo_failure(xop, "missing value for encoder option"); 2013 else { 2014 if (xo_encoder_init(xop, vp)) { 2015 xo_failure(xop, "encoder not found: %s", vp); 2016 rc = -1; 2017 } 2018 } 2019 2020 } else { 2021 xo_warnx("unknown libxo option value: '%s'", cp); 2022 rc = -1; 2023 } 2024 } 2025 } 2026 } 2027 2028 if (style > 0) 2029 xop->xo_style= style; 2030 2031 return rc; 2032} 2033 2034/** 2035 * Set one or more flags for a given handle (or default if handle is NULL). 2036 * These flags will affect future output. 2037 * 2038 * @xop XO handle to alter (or NULL for default handle) 2039 * @flags Flags to be set (XOF_*) 2040 */ 2041void 2042xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags) 2043{ 2044 xop = xo_default(xop); 2045 2046 XOF_SET(xop, flags); 2047} 2048 2049xo_xof_flags_t 2050xo_get_flags (xo_handle_t *xop) 2051{ 2052 xop = xo_default(xop); 2053 2054 return xop->xo_flags; 2055} 2056 2057/* 2058 * strndup with a twist: len < 0 means strlen 2059 */ 2060static char * 2061xo_strndup (const char *str, int len) 2062{ 2063 if (len < 0) 2064 len = strlen(str); 2065 2066 char *cp = xo_realloc(NULL, len + 1); 2067 if (cp) { 2068 memcpy(cp, str, len); 2069 cp[len] = '\0'; 2070 } 2071 2072 return cp; 2073} 2074 2075/** 2076 * Record a leading prefix for the XPath we generate. This allows the 2077 * generated data to be placed within an XML hierarchy but still have 2078 * accurate XPath expressions. 2079 * 2080 * @xop XO handle to alter (or NULL for default handle) 2081 * @path The XPath expression 2082 */ 2083void 2084xo_set_leading_xpath (xo_handle_t *xop, const char *path) 2085{ 2086 xop = xo_default(xop); 2087 2088 if (xop->xo_leading_xpath) { 2089 xo_free(xop->xo_leading_xpath); 2090 xop->xo_leading_xpath = NULL; 2091 } 2092 2093 if (path == NULL) 2094 return; 2095 2096 xop->xo_leading_xpath = xo_strndup(path, -1); 2097} 2098 2099/** 2100 * Record the info data for a set of tags 2101 * 2102 * @xop XO handle to alter (or NULL for default handle) 2103 * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED) 2104 * @count Number of entries in info (or -1 to count them ourselves) 2105 */ 2106void 2107xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count) 2108{ 2109 xop = xo_default(xop); 2110 2111 if (count < 0 && infop) { 2112 xo_info_t *xip; 2113 2114 for (xip = infop, count = 0; xip->xi_name; xip++, count++) 2115 continue; 2116 } 2117 2118 xop->xo_info = infop; 2119 xop->xo_info_count = count; 2120} 2121 2122/** 2123 * Set the formatter callback for a handle. The callback should 2124 * return a newly formatting contents of a formatting instruction, 2125 * meaning the bits inside the braces. 2126 */ 2127void 2128xo_set_formatter (xo_handle_t *xop, xo_formatter_t func, 2129 xo_checkpointer_t cfunc) 2130{ 2131 xop = xo_default(xop); 2132 2133 xop->xo_formatter = func; 2134 xop->xo_checkpointer = cfunc; 2135} 2136 2137/** 2138 * Clear one or more flags for a given handle (or default if handle is NULL). 2139 * These flags will affect future output. 2140 * 2141 * @xop XO handle to alter (or NULL for default handle) 2142 * @flags Flags to be cleared (XOF_*) 2143 */ 2144void 2145xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags) 2146{ 2147 xop = xo_default(xop); 2148 2149 XOF_CLEAR(xop, flags); 2150} 2151 2152static const char * 2153xo_state_name (xo_state_t state) 2154{ 2155 static const char *names[] = { 2156 "init", 2157 "open_container", 2158 "close_container", 2159 "open_list", 2160 "close_list", 2161 "open_instance", 2162 "close_instance", 2163 "open_leaf_list", 2164 "close_leaf_list", 2165 "discarding", 2166 "marker", 2167 "emit", 2168 "emit_leaf_list", 2169 "finish", 2170 NULL 2171 }; 2172 2173 if (state < (sizeof(names) / sizeof(names[0]))) 2174 return names[state]; 2175 2176 return "unknown"; 2177} 2178 2179static void 2180xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED) 2181{ 2182 static char div_open[] = "<div class=\"line\">"; 2183 static char div_open_blank[] = "<div class=\"blank-line\">"; 2184 2185 if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) 2186 return; 2187 2188 if (xo_style(xop) != XO_STYLE_HTML) 2189 return; 2190 2191 XOIF_SET(xop, XOIF_DIV_OPEN); 2192 if (flags & XFF_BLANK_LINE) 2193 xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1); 2194 else 2195 xo_data_append(xop, div_open, sizeof(div_open) - 1); 2196 2197 if (XOF_ISSET(xop, XOF_PRETTY)) 2198 xo_data_append(xop, "\n", 1); 2199} 2200 2201static void 2202xo_line_close (xo_handle_t *xop) 2203{ 2204 static char div_close[] = "</div>"; 2205 2206 switch (xo_style(xop)) { 2207 case XO_STYLE_HTML: 2208 if (!XOIF_ISSET(xop, XOIF_DIV_OPEN)) 2209 xo_line_ensure_open(xop, 0); 2210 2211 XOIF_CLEAR(xop, XOIF_DIV_OPEN); 2212 xo_data_append(xop, div_close, sizeof(div_close) - 1); 2213 2214 if (XOF_ISSET(xop, XOF_PRETTY)) 2215 xo_data_append(xop, "\n", 1); 2216 break; 2217 2218 case XO_STYLE_TEXT: 2219 xo_data_append(xop, "\n", 1); 2220 break; 2221 } 2222} 2223 2224static int 2225xo_info_compare (const void *key, const void *data) 2226{ 2227 const char *name = key; 2228 const xo_info_t *xip = data; 2229 2230 return strcmp(name, xip->xi_name); 2231} 2232 2233 2234static xo_info_t * 2235xo_info_find (xo_handle_t *xop, const char *name, int nlen) 2236{ 2237 xo_info_t *xip; 2238 char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */ 2239 2240 memcpy(cp, name, nlen); 2241 cp[nlen] = '\0'; 2242 2243 xip = bsearch(cp, xop->xo_info, xop->xo_info_count, 2244 sizeof(xop->xo_info[0]), xo_info_compare); 2245 return xip; 2246} 2247 2248#define CONVERT(_have, _need) (((_have) << 8) | (_need)) 2249 2250/* 2251 * Check to see that the conversion is safe and sane. 2252 */ 2253static int 2254xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc) 2255{ 2256 switch (CONVERT(have_enc, need_enc)) { 2257 case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8): 2258 case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE): 2259 case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8): 2260 case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE): 2261 case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE): 2262 case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8): 2263 return 0; 2264 2265 default: 2266 xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc); 2267 return 1; 2268 } 2269} 2270 2271static int 2272xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, 2273 xo_xff_flags_t flags, 2274 const wchar_t *wcp, const char *cp, int len, int max, 2275 int need_enc, int have_enc) 2276{ 2277 int cols = 0; 2278 wchar_t wc = 0; 2279 int ilen, olen, width; 2280 int attr = (flags & XFF_ATTR); 2281 const char *sp; 2282 2283 if (len > 0 && !xo_buf_has_room(xbp, len)) 2284 return 0; 2285 2286 for (;;) { 2287 if (len == 0) 2288 break; 2289 2290 if (cp) { 2291 if (*cp == '\0') 2292 break; 2293 if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) { 2294 cp += 1; 2295 len -= 1; 2296 } 2297 } 2298 2299 if (wcp && *wcp == L'\0') 2300 break; 2301 2302 ilen = 0; 2303 2304 switch (have_enc) { 2305 case XF_ENC_WIDE: /* Wide character */ 2306 wc = *wcp++; 2307 ilen = 1; 2308 break; 2309 2310 case XF_ENC_UTF8: /* UTF-8 */ 2311 ilen = xo_utf8_to_wc_len(cp); 2312 if (ilen < 0) { 2313 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp); 2314 return -1; /* Can't continue; we can't find the end */ 2315 } 2316 2317 if (len > 0 && len < ilen) { 2318 len = 0; /* Break out of the loop */ 2319 continue; 2320 } 2321 2322 wc = xo_utf8_char(cp, ilen); 2323 if (wc == (wchar_t) -1) { 2324 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d", 2325 *cp, ilen); 2326 return -1; /* Can't continue; we can't find the end */ 2327 } 2328 cp += ilen; 2329 break; 2330 2331 case XF_ENC_LOCALE: /* Native locale */ 2332 ilen = (len > 0) ? len : MB_LEN_MAX; 2333 ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate); 2334 if (ilen < 0) { /* Invalid data; skip */ 2335 xo_failure(xop, "invalid mbs char: %02hhx", *cp); 2336 wc = L'?'; 2337 ilen = 1; 2338 } 2339 2340 if (ilen == 0) { /* Hit a wide NUL character */ 2341 len = 0; 2342 continue; 2343 } 2344 2345 cp += ilen; 2346 break; 2347 } 2348 2349 /* Reduce len, but not below zero */ 2350 if (len > 0) { 2351 len -= ilen; 2352 if (len < 0) 2353 len = 0; 2354 } 2355 2356 /* 2357 * Find the width-in-columns of this character, which must be done 2358 * in wide characters, since we lack a mbswidth() function. If 2359 * it doesn't fit 2360 */ 2361 width = xo_wcwidth(wc); 2362 if (width < 0) 2363 width = iswcntrl(wc) ? 0 : 1; 2364 2365 if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) { 2366 if (max > 0 && cols + width > max) 2367 break; 2368 } 2369 2370 switch (need_enc) { 2371 case XF_ENC_UTF8: 2372 2373 /* Output in UTF-8 needs to be escaped, based on the style */ 2374 switch (xo_style(xop)) { 2375 case XO_STYLE_XML: 2376 case XO_STYLE_HTML: 2377 if (wc == '<') 2378 sp = xo_xml_lt; 2379 else if (wc == '>') 2380 sp = xo_xml_gt; 2381 else if (wc == '&') 2382 sp = xo_xml_amp; 2383 else if (attr && wc == '"') 2384 sp = xo_xml_quot; 2385 else 2386 break; 2387 2388 int slen = strlen(sp); 2389 if (!xo_buf_has_room(xbp, slen - 1)) 2390 return -1; 2391 2392 memcpy(xbp->xb_curp, sp, slen); 2393 xbp->xb_curp += slen; 2394 goto done_with_encoding; /* Need multi-level 'break' */ 2395 2396 case XO_STYLE_JSON: 2397 if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r') 2398 break; 2399 2400 if (!xo_buf_has_room(xbp, 2)) 2401 return -1; 2402 2403 *xbp->xb_curp++ = '\\'; 2404 if (wc == '\n') 2405 wc = 'n'; 2406 else if (wc == '\r') 2407 wc = 'r'; 2408 else wc = wc & 0x7f; 2409 2410 *xbp->xb_curp++ = wc; 2411 goto done_with_encoding; 2412 2413 case XO_STYLE_SDPARAMS: 2414 if (wc != '\\' && wc != '"' && wc != ']') 2415 break; 2416 2417 if (!xo_buf_has_room(xbp, 2)) 2418 return -1; 2419 2420 *xbp->xb_curp++ = '\\'; 2421 wc = wc & 0x7f; 2422 *xbp->xb_curp++ = wc; 2423 goto done_with_encoding; 2424 } 2425 2426 olen = xo_utf8_emit_len(wc); 2427 if (olen < 0) { 2428 xo_failure(xop, "ignoring bad length"); 2429 continue; 2430 } 2431 2432 if (!xo_buf_has_room(xbp, olen)) 2433 return -1; 2434 2435 xo_utf8_emit_char(xbp->xb_curp, olen, wc); 2436 xbp->xb_curp += olen; 2437 break; 2438 2439 case XF_ENC_LOCALE: 2440 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) 2441 return -1; 2442 2443 olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); 2444 if (olen <= 0) { 2445 xo_failure(xop, "could not convert wide char: %lx", 2446 (unsigned long) wc); 2447 width = 1; 2448 *xbp->xb_curp++ = '?'; 2449 } else 2450 xbp->xb_curp += olen; 2451 break; 2452 } 2453 2454 done_with_encoding: 2455 cols += width; 2456 } 2457 2458 return cols; 2459} 2460 2461static int 2462xo_needed_encoding (xo_handle_t *xop) 2463{ 2464 if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */ 2465 return XF_ENC_UTF8; 2466 2467 if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */ 2468 return XF_ENC_LOCALE; 2469 2470 return XF_ENC_UTF8; /* Otherwise, we love UTF-8 */ 2471} 2472 2473static int 2474xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags, 2475 xo_format_t *xfp) 2476{ 2477 static char null[] = "(null)"; 2478 static char null_no_quotes[] = "null"; 2479 2480 char *cp = NULL; 2481 wchar_t *wcp = NULL; 2482 int len, cols = 0, rc = 0; 2483 int off = xbp->xb_curp - xbp->xb_bufp, off2; 2484 int need_enc = xo_needed_encoding(xop); 2485 2486 if (xo_check_conversion(xop, xfp->xf_enc, need_enc)) 2487 return 0; 2488 2489 len = xfp->xf_width[XF_WIDTH_SIZE]; 2490 2491 if (xfp->xf_fc == 'm') { 2492 cp = strerror(xop->xo_errno); 2493 if (len < 0) 2494 len = cp ? strlen(cp) : 0; 2495 goto normal_string; 2496 2497 } else if (xfp->xf_enc == XF_ENC_WIDE) { 2498 wcp = va_arg(xop->xo_vap, wchar_t *); 2499 if (xfp->xf_skip) 2500 return 0; 2501 2502 /* 2503 * Dont' deref NULL; use the traditional "(null)" instead 2504 * of the more accurate "who's been a naughty boy, then?". 2505 */ 2506 if (wcp == NULL) { 2507 cp = null; 2508 len = sizeof(null) - 1; 2509 } 2510 2511 } else { 2512 cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */ 2513 2514 normal_string: 2515 if (xfp->xf_skip) 2516 return 0; 2517 2518 /* Echo "Dont' deref NULL" logic */ 2519 if (cp == NULL) { 2520 if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) { 2521 cp = null_no_quotes; 2522 len = sizeof(null_no_quotes) - 1; 2523 } else { 2524 cp = null; 2525 len = sizeof(null) - 1; 2526 } 2527 } 2528 2529 /* 2530 * Optimize the most common case, which is "%s". We just 2531 * need to copy the complete string to the output buffer. 2532 */ 2533 if (xfp->xf_enc == need_enc 2534 && xfp->xf_width[XF_WIDTH_MIN] < 0 2535 && xfp->xf_width[XF_WIDTH_SIZE] < 0 2536 && xfp->xf_width[XF_WIDTH_MAX] < 0 2537 && !(XOIF_ISSET(xop, XOIF_ANCHOR) 2538 || XOF_ISSET(xop, XOF_COLUMNS))) { 2539 len = strlen(cp); 2540 xo_buf_escape(xop, xbp, cp, len, flags); 2541 2542 /* 2543 * Our caller expects xb_curp left untouched, so we have 2544 * to reset it and return the number of bytes written to 2545 * the buffer. 2546 */ 2547 off2 = xbp->xb_curp - xbp->xb_bufp; 2548 rc = off2 - off; 2549 xbp->xb_curp = xbp->xb_bufp + off; 2550 2551 return rc; 2552 } 2553 } 2554 2555 cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len, 2556 xfp->xf_width[XF_WIDTH_MAX], 2557 need_enc, xfp->xf_enc); 2558 if (cols < 0) 2559 goto bail; 2560 2561 /* 2562 * xo_buf_append* will move xb_curp, so we save/restore it. 2563 */ 2564 off2 = xbp->xb_curp - xbp->xb_bufp; 2565 rc = off2 - off; 2566 xbp->xb_curp = xbp->xb_bufp + off; 2567 2568 if (cols < xfp->xf_width[XF_WIDTH_MIN]) { 2569 /* 2570 * Find the number of columns needed to display the string. 2571 * If we have the original wide string, we just call wcswidth, 2572 * but if we did the work ourselves, then we need to do it. 2573 */ 2574 int delta = xfp->xf_width[XF_WIDTH_MIN] - cols; 2575 if (!xo_buf_has_room(xbp, delta)) 2576 goto bail; 2577 2578 /* 2579 * If seen_minus, then pad on the right; otherwise move it so 2580 * we can pad on the left. 2581 */ 2582 if (xfp->xf_seen_minus) { 2583 cp = xbp->xb_curp + rc; 2584 } else { 2585 cp = xbp->xb_curp; 2586 memmove(xbp->xb_curp + delta, xbp->xb_curp, rc); 2587 } 2588 2589 /* Set the padding */ 2590 memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta); 2591 rc += delta; 2592 cols += delta; 2593 } 2594 2595 if (XOF_ISSET(xop, XOF_COLUMNS)) 2596 xop->xo_columns += cols; 2597 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 2598 xop->xo_anchor_columns += cols; 2599 2600 return rc; 2601 2602 bail: 2603 xbp->xb_curp = xbp->xb_bufp + off; 2604 return 0; 2605} 2606 2607/* 2608 * Look backwards in a buffer to find a numeric value 2609 */ 2610static int 2611xo_buf_find_last_number (xo_buffer_t *xbp, int start_offset) 2612{ 2613 int rc = 0; /* Fail with zero */ 2614 int digit = 1; 2615 char *sp = xbp->xb_bufp; 2616 char *cp = sp + start_offset; 2617 2618 while (--cp >= sp) 2619 if (isdigit((int) *cp)) 2620 break; 2621 2622 for ( ; cp >= sp; cp--) { 2623 if (!isdigit((int) *cp)) 2624 break; 2625 rc += (*cp - '0') * digit; 2626 digit *= 10; 2627 } 2628 2629 return rc; 2630} 2631 2632static int 2633xo_count_utf8_cols (const char *str, int len) 2634{ 2635 int tlen; 2636 wchar_t wc; 2637 int cols = 0; 2638 const char *ep = str + len; 2639 2640 while (str < ep) { 2641 tlen = xo_utf8_to_wc_len(str); 2642 if (tlen < 0) /* Broken input is very bad */ 2643 return cols; 2644 2645 wc = xo_utf8_char(str, tlen); 2646 if (wc == (wchar_t) -1) 2647 return cols; 2648 2649 /* We only print printable characters */ 2650 if (iswprint((wint_t) wc)) { 2651 /* 2652 * Find the width-in-columns of this character, which must be done 2653 * in wide characters, since we lack a mbswidth() function. 2654 */ 2655 int width = xo_wcwidth(wc); 2656 if (width < 0) 2657 width = iswcntrl(wc) ? 0 : 1; 2658 2659 cols += width; 2660 } 2661 2662 str += tlen; 2663 } 2664 2665 return cols; 2666} 2667 2668#ifdef HAVE_GETTEXT 2669static inline const char * 2670xo_dgettext (xo_handle_t *xop, const char *str) 2671{ 2672 const char *domainname = xop->xo_gt_domain; 2673 const char *res; 2674 2675 res = dgettext(domainname, str); 2676 2677 if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) 2678 fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n", 2679 domainname ? "domain \"" : "", xo_printable(domainname), 2680 domainname ? "\", " : "", xo_printable(str), xo_printable(res)); 2681 2682 return res; 2683} 2684 2685static inline const char * 2686xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural, 2687 unsigned long int n) 2688{ 2689 const char *domainname = xop->xo_gt_domain; 2690 const char *res; 2691 2692 res = dngettext(domainname, sing, plural, n); 2693 if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) 2694 fprintf(stderr, "xo: gettext: %s%s%s" 2695 "msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n", 2696 domainname ? "domain \"" : "", 2697 xo_printable(domainname), domainname ? "\", " : "", 2698 xo_printable(sing), 2699 xo_printable(plural), n, xo_printable(res)); 2700 2701 return res; 2702} 2703#else /* HAVE_GETTEXT */ 2704static inline const char * 2705xo_dgettext (xo_handle_t *xop UNUSED, const char *str) 2706{ 2707 return str; 2708} 2709 2710static inline const char * 2711xo_dngettext (xo_handle_t *xop UNUSED, const char *singular, 2712 const char *plural, unsigned long int n) 2713{ 2714 return (n == 1) ? singular : plural; 2715} 2716#endif /* HAVE_GETTEXT */ 2717 2718/* 2719 * This is really _re_formatting, since the normal format code has 2720 * generated a beautiful string into xo_data, starting at 2721 * start_offset. We need to see if it's plural, which means 2722 * comma-separated options, or singular. Then we make the appropriate 2723 * call to d[n]gettext() to get the locale-based version. Note that 2724 * both input and output of gettext() this should be UTF-8. 2725 */ 2726static int 2727xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags, 2728 int start_offset, int cols, int need_enc) 2729{ 2730 xo_buffer_t *xbp = &xop->xo_data; 2731 2732 if (!xo_buf_has_room(xbp, 1)) 2733 return cols; 2734 2735 xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */ 2736 2737 char *cp = xbp->xb_bufp + start_offset; 2738 int len = xbp->xb_curp - cp; 2739 const char *newstr = NULL; 2740 2741 /* 2742 * The plural flag asks us to look backwards at the last numeric 2743 * value rendered and disect the string into two pieces. 2744 */ 2745 if (flags & XFF_GT_PLURAL) { 2746 int n = xo_buf_find_last_number(xbp, start_offset); 2747 char *two = memchr(cp, (int) ',', len); 2748 if (two == NULL) { 2749 xo_failure(xop, "no comma in plural gettext field: '%s'", cp); 2750 return cols; 2751 } 2752 2753 if (two == cp) { 2754 xo_failure(xop, "nothing before comma in plural gettext " 2755 "field: '%s'", cp); 2756 return cols; 2757 } 2758 2759 if (two == xbp->xb_curp) { 2760 xo_failure(xop, "nothing after comma in plural gettext " 2761 "field: '%s'", cp); 2762 return cols; 2763 } 2764 2765 *two++ = '\0'; 2766 if (flags & XFF_GT_FIELD) { 2767 newstr = xo_dngettext(xop, cp, two, n); 2768 } else { 2769 /* Don't do a gettext() look up, just get the plural form */ 2770 newstr = (n == 1) ? cp : two; 2771 } 2772 2773 /* 2774 * If we returned the first string, optimize a bit by 2775 * backing up over comma 2776 */ 2777 if (newstr == cp) { 2778 xbp->xb_curp = two - 1; /* One for comma */ 2779 /* 2780 * If the caller wanted UTF8, we're done; nothing changed, 2781 * but we need to count the columns used. 2782 */ 2783 if (need_enc == XF_ENC_UTF8) 2784 return xo_count_utf8_cols(cp, xbp->xb_curp - cp); 2785 } 2786 2787 } else { 2788 /* The simple case (singular) */ 2789 newstr = xo_dgettext(xop, cp); 2790 2791 if (newstr == cp) { 2792 /* If the caller wanted UTF8, we're done; nothing changed */ 2793 if (need_enc == XF_ENC_UTF8) 2794 return cols; 2795 } 2796 } 2797 2798 /* 2799 * Since the new string string might be in gettext's buffer or 2800 * in the buffer (as the plural form), we make a copy. 2801 */ 2802 int nlen = strlen(newstr); 2803 char *newcopy = alloca(nlen + 1); 2804 memcpy(newcopy, newstr, nlen + 1); 2805 2806 xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */ 2807 return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0, 2808 need_enc, XF_ENC_UTF8); 2809} 2810 2811static void 2812xo_data_append_content (xo_handle_t *xop, const char *str, int len, 2813 xo_xff_flags_t flags) 2814{ 2815 int cols; 2816 int need_enc = xo_needed_encoding(xop); 2817 int start_offset = xo_buf_offset(&xop->xo_data); 2818 2819 cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags, 2820 NULL, str, len, -1, 2821 need_enc, XF_ENC_UTF8); 2822 if (flags & XFF_GT_FLAGS) 2823 cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc); 2824 2825 if (XOF_ISSET(xop, XOF_COLUMNS)) 2826 xop->xo_columns += cols; 2827 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 2828 xop->xo_anchor_columns += cols; 2829} 2830 2831static void 2832xo_bump_width (xo_format_t *xfp, int digit) 2833{ 2834 int *ip = &xfp->xf_width[xfp->xf_dots]; 2835 2836 *ip = ((*ip > 0) ? *ip : 0) * 10 + digit; 2837} 2838 2839static int 2840xo_trim_ws (xo_buffer_t *xbp, int len) 2841{ 2842 char *cp, *sp, *ep; 2843 int delta; 2844 2845 /* First trim leading space */ 2846 for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { 2847 if (*cp != ' ') 2848 break; 2849 } 2850 2851 delta = cp - sp; 2852 if (delta) { 2853 len -= delta; 2854 memmove(sp, cp, len); 2855 } 2856 2857 /* Then trim off the end */ 2858 for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) { 2859 if (ep[-1] != ' ') 2860 break; 2861 } 2862 2863 delta = sp - ep; 2864 if (delta) { 2865 len -= delta; 2866 cp[len] = '\0'; 2867 } 2868 2869 return len; 2870} 2871 2872/* 2873 * Interface to format a single field. The arguments are in xo_vap, 2874 * and the format is in 'fmt'. If 'xbp' is null, we use xop->xo_data; 2875 * this is the most common case. 2876 */ 2877static int 2878xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp, 2879 const char *fmt, int flen, xo_xff_flags_t flags) 2880{ 2881 xo_format_t xf; 2882 const char *cp, *ep, *sp, *xp = NULL; 2883 int rc, cols; 2884 int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop); 2885 unsigned make_output = !(flags & XFF_NO_OUTPUT); 2886 int need_enc = xo_needed_encoding(xop); 2887 int real_need_enc = need_enc; 2888 int old_cols = xop->xo_columns; 2889 2890 /* The gettext interface is UTF-8, so we'll need that for now */ 2891 if (flags & XFF_GT_FIELD) 2892 need_enc = XF_ENC_UTF8; 2893 2894 if (xbp == NULL) 2895 xbp = &xop->xo_data; 2896 2897 unsigned start_offset = xo_buf_offset(xbp); 2898 2899 for (cp = fmt, ep = fmt + flen; cp < ep; cp++) { 2900 /* 2901 * Since we're starting a new field, save the starting offset. 2902 * We'll need this later for field-related operations. 2903 */ 2904 2905 if (*cp != '%') { 2906 add_one: 2907 if (xp == NULL) 2908 xp = cp; 2909 2910 if (*cp == '\\' && cp[1] != '\0') 2911 cp += 1; 2912 continue; 2913 2914 } if (cp + 1 < ep && cp[1] == '%') { 2915 cp += 1; 2916 goto add_one; 2917 } 2918 2919 if (xp) { 2920 if (make_output) { 2921 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, 2922 NULL, xp, cp - xp, -1, 2923 need_enc, XF_ENC_UTF8); 2924 if (XOF_ISSET(xop, XOF_COLUMNS)) 2925 xop->xo_columns += cols; 2926 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 2927 xop->xo_anchor_columns += cols; 2928 } 2929 2930 xp = NULL; 2931 } 2932 2933 bzero(&xf, sizeof(xf)); 2934 xf.xf_leading_zero = -1; 2935 xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1; 2936 2937 /* 2938 * "%@" starts an XO-specific set of flags: 2939 * @X@ - XML-only field; ignored if style isn't XML 2940 */ 2941 if (cp[1] == '@') { 2942 for (cp += 2; cp < ep; cp++) { 2943 if (*cp == '@') { 2944 break; 2945 } 2946 if (*cp == '*') { 2947 /* 2948 * '*' means there's a "%*.*s" value in vap that 2949 * we want to ignore 2950 */ 2951 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) 2952 va_arg(xop->xo_vap, int); 2953 } 2954 } 2955 } 2956 2957 /* Hidden fields are only visible to JSON and XML */ 2958 if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) { 2959 if (style != XO_STYLE_XML 2960 && !xo_style_is_encoding(xop)) 2961 xf.xf_skip = 1; 2962 } else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) { 2963 if (style != XO_STYLE_TEXT 2964 && xo_style(xop) != XO_STYLE_HTML) 2965 xf.xf_skip = 1; 2966 } 2967 2968 if (!make_output) 2969 xf.xf_skip = 1; 2970 2971 /* 2972 * Looking at one piece of a format; find the end and 2973 * call snprintf. Then advance xo_vap on our own. 2974 * 2975 * Note that 'n', 'v', and '$' are not supported. 2976 */ 2977 sp = cp; /* Save start pointer */ 2978 for (cp += 1; cp < ep; cp++) { 2979 if (*cp == 'l') 2980 xf.xf_lflag += 1; 2981 else if (*cp == 'h') 2982 xf.xf_hflag += 1; 2983 else if (*cp == 'j') 2984 xf.xf_jflag += 1; 2985 else if (*cp == 't') 2986 xf.xf_tflag += 1; 2987 else if (*cp == 'z') 2988 xf.xf_zflag += 1; 2989 else if (*cp == 'q') 2990 xf.xf_qflag += 1; 2991 else if (*cp == '.') { 2992 if (++xf.xf_dots >= XF_WIDTH_NUM) { 2993 xo_failure(xop, "Too many dots in format: '%s'", fmt); 2994 return -1; 2995 } 2996 } else if (*cp == '-') 2997 xf.xf_seen_minus = 1; 2998 else if (isdigit((int) *cp)) { 2999 if (xf.xf_leading_zero < 0) 3000 xf.xf_leading_zero = (*cp == '0'); 3001 xo_bump_width(&xf, *cp - '0'); 3002 } else if (*cp == '*') { 3003 xf.xf_stars += 1; 3004 xf.xf_star[xf.xf_dots] = 1; 3005 } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL) 3006 break; 3007 else if (*cp == 'n' || *cp == 'v') { 3008 xo_failure(xop, "unsupported format: '%s'", fmt); 3009 return -1; 3010 } 3011 } 3012 3013 if (cp == ep) 3014 xo_failure(xop, "field format missing format character: %s", 3015 fmt); 3016 3017 xf.xf_fc = *cp; 3018 3019 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) { 3020 if (*cp == 's' || *cp == 'S') { 3021 /* Handle "%*.*.*s" */ 3022 int s; 3023 for (s = 0; s < XF_WIDTH_NUM; s++) { 3024 if (xf.xf_star[s]) { 3025 xf.xf_width[s] = va_arg(xop->xo_vap, int); 3026 3027 /* Normalize a negative width value */ 3028 if (xf.xf_width[s] < 0) { 3029 if (s == 0) { 3030 xf.xf_width[0] = -xf.xf_width[0]; 3031 xf.xf_seen_minus = 1; 3032 } else 3033 xf.xf_width[s] = -1; /* Ignore negative values */ 3034 } 3035 } 3036 } 3037 } 3038 } 3039 3040 /* If no max is given, it defaults to size */ 3041 if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0) 3042 xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE]; 3043 3044 if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U') 3045 xf.xf_lflag = 1; 3046 3047 if (!xf.xf_skip) { 3048 xo_buffer_t *fbp = &xop->xo_fmt; 3049 int len = cp - sp + 1; 3050 if (!xo_buf_has_room(fbp, len + 1)) 3051 return -1; 3052 3053 char *newfmt = fbp->xb_curp; 3054 memcpy(newfmt, sp, len); 3055 newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */ 3056 newfmt[len] = '\0'; 3057 3058 /* 3059 * Bad news: our strings are UTF-8, but the stock printf 3060 * functions won't handle field widths for wide characters 3061 * correctly. So we have to handle this ourselves. 3062 */ 3063 if (xop->xo_formatter == NULL 3064 && (xf.xf_fc == 's' || xf.xf_fc == 'S' 3065 || xf.xf_fc == 'm')) { 3066 3067 xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8 3068 : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE 3069 : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8; 3070 3071 rc = xo_format_string(xop, xbp, flags, &xf); 3072 3073 if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop)) 3074 rc = xo_trim_ws(xbp, rc); 3075 3076 } else { 3077 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap); 3078 3079 /* 3080 * For XML and HTML, we need "&<>" processing; for JSON, 3081 * it's quotes. Text gets nothing. 3082 */ 3083 switch (style) { 3084 case XO_STYLE_XML: 3085 if (flags & XFF_TRIM_WS) 3086 columns = rc = xo_trim_ws(xbp, rc); 3087 /* fall thru */ 3088 case XO_STYLE_HTML: 3089 rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR)); 3090 break; 3091 3092 case XO_STYLE_JSON: 3093 if (flags & XFF_TRIM_WS) 3094 columns = rc = xo_trim_ws(xbp, rc); 3095 rc = xo_escape_json(xbp, rc, 0); 3096 break; 3097 3098 case XO_STYLE_SDPARAMS: 3099 if (flags & XFF_TRIM_WS) 3100 columns = rc = xo_trim_ws(xbp, rc); 3101 rc = xo_escape_sdparams(xbp, rc, 0); 3102 break; 3103 3104 case XO_STYLE_ENCODER: 3105 if (flags & XFF_TRIM_WS) 3106 columns = rc = xo_trim_ws(xbp, rc); 3107 break; 3108 } 3109 3110 /* 3111 * We can assume all the non-%s data we've 3112 * added is ASCII, so the columns and bytes are the 3113 * same. xo_format_string handles all the fancy 3114 * string conversions and updates xo_anchor_columns 3115 * accordingly. 3116 */ 3117 if (XOF_ISSET(xop, XOF_COLUMNS)) 3118 xop->xo_columns += columns; 3119 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 3120 xop->xo_anchor_columns += columns; 3121 } 3122 3123 xbp->xb_curp += rc; 3124 } 3125 3126 /* 3127 * Now for the tricky part: we need to move the argument pointer 3128 * along by the amount needed. 3129 */ 3130 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) { 3131 3132 if (xf.xf_fc == 's' ||xf.xf_fc == 'S') { 3133 /* 3134 * The 'S' and 's' formats are normally handled in 3135 * xo_format_string, but if we skipped it, then we 3136 * need to pop it. 3137 */ 3138 if (xf.xf_skip) 3139 va_arg(xop->xo_vap, char *); 3140 3141 } else if (xf.xf_fc == 'm') { 3142 /* Nothing on the stack for "%m" */ 3143 3144 } else { 3145 int s; 3146 for (s = 0; s < XF_WIDTH_NUM; s++) { 3147 if (xf.xf_star[s]) 3148 va_arg(xop->xo_vap, int); 3149 } 3150 3151 if (strchr("diouxXDOU", xf.xf_fc) != NULL) { 3152 if (xf.xf_hflag > 1) { 3153 va_arg(xop->xo_vap, int); 3154 3155 } else if (xf.xf_hflag > 0) { 3156 va_arg(xop->xo_vap, int); 3157 3158 } else if (xf.xf_lflag > 1) { 3159 va_arg(xop->xo_vap, unsigned long long); 3160 3161 } else if (xf.xf_lflag > 0) { 3162 va_arg(xop->xo_vap, unsigned long); 3163 3164 } else if (xf.xf_jflag > 0) { 3165 va_arg(xop->xo_vap, intmax_t); 3166 3167 } else if (xf.xf_tflag > 0) { 3168 va_arg(xop->xo_vap, ptrdiff_t); 3169 3170 } else if (xf.xf_zflag > 0) { 3171 va_arg(xop->xo_vap, size_t); 3172 3173 } else if (xf.xf_qflag > 0) { 3174 va_arg(xop->xo_vap, quad_t); 3175 3176 } else { 3177 va_arg(xop->xo_vap, int); 3178 } 3179 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL) 3180 if (xf.xf_lflag) 3181 va_arg(xop->xo_vap, long double); 3182 else 3183 va_arg(xop->xo_vap, double); 3184 3185 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag)) 3186 va_arg(xop->xo_vap, wint_t); 3187 3188 else if (xf.xf_fc == 'c') 3189 va_arg(xop->xo_vap, int); 3190 3191 else if (xf.xf_fc == 'p') 3192 va_arg(xop->xo_vap, void *); 3193 } 3194 } 3195 } 3196 3197 if (xp) { 3198 if (make_output) { 3199 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, 3200 NULL, xp, cp - xp, -1, 3201 need_enc, XF_ENC_UTF8); 3202 3203 if (XOF_ISSET(xop, XOF_COLUMNS)) 3204 xop->xo_columns += cols; 3205 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 3206 xop->xo_anchor_columns += cols; 3207 } 3208 3209 xp = NULL; 3210 } 3211 3212 if (flags & XFF_GT_FLAGS) { 3213 /* 3214 * Handle gettext()ing the field by looking up the value 3215 * and then copying it in, while converting to locale, if 3216 * needed. 3217 */ 3218 int new_cols = xo_format_gettext(xop, flags, start_offset, 3219 old_cols, real_need_enc); 3220 3221 if (XOF_ISSET(xop, XOF_COLUMNS)) 3222 xop->xo_columns += new_cols - old_cols; 3223 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 3224 xop->xo_anchor_columns += new_cols - old_cols; 3225 } 3226 3227 return 0; 3228} 3229 3230static char * 3231xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding) 3232{ 3233 char *cp = encoding; 3234 3235 if (cp[0] != '%' || !isdigit((int) cp[1])) 3236 return encoding; 3237 3238 for (cp += 2; *cp; cp++) { 3239 if (!isdigit((int) *cp)) 3240 break; 3241 } 3242 3243 cp -= 1; 3244 *cp = '%'; 3245 3246 return cp; 3247} 3248 3249static void 3250xo_color_append_html (xo_handle_t *xop) 3251{ 3252 /* 3253 * If the color buffer has content, we add it now. It's already 3254 * prebuilt and ready, since we want to add it to every <div>. 3255 */ 3256 if (!xo_buf_is_empty(&xop->xo_color_buf)) { 3257 xo_buffer_t *xbp = &xop->xo_color_buf; 3258 3259 xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp); 3260 } 3261} 3262 3263/* 3264 * A wrapper for humanize_number that autoscales, since the 3265 * HN_AUTOSCALE flag scales as needed based on the size of 3266 * the output buffer, not the size of the value. I also 3267 * wish HN_DECIMAL was more imperative, without the <10 3268 * test. But the boat only goes where we want when we hold 3269 * the rudder, so xo_humanize fixes part of the problem. 3270 */ 3271static int 3272xo_humanize (char *buf, int len, uint64_t value, int flags) 3273{ 3274 int scale = 0; 3275 3276 if (value) { 3277 uint64_t left = value; 3278 3279 if (flags & HN_DIVISOR_1000) { 3280 for ( ; left; scale++) 3281 left /= 1000; 3282 } else { 3283 for ( ; left; scale++) 3284 left /= 1024; 3285 } 3286 scale -= 1; 3287 } 3288 3289 return xo_humanize_number(buf, len, value, "", scale, flags); 3290} 3291 3292/* 3293 * This is an area where we can save information from the handle for 3294 * later restoration. We need to know what data was rendered to know 3295 * what needs cleaned up. 3296 */ 3297typedef struct xo_humanize_save_s { 3298 unsigned xhs_offset; /* Saved xo_offset */ 3299 unsigned xhs_columns; /* Saved xo_columns */ 3300 unsigned xhs_anchor_columns; /* Saved xo_anchor_columns */ 3301} xo_humanize_save_t; 3302 3303/* 3304 * Format a "humanized" value for a numeric, meaning something nice 3305 * like "44M" instead of "44470272". We autoscale, choosing the 3306 * most appropriate value for K/M/G/T/P/E based on the value given. 3307 */ 3308static void 3309xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp, 3310 xo_humanize_save_t *savep, xo_xff_flags_t flags) 3311{ 3312 if (XOF_ISSET(xop, XOF_NO_HUMANIZE)) 3313 return; 3314 3315 unsigned end_offset = xbp->xb_curp - xbp->xb_bufp; 3316 if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */ 3317 return; 3318 3319 /* 3320 * We have a string that's allegedly a number. We want to 3321 * humanize it, which means turning it back into a number 3322 * and calling xo_humanize_number on it. 3323 */ 3324 uint64_t value; 3325 char *ep; 3326 3327 xo_buf_append(xbp, "", 1); /* NUL-terminate it */ 3328 3329 value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0); 3330 if (!(value == ULLONG_MAX && errno == ERANGE) 3331 && (ep != xbp->xb_bufp + savep->xhs_offset)) { 3332 /* 3333 * There are few values where humanize_number needs 3334 * more bytes than the original value. I've used 3335 * 10 as a rectal number to cover those scenarios. 3336 */ 3337 if (xo_buf_has_room(xbp, 10)) { 3338 xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset; 3339 3340 int rc; 3341 int left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp; 3342 int hn_flags = HN_NOSPACE; /* On by default */ 3343 3344 if (flags & XFF_HN_SPACE) 3345 hn_flags &= ~HN_NOSPACE; 3346 3347 if (flags & XFF_HN_DECIMAL) 3348 hn_flags |= HN_DECIMAL; 3349 3350 if (flags & XFF_HN_1000) 3351 hn_flags |= HN_DIVISOR_1000; 3352 3353 rc = xo_humanize(xbp->xb_curp, 3354 left, value, hn_flags); 3355 if (rc > 0) { 3356 xbp->xb_curp += rc; 3357 xop->xo_columns = savep->xhs_columns + rc; 3358 xop->xo_anchor_columns = savep->xhs_anchor_columns + rc; 3359 } 3360 } 3361 } 3362} 3363 3364static void 3365xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 3366 const char *name, int nlen, 3367 const char *value, int vlen, 3368 const char *encoding, int elen) 3369{ 3370 static char div_start[] = "<div class=\""; 3371 static char div_tag[] = "\" data-tag=\""; 3372 static char div_xpath[] = "\" data-xpath=\""; 3373 static char div_key[] = "\" data-key=\"key"; 3374 static char div_end[] = "\">"; 3375 static char div_close[] = "</div>"; 3376 3377 /* 3378 * To build our XPath predicate, we need to save the va_list before 3379 * we format our data, and then restore it before we format the 3380 * xpath expression. 3381 * Display-only keys implies that we've got an encode-only key 3382 * elsewhere, so we don't use them from making predicates. 3383 */ 3384 int need_predidate = 3385 (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY) 3386 && XOF_ISSET(xop, XOF_XPATH)); 3387 3388 if (need_predidate) { 3389 va_list va_local; 3390 3391 va_copy(va_local, xop->xo_vap); 3392 if (xop->xo_checkpointer) 3393 xop->xo_checkpointer(xop, xop->xo_vap, 0); 3394 3395 /* 3396 * Build an XPath predicate expression to match this key. 3397 * We use the format buffer. 3398 */ 3399 xo_buffer_t *pbp = &xop->xo_predicate; 3400 pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */ 3401 3402 xo_buf_append(pbp, "[", 1); 3403 xo_buf_escape(xop, pbp, name, nlen, 0); 3404 if (XOF_ISSET(xop, XOF_PRETTY)) 3405 xo_buf_append(pbp, " = '", 4); 3406 else 3407 xo_buf_append(pbp, "='", 2); 3408 3409 /* The encoding format defaults to the normal format */ 3410 if (encoding == NULL) { 3411 char *enc = alloca(vlen + 1); 3412 memcpy(enc, value, vlen); 3413 enc[vlen] = '\0'; 3414 encoding = xo_fix_encoding(xop, enc); 3415 elen = strlen(encoding); 3416 } 3417 3418 xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR; 3419 pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY); 3420 xo_do_format_field(xop, pbp, encoding, elen, pflags); 3421 3422 xo_buf_append(pbp, "']", 2); 3423 3424 /* Now we record this predicate expression in the stack */ 3425 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3426 int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0; 3427 int dlen = pbp->xb_curp - pbp->xb_bufp; 3428 3429 char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1); 3430 if (cp) { 3431 memcpy(cp + olen, pbp->xb_bufp, dlen); 3432 cp[olen + dlen] = '\0'; 3433 xsp->xs_keys = cp; 3434 } 3435 3436 /* Now we reset the xo_vap as if we were never here */ 3437 va_end(xop->xo_vap); 3438 va_copy(xop->xo_vap, va_local); 3439 va_end(va_local); 3440 if (xop->xo_checkpointer) 3441 xop->xo_checkpointer(xop, xop->xo_vap, 1); 3442 } 3443 3444 if (flags & XFF_ENCODE_ONLY) { 3445 /* 3446 * Even if this is encode-only, we need to go thru the 3447 * work of formatting it to make sure the args are cleared 3448 * from xo_vap. 3449 */ 3450 xo_do_format_field(xop, NULL, encoding, elen, 3451 flags | XFF_NO_OUTPUT); 3452 return; 3453 } 3454 3455 xo_line_ensure_open(xop, 0); 3456 3457 if (XOF_ISSET(xop, XOF_PRETTY)) 3458 xo_buf_indent(xop, xop->xo_indent_by); 3459 3460 xo_data_append(xop, div_start, sizeof(div_start) - 1); 3461 xo_data_append(xop, class, strlen(class)); 3462 3463 /* 3464 * If the color buffer has content, we add it now. It's already 3465 * prebuilt and ready, since we want to add it to every <div>. 3466 */ 3467 if (!xo_buf_is_empty(&xop->xo_color_buf)) { 3468 xo_buffer_t *xbp = &xop->xo_color_buf; 3469 3470 xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp); 3471 } 3472 3473 if (name) { 3474 xo_data_append(xop, div_tag, sizeof(div_tag) - 1); 3475 xo_data_escape(xop, name, nlen); 3476 3477 /* 3478 * Save the offset at which we'd place units. See xo_format_units. 3479 */ 3480 if (XOF_ISSET(xop, XOF_UNITS)) { 3481 XOIF_SET(xop, XOIF_UNITS_PENDING); 3482 /* 3483 * Note: We need the '+1' here because we know we've not 3484 * added the closing quote. We add one, knowing the quote 3485 * will be added shortly. 3486 */ 3487 xop->xo_units_offset = 3488 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1; 3489 } 3490 3491 if (XOF_ISSET(xop, XOF_XPATH)) { 3492 int i; 3493 xo_stack_t *xsp; 3494 3495 xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1); 3496 if (xop->xo_leading_xpath) 3497 xo_data_append(xop, xop->xo_leading_xpath, 3498 strlen(xop->xo_leading_xpath)); 3499 3500 for (i = 0; i <= xop->xo_depth; i++) { 3501 xsp = &xop->xo_stack[i]; 3502 if (xsp->xs_name == NULL) 3503 continue; 3504 3505 /* 3506 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames 3507 * are directly under XSS_OPEN_INSTANCE frames so we 3508 * don't need to put these in our XPath expressions. 3509 */ 3510 if (xsp->xs_state == XSS_OPEN_LIST 3511 || xsp->xs_state == XSS_OPEN_LEAF_LIST) 3512 continue; 3513 3514 xo_data_append(xop, "/", 1); 3515 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name)); 3516 if (xsp->xs_keys) { 3517 /* Don't show keys for the key field */ 3518 if (i != xop->xo_depth || !(flags & XFF_KEY)) 3519 xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys)); 3520 } 3521 } 3522 3523 xo_data_append(xop, "/", 1); 3524 xo_data_escape(xop, name, nlen); 3525 } 3526 3527 if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) { 3528 static char in_type[] = "\" data-type=\""; 3529 static char in_help[] = "\" data-help=\""; 3530 3531 xo_info_t *xip = xo_info_find(xop, name, nlen); 3532 if (xip) { 3533 if (xip->xi_type) { 3534 xo_data_append(xop, in_type, sizeof(in_type) - 1); 3535 xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type)); 3536 } 3537 if (xip->xi_help) { 3538 xo_data_append(xop, in_help, sizeof(in_help) - 1); 3539 xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help)); 3540 } 3541 } 3542 } 3543 3544 if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) 3545 xo_data_append(xop, div_key, sizeof(div_key) - 1); 3546 } 3547 3548 xo_buffer_t *xbp = &xop->xo_data; 3549 unsigned base_offset = xbp->xb_curp - xbp->xb_bufp; 3550 3551 xo_data_append(xop, div_end, sizeof(div_end) - 1); 3552 3553 xo_humanize_save_t save; /* Save values for humanizing logic */ 3554 3555 save.xhs_offset = xbp->xb_curp - xbp->xb_bufp; 3556 save.xhs_columns = xop->xo_columns; 3557 save.xhs_anchor_columns = xop->xo_anchor_columns; 3558 3559 xo_do_format_field(xop, NULL, value, vlen, flags); 3560 3561 if (flags & XFF_HUMANIZE) { 3562 /* 3563 * Unlike text style, we want to retain the original value and 3564 * stuff it into the "data-number" attribute. 3565 */ 3566 static const char div_number[] = "\" data-number=\""; 3567 int div_len = sizeof(div_number) - 1; 3568 3569 unsigned end_offset = xbp->xb_curp - xbp->xb_bufp; 3570 int olen = end_offset - save.xhs_offset; 3571 3572 char *cp = alloca(olen + 1); 3573 memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen); 3574 cp[olen] = '\0'; 3575 3576 xo_format_humanize(xop, xbp, &save, flags); 3577 3578 if (xo_buf_has_room(xbp, div_len + olen)) { 3579 unsigned new_offset = xbp->xb_curp - xbp->xb_bufp; 3580 3581 3582 /* Move the humanized string off to the left */ 3583 memmove(xbp->xb_bufp + base_offset + div_len + olen, 3584 xbp->xb_bufp + base_offset, new_offset - base_offset); 3585 3586 /* Copy the data_number attribute name */ 3587 memcpy(xbp->xb_bufp + base_offset, div_number, div_len); 3588 3589 /* Copy the original long value */ 3590 memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen); 3591 xbp->xb_curp += div_len + olen; 3592 } 3593 } 3594 3595 xo_data_append(xop, div_close, sizeof(div_close) - 1); 3596 3597 if (XOF_ISSET(xop, XOF_PRETTY)) 3598 xo_data_append(xop, "\n", 1); 3599} 3600 3601static void 3602xo_format_text (xo_handle_t *xop, const char *str, int len) 3603{ 3604 switch (xo_style(xop)) { 3605 case XO_STYLE_TEXT: 3606 xo_buf_append_locale(xop, &xop->xo_data, str, len); 3607 break; 3608 3609 case XO_STYLE_HTML: 3610 xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0); 3611 break; 3612 } 3613} 3614 3615static void 3616xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip) 3617{ 3618 const char *str = xfip->xfi_content; 3619 unsigned len = xfip->xfi_clen; 3620 const char *fmt = xfip->xfi_format; 3621 unsigned flen = xfip->xfi_flen; 3622 xo_xff_flags_t flags = xfip->xfi_flags; 3623 3624 static char div_open[] = "<div class=\"title"; 3625 static char div_middle[] = "\">"; 3626 static char div_close[] = "</div>"; 3627 3628 if (flen == 0) { 3629 fmt = "%s"; 3630 flen = 2; 3631 } 3632 3633 switch (xo_style(xop)) { 3634 case XO_STYLE_XML: 3635 case XO_STYLE_JSON: 3636 case XO_STYLE_SDPARAMS: 3637 case XO_STYLE_ENCODER: 3638 /* 3639 * Even though we don't care about text, we need to do 3640 * enough parsing work to skip over the right bits of xo_vap. 3641 */ 3642 if (len == 0) 3643 xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT); 3644 return; 3645 } 3646 3647 xo_buffer_t *xbp = &xop->xo_data; 3648 int start = xbp->xb_curp - xbp->xb_bufp; 3649 int left = xbp->xb_size - start; 3650 int rc; 3651 3652 if (xo_style(xop) == XO_STYLE_HTML) { 3653 xo_line_ensure_open(xop, 0); 3654 if (XOF_ISSET(xop, XOF_PRETTY)) 3655 xo_buf_indent(xop, xop->xo_indent_by); 3656 xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1); 3657 xo_color_append_html(xop); 3658 xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1); 3659 } 3660 3661 start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */ 3662 if (len) { 3663 char *newfmt = alloca(flen + 1); 3664 memcpy(newfmt, fmt, flen); 3665 newfmt[flen] = '\0'; 3666 3667 /* If len is non-zero, the format string apply to the name */ 3668 char *newstr = alloca(len + 1); 3669 memcpy(newstr, str, len); 3670 newstr[len] = '\0'; 3671 3672 if (newstr[len - 1] == 's') { 3673 char *bp; 3674 3675 rc = snprintf(NULL, 0, newfmt, newstr); 3676 if (rc > 0) { 3677 /* 3678 * We have to do this the hard way, since we might need 3679 * the columns. 3680 */ 3681 bp = alloca(rc + 1); 3682 rc = snprintf(bp, rc + 1, newfmt, newstr); 3683 3684 xo_data_append_content(xop, bp, rc, flags); 3685 } 3686 goto move_along; 3687 3688 } else { 3689 rc = snprintf(xbp->xb_curp, left, newfmt, newstr); 3690 if (rc >= left) { 3691 if (!xo_buf_has_room(xbp, rc)) 3692 return; 3693 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); 3694 rc = snprintf(xbp->xb_curp, left, newfmt, newstr); 3695 } 3696 3697 if (rc > 0) { 3698 if (XOF_ISSET(xop, XOF_COLUMNS)) 3699 xop->xo_columns += rc; 3700 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 3701 xop->xo_anchor_columns += rc; 3702 } 3703 } 3704 3705 } else { 3706 xo_do_format_field(xop, NULL, fmt, flen, flags); 3707 3708 /* xo_do_format_field moved curp, so we need to reset it */ 3709 rc = xbp->xb_curp - (xbp->xb_bufp + start); 3710 xbp->xb_curp = xbp->xb_bufp + start; 3711 } 3712 3713 /* If we're styling HTML, then we need to escape it */ 3714 if (xo_style(xop) == XO_STYLE_HTML) { 3715 rc = xo_escape_xml(xbp, rc, 0); 3716 } 3717 3718 if (rc > 0) 3719 xbp->xb_curp += rc; 3720 3721 move_along: 3722 if (xo_style(xop) == XO_STYLE_HTML) { 3723 xo_data_append(xop, div_close, sizeof(div_close) - 1); 3724 if (XOF_ISSET(xop, XOF_PRETTY)) 3725 xo_data_append(xop, "\n", 1); 3726 } 3727} 3728 3729static void 3730xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags) 3731{ 3732 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) { 3733 xo_data_append(xop, ",", 1); 3734 if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY)) 3735 xo_data_append(xop, "\n", 1); 3736 } else 3737 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 3738} 3739 3740#if 0 3741/* Useful debugging function */ 3742void 3743xo_arg (xo_handle_t *xop); 3744void 3745xo_arg (xo_handle_t *xop) 3746{ 3747 xop = xo_default(xop); 3748 fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned)); 3749} 3750#endif /* 0 */ 3751 3752static void 3753xo_format_value (xo_handle_t *xop, const char *name, int nlen, 3754 const char *format, int flen, 3755 const char *encoding, int elen, xo_xff_flags_t flags) 3756{ 3757 int pretty = XOF_ISSET(xop, XOF_PRETTY); 3758 int quote; 3759 3760 /* 3761 * Before we emit a value, we need to know that the frame is ready. 3762 */ 3763 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 3764 3765 if (flags & XFF_LEAF_LIST) { 3766 /* 3767 * Check if we've already started to emit normal leafs 3768 * or if we're not in a leaf list. 3769 */ 3770 if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY)) 3771 || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) { 3772 char nbuf[nlen + 1]; 3773 memcpy(nbuf, name, nlen); 3774 nbuf[nlen] = '\0'; 3775 3776 int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST); 3777 if (rc < 0) 3778 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; 3779 else 3780 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST; 3781 } 3782 3783 xsp = &xop->xo_stack[xop->xo_depth]; 3784 if (xsp->xs_name) { 3785 name = xsp->xs_name; 3786 nlen = strlen(name); 3787 } 3788 3789 } else if (flags & XFF_KEY) { 3790 /* Emitting a 'k' (key) field */ 3791 if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) { 3792 xo_failure(xop, "key field emitted after normal value field: '%.*s'", 3793 nlen, name); 3794 3795 } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) { 3796 char nbuf[nlen + 1]; 3797 memcpy(nbuf, name, nlen); 3798 nbuf[nlen] = '\0'; 3799 3800 int rc = xo_transition(xop, 0, nbuf, XSS_EMIT); 3801 if (rc < 0) 3802 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; 3803 else 3804 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY; 3805 3806 xsp = &xop->xo_stack[xop->xo_depth]; 3807 xsp->xs_flags |= XSF_EMIT_KEY; 3808 } 3809 3810 } else { 3811 /* Emitting a normal value field */ 3812 if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST) 3813 || !(xsp->xs_flags & XSF_EMIT)) { 3814 char nbuf[nlen + 1]; 3815 memcpy(nbuf, name, nlen); 3816 nbuf[nlen] = '\0'; 3817 3818 int rc = xo_transition(xop, 0, nbuf, XSS_EMIT); 3819 if (rc < 0) 3820 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; 3821 else 3822 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT; 3823 3824 xsp = &xop->xo_stack[xop->xo_depth]; 3825 xsp->xs_flags |= XSF_EMIT; 3826 } 3827 } 3828 3829 xo_buffer_t *xbp = &xop->xo_data; 3830 xo_humanize_save_t save; /* Save values for humanizing logic */ 3831 3832 switch (xo_style(xop)) { 3833 case XO_STYLE_TEXT: 3834 if (flags & XFF_ENCODE_ONLY) 3835 flags |= XFF_NO_OUTPUT; 3836 3837 save.xhs_offset = xbp->xb_curp - xbp->xb_bufp; 3838 save.xhs_columns = xop->xo_columns; 3839 save.xhs_anchor_columns = xop->xo_anchor_columns; 3840 3841 xo_do_format_field(xop, NULL, format, flen, flags); 3842 3843 if (flags & XFF_HUMANIZE) 3844 xo_format_humanize(xop, xbp, &save, flags); 3845 break; 3846 3847 case XO_STYLE_HTML: 3848 if (flags & XFF_ENCODE_ONLY) 3849 flags |= XFF_NO_OUTPUT; 3850 3851 xo_buf_append_div(xop, "data", flags, name, nlen, 3852 format, flen, encoding, elen); 3853 break; 3854 3855 case XO_STYLE_XML: 3856 /* 3857 * Even though we're not making output, we still need to 3858 * let the formatting code handle the va_arg popping. 3859 */ 3860 if (flags & XFF_DISPLAY_ONLY) { 3861 flags |= XFF_NO_OUTPUT; 3862 xo_do_format_field(xop, NULL, format, flen, flags); 3863 break; 3864 } 3865 3866 if (encoding) { 3867 format = encoding; 3868 flen = elen; 3869 } else { 3870 char *enc = alloca(flen + 1); 3871 memcpy(enc, format, flen); 3872 enc[flen] = '\0'; 3873 format = xo_fix_encoding(xop, enc); 3874 flen = strlen(format); 3875 } 3876 3877 if (nlen == 0) { 3878 static char missing[] = "missing-field-name"; 3879 xo_failure(xop, "missing field name: %s", format); 3880 name = missing; 3881 nlen = sizeof(missing) - 1; 3882 } 3883 3884 if (pretty) 3885 xo_buf_indent(xop, -1); 3886 xo_data_append(xop, "<", 1); 3887 xo_data_escape(xop, name, nlen); 3888 3889 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { 3890 xo_data_append(xop, xop->xo_attrs.xb_bufp, 3891 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); 3892 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; 3893 } 3894 3895 /* 3896 * We indicate 'key' fields using the 'key' attribute. While 3897 * this is really committing the crime of mixing meta-data with 3898 * data, it's often useful. Especially when format meta-data is 3899 * difficult to come by. 3900 */ 3901 if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) { 3902 static char attr[] = " key=\"key\""; 3903 xo_data_append(xop, attr, sizeof(attr) - 1); 3904 } 3905 3906 /* 3907 * Save the offset at which we'd place units. See xo_format_units. 3908 */ 3909 if (XOF_ISSET(xop, XOF_UNITS)) { 3910 XOIF_SET(xop, XOIF_UNITS_PENDING); 3911 xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp; 3912 } 3913 3914 xo_data_append(xop, ">", 1); 3915 xo_do_format_field(xop, NULL, format, flen, flags); 3916 xo_data_append(xop, "</", 2); 3917 xo_data_escape(xop, name, nlen); 3918 xo_data_append(xop, ">", 1); 3919 if (pretty) 3920 xo_data_append(xop, "\n", 1); 3921 break; 3922 3923 case XO_STYLE_JSON: 3924 if (flags & XFF_DISPLAY_ONLY) { 3925 flags |= XFF_NO_OUTPUT; 3926 xo_do_format_field(xop, NULL, format, flen, flags); 3927 break; 3928 } 3929 3930 if (encoding) { 3931 format = encoding; 3932 flen = elen; 3933 } else { 3934 char *enc = alloca(flen + 1); 3935 memcpy(enc, format, flen); 3936 enc[flen] = '\0'; 3937 format = xo_fix_encoding(xop, enc); 3938 flen = strlen(format); 3939 } 3940 3941 int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST); 3942 3943 xo_format_prep(xop, flags); 3944 3945 if (flags & XFF_QUOTE) 3946 quote = 1; 3947 else if (flags & XFF_NOQUOTE) 3948 quote = 0; 3949 else if (flen == 0) { 3950 quote = 0; 3951 format = "true"; /* JSON encodes empty tags as a boolean true */ 3952 flen = 4; 3953 } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL) 3954 quote = 1; 3955 else 3956 quote = 0; 3957 3958 if (nlen == 0) { 3959 static char missing[] = "missing-field-name"; 3960 xo_failure(xop, "missing field name: %s", format); 3961 name = missing; 3962 nlen = sizeof(missing) - 1; 3963 } 3964 3965 if (flags & XFF_LEAF_LIST) { 3966 if (!first && pretty) 3967 xo_data_append(xop, "\n", 1); 3968 if (pretty) 3969 xo_buf_indent(xop, -1); 3970 } else { 3971 if (pretty) 3972 xo_buf_indent(xop, -1); 3973 xo_data_append(xop, "\"", 1); 3974 3975 xbp = &xop->xo_data; 3976 int off = xbp->xb_curp - xbp->xb_bufp; 3977 3978 xo_data_escape(xop, name, nlen); 3979 3980 if (XOF_ISSET(xop, XOF_UNDERSCORES)) { 3981 int now = xbp->xb_curp - xbp->xb_bufp; 3982 for ( ; off < now; off++) 3983 if (xbp->xb_bufp[off] == '-') 3984 xbp->xb_bufp[off] = '_'; 3985 } 3986 xo_data_append(xop, "\":", 2); 3987 if (pretty) 3988 xo_data_append(xop, " ", 1); 3989 } 3990 3991 if (quote) 3992 xo_data_append(xop, "\"", 1); 3993 3994 xo_do_format_field(xop, NULL, format, flen, flags); 3995 3996 if (quote) 3997 xo_data_append(xop, "\"", 1); 3998 break; 3999 4000 case XO_STYLE_SDPARAMS: 4001 if (flags & XFF_DISPLAY_ONLY) { 4002 flags |= XFF_NO_OUTPUT; 4003 xo_do_format_field(xop, NULL, format, flen, flags); 4004 break; 4005 } 4006 4007 if (encoding) { 4008 format = encoding; 4009 flen = elen; 4010 } else { 4011 char *enc = alloca(flen + 1); 4012 memcpy(enc, format, flen); 4013 enc[flen] = '\0'; 4014 format = xo_fix_encoding(xop, enc); 4015 flen = strlen(format); 4016 } 4017 4018 if (nlen == 0) { 4019 static char missing[] = "missing-field-name"; 4020 xo_failure(xop, "missing field name: %s", format); 4021 name = missing; 4022 nlen = sizeof(missing) - 1; 4023 } 4024 4025 xo_data_escape(xop, name, nlen); 4026 xo_data_append(xop, "=\"", 2); 4027 xo_do_format_field(xop, NULL, format, flen, flags); 4028 xo_data_append(xop, "\" ", 2); 4029 break; 4030 4031 case XO_STYLE_ENCODER: 4032 if (flags & XFF_DISPLAY_ONLY) { 4033 flags |= XFF_NO_OUTPUT; 4034 xo_do_format_field(xop, NULL, format, flen, flags); 4035 break; 4036 } 4037 4038 if (flags & XFF_QUOTE) 4039 quote = 1; 4040 else if (flags & XFF_NOQUOTE) 4041 quote = 0; 4042 else if (flen == 0) { 4043 quote = 0; 4044 format = "true"; /* JSON encodes empty tags as a boolean true */ 4045 flen = 4; 4046 } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL) 4047 quote = 1; 4048 else 4049 quote = 0; 4050 4051 if (encoding) { 4052 format = encoding; 4053 flen = elen; 4054 } else { 4055 char *enc = alloca(flen + 1); 4056 memcpy(enc, format, flen); 4057 enc[flen] = '\0'; 4058 format = xo_fix_encoding(xop, enc); 4059 flen = strlen(format); 4060 } 4061 4062 if (nlen == 0) { 4063 static char missing[] = "missing-field-name"; 4064 xo_failure(xop, "missing field name: %s", format); 4065 name = missing; 4066 nlen = sizeof(missing) - 1; 4067 } 4068 4069 unsigned name_offset = xo_buf_offset(&xop->xo_data); 4070 xo_data_append(xop, name, nlen); 4071 xo_data_append(xop, "", 1); 4072 4073 unsigned value_offset = xo_buf_offset(&xop->xo_data); 4074 xo_do_format_field(xop, NULL, format, flen, flags); 4075 xo_data_append(xop, "", 1); 4076 4077 xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT, 4078 xo_buf_data(&xop->xo_data, name_offset), 4079 xo_buf_data(&xop->xo_data, value_offset)); 4080 xo_buf_reset(&xop->xo_data); 4081 break; 4082 } 4083} 4084 4085static void 4086xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip) 4087{ 4088 const char *str = xfip->xfi_content; 4089 unsigned len = xfip->xfi_clen; 4090 const char *fmt = xfip->xfi_format; 4091 unsigned flen = xfip->xfi_flen; 4092 4093 /* Start by discarding previous domain */ 4094 if (xop->xo_gt_domain) { 4095 xo_free(xop->xo_gt_domain); 4096 xop->xo_gt_domain = NULL; 4097 } 4098 4099 /* An empty {G:} means no domainname */ 4100 if (len == 0 && flen == 0) 4101 return; 4102 4103 int start_offset = -1; 4104 if (len == 0 && flen != 0) { 4105 /* Need to do format the data to get the domainname from args */ 4106 start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp; 4107 xo_do_format_field(xop, NULL, fmt, flen, 0); 4108 4109 int end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp; 4110 len = end_offset - start_offset; 4111 str = xop->xo_data.xb_bufp + start_offset; 4112 } 4113 4114 xop->xo_gt_domain = xo_strndup(str, len); 4115 4116 /* Reset the current buffer point to avoid emitting the name as output */ 4117 if (start_offset >= 0) 4118 xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset; 4119} 4120 4121static void 4122xo_format_content (xo_handle_t *xop, const char *class_name, 4123 const char *tag_name, 4124 const char *str, int len, const char *fmt, int flen, 4125 xo_xff_flags_t flags) 4126{ 4127 switch (xo_style(xop)) { 4128 case XO_STYLE_TEXT: 4129 if (len) 4130 xo_data_append_content(xop, str, len, flags); 4131 else 4132 xo_do_format_field(xop, NULL, fmt, flen, flags); 4133 break; 4134 4135 case XO_STYLE_HTML: 4136 if (len == 0) { 4137 str = fmt; 4138 len = flen; 4139 } 4140 4141 xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0); 4142 break; 4143 4144 case XO_STYLE_XML: 4145 case XO_STYLE_JSON: 4146 case XO_STYLE_SDPARAMS: 4147 if (tag_name) { 4148 if (len == 0) { 4149 str = fmt; 4150 len = flen; 4151 } 4152 4153 xo_open_container_h(xop, tag_name); 4154 xo_format_value(xop, "message", 7, str, len, NULL, 0, flags); 4155 xo_close_container_h(xop, tag_name); 4156 4157 } else { 4158 /* 4159 * Even though we don't care about labels, we need to do 4160 * enough parsing work to skip over the right bits of xo_vap. 4161 */ 4162 if (len == 0) 4163 xo_do_format_field(xop, NULL, fmt, flen, 4164 flags | XFF_NO_OUTPUT); 4165 } 4166 break; 4167 4168 case XO_STYLE_ENCODER: 4169 if (len == 0) 4170 xo_do_format_field(xop, NULL, fmt, flen, 4171 flags | XFF_NO_OUTPUT); 4172 break; 4173 } 4174} 4175 4176static const char *xo_color_names[] = { 4177 "default", /* XO_COL_DEFAULT */ 4178 "black", /* XO_COL_BLACK */ 4179 "red", /* XO_CLOR_RED */ 4180 "green", /* XO_COL_GREEN */ 4181 "yellow", /* XO_COL_YELLOW */ 4182 "blue", /* XO_COL_BLUE */ 4183 "magenta", /* XO_COL_MAGENTA */ 4184 "cyan", /* XO_COL_CYAN */ 4185 "white", /* XO_COL_WHITE */ 4186 NULL 4187}; 4188 4189static int 4190xo_color_find (const char *str) 4191{ 4192 int i; 4193 4194 for (i = 0; xo_color_names[i]; i++) { 4195 if (strcmp(xo_color_names[i], str) == 0) 4196 return i; 4197 } 4198 4199 return -1; 4200} 4201 4202static const char *xo_effect_names[] = { 4203 "reset", /* XO_EFF_RESET */ 4204 "normal", /* XO_EFF_NORMAL */ 4205 "bold", /* XO_EFF_BOLD */ 4206 "underline", /* XO_EFF_UNDERLINE */ 4207 "inverse", /* XO_EFF_INVERSE */ 4208 NULL 4209}; 4210 4211static const char *xo_effect_on_codes[] = { 4212 "0", /* XO_EFF_RESET */ 4213 "0", /* XO_EFF_NORMAL */ 4214 "1", /* XO_EFF_BOLD */ 4215 "4", /* XO_EFF_UNDERLINE */ 4216 "7", /* XO_EFF_INVERSE */ 4217 NULL 4218}; 4219 4220#if 0 4221/* 4222 * See comment below re: joy of terminal standards. These can 4223 * be use by just adding: 4224 * + if (newp->xoc_effects & bit) 4225 * code = xo_effect_on_codes[i]; 4226 * + else 4227 * + code = xo_effect_off_codes[i]; 4228 * in xo_color_handle_text. 4229 */ 4230static const char *xo_effect_off_codes[] = { 4231 "0", /* XO_EFF_RESET */ 4232 "0", /* XO_EFF_NORMAL */ 4233 "21", /* XO_EFF_BOLD */ 4234 "24", /* XO_EFF_UNDERLINE */ 4235 "27", /* XO_EFF_INVERSE */ 4236 NULL 4237}; 4238#endif /* 0 */ 4239 4240static int 4241xo_effect_find (const char *str) 4242{ 4243 int i; 4244 4245 for (i = 0; xo_effect_names[i]; i++) { 4246 if (strcmp(xo_effect_names[i], str) == 0) 4247 return i; 4248 } 4249 4250 return -1; 4251} 4252 4253static void 4254xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str) 4255{ 4256#ifdef LIBXO_TEXT_ONLY 4257 return; 4258#endif /* LIBXO_TEXT_ONLY */ 4259 4260 char *cp, *ep, *np, *xp; 4261 int len = strlen(str); 4262 int rc; 4263 4264 /* 4265 * Possible tokens: colors, bg-colors, effects, no-effects, "reset". 4266 */ 4267 for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) { 4268 /* Trim leading whitespace */ 4269 while (isspace((int) *cp)) 4270 cp += 1; 4271 4272 np = strchr(cp, ','); 4273 if (np) 4274 *np++ = '\0'; 4275 4276 /* Trim trailing whitespace */ 4277 xp = cp + strlen(cp) - 1; 4278 while (isspace(*xp) && xp > cp) 4279 *xp-- = '\0'; 4280 4281 if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') { 4282 rc = xo_color_find(cp + 3); 4283 if (rc < 0) 4284 goto unknown; 4285 4286 xocp->xoc_col_fg = rc; 4287 4288 } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') { 4289 rc = xo_color_find(cp + 3); 4290 if (rc < 0) 4291 goto unknown; 4292 xocp->xoc_col_bg = rc; 4293 4294 } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') { 4295 rc = xo_effect_find(cp + 3); 4296 if (rc < 0) 4297 goto unknown; 4298 xocp->xoc_effects &= ~(1 << rc); 4299 4300 } else { 4301 rc = xo_effect_find(cp); 4302 if (rc < 0) 4303 goto unknown; 4304 xocp->xoc_effects |= 1 << rc; 4305 4306 switch (1 << rc) { 4307 case XO_EFF_RESET: 4308 xocp->xoc_col_fg = xocp->xoc_col_bg = 0; 4309 /* Note: not "|=" since we want to wipe out the old value */ 4310 xocp->xoc_effects = XO_EFF_RESET; 4311 break; 4312 4313 case XO_EFF_NORMAL: 4314 xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE 4315 | XO_EFF_INVERSE | XO_EFF_NORMAL); 4316 break; 4317 } 4318 } 4319 continue; 4320 4321 unknown: 4322 if (XOF_ISSET(xop, XOF_WARN)) 4323 xo_failure(xop, "unknown color/effect string detected: '%s'", cp); 4324 } 4325} 4326 4327static inline int 4328xo_colors_enabled (xo_handle_t *xop UNUSED) 4329{ 4330#ifdef LIBXO_TEXT_ONLY 4331 return 0; 4332#else /* LIBXO_TEXT_ONLY */ 4333 return XOF_ISSET(xop, XOF_COLOR); 4334#endif /* LIBXO_TEXT_ONLY */ 4335} 4336 4337static void 4338xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp) 4339{ 4340 char buf[BUFSIZ]; 4341 char *cp = buf, *ep = buf + sizeof(buf); 4342 unsigned i, bit; 4343 xo_colors_t *oldp = &xop->xo_colors; 4344 const char *code; 4345 4346 /* 4347 * Start the buffer with an escape. We don't want to add the '[' 4348 * now, since we let xo_effect_text_add unconditionally add the ';'. 4349 * We'll replace the first ';' with a '[' when we're done. 4350 */ 4351 *cp++ = 0x1b; /* Escape */ 4352 4353 /* 4354 * Terminals were designed back in the age before "certainty" was 4355 * invented, when standards were more what you'd call "guidelines" 4356 * than actual rules. Anyway we can't depend on them to operate 4357 * correctly. So when display attributes are changed, we punt, 4358 * reseting them all and turning back on the ones we want to keep. 4359 * Longer, but should be completely reliable. Savvy? 4360 */ 4361 if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) { 4362 newp->xoc_effects |= XO_EFF_RESET; 4363 oldp->xoc_effects = 0; 4364 } 4365 4366 for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) { 4367 if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit)) 4368 continue; 4369 4370 code = xo_effect_on_codes[i]; 4371 4372 cp += snprintf(cp, ep - cp, ";%s", code); 4373 if (cp >= ep) 4374 return; /* Should not occur */ 4375 4376 if (bit == XO_EFF_RESET) { 4377 /* Mark up the old value so we can detect current values as new */ 4378 oldp->xoc_effects = 0; 4379 oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT; 4380 } 4381 } 4382 4383 if (newp->xoc_col_fg != oldp->xoc_col_fg) { 4384 cp += snprintf(cp, ep - cp, ";3%u", 4385 (newp->xoc_col_fg != XO_COL_DEFAULT) 4386 ? newp->xoc_col_fg - 1 : 9); 4387 } 4388 4389 if (newp->xoc_col_bg != oldp->xoc_col_bg) { 4390 cp += snprintf(cp, ep - cp, ";4%u", 4391 (newp->xoc_col_bg != XO_COL_DEFAULT) 4392 ? newp->xoc_col_bg - 1 : 9); 4393 } 4394 4395 if (cp - buf != 1 && cp < ep - 3) { 4396 buf[1] = '['; /* Overwrite leading ';' */ 4397 *cp++ = 'm'; 4398 *cp = '\0'; 4399 xo_buf_append(&xop->xo_data, buf, cp - buf); 4400 } 4401} 4402 4403static void 4404xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp) 4405{ 4406 xo_colors_t *oldp = &xop->xo_colors; 4407 4408 /* 4409 * HTML colors are mostly trivial: fill in xo_color_buf with 4410 * a set of class tags representing the colors and effects. 4411 */ 4412 4413 /* If nothing changed, then do nothing */ 4414 if (oldp->xoc_effects == newp->xoc_effects 4415 && oldp->xoc_col_fg == newp->xoc_col_fg 4416 && oldp->xoc_col_bg == newp->xoc_col_bg) 4417 return; 4418 4419 unsigned i, bit; 4420 xo_buffer_t *xbp = &xop->xo_color_buf; 4421 4422 xo_buf_reset(xbp); /* We rebuild content after each change */ 4423 4424 for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) { 4425 if (!(newp->xoc_effects & bit)) 4426 continue; 4427 4428 xo_buf_append_str(xbp, " effect-"); 4429 xo_buf_append_str(xbp, xo_effect_names[i]); 4430 } 4431 4432 const char *fg = NULL; 4433 const char *bg = NULL; 4434 4435 if (newp->xoc_col_fg != XO_COL_DEFAULT) 4436 fg = xo_color_names[newp->xoc_col_fg]; 4437 if (newp->xoc_col_bg != XO_COL_DEFAULT) 4438 bg = xo_color_names[newp->xoc_col_bg]; 4439 4440 if (newp->xoc_effects & XO_EFF_INVERSE) { 4441 const char *tmp = fg; 4442 fg = bg; 4443 bg = tmp; 4444 if (fg == NULL) 4445 fg = "inverse"; 4446 if (bg == NULL) 4447 bg = "inverse"; 4448 4449 } 4450 4451 if (fg) { 4452 xo_buf_append_str(xbp, " color-fg-"); 4453 xo_buf_append_str(xbp, fg); 4454 } 4455 4456 if (bg) { 4457 xo_buf_append_str(xbp, " color-bg-"); 4458 xo_buf_append_str(xbp, bg); 4459 } 4460} 4461 4462static void 4463xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip) 4464{ 4465 const char *str = xfip->xfi_content; 4466 unsigned len = xfip->xfi_clen; 4467 const char *fmt = xfip->xfi_format; 4468 unsigned flen = xfip->xfi_flen; 4469 4470 xo_buffer_t xb; 4471 4472 /* If the string is static and we've in an encoding style, bail */ 4473 if (len != 0 && xo_style_is_encoding(xop)) 4474 return; 4475 4476 xo_buf_init(&xb); 4477 4478 if (len) 4479 xo_buf_append(&xb, str, len); 4480 else if (flen) 4481 xo_do_format_field(xop, &xb, fmt, flen, 0); 4482 else 4483 xo_buf_append(&xb, "reset", 6); /* Default if empty */ 4484 4485 if (xo_colors_enabled(xop)) { 4486 switch (xo_style(xop)) { 4487 case XO_STYLE_TEXT: 4488 case XO_STYLE_HTML: 4489 xo_buf_append(&xb, "", 1); 4490 4491 xo_colors_t xoc = xop->xo_colors; 4492 xo_colors_parse(xop, &xoc, xb.xb_bufp); 4493 4494 if (xo_style(xop) == XO_STYLE_TEXT) { 4495 /* 4496 * Text mode means emitting the colors as ANSI character 4497 * codes. This will allow people who like colors to have 4498 * colors. The issue is, of course conflicting with the 4499 * user's perfectly reasonable color scheme. Which leads 4500 * to the hell of LSCOLORS, where even app need to have 4501 * customization hooks for adjusting colors. Instead we 4502 * provide a simpler-but-still-annoying answer where one 4503 * can map colors to other colors. 4504 */ 4505 xo_colors_handle_text(xop, &xoc); 4506 xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */ 4507 4508 } else { 4509 /* 4510 * HTML output is wrapped in divs, so the color information 4511 * must appear in every div until cleared. Most pathetic. 4512 * Most unavoidable. 4513 */ 4514 xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */ 4515 xo_colors_handle_html(xop, &xoc); 4516 } 4517 4518 xop->xo_colors = xoc; 4519 break; 4520 4521 case XO_STYLE_XML: 4522 case XO_STYLE_JSON: 4523 case XO_STYLE_SDPARAMS: 4524 case XO_STYLE_ENCODER: 4525 /* 4526 * Nothing to do; we did all that work just to clear the stack of 4527 * formatting arguments. 4528 */ 4529 break; 4530 } 4531 } 4532 4533 xo_buf_cleanup(&xb); 4534} 4535 4536static void 4537xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip) 4538{ 4539 const char *str = xfip->xfi_content; 4540 unsigned len = xfip->xfi_clen; 4541 const char *fmt = xfip->xfi_format; 4542 unsigned flen = xfip->xfi_flen; 4543 xo_xff_flags_t flags = xfip->xfi_flags; 4544 4545 static char units_start_xml[] = " units=\""; 4546 static char units_start_html[] = " data-units=\""; 4547 4548 if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) { 4549 xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags); 4550 return; 4551 } 4552 4553 xo_buffer_t *xbp = &xop->xo_data; 4554 int start = xop->xo_units_offset; 4555 int stop = xbp->xb_curp - xbp->xb_bufp; 4556 4557 if (xo_style(xop) == XO_STYLE_XML) 4558 xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1); 4559 else if (xo_style(xop) == XO_STYLE_HTML) 4560 xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1); 4561 else 4562 return; 4563 4564 if (len) 4565 xo_data_escape(xop, str, len); 4566 else 4567 xo_do_format_field(xop, NULL, fmt, flen, flags); 4568 4569 xo_buf_append(xbp, "\"", 1); 4570 4571 int now = xbp->xb_curp - xbp->xb_bufp; 4572 int delta = now - stop; 4573 if (delta <= 0) { /* Strange; no output to move */ 4574 xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */ 4575 return; 4576 } 4577 4578 /* 4579 * Now we're in it alright. We've need to insert the unit value 4580 * we just created into the right spot. We make a local copy, 4581 * move it and then insert our copy. We know there's room in the 4582 * buffer, since we're just moving this around. 4583 */ 4584 char *buf = alloca(delta); 4585 4586 memcpy(buf, xbp->xb_bufp + stop, delta); 4587 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); 4588 memmove(xbp->xb_bufp + start, buf, delta); 4589} 4590 4591static int 4592xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip) 4593{ 4594 const char *str = xfip->xfi_content; 4595 unsigned len = xfip->xfi_clen; 4596 const char *fmt = xfip->xfi_format; 4597 unsigned flen = xfip->xfi_flen; 4598 4599 long width = 0; 4600 char *bp; 4601 char *cp; 4602 4603 if (len) { 4604 bp = alloca(len + 1); /* Make local NUL-terminated copy of str */ 4605 memcpy(bp, str, len); 4606 bp[len] = '\0'; 4607 4608 width = strtol(bp, &cp, 0); 4609 if (width == LONG_MIN || width == LONG_MAX 4610 || bp == cp || *cp != '\0' ) { 4611 width = 0; 4612 xo_failure(xop, "invalid width for anchor: '%s'", bp); 4613 } 4614 } else if (flen) { 4615 if (flen != 2 || strncmp("%d", fmt, flen) != 0) 4616 xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt); 4617 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) 4618 width = va_arg(xop->xo_vap, int); 4619 } 4620 4621 return width; 4622} 4623 4624static void 4625xo_anchor_clear (xo_handle_t *xop) 4626{ 4627 XOIF_CLEAR(xop, XOIF_ANCHOR); 4628 xop->xo_anchor_offset = 0; 4629 xop->xo_anchor_columns = 0; 4630 xop->xo_anchor_min_width = 0; 4631} 4632 4633/* 4634 * An anchor is a marker used to delay field width implications. 4635 * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}". 4636 * We are looking for output like " 1/4/5" 4637 * 4638 * To make this work, we record the anchor and then return to 4639 * format it when the end anchor tag is seen. 4640 */ 4641static void 4642xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip) 4643{ 4644 if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) 4645 return; 4646 4647 if (XOIF_ISSET(xop, XOIF_ANCHOR)) 4648 xo_failure(xop, "the anchor already recording is discarded"); 4649 4650 XOIF_SET(xop, XOIF_ANCHOR); 4651 xo_buffer_t *xbp = &xop->xo_data; 4652 xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp; 4653 xop->xo_anchor_columns = 0; 4654 4655 /* 4656 * Now we find the width, if possible. If it's not there, 4657 * we'll get it on the end anchor. 4658 */ 4659 xop->xo_anchor_min_width = xo_find_width(xop, xfip); 4660} 4661 4662static void 4663xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip) 4664{ 4665 if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) 4666 return; 4667 4668 if (!XOIF_ISSET(xop, XOIF_ANCHOR)) { 4669 xo_failure(xop, "no start anchor"); 4670 return; 4671 } 4672 4673 XOIF_CLEAR(xop, XOIF_UNITS_PENDING); 4674 4675 int width = xo_find_width(xop, xfip); 4676 if (width == 0) 4677 width = xop->xo_anchor_min_width; 4678 4679 if (width == 0) /* No width given; nothing to do */ 4680 goto done; 4681 4682 xo_buffer_t *xbp = &xop->xo_data; 4683 int start = xop->xo_anchor_offset; 4684 int stop = xbp->xb_curp - xbp->xb_bufp; 4685 int abswidth = (width > 0) ? width : -width; 4686 int blen = abswidth - xop->xo_anchor_columns; 4687 4688 if (blen <= 0) /* Already over width */ 4689 goto done; 4690 4691 if (abswidth > XO_MAX_ANCHOR_WIDTH) { 4692 xo_failure(xop, "width over %u are not supported", 4693 XO_MAX_ANCHOR_WIDTH); 4694 goto done; 4695 } 4696 4697 /* Make a suitable padding field and emit it */ 4698 char *buf = alloca(blen); 4699 memset(buf, ' ', blen); 4700 xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0); 4701 4702 if (width < 0) /* Already left justified */ 4703 goto done; 4704 4705 int now = xbp->xb_curp - xbp->xb_bufp; 4706 int delta = now - stop; 4707 if (delta <= 0) /* Strange; no output to move */ 4708 goto done; 4709 4710 /* 4711 * Now we're in it alright. We've need to insert the padding data 4712 * we just created (which might be an HTML <div> or text) before 4713 * the formatted data. We make a local copy, move it and then 4714 * insert our copy. We know there's room in the buffer, since 4715 * we're just moving this around. 4716 */ 4717 if (delta > blen) 4718 buf = alloca(delta); /* Expand buffer if needed */ 4719 4720 memcpy(buf, xbp->xb_bufp + stop, delta); 4721 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); 4722 memmove(xbp->xb_bufp + start, buf, delta); 4723 4724 done: 4725 xo_anchor_clear(xop); 4726} 4727 4728static const char * 4729xo_class_name (int ftype) 4730{ 4731 switch (ftype) { 4732 case 'D': return "decoration"; 4733 case 'E': return "error"; 4734 case 'L': return "label"; 4735 case 'N': return "note"; 4736 case 'P': return "padding"; 4737 case 'W': return "warning"; 4738 } 4739 4740 return NULL; 4741} 4742 4743static const char * 4744xo_tag_name (int ftype) 4745{ 4746 switch (ftype) { 4747 case 'E': return "__error"; 4748 case 'W': return "__warning"; 4749 } 4750 4751 return NULL; 4752} 4753 4754static int 4755xo_role_wants_default_format (int ftype) 4756{ 4757 switch (ftype) { 4758 /* These roles can be completely empty and/or without formatting */ 4759 case 'C': 4760 case 'G': 4761 case '[': 4762 case ']': 4763 return 0; 4764 } 4765 4766 return 1; 4767} 4768 4769static xo_mapping_t xo_role_names[] = { 4770 { 'C', "color" }, 4771 { 'D', "decoration" }, 4772 { 'E', "error" }, 4773 { 'L', "label" }, 4774 { 'N', "note" }, 4775 { 'P', "padding" }, 4776 { 'T', "title" }, 4777 { 'U', "units" }, 4778 { 'V', "value" }, 4779 { 'W', "warning" }, 4780 { '[', "start-anchor" }, 4781 { ']', "stop-anchor" }, 4782 { 0, NULL } 4783}; 4784 4785#define XO_ROLE_EBRACE '{' /* Escaped braces */ 4786#define XO_ROLE_TEXT '+' 4787#define XO_ROLE_NEWLINE '\n' 4788 4789static xo_mapping_t xo_modifier_names[] = { 4790 { XFF_COLON, "colon" }, 4791 { XFF_COMMA, "comma" }, 4792 { XFF_DISPLAY_ONLY, "display" }, 4793 { XFF_ENCODE_ONLY, "encoding" }, 4794 { XFF_GT_FIELD, "gettext" }, 4795 { XFF_HUMANIZE, "humanize" }, 4796 { XFF_HUMANIZE, "hn" }, 4797 { XFF_HN_SPACE, "hn-space" }, 4798 { XFF_HN_DECIMAL, "hn-decimal" }, 4799 { XFF_HN_1000, "hn-1000" }, 4800 { XFF_KEY, "key" }, 4801 { XFF_LEAF_LIST, "leaf-list" }, 4802 { XFF_LEAF_LIST, "list" }, 4803 { XFF_NOQUOTE, "no-quotes" }, 4804 { XFF_NOQUOTE, "no-quote" }, 4805 { XFF_GT_PLURAL, "plural" }, 4806 { XFF_QUOTE, "quotes" }, 4807 { XFF_QUOTE, "quote" }, 4808 { XFF_TRIM_WS, "trim" }, 4809 { XFF_WS, "white" }, 4810 { 0, NULL } 4811}; 4812 4813#ifdef NOT_NEEDED_YET 4814static xo_mapping_t xo_modifier_short_names[] = { 4815 { XFF_COLON, "c" }, 4816 { XFF_DISPLAY_ONLY, "d" }, 4817 { XFF_ENCODE_ONLY, "e" }, 4818 { XFF_GT_FIELD, "g" }, 4819 { XFF_HUMANIZE, "h" }, 4820 { XFF_KEY, "k" }, 4821 { XFF_LEAF_LIST, "l" }, 4822 { XFF_NOQUOTE, "n" }, 4823 { XFF_GT_PLURAL, "p" }, 4824 { XFF_QUOTE, "q" }, 4825 { XFF_TRIM_WS, "t" }, 4826 { XFF_WS, "w" }, 4827 { 0, NULL } 4828}; 4829#endif /* NOT_NEEDED_YET */ 4830 4831static int 4832xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt) 4833{ 4834 int rc = 1; 4835 const char *cp; 4836 4837 for (cp = fmt; *cp; cp++) 4838 if (*cp == '{' || *cp == '\n') 4839 rc += 1; 4840 4841 return rc * 2 + 1; 4842} 4843 4844/* 4845 * The field format is: 4846 * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}' 4847 * Roles are optional and include the following field types: 4848 * 'D': decoration; something non-text and non-data (colons, commmas) 4849 * 'E': error message 4850 * 'G': gettext() the entire string; optional domainname as content 4851 * 'L': label; text preceding data 4852 * 'N': note; text following data 4853 * 'P': padding; whitespace 4854 * 'T': Title, where 'content' is a column title 4855 * 'U': Units, where 'content' is the unit label 4856 * 'V': value, where 'content' is the name of the field (the default) 4857 * 'W': warning message 4858 * '[': start a section of anchored text 4859 * ']': end a section of anchored text 4860 * The following modifiers are also supported: 4861 * 'c': flag: emit a colon after the label 4862 * 'd': field is only emitted for display styles (text and html) 4863 * 'e': field is only emitted for encoding styles (xml and json) 4864 * 'g': gettext() the field 4865 * 'h': humanize a numeric value (only for display styles) 4866 * 'k': this field is a key, suitable for XPath predicates 4867 * 'l': a leaf-list, a simple list of values 4868 * 'n': no quotes around this field 4869 * 'p': the field has plural gettext semantics (ngettext) 4870 * 'q': add quotes around this field 4871 * 't': trim whitespace around the value 4872 * 'w': emit a blank after the label 4873 * The print-fmt and encode-fmt strings is the printf-style formating 4874 * for this data. JSON and XML will use the encoding-fmt, if present. 4875 * If the encode-fmt is not provided, it defaults to the print-fmt. 4876 * If the print-fmt is not provided, it defaults to 's'. 4877 */ 4878static const char * 4879xo_parse_roles (xo_handle_t *xop, const char *fmt, 4880 const char *basep, xo_field_info_t *xfip) 4881{ 4882 const char *sp; 4883 unsigned ftype = 0; 4884 xo_xff_flags_t flags = 0; 4885 uint8_t fnum = 0; 4886 4887 for (sp = basep; sp; sp++) { 4888 if (*sp == ':' || *sp == '/' || *sp == '}') 4889 break; 4890 4891 if (*sp == '\\') { 4892 if (sp[1] == '\0') { 4893 xo_failure(xop, "backslash at the end of string"); 4894 return NULL; 4895 } 4896 4897 /* Anything backslashed is ignored */ 4898 sp += 1; 4899 continue; 4900 } 4901 4902 if (*sp == ',') { 4903 const char *np; 4904 for (np = ++sp; *np; np++) 4905 if (*np == ':' || *np == '/' || *np == '}' || *np == ',') 4906 break; 4907 4908 int slen = np - sp; 4909 if (slen > 0) { 4910 xo_xff_flags_t value; 4911 4912 value = xo_name_lookup(xo_role_names, sp, slen); 4913 if (value) 4914 ftype = value; 4915 else { 4916 value = xo_name_lookup(xo_modifier_names, sp, slen); 4917 if (value) 4918 flags |= value; 4919 else 4920 xo_failure(xop, "unknown keyword ignored: '%.*s'", 4921 slen, sp); 4922 } 4923 } 4924 4925 sp = np - 1; 4926 continue; 4927 } 4928 4929 switch (*sp) { 4930 case 'C': 4931 case 'D': 4932 case 'E': 4933 case 'G': 4934 case 'L': 4935 case 'N': 4936 case 'P': 4937 case 'T': 4938 case 'U': 4939 case 'V': 4940 case 'W': 4941 case '[': 4942 case ']': 4943 if (ftype != 0) { 4944 xo_failure(xop, "field descriptor uses multiple types: '%s'", 4945 xo_printable(fmt)); 4946 return NULL; 4947 } 4948 ftype = *sp; 4949 break; 4950 4951 case '0': 4952 case '1': 4953 case '2': 4954 case '3': 4955 case '4': 4956 case '5': 4957 case '6': 4958 case '7': 4959 case '8': 4960 case '9': 4961 fnum = (fnum * 10) + (*sp - '0'); 4962 break; 4963 4964 case 'c': 4965 flags |= XFF_COLON; 4966 break; 4967 4968 case 'd': 4969 flags |= XFF_DISPLAY_ONLY; 4970 break; 4971 4972 case 'e': 4973 flags |= XFF_ENCODE_ONLY; 4974 break; 4975 4976 case 'g': 4977 flags |= XFF_GT_FIELD; 4978 break; 4979 4980 case 'h': 4981 flags |= XFF_HUMANIZE; 4982 break; 4983 4984 case 'k': 4985 flags |= XFF_KEY; 4986 break; 4987 4988 case 'l': 4989 flags |= XFF_LEAF_LIST; 4990 break; 4991 4992 case 'n': 4993 flags |= XFF_NOQUOTE; 4994 break; 4995 4996 case 'p': 4997 flags |= XFF_GT_PLURAL; 4998 break; 4999 5000 case 'q': 5001 flags |= XFF_QUOTE; 5002 break; 5003 5004 case 't': 5005 flags |= XFF_TRIM_WS; 5006 break; 5007 5008 case 'w': 5009 flags |= XFF_WS; 5010 break; 5011 5012 default: 5013 xo_failure(xop, "field descriptor uses unknown modifier: '%s'", 5014 xo_printable(fmt)); 5015 /* 5016 * No good answer here; a bad format will likely 5017 * mean a core file. We just return and hope 5018 * the caller notices there's no output, and while 5019 * that seems, well, bad, there's nothing better. 5020 */ 5021 return NULL; 5022 } 5023 5024 if (ftype == 'N' || ftype == 'U') { 5025 if (flags & XFF_COLON) { 5026 xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: " 5027 "'%s'", xo_printable(fmt)); 5028 flags &= ~XFF_COLON; 5029 } 5030 } 5031 } 5032 5033 xfip->xfi_flags = flags; 5034 xfip->xfi_ftype = ftype ?: 'V'; 5035 xfip->xfi_fnum = fnum; 5036 5037 return sp; 5038} 5039 5040/* 5041 * Number any remaining fields that need numbers. Note that some 5042 * field types (text, newline, escaped braces) never get numbers. 5043 */ 5044static void 5045xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED, 5046 const char *fmt UNUSED, 5047 xo_field_info_t *fields) 5048{ 5049 xo_field_info_t *xfip; 5050 unsigned fnum, max_fields; 5051 uint64_t bits = 0; 5052 5053 /* First make a list of add the explicitly used bits */ 5054 for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) { 5055 switch (xfip->xfi_ftype) { 5056 case XO_ROLE_NEWLINE: /* Don't get numbered */ 5057 case XO_ROLE_TEXT: 5058 case XO_ROLE_EBRACE: 5059 case 'G': 5060 continue; 5061 } 5062 5063 fnum += 1; 5064 if (fnum >= 63) 5065 break; 5066 5067 if (xfip->xfi_fnum) 5068 bits |= 1 << xfip->xfi_fnum; 5069 } 5070 5071 max_fields = fnum; 5072 5073 for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) { 5074 switch (xfip->xfi_ftype) { 5075 case XO_ROLE_NEWLINE: /* Don't get numbered */ 5076 case XO_ROLE_TEXT: 5077 case XO_ROLE_EBRACE: 5078 case 'G': 5079 continue; 5080 } 5081 5082 if (xfip->xfi_fnum != 0) 5083 continue; 5084 5085 /* Find the next unassigned field */ 5086 for (fnum++; bits & (1 << fnum); fnum++) 5087 continue; 5088 5089 if (fnum > max_fields) 5090 break; 5091 5092 xfip->xfi_fnum = fnum; /* Mark the field number */ 5093 bits |= 1 << fnum; /* Mark it used */ 5094 } 5095} 5096 5097/* 5098 * The format string uses field numbers, so we need to whiffle thru it 5099 * and make sure everything's sane and lovely. 5100 */ 5101static int 5102xo_parse_field_numbers (xo_handle_t *xop, const char *fmt, 5103 xo_field_info_t *fields, unsigned num_fields) 5104{ 5105 xo_field_info_t *xfip; 5106 unsigned field, fnum; 5107 uint64_t bits = 0; 5108 5109 for (xfip = fields, field = 0; field < num_fields; xfip++, field++) { 5110 /* Fields default to 1:1 with natural position */ 5111 if (xfip->xfi_fnum == 0) 5112 xfip->xfi_fnum = field + 1; 5113 else if (xfip->xfi_fnum > num_fields) { 5114 xo_failure(xop, "field number exceeds number of fields: '%s'", fmt); 5115 return -1; 5116 } 5117 5118 fnum = xfip->xfi_fnum - 1; /* Move to zero origin */ 5119 if (fnum < 64) { /* Only test what fits */ 5120 if (bits & (1 << fnum)) { 5121 xo_failure(xop, "field number %u reused: '%s'", 5122 xfip->xfi_fnum, fmt); 5123 return -1; 5124 } 5125 bits |= 1 << fnum; 5126 } 5127 } 5128 5129 return 0; 5130} 5131 5132static int 5133xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields, 5134 unsigned num_fields, const char *fmt) 5135{ 5136 static const char default_format[] = "%s"; 5137 const char *cp, *sp, *ep, *basep; 5138 unsigned field = 0; 5139 xo_field_info_t *xfip = fields; 5140 unsigned seen_fnum = 0; 5141 5142 for (cp = fmt; *cp && field < num_fields; field++, xfip++) { 5143 xfip->xfi_start = cp; 5144 5145 if (*cp == '\n') { 5146 xfip->xfi_ftype = XO_ROLE_NEWLINE; 5147 xfip->xfi_len = 1; 5148 cp += 1; 5149 continue; 5150 } 5151 5152 if (*cp != '{') { 5153 /* Normal text */ 5154 for (sp = cp; *sp; sp++) { 5155 if (*sp == '{' || *sp == '\n') 5156 break; 5157 } 5158 5159 xfip->xfi_ftype = XO_ROLE_TEXT; 5160 xfip->xfi_content = cp; 5161 xfip->xfi_clen = sp - cp; 5162 xfip->xfi_next = sp; 5163 5164 cp = sp; 5165 continue; 5166 } 5167 5168 if (cp[1] == '{') { /* Start of {{escaped braces}} */ 5169 xfip->xfi_start = cp + 1; /* Start at second brace */ 5170 xfip->xfi_ftype = XO_ROLE_EBRACE; 5171 5172 cp += 2; /* Skip over _both_ characters */ 5173 for (sp = cp; *sp; sp++) { 5174 if (*sp == '}' && sp[1] == '}') 5175 break; 5176 } 5177 if (*sp == '\0') { 5178 xo_failure(xop, "missing closing '}}': '%s'", 5179 xo_printable(fmt)); 5180 return -1; 5181 } 5182 5183 xfip->xfi_len = sp - xfip->xfi_start + 1; 5184 5185 /* Move along the string, but don't run off the end */ 5186 if (*sp == '}' && sp[1] == '}') 5187 sp += 2; 5188 cp = *sp ? sp : sp; 5189 xfip->xfi_next = cp; 5190 continue; 5191 } 5192 5193 /* We are looking at the start of a field definition */ 5194 xfip->xfi_start = basep = cp + 1; 5195 5196 const char *format = NULL; 5197 int flen = 0; 5198 5199 /* Looking at roles and modifiers */ 5200 sp = xo_parse_roles(xop, fmt, basep, xfip); 5201 if (sp == NULL) { 5202 /* xo_failure has already been called */ 5203 return -1; 5204 } 5205 5206 if (xfip->xfi_fnum) 5207 seen_fnum = 1; 5208 5209 /* Looking at content */ 5210 if (*sp == ':') { 5211 for (ep = ++sp; *sp; sp++) { 5212 if (*sp == '}' || *sp == '/') 5213 break; 5214 if (*sp == '\\') { 5215 if (sp[1] == '\0') { 5216 xo_failure(xop, "backslash at the end of string"); 5217 return -1; 5218 } 5219 sp += 1; 5220 continue; 5221 } 5222 } 5223 if (ep != sp) { 5224 xfip->xfi_clen = sp - ep; 5225 xfip->xfi_content = ep; 5226 } 5227 } else { 5228 xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt)); 5229 return -1; 5230 } 5231 5232 /* Looking at main (display) format */ 5233 if (*sp == '/') { 5234 for (ep = ++sp; *sp; sp++) { 5235 if (*sp == '}' || *sp == '/') 5236 break; 5237 if (*sp == '\\') { 5238 if (sp[1] == '\0') { 5239 xo_failure(xop, "backslash at the end of string"); 5240 return -1; 5241 } 5242 sp += 1; 5243 continue; 5244 } 5245 } 5246 flen = sp - ep; 5247 format = ep; 5248 } 5249 5250 /* Looking at encoding format */ 5251 if (*sp == '/') { 5252 for (ep = ++sp; *sp; sp++) { 5253 if (*sp == '}') 5254 break; 5255 } 5256 5257 xfip->xfi_encoding = ep; 5258 xfip->xfi_elen = sp - ep; 5259 } 5260 5261 if (*sp != '}') { 5262 xo_failure(xop, "missing closing '}': %s", xo_printable(fmt)); 5263 return -1; 5264 } 5265 5266 xfip->xfi_len = sp - xfip->xfi_start; 5267 xfip->xfi_next = ++sp; 5268 5269 /* If we have content, then we have a default format */ 5270 if (xfip->xfi_clen || format) { 5271 if (format) { 5272 xfip->xfi_format = format; 5273 xfip->xfi_flen = flen; 5274 } else if (xo_role_wants_default_format(xfip->xfi_ftype)) { 5275 xfip->xfi_format = default_format; 5276 xfip->xfi_flen = 2; 5277 } 5278 } 5279 5280 cp = sp; 5281 } 5282 5283 int rc = 0; 5284 5285 /* 5286 * If we saw a field number on at least one field, then we need 5287 * to enforce some rules and/or guidelines. 5288 */ 5289 if (seen_fnum) 5290 rc = xo_parse_field_numbers(xop, fmt, fields, field); 5291 5292 return rc; 5293} 5294 5295/* 5296 * We are passed a pointer to a format string just past the "{G:}" 5297 * field. We build a simplified version of the format string. 5298 */ 5299static int 5300xo_gettext_simplify_format (xo_handle_t *xop UNUSED, 5301 xo_buffer_t *xbp, 5302 xo_field_info_t *fields, 5303 int this_field, 5304 const char *fmt UNUSED, 5305 xo_simplify_field_func_t field_cb) 5306{ 5307 unsigned ftype; 5308 xo_xff_flags_t flags; 5309 int field = this_field + 1; 5310 xo_field_info_t *xfip; 5311 char ch; 5312 5313 for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) { 5314 ftype = xfip->xfi_ftype; 5315 flags = xfip->xfi_flags; 5316 5317 if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') { 5318 if (field_cb) 5319 field_cb(xfip->xfi_content, xfip->xfi_clen, 5320 (flags & XFF_GT_PLURAL) ? 1 : 0); 5321 } 5322 5323 switch (ftype) { 5324 case 'G': 5325 /* Ignore gettext roles */ 5326 break; 5327 5328 case XO_ROLE_NEWLINE: 5329 xo_buf_append(xbp, "\n", 1); 5330 break; 5331 5332 case XO_ROLE_EBRACE: 5333 xo_buf_append(xbp, "{", 1); 5334 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); 5335 xo_buf_append(xbp, "}", 1); 5336 break; 5337 5338 case XO_ROLE_TEXT: 5339 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); 5340 break; 5341 5342 default: 5343 xo_buf_append(xbp, "{", 1); 5344 if (ftype != 'V') { 5345 ch = ftype; 5346 xo_buf_append(xbp, &ch, 1); 5347 } 5348 5349 unsigned fnum = xfip->xfi_fnum ?: 0; 5350 if (fnum) { 5351 char num[12]; 5352 /* Field numbers are origin 1, not 0, following printf(3) */ 5353 snprintf(num, sizeof(num), "%u", fnum); 5354 xo_buf_append(xbp, num, strlen(num)); 5355 } 5356 5357 xo_buf_append(xbp, ":", 1); 5358 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); 5359 xo_buf_append(xbp, "}", 1); 5360 } 5361 } 5362 5363 xo_buf_append(xbp, "", 1); 5364 return 0; 5365} 5366 5367void 5368xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */ 5369void 5370xo_dump_fields (xo_field_info_t *fields) 5371{ 5372 xo_field_info_t *xfip; 5373 5374 for (xfip = fields; xfip->xfi_ftype; xfip++) { 5375 printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n", 5376 (unsigned long) (xfip - fields), xfip->xfi_fnum, 5377 (unsigned long) xfip->xfi_flags, 5378 isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ', 5379 xfip->xfi_ftype, 5380 xfip->xfi_clen, xfip->xfi_content ?: "", 5381 xfip->xfi_flen, xfip->xfi_format ?: "", 5382 xfip->xfi_elen, xfip->xfi_encoding ?: ""); 5383 } 5384} 5385 5386#ifdef HAVE_GETTEXT 5387/* 5388 * Find the field that matches the given field number 5389 */ 5390static xo_field_info_t * 5391xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum) 5392{ 5393 xo_field_info_t *xfip; 5394 5395 for (xfip = fields; xfip->xfi_ftype; xfip++) 5396 if (xfip->xfi_fnum == fnum) 5397 return xfip; 5398 5399 return NULL; 5400} 5401 5402/* 5403 * At this point, we need to consider if the fields have been reordered, 5404 * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}". 5405 * 5406 * We need to rewrite the new_fields using the old fields order, 5407 * so that we can render the message using the arguments as they 5408 * appear on the stack. It's a lot of work, but we don't really 5409 * want to (eventually) fall into the standard printf code which 5410 * means using the arguments straight (and in order) from the 5411 * varargs we were originally passed. 5412 */ 5413static void 5414xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED, 5415 xo_field_info_t *fields, unsigned max_fields) 5416{ 5417 xo_field_info_t tmp[max_fields]; 5418 bzero(tmp, max_fields * sizeof(tmp[0])); 5419 5420 unsigned fnum = 0; 5421 xo_field_info_t *newp, *outp, *zp; 5422 for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) { 5423 switch (newp->xfi_ftype) { 5424 case XO_ROLE_NEWLINE: /* Don't get numbered */ 5425 case XO_ROLE_TEXT: 5426 case XO_ROLE_EBRACE: 5427 case 'G': 5428 *outp = *newp; 5429 outp->xfi_renum = 0; 5430 continue; 5431 } 5432 5433 zp = xo_gettext_find_field(fields, ++fnum); 5434 if (zp == NULL) { /* Should not occur */ 5435 *outp = *newp; 5436 outp->xfi_renum = 0; 5437 continue; 5438 } 5439 5440 *outp = *zp; 5441 outp->xfi_renum = newp->xfi_fnum; 5442 } 5443 5444 memcpy(fields, tmp, max_fields * sizeof(tmp[0])); 5445} 5446 5447/* 5448 * We've got two lists of fields, the old list from the original 5449 * format string and the new one from the parsed gettext reply. The 5450 * new list has the localized words, where the old list has the 5451 * formatting information. We need to combine them into a single list 5452 * (the new list). 5453 * 5454 * If the list needs to be reordered, then we've got more serious work 5455 * to do. 5456 */ 5457static int 5458xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED, 5459 const char *gtfmt, xo_field_info_t *old_fields, 5460 xo_field_info_t *new_fields, unsigned new_max_fields, 5461 int *reorderedp) 5462{ 5463 int reordered = 0; 5464 xo_field_info_t *newp, *oldp, *startp = old_fields; 5465 5466 xo_gettext_finish_numbering_fields(xop, fmt, old_fields); 5467 5468 for (newp = new_fields; newp->xfi_ftype; newp++) { 5469 switch (newp->xfi_ftype) { 5470 case XO_ROLE_NEWLINE: 5471 case XO_ROLE_TEXT: 5472 case XO_ROLE_EBRACE: 5473 continue; 5474 5475 case 'V': 5476 for (oldp = startp; oldp->xfi_ftype; oldp++) { 5477 if (oldp->xfi_ftype != 'V') 5478 continue; 5479 if (newp->xfi_clen != oldp->xfi_clen 5480 || strncmp(newp->xfi_content, oldp->xfi_content, 5481 oldp->xfi_clen) != 0) { 5482 reordered = 1; 5483 continue; 5484 } 5485 startp = oldp + 1; 5486 break; 5487 } 5488 5489 /* Didn't find it on the first pass (starting from start) */ 5490 if (oldp->xfi_ftype == 0) { 5491 for (oldp = old_fields; oldp < startp; oldp++) { 5492 if (oldp->xfi_ftype != 'V') 5493 continue; 5494 if (newp->xfi_clen != oldp->xfi_clen) 5495 continue; 5496 if (strncmp(newp->xfi_content, oldp->xfi_content, 5497 oldp->xfi_clen) != 0) 5498 continue; 5499 reordered = 1; 5500 break; 5501 } 5502 if (oldp == startp) { 5503 /* Field not found */ 5504 xo_failure(xop, "post-gettext format can't find field " 5505 "'%.*s' in format '%s'", 5506 newp->xfi_clen, newp->xfi_content, 5507 xo_printable(gtfmt)); 5508 return -1; 5509 } 5510 } 5511 break; 5512 5513 default: 5514 /* 5515 * Other fields don't have names for us to use, so if 5516 * the types aren't the same, then we'll have to assume 5517 * the original field is a match. 5518 */ 5519 for (oldp = startp; oldp->xfi_ftype; oldp++) { 5520 if (oldp->xfi_ftype == 'V') /* Can't go past these */ 5521 break; 5522 if (oldp->xfi_ftype == newp->xfi_ftype) 5523 goto copy_it; /* Assumably we have a match */ 5524 } 5525 continue; 5526 } 5527 5528 /* 5529 * Found a match; copy over appropriate fields 5530 */ 5531 copy_it: 5532 newp->xfi_flags = oldp->xfi_flags; 5533 newp->xfi_fnum = oldp->xfi_fnum; 5534 newp->xfi_format = oldp->xfi_format; 5535 newp->xfi_flen = oldp->xfi_flen; 5536 newp->xfi_encoding = oldp->xfi_encoding; 5537 newp->xfi_elen = oldp->xfi_elen; 5538 } 5539 5540 *reorderedp = reordered; 5541 if (reordered) { 5542 xo_gettext_finish_numbering_fields(xop, fmt, new_fields); 5543 xo_gettext_rewrite_fields(xop, new_fields, new_max_fields); 5544 } 5545 5546 return 0; 5547} 5548 5549/* 5550 * We don't want to make gettext() calls here with a complete format 5551 * string, since that means changing a flag would mean a 5552 * labor-intensive re-translation expense. Instead we build a 5553 * simplified form with a reduced level of detail, perform a lookup on 5554 * that string and then re-insert the formating info. 5555 * 5556 * So something like: 5557 * xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...) 5558 * would have a lookup string of: 5559 * "close {:fd} returned {:error} {:test}\n" 5560 * 5561 * We also need to handling reordering of fields, where the gettext() 5562 * reply string uses fields in a different order than the original 5563 * format string: 5564 * "cluse-a {:fd} retoorned {:test}. Bork {:error} Bork. Bork.\n" 5565 * If we have to reorder fields within the message, then things get 5566 * complicated. See xo_gettext_rewrite_fields. 5567 * 5568 * Summary: i18n aighn't cheap. 5569 */ 5570static const char * 5571xo_gettext_build_format (xo_handle_t *xop UNUSED, 5572 xo_field_info_t *fields UNUSED, 5573 int this_field UNUSED, 5574 const char *fmt, char **new_fmtp) 5575{ 5576 if (xo_style_is_encoding(xop)) 5577 goto bail; 5578 5579 xo_buffer_t xb; 5580 xo_buf_init(&xb); 5581 5582 if (xo_gettext_simplify_format(xop, &xb, fields, 5583 this_field, fmt, NULL)) 5584 goto bail2; 5585 5586 const char *gtfmt = xo_dgettext(xop, xb.xb_bufp); 5587 if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0) 5588 goto bail2; 5589 5590 xo_buf_cleanup(&xb); 5591 5592 char *new_fmt = xo_strndup(gtfmt, -1); 5593 if (new_fmt == NULL) 5594 goto bail2; 5595 5596 *new_fmtp = new_fmt; 5597 return new_fmt; 5598 5599 bail2: 5600 xo_buf_cleanup(&xb); 5601 bail: 5602 *new_fmtp = NULL; 5603 return fmt; 5604} 5605 5606static void 5607xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields, 5608 unsigned *fstart, unsigned min_fstart, 5609 unsigned *fend, unsigned max_fend) 5610{ 5611 xo_field_info_t *xfip; 5612 char *buf; 5613 unsigned base = fstart[min_fstart]; 5614 unsigned blen = fend[max_fend] - base; 5615 xo_buffer_t *xbp = &xop->xo_data; 5616 5617 if (blen == 0) 5618 return; 5619 5620 buf = xo_realloc(NULL, blen); 5621 if (buf == NULL) 5622 return; 5623 5624 memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */ 5625 5626 unsigned field = min_fstart, soff, doff = base, len, fnum; 5627 xo_field_info_t *zp; 5628 5629 /* 5630 * Be aware there are two competing views of "field number": we 5631 * want the user to thing in terms of "The {1:size}" where {G:}, 5632 * newlines, escaped braces, and text don't have numbers. But is 5633 * also the internal view, where we have an array of 5634 * xo_field_info_t and every field have an index. fnum, fstart[] 5635 * and fend[] are the latter, but xfi_renum is the former. 5636 */ 5637 for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) { 5638 fnum = field; 5639 if (xfip->xfi_renum) { 5640 zp = xo_gettext_find_field(fields, xfip->xfi_renum); 5641 fnum = zp ? zp - fields : field; 5642 } 5643 5644 soff = fstart[fnum]; 5645 len = fend[fnum] - soff; 5646 5647 if (len > 0) { 5648 soff -= base; 5649 memcpy(xbp->xb_bufp + doff, buf + soff, len); 5650 doff += len; 5651 } 5652 } 5653 5654 xo_free(buf); 5655} 5656#else /* HAVE_GETTEXT */ 5657static const char * 5658xo_gettext_build_format (xo_handle_t *xop UNUSED, 5659 xo_field_info_t *fields UNUSED, 5660 int this_field UNUSED, 5661 const char *fmt UNUSED, char **new_fmtp) 5662{ 5663 *new_fmtp = NULL; 5664 return fmt; 5665} 5666 5667static int 5668xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED, 5669 const char *gtfmt UNUSED, 5670 xo_field_info_t *old_fields UNUSED, 5671 xo_field_info_t *new_fields UNUSED, 5672 unsigned new_max_fields UNUSED, 5673 int *reorderedp UNUSED) 5674{ 5675 return -1; 5676} 5677 5678static void 5679xo_gettext_rebuild_content (xo_handle_t *xop UNUSED, 5680 xo_field_info_t *fields UNUSED, 5681 unsigned *fstart UNUSED, unsigned min_fstart UNUSED, 5682 unsigned *fend UNUSED, unsigned max_fend UNUSED) 5683{ 5684 return; 5685} 5686#endif /* HAVE_GETTEXT */ 5687 5688/* 5689 * The central function for emitting libxo output. 5690 */ 5691static int 5692xo_do_emit (xo_handle_t *xop, const char *fmt) 5693{ 5694 int gettext_inuse = 0; 5695 int gettext_changed = 0; 5696 int gettext_reordered = 0; 5697 xo_field_info_t *new_fields = NULL; 5698 5699 int rc = 0; 5700 int flush = XOF_ISSET(xop, XOF_FLUSH); 5701 int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE); 5702 char *new_fmt = NULL; 5703 5704 if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER) 5705 flush_line = 0; 5706 5707 xop->xo_columns = 0; /* Always reset it */ 5708 xop->xo_errno = errno; /* Save for "%m" */ 5709 5710 unsigned max_fields = xo_count_fields(xop, fmt), field; 5711 xo_field_info_t fields[max_fields], *xfip; 5712 5713 bzero(fields, max_fields * sizeof(fields[0])); 5714 5715 if (xo_parse_fields(xop, fields, max_fields, fmt)) 5716 return -1; /* Warning already displayed */ 5717 5718 unsigned ftype; 5719 xo_xff_flags_t flags; 5720 5721 /* 5722 * Some overhead for gettext; if the fields in the msgstr returned 5723 * by gettext are reordered, then we need to record start and end 5724 * for each field. We'll go ahead and render the fields in the 5725 * normal order, but later we can then reconstruct the reordered 5726 * fields using these fstart/fend values. 5727 */ 5728 unsigned flimit = max_fields * 2; /* Pessimistic limit */ 5729 unsigned min_fstart = flimit - 1; 5730 unsigned max_fend = 0; /* Highest recorded fend[] entry */ 5731 unsigned fstart[flimit]; 5732 bzero(fstart, flimit * sizeof(fstart[0])); 5733 unsigned fend[flimit]; 5734 bzero(fend, flimit * sizeof(fend[0])); 5735 5736 for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields; 5737 xfip++, field++) { 5738 ftype = xfip->xfi_ftype; 5739 flags = xfip->xfi_flags; 5740 5741 /* Record field start offset */ 5742 if (gettext_reordered) { 5743 fstart[field] = xo_buf_offset(&xop->xo_data); 5744 if (min_fstart > field) 5745 min_fstart = field; 5746 } 5747 5748 if (ftype == XO_ROLE_NEWLINE) { 5749 xo_line_close(xop); 5750 if (flush_line && xo_flush_h(xop) < 0) 5751 return -1; 5752 goto bottom; 5753 5754 } else if (ftype == XO_ROLE_EBRACE) { 5755 xo_format_text(xop, xfip->xfi_start, xfip->xfi_len); 5756 goto bottom; 5757 5758 } else if (ftype == XO_ROLE_TEXT) { 5759 /* Normal text */ 5760 xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen); 5761 goto bottom; 5762 } 5763 5764 /* 5765 * Notes and units need the 'w' flag handled before the content. 5766 */ 5767 if (ftype == 'N' || ftype == 'U') { 5768 if (flags & XFF_WS) { 5769 xo_format_content(xop, "padding", NULL, " ", 1, 5770 NULL, 0, flags); 5771 flags &= ~XFF_WS; /* Block later handling of this */ 5772 } 5773 } 5774 5775 if (ftype == 'V') 5776 xo_format_value(xop, xfip->xfi_content, xfip->xfi_clen, 5777 xfip->xfi_format, xfip->xfi_flen, 5778 xfip->xfi_encoding, xfip->xfi_elen, flags); 5779 else if (ftype == '[') 5780 xo_anchor_start(xop, xfip); 5781 else if (ftype == ']') 5782 xo_anchor_stop(xop, xfip); 5783 else if (ftype == 'C') 5784 xo_format_colors(xop, xfip); 5785 5786 else if (ftype == 'G') { 5787 /* 5788 * A {G:domain} field; disect the domain name and translate 5789 * the remaining portion of the input string. If the user 5790 * didn't put the {G:} at the start of the format string, then 5791 * assumably they just want us to translate the rest of it. 5792 * Since gettext returns strings in a static buffer, we make 5793 * a copy in new_fmt. 5794 */ 5795 xo_set_gettext_domain(xop, xfip); 5796 5797 if (!gettext_inuse) { /* Only translate once */ 5798 gettext_inuse = 1; 5799 if (new_fmt) { 5800 xo_free(new_fmt); 5801 new_fmt = NULL; 5802 } 5803 5804 xo_gettext_build_format(xop, fields, field, 5805 xfip->xfi_next, &new_fmt); 5806 if (new_fmt) { 5807 gettext_changed = 1; 5808 5809 unsigned new_max_fields = xo_count_fields(xop, new_fmt); 5810 5811 if (++new_max_fields < max_fields) 5812 new_max_fields = max_fields; 5813 5814 /* Leave a blank slot at the beginning */ 5815 int sz = (new_max_fields + 1) * sizeof(xo_field_info_t); 5816 new_fields = alloca(sz); 5817 bzero(new_fields, sz); 5818 5819 if (!xo_parse_fields(xop, new_fields + 1, 5820 new_max_fields, new_fmt)) { 5821 gettext_reordered = 0; 5822 5823 if (!xo_gettext_combine_formats(xop, fmt, new_fmt, 5824 fields, new_fields + 1, 5825 new_max_fields, &gettext_reordered)) { 5826 5827 if (gettext_reordered) { 5828 if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) 5829 xo_failure(xop, "gettext finds reordered " 5830 "fields in '%s' and '%s'", 5831 xo_printable(fmt), 5832 xo_printable(new_fmt)); 5833 flush_line = 0; /* Must keep at content */ 5834 XOIF_SET(xop, XOIF_REORDER); 5835 } 5836 5837 field = -1; /* Will be incremented at top of loop */ 5838 xfip = new_fields; 5839 max_fields = new_max_fields; 5840 } 5841 } 5842 } 5843 } 5844 continue; 5845 5846 } else if (xfip->xfi_clen || xfip->xfi_format) { 5847 5848 const char *class_name = xo_class_name(ftype); 5849 if (class_name) 5850 xo_format_content(xop, class_name, xo_tag_name(ftype), 5851 xfip->xfi_content, xfip->xfi_clen, 5852 xfip->xfi_format, xfip->xfi_flen, flags); 5853 else if (ftype == 'T') 5854 xo_format_title(xop, xfip); 5855 else if (ftype == 'U') 5856 xo_format_units(xop, xfip); 5857 else 5858 xo_failure(xop, "unknown field type: '%c'", ftype); 5859 } 5860 5861 if (flags & XFF_COLON) 5862 xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0); 5863 5864 if (flags & XFF_WS) 5865 xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0); 5866 5867 bottom: 5868 /* Record the end-of-field offset */ 5869 if (gettext_reordered) { 5870 fend[field] = xo_buf_offset(&xop->xo_data); 5871 max_fend = field; 5872 } 5873 } 5874 5875 if (gettext_changed && gettext_reordered) { 5876 /* Final step: rebuild the content using the rendered fields */ 5877 xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart, 5878 fend, max_fend); 5879 } 5880 5881 XOIF_CLEAR(xop, XOIF_REORDER); 5882 5883 /* If we don't have an anchor, write the text out */ 5884 if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) { 5885 if (xo_write(xop) < 0) 5886 rc = -1; /* Report failure */ 5887 else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0) 5888 rc = -1; 5889 } 5890 5891 if (new_fmt) 5892 xo_free(new_fmt); 5893 5894 /* 5895 * We've carried the gettext domainname inside our handle just for 5896 * convenience, but we need to ensure it doesn't survive across 5897 * xo_emit calls. 5898 */ 5899 if (xop->xo_gt_domain) { 5900 xo_free(xop->xo_gt_domain); 5901 xop->xo_gt_domain = NULL; 5902 } 5903 5904 return (rc < 0) ? rc : (int) xop->xo_columns; 5905} 5906 5907/* 5908 * Rebuild a format string in a gettext-friendly format. This function 5909 * is exposed to tools can perform this function. See xo(1). 5910 */ 5911char * 5912xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers, 5913 xo_simplify_field_func_t field_cb) 5914{ 5915 xop = xo_default(xop); 5916 5917 xop->xo_columns = 0; /* Always reset it */ 5918 xop->xo_errno = errno; /* Save for "%m" */ 5919 5920 unsigned max_fields = xo_count_fields(xop, fmt); 5921 xo_field_info_t fields[max_fields]; 5922 5923 bzero(fields, max_fields * sizeof(fields[0])); 5924 5925 if (xo_parse_fields(xop, fields, max_fields, fmt)) 5926 return NULL; /* Warning already displayed */ 5927 5928 xo_buffer_t xb; 5929 xo_buf_init(&xb); 5930 5931 if (with_numbers) 5932 xo_gettext_finish_numbering_fields(xop, fmt, fields); 5933 5934 if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb)) 5935 return NULL; 5936 5937 return xb.xb_bufp; 5938} 5939 5940int 5941xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap) 5942{ 5943 int rc; 5944 5945 xop = xo_default(xop); 5946 va_copy(xop->xo_vap, vap); 5947 rc = xo_do_emit(xop, fmt); 5948 va_end(xop->xo_vap); 5949 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 5950 5951 return rc; 5952} 5953 5954int 5955xo_emit_h (xo_handle_t *xop, const char *fmt, ...) 5956{ 5957 int rc; 5958 5959 xop = xo_default(xop); 5960 va_start(xop->xo_vap, fmt); 5961 rc = xo_do_emit(xop, fmt); 5962 va_end(xop->xo_vap); 5963 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 5964 5965 return rc; 5966} 5967 5968int 5969xo_emit (const char *fmt, ...) 5970{ 5971 xo_handle_t *xop = xo_default(NULL); 5972 int rc; 5973 5974 va_start(xop->xo_vap, fmt); 5975 rc = xo_do_emit(xop, fmt); 5976 va_end(xop->xo_vap); 5977 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 5978 5979 return rc; 5980} 5981 5982int 5983xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap) 5984{ 5985 const int extra = 5; /* space, equals, quote, quote, and nul */ 5986 xop = xo_default(xop); 5987 5988 int rc = 0; 5989 int nlen = strlen(name); 5990 xo_buffer_t *xbp = &xop->xo_attrs; 5991 unsigned name_offset, value_offset; 5992 5993 switch (xo_style(xop)) { 5994 case XO_STYLE_XML: 5995 if (!xo_buf_has_room(xbp, nlen + extra)) 5996 return -1; 5997 5998 *xbp->xb_curp++ = ' '; 5999 memcpy(xbp->xb_curp, name, nlen); 6000 xbp->xb_curp += nlen; 6001 *xbp->xb_curp++ = '='; 6002 *xbp->xb_curp++ = '"'; 6003 6004 rc = xo_vsnprintf(xop, xbp, fmt, vap); 6005 6006 if (rc >= 0) { 6007 rc = xo_escape_xml(xbp, rc, 1); 6008 xbp->xb_curp += rc; 6009 } 6010 6011 if (!xo_buf_has_room(xbp, 2)) 6012 return -1; 6013 6014 *xbp->xb_curp++ = '"'; 6015 *xbp->xb_curp = '\0'; 6016 6017 rc += nlen + extra; 6018 break; 6019 6020 case XO_STYLE_ENCODER: 6021 name_offset = xo_buf_offset(xbp); 6022 xo_buf_append(xbp, name, nlen); 6023 xo_buf_append(xbp, "", 1); 6024 6025 value_offset = xo_buf_offset(xbp); 6026 rc = xo_vsnprintf(xop, xbp, fmt, vap); 6027 if (rc >= 0) { 6028 xbp->xb_curp += rc; 6029 *xbp->xb_curp = '\0'; 6030 rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE, 6031 xo_buf_data(xbp, name_offset), 6032 xo_buf_data(xbp, value_offset)); 6033 } 6034 } 6035 6036 return rc; 6037} 6038 6039int 6040xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...) 6041{ 6042 int rc; 6043 va_list vap; 6044 6045 va_start(vap, fmt); 6046 rc = xo_attr_hv(xop, name, fmt, vap); 6047 va_end(vap); 6048 6049 return rc; 6050} 6051 6052int 6053xo_attr (const char *name, const char *fmt, ...) 6054{ 6055 int rc; 6056 va_list vap; 6057 6058 va_start(vap, fmt); 6059 rc = xo_attr_hv(NULL, name, fmt, vap); 6060 va_end(vap); 6061 6062 return rc; 6063} 6064 6065static void 6066xo_stack_set_flags (xo_handle_t *xop) 6067{ 6068 if (XOF_ISSET(xop, XOF_NOT_FIRST)) { 6069 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6070 6071 xsp->xs_flags |= XSF_NOT_FIRST; 6072 XOF_CLEAR(xop, XOF_NOT_FIRST); 6073 } 6074} 6075 6076static void 6077xo_depth_change (xo_handle_t *xop, const char *name, 6078 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags) 6079{ 6080 if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT) 6081 indent = 0; 6082 6083 if (XOF_ISSET(xop, XOF_DTRT)) 6084 flags |= XSF_DTRT; 6085 6086 if (delta >= 0) { /* Push operation */ 6087 if (xo_depth_check(xop, xop->xo_depth + delta)) 6088 return; 6089 6090 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta]; 6091 xsp->xs_flags = flags; 6092 xsp->xs_state = state; 6093 xo_stack_set_flags(xop); 6094 6095 if (name == NULL) 6096 name = XO_FAILURE_NAME; 6097 6098 xsp->xs_name = xo_strndup(name, -1); 6099 6100 } else { /* Pop operation */ 6101 if (xop->xo_depth == 0) { 6102 if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE)) 6103 xo_failure(xop, "close with empty stack: '%s'", name); 6104 return; 6105 } 6106 6107 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6108 if (XOF_ISSET(xop, XOF_WARN)) { 6109 const char *top = xsp->xs_name; 6110 if (top && strcmp(name, top) != 0) { 6111 xo_failure(xop, "incorrect close: '%s' .vs. '%s'", 6112 name, top); 6113 return; 6114 } 6115 if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) { 6116 xo_failure(xop, "list close on list confict: '%s'", 6117 name); 6118 return; 6119 } 6120 if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) { 6121 xo_failure(xop, "list close on instance confict: '%s'", 6122 name); 6123 return; 6124 } 6125 } 6126 6127 if (xsp->xs_name) { 6128 xo_free(xsp->xs_name); 6129 xsp->xs_name = NULL; 6130 } 6131 if (xsp->xs_keys) { 6132 xo_free(xsp->xs_keys); 6133 xsp->xs_keys = NULL; 6134 } 6135 } 6136 6137 xop->xo_depth += delta; /* Record new depth */ 6138 xop->xo_indent += indent; 6139} 6140 6141void 6142xo_set_depth (xo_handle_t *xop, int depth) 6143{ 6144 xop = xo_default(xop); 6145 6146 if (xo_depth_check(xop, depth)) 6147 return; 6148 6149 xop->xo_depth += depth; 6150 xop->xo_indent += depth; 6151} 6152 6153static xo_xsf_flags_t 6154xo_stack_flags (unsigned xflags) 6155{ 6156 if (xflags & XOF_DTRT) 6157 return XSF_DTRT; 6158 return 0; 6159} 6160 6161static void 6162xo_emit_top (xo_handle_t *xop, const char *ppn) 6163{ 6164 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); 6165 XOIF_SET(xop, XOIF_TOP_EMITTED); 6166 6167 if (xop->xo_version) { 6168 xo_printf(xop, "%*s\"__version\": \"%s\", %s", 6169 xo_indent(xop), "", xop->xo_version, ppn); 6170 xo_free(xop->xo_version); 6171 xop->xo_version = NULL; 6172 } 6173} 6174 6175static int 6176xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) 6177{ 6178 int rc = 0; 6179 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6180 const char *pre_nl = ""; 6181 6182 if (name == NULL) { 6183 xo_failure(xop, "NULL passed for container name"); 6184 name = XO_FAILURE_NAME; 6185 } 6186 6187 flags |= xop->xo_flags; /* Pick up handle flags */ 6188 6189 switch (xo_style(xop)) { 6190 case XO_STYLE_XML: 6191 rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name); 6192 6193 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { 6194 rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp; 6195 xo_data_append(xop, xop->xo_attrs.xb_bufp, 6196 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); 6197 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; 6198 } 6199 6200 rc += xo_printf(xop, ">%s", ppn); 6201 break; 6202 6203 case XO_STYLE_JSON: 6204 xo_stack_set_flags(xop); 6205 6206 if (!XOF_ISSET(xop, XOF_NO_TOP) 6207 && !XOIF_ISSET(xop, XOIF_TOP_EMITTED)) 6208 xo_emit_top(xop, ppn); 6209 6210 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6211 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; 6212 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6213 6214 rc = xo_printf(xop, "%s%*s\"%s\": {%s", 6215 pre_nl, xo_indent(xop), "", name, ppn); 6216 break; 6217 6218 case XO_STYLE_SDPARAMS: 6219 break; 6220 6221 case XO_STYLE_ENCODER: 6222 rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL); 6223 break; 6224 } 6225 6226 xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER, 6227 xo_stack_flags(flags)); 6228 6229 return rc; 6230} 6231 6232static int 6233xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) 6234{ 6235 return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER); 6236} 6237 6238int 6239xo_open_container_h (xo_handle_t *xop, const char *name) 6240{ 6241 return xo_open_container_hf(xop, 0, name); 6242} 6243 6244int 6245xo_open_container (const char *name) 6246{ 6247 return xo_open_container_hf(NULL, 0, name); 6248} 6249 6250int 6251xo_open_container_hd (xo_handle_t *xop, const char *name) 6252{ 6253 return xo_open_container_hf(xop, XOF_DTRT, name); 6254} 6255 6256int 6257xo_open_container_d (const char *name) 6258{ 6259 return xo_open_container_hf(NULL, XOF_DTRT, name); 6260} 6261 6262static int 6263xo_do_close_container (xo_handle_t *xop, const char *name) 6264{ 6265 xop = xo_default(xop); 6266 6267 int rc = 0; 6268 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6269 const char *pre_nl = ""; 6270 6271 if (name == NULL) { 6272 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6273 6274 name = xsp->xs_name; 6275 if (name) { 6276 int len = strlen(name) + 1; 6277 /* We need to make a local copy; xo_depth_change will free it */ 6278 char *cp = alloca(len); 6279 memcpy(cp, name, len); 6280 name = cp; 6281 } else if (!(xsp->xs_flags & XSF_DTRT)) { 6282 xo_failure(xop, "missing name without 'dtrt' mode"); 6283 name = XO_FAILURE_NAME; 6284 } 6285 } 6286 6287 switch (xo_style(xop)) { 6288 case XO_STYLE_XML: 6289 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0); 6290 rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn); 6291 break; 6292 6293 case XO_STYLE_JSON: 6294 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6295 ppn = (xop->xo_depth <= 1) ? "\n" : ""; 6296 6297 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0); 6298 rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn); 6299 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6300 break; 6301 6302 case XO_STYLE_HTML: 6303 case XO_STYLE_TEXT: 6304 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0); 6305 break; 6306 6307 case XO_STYLE_SDPARAMS: 6308 break; 6309 6310 case XO_STYLE_ENCODER: 6311 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0); 6312 rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL); 6313 break; 6314 } 6315 6316 return rc; 6317} 6318 6319int 6320xo_close_container_h (xo_handle_t *xop, const char *name) 6321{ 6322 return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER); 6323} 6324 6325int 6326xo_close_container (const char *name) 6327{ 6328 return xo_close_container_h(NULL, name); 6329} 6330 6331int 6332xo_close_container_hd (xo_handle_t *xop) 6333{ 6334 return xo_close_container_h(xop, NULL); 6335} 6336 6337int 6338xo_close_container_d (void) 6339{ 6340 return xo_close_container_h(NULL, NULL); 6341} 6342 6343static int 6344xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 6345{ 6346 int rc = 0; 6347 int indent = 0; 6348 6349 xop = xo_default(xop); 6350 6351 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6352 const char *pre_nl = ""; 6353 6354 switch (xo_style(xop)) { 6355 case XO_STYLE_JSON: 6356 6357 indent = 1; 6358 if (!XOF_ISSET(xop, XOF_NO_TOP) 6359 && !XOIF_ISSET(xop, XOIF_TOP_EMITTED)) 6360 xo_emit_top(xop, ppn); 6361 6362 if (name == NULL) { 6363 xo_failure(xop, "NULL passed for list name"); 6364 name = XO_FAILURE_NAME; 6365 } 6366 6367 xo_stack_set_flags(xop); 6368 6369 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6370 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; 6371 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6372 6373 rc = xo_printf(xop, "%s%*s\"%s\": [%s", 6374 pre_nl, xo_indent(xop), "", name, ppn); 6375 break; 6376 6377 case XO_STYLE_ENCODER: 6378 rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL); 6379 break; 6380 } 6381 6382 xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST, 6383 XSF_LIST | xo_stack_flags(flags)); 6384 6385 return rc; 6386} 6387 6388static int 6389xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 6390{ 6391 return xo_transition(xop, flags, name, XSS_OPEN_LIST); 6392} 6393 6394int 6395xo_open_list_h (xo_handle_t *xop, const char *name UNUSED) 6396{ 6397 return xo_open_list_hf(xop, 0, name); 6398} 6399 6400int 6401xo_open_list (const char *name) 6402{ 6403 return xo_open_list_hf(NULL, 0, name); 6404} 6405 6406int 6407xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED) 6408{ 6409 return xo_open_list_hf(xop, XOF_DTRT, name); 6410} 6411 6412int 6413xo_open_list_d (const char *name) 6414{ 6415 return xo_open_list_hf(NULL, XOF_DTRT, name); 6416} 6417 6418static int 6419xo_do_close_list (xo_handle_t *xop, const char *name) 6420{ 6421 int rc = 0; 6422 const char *pre_nl = ""; 6423 6424 if (name == NULL) { 6425 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6426 6427 name = xsp->xs_name; 6428 if (name) { 6429 int len = strlen(name) + 1; 6430 /* We need to make a local copy; xo_depth_change will free it */ 6431 char *cp = alloca(len); 6432 memcpy(cp, name, len); 6433 name = cp; 6434 } else if (!(xsp->xs_flags & XSF_DTRT)) { 6435 xo_failure(xop, "missing name without 'dtrt' mode"); 6436 name = XO_FAILURE_NAME; 6437 } 6438 } 6439 6440 switch (xo_style(xop)) { 6441 case XO_STYLE_JSON: 6442 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6443 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6444 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6445 6446 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST); 6447 rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); 6448 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6449 break; 6450 6451 case XO_STYLE_ENCODER: 6452 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST); 6453 rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL); 6454 break; 6455 6456 default: 6457 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST); 6458 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6459 break; 6460 } 6461 6462 return rc; 6463} 6464 6465int 6466xo_close_list_h (xo_handle_t *xop, const char *name) 6467{ 6468 return xo_transition(xop, 0, name, XSS_CLOSE_LIST); 6469} 6470 6471int 6472xo_close_list (const char *name) 6473{ 6474 return xo_close_list_h(NULL, name); 6475} 6476 6477int 6478xo_close_list_hd (xo_handle_t *xop) 6479{ 6480 return xo_close_list_h(xop, NULL); 6481} 6482 6483int 6484xo_close_list_d (void) 6485{ 6486 return xo_close_list_h(NULL, NULL); 6487} 6488 6489static int 6490xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 6491{ 6492 int rc = 0; 6493 int indent = 0; 6494 6495 xop = xo_default(xop); 6496 6497 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6498 const char *pre_nl = ""; 6499 6500 switch (xo_style(xop)) { 6501 case XO_STYLE_JSON: 6502 indent = 1; 6503 6504 if (!XOF_ISSET(xop, XOF_NO_TOP)) { 6505 if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) { 6506 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); 6507 XOIF_SET(xop, XOIF_TOP_EMITTED); 6508 } 6509 } 6510 6511 if (name == NULL) { 6512 xo_failure(xop, "NULL passed for list name"); 6513 name = XO_FAILURE_NAME; 6514 } 6515 6516 xo_stack_set_flags(xop); 6517 6518 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6519 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; 6520 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6521 6522 rc = xo_printf(xop, "%s%*s\"%s\": [%s", 6523 pre_nl, xo_indent(xop), "", name, ppn); 6524 break; 6525 6526 case XO_STYLE_ENCODER: 6527 rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL); 6528 break; 6529 } 6530 6531 xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST, 6532 XSF_LIST | xo_stack_flags(flags)); 6533 6534 return rc; 6535} 6536 6537static int 6538xo_do_close_leaf_list (xo_handle_t *xop, const char *name) 6539{ 6540 int rc = 0; 6541 const char *pre_nl = ""; 6542 6543 if (name == NULL) { 6544 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6545 6546 name = xsp->xs_name; 6547 if (name) { 6548 int len = strlen(name) + 1; 6549 /* We need to make a local copy; xo_depth_change will free it */ 6550 char *cp = alloca(len); 6551 memcpy(cp, name, len); 6552 name = cp; 6553 } else if (!(xsp->xs_flags & XSF_DTRT)) { 6554 xo_failure(xop, "missing name without 'dtrt' mode"); 6555 name = XO_FAILURE_NAME; 6556 } 6557 } 6558 6559 switch (xo_style(xop)) { 6560 case XO_STYLE_JSON: 6561 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6562 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6563 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6564 6565 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST); 6566 rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); 6567 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6568 break; 6569 6570 case XO_STYLE_ENCODER: 6571 rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL); 6572 /*fallthru*/ 6573 6574 default: 6575 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST); 6576 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6577 break; 6578 } 6579 6580 return rc; 6581} 6582 6583static int 6584xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 6585{ 6586 xop = xo_default(xop); 6587 6588 int rc = 0; 6589 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6590 const char *pre_nl = ""; 6591 6592 flags |= xop->xo_flags; 6593 6594 if (name == NULL) { 6595 xo_failure(xop, "NULL passed for instance name"); 6596 name = XO_FAILURE_NAME; 6597 } 6598 6599 switch (xo_style(xop)) { 6600 case XO_STYLE_XML: 6601 rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name); 6602 6603 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { 6604 rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp; 6605 xo_data_append(xop, xop->xo_attrs.xb_bufp, 6606 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); 6607 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; 6608 } 6609 6610 rc += xo_printf(xop, ">%s", ppn); 6611 break; 6612 6613 case XO_STYLE_JSON: 6614 xo_stack_set_flags(xop); 6615 6616 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) 6617 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; 6618 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6619 6620 rc = xo_printf(xop, "%s%*s{%s", 6621 pre_nl, xo_indent(xop), "", ppn); 6622 break; 6623 6624 case XO_STYLE_SDPARAMS: 6625 break; 6626 6627 case XO_STYLE_ENCODER: 6628 rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL); 6629 break; 6630 } 6631 6632 xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags)); 6633 6634 return rc; 6635} 6636 6637static int 6638xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) 6639{ 6640 return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE); 6641} 6642 6643int 6644xo_open_instance_h (xo_handle_t *xop, const char *name) 6645{ 6646 return xo_open_instance_hf(xop, 0, name); 6647} 6648 6649int 6650xo_open_instance (const char *name) 6651{ 6652 return xo_open_instance_hf(NULL, 0, name); 6653} 6654 6655int 6656xo_open_instance_hd (xo_handle_t *xop, const char *name) 6657{ 6658 return xo_open_instance_hf(xop, XOF_DTRT, name); 6659} 6660 6661int 6662xo_open_instance_d (const char *name) 6663{ 6664 return xo_open_instance_hf(NULL, XOF_DTRT, name); 6665} 6666 6667static int 6668xo_do_close_instance (xo_handle_t *xop, const char *name) 6669{ 6670 xop = xo_default(xop); 6671 6672 int rc = 0; 6673 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6674 const char *pre_nl = ""; 6675 6676 if (name == NULL) { 6677 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; 6678 6679 name = xsp->xs_name; 6680 if (name) { 6681 int len = strlen(name) + 1; 6682 /* We need to make a local copy; xo_depth_change will free it */ 6683 char *cp = alloca(len); 6684 memcpy(cp, name, len); 6685 name = cp; 6686 } else if (!(xsp->xs_flags & XSF_DTRT)) { 6687 xo_failure(xop, "missing name without 'dtrt' mode"); 6688 name = XO_FAILURE_NAME; 6689 } 6690 } 6691 6692 switch (xo_style(xop)) { 6693 case XO_STYLE_XML: 6694 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0); 6695 rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn); 6696 break; 6697 6698 case XO_STYLE_JSON: 6699 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; 6700 6701 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0); 6702 rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), ""); 6703 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; 6704 break; 6705 6706 case XO_STYLE_HTML: 6707 case XO_STYLE_TEXT: 6708 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0); 6709 break; 6710 6711 case XO_STYLE_SDPARAMS: 6712 break; 6713 6714 case XO_STYLE_ENCODER: 6715 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0); 6716 rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL); 6717 break; 6718 } 6719 6720 return rc; 6721} 6722 6723int 6724xo_close_instance_h (xo_handle_t *xop, const char *name) 6725{ 6726 return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE); 6727} 6728 6729int 6730xo_close_instance (const char *name) 6731{ 6732 return xo_close_instance_h(NULL, name); 6733} 6734 6735int 6736xo_close_instance_hd (xo_handle_t *xop) 6737{ 6738 return xo_close_instance_h(xop, NULL); 6739} 6740 6741int 6742xo_close_instance_d (void) 6743{ 6744 return xo_close_instance_h(NULL, NULL); 6745} 6746 6747static int 6748xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit) 6749{ 6750 xo_stack_t *xsp; 6751 int rc = 0; 6752 xo_xsf_flags_t flags; 6753 6754 for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) { 6755 switch (xsp->xs_state) { 6756 case XSS_INIT: 6757 /* Nothing */ 6758 rc = 0; 6759 break; 6760 6761 case XSS_OPEN_CONTAINER: 6762 rc = xo_do_close_container(xop, NULL); 6763 break; 6764 6765 case XSS_OPEN_LIST: 6766 rc = xo_do_close_list(xop, NULL); 6767 break; 6768 6769 case XSS_OPEN_INSTANCE: 6770 rc = xo_do_close_instance(xop, NULL); 6771 break; 6772 6773 case XSS_OPEN_LEAF_LIST: 6774 rc = xo_do_close_leaf_list(xop, NULL); 6775 break; 6776 6777 case XSS_MARKER: 6778 flags = xsp->xs_flags & XSF_MARKER_FLAGS; 6779 xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0); 6780 xop->xo_stack[xop->xo_depth].xs_flags |= flags; 6781 rc = 0; 6782 break; 6783 } 6784 6785 if (rc < 0) 6786 xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc); 6787 } 6788 6789 return 0; 6790} 6791 6792/* 6793 * This function is responsible for clearing out whatever is needed 6794 * to get to the desired state, if possible. 6795 */ 6796static int 6797xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state) 6798{ 6799 xo_stack_t *xsp, *limit = NULL; 6800 int rc; 6801 xo_state_t need_state = new_state; 6802 6803 if (new_state == XSS_CLOSE_CONTAINER) 6804 need_state = XSS_OPEN_CONTAINER; 6805 else if (new_state == XSS_CLOSE_LIST) 6806 need_state = XSS_OPEN_LIST; 6807 else if (new_state == XSS_CLOSE_INSTANCE) 6808 need_state = XSS_OPEN_INSTANCE; 6809 else if (new_state == XSS_CLOSE_LEAF_LIST) 6810 need_state = XSS_OPEN_LEAF_LIST; 6811 else if (new_state == XSS_MARKER) 6812 need_state = XSS_MARKER; 6813 else 6814 return 0; /* Unknown or useless new states are ignored */ 6815 6816 for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) { 6817 /* 6818 * Marker's normally stop us from going any further, unless 6819 * we are popping a marker (new_state == XSS_MARKER). 6820 */ 6821 if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) { 6822 if (name) { 6823 xo_failure(xop, "close (xo_%s) fails at marker '%s'; " 6824 "not found '%s'", 6825 xo_state_name(new_state), 6826 xsp->xs_name, name); 6827 return 0; 6828 6829 } else { 6830 limit = xsp; 6831 xo_failure(xop, "close stops at marker '%s'", xsp->xs_name); 6832 } 6833 break; 6834 } 6835 6836 if (xsp->xs_state != need_state) 6837 continue; 6838 6839 if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0) 6840 continue; 6841 6842 limit = xsp; 6843 break; 6844 } 6845 6846 if (limit == NULL) { 6847 xo_failure(xop, "xo_%s can't find match for '%s'", 6848 xo_state_name(new_state), name); 6849 return 0; 6850 } 6851 6852 rc = xo_do_close_all(xop, limit); 6853 6854 return rc; 6855} 6856 6857/* 6858 * We are in a given state and need to transition to the new state. 6859 */ 6860static int 6861xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, 6862 xo_state_t new_state) 6863{ 6864 xo_stack_t *xsp; 6865 int rc; 6866 int old_state, on_marker; 6867 6868 xop = xo_default(xop); 6869 6870 rc = 0; 6871 xsp = &xop->xo_stack[xop->xo_depth]; 6872 old_state = xsp->xs_state; 6873 on_marker = (old_state == XSS_MARKER); 6874 6875 /* If there's a marker on top of the stack, we need to find a real state */ 6876 while (old_state == XSS_MARKER) { 6877 if (xsp == xop->xo_stack) 6878 break; 6879 xsp -= 1; 6880 old_state = xsp->xs_state; 6881 } 6882 6883 /* 6884 * At this point, the list of possible states are: 6885 * XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST, 6886 * XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING 6887 */ 6888 switch (XSS_TRANSITION(old_state, new_state)) { 6889 6890 open_container: 6891 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER): 6892 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER): 6893 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER): 6894 rc = xo_do_open_container(xop, flags, name); 6895 break; 6896 6897 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER): 6898 if (on_marker) 6899 goto marker_prevents_close; 6900 rc = xo_do_close_list(xop, NULL); 6901 if (rc >= 0) 6902 goto open_container; 6903 break; 6904 6905 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER): 6906 if (on_marker) 6907 goto marker_prevents_close; 6908 rc = xo_do_close_leaf_list(xop, NULL); 6909 if (rc >= 0) 6910 goto open_container; 6911 break; 6912 6913 /*close_container:*/ 6914 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER): 6915 if (on_marker) 6916 goto marker_prevents_close; 6917 rc = xo_do_close(xop, name, new_state); 6918 break; 6919 6920 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER): 6921 /* This is an exception for "xo --close" */ 6922 rc = xo_do_close_container(xop, name); 6923 break; 6924 6925 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER): 6926 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER): 6927 if (on_marker) 6928 goto marker_prevents_close; 6929 rc = xo_do_close(xop, name, new_state); 6930 break; 6931 6932 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER): 6933 if (on_marker) 6934 goto marker_prevents_close; 6935 rc = xo_do_close_leaf_list(xop, NULL); 6936 if (rc >= 0) 6937 rc = xo_do_close(xop, name, new_state); 6938 break; 6939 6940 open_list: 6941 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST): 6942 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST): 6943 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST): 6944 rc = xo_do_open_list(xop, flags, name); 6945 break; 6946 6947 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST): 6948 if (on_marker) 6949 goto marker_prevents_close; 6950 rc = xo_do_close_list(xop, NULL); 6951 if (rc >= 0) 6952 goto open_list; 6953 break; 6954 6955 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST): 6956 if (on_marker) 6957 goto marker_prevents_close; 6958 rc = xo_do_close_leaf_list(xop, NULL); 6959 if (rc >= 0) 6960 goto open_list; 6961 break; 6962 6963 /*close_list:*/ 6964 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST): 6965 if (on_marker) 6966 goto marker_prevents_close; 6967 rc = xo_do_close(xop, name, new_state); 6968 break; 6969 6970 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST): 6971 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST): 6972 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST): 6973 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST): 6974 rc = xo_do_close(xop, name, new_state); 6975 break; 6976 6977 open_instance: 6978 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE): 6979 rc = xo_do_open_instance(xop, flags, name); 6980 break; 6981 6982 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE): 6983 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE): 6984 rc = xo_do_open_list(xop, flags, name); 6985 if (rc >= 0) 6986 goto open_instance; 6987 break; 6988 6989 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE): 6990 if (on_marker) { 6991 rc = xo_do_open_list(xop, flags, name); 6992 } else { 6993 rc = xo_do_close_instance(xop, NULL); 6994 } 6995 if (rc >= 0) 6996 goto open_instance; 6997 break; 6998 6999 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE): 7000 if (on_marker) 7001 goto marker_prevents_close; 7002 rc = xo_do_close_leaf_list(xop, NULL); 7003 if (rc >= 0) 7004 goto open_instance; 7005 break; 7006 7007 /*close_instance:*/ 7008 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE): 7009 if (on_marker) 7010 goto marker_prevents_close; 7011 rc = xo_do_close_instance(xop, name); 7012 break; 7013 7014 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE): 7015 /* This one makes no sense; ignore it */ 7016 xo_failure(xop, "xo_close_instance ignored when called from " 7017 "initial state ('%s')", name ?: "(unknown)"); 7018 break; 7019 7020 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE): 7021 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE): 7022 if (on_marker) 7023 goto marker_prevents_close; 7024 rc = xo_do_close(xop, name, new_state); 7025 break; 7026 7027 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE): 7028 if (on_marker) 7029 goto marker_prevents_close; 7030 rc = xo_do_close_leaf_list(xop, NULL); 7031 if (rc >= 0) 7032 rc = xo_do_close(xop, name, new_state); 7033 break; 7034 7035 open_leaf_list: 7036 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST): 7037 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST): 7038 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST): 7039 rc = xo_do_open_leaf_list(xop, flags, name); 7040 break; 7041 7042 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST): 7043 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST): 7044 if (on_marker) 7045 goto marker_prevents_close; 7046 rc = xo_do_close_list(xop, NULL); 7047 if (rc >= 0) 7048 goto open_leaf_list; 7049 break; 7050 7051 /*close_leaf_list:*/ 7052 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST): 7053 if (on_marker) 7054 goto marker_prevents_close; 7055 rc = xo_do_close_leaf_list(xop, name); 7056 break; 7057 7058 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST): 7059 /* Makes no sense; ignore */ 7060 xo_failure(xop, "xo_close_leaf_list ignored when called from " 7061 "initial state ('%s')", name ?: "(unknown)"); 7062 break; 7063 7064 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST): 7065 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST): 7066 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST): 7067 if (on_marker) 7068 goto marker_prevents_close; 7069 rc = xo_do_close(xop, name, new_state); 7070 break; 7071 7072 /*emit:*/ 7073 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT): 7074 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT): 7075 break; 7076 7077 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT): 7078 if (on_marker) 7079 goto marker_prevents_close; 7080 rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST); 7081 break; 7082 7083 case XSS_TRANSITION(XSS_INIT, XSS_EMIT): 7084 break; 7085 7086 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT): 7087 if (on_marker) 7088 goto marker_prevents_close; 7089 rc = xo_do_close_leaf_list(xop, NULL); 7090 break; 7091 7092 /*emit_leaf_list:*/ 7093 case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST): 7094 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST): 7095 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST): 7096 rc = xo_do_open_leaf_list(xop, flags, name); 7097 break; 7098 7099 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST): 7100 break; 7101 7102 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST): 7103 /* 7104 * We need to be backward compatible with the pre-xo_open_leaf_list 7105 * API, where both lists and leaf-lists were opened as lists. So 7106 * if we find an open list that hasn't had anything written to it, 7107 * we'll accept it. 7108 */ 7109 break; 7110 7111 default: 7112 xo_failure(xop, "unknown transition: (%u -> %u)", 7113 xsp->xs_state, new_state); 7114 } 7115 7116 return rc; 7117 7118 marker_prevents_close: 7119 xo_failure(xop, "marker '%s' prevents transition from %s to %s", 7120 xop->xo_stack[xop->xo_depth].xs_name, 7121 xo_state_name(old_state), xo_state_name(new_state)); 7122 return -1; 7123} 7124 7125int 7126xo_open_marker_h (xo_handle_t *xop, const char *name) 7127{ 7128 xop = xo_default(xop); 7129 7130 xo_depth_change(xop, name, 1, 0, XSS_MARKER, 7131 xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS); 7132 7133 return 0; 7134} 7135 7136int 7137xo_open_marker (const char *name) 7138{ 7139 return xo_open_marker_h(NULL, name); 7140} 7141 7142int 7143xo_close_marker_h (xo_handle_t *xop, const char *name) 7144{ 7145 xop = xo_default(xop); 7146 7147 return xo_do_close(xop, name, XSS_MARKER); 7148} 7149 7150int 7151xo_close_marker (const char *name) 7152{ 7153 return xo_close_marker_h(NULL, name); 7154} 7155 7156/* 7157 * Record custom output functions into the xo handle, allowing 7158 * integration with a variety of output frameworks. 7159 */ 7160void 7161xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func, 7162 xo_close_func_t close_func, xo_flush_func_t flush_func) 7163{ 7164 xop = xo_default(xop); 7165 7166 xop->xo_opaque = opaque; 7167 xop->xo_write = write_func; 7168 xop->xo_close = close_func; 7169 xop->xo_flush = flush_func; 7170} 7171 7172void 7173xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func) 7174{ 7175 xo_realloc = realloc_func; 7176 xo_free = free_func; 7177} 7178 7179int 7180xo_flush_h (xo_handle_t *xop) 7181{ 7182 static char div_close[] = "</div>"; 7183 int rc; 7184 7185 xop = xo_default(xop); 7186 7187 switch (xo_style(xop)) { 7188 case XO_STYLE_HTML: 7189 if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) { 7190 XOIF_CLEAR(xop, XOIF_DIV_OPEN); 7191 xo_data_append(xop, div_close, sizeof(div_close) - 1); 7192 7193 if (XOF_ISSET(xop, XOF_PRETTY)) 7194 xo_data_append(xop, "\n", 1); 7195 } 7196 break; 7197 7198 case XO_STYLE_ENCODER: 7199 xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL); 7200 } 7201 7202 rc = xo_write(xop); 7203 if (rc >= 0 && xop->xo_flush) 7204 if (xop->xo_flush(xop->xo_opaque) < 0) 7205 return -1; 7206 7207 return rc; 7208} 7209 7210int 7211xo_flush (void) 7212{ 7213 return xo_flush_h(NULL); 7214} 7215 7216int 7217xo_finish_h (xo_handle_t *xop) 7218{ 7219 const char *cp = ""; 7220 xop = xo_default(xop); 7221 7222 if (!XOF_ISSET(xop, XOF_NO_CLOSE)) 7223 xo_do_close_all(xop, xop->xo_stack); 7224 7225 switch (xo_style(xop)) { 7226 case XO_STYLE_JSON: 7227 if (!XOF_ISSET(xop, XOF_NO_TOP)) { 7228 if (XOIF_ISSET(xop, XOIF_TOP_EMITTED)) 7229 XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */ 7230 else 7231 cp = "{ "; 7232 xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp); 7233 } 7234 break; 7235 7236 case XO_STYLE_ENCODER: 7237 xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL); 7238 break; 7239 } 7240 7241 return xo_flush_h(xop); 7242} 7243 7244int 7245xo_finish (void) 7246{ 7247 return xo_finish_h(NULL); 7248} 7249 7250/* 7251 * xo_finish_atexit is suitable for atexit() calls, to force clear up 7252 * and finalizing output. 7253 */ 7254void 7255xo_finish_atexit (void) 7256{ 7257 (void) xo_finish_h(NULL); 7258} 7259 7260/* 7261 * Generate an error message, such as would be displayed on stderr 7262 */ 7263void 7264xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap) 7265{ 7266 xop = xo_default(xop); 7267 7268 /* 7269 * If the format string doesn't end with a newline, we pop 7270 * one on ourselves. 7271 */ 7272 int len = strlen(fmt); 7273 if (len > 0 && fmt[len - 1] != '\n') { 7274 char *newfmt = alloca(len + 2); 7275 memcpy(newfmt, fmt, len); 7276 newfmt[len] = '\n'; 7277 newfmt[len] = '\0'; 7278 fmt = newfmt; 7279 } 7280 7281 switch (xo_style(xop)) { 7282 case XO_STYLE_TEXT: 7283 vfprintf(stderr, fmt, vap); 7284 break; 7285 7286 case XO_STYLE_HTML: 7287 va_copy(xop->xo_vap, vap); 7288 7289 xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0); 7290 7291 if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) 7292 xo_line_close(xop); 7293 7294 xo_write(xop); 7295 7296 va_end(xop->xo_vap); 7297 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 7298 break; 7299 7300 case XO_STYLE_XML: 7301 case XO_STYLE_JSON: 7302 va_copy(xop->xo_vap, vap); 7303 7304 xo_open_container_h(xop, "error"); 7305 xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0); 7306 xo_close_container_h(xop, "error"); 7307 7308 va_end(xop->xo_vap); 7309 bzero(&xop->xo_vap, sizeof(xop->xo_vap)); 7310 break; 7311 7312 case XO_STYLE_SDPARAMS: 7313 case XO_STYLE_ENCODER: 7314 break; 7315 } 7316} 7317 7318void 7319xo_error_h (xo_handle_t *xop, const char *fmt, ...) 7320{ 7321 va_list vap; 7322 7323 va_start(vap, fmt); 7324 xo_error_hv(xop, fmt, vap); 7325 va_end(vap); 7326} 7327 7328/* 7329 * Generate an error message, such as would be displayed on stderr 7330 */ 7331void 7332xo_error (const char *fmt, ...) 7333{ 7334 va_list vap; 7335 7336 va_start(vap, fmt); 7337 xo_error_hv(NULL, fmt, vap); 7338 va_end(vap); 7339} 7340 7341/* 7342 * Parse any libxo-specific options from the command line, removing them 7343 * so the main() argument parsing won't see them. We return the new value 7344 * for argc or -1 for error. If an error occurred, the program should 7345 * exit. A suitable error message has already been displayed. 7346 */ 7347int 7348xo_parse_args (int argc, char **argv) 7349{ 7350 static char libxo_opt[] = "--libxo"; 7351 char *cp; 7352 int i, save; 7353 7354 /* Save our program name for xo_err and friends */ 7355 xo_program = argv[0]; 7356 cp = strrchr(xo_program, '/'); 7357 if (cp) 7358 xo_program = cp + 1; 7359 7360 for (save = i = 1; i < argc; i++) { 7361 if (argv[i] == NULL 7362 || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) { 7363 if (save != i) 7364 argv[save] = argv[i]; 7365 save += 1; 7366 continue; 7367 } 7368 7369 cp = argv[i] + sizeof(libxo_opt) - 1; 7370 if (*cp == 0) { 7371 cp = argv[++i]; 7372 if (cp == 0) { 7373 xo_warnx("missing libxo option"); 7374 return -1; 7375 } 7376 7377 if (xo_set_options(NULL, cp) < 0) 7378 return -1; 7379 } else if (*cp == ':') { 7380 if (xo_set_options(NULL, cp) < 0) 7381 return -1; 7382 7383 } else if (*cp == '=') { 7384 if (xo_set_options(NULL, ++cp) < 0) 7385 return -1; 7386 7387 } else if (*cp == '-') { 7388 cp += 1; 7389 if (strcmp(cp, "check") == 0) { 7390 exit(XO_HAS_LIBXO); 7391 7392 } else { 7393 xo_warnx("unknown libxo option: '%s'", argv[i]); 7394 return -1; 7395 } 7396 } else { 7397 xo_warnx("unknown libxo option: '%s'", argv[i]); 7398 return -1; 7399 } 7400 } 7401 7402 argv[save] = NULL; 7403 return save; 7404} 7405 7406/* 7407 * Debugging function that dumps the current stack of open libxo constructs, 7408 * suitable for calling from the debugger. 7409 */ 7410void 7411xo_dump_stack (xo_handle_t *xop) 7412{ 7413 int i; 7414 xo_stack_t *xsp; 7415 7416 xop = xo_default(xop); 7417 7418 fprintf(stderr, "Stack dump:\n"); 7419 7420 xsp = xop->xo_stack; 7421 for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) { 7422 fprintf(stderr, " [%d] %s '%s' [%x]\n", 7423 i, xo_state_name(xsp->xs_state), 7424 xsp->xs_name ?: "--", xsp->xs_flags); 7425 } 7426} 7427 7428/* 7429 * Record the program name used for error messages 7430 */ 7431void 7432xo_set_program (const char *name) 7433{ 7434 xo_program = name; 7435} 7436 7437void 7438xo_set_version_h (xo_handle_t *xop, const char *version UNUSED) 7439{ 7440 xop = xo_default(xop); 7441 7442 if (version == NULL || strchr(version, '"') != NULL) 7443 return; 7444 7445 if (!xo_style_is_encoding(xop)) 7446 return; 7447 7448 switch (xo_style(xop)) { 7449 case XO_STYLE_XML: 7450 /* For XML, we record this as an attribute for the first tag */ 7451 xo_attr_h(xop, "__version", "%s", version); 7452 break; 7453 7454 case XO_STYLE_JSON: 7455 /* 7456 * For JSON, we record the version string in our handle, and emit 7457 * it in xo_emit_top. 7458 */ 7459 xop->xo_version = xo_strndup(version, -1); 7460 break; 7461 7462 case XO_STYLE_ENCODER: 7463 xo_encoder_handle(xop, XO_OP_VERSION, NULL, version); 7464 break; 7465 } 7466} 7467 7468/* 7469 * Set the version number for the API content being carried thru 7470 * the xo handle. 7471 */ 7472void 7473xo_set_version (const char *version) 7474{ 7475 xo_set_version_h(NULL, version); 7476} 7477 7478/* 7479 * Generate a warning. Normally, this is a text message written to 7480 * standard error. If the XOF_WARN_XML flag is set, then we generate 7481 * XMLified content on standard output. 7482 */ 7483void 7484xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code, 7485 const char *fmt, va_list vap) 7486{ 7487 xop = xo_default(xop); 7488 7489 if (fmt == NULL) 7490 return; 7491 7492 xo_open_marker_h(xop, "xo_emit_warn_hcv"); 7493 xo_open_container_h(xop, as_warning ? "__warning" : "__error"); 7494 7495 if (xo_program) 7496 xo_emit("{wc:program}", xo_program); 7497 7498 if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) { 7499 va_list ap; 7500 xo_handle_t temp; 7501 7502 bzero(&temp, sizeof(temp)); 7503 temp.xo_style = XO_STYLE_TEXT; 7504 xo_buf_init(&temp.xo_data); 7505 xo_depth_check(&temp, XO_DEPTH); 7506 7507 va_copy(ap, vap); 7508 (void) xo_emit_hv(&temp, fmt, ap); 7509 va_end(ap); 7510 7511 xo_buffer_t *src = &temp.xo_data; 7512 xo_format_value(xop, "message", 7, src->xb_bufp, 7513 src->xb_curp - src->xb_bufp, NULL, 0, 0); 7514 7515 xo_free(temp.xo_stack); 7516 xo_buf_cleanup(src); 7517 } 7518 7519 (void) xo_emit_hv(xop, fmt, vap); 7520 7521 int len = strlen(fmt); 7522 if (len > 0 && fmt[len - 1] != '\n') { 7523 if (code > 0) { 7524 const char *msg = strerror(code); 7525 if (msg) 7526 xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg); 7527 } 7528 xo_emit("\n"); 7529 } 7530 7531 xo_close_marker_h(xop, "xo_emit_warn_hcv"); 7532 xo_flush_h(xop); 7533} 7534 7535void 7536xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) 7537{ 7538 va_list vap; 7539 7540 va_start(vap, fmt); 7541 xo_emit_warn_hcv(xop, 1, code, fmt, vap); 7542 va_end(vap); 7543} 7544 7545void 7546xo_emit_warn_c (int code, const char *fmt, ...) 7547{ 7548 va_list vap; 7549 7550 va_start(vap, fmt); 7551 xo_emit_warn_hcv(NULL, 1, code, fmt, vap); 7552 va_end(vap); 7553} 7554 7555void 7556xo_emit_warn (const char *fmt, ...) 7557{ 7558 int code = errno; 7559 va_list vap; 7560 7561 va_start(vap, fmt); 7562 xo_emit_warn_hcv(NULL, 1, code, fmt, vap); 7563 va_end(vap); 7564} 7565 7566void 7567xo_emit_warnx (const char *fmt, ...) 7568{ 7569 va_list vap; 7570 7571 va_start(vap, fmt); 7572 xo_emit_warn_hcv(NULL, 1, -1, fmt, vap); 7573 va_end(vap); 7574} 7575 7576void 7577xo_emit_err_v (int eval, int code, const char *fmt, va_list vap) 7578{ 7579 xo_emit_warn_hcv(NULL, 0, code, fmt, vap); 7580 xo_finish(); 7581 exit(eval); 7582} 7583 7584void 7585xo_emit_err (int eval, const char *fmt, ...) 7586{ 7587 int code = errno; 7588 va_list vap; 7589 va_start(vap, fmt); 7590 xo_emit_err_v(0, code, fmt, vap); 7591 va_end(vap); 7592 exit(eval); 7593} 7594 7595void 7596xo_emit_errx (int eval, const char *fmt, ...) 7597{ 7598 va_list vap; 7599 7600 va_start(vap, fmt); 7601 xo_emit_err_v(0, -1, fmt, vap); 7602 va_end(vap); 7603 xo_finish(); 7604 exit(eval); 7605} 7606 7607void 7608xo_emit_errc (int eval, int code, const char *fmt, ...) 7609{ 7610 va_list vap; 7611 7612 va_start(vap, fmt); 7613 xo_emit_warn_hcv(NULL, 0, code, fmt, vap); 7614 va_end(vap); 7615 xo_finish(); 7616 exit(eval); 7617} 7618 7619/* 7620 * Get the opaque private pointer for an xo handle 7621 */ 7622void * 7623xo_get_private (xo_handle_t *xop) 7624{ 7625 xop = xo_default(xop); 7626 return xop->xo_private; 7627} 7628 7629/* 7630 * Set the opaque private pointer for an xo handle. 7631 */ 7632void 7633xo_set_private (xo_handle_t *xop, void *opaque) 7634{ 7635 xop = xo_default(xop); 7636 xop->xo_private = opaque; 7637} 7638 7639/* 7640 * Get the encoder function 7641 */ 7642xo_encoder_func_t 7643xo_get_encoder (xo_handle_t *xop) 7644{ 7645 xop = xo_default(xop); 7646 return xop->xo_encoder; 7647} 7648 7649/* 7650 * Record an encoder callback function in an xo handle. 7651 */ 7652void 7653xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder) 7654{ 7655 xop = xo_default(xop); 7656 7657 xop->xo_style = XO_STYLE_ENCODER; 7658 xop->xo_encoder = encoder; 7659} 7660