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