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