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