Index: head/contrib/libxo/libxo/libxo.c =================================================================== --- head/contrib/libxo/libxo/libxo.c (revision 319708) +++ head/contrib/libxo/libxo/libxo.c (revision 319709) @@ -1,8269 +1,8268 @@ /* * Copyright (c) 2014-2015, Juniper Networks, Inc. * All rights reserved. * This SOFTWARE is licensed under the LICENSE provided in the * ../Copyright file. By downloading, installing, copying, or otherwise * using the SOFTWARE, you agree to be bound by the terms of that * LICENSE. * Phil Shafer, July 2014 * * This is the implementation of libxo, the formatting library that * generates multiple styles of output from a single code path. * Command line utilities can have their normal text output while * automation tools can see XML or JSON output, and web tools can use * HTML output that encodes the text output annotated with additional * information. Specialized encoders can be built that allow custom * encoding including binary ones like CBOR, thrift, protobufs, etc. * * Full documentation is available in ./doc/libxo.txt or online at: * http://juniper.github.io/libxo/libxo-manual.html * * For first time readers, the core bits of code to start looking at are: * - xo_do_emit() -- parse and emit a set of fields * - xo_do_emit_fields -- the central function of the library * - xo_do_format_field() -- handles formatting a single field * - xo_transiton() -- the state machine that keeps things sane * and of course the "xo_handle_t" data structure, which carries all * configuration and state. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xo_config.h" #include "xo.h" #include "xo_encoder.h" #include "xo_buf.h" /* * We ask wcwidth() to do an impossible job, really. It's supposed to * need to tell us the number of columns consumed to display a unicode * character. It returns that number without any sort of context, but * we know they are characters whose glyph differs based on placement * (end of word, middle of word, etc) and many that affect characters * previously emitted. Without content, it can't hope to tell us. * But it's the only standard tool we've got, so we use it. We would * use wcswidth() but it typically just loops through adding the results * of wcwidth() calls in an entirely unhelpful way. * * Even then, there are many poor implementations (macosx), so we have * to carry our own. We could have configure.ac test this (with * something like 'assert(wcwidth(0x200d) == 0)'), but it would have * to run a binary, which breaks cross-compilation. Hmm... I could * run this test at init time and make a warning for our dear user. * * Anyhow, it remains a best-effort sort of thing. And it's all made * more hopeless because we assume the display code doing the rendering is * playing by the same rules we are. If it display 0x200d as a square * box or a funky question mark, the output will be hosed. */ #ifdef LIBXO_WCWIDTH #include "xo_wcwidth.h" #else /* LIBXO_WCWIDTH */ #define xo_wcwidth(_x) wcwidth(_x) #endif /* LIBXO_WCWIDTH */ #ifdef HAVE_STDIO_EXT_H #include #endif /* HAVE_STDIO_EXT_H */ /* * humanize_number is a great function, unless you don't have it. So * we carry one in our pocket. */ #ifdef HAVE_HUMANIZE_NUMBER #include #define xo_humanize_number humanize_number #else /* HAVE_HUMANIZE_NUMBER */ #include "xo_humanize.h" #endif /* HAVE_HUMANIZE_NUMBER */ #ifdef HAVE_GETTEXT #include #endif /* HAVE_GETTEXT */ /* * Three styles of specifying thread-local variables are supported. * configure.ac has the brains to run each possibility through the * compiler and see what works; we are left to define the THREAD_LOCAL * macro to the right value. Most toolchains (clang, gcc) use * "before", but some (borland) use "after" and I've heard of some * (ms) that use __declspec. Any others out there? */ #define THREAD_LOCAL_before 1 #define THREAD_LOCAL_after 2 #define THREAD_LOCAL_declspec 3 #ifndef HAVE_THREAD_LOCAL #define THREAD_LOCAL(_x) _x #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before #define THREAD_LOCAL(_x) __thread _x #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after #define THREAD_LOCAL(_x) _x __thread #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec #define THREAD_LOCAL(_x) __declspec(_x) #else #error unknown thread-local setting #endif /* HAVE_THREADS_H */ const char xo_version[] = LIBXO_VERSION; const char xo_version_extra[] = LIBXO_VERSION_EXTRA; static const char xo_default_format[] = "%s"; #ifndef UNUSED #define UNUSED __attribute__ ((__unused__)) #endif /* UNUSED */ #define XO_INDENT_BY 2 /* Amount to indent when pretty printing */ #define XO_DEPTH 128 /* Default stack depth */ #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */ #define XO_FAILURE_NAME "failure" /* Flags for the stack frame */ typedef unsigned xo_xsf_flags_t; /* XSF_* flags */ #define XSF_NOT_FIRST (1<<0) /* Not the first element */ #define XSF_LIST (1<<1) /* Frame is a list */ #define XSF_INSTANCE (1<<2) /* Frame is an instance */ #define XSF_DTRT (1<<3) /* Save the name for DTRT mode */ #define XSF_CONTENT (1<<4) /* Some content has been emitted */ #define XSF_EMIT (1<<5) /* Some field has been emitted */ #define XSF_EMIT_KEY (1<<6) /* A key has been emitted */ #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */ /* These are the flags we propagate between markers and their parents */ #define XSF_MARKER_FLAGS \ (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST ) /* * A word about states: We use a finite state machine (FMS) approach * to help remove fragility from the caller's code. Instead of * requiring a specific order of calls, we'll allow the caller more * flexibility and make the library responsible for recovering from * missed steps. The goal is that the library should not be capable * of emitting invalid xml or json, but the developer shouldn't need * to know or understand all the details about these encodings. * * You can think of states as either states or events, since they * function rather like both. None of the XO_CLOSE_* events will * persist as states, since the matching stack frame will be popped. * Same is true of XSS_EMIT, which is an event that asks us to * prep for emitting output fields. */ /* Stack frame states */ typedef unsigned xo_state_t; #define XSS_INIT 0 /* Initial stack state */ #define XSS_OPEN_CONTAINER 1 #define XSS_CLOSE_CONTAINER 2 #define XSS_OPEN_LIST 3 #define XSS_CLOSE_LIST 4 #define XSS_OPEN_INSTANCE 5 #define XSS_CLOSE_INSTANCE 6 #define XSS_OPEN_LEAF_LIST 7 #define XSS_CLOSE_LEAF_LIST 8 #define XSS_DISCARDING 9 /* Discarding data until recovered */ #define XSS_MARKER 10 /* xo_open_marker's marker */ #define XSS_EMIT 11 /* xo_emit has a leaf field */ #define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */ #define XSS_FINISH 13 /* xo_finish was called */ #define XSS_MAX 13 #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new)) /* * xo_stack_t: As we open and close containers and levels, we * create a stack of frames to track them. This is needed for * XOF_WARN and XOF_XPATH. */ typedef struct xo_stack_s { xo_xsf_flags_t xs_flags; /* Flags for this frame */ xo_state_t xs_state; /* State for this stack frame */ char *xs_name; /* Name (for XPath value) */ char *xs_keys; /* XPath predicate for any key fields */ } xo_stack_t; /* * libxo supports colors and effects, for those who like them. * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_* * ("effects") are bits since we need to maintain state. */ typedef uint8_t xo_color_t; #define XO_COL_DEFAULT 0 #define XO_COL_BLACK 1 #define XO_COL_RED 2 #define XO_COL_GREEN 3 #define XO_COL_YELLOW 4 #define XO_COL_BLUE 5 #define XO_COL_MAGENTA 6 #define XO_COL_CYAN 7 #define XO_COL_WHITE 8 #define XO_NUM_COLORS 9 /* * Yes, there's no blink. We're civilized. We like users. Blink * isn't something one does to someone you like. Friends don't let * friends use blink. On friends. You know what I mean. Blink is * like, well, it's like bursting into show tunes at a funeral. It's * just not done. Not something anyone wants. And on those rare * instances where it might actually be appropriate, it's still wrong, * since it's likely done by the wrong person for the wrong reason. * Just like blink. And if I implemented blink, I'd be like a funeral * director who adds "Would you like us to burst into show tunes?" on * the list of questions asked while making funeral arrangements. * It's formalizing wrongness in the wrong way. And we're just too * civilized to do that. Hhhmph! */ #define XO_EFF_RESET (1<<0) #define XO_EFF_NORMAL (1<<1) #define XO_EFF_BOLD (1<<2) #define XO_EFF_UNDERLINE (1<<3) #define XO_EFF_INVERSE (1<<4) #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */ typedef uint8_t xo_effect_t; -typedef uint8_t xo_color_t; typedef struct xo_colors_s { xo_effect_t xoc_effects; /* Current effect set */ xo_color_t xoc_col_fg; /* Foreground color */ xo_color_t xoc_col_bg; /* Background color */ } xo_colors_t; /* * xo_handle_t: this is the principle data structure for libxo. * It's used as a store for state, options, content, and all manor * of other information. */ struct xo_handle_s { xo_xof_flags_t xo_flags; /* Flags (XOF_*) from the user*/ xo_xof_flags_t xo_iflags; /* Internal flags (XOIF_*) */ xo_style_t xo_style; /* XO_STYLE_* value */ unsigned short xo_indent; /* Indent level (if pretty) */ unsigned short xo_indent_by; /* Indent amount (tab stop) */ xo_write_func_t xo_write; /* Write callback */ xo_close_func_t xo_close; /* Close callback */ xo_flush_func_t xo_flush; /* Flush callback */ xo_formatter_t xo_formatter; /* Custom formating function */ xo_checkpointer_t xo_checkpointer; /* Custom formating support function */ void *xo_opaque; /* Opaque data for write function */ xo_buffer_t xo_data; /* Output data */ xo_buffer_t xo_fmt; /* Work area for building format strings */ xo_buffer_t xo_attrs; /* Work area for building XML attributes */ xo_buffer_t xo_predicate; /* Work area for building XPath predicates */ xo_stack_t *xo_stack; /* Stack pointer */ int xo_depth; /* Depth of stack */ int xo_stack_size; /* Size of the stack */ xo_info_t *xo_info; /* Info fields for all elements */ int xo_info_count; /* Number of info entries */ va_list xo_vap; /* Variable arguments (stdargs) */ char *xo_leading_xpath; /* A leading XPath expression */ mbstate_t xo_mbstate; /* Multi-byte character conversion state */ ssize_t xo_anchor_offset; /* Start of anchored text */ ssize_t xo_anchor_columns; /* Number of columns since the start anchor */ ssize_t xo_anchor_min_width; /* Desired width of anchored text */ ssize_t xo_units_offset; /* Start of units insertion point */ ssize_t xo_columns; /* Columns emitted during this xo_emit call */ #ifndef LIBXO_TEXT_ONLY uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */ uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */ #endif /* LIBXO_TEXT_ONLY */ xo_colors_t xo_colors; /* Current color and effect values */ xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */ char *xo_version; /* Version string */ int xo_errno; /* Saved errno for "%m" */ char *xo_gt_domain; /* Gettext domain, suitable for dgettext(3) */ xo_encoder_func_t xo_encoder; /* Encoding function */ void *xo_private; /* Private data for external encoders */ }; /* Flag operations */ #define XOF_BIT_ISSET(_flag, _bit) (((_flag) & (_bit)) ? 1 : 0) #define XOF_BIT_SET(_flag, _bit) do { (_flag) |= (_bit); } while (0) #define XOF_BIT_CLEAR(_flag, _bit) do { (_flag) &= ~(_bit); } while (0) #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit) #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit) #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit) #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit) #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit) #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit) /* Internal flags */ #define XOIF_REORDER XOF_BIT(0) /* Reordering fields; record field info */ #define XOIF_DIV_OPEN XOF_BIT(1) /* A
is open */ #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */ #define XOIF_ANCHOR XOF_BIT(3) /* An anchor is in place */ #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */ #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */ /* Flags for formatting functions */ typedef unsigned long xo_xff_flags_t; #define XFF_COLON (1<<0) /* Append a ":" */ #define XFF_COMMA (1<<1) /* Append a "," iff there's more output */ #define XFF_WS (1<<2) /* Append a blank */ #define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding styles (XML, JSON) */ #define XFF_QUOTE (1<<4) /* Force quotes */ #define XFF_NOQUOTE (1<<5) /* Force no quotes */ #define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display styles (text, html) */ #define XFF_KEY (1<<7) /* Field is a key (for XPath) */ #define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */ #define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */ #define XFF_BLANK_LINE (1<<10) /* Emit a blank line */ #define XFF_NO_OUTPUT (1<<11) /* Do not make any output */ #define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */ #define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */ #define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */ #define XFF_HUMANIZE (1<<15) /* Humanize the value (for display styles) */ #define XFF_HN_SPACE (1<<16) /* Humanize: put space before suffix */ #define XFF_HN_DECIMAL (1<<17) /* Humanize: add one decimal place if <10 */ #define XFF_HN_1000 (1<<18) /* Humanize: use 1000, not 1024 */ #define XFF_GT_FIELD (1<<19) /* Call gettext() on a field */ #define XFF_GT_PLURAL (1<<20) /* Call dngettext to find plural form */ #define XFF_ARGUMENT (1<<21) /* Content provided via argument */ /* Flags to turn off when we don't want i18n processing */ #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL) /* * Normal printf has width and precision, which for strings operate as * min and max number of columns. But this depends on the idea that * one byte means one column, which UTF-8 and multi-byte characters * pitches on its ear. It may take 40 bytes of data to populate 14 * columns, but we can't go off looking at 40 bytes of data without the * caller's permission for fear/knowledge that we'll generate core files. * * So we make three values, distinguishing between "max column" and * "number of bytes that we will inspect inspect safely" We call the * later "size", and make the format "%[[].[[].]]s". * * Under the "first do no harm" theory, we default "max" to "size". * This is a reasonable assumption for folks that don't grok the * MBS/WCS/UTF-8 world, and while it will be annoying, it will never * be evil. * * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14 * columns of output, but will never look at more than 14 bytes of the * input buffer. This is mostly compatible with printf and caller's * expectations. * * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however * many bytes (or until a NUL is seen) are needed to fill 14 columns * of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up * to xx bytes (or until a NUL is seen) in order to fill 14 columns * of output. * * It's fairly amazing how a good idea (handle all languages of the * world) blows such a big hole in the bottom of the fairly weak boat * that is C string handling. The simplicity and completenesss are * sunk in ways we haven't even begun to understand. */ #define XF_WIDTH_MIN 0 /* Minimal width */ #define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */ #define XF_WIDTH_MAX 2 /* Maximum width */ #define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */ /* Input and output string encodings */ #define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */ #define XF_ENC_UTF8 2 /* UTF-8 */ #define XF_ENC_LOCALE 3 /* Current locale */ /* * A place to parse printf-style format flags for each field */ typedef struct xo_format_s { unsigned char xf_fc; /* Format character */ unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */ unsigned char xf_skip; /* Skip this field */ unsigned char xf_lflag; /* 'l' (long) */ unsigned char xf_hflag;; /* 'h' (half) */ unsigned char xf_jflag; /* 'j' (intmax_t) */ unsigned char xf_tflag; /* 't' (ptrdiff_t) */ unsigned char xf_zflag; /* 'z' (size_t) */ unsigned char xf_qflag; /* 'q' (quad_t) */ unsigned char xf_seen_minus; /* Seen a minus */ int xf_leading_zero; /* Seen a leading zero (zero fill) */ unsigned xf_dots; /* Seen one or more '.'s */ int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */ unsigned xf_stars; /* Seen one or more '*'s */ unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */ } xo_format_t; /* * This structure represents the parsed field information, suitable for * processing by xo_do_emit and anything else that needs to parse fields. * Note that all pointers point to the main format string. * * XXX This is a first step toward compilable or cachable format * strings. We can also cache the results of dgettext when no format * is used, assuming the 'p' modifier has _not_ been set. */ typedef struct xo_field_info_s { xo_xff_flags_t xfi_flags; /* Flags for this field */ unsigned xfi_ftype; /* Field type, as character (e.g. 'V') */ const char *xfi_start; /* Start of field in the format string */ const char *xfi_content; /* Field's content */ const char *xfi_format; /* Field's Format */ const char *xfi_encoding; /* Field's encoding format */ const char *xfi_next; /* Next character in format string */ ssize_t xfi_len; /* Length of field */ ssize_t xfi_clen; /* Content length */ ssize_t xfi_flen; /* Format length */ ssize_t xfi_elen; /* Encoding length */ unsigned xfi_fnum; /* Field number (if used; 0 otherwise) */ unsigned xfi_renum; /* Reordered number (0 == no renumbering) */ } xo_field_info_t; /* * We keep a 'default' handle to allow callers to avoid having to * allocate one. Passing NULL to any of our functions will use * this default handle. Most functions have a variant that doesn't * require a handle at all, since most output is to stdout, which * the default handle handles handily. */ static THREAD_LOCAL(xo_handle_t) xo_default_handle; static THREAD_LOCAL(int) xo_default_inited; static int xo_locale_inited; static const char *xo_program; /* * To allow libxo to be used in diverse environment, we allow the * caller to give callbacks for memory allocation. */ xo_realloc_func_t xo_realloc = realloc; xo_free_func_t xo_free = free; /* Forward declarations */ static void xo_failure (xo_handle_t *xop, const char *fmt, ...); static ssize_t xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, xo_state_t new_state); static int xo_set_options_simple (xo_handle_t *xop, const char *input); static int xo_color_find (const char *str); static void xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, const char *name, ssize_t nlen, const char *value, ssize_t vlen, const char *encoding, ssize_t elen); static void xo_anchor_clear (xo_handle_t *xop); /* * xo_style is used to retrieve the current style. When we're built * for "text only" mode, we use this function to drive the removal * of most of the code in libxo. We return a constant and the compiler * happily removes the non-text code that is not longer executed. This * trims our code nicely without needing to trampel perfectly readable * code with ifdefs. */ static inline xo_style_t xo_style (xo_handle_t *xop UNUSED) { #ifdef LIBXO_TEXT_ONLY return XO_STYLE_TEXT; #else /* LIBXO_TEXT_ONLY */ return xop->xo_style; #endif /* LIBXO_TEXT_ONLY */ } /* * Callback to write data to a FILE pointer */ static xo_ssize_t xo_write_to_file (void *opaque, const char *data) { FILE *fp = (FILE *) opaque; return fprintf(fp, "%s", data); } /* * Callback to close a file */ static void xo_close_file (void *opaque) { FILE *fp = (FILE *) opaque; fclose(fp); } /* * Callback to flush a FILE pointer */ static int xo_flush_file (void *opaque) { FILE *fp = (FILE *) opaque; return fflush(fp); } /* * Use a rotating stock of buffers to make a printable string */ #define XO_NUMBUFS 8 #define XO_SMBUFSZ 128 static const char * xo_printable (const char *str) { static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ]; static THREAD_LOCAL(int) bufnum = 0; if (str == NULL) return ""; if (++bufnum == XO_NUMBUFS) bufnum = 0; char *res = bufset[bufnum], *cp, *ep; for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) { if (*str == '\n') { *cp++ = '\\'; *cp = 'n'; } else if (*str == '\r') { *cp++ = '\\'; *cp = 'r'; } else if (*str == '\"') { *cp++ = '\\'; *cp = '"'; } else *cp = *str; } *cp = '\0'; return res; } static int xo_depth_check (xo_handle_t *xop, int depth) { xo_stack_t *xsp; if (depth >= xop->xo_stack_size) { depth += XO_DEPTH; /* Extra room */ xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth); if (xsp == NULL) { xo_failure(xop, "xo_depth_check: out of memory (%d)", depth); return -1; } int count = depth - xop->xo_stack_size; bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp)); xop->xo_stack_size = depth; xop->xo_stack = xsp; } return 0; } void xo_no_setlocale (void) { xo_locale_inited = 1; /* Skip initialization */ } /* * We need to decide if stdout is line buffered (_IOLBF). Lacking a * standard way to decide this (e.g. getlinebuf()), we have configure * look to find __flbf, which glibc supported. If not, we'll rely on * isatty, with the assumption that terminals are the only thing * that's line buffered. We _could_ test for "steam._flags & _IOLBF", * which is all __flbf does, but that's even tackier. Like a * bedazzled Elvis outfit on an ugly lap dog sort of tacky. Not * something we're willing to do. */ static int xo_is_line_buffered (FILE *stream) { #if HAVE___FLBF if (__flbf(stream)) return 1; #else /* HAVE___FLBF */ if (isatty(fileno(stream))) return 1; #endif /* HAVE___FLBF */ return 0; } /* * Initialize an xo_handle_t, using both static defaults and * the global settings from the LIBXO_OPTIONS environment * variable. */ static void xo_init_handle (xo_handle_t *xop) { xop->xo_opaque = stdout; xop->xo_write = xo_write_to_file; xop->xo_flush = xo_flush_file; if (xo_is_line_buffered(stdout)) XOF_SET(xop, XOF_FLUSH_LINE); /* * We need to initialize the locale, which isn't really pretty. * Libraries should depend on their caller to set up the * environment. But we really can't count on the caller to do * this, because well, they won't. Trust me. */ if (!xo_locale_inited) { xo_locale_inited = 1; /* Only do this once */ const char *cp = getenv("LC_CTYPE"); if (cp == NULL) cp = getenv("LANG"); if (cp == NULL) cp = getenv("LC_ALL"); if (cp == NULL) cp = "C"; /* Default for C programs */ (void) setlocale(LC_CTYPE, cp); } /* * Initialize only the xo_buffers we know we'll need; the others * can be allocated as needed. */ xo_buf_init(&xop->xo_data); xo_buf_init(&xop->xo_fmt); if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS)) return; XOIF_SET(xop, XOIF_INIT_IN_PROGRESS); xop->xo_indent_by = XO_INDENT_BY; xo_depth_check(xop, XO_DEPTH); XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS); } /* * Initialize the default handle. */ static void xo_default_init (void) { xo_handle_t *xop = &xo_default_handle; xo_init_handle(xop); #if !defined(NO_LIBXO_OPTIONS) if (!XOF_ISSET(xop, XOF_NO_ENV)) { char *env = getenv("LIBXO_OPTIONS"); if (env) xo_set_options_simple(xop, env); } #endif /* NO_LIBXO_OPTIONS */ xo_default_inited = 1; } /* * Cheap convenience function to return either the argument, or * the internal handle, after it has been initialized. The usage * is: * xop = xo_default(xop); */ static xo_handle_t * xo_default (xo_handle_t *xop) { if (xop == NULL) { if (xo_default_inited == 0) xo_default_init(); xop = &xo_default_handle; } return xop; } /* * Return the number of spaces we should be indenting. If * we are pretty-printing, this is indent * indent_by. */ static int xo_indent (xo_handle_t *xop) { int rc = 0; xop = xo_default(xop); if (XOF_ISSET(xop, XOF_PRETTY)) { rc = xop->xo_indent * xop->xo_indent_by; if (XOIF_ISSET(xop, XOIF_TOP_EMITTED)) rc += xop->xo_indent_by; } return (rc > 0) ? rc : 0; } static void xo_buf_indent (xo_handle_t *xop, int indent) { xo_buffer_t *xbp = &xop->xo_data; if (indent <= 0) indent = xo_indent(xop); if (!xo_buf_has_room(xbp, indent)) return; memset(xbp->xb_curp, ' ', indent); xbp->xb_curp += indent; } static char xo_xml_amp[] = "&"; static char xo_xml_lt[] = "<"; static char xo_xml_gt[] = ">"; static char xo_xml_quot[] = """; static ssize_t xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags) { ssize_t slen; ssize_t delta = 0; char *cp, *ep, *ip; const char *sp; int attr = XOF_BIT_ISSET(flags, XFF_ATTR); for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */ if (*cp == '<') delta += sizeof(xo_xml_lt) - 2; else if (*cp == '>') delta += sizeof(xo_xml_gt) - 2; else if (*cp == '&') delta += sizeof(xo_xml_amp) - 2; else if (attr && *cp == '"') delta += sizeof(xo_xml_quot) - 2; } if (delta == 0) /* Nothing to escape; bail */ return len; if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ return 0; ep = xbp->xb_curp; cp = ep + len; ip = cp + delta; do { cp -= 1; ip -= 1; if (*cp == '<') sp = xo_xml_lt; else if (*cp == '>') sp = xo_xml_gt; else if (*cp == '&') sp = xo_xml_amp; else if (attr && *cp == '"') sp = xo_xml_quot; else { *ip = *cp; continue; } slen = strlen(sp); ip -= slen - 1; memcpy(ip, sp, slen); } while (cp > ep && cp != ip); return len + delta; } static ssize_t xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED) { ssize_t delta = 0; char *cp, *ep, *ip; for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { if (*cp == '\\' || *cp == '"') delta += 1; else if (*cp == '\n' || *cp == '\r') delta += 1; } if (delta == 0) /* Nothing to escape; bail */ return len; if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ return 0; ep = xbp->xb_curp; cp = ep + len; ip = cp + delta; do { cp -= 1; ip -= 1; if (*cp == '\\' || *cp == '"') { *ip-- = *cp; *ip = '\\'; } else if (*cp == '\n') { *ip-- = 'n'; *ip = '\\'; } else if (*cp == '\r') { *ip-- = 'r'; *ip = '\\'; } else { *ip = *cp; } } while (cp > ep && cp != ip); return len + delta; } /* * PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and * ; ']' MUST be escaped. */ static ssize_t xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED) { ssize_t delta = 0; char *cp, *ep, *ip; for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { if (*cp == '\\' || *cp == '"' || *cp == ']') delta += 1; } if (delta == 0) /* Nothing to escape; bail */ return len; if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */ return 0; ep = xbp->xb_curp; cp = ep + len; ip = cp + delta; do { cp -= 1; ip -= 1; if (*cp == '\\' || *cp == '"' || *cp == ']') { *ip-- = *cp; *ip = '\\'; } else { *ip = *cp; } } while (cp > ep && cp != ip); return len + delta; } static void xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, const char *str, ssize_t len, xo_xff_flags_t flags) { if (!xo_buf_has_room(xbp, len)) return; memcpy(xbp->xb_curp, str, len); switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_HTML: len = xo_escape_xml(xbp, len, flags); break; case XO_STYLE_JSON: len = xo_escape_json(xbp, len, flags); break; case XO_STYLE_SDPARAMS: len = xo_escape_sdparams(xbp, len, flags); break; } xbp->xb_curp += len; } /* * Write the current contents of the data buffer using the handle's * xo_write function. */ static ssize_t xo_write (xo_handle_t *xop) { ssize_t rc = 0; xo_buffer_t *xbp = &xop->xo_data; if (xbp->xb_curp != xbp->xb_bufp) { xo_buf_append(xbp, "", 1); /* Append ending NUL */ xo_anchor_clear(xop); if (xop->xo_write) rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp); xbp->xb_curp = xbp->xb_bufp; } /* Turn off the flags that don't survive across writes */ XOIF_CLEAR(xop, XOIF_UNITS_PENDING); return rc; } /* * Format arguments into our buffer. If a custom formatter has been set, * we use that to do the work; otherwise we vsnprintf(). */ static ssize_t xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) { va_list va_local; ssize_t rc; ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); va_copy(va_local, vap); if (xop->xo_formatter) rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); else rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) { va_end(va_local); return -1; } /* * After we call vsnprintf(), the stage of vap is not defined. * We need to copy it before we pass. Then we have to do our * own logic below to move it along. This is because the * implementation can have va_list be a pointer (bsd) or a * structure (macosx) or anything in between. */ va_end(va_local); /* Reset vap to the start */ va_copy(va_local, vap); left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); if (xop->xo_formatter) rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); else rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); } va_end(va_local); return rc; } /* * Print some data through the handle. */ static ssize_t xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap) { xo_buffer_t *xbp = &xop->xo_data; ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); ssize_t rc; va_list va_local; va_copy(va_local, vap); rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) { va_end(va_local); return -1; } va_end(va_local); /* Reset vap to the start */ va_copy(va_local, vap); left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); } va_end(va_local); if (rc > 0) xbp->xb_curp += rc; return rc; } static ssize_t xo_printf (xo_handle_t *xop, const char *fmt, ...) { ssize_t rc; va_list vap; va_start(vap, fmt); rc = xo_printf_v(xop, fmt, vap); va_end(vap); return rc; } /* * These next few function are make The Essential UTF-8 Ginsu Knife. * Identify an input and output character, and convert it. */ static uint8_t xo_utf8_data_bits[5] = { 0, 0x7f, 0x1f, 0x0f, 0x07 }; static uint8_t xo_utf8_len_bits[5] = { 0, 0x00, 0xc0, 0xe0, 0xf0 }; /* * If the byte has a high-bit set, it's UTF-8, not ASCII. */ static int xo_is_utf8 (char ch) { return (ch & 0x80); } /* * Look at the high bits of the first byte to determine the length * of the UTF-8 character. */ static inline ssize_t xo_utf8_to_wc_len (const char *buf) { uint8_t bval = (uint8_t) *buf; ssize_t len; if ((bval & 0x80) == 0x0) len = 1; else if ((bval & 0xe0) == 0xc0) len = 2; else if ((bval & 0xf0) == 0xe0) len = 3; else if ((bval & 0xf8) == 0xf0) len = 4; else len = -1; return len; } static ssize_t xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz) { unsigned b = (unsigned char) *buf; ssize_t len, i; len = xo_utf8_to_wc_len(buf); if (len < 0) { xo_failure(xop, "invalid UTF-8 data: %02hhx", b); return -1; } if (len > bufsiz) { xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)", b, len, bufsiz); return -1; } for (i = 2; i < len; i++) { b = (unsigned char ) buf[i]; if ((b & 0xc0) != 0x80) { xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b); return -1; } } return len; } /* * Build a wide character from the input buffer; the number of * bits we pull off the first character is dependent on the length, * but we put 6 bits off all other bytes. */ static inline wchar_t xo_utf8_char (const char *buf, ssize_t len) { /* Most common case: singleton byte */ if (len == 1) return (unsigned char) buf[0]; ssize_t i; wchar_t wc; const unsigned char *cp = (const unsigned char *) buf; wc = *cp & xo_utf8_data_bits[len]; for (i = 1; i < len; i++) { wc <<= 6; /* Low six bits have data */ wc |= cp[i] & 0x3f; if ((cp[i] & 0xc0) != 0x80) return (wchar_t) -1; } return wc; } /* * Determine the number of bytes needed to encode a wide character. */ static ssize_t xo_utf8_emit_len (wchar_t wc) { ssize_t len; if ((wc & ((1 << 7) - 1)) == wc) /* Simple case */ len = 1; else if ((wc & ((1 << 11) - 1)) == wc) len = 2; else if ((wc & ((1 << 16) - 1)) == wc) len = 3; else if ((wc & ((1 << 21) - 1)) == wc) len = 4; else len = -1; /* Invalid */ return len; } /* * Emit a single wide character into the given buffer */ static void xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc) { ssize_t i; if (len == 1) { /* Simple case */ buf[0] = wc & 0x7f; return; } /* Start with the low bits and insert them, six bits as a time */ for (i = len - 1; i >= 0; i--) { buf[i] = 0x80 | (wc & 0x3f); wc >>= 6; /* Drop the low six bits */ } /* Finish off the first byte with the length bits */ buf[0] &= xo_utf8_data_bits[len]; /* Clear out the length bits */ buf[0] |= xo_utf8_len_bits[len]; /* Drop in new length bits */ } /* * Append a single UTF-8 character to a buffer, converting it to locale * encoding. Returns the number of columns consumed by that character, * as best we can determine it. */ static ssize_t xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp, const char *ibuf, ssize_t ilen) { wchar_t wc; ssize_t len; /* * Build our wide character from the input buffer; the number of * bits we pull off the first character is dependent on the length, * but we put 6 bits off all other bytes. */ wc = xo_utf8_char(ibuf, ilen); if (wc == (wchar_t) -1) { xo_failure(xop, "invalid UTF-8 byte sequence"); return 0; } if (XOF_ISSET(xop, XOF_NO_LOCALE)) { if (!xo_buf_has_room(xbp, ilen)) return 0; memcpy(xbp->xb_curp, ibuf, ilen); xbp->xb_curp += ilen; } else { if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) return 0; bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate)); len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); if (len <= 0) { xo_failure(xop, "could not convert wide char: %lx", (unsigned long) wc); return 0; } xbp->xb_curp += len; } return xo_wcwidth(wc); } /* * Append a UTF-8 string to a buffer, converting it into locale encoding */ static void xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp, const char *cp, ssize_t len) { const char *sp = cp, *ep = cp + len; ssize_t save_off = xbp->xb_bufp - xbp->xb_curp; ssize_t slen; int cols = 0; for ( ; cp < ep; cp++) { if (!xo_is_utf8(*cp)) { cols += 1; continue; } /* * We're looking at a non-ascii UTF-8 character. * First we copy the previous data. * Then we need find the length and validate it. * Then we turn it into a wide string. * Then we turn it into a localized string. * Then we repeat. Isn't i18n fun? */ if (sp != cp) xo_buf_append(xbp, sp, cp - sp); /* Append previous data */ slen = xo_buf_utf8_len(xop, cp, ep - cp); if (slen <= 0) { /* Bad data; back it all out */ xbp->xb_curp = xbp->xb_bufp + save_off; return; } cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen); /* Next time through, we'll start at the next character */ cp += slen - 1; sp = cp + 1; } /* Update column values */ if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += cols; /* Before we fall into the basic logic below, we need reset len */ len = ep - sp; if (len != 0) /* Append trailing data */ xo_buf_append(xbp, sp, len); } /* * Append the given string to the given buffer, without escaping or * character set conversion. This is the straight copy to the data * buffer with no fanciness. */ static void xo_data_append (xo_handle_t *xop, const char *str, ssize_t len) { xo_buf_append(&xop->xo_data, str, len); } /* * Append the given string to the given buffer */ static void xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len) { xo_buf_escape(xop, &xop->xo_data, str, len, 0); } #ifdef LIBXO_NO_RETAIN /* * Empty implementations of the retain logic */ void xo_retain_clear_all (void) { return; } void xo_retain_clear (const char *fmt UNUSED) { return; } static void xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED, unsigned num_fields UNUSED) { return; } static int xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED, unsigned *nump UNUSED) { return -1; } #else /* !LIBXO_NO_RETAIN */ /* * Retain: We retain parsed field definitions to enhance performance, * especially inside loops. We depend on the caller treating the format * strings as immutable, so that we can retain pointers into them. We * hold the pointers in a hash table, so allow quick access. Retained * information is retained until xo_retain_clear is called. */ /* * xo_retain_entry_t holds information about one retained set of * parsed fields. */ typedef struct xo_retain_entry_s { struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */ unsigned long xre_hits; /* Number of times we've hit */ const char *xre_format; /* Pointer to format string */ unsigned xre_num_fields; /* Number of fields saved */ xo_field_info_t *xre_fields; /* Pointer to fields */ } xo_retain_entry_t; /* * xo_retain_t holds a complete set of parsed fields as a hash table. */ #ifndef XO_RETAIN_SIZE #define XO_RETAIN_SIZE 6 #endif /* XO_RETAIN_SIZE */ #define RETAIN_HASH_SIZE (1<> 4) & (((1 << 24) - 1))); val = (val ^ 61) ^ (val >> 16); val = val + (val << 3); val = val ^ (val >> 4); val = val * 0x3a8f05c5; /* My large prime number */ val = val ^ (val >> 15); val &= RETAIN_HASH_SIZE - 1; return val; } /* * Walk all buckets, clearing all retained entries */ void xo_retain_clear_all (void) { int i; xo_retain_entry_t *xrep, *next; for (i = 0; i < RETAIN_HASH_SIZE; i++) { for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) { next = xrep->xre_next; xo_free(xrep); } xo_retain.xr_bucket[i] = NULL; } xo_retain_count = 0; } /* * Walk all buckets, clearing all retained entries */ void xo_retain_clear (const char *fmt) { xo_retain_entry_t **xrepp; unsigned hash = xo_retain_hash(fmt); for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp; xrepp = &(*xrepp)->xre_next) { if ((*xrepp)->xre_format == fmt) { *xrepp = (*xrepp)->xre_next; xo_retain_count -= 1; return; } } } /* * Search the hash for an entry matching 'fmt'; return it's fields. */ static int xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump) { if (xo_retain_count == 0) return -1; unsigned hash = xo_retain_hash(fmt); xo_retain_entry_t *xrep; for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL; xrep = xrep->xre_next) { if (xrep->xre_format == fmt) { *valp = xrep->xre_fields; *nump = xrep->xre_num_fields; xrep->xre_hits += 1; return 0; } } return -1; } static void xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields) { unsigned hash = xo_retain_hash(fmt); xo_retain_entry_t *xrep; ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields); xo_field_info_t *xfip; xrep = xo_realloc(NULL, sz); if (xrep == NULL) return; xfip = (xo_field_info_t *) &xrep[1]; memcpy(xfip, fields, num_fields * sizeof(*fields)); bzero(xrep, sizeof(*xrep)); xrep->xre_format = fmt; xrep->xre_fields = xfip; xrep->xre_num_fields = num_fields; /* Record the field info in the retain bucket */ xrep->xre_next = xo_retain.xr_bucket[hash]; xo_retain.xr_bucket[hash] = xrep; xo_retain_count += 1; } #endif /* !LIBXO_NO_RETAIN */ /* * Generate a warning. Normally, this is a text message written to * standard error. If the XOF_WARN_XML flag is set, then we generate * XMLified content on standard output. */ static void xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, const char *fmt, va_list vap) { xop = xo_default(xop); if (check_warn && !XOF_ISSET(xop, XOF_WARN)) return; if (fmt == NULL) return; ssize_t len = strlen(fmt); ssize_t plen = xo_program ? strlen(xo_program) : 0; char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */ if (plen) { memcpy(newfmt, xo_program, plen); newfmt[plen++] = ':'; newfmt[plen++] = ' '; } memcpy(newfmt + plen, fmt, len); newfmt[len + plen] = '\0'; if (XOF_ISSET(xop, XOF_WARN_XML)) { static char err_open[] = ""; static char err_close[] = ""; static char msg_open[] = ""; static char msg_close[] = ""; xo_buffer_t *xbp = &xop->xo_data; xo_buf_append(xbp, err_open, sizeof(err_open) - 1); xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); va_list va_local; va_copy(va_local, vap); ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap); if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) { va_end(va_local); return; } va_end(vap); /* Reset vap to the start */ va_copy(vap, va_local); left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); rc = vsnprintf(xbp->xb_curp, left, fmt, vap); } va_end(va_local); rc = xo_escape_xml(xbp, rc, 1); xbp->xb_curp += rc; xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); xo_buf_append(xbp, err_close, sizeof(err_close) - 1); if (code >= 0) { const char *msg = strerror(code); if (msg) { xo_buf_append(xbp, ": ", 2); xo_buf_append(xbp, msg, strlen(msg)); } } xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ (void) xo_write(xop); } else { vfprintf(stderr, newfmt, vap); if (code >= 0) { const char *msg = strerror(code); if (msg) fprintf(stderr, ": %s", msg); } fprintf(stderr, "\n"); } } void xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_warn_hcv(xop, code, 0, fmt, vap); va_end(vap); } void xo_warn_c (int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, code, 0, fmt, vap); va_end(vap); } void xo_warn (const char *fmt, ...) { int code = errno; va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, code, 0, fmt, vap); va_end(vap); } void xo_warnx (const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, -1, 0, fmt, vap); va_end(vap); } void xo_err (int eval, const char *fmt, ...) { int code = errno; va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, code, 0, fmt, vap); va_end(vap); xo_finish(); exit(eval); } void xo_errx (int eval, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, -1, 0, fmt, vap); va_end(vap); xo_finish(); exit(eval); } void xo_errc (int eval, int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_warn_hcv(NULL, code, 0, fmt, vap); va_end(vap); xo_finish(); exit(eval); } /* * Generate a warning. Normally, this is a text message written to * standard error. If the XOF_WARN_XML flag is set, then we generate * XMLified content on standard output. */ void xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) { static char msg_open[] = ""; static char msg_close[] = ""; xo_buffer_t *xbp; ssize_t rc; va_list va_local; xop = xo_default(xop); if (fmt == NULL || *fmt == '\0') return; int need_nl = (fmt[strlen(fmt) - 1] != '\n'); switch (xo_style(xop)) { case XO_STYLE_XML: xbp = &xop->xo_data; if (XOF_ISSET(xop, XOF_PRETTY)) xo_buf_indent(xop, xop->xo_indent_by); xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1); va_copy(va_local, vap); ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); rc = vsnprintf(xbp->xb_curp, left, fmt, vap); if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) { va_end(va_local); return; } va_end(vap); /* Reset vap to the start */ va_copy(vap, va_local); left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); rc = vsnprintf(xbp->xb_curp, left, fmt, vap); } va_end(va_local); rc = xo_escape_xml(xbp, rc, 0); xbp->xb_curp += rc; if (need_nl && code > 0) { const char *msg = strerror(code); if (msg) { xo_buf_append(xbp, ": ", 2); xo_buf_append(xbp, msg, strlen(msg)); } } if (need_nl) xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */ (void) xo_write(xop); break; case XO_STYLE_HTML: { char buf[BUFSIZ], *bp = buf, *cp; ssize_t bufsiz = sizeof(buf); ssize_t rc2; va_copy(va_local, vap); rc = vsnprintf(bp, bufsiz, fmt, va_local); if (rc > bufsiz) { bufsiz = rc + BUFSIZ; bp = alloca(bufsiz); va_end(va_local); va_copy(va_local, vap); rc = vsnprintf(bp, bufsiz, fmt, va_local); } va_end(va_local); cp = bp + rc; if (need_nl) { rc2 = snprintf(cp, bufsiz - rc, "%s%s\n", (code > 0) ? ": " : "", (code > 0) ? strerror(code) : ""); if (rc2 > 0) rc += rc2; } xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0); } break; case XO_STYLE_JSON: case XO_STYLE_SDPARAMS: case XO_STYLE_ENCODER: /* No means of representing messages */ return; case XO_STYLE_TEXT: rc = xo_printf_v(xop, fmt, vap); /* * XXX need to handle UTF-8 widths */ if (rc > 0) { if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += rc; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += rc; } if (need_nl && code > 0) { const char *msg = strerror(code); if (msg) { xo_printf(xop, ": %s", msg); } } if (need_nl) xo_printf(xop, "\n"); break; } switch (xo_style(xop)) { case XO_STYLE_HTML: if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) { static char div_close[] = "
"; XOIF_CLEAR(xop, XOIF_DIV_OPEN); xo_data_append(xop, div_close, sizeof(div_close) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); } break; } (void) xo_flush_h(xop); } void xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_message_hcv(xop, code, fmt, vap); va_end(vap); } void xo_message_c (int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_message_hcv(NULL, code, fmt, vap); va_end(vap); } void xo_message_e (const char *fmt, ...) { int code = errno; va_list vap; va_start(vap, fmt); xo_message_hcv(NULL, code, fmt, vap); va_end(vap); } void xo_message (const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_message_hcv(NULL, 0, fmt, vap); va_end(vap); } static void xo_failure (xo_handle_t *xop, const char *fmt, ...) { if (!XOF_ISSET(xop, XOF_WARN)) return; va_list vap; va_start(vap, fmt); xo_warn_hcv(xop, -1, 1, fmt, vap); va_end(vap); } /** * Create a handle for use by later libxo functions. * * Note: normal use of libxo does not require a distinct handle, since * the default handle (used when NULL is passed) generates text on stdout. * * @param style Style of output desired (XO_STYLE_* value) * @param flags Set of XOF_* flags in use with this handle * @return Newly allocated handle * @see xo_destroy */ xo_handle_t * xo_create (xo_style_t style, xo_xof_flags_t flags) { xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop)); if (xop) { bzero(xop, sizeof(*xop)); xop->xo_style = style; XOF_SET(xop, flags); xo_init_handle(xop); xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */ } return xop; } /** * Create a handle that will write to the given file. Use * the XOF_CLOSE_FP flag to have the file closed on xo_destroy(). * * @param fp FILE pointer to use * @param style Style of output desired (XO_STYLE_* value) * @param flags Set of XOF_* flags to use with this handle * @return Newly allocated handle * @see xo_destroy */ xo_handle_t * xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags) { xo_handle_t *xop = xo_create(style, flags); if (xop) { xop->xo_opaque = fp; xop->xo_write = xo_write_to_file; xop->xo_close = xo_close_file; xop->xo_flush = xo_flush_file; } return xop; } /** * Set the default handler to output to a file. * * @param xop libxo handle * @param fp FILE pointer to use * @return 0 on success, non-zero on failure */ int xo_set_file_h (xo_handle_t *xop, FILE *fp) { xop = xo_default(xop); if (fp == NULL) { xo_failure(xop, "xo_set_file: NULL fp"); return -1; } xop->xo_opaque = fp; xop->xo_write = xo_write_to_file; xop->xo_close = xo_close_file; xop->xo_flush = xo_flush_file; return 0; } /** * Set the default handler to output to a file. * * @param fp FILE pointer to use * @return 0 on success, non-zero on failure */ int xo_set_file (FILE *fp) { return xo_set_file_h(NULL, fp); } /** * Release any resources held by the handle. * * @param xop XO handle to alter (or NULL for default handle) */ void xo_destroy (xo_handle_t *xop_arg) { xo_handle_t *xop = xo_default(xop_arg); xo_flush_h(xop); if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP)) xop->xo_close(xop->xo_opaque); xo_free(xop->xo_stack); xo_buf_cleanup(&xop->xo_data); xo_buf_cleanup(&xop->xo_fmt); xo_buf_cleanup(&xop->xo_predicate); xo_buf_cleanup(&xop->xo_attrs); xo_buf_cleanup(&xop->xo_color_buf); if (xop->xo_version) xo_free(xop->xo_version); if (xop_arg == NULL) { bzero(&xo_default_handle, sizeof(xo_default_handle)); xo_default_inited = 0; } else xo_free(xop); } /** * Record a new output style to use for the given handle (or default if * handle is NULL). This output style will be used for any future output. * * @param xop XO handle to alter (or NULL for default handle) * @param style new output style (XO_STYLE_*) */ void xo_set_style (xo_handle_t *xop, xo_style_t style) { xop = xo_default(xop); xop->xo_style = style; } /** * Return the current style of a handle * * @param xop XO handle to access * @return The handle's current style */ xo_style_t xo_get_style (xo_handle_t *xop) { xop = xo_default(xop); return xo_style(xop); } /** * Return the XO_STYLE_* value matching a given name * * @param name String name of a style * @return XO_STYLE_* value */ static int xo_name_to_style (const char *name) { if (strcmp(name, "xml") == 0) return XO_STYLE_XML; else if (strcmp(name, "json") == 0) return XO_STYLE_JSON; else if (strcmp(name, "encoder") == 0) return XO_STYLE_ENCODER; else if (strcmp(name, "text") == 0) return XO_STYLE_TEXT; else if (strcmp(name, "html") == 0) return XO_STYLE_HTML; else if (strcmp(name, "sdparams") == 0) return XO_STYLE_SDPARAMS; return -1; } /* * Indicate if the style is an "encoding" one as opposed to a "display" one. */ static int xo_style_is_encoding (xo_handle_t *xop) { if (xo_style(xop) == XO_STYLE_JSON || xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_SDPARAMS || xo_style(xop) == XO_STYLE_ENCODER) return 1; return 0; } /* Simple name-value mapping */ typedef struct xo_mapping_s { xo_xff_flags_t xm_value; /* Flag value */ const char *xm_name; /* String name */ } xo_mapping_t; static xo_xff_flags_t xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len) { if (len == 0) return 0; if (len < 0) len = strlen(value); while (isspace((int) *value)) { value += 1; len -= 1; } while (isspace((int) value[len])) len -= 1; if (*value == '\0') return 0; for ( ; map->xm_name; map++) if (strncmp(map->xm_name, value, len) == 0) return map->xm_value; return 0; } #ifdef NOT_NEEDED_YET static const char * xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value) { if (value == 0) return NULL; for ( ; map->xm_name; map++) if (map->xm_value == value) return map->xm_name; return NULL; } #endif /* NOT_NEEDED_YET */ static xo_mapping_t xo_xof_names[] = { { XOF_COLOR_ALLOWED, "color" }, { XOF_COLOR, "color-force" }, { XOF_COLUMNS, "columns" }, { XOF_DTRT, "dtrt" }, { XOF_FLUSH, "flush" }, { XOF_FLUSH_LINE, "flush-line" }, { XOF_IGNORE_CLOSE, "ignore-close" }, { XOF_INFO, "info" }, { XOF_KEYS, "keys" }, { XOF_LOG_GETTEXT, "log-gettext" }, { XOF_LOG_SYSLOG, "log-syslog" }, { XOF_NO_HUMANIZE, "no-humanize" }, { XOF_NO_LOCALE, "no-locale" }, { XOF_RETAIN_NONE, "no-retain" }, { XOF_NO_TOP, "no-top" }, { XOF_NOT_FIRST, "not-first" }, { XOF_PRETTY, "pretty" }, { XOF_RETAIN_ALL, "retain" }, { XOF_UNDERSCORES, "underscores" }, { XOF_UNITS, "units" }, { XOF_WARN, "warn" }, { XOF_WARN_XML, "warn-xml" }, { XOF_XPATH, "xpath" }, { 0, NULL } }; /* Options available via the environment variable ($LIBXO_OPTIONS) */ static xo_mapping_t xo_xof_simple_names[] = { { XOF_COLOR_ALLOWED, "color" }, { XOF_FLUSH, "flush" }, { XOF_FLUSH_LINE, "flush-line" }, { XOF_NO_HUMANIZE, "no-humanize" }, { XOF_NO_LOCALE, "no-locale" }, { XOF_RETAIN_NONE, "no-retain" }, { XOF_PRETTY, "pretty" }, { XOF_RETAIN_ALL, "retain" }, { XOF_UNDERSCORES, "underscores" }, { XOF_WARN, "warn" }, { 0, NULL } }; /* * Convert string name to XOF_* flag value. * Not all are useful. Or safe. Or sane. */ static unsigned xo_name_to_flag (const char *name) { return (unsigned) xo_name_lookup(xo_xof_names, name, -1); } /** * Set the style of an libxo handle based on a string name * * @param xop XO handle * @param name String value of name * @return 0 on success, non-zero on failure */ int xo_set_style_name (xo_handle_t *xop, const char *name) { if (name == NULL) return -1; int style = xo_name_to_style(name); if (style < 0) return -1; xo_set_style(xop, style); return 0; } /* * Fill in the color map, based on the input string; currently unimplemented * Look for something like "colors=red/blue+green/yellow" as fg/bg pairs. */ static void xo_set_color_map (xo_handle_t *xop, char *value) { #ifdef LIBXO_TEXT_ONLY return; #endif /* LIBXO_TEXT_ONLY */ char *cp, *ep, *vp, *np; ssize_t len = value ? strlen(value) + 1 : 0; int num = 1, fg, bg; for (cp = value, ep = cp + len - 1; cp && *cp && cp < ep; cp = np) { np = strchr(cp, '+'); if (np) *np++ = '\0'; vp = strchr(cp, '/'); if (vp) *vp++ = '\0'; fg = *cp ? xo_color_find(cp) : -1; bg = (vp && *vp) ? xo_color_find(vp) : -1; xop->xo_color_map_fg[num] = (fg < 0) ? num : fg; xop->xo_color_map_bg[num] = (bg < 0) ? num : bg; if (++num > XO_NUM_COLORS) break; } /* If no color initialization happened, then we don't need the map */ if (num > 0) XOF_SET(xop, XOF_COLOR_MAP); else XOF_CLEAR(xop, XOF_COLOR_MAP); /* Fill in the rest of the colors with the defaults */ for ( ; num < XO_NUM_COLORS; num++) xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num; } static int xo_set_options_simple (xo_handle_t *xop, const char *input) { xo_xof_flags_t new_flag; char *cp, *ep, *vp, *np, *bp; ssize_t len = strlen(input) + 1; bp = alloca(len); memcpy(bp, input, len); for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) { np = strchr(cp, ','); if (np) *np++ = '\0'; vp = strchr(cp, '='); if (vp) *vp++ = '\0'; if (strcmp("colors", cp) == 0) { xo_set_color_map(xop, vp); continue; } new_flag = xo_name_lookup(xo_xof_simple_names, cp, -1); if (new_flag != 0) { XOF_SET(xop, new_flag); } else if (strcmp(cp, "no-color") == 0) { XOF_CLEAR(xop, XOF_COLOR_ALLOWED); } else { xo_failure(xop, "unknown simple option: %s", cp); return -1; } } return 0; } /** * Set the options for a handle using a string of options * passed in. The input is a comma-separated set of names * and optional values: "xml,pretty,indent=4" * * @param xop XO handle * @param input Comma-separated set of option values * @return 0 on success, non-zero on failure */ int xo_set_options (xo_handle_t *xop, const char *input) { char *cp, *ep, *vp, *np, *bp; int style = -1, new_style, rc = 0; ssize_t len; xo_xof_flags_t new_flag; if (input == NULL) return 0; xop = xo_default(xop); #ifdef LIBXO_COLOR_ON_BY_DEFAULT /* If the installer used --enable-color-on-by-default, then we allow it */ XOF_SET(xop, XOF_COLOR_ALLOWED); #endif /* LIBXO_COLOR_ON_BY_DEFAULT */ /* * We support a simpler, old-school style of giving option * also, using a single character for each option. It's * ideal for lazy people, such as myself. */ if (*input == ':') { ssize_t sz; for (input++ ; *input; input++) { switch (*input) { case 'c': XOF_SET(xop, XOF_COLOR_ALLOWED); break; case 'f': XOF_SET(xop, XOF_FLUSH); break; case 'F': XOF_SET(xop, XOF_FLUSH_LINE); break; case 'g': XOF_SET(xop, XOF_LOG_GETTEXT); break; case 'H': xop->xo_style = XO_STYLE_HTML; break; case 'I': XOF_SET(xop, XOF_INFO); break; case 'i': sz = strspn(input + 1, "0123456789"); if (sz > 0) { xop->xo_indent_by = atoi(input + 1); input += sz - 1; /* Skip value */ } break; case 'J': xop->xo_style = XO_STYLE_JSON; break; case 'k': XOF_SET(xop, XOF_KEYS); break; case 'n': XOF_SET(xop, XOF_NO_HUMANIZE); break; case 'P': XOF_SET(xop, XOF_PRETTY); break; case 'T': xop->xo_style = XO_STYLE_TEXT; break; case 'U': XOF_SET(xop, XOF_UNITS); break; case 'u': XOF_SET(xop, XOF_UNDERSCORES); break; case 'W': XOF_SET(xop, XOF_WARN); break; case 'X': xop->xo_style = XO_STYLE_XML; break; case 'x': XOF_SET(xop, XOF_XPATH); break; } } return 0; } len = strlen(input) + 1; bp = alloca(len); memcpy(bp, input, len); for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) { np = strchr(cp, ','); if (np) *np++ = '\0'; vp = strchr(cp, '='); if (vp) *vp++ = '\0'; if (strcmp("colors", cp) == 0) { xo_set_color_map(xop, vp); continue; } /* * For options, we don't allow "encoder" since we want to * handle it explicitly below as "encoder=xxx". */ new_style = xo_name_to_style(cp); if (new_style >= 0 && new_style != XO_STYLE_ENCODER) { if (style >= 0) xo_warnx("ignoring multiple styles: '%s'", cp); else style = new_style; } else { new_flag = xo_name_to_flag(cp); if (new_flag != 0) XOF_SET(xop, new_flag); else if (strcmp(cp, "no-color") == 0) XOF_CLEAR(xop, XOF_COLOR_ALLOWED); else if (strcmp(cp, "indent") == 0) { if (vp) xop->xo_indent_by = atoi(vp); else xo_failure(xop, "missing value for indent option"); } else if (strcmp(cp, "encoder") == 0) { if (vp == NULL) xo_failure(xop, "missing value for encoder option"); else { if (xo_encoder_init(xop, vp)) { xo_failure(xop, "encoder not found: %s", vp); rc = -1; } } } else { xo_warnx("unknown libxo option value: '%s'", cp); rc = -1; } } } if (style > 0) xop->xo_style= style; return rc; } /** * Set one or more flags for a given handle (or default if handle is NULL). * These flags will affect future output. * * @param xop XO handle to alter (or NULL for default handle) * @param flags Flags to be set (XOF_*) */ void xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags) { xop = xo_default(xop); XOF_SET(xop, flags); } /** * Accessor to return the current set of flags for a handle * @param xop XO handle * @return Current set of flags */ xo_xof_flags_t xo_get_flags (xo_handle_t *xop) { xop = xo_default(xop); return xop->xo_flags; } /** * strndup with a twist: len < 0 means len = strlen(str) */ static char * xo_strndup (const char *str, ssize_t len) { if (len < 0) len = strlen(str); char *cp = xo_realloc(NULL, len + 1); if (cp) { memcpy(cp, str, len); cp[len] = '\0'; } return cp; } /** * Record a leading prefix for the XPath we generate. This allows the * generated data to be placed within an XML hierarchy but still have * accurate XPath expressions. * * @param xop XO handle to alter (or NULL for default handle) * @param path The XPath expression */ void xo_set_leading_xpath (xo_handle_t *xop, const char *path) { xop = xo_default(xop); if (xop->xo_leading_xpath) { xo_free(xop->xo_leading_xpath); xop->xo_leading_xpath = NULL; } if (path == NULL) return; xop->xo_leading_xpath = xo_strndup(path, -1); } /** * Record the info data for a set of tags * * @param xop XO handle to alter (or NULL for default handle) * @param info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED) * @pararm count Number of entries in info (or -1 to count them ourselves) */ void xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count) { xop = xo_default(xop); if (count < 0 && infop) { xo_info_t *xip; for (xip = infop, count = 0; xip->xi_name; xip++, count++) continue; } xop->xo_info = infop; xop->xo_info_count = count; } /** * Set the formatter callback for a handle. The callback should * return a newly formatting contents of a formatting instruction, * meaning the bits inside the braces. */ void xo_set_formatter (xo_handle_t *xop, xo_formatter_t func, xo_checkpointer_t cfunc) { xop = xo_default(xop); xop->xo_formatter = func; xop->xo_checkpointer = cfunc; } /** * Clear one or more flags for a given handle (or default if handle is NULL). * These flags will affect future output. * * @param xop XO handle to alter (or NULL for default handle) * @param flags Flags to be cleared (XOF_*) */ void xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags) { xop = xo_default(xop); XOF_CLEAR(xop, flags); } static const char * xo_state_name (xo_state_t state) { static const char *names[] = { "init", "open_container", "close_container", "open_list", "close_list", "open_instance", "close_instance", "open_leaf_list", "close_leaf_list", "discarding", "marker", "emit", "emit_leaf_list", "finish", NULL }; if (state < (sizeof(names) / sizeof(names[0]))) return names[state]; return "unknown"; } static void xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED) { static char div_open[] = "
"; static char div_open_blank[] = "
"; if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) return; if (xo_style(xop) != XO_STYLE_HTML) return; XOIF_SET(xop, XOIF_DIV_OPEN); if (flags & XFF_BLANK_LINE) xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1); else xo_data_append(xop, div_open, sizeof(div_open) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); } static void xo_line_close (xo_handle_t *xop) { static char div_close[] = "
"; switch (xo_style(xop)) { case XO_STYLE_HTML: if (!XOIF_ISSET(xop, XOIF_DIV_OPEN)) xo_line_ensure_open(xop, 0); XOIF_CLEAR(xop, XOIF_DIV_OPEN); xo_data_append(xop, div_close, sizeof(div_close) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); break; case XO_STYLE_TEXT: xo_data_append(xop, "\n", 1); break; } } static int xo_info_compare (const void *key, const void *data) { const char *name = key; const xo_info_t *xip = data; return strcmp(name, xip->xi_name); } static xo_info_t * xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen) { xo_info_t *xip; char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */ memcpy(cp, name, nlen); cp[nlen] = '\0'; xip = bsearch(cp, xop->xo_info, xop->xo_info_count, sizeof(xop->xo_info[0]), xo_info_compare); return xip; } #define CONVERT(_have, _need) (((_have) << 8) | (_need)) /* * Check to see that the conversion is safe and sane. */ static int xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc) { switch (CONVERT(have_enc, need_enc)) { case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8): case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE): case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8): case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE): case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE): case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8): return 0; default: xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc); return 1; } } static int xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags, const wchar_t *wcp, const char *cp, ssize_t len, int max, int need_enc, int have_enc) { int cols = 0; wchar_t wc = 0; ssize_t ilen, olen; ssize_t width; int attr = XOF_BIT_ISSET(flags, XFF_ATTR); const char *sp; if (len > 0 && !xo_buf_has_room(xbp, len)) return 0; for (;;) { if (len == 0) break; if (cp) { if (*cp == '\0') break; if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) { cp += 1; len -= 1; } } if (wcp && *wcp == L'\0') break; ilen = 0; switch (have_enc) { case XF_ENC_WIDE: /* Wide character */ wc = *wcp++; ilen = 1; break; case XF_ENC_UTF8: /* UTF-8 */ ilen = xo_utf8_to_wc_len(cp); if (ilen < 0) { xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp); return -1; /* Can't continue; we can't find the end */ } if (len > 0 && len < ilen) { len = 0; /* Break out of the loop */ continue; } wc = xo_utf8_char(cp, ilen); if (wc == (wchar_t) -1) { xo_failure(xop, "invalid UTF-8 character: %02hhx/%d", *cp, ilen); return -1; /* Can't continue; we can't find the end */ } cp += ilen; break; case XF_ENC_LOCALE: /* Native locale */ ilen = (len > 0) ? len : MB_LEN_MAX; ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate); if (ilen < 0) { /* Invalid data; skip */ xo_failure(xop, "invalid mbs char: %02hhx", *cp); wc = L'?'; ilen = 1; } if (ilen == 0) { /* Hit a wide NUL character */ len = 0; continue; } cp += ilen; break; } /* Reduce len, but not below zero */ if (len > 0) { len -= ilen; if (len < 0) len = 0; } /* * Find the width-in-columns of this character, which must be done * in wide characters, since we lack a mbswidth() function. If * it doesn't fit */ width = xo_wcwidth(wc); if (width < 0) width = iswcntrl(wc) ? 0 : 1; if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) { if (max > 0 && cols + width > max) break; } switch (need_enc) { case XF_ENC_UTF8: /* Output in UTF-8 needs to be escaped, based on the style */ switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_HTML: if (wc == '<') sp = xo_xml_lt; else if (wc == '>') sp = xo_xml_gt; else if (wc == '&') sp = xo_xml_amp; else if (attr && wc == '"') sp = xo_xml_quot; else break; ssize_t slen = strlen(sp); if (!xo_buf_has_room(xbp, slen - 1)) return -1; memcpy(xbp->xb_curp, sp, slen); xbp->xb_curp += slen; goto done_with_encoding; /* Need multi-level 'break' */ case XO_STYLE_JSON: if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r') break; if (!xo_buf_has_room(xbp, 2)) return -1; *xbp->xb_curp++ = '\\'; if (wc == '\n') wc = 'n'; else if (wc == '\r') wc = 'r'; else wc = wc & 0x7f; *xbp->xb_curp++ = wc; goto done_with_encoding; case XO_STYLE_SDPARAMS: if (wc != '\\' && wc != '"' && wc != ']') break; if (!xo_buf_has_room(xbp, 2)) return -1; *xbp->xb_curp++ = '\\'; wc = wc & 0x7f; *xbp->xb_curp++ = wc; goto done_with_encoding; } olen = xo_utf8_emit_len(wc); if (olen < 0) { xo_failure(xop, "ignoring bad length"); continue; } if (!xo_buf_has_room(xbp, olen)) return -1; xo_utf8_emit_char(xbp->xb_curp, olen, wc); xbp->xb_curp += olen; break; case XF_ENC_LOCALE: if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1)) return -1; olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate); if (olen <= 0) { xo_failure(xop, "could not convert wide char: %lx", (unsigned long) wc); width = 1; *xbp->xb_curp++ = '?'; } else xbp->xb_curp += olen; break; } done_with_encoding: cols += width; } return cols; } static int xo_needed_encoding (xo_handle_t *xop) { if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */ return XF_ENC_UTF8; if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */ return XF_ENC_LOCALE; return XF_ENC_UTF8; /* Otherwise, we love UTF-8 */ } static ssize_t xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags, xo_format_t *xfp) { static char null[] = "(null)"; static char null_no_quotes[] = "null"; char *cp = NULL; wchar_t *wcp = NULL; ssize_t len; ssize_t cols = 0, rc = 0; ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2; int need_enc = xo_needed_encoding(xop); if (xo_check_conversion(xop, xfp->xf_enc, need_enc)) return 0; len = xfp->xf_width[XF_WIDTH_SIZE]; if (xfp->xf_fc == 'm') { cp = strerror(xop->xo_errno); if (len < 0) len = cp ? strlen(cp) : 0; goto normal_string; } else if (xfp->xf_enc == XF_ENC_WIDE) { wcp = va_arg(xop->xo_vap, wchar_t *); if (xfp->xf_skip) return 0; /* * Dont' deref NULL; use the traditional "(null)" instead * of the more accurate "who's been a naughty boy, then?". */ if (wcp == NULL) { cp = null; len = sizeof(null) - 1; } } else { cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */ normal_string: if (xfp->xf_skip) return 0; /* Echo "Dont' deref NULL" logic */ if (cp == NULL) { if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) { cp = null_no_quotes; len = sizeof(null_no_quotes) - 1; } else { cp = null; len = sizeof(null) - 1; } } /* * Optimize the most common case, which is "%s". We just * need to copy the complete string to the output buffer. */ if (xfp->xf_enc == need_enc && xfp->xf_width[XF_WIDTH_MIN] < 0 && xfp->xf_width[XF_WIDTH_SIZE] < 0 && xfp->xf_width[XF_WIDTH_MAX] < 0 && !(XOIF_ISSET(xop, XOIF_ANCHOR) || XOF_ISSET(xop, XOF_COLUMNS))) { len = strlen(cp); xo_buf_escape(xop, xbp, cp, len, flags); /* * Our caller expects xb_curp left untouched, so we have * to reset it and return the number of bytes written to * the buffer. */ off2 = xbp->xb_curp - xbp->xb_bufp; rc = off2 - off; xbp->xb_curp = xbp->xb_bufp + off; return rc; } } cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len, xfp->xf_width[XF_WIDTH_MAX], need_enc, xfp->xf_enc); if (cols < 0) goto bail; /* * xo_buf_append* will move xb_curp, so we save/restore it. */ off2 = xbp->xb_curp - xbp->xb_bufp; rc = off2 - off; xbp->xb_curp = xbp->xb_bufp + off; if (cols < xfp->xf_width[XF_WIDTH_MIN]) { /* * Find the number of columns needed to display the string. * If we have the original wide string, we just call wcswidth, * but if we did the work ourselves, then we need to do it. */ int delta = xfp->xf_width[XF_WIDTH_MIN] - cols; if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN])) goto bail; /* * If seen_minus, then pad on the right; otherwise move it so * we can pad on the left. */ if (xfp->xf_seen_minus) { cp = xbp->xb_curp + rc; } else { cp = xbp->xb_curp; memmove(xbp->xb_curp + delta, xbp->xb_curp, rc); } /* Set the padding */ memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta); rc += delta; cols += delta; } if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += cols; return rc; bail: xbp->xb_curp = xbp->xb_bufp + off; return 0; } /* * Look backwards in a buffer to find a numeric value */ static int xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset) { int rc = 0; /* Fail with zero */ int digit = 1; char *sp = xbp->xb_bufp; char *cp = sp + start_offset; while (--cp >= sp) if (isdigit((int) *cp)) break; for ( ; cp >= sp; cp--) { if (!isdigit((int) *cp)) break; rc += (*cp - '0') * digit; digit *= 10; } return rc; } static ssize_t xo_count_utf8_cols (const char *str, ssize_t len) { ssize_t tlen; wchar_t wc; ssize_t cols = 0; const char *ep = str + len; while (str < ep) { tlen = xo_utf8_to_wc_len(str); if (tlen < 0) /* Broken input is very bad */ return cols; wc = xo_utf8_char(str, tlen); if (wc == (wchar_t) -1) return cols; /* We only print printable characters */ if (iswprint((wint_t) wc)) { /* * Find the width-in-columns of this character, which must be done * in wide characters, since we lack a mbswidth() function. */ ssize_t width = xo_wcwidth(wc); if (width < 0) width = iswcntrl(wc) ? 0 : 1; cols += width; } str += tlen; } return cols; } #ifdef HAVE_GETTEXT static inline const char * xo_dgettext (xo_handle_t *xop, const char *str) { const char *domainname = xop->xo_gt_domain; const char *res; res = dgettext(domainname, str); if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n", domainname ? "domain \"" : "", xo_printable(domainname), domainname ? "\", " : "", xo_printable(str), xo_printable(res)); return res; } static inline const char * xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural, unsigned long int n) { const char *domainname = xop->xo_gt_domain; const char *res; res = dngettext(domainname, sing, plural, n); if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) fprintf(stderr, "xo: gettext: %s%s%s" "msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n", domainname ? "domain \"" : "", xo_printable(domainname), domainname ? "\", " : "", xo_printable(sing), xo_printable(plural), n, xo_printable(res)); return res; } #else /* HAVE_GETTEXT */ static inline const char * xo_dgettext (xo_handle_t *xop UNUSED, const char *str) { return str; } static inline const char * xo_dngettext (xo_handle_t *xop UNUSED, const char *singular, const char *plural, unsigned long int n) { return (n == 1) ? singular : plural; } #endif /* HAVE_GETTEXT */ /* * This is really _re_formatting, since the normal format code has * generated a beautiful string into xo_data, starting at * start_offset. We need to see if it's plural, which means * comma-separated options, or singular. Then we make the appropriate * call to d[n]gettext() to get the locale-based version. Note that * both input and output of gettext() this should be UTF-8. */ static ssize_t xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags, ssize_t start_offset, ssize_t cols, int need_enc) { xo_buffer_t *xbp = &xop->xo_data; if (!xo_buf_has_room(xbp, 1)) return cols; xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */ char *cp = xbp->xb_bufp + start_offset; ssize_t len = xbp->xb_curp - cp; const char *newstr = NULL; /* * The plural flag asks us to look backwards at the last numeric * value rendered and disect the string into two pieces. */ if (flags & XFF_GT_PLURAL) { int n = xo_buf_find_last_number(xbp, start_offset); char *two = memchr(cp, (int) ',', len); if (two == NULL) { xo_failure(xop, "no comma in plural gettext field: '%s'", cp); return cols; } if (two == cp) { xo_failure(xop, "nothing before comma in plural gettext " "field: '%s'", cp); return cols; } if (two == xbp->xb_curp) { xo_failure(xop, "nothing after comma in plural gettext " "field: '%s'", cp); return cols; } *two++ = '\0'; if (flags & XFF_GT_FIELD) { newstr = xo_dngettext(xop, cp, two, n); } else { /* Don't do a gettext() look up, just get the plural form */ newstr = (n == 1) ? cp : two; } /* * If we returned the first string, optimize a bit by * backing up over comma */ if (newstr == cp) { xbp->xb_curp = two - 1; /* One for comma */ /* * If the caller wanted UTF8, we're done; nothing changed, * but we need to count the columns used. */ if (need_enc == XF_ENC_UTF8) return xo_count_utf8_cols(cp, xbp->xb_curp - cp); } } else { /* The simple case (singular) */ newstr = xo_dgettext(xop, cp); if (newstr == cp) { /* If the caller wanted UTF8, we're done; nothing changed */ if (need_enc == XF_ENC_UTF8) return cols; } } /* * Since the new string string might be in gettext's buffer or * in the buffer (as the plural form), we make a copy. */ ssize_t nlen = strlen(newstr); char *newcopy = alloca(nlen + 1); memcpy(newcopy, newstr, nlen + 1); xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */ return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0, need_enc, XF_ENC_UTF8); } static void xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len, xo_xff_flags_t flags) { int cols; int need_enc = xo_needed_encoding(xop); ssize_t start_offset = xo_buf_offset(&xop->xo_data); cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags, NULL, str, len, -1, need_enc, XF_ENC_UTF8); if (flags & XFF_GT_FLAGS) cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc); if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += cols; } /** * Bump one of the 'width' values in a format strings (e.g. "%40.50.60s"). * @param xfp Formatting instructions * @param digit Single digit (0-9) of input */ static void xo_bump_width (xo_format_t *xfp, int digit) { int *ip = &xfp->xf_width[xfp->xf_dots]; *ip = ((*ip > 0) ? *ip : 0) * 10 + digit; } static ssize_t xo_trim_ws (xo_buffer_t *xbp, ssize_t len) { char *cp, *sp, *ep; ssize_t delta; /* First trim leading space */ for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { if (*cp != ' ') break; } delta = cp - sp; if (delta) { len -= delta; memmove(sp, cp, len); } /* Then trim off the end */ for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) { if (ep[-1] != ' ') break; } delta = sp - ep; if (delta) { len -= delta; cp[len] = '\0'; } return len; } /* * Interface to format a single field. The arguments are in xo_vap, * and the format is in 'fmt'. If 'xbp' is null, we use xop->xo_data; * this is the most common case. */ static ssize_t xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, ssize_t flen, xo_xff_flags_t flags) { xo_format_t xf; const char *cp, *ep, *sp, *xp = NULL; ssize_t rc, cols; int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop); unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0; int need_enc = xo_needed_encoding(xop); int real_need_enc = need_enc; ssize_t old_cols = xop->xo_columns; /* The gettext interface is UTF-8, so we'll need that for now */ if (flags & XFF_GT_FIELD) need_enc = XF_ENC_UTF8; if (xbp == NULL) xbp = &xop->xo_data; ssize_t start_offset = xo_buf_offset(xbp); for (cp = fmt, ep = fmt + flen; cp < ep; cp++) { /* * Since we're starting a new field, save the starting offset. * We'll need this later for field-related operations. */ if (*cp != '%') { add_one: if (xp == NULL) xp = cp; if (*cp == '\\' && cp[1] != '\0') cp += 1; continue; } if (cp + 1 < ep && cp[1] == '%') { cp += 1; goto add_one; } if (xp) { if (make_output) { cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, NULL, xp, cp - xp, -1, need_enc, XF_ENC_UTF8); if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += cols; } xp = NULL; } bzero(&xf, sizeof(xf)); xf.xf_leading_zero = -1; xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1; /* * "%@" starts an XO-specific set of flags: * @X@ - XML-only field; ignored if style isn't XML */ if (cp[1] == '@') { for (cp += 2; cp < ep; cp++) { if (*cp == '@') { break; } if (*cp == '*') { /* * '*' means there's a "%*.*s" value in vap that * we want to ignore */ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) va_arg(xop->xo_vap, int); } } } /* Hidden fields are only visible to JSON and XML */ if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) { if (style != XO_STYLE_XML && !xo_style_is_encoding(xop)) xf.xf_skip = 1; } else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) { if (style != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) xf.xf_skip = 1; } if (!make_output) xf.xf_skip = 1; /* * Looking at one piece of a format; find the end and * call snprintf. Then advance xo_vap on our own. * * Note that 'n', 'v', and '$' are not supported. */ sp = cp; /* Save start pointer */ for (cp += 1; cp < ep; cp++) { if (*cp == 'l') xf.xf_lflag += 1; else if (*cp == 'h') xf.xf_hflag += 1; else if (*cp == 'j') xf.xf_jflag += 1; else if (*cp == 't') xf.xf_tflag += 1; else if (*cp == 'z') xf.xf_zflag += 1; else if (*cp == 'q') xf.xf_qflag += 1; else if (*cp == '.') { if (++xf.xf_dots >= XF_WIDTH_NUM) { xo_failure(xop, "Too many dots in format: '%s'", fmt); return -1; } } else if (*cp == '-') xf.xf_seen_minus = 1; else if (isdigit((int) *cp)) { if (xf.xf_leading_zero < 0) xf.xf_leading_zero = (*cp == '0'); xo_bump_width(&xf, *cp - '0'); } else if (*cp == '*') { xf.xf_stars += 1; xf.xf_star[xf.xf_dots] = 1; } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL) break; else if (*cp == 'n' || *cp == 'v') { xo_failure(xop, "unsupported format: '%s'", fmt); return -1; } } if (cp == ep) xo_failure(xop, "field format missing format character: %s", fmt); xf.xf_fc = *cp; if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) { if (*cp == 's' || *cp == 'S') { /* Handle "%*.*.*s" */ int s; for (s = 0; s < XF_WIDTH_NUM; s++) { if (xf.xf_star[s]) { xf.xf_width[s] = va_arg(xop->xo_vap, int); /* Normalize a negative width value */ if (xf.xf_width[s] < 0) { if (s == 0) { xf.xf_width[0] = -xf.xf_width[0]; xf.xf_seen_minus = 1; } else xf.xf_width[s] = -1; /* Ignore negative values */ } } } } } /* If no max is given, it defaults to size */ if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0) xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE]; if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U') xf.xf_lflag = 1; if (!xf.xf_skip) { xo_buffer_t *fbp = &xop->xo_fmt; ssize_t len = cp - sp + 1; if (!xo_buf_has_room(fbp, len + 1)) return -1; char *newfmt = fbp->xb_curp; memcpy(newfmt, sp, len); newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */ newfmt[len] = '\0'; /* * Bad news: our strings are UTF-8, but the stock printf * functions won't handle field widths for wide characters * correctly. So we have to handle this ourselves. */ if (xop->xo_formatter == NULL && (xf.xf_fc == 's' || xf.xf_fc == 'S' || xf.xf_fc == 'm')) { xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8 : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8; rc = xo_format_string(xop, xbp, flags, &xf); if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop)) rc = xo_trim_ws(xbp, rc); } else { ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap); /* * For XML and HTML, we need "&<>" processing; for JSON, * it's quotes. Text gets nothing. */ switch (style) { case XO_STYLE_XML: if (flags & XFF_TRIM_WS) columns = rc = xo_trim_ws(xbp, rc); /* FALLTHRU */ case XO_STYLE_HTML: rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR)); break; case XO_STYLE_JSON: if (flags & XFF_TRIM_WS) columns = rc = xo_trim_ws(xbp, rc); rc = xo_escape_json(xbp, rc, 0); break; case XO_STYLE_SDPARAMS: if (flags & XFF_TRIM_WS) columns = rc = xo_trim_ws(xbp, rc); rc = xo_escape_sdparams(xbp, rc, 0); break; case XO_STYLE_ENCODER: if (flags & XFF_TRIM_WS) columns = rc = xo_trim_ws(xbp, rc); break; } /* * We can assume all the non-%s data we've * added is ASCII, so the columns and bytes are the * same. xo_format_string handles all the fancy * string conversions and updates xo_anchor_columns * accordingly. */ if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += columns; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += columns; } xbp->xb_curp += rc; } /* * Now for the tricky part: we need to move the argument pointer * along by the amount needed. */ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) { if (xf.xf_fc == 's' ||xf.xf_fc == 'S') { /* * The 'S' and 's' formats are normally handled in * xo_format_string, but if we skipped it, then we * need to pop it. */ if (xf.xf_skip) va_arg(xop->xo_vap, char *); } else if (xf.xf_fc == 'm') { /* Nothing on the stack for "%m" */ } else { int s; for (s = 0; s < XF_WIDTH_NUM; s++) { if (xf.xf_star[s]) va_arg(xop->xo_vap, int); } if (strchr("diouxXDOU", xf.xf_fc) != NULL) { if (xf.xf_hflag > 1) { va_arg(xop->xo_vap, int); } else if (xf.xf_hflag > 0) { va_arg(xop->xo_vap, int); } else if (xf.xf_lflag > 1) { va_arg(xop->xo_vap, unsigned long long); } else if (xf.xf_lflag > 0) { va_arg(xop->xo_vap, unsigned long); } else if (xf.xf_jflag > 0) { va_arg(xop->xo_vap, intmax_t); } else if (xf.xf_tflag > 0) { va_arg(xop->xo_vap, ptrdiff_t); } else if (xf.xf_zflag > 0) { va_arg(xop->xo_vap, size_t); } else if (xf.xf_qflag > 0) { va_arg(xop->xo_vap, quad_t); } else { va_arg(xop->xo_vap, int); } } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL) if (xf.xf_lflag) va_arg(xop->xo_vap, long double); else va_arg(xop->xo_vap, double); else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag)) va_arg(xop->xo_vap, wint_t); else if (xf.xf_fc == 'c') va_arg(xop->xo_vap, int); else if (xf.xf_fc == 'p') va_arg(xop->xo_vap, void *); } } } if (xp) { if (make_output) { cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE, NULL, xp, cp - xp, -1, need_enc, XF_ENC_UTF8); if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += cols; } xp = NULL; } if (flags & XFF_GT_FLAGS) { /* * Handle gettext()ing the field by looking up the value * and then copying it in, while converting to locale, if * needed. */ ssize_t new_cols = xo_format_gettext(xop, flags, start_offset, old_cols, real_need_enc); if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += new_cols - old_cols; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += new_cols - old_cols; } return 0; } static char * xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding) { char *cp = encoding; if (cp[0] != '%' || !isdigit((int) cp[1])) return encoding; for (cp += 2; *cp; cp++) { if (!isdigit((int) *cp)) break; } cp -= 1; *cp = '%'; return cp; } static void xo_color_append_html (xo_handle_t *xop) { /* * If the color buffer has content, we add it now. It's already * prebuilt and ready, since we want to add it to every
. */ if (!xo_buf_is_empty(&xop->xo_color_buf)) { xo_buffer_t *xbp = &xop->xo_color_buf; xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp); } } /* * A wrapper for humanize_number that autoscales, since the * HN_AUTOSCALE flag scales as needed based on the size of * the output buffer, not the size of the value. I also * wish HN_DECIMAL was more imperative, without the <10 * test. But the boat only goes where we want when we hold * the rudder, so xo_humanize fixes part of the problem. */ static ssize_t xo_humanize (char *buf, ssize_t len, uint64_t value, int flags) { int scale = 0; if (value) { uint64_t left = value; if (flags & HN_DIVISOR_1000) { for ( ; left; scale++) left /= 1000; } else { for ( ; left; scale++) left /= 1024; } scale -= 1; } return xo_humanize_number(buf, len, value, "", scale, flags); } /* * This is an area where we can save information from the handle for * later restoration. We need to know what data was rendered to know * what needs cleaned up. */ typedef struct xo_humanize_save_s { ssize_t xhs_offset; /* Saved xo_offset */ ssize_t xhs_columns; /* Saved xo_columns */ ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */ } xo_humanize_save_t; /* * Format a "humanized" value for a numeric, meaning something nice * like "44M" instead of "44470272". We autoscale, choosing the * most appropriate value for K/M/G/T/P/E based on the value given. */ static void xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp, xo_humanize_save_t *savep, xo_xff_flags_t flags) { if (XOF_ISSET(xop, XOF_NO_HUMANIZE)) return; ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp; if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */ return; /* * We have a string that's allegedly a number. We want to * humanize it, which means turning it back into a number * and calling xo_humanize_number on it. */ uint64_t value; char *ep; xo_buf_append(xbp, "", 1); /* NUL-terminate it */ value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0); if (!(value == ULLONG_MAX && errno == ERANGE) && (ep != xbp->xb_bufp + savep->xhs_offset)) { /* * There are few values where humanize_number needs * more bytes than the original value. I've used * 10 as a rectal number to cover those scenarios. */ if (xo_buf_has_room(xbp, 10)) { xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset; ssize_t rc; ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp; int hn_flags = HN_NOSPACE; /* On by default */ if (flags & XFF_HN_SPACE) hn_flags &= ~HN_NOSPACE; if (flags & XFF_HN_DECIMAL) hn_flags |= HN_DECIMAL; if (flags & XFF_HN_1000) hn_flags |= HN_DIVISOR_1000; rc = xo_humanize(xbp->xb_curp, left, value, hn_flags); if (rc > 0) { xbp->xb_curp += rc; xop->xo_columns = savep->xhs_columns + rc; xop->xo_anchor_columns = savep->xhs_anchor_columns + rc; } } } } static void xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, const char *name, ssize_t nlen, const char *value, ssize_t vlen, const char *encoding, ssize_t elen) { static char div_start[] = "
"; static char div_close[] = "
"; /* The encoding format defaults to the normal format */ if (encoding == NULL) { char *enc = alloca(vlen + 1); memcpy(enc, value, vlen); enc[vlen] = '\0'; encoding = xo_fix_encoding(xop, enc); elen = strlen(encoding); } /* * To build our XPath predicate, we need to save the va_list before * we format our data, and then restore it before we format the * xpath expression. * Display-only keys implies that we've got an encode-only key * elsewhere, so we don't use them from making predicates. */ int need_predidate = (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY) && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0; if (need_predidate) { va_list va_local; va_copy(va_local, xop->xo_vap); if (xop->xo_checkpointer) xop->xo_checkpointer(xop, xop->xo_vap, 0); /* * Build an XPath predicate expression to match this key. * We use the format buffer. */ xo_buffer_t *pbp = &xop->xo_predicate; pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */ xo_buf_append(pbp, "[", 1); xo_buf_escape(xop, pbp, name, nlen, 0); if (XOF_ISSET(xop, XOF_PRETTY)) xo_buf_append(pbp, " = '", 4); else xo_buf_append(pbp, "='", 2); xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR; pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY); xo_do_format_field(xop, pbp, encoding, elen, pflags); xo_buf_append(pbp, "']", 2); /* Now we record this predicate expression in the stack */ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0; ssize_t dlen = pbp->xb_curp - pbp->xb_bufp; char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1); if (cp) { memcpy(cp + olen, pbp->xb_bufp, dlen); cp[olen + dlen] = '\0'; xsp->xs_keys = cp; } /* Now we reset the xo_vap as if we were never here */ va_end(xop->xo_vap); va_copy(xop->xo_vap, va_local); va_end(va_local); if (xop->xo_checkpointer) xop->xo_checkpointer(xop, xop->xo_vap, 1); } if (flags & XFF_ENCODE_ONLY) { /* * Even if this is encode-only, we need to go through the * work of formatting it to make sure the args are cleared * from xo_vap. */ xo_do_format_field(xop, NULL, encoding, elen, flags | XFF_NO_OUTPUT); return; } xo_line_ensure_open(xop, 0); if (XOF_ISSET(xop, XOF_PRETTY)) xo_buf_indent(xop, xop->xo_indent_by); xo_data_append(xop, div_start, sizeof(div_start) - 1); xo_data_append(xop, class, strlen(class)); /* * If the color buffer has content, we add it now. It's already * prebuilt and ready, since we want to add it to every
. */ if (!xo_buf_is_empty(&xop->xo_color_buf)) { xo_buffer_t *xbp = &xop->xo_color_buf; xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp); } if (name) { xo_data_append(xop, div_tag, sizeof(div_tag) - 1); xo_data_escape(xop, name, nlen); /* * Save the offset at which we'd place units. See xo_format_units. */ if (XOF_ISSET(xop, XOF_UNITS)) { XOIF_SET(xop, XOIF_UNITS_PENDING); /* * Note: We need the '+1' here because we know we've not * added the closing quote. We add one, knowing the quote * will be added shortly. */ xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1; } if (XOF_ISSET(xop, XOF_XPATH)) { int i; xo_stack_t *xsp; xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1); if (xop->xo_leading_xpath) xo_data_append(xop, xop->xo_leading_xpath, strlen(xop->xo_leading_xpath)); for (i = 0; i <= xop->xo_depth; i++) { xsp = &xop->xo_stack[i]; if (xsp->xs_name == NULL) continue; /* * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames * are directly under XSS_OPEN_INSTANCE frames so we * don't need to put these in our XPath expressions. */ if (xsp->xs_state == XSS_OPEN_LIST || xsp->xs_state == XSS_OPEN_LEAF_LIST) continue; xo_data_append(xop, "/", 1); xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name)); if (xsp->xs_keys) { /* Don't show keys for the key field */ if (i != xop->xo_depth || !(flags & XFF_KEY)) xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys)); } } xo_data_append(xop, "/", 1); xo_data_escape(xop, name, nlen); } if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) { static char in_type[] = "\" data-type=\""; static char in_help[] = "\" data-help=\""; xo_info_t *xip = xo_info_find(xop, name, nlen); if (xip) { if (xip->xi_type) { xo_data_append(xop, in_type, sizeof(in_type) - 1); xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type)); } if (xip->xi_help) { xo_data_append(xop, in_help, sizeof(in_help) - 1); xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help)); } } } if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) xo_data_append(xop, div_key, sizeof(div_key) - 1); } xo_buffer_t *xbp = &xop->xo_data; ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp; xo_data_append(xop, div_end, sizeof(div_end) - 1); xo_humanize_save_t save; /* Save values for humanizing logic */ save.xhs_offset = xbp->xb_curp - xbp->xb_bufp; save.xhs_columns = xop->xo_columns; save.xhs_anchor_columns = xop->xo_anchor_columns; xo_do_format_field(xop, NULL, value, vlen, flags); if (flags & XFF_HUMANIZE) { /* * Unlike text style, we want to retain the original value and * stuff it into the "data-number" attribute. */ static const char div_number[] = "\" data-number=\""; ssize_t div_len = sizeof(div_number) - 1; ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp; ssize_t olen = end_offset - save.xhs_offset; char *cp = alloca(olen + 1); memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen); cp[olen] = '\0'; xo_format_humanize(xop, xbp, &save, flags); if (xo_buf_has_room(xbp, div_len + olen)) { ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp; /* Move the humanized string off to the left */ memmove(xbp->xb_bufp + base_offset + div_len + olen, xbp->xb_bufp + base_offset, new_offset - base_offset); /* Copy the data_number attribute name */ memcpy(xbp->xb_bufp + base_offset, div_number, div_len); /* Copy the original long value */ memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen); xbp->xb_curp += div_len + olen; } } xo_data_append(xop, div_close, sizeof(div_close) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); } static void xo_format_text (xo_handle_t *xop, const char *str, ssize_t len) { switch (xo_style(xop)) { case XO_STYLE_TEXT: xo_buf_append_locale(xop, &xop->xo_data, str, len); break; case XO_STYLE_HTML: xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0); break; } } static void xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { const char *fmt = xfip->xfi_format; ssize_t flen = xfip->xfi_flen; xo_xff_flags_t flags = xfip->xfi_flags; static char div_open[] = "
"; static char div_close[] = "
"; if (flen == 0) { fmt = "%s"; flen = 2; } switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_JSON: case XO_STYLE_SDPARAMS: case XO_STYLE_ENCODER: /* * Even though we don't care about text, we need to do * enough parsing work to skip over the right bits of xo_vap. */ if (len == 0) xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT); return; } xo_buffer_t *xbp = &xop->xo_data; ssize_t start = xbp->xb_curp - xbp->xb_bufp; ssize_t left = xbp->xb_size - start; ssize_t rc; if (xo_style(xop) == XO_STYLE_HTML) { xo_line_ensure_open(xop, 0); if (XOF_ISSET(xop, XOF_PRETTY)) xo_buf_indent(xop, xop->xo_indent_by); xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1); xo_color_append_html(xop); xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1); } start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */ if (len) { char *newfmt = alloca(flen + 1); memcpy(newfmt, fmt, flen); newfmt[flen] = '\0'; /* If len is non-zero, the format string apply to the name */ char *newstr = alloca(len + 1); memcpy(newstr, str, len); newstr[len] = '\0'; if (newstr[len - 1] == 's') { char *bp; rc = snprintf(NULL, 0, newfmt, newstr); if (rc > 0) { /* * We have to do this the hard way, since we might need * the columns. */ bp = alloca(rc + 1); rc = snprintf(bp, rc + 1, newfmt, newstr); xo_data_append_content(xop, bp, rc, flags); } goto move_along; } else { rc = snprintf(xbp->xb_curp, left, newfmt, newstr); if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) return; left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); rc = snprintf(xbp->xb_curp, left, newfmt, newstr); } if (rc > 0) { if (XOF_ISSET(xop, XOF_COLUMNS)) xop->xo_columns += rc; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xop->xo_anchor_columns += rc; } } } else { xo_do_format_field(xop, NULL, fmt, flen, flags); /* xo_do_format_field moved curp, so we need to reset it */ rc = xbp->xb_curp - (xbp->xb_bufp + start); xbp->xb_curp = xbp->xb_bufp + start; } /* If we're styling HTML, then we need to escape it */ if (xo_style(xop) == XO_STYLE_HTML) { rc = xo_escape_xml(xbp, rc, 0); } if (rc > 0) xbp->xb_curp += rc; move_along: if (xo_style(xop) == XO_STYLE_HTML) { xo_data_append(xop, div_close, sizeof(div_close) - 1); if (XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); } } static void xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags) { if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) { xo_data_append(xop, ",", 1); if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY)) xo_data_append(xop, "\n", 1); } else xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; } #if 0 /* Useful debugging function */ void xo_arg (xo_handle_t *xop); void xo_arg (xo_handle_t *xop) { xop = xo_default(xop); fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned)); } #endif /* 0 */ static void xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen, const char *format, ssize_t flen, const char *encoding, ssize_t elen, xo_xff_flags_t flags) { int pretty = XOF_ISSET(xop, XOF_PRETTY); int quote; /* * Before we emit a value, we need to know that the frame is ready. */ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; if (flags & XFF_LEAF_LIST) { /* * Check if we've already started to emit normal leafs * or if we're not in a leaf list. */ if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY)) || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) { char nbuf[nlen + 1]; memcpy(nbuf, name, nlen); nbuf[nlen] = '\0'; ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST); if (rc < 0) flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; else xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST; } xsp = &xop->xo_stack[xop->xo_depth]; if (xsp->xs_name) { name = xsp->xs_name; nlen = strlen(name); } } else if (flags & XFF_KEY) { /* Emitting a 'k' (key) field */ if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) { xo_failure(xop, "key field emitted after normal value field: '%.*s'", nlen, name); } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) { char nbuf[nlen + 1]; memcpy(nbuf, name, nlen); nbuf[nlen] = '\0'; ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT); if (rc < 0) flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; else xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY; xsp = &xop->xo_stack[xop->xo_depth]; xsp->xs_flags |= XSF_EMIT_KEY; } } else { /* Emitting a normal value field */ if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST) || !(xsp->xs_flags & XSF_EMIT)) { char nbuf[nlen + 1]; memcpy(nbuf, name, nlen); nbuf[nlen] = '\0'; ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT); if (rc < 0) flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY; else xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT; xsp = &xop->xo_stack[xop->xo_depth]; xsp->xs_flags |= XSF_EMIT; } } xo_buffer_t *xbp = &xop->xo_data; xo_humanize_save_t save; /* Save values for humanizing logic */ switch (xo_style(xop)) { case XO_STYLE_TEXT: if (flags & XFF_ENCODE_ONLY) flags |= XFF_NO_OUTPUT; save.xhs_offset = xbp->xb_curp - xbp->xb_bufp; save.xhs_columns = xop->xo_columns; save.xhs_anchor_columns = xop->xo_anchor_columns; xo_do_format_field(xop, NULL, format, flen, flags); if (flags & XFF_HUMANIZE) xo_format_humanize(xop, xbp, &save, flags); break; case XO_STYLE_HTML: if (flags & XFF_ENCODE_ONLY) flags |= XFF_NO_OUTPUT; xo_buf_append_div(xop, "data", flags, name, nlen, format, flen, encoding, elen); break; case XO_STYLE_XML: /* * Even though we're not making output, we still need to * let the formatting code handle the va_arg popping. */ if (flags & XFF_DISPLAY_ONLY) { flags |= XFF_NO_OUTPUT; xo_do_format_field(xop, NULL, format, flen, flags); break; } if (encoding) { format = encoding; flen = elen; } else { char *enc = alloca(flen + 1); memcpy(enc, format, flen); enc[flen] = '\0'; format = xo_fix_encoding(xop, enc); flen = strlen(format); } if (nlen == 0) { static char missing[] = "missing-field-name"; xo_failure(xop, "missing field name: %s", format); name = missing; nlen = sizeof(missing) - 1; } if (pretty) xo_buf_indent(xop, -1); xo_data_append(xop, "<", 1); xo_data_escape(xop, name, nlen); if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { xo_data_append(xop, xop->xo_attrs.xb_bufp, xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; } /* * We indicate 'key' fields using the 'key' attribute. While * this is really committing the crime of mixing meta-data with * data, it's often useful. Especially when format meta-data is * difficult to come by. */ if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) { static char attr[] = " key=\"key\""; xo_data_append(xop, attr, sizeof(attr) - 1); } /* * Save the offset at which we'd place units. See xo_format_units. */ if (XOF_ISSET(xop, XOF_UNITS)) { XOIF_SET(xop, XOIF_UNITS_PENDING); xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp; } xo_data_append(xop, ">", 1); xo_do_format_field(xop, NULL, format, flen, flags); xo_data_append(xop, "", 1); if (pretty) xo_data_append(xop, "\n", 1); break; case XO_STYLE_JSON: if (flags & XFF_DISPLAY_ONLY) { flags |= XFF_NO_OUTPUT; xo_do_format_field(xop, NULL, format, flen, flags); break; } if (encoding) { format = encoding; flen = elen; } else { char *enc = alloca(flen + 1); memcpy(enc, format, flen); enc[flen] = '\0'; format = xo_fix_encoding(xop, enc); flen = strlen(format); } int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) ? 0 : 1; xo_format_prep(xop, flags); if (flags & XFF_QUOTE) quote = 1; else if (flags & XFF_NOQUOTE) quote = 0; else if (flen == 0) { quote = 0; format = "true"; /* JSON encodes empty tags as a boolean true */ flen = 4; } else if (strchr("diouDOUeEfFgG", format[flen - 1]) == NULL) quote = 1; else quote = 0; if (nlen == 0) { static char missing[] = "missing-field-name"; xo_failure(xop, "missing field name: %s", format); name = missing; nlen = sizeof(missing) - 1; } if (flags & XFF_LEAF_LIST) { if (!first && pretty) xo_data_append(xop, "\n", 1); if (pretty) xo_buf_indent(xop, -1); } else { if (pretty) xo_buf_indent(xop, -1); xo_data_append(xop, "\"", 1); xbp = &xop->xo_data; ssize_t off = xbp->xb_curp - xbp->xb_bufp; xo_data_escape(xop, name, nlen); if (XOF_ISSET(xop, XOF_UNDERSCORES)) { ssize_t coff = xbp->xb_curp - xbp->xb_bufp; for ( ; off < coff; off++) if (xbp->xb_bufp[off] == '-') xbp->xb_bufp[off] = '_'; } xo_data_append(xop, "\":", 2); if (pretty) xo_data_append(xop, " ", 1); } if (quote) xo_data_append(xop, "\"", 1); xo_do_format_field(xop, NULL, format, flen, flags); if (quote) xo_data_append(xop, "\"", 1); break; case XO_STYLE_SDPARAMS: if (flags & XFF_DISPLAY_ONLY) { flags |= XFF_NO_OUTPUT; xo_do_format_field(xop, NULL, format, flen, flags); break; } if (encoding) { format = encoding; flen = elen; } else { char *enc = alloca(flen + 1); memcpy(enc, format, flen); enc[flen] = '\0'; format = xo_fix_encoding(xop, enc); flen = strlen(format); } if (nlen == 0) { static char missing[] = "missing-field-name"; xo_failure(xop, "missing field name: %s", format); name = missing; nlen = sizeof(missing) - 1; } xo_data_escape(xop, name, nlen); xo_data_append(xop, "=\"", 2); xo_do_format_field(xop, NULL, format, flen, flags); xo_data_append(xop, "\" ", 2); break; case XO_STYLE_ENCODER: if (flags & XFF_DISPLAY_ONLY) { flags |= XFF_NO_OUTPUT; xo_do_format_field(xop, NULL, format, flen, flags); break; } if (flags & XFF_QUOTE) quote = 1; else if (flags & XFF_NOQUOTE) quote = 0; else if (flen == 0) { quote = 0; format = "true"; /* JSON encodes empty tags as a boolean true */ flen = 4; } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL) quote = 1; else quote = 0; if (encoding) { format = encoding; flen = elen; } else { char *enc = alloca(flen + 1); memcpy(enc, format, flen); enc[flen] = '\0'; format = xo_fix_encoding(xop, enc); flen = strlen(format); } if (nlen == 0) { static char missing[] = "missing-field-name"; xo_failure(xop, "missing field name: %s", format); name = missing; nlen = sizeof(missing) - 1; } ssize_t name_offset = xo_buf_offset(&xop->xo_data); xo_data_append(xop, name, nlen); xo_data_append(xop, "", 1); ssize_t value_offset = xo_buf_offset(&xop->xo_data); xo_do_format_field(xop, NULL, format, flen, flags); xo_data_append(xop, "", 1); xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT, xo_buf_data(&xop->xo_data, name_offset), xo_buf_data(&xop->xo_data, value_offset), flags); xo_buf_reset(&xop->xo_data); break; } } static void xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { const char *fmt = xfip->xfi_format; ssize_t flen = xfip->xfi_flen; /* Start by discarding previous domain */ if (xop->xo_gt_domain) { xo_free(xop->xo_gt_domain); xop->xo_gt_domain = NULL; } /* An empty {G:} means no domainname */ if (len == 0 && flen == 0) return; ssize_t start_offset = -1; if (len == 0 && flen != 0) { /* Need to do format the data to get the domainname from args */ start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp; xo_do_format_field(xop, NULL, fmt, flen, 0); ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp; len = end_offset - start_offset; str = xop->xo_data.xb_bufp + start_offset; } xop->xo_gt_domain = xo_strndup(str, len); /* Reset the current buffer point to avoid emitting the name as output */ if (start_offset >= 0) xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset; } static void xo_format_content (xo_handle_t *xop, const char *class_name, const char *tag_name, const char *str, ssize_t len, const char *fmt, ssize_t flen, xo_xff_flags_t flags) { switch (xo_style(xop)) { case XO_STYLE_TEXT: if (len) xo_data_append_content(xop, str, len, flags); else xo_do_format_field(xop, NULL, fmt, flen, flags); break; case XO_STYLE_HTML: if (len == 0) { str = fmt; len = flen; } xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0); break; case XO_STYLE_XML: case XO_STYLE_JSON: case XO_STYLE_SDPARAMS: if (tag_name) { if (len == 0) { str = fmt; len = flen; } xo_open_container_h(xop, tag_name); xo_format_value(xop, "message", 7, str, len, NULL, 0, flags); xo_close_container_h(xop, tag_name); } else { /* * Even though we don't care about labels, we need to do * enough parsing work to skip over the right bits of xo_vap. */ if (len == 0) xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT); } break; case XO_STYLE_ENCODER: if (len == 0) xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT); break; } } static const char *xo_color_names[] = { "default", /* XO_COL_DEFAULT */ "black", /* XO_COL_BLACK */ "red", /* XO_CLOR_RED */ "green", /* XO_COL_GREEN */ "yellow", /* XO_COL_YELLOW */ "blue", /* XO_COL_BLUE */ "magenta", /* XO_COL_MAGENTA */ "cyan", /* XO_COL_CYAN */ "white", /* XO_COL_WHITE */ NULL }; static int xo_color_find (const char *str) { int i; for (i = 0; xo_color_names[i]; i++) { if (strcmp(xo_color_names[i], str) == 0) return i; } return -1; } static const char *xo_effect_names[] = { "reset", /* XO_EFF_RESET */ "normal", /* XO_EFF_NORMAL */ "bold", /* XO_EFF_BOLD */ "underline", /* XO_EFF_UNDERLINE */ "inverse", /* XO_EFF_INVERSE */ NULL }; static const char *xo_effect_on_codes[] = { "0", /* XO_EFF_RESET */ "0", /* XO_EFF_NORMAL */ "1", /* XO_EFF_BOLD */ "4", /* XO_EFF_UNDERLINE */ "7", /* XO_EFF_INVERSE */ NULL }; #if 0 /* * See comment below re: joy of terminal standards. These can * be use by just adding: * + if (newp->xoc_effects & bit) * code = xo_effect_on_codes[i]; * + else * + code = xo_effect_off_codes[i]; * in xo_color_handle_text. */ static const char *xo_effect_off_codes[] = { "0", /* XO_EFF_RESET */ "0", /* XO_EFF_NORMAL */ "21", /* XO_EFF_BOLD */ "24", /* XO_EFF_UNDERLINE */ "27", /* XO_EFF_INVERSE */ NULL }; #endif /* 0 */ static int xo_effect_find (const char *str) { int i; for (i = 0; xo_effect_names[i]; i++) { if (strcmp(xo_effect_names[i], str) == 0) return i; } return -1; } static void xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str) { #ifdef LIBXO_TEXT_ONLY return; #endif /* LIBXO_TEXT_ONLY */ char *cp, *ep, *np, *xp; ssize_t len = strlen(str); int rc; /* * Possible tokens: colors, bg-colors, effects, no-effects, "reset". */ for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) { /* Trim leading whitespace */ while (isspace((int) *cp)) cp += 1; np = strchr(cp, ','); if (np) *np++ = '\0'; /* Trim trailing whitespace */ xp = cp + strlen(cp) - 1; while (isspace(*xp) && xp > cp) *xp-- = '\0'; if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') { rc = xo_color_find(cp + 3); if (rc < 0) goto unknown; xocp->xoc_col_fg = rc; } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') { rc = xo_color_find(cp + 3); if (rc < 0) goto unknown; xocp->xoc_col_bg = rc; } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') { rc = xo_effect_find(cp + 3); if (rc < 0) goto unknown; xocp->xoc_effects &= ~(1 << rc); } else { rc = xo_effect_find(cp); if (rc < 0) goto unknown; xocp->xoc_effects |= 1 << rc; switch (1 << rc) { case XO_EFF_RESET: xocp->xoc_col_fg = xocp->xoc_col_bg = 0; /* Note: not "|=" since we want to wipe out the old value */ xocp->xoc_effects = XO_EFF_RESET; break; case XO_EFF_NORMAL: xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE | XO_EFF_INVERSE | XO_EFF_NORMAL); break; } } continue; unknown: if (XOF_ISSET(xop, XOF_WARN)) xo_failure(xop, "unknown color/effect string detected: '%s'", cp); } } static inline int xo_colors_enabled (xo_handle_t *xop UNUSED) { #ifdef LIBXO_TEXT_ONLY return 0; #else /* LIBXO_TEXT_ONLY */ return XOF_ISSET(xop, XOF_COLOR); #endif /* LIBXO_TEXT_ONLY */ } /* * If the color map is in use (--libxo colors=xxxx), then update * the incoming foreground and background colors from the map. */ static void xo_colors_update (xo_handle_t *xop, xo_colors_t *newp) { #ifdef LIBXO_TEXT_ONLY return; #endif /* LIBXO_TEXT_ONLY */ xo_color_t fg = newp->xoc_col_fg; if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS) fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */ newp->xoc_col_fg = fg; xo_color_t bg = newp->xoc_col_bg; if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS) bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */ newp->xoc_col_bg = bg; } static void xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp) { char buf[BUFSIZ]; char *cp = buf, *ep = buf + sizeof(buf); unsigned i, bit; xo_colors_t *oldp = &xop->xo_colors; const char *code = NULL; /* * Start the buffer with an escape. We don't want to add the '[' * now, since we let xo_effect_text_add unconditionally add the ';'. * We'll replace the first ';' with a '[' when we're done. */ *cp++ = 0x1b; /* Escape */ /* * Terminals were designed back in the age before "certainty" was * invented, when standards were more what you'd call "guidelines" * than actual rules. Anyway we can't depend on them to operate * correctly. So when display attributes are changed, we punt, * reseting them all and turning back on the ones we want to keep. * Longer, but should be completely reliable. Savvy? */ if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) { newp->xoc_effects |= XO_EFF_RESET; oldp->xoc_effects = 0; } for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) { if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit)) continue; code = xo_effect_on_codes[i]; cp += snprintf(cp, ep - cp, ";%s", code); if (cp >= ep) return; /* Should not occur */ if (bit == XO_EFF_RESET) { /* Mark up the old value so we can detect current values as new */ oldp->xoc_effects = 0; oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT; } } xo_color_t fg = newp->xoc_col_fg; if (fg != oldp->xoc_col_fg) { cp += snprintf(cp, ep - cp, ";3%u", (fg != XO_COL_DEFAULT) ? fg - 1 : 9); } xo_color_t bg = newp->xoc_col_bg; if (bg != oldp->xoc_col_bg) { cp += snprintf(cp, ep - cp, ";4%u", (bg != XO_COL_DEFAULT) ? bg - 1 : 9); } if (cp - buf != 1 && cp < ep - 3) { buf[1] = '['; /* Overwrite leading ';' */ *cp++ = 'm'; *cp = '\0'; xo_buf_append(&xop->xo_data, buf, cp - buf); } } static void xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp) { xo_colors_t *oldp = &xop->xo_colors; /* * HTML colors are mostly trivial: fill in xo_color_buf with * a set of class tags representing the colors and effects. */ /* If nothing changed, then do nothing */ if (oldp->xoc_effects == newp->xoc_effects && oldp->xoc_col_fg == newp->xoc_col_fg && oldp->xoc_col_bg == newp->xoc_col_bg) return; unsigned i, bit; xo_buffer_t *xbp = &xop->xo_color_buf; xo_buf_reset(xbp); /* We rebuild content after each change */ for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) { if (!(newp->xoc_effects & bit)) continue; xo_buf_append_str(xbp, " effect-"); xo_buf_append_str(xbp, xo_effect_names[i]); } const char *fg = NULL; const char *bg = NULL; if (newp->xoc_col_fg != XO_COL_DEFAULT) fg = xo_color_names[newp->xoc_col_fg]; if (newp->xoc_col_bg != XO_COL_DEFAULT) bg = xo_color_names[newp->xoc_col_bg]; if (newp->xoc_effects & XO_EFF_INVERSE) { const char *tmp = fg; fg = bg; bg = tmp; if (fg == NULL) fg = "inverse"; if (bg == NULL) bg = "inverse"; } if (fg) { xo_buf_append_str(xbp, " color-fg-"); xo_buf_append_str(xbp, fg); } if (bg) { xo_buf_append_str(xbp, " color-bg-"); xo_buf_append_str(xbp, bg); } } static void xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { const char *fmt = xfip->xfi_format; ssize_t flen = xfip->xfi_flen; xo_buffer_t xb; /* If the string is static and we've in an encoding style, bail */ if (len != 0 && xo_style_is_encoding(xop)) return; xo_buf_init(&xb); if (len) xo_buf_append(&xb, str, len); else if (flen) xo_do_format_field(xop, &xb, fmt, flen, 0); else xo_buf_append(&xb, "reset", 6); /* Default if empty */ if (xo_colors_enabled(xop)) { switch (xo_style(xop)) { case XO_STYLE_TEXT: case XO_STYLE_HTML: xo_buf_append(&xb, "", 1); xo_colors_t xoc = xop->xo_colors; xo_colors_parse(xop, &xoc, xb.xb_bufp); xo_colors_update(xop, &xoc); if (xo_style(xop) == XO_STYLE_TEXT) { /* * Text mode means emitting the colors as ANSI character * codes. This will allow people who like colors to have * colors. The issue is, of course conflicting with the * user's perfectly reasonable color scheme. Which leads * to the hell of LSCOLORS, where even app need to have * customization hooks for adjusting colors. Instead we * provide a simpler-but-still-annoying answer where one * can map colors to other colors. */ xo_colors_handle_text(xop, &xoc); xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */ } else { /* * HTML output is wrapped in divs, so the color information * must appear in every div until cleared. Most pathetic. * Most unavoidable. */ xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */ xo_colors_handle_html(xop, &xoc); } xop->xo_colors = xoc; break; case XO_STYLE_XML: case XO_STYLE_JSON: case XO_STYLE_SDPARAMS: case XO_STYLE_ENCODER: /* * Nothing to do; we did all that work just to clear the stack of * formatting arguments. */ break; } } xo_buf_cleanup(&xb); } static void xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { const char *fmt = xfip->xfi_format; ssize_t flen = xfip->xfi_flen; xo_xff_flags_t flags = xfip->xfi_flags; static char units_start_xml[] = " units=\""; static char units_start_html[] = " data-units=\""; if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) { xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags); return; } xo_buffer_t *xbp = &xop->xo_data; ssize_t start = xop->xo_units_offset; ssize_t stop = xbp->xb_curp - xbp->xb_bufp; if (xo_style(xop) == XO_STYLE_XML) xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1); else if (xo_style(xop) == XO_STYLE_HTML) xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1); else return; if (len) xo_data_escape(xop, str, len); else xo_do_format_field(xop, NULL, fmt, flen, flags); xo_buf_append(xbp, "\"", 1); ssize_t now = xbp->xb_curp - xbp->xb_bufp; ssize_t delta = now - stop; if (delta <= 0) { /* Strange; no output to move */ xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */ return; } /* * Now we're in it alright. We've need to insert the unit value * we just created into the right spot. We make a local copy, * move it and then insert our copy. We know there's room in the * buffer, since we're just moving this around. */ char *buf = alloca(delta); memcpy(buf, xbp->xb_bufp + stop, delta); memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); memmove(xbp->xb_bufp + start, buf, delta); } static ssize_t xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { const char *fmt = xfip->xfi_format; ssize_t flen = xfip->xfi_flen; long width = 0; char *bp; char *cp; if (len) { bp = alloca(len + 1); /* Make local NUL-terminated copy of str */ memcpy(bp, str, len); bp[len] = '\0'; width = strtol(bp, &cp, 0); if (width == LONG_MIN || width == LONG_MAX || bp == cp || *cp != '\0' ) { width = 0; xo_failure(xop, "invalid width for anchor: '%s'", bp); } } else if (flen) { if (flen != 2 || strncmp("%d", fmt, flen) != 0) xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt); if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) width = va_arg(xop->xo_vap, int); } return width; } static void xo_anchor_clear (xo_handle_t *xop) { XOIF_CLEAR(xop, XOIF_ANCHOR); xop->xo_anchor_offset = 0; xop->xo_anchor_columns = 0; xop->xo_anchor_min_width = 0; } /* * An anchor is a marker used to delay field width implications. * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}". * We are looking for output like " 1/4/5" * * To make this work, we record the anchor and then return to * format it when the end anchor tag is seen. */ static void xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) return; if (XOIF_ISSET(xop, XOIF_ANCHOR)) xo_failure(xop, "the anchor already recording is discarded"); XOIF_SET(xop, XOIF_ANCHOR); xo_buffer_t *xbp = &xop->xo_data; xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp; xop->xo_anchor_columns = 0; /* * Now we find the width, if possible. If it's not there, * we'll get it on the end anchor. */ xop->xo_anchor_min_width = xo_find_width(xop, xfip, str, len); } static void xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip, const char *str, ssize_t len) { if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) return; if (!XOIF_ISSET(xop, XOIF_ANCHOR)) { xo_failure(xop, "no start anchor"); return; } XOIF_CLEAR(xop, XOIF_UNITS_PENDING); ssize_t width = xo_find_width(xop, xfip, str, len); if (width == 0) width = xop->xo_anchor_min_width; if (width == 0) /* No width given; nothing to do */ goto done; xo_buffer_t *xbp = &xop->xo_data; ssize_t start = xop->xo_anchor_offset; ssize_t stop = xbp->xb_curp - xbp->xb_bufp; ssize_t abswidth = (width > 0) ? width : -width; ssize_t blen = abswidth - xop->xo_anchor_columns; if (blen <= 0) /* Already over width */ goto done; if (abswidth > XO_MAX_ANCHOR_WIDTH) { xo_failure(xop, "width over %u are not supported", XO_MAX_ANCHOR_WIDTH); goto done; } /* Make a suitable padding field and emit it */ char *buf = alloca(blen); memset(buf, ' ', blen); xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0); if (width < 0) /* Already left justified */ goto done; ssize_t now = xbp->xb_curp - xbp->xb_bufp; ssize_t delta = now - stop; if (delta <= 0) /* Strange; no output to move */ goto done; /* * Now we're in it alright. We've need to insert the padding data * we just created (which might be an HTML
or text) before * the formatted data. We make a local copy, move it and then * insert our copy. We know there's room in the buffer, since * we're just moving this around. */ if (delta > blen) buf = alloca(delta); /* Expand buffer if needed */ memcpy(buf, xbp->xb_bufp + stop, delta); memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start); memmove(xbp->xb_bufp + start, buf, delta); done: xo_anchor_clear(xop); } static const char * xo_class_name (int ftype) { switch (ftype) { case 'D': return "decoration"; case 'E': return "error"; case 'L': return "label"; case 'N': return "note"; case 'P': return "padding"; case 'W': return "warning"; } return NULL; } static const char * xo_tag_name (int ftype) { switch (ftype) { case 'E': return "__error"; case 'W': return "__warning"; } return NULL; } static int xo_role_wants_default_format (int ftype) { switch (ftype) { /* These roles can be completely empty and/or without formatting */ case 'C': case 'G': case '[': case ']': return 0; } return 1; } static xo_mapping_t xo_role_names[] = { { 'C', "color" }, { 'D', "decoration" }, { 'E', "error" }, { 'L', "label" }, { 'N', "note" }, { 'P', "padding" }, { 'T', "title" }, { 'U', "units" }, { 'V', "value" }, { 'W', "warning" }, { '[', "start-anchor" }, { ']', "stop-anchor" }, { 0, NULL } }; #define XO_ROLE_EBRACE '{' /* Escaped braces */ #define XO_ROLE_TEXT '+' #define XO_ROLE_NEWLINE '\n' static xo_mapping_t xo_modifier_names[] = { { XFF_ARGUMENT, "argument" }, { XFF_COLON, "colon" }, { XFF_COMMA, "comma" }, { XFF_DISPLAY_ONLY, "display" }, { XFF_ENCODE_ONLY, "encoding" }, { XFF_GT_FIELD, "gettext" }, { XFF_HUMANIZE, "humanize" }, { XFF_HUMANIZE, "hn" }, { XFF_HN_SPACE, "hn-space" }, { XFF_HN_DECIMAL, "hn-decimal" }, { XFF_HN_1000, "hn-1000" }, { XFF_KEY, "key" }, { XFF_LEAF_LIST, "leaf-list" }, { XFF_LEAF_LIST, "list" }, { XFF_NOQUOTE, "no-quotes" }, { XFF_NOQUOTE, "no-quote" }, { XFF_GT_PLURAL, "plural" }, { XFF_QUOTE, "quotes" }, { XFF_QUOTE, "quote" }, { XFF_TRIM_WS, "trim" }, { XFF_WS, "white" }, { 0, NULL } }; #ifdef NOT_NEEDED_YET static xo_mapping_t xo_modifier_short_names[] = { { XFF_COLON, "c" }, { XFF_DISPLAY_ONLY, "d" }, { XFF_ENCODE_ONLY, "e" }, { XFF_GT_FIELD, "g" }, { XFF_HUMANIZE, "h" }, { XFF_KEY, "k" }, { XFF_LEAF_LIST, "l" }, { XFF_NOQUOTE, "n" }, { XFF_GT_PLURAL, "p" }, { XFF_QUOTE, "q" }, { XFF_TRIM_WS, "t" }, { XFF_WS, "w" }, { 0, NULL } }; #endif /* NOT_NEEDED_YET */ static int xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt) { int rc = 1; const char *cp; for (cp = fmt; *cp; cp++) if (*cp == '{' || *cp == '\n') rc += 1; return rc * 2 + 1; } /* * The field format is: * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}' * Roles are optional and include the following field types: * 'D': decoration; something non-text and non-data (colons, commmas) * 'E': error message * 'G': gettext() the entire string; optional domainname as content * 'L': label; text preceding data * 'N': note; text following data * 'P': padding; whitespace * 'T': Title, where 'content' is a column title * 'U': Units, where 'content' is the unit label * 'V': value, where 'content' is the name of the field (the default) * 'W': warning message * '[': start a section of anchored text * ']': end a section of anchored text * The following modifiers are also supported: * 'a': content is provided via argument (const char *), not descriptor * 'c': flag: emit a colon after the label * 'd': field is only emitted for display styles (text and html) * 'e': field is only emitted for encoding styles (xml and json) * 'g': gettext() the field * 'h': humanize a numeric value (only for display styles) * 'k': this field is a key, suitable for XPath predicates * 'l': a leaf-list, a simple list of values * 'n': no quotes around this field * 'p': the field has plural gettext semantics (ngettext) * 'q': add quotes around this field * 't': trim whitespace around the value * 'w': emit a blank after the label * The print-fmt and encode-fmt strings is the printf-style formating * for this data. JSON and XML will use the encoding-fmt, if present. * If the encode-fmt is not provided, it defaults to the print-fmt. * If the print-fmt is not provided, it defaults to 's'. */ static const char * xo_parse_roles (xo_handle_t *xop, const char *fmt, const char *basep, xo_field_info_t *xfip) { const char *sp; unsigned ftype = 0; xo_xff_flags_t flags = 0; uint8_t fnum = 0; for (sp = basep; sp && *sp; sp++) { if (*sp == ':' || *sp == '/' || *sp == '}') break; if (*sp == '\\') { if (sp[1] == '\0') { xo_failure(xop, "backslash at the end of string"); return NULL; } /* Anything backslashed is ignored */ sp += 1; continue; } if (*sp == ',') { const char *np; for (np = ++sp; *np; np++) if (*np == ':' || *np == '/' || *np == '}' || *np == ',') break; ssize_t slen = np - sp; if (slen > 0) { xo_xff_flags_t value; value = xo_name_lookup(xo_role_names, sp, slen); if (value) ftype = value; else { value = xo_name_lookup(xo_modifier_names, sp, slen); if (value) flags |= value; else xo_failure(xop, "unknown keyword ignored: '%.*s'", slen, sp); } } sp = np - 1; continue; } switch (*sp) { case 'C': case 'D': case 'E': case 'G': case 'L': case 'N': case 'P': case 'T': case 'U': case 'V': case 'W': case '[': case ']': if (ftype != 0) { xo_failure(xop, "field descriptor uses multiple types: '%s'", xo_printable(fmt)); return NULL; } ftype = *sp; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': fnum = (fnum * 10) + (*sp - '0'); break; case 'a': flags |= XFF_ARGUMENT; break; case 'c': flags |= XFF_COLON; break; case 'd': flags |= XFF_DISPLAY_ONLY; break; case 'e': flags |= XFF_ENCODE_ONLY; break; case 'g': flags |= XFF_GT_FIELD; break; case 'h': flags |= XFF_HUMANIZE; break; case 'k': flags |= XFF_KEY; break; case 'l': flags |= XFF_LEAF_LIST; break; case 'n': flags |= XFF_NOQUOTE; break; case 'p': flags |= XFF_GT_PLURAL; break; case 'q': flags |= XFF_QUOTE; break; case 't': flags |= XFF_TRIM_WS; break; case 'w': flags |= XFF_WS; break; default: xo_failure(xop, "field descriptor uses unknown modifier: '%s'", xo_printable(fmt)); /* * No good answer here; a bad format will likely * mean a core file. We just return and hope * the caller notices there's no output, and while * that seems, well, bad, there's nothing better. */ return NULL; } if (ftype == 'N' || ftype == 'U') { if (flags & XFF_COLON) { xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: " "'%s'", xo_printable(fmt)); flags &= ~XFF_COLON; } } } xfip->xfi_flags = flags; xfip->xfi_ftype = ftype ?: 'V'; xfip->xfi_fnum = fnum; return sp; } /* * Number any remaining fields that need numbers. Note that some * field types (text, newline, escaped braces) never get numbers. */ static void xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED, const char *fmt UNUSED, xo_field_info_t *fields) { xo_field_info_t *xfip; unsigned fnum, max_fields; uint64_t bits = 0; /* First make a list of add the explicitly used bits */ for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) { switch (xfip->xfi_ftype) { case XO_ROLE_NEWLINE: /* Don't get numbered */ case XO_ROLE_TEXT: case XO_ROLE_EBRACE: case 'G': continue; } fnum += 1; if (fnum >= 63) break; if (xfip->xfi_fnum) bits |= 1 << xfip->xfi_fnum; } max_fields = fnum; for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) { switch (xfip->xfi_ftype) { case XO_ROLE_NEWLINE: /* Don't get numbered */ case XO_ROLE_TEXT: case XO_ROLE_EBRACE: case 'G': continue; } if (xfip->xfi_fnum != 0) continue; /* Find the next unassigned field */ for (fnum++; bits & (1 << fnum); fnum++) continue; if (fnum > max_fields) break; xfip->xfi_fnum = fnum; /* Mark the field number */ bits |= 1 << fnum; /* Mark it used */ } } /* * The format string uses field numbers, so we need to whiffle through it * and make sure everything's sane and lovely. */ static int xo_parse_field_numbers (xo_handle_t *xop, const char *fmt, xo_field_info_t *fields, unsigned num_fields) { xo_field_info_t *xfip; unsigned field, fnum; uint64_t bits = 0; for (xfip = fields, field = 0; field < num_fields; xfip++, field++) { /* Fields default to 1:1 with natural position */ if (xfip->xfi_fnum == 0) xfip->xfi_fnum = field + 1; else if (xfip->xfi_fnum > num_fields) { xo_failure(xop, "field number exceeds number of fields: '%s'", fmt); return -1; } fnum = xfip->xfi_fnum - 1; /* Move to zero origin */ if (fnum < 64) { /* Only test what fits */ if (bits & (1 << fnum)) { xo_failure(xop, "field number %u reused: '%s'", xfip->xfi_fnum, fmt); return -1; } bits |= 1 << fnum; } } return 0; } static int xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields, unsigned num_fields, const char *fmt) { const char *cp, *sp, *ep, *basep; unsigned field = 0; xo_field_info_t *xfip = fields; unsigned seen_fnum = 0; for (cp = fmt; *cp && field < num_fields; field++, xfip++) { xfip->xfi_start = cp; if (*cp == '\n') { xfip->xfi_ftype = XO_ROLE_NEWLINE; xfip->xfi_len = 1; cp += 1; continue; } if (*cp != '{') { /* Normal text */ for (sp = cp; *sp; sp++) { if (*sp == '{' || *sp == '\n') break; } xfip->xfi_ftype = XO_ROLE_TEXT; xfip->xfi_content = cp; xfip->xfi_clen = sp - cp; xfip->xfi_next = sp; cp = sp; continue; } if (cp[1] == '{') { /* Start of {{escaped braces}} */ xfip->xfi_start = cp + 1; /* Start at second brace */ xfip->xfi_ftype = XO_ROLE_EBRACE; cp += 2; /* Skip over _both_ characters */ for (sp = cp; *sp; sp++) { if (*sp == '}' && sp[1] == '}') break; } if (*sp == '\0') { xo_failure(xop, "missing closing '}}': '%s'", xo_printable(fmt)); return -1; } xfip->xfi_len = sp - xfip->xfi_start + 1; /* Move along the string, but don't run off the end */ if (*sp == '}' && sp[1] == '}') sp += 2; cp = *sp ? sp : sp; xfip->xfi_next = cp; continue; } /* We are looking at the start of a field definition */ xfip->xfi_start = basep = cp + 1; const char *format = NULL; ssize_t flen = 0; /* Looking at roles and modifiers */ sp = xo_parse_roles(xop, fmt, basep, xfip); if (sp == NULL) { /* xo_failure has already been called */ return -1; } if (xfip->xfi_fnum) seen_fnum = 1; /* Looking at content */ if (*sp == ':') { for (ep = ++sp; *sp; sp++) { if (*sp == '}' || *sp == '/') break; if (*sp == '\\') { if (sp[1] == '\0') { xo_failure(xop, "backslash at the end of string"); return -1; } sp += 1; continue; } } if (ep != sp) { xfip->xfi_clen = sp - ep; xfip->xfi_content = ep; } } else { xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt)); return -1; } /* Looking at main (display) format */ if (*sp == '/') { for (ep = ++sp; *sp; sp++) { if (*sp == '}' || *sp == '/') break; if (*sp == '\\') { if (sp[1] == '\0') { xo_failure(xop, "backslash at the end of string"); return -1; } sp += 1; continue; } } flen = sp - ep; format = ep; } /* Looking at encoding format */ if (*sp == '/') { for (ep = ++sp; *sp; sp++) { if (*sp == '}') break; } xfip->xfi_encoding = ep; xfip->xfi_elen = sp - ep; } if (*sp != '}') { xo_failure(xop, "missing closing '}': %s", xo_printable(fmt)); return -1; } xfip->xfi_len = sp - xfip->xfi_start; xfip->xfi_next = ++sp; /* If we have content, then we have a default format */ if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) { if (format) { xfip->xfi_format = format; xfip->xfi_flen = flen; } else if (xo_role_wants_default_format(xfip->xfi_ftype)) { xfip->xfi_format = xo_default_format; xfip->xfi_flen = 2; } } cp = sp; } int rc = 0; /* * If we saw a field number on at least one field, then we need * to enforce some rules and/or guidelines. */ if (seen_fnum) rc = xo_parse_field_numbers(xop, fmt, fields, field); return rc; } /* * We are passed a pointer to a format string just past the "{G:}" * field. We build a simplified version of the format string. */ static int xo_gettext_simplify_format (xo_handle_t *xop UNUSED, xo_buffer_t *xbp, xo_field_info_t *fields, int this_field, const char *fmt UNUSED, xo_simplify_field_func_t field_cb) { unsigned ftype; xo_xff_flags_t flags; int field = this_field + 1; xo_field_info_t *xfip; char ch; for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) { ftype = xfip->xfi_ftype; flags = xfip->xfi_flags; if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') { if (field_cb) field_cb(xfip->xfi_content, xfip->xfi_clen, (flags & XFF_GT_PLURAL) ? 1 : 0); } switch (ftype) { case 'G': /* Ignore gettext roles */ break; case XO_ROLE_NEWLINE: xo_buf_append(xbp, "\n", 1); break; case XO_ROLE_EBRACE: xo_buf_append(xbp, "{", 1); xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); xo_buf_append(xbp, "}", 1); break; case XO_ROLE_TEXT: xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); break; default: xo_buf_append(xbp, "{", 1); if (ftype != 'V') { ch = ftype; xo_buf_append(xbp, &ch, 1); } unsigned fnum = xfip->xfi_fnum ?: 0; if (fnum) { char num[12]; /* Field numbers are origin 1, not 0, following printf(3) */ snprintf(num, sizeof(num), "%u", fnum); xo_buf_append(xbp, num, strlen(num)); } xo_buf_append(xbp, ":", 1); xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen); xo_buf_append(xbp, "}", 1); } } xo_buf_append(xbp, "", 1); return 0; } void xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */ void xo_dump_fields (xo_field_info_t *fields) { xo_field_info_t *xfip; for (xfip = fields; xfip->xfi_ftype; xfip++) { printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n", (unsigned long) (xfip - fields), xfip->xfi_fnum, (unsigned long) xfip->xfi_flags, isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ', xfip->xfi_ftype, (int) xfip->xfi_clen, xfip->xfi_content ?: "", (int) xfip->xfi_flen, xfip->xfi_format ?: "", (int) xfip->xfi_elen, xfip->xfi_encoding ?: ""); } } #ifdef HAVE_GETTEXT /* * Find the field that matches the given field number */ static xo_field_info_t * xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum) { xo_field_info_t *xfip; for (xfip = fields; xfip->xfi_ftype; xfip++) if (xfip->xfi_fnum == fnum) return xfip; return NULL; } /* * At this point, we need to consider if the fields have been reordered, * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}". * * We need to rewrite the new_fields using the old fields order, * so that we can render the message using the arguments as they * appear on the stack. It's a lot of work, but we don't really * want to (eventually) fall into the standard printf code which * means using the arguments straight (and in order) from the * varargs we were originally passed. */ static void xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED, xo_field_info_t *fields, unsigned max_fields) { xo_field_info_t tmp[max_fields]; bzero(tmp, max_fields * sizeof(tmp[0])); unsigned fnum = 0; xo_field_info_t *newp, *outp, *zp; for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) { switch (newp->xfi_ftype) { case XO_ROLE_NEWLINE: /* Don't get numbered */ case XO_ROLE_TEXT: case XO_ROLE_EBRACE: case 'G': *outp = *newp; outp->xfi_renum = 0; continue; } zp = xo_gettext_find_field(fields, ++fnum); if (zp == NULL) { /* Should not occur */ *outp = *newp; outp->xfi_renum = 0; continue; } *outp = *zp; outp->xfi_renum = newp->xfi_fnum; } memcpy(fields, tmp, max_fields * sizeof(tmp[0])); } /* * We've got two lists of fields, the old list from the original * format string and the new one from the parsed gettext reply. The * new list has the localized words, where the old list has the * formatting information. We need to combine them into a single list * (the new list). * * If the list needs to be reordered, then we've got more serious work * to do. */ static int xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED, const char *gtfmt, xo_field_info_t *old_fields, xo_field_info_t *new_fields, unsigned new_max_fields, int *reorderedp) { int reordered = 0; xo_field_info_t *newp, *oldp, *startp = old_fields; xo_gettext_finish_numbering_fields(xop, fmt, old_fields); for (newp = new_fields; newp->xfi_ftype; newp++) { switch (newp->xfi_ftype) { case XO_ROLE_NEWLINE: case XO_ROLE_TEXT: case XO_ROLE_EBRACE: continue; case 'V': for (oldp = startp; oldp->xfi_ftype; oldp++) { if (oldp->xfi_ftype != 'V') continue; if (newp->xfi_clen != oldp->xfi_clen || strncmp(newp->xfi_content, oldp->xfi_content, oldp->xfi_clen) != 0) { reordered = 1; continue; } startp = oldp + 1; break; } /* Didn't find it on the first pass (starting from start) */ if (oldp->xfi_ftype == 0) { for (oldp = old_fields; oldp < startp; oldp++) { if (oldp->xfi_ftype != 'V') continue; if (newp->xfi_clen != oldp->xfi_clen) continue; if (strncmp(newp->xfi_content, oldp->xfi_content, oldp->xfi_clen) != 0) continue; reordered = 1; break; } if (oldp == startp) { /* Field not found */ xo_failure(xop, "post-gettext format can't find field " "'%.*s' in format '%s'", newp->xfi_clen, newp->xfi_content, xo_printable(gtfmt)); return -1; } } break; default: /* * Other fields don't have names for us to use, so if * the types aren't the same, then we'll have to assume * the original field is a match. */ for (oldp = startp; oldp->xfi_ftype; oldp++) { if (oldp->xfi_ftype == 'V') /* Can't go past these */ break; if (oldp->xfi_ftype == newp->xfi_ftype) goto copy_it; /* Assumably we have a match */ } continue; } /* * Found a match; copy over appropriate fields */ copy_it: newp->xfi_flags = oldp->xfi_flags; newp->xfi_fnum = oldp->xfi_fnum; newp->xfi_format = oldp->xfi_format; newp->xfi_flen = oldp->xfi_flen; newp->xfi_encoding = oldp->xfi_encoding; newp->xfi_elen = oldp->xfi_elen; } *reorderedp = reordered; if (reordered) { xo_gettext_finish_numbering_fields(xop, fmt, new_fields); xo_gettext_rewrite_fields(xop, new_fields, new_max_fields); } return 0; } /* * We don't want to make gettext() calls here with a complete format * string, since that means changing a flag would mean a * labor-intensive re-translation expense. Instead we build a * simplified form with a reduced level of detail, perform a lookup on * that string and then re-insert the formating info. * * So something like: * xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...) * would have a lookup string of: * "close {:fd} returned {:error} {:test}\n" * * We also need to handling reordering of fields, where the gettext() * reply string uses fields in a different order than the original * format string: * "cluse-a {:fd} retoorned {:test}. Bork {:error} Bork. Bork.\n" * If we have to reorder fields within the message, then things get * complicated. See xo_gettext_rewrite_fields. * * Summary: i18n aighn't cheap. */ static const char * xo_gettext_build_format (xo_handle_t *xop, xo_field_info_t *fields, int this_field, const char *fmt, char **new_fmtp) { if (xo_style_is_encoding(xop)) goto bail; xo_buffer_t xb; xo_buf_init(&xb); if (xo_gettext_simplify_format(xop, &xb, fields, this_field, fmt, NULL)) goto bail2; const char *gtfmt = xo_dgettext(xop, xb.xb_bufp); if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0) goto bail2; char *new_fmt = xo_strndup(gtfmt, -1); if (new_fmt == NULL) goto bail2; xo_buf_cleanup(&xb); *new_fmtp = new_fmt; return new_fmt; bail2: xo_buf_cleanup(&xb); bail: *new_fmtp = NULL; return fmt; } static void xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields, ssize_t *fstart, unsigned min_fstart, ssize_t *fend, unsigned max_fend) { xo_field_info_t *xfip; char *buf; ssize_t base = fstart[min_fstart]; ssize_t blen = fend[max_fend] - base; xo_buffer_t *xbp = &xop->xo_data; if (blen == 0) return; buf = xo_realloc(NULL, blen); if (buf == NULL) return; memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */ unsigned field = min_fstart, len, fnum; ssize_t soff, doff = base; xo_field_info_t *zp; /* * Be aware there are two competing views of "field number": we * want the user to thing in terms of "The {1:size}" where {G:}, * newlines, escaped braces, and text don't have numbers. But is * also the internal view, where we have an array of * xo_field_info_t and every field have an index. fnum, fstart[] * and fend[] are the latter, but xfi_renum is the former. */ for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) { fnum = field; if (xfip->xfi_renum) { zp = xo_gettext_find_field(fields, xfip->xfi_renum); fnum = zp ? zp - fields : field; } soff = fstart[fnum]; len = fend[fnum] - soff; if (len > 0) { soff -= base; memcpy(xbp->xb_bufp + doff, buf + soff, len); doff += len; } } xo_free(buf); } #else /* HAVE_GETTEXT */ static const char * xo_gettext_build_format (xo_handle_t *xop UNUSED, xo_field_info_t *fields UNUSED, int this_field UNUSED, const char *fmt UNUSED, char **new_fmtp) { *new_fmtp = NULL; return fmt; } static int xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED, const char *gtfmt UNUSED, xo_field_info_t *old_fields UNUSED, xo_field_info_t *new_fields UNUSED, unsigned new_max_fields UNUSED, int *reorderedp UNUSED) { return -1; } static void xo_gettext_rebuild_content (xo_handle_t *xop UNUSED, xo_field_info_t *fields UNUSED, ssize_t *fstart UNUSED, unsigned min_fstart UNUSED, ssize_t *fend UNUSED, unsigned max_fend UNUSED) { return; } #endif /* HAVE_GETTEXT */ /* * Emit a set of fields. This is really the core of libxo. */ static ssize_t xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields, unsigned max_fields, const char *fmt) { int gettext_inuse = 0; int gettext_changed = 0; int gettext_reordered = 0; unsigned ftype; xo_xff_flags_t flags; xo_field_info_t *new_fields = NULL; xo_field_info_t *xfip; unsigned field; ssize_t rc = 0; int flush = XOF_ISSET(xop, XOF_FLUSH); int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE); char *new_fmt = NULL; if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER) flush_line = 0; /* * Some overhead for gettext; if the fields in the msgstr returned * by gettext are reordered, then we need to record start and end * for each field. We'll go ahead and render the fields in the * normal order, but later we can then reconstruct the reordered * fields using these fstart/fend values. */ unsigned flimit = max_fields * 2; /* Pessimistic limit */ unsigned min_fstart = flimit - 1; unsigned max_fend = 0; /* Highest recorded fend[] entry */ ssize_t fstart[flimit]; bzero(fstart, flimit * sizeof(fstart[0])); ssize_t fend[flimit]; bzero(fend, flimit * sizeof(fend[0])); for (xfip = fields, field = 0; field < max_fields && xfip->xfi_ftype; xfip++, field++) { ftype = xfip->xfi_ftype; flags = xfip->xfi_flags; /* Record field start offset */ if (gettext_reordered) { fstart[field] = xo_buf_offset(&xop->xo_data); if (min_fstart > field) min_fstart = field; } const char *content = xfip->xfi_content; ssize_t clen = xfip->xfi_clen; if (flags & XFF_ARGUMENT) { /* * Argument flag means the content isn't given in the descriptor, * but as a UTF-8 string ('const char *') argument in xo_vap. */ content = va_arg(xop->xo_vap, char *); clen = content ? strlen(content) : 0; } if (ftype == XO_ROLE_NEWLINE) { xo_line_close(xop); if (flush_line && xo_flush_h(xop) < 0) return -1; goto bottom; } else if (ftype == XO_ROLE_EBRACE) { xo_format_text(xop, xfip->xfi_start, xfip->xfi_len); goto bottom; } else if (ftype == XO_ROLE_TEXT) { /* Normal text */ xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen); goto bottom; } /* * Notes and units need the 'w' flag handled before the content. */ if (ftype == 'N' || ftype == 'U') { if (flags & XFF_WS) { xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, flags); flags &= ~XFF_WS; /* Block later handling of this */ } } if (ftype == 'V') xo_format_value(xop, content, clen, xfip->xfi_format, xfip->xfi_flen, xfip->xfi_encoding, xfip->xfi_elen, flags); else if (ftype == '[') xo_anchor_start(xop, xfip, content, clen); else if (ftype == ']') xo_anchor_stop(xop, xfip, content, clen); else if (ftype == 'C') xo_format_colors(xop, xfip, content, clen); else if (ftype == 'G') { /* * A {G:domain} field; disect the domain name and translate * the remaining portion of the input string. If the user * didn't put the {G:} at the start of the format string, then * assumably they just want us to translate the rest of it. * Since gettext returns strings in a static buffer, we make * a copy in new_fmt. */ xo_set_gettext_domain(xop, xfip, content, clen); if (!gettext_inuse) { /* Only translate once */ gettext_inuse = 1; if (new_fmt) { xo_free(new_fmt); new_fmt = NULL; } xo_gettext_build_format(xop, fields, field, xfip->xfi_next, &new_fmt); if (new_fmt) { gettext_changed = 1; unsigned new_max_fields = xo_count_fields(xop, new_fmt); if (++new_max_fields < max_fields) new_max_fields = max_fields; /* Leave a blank slot at the beginning */ ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t); new_fields = alloca(sz); bzero(new_fields, sz); if (!xo_parse_fields(xop, new_fields + 1, new_max_fields, new_fmt)) { gettext_reordered = 0; if (!xo_gettext_combine_formats(xop, fmt, new_fmt, fields, new_fields + 1, new_max_fields, &gettext_reordered)) { if (gettext_reordered) { if (XOF_ISSET(xop, XOF_LOG_GETTEXT)) xo_failure(xop, "gettext finds reordered " "fields in '%s' and '%s'", xo_printable(fmt), xo_printable(new_fmt)); flush_line = 0; /* Must keep at content */ XOIF_SET(xop, XOIF_REORDER); } field = -1; /* Will be incremented at top of loop */ xfip = new_fields; max_fields = new_max_fields; } } } } continue; } else if (clen || xfip->xfi_format) { const char *class_name = xo_class_name(ftype); if (class_name) xo_format_content(xop, class_name, xo_tag_name(ftype), content, clen, xfip->xfi_format, xfip->xfi_flen, flags); else if (ftype == 'T') xo_format_title(xop, xfip, content, clen); else if (ftype == 'U') xo_format_units(xop, xfip, content, clen); else xo_failure(xop, "unknown field type: '%c'", ftype); } if (flags & XFF_COLON) xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0); if (flags & XFF_WS) xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0); bottom: /* Record the end-of-field offset */ if (gettext_reordered) { fend[field] = xo_buf_offset(&xop->xo_data); max_fend = field; } } if (gettext_changed && gettext_reordered) { /* Final step: rebuild the content using the rendered fields */ xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart, fend, max_fend); } XOIF_CLEAR(xop, XOIF_REORDER); /* * If we've got enough data, flush it. */ if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER) flush = 1; /* If we don't have an anchor, write the text out */ if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) { if (xo_write(xop) < 0) rc = -1; /* Report failure */ else if (xo_flush_h(xop) < 0) rc = -1; } if (new_fmt) xo_free(new_fmt); /* * We've carried the gettext domainname inside our handle just for * convenience, but we need to ensure it doesn't survive across * xo_emit calls. */ if (xop->xo_gt_domain) { xo_free(xop->xo_gt_domain); xop->xo_gt_domain = NULL; } return (rc < 0) ? rc : xop->xo_columns; } /* * Parse and emit a set of fields */ static int xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt) { xop->xo_columns = 0; /* Always reset it */ xop->xo_errno = errno; /* Save for "%m" */ if (fmt == NULL) return 0; unsigned max_fields; xo_field_info_t *fields = NULL; /* Adjust XOEF_RETAIN based on global flags */ if (XOF_ISSET(xop, XOF_RETAIN_ALL)) flags |= XOEF_RETAIN; if (XOF_ISSET(xop, XOF_RETAIN_NONE)) flags &= ~XOEF_RETAIN; /* * Check for 'retain' flag, telling us to retain the field * information. If we've already saved it, then we can avoid * re-parsing the format string. */ if (!(flags & XOEF_RETAIN) || xo_retain_find(fmt, &fields, &max_fields) != 0 || fields == NULL) { /* Nothing retained; parse the format string */ max_fields = xo_count_fields(xop, fmt); fields = alloca(max_fields * sizeof(fields[0])); bzero(fields, max_fields * sizeof(fields[0])); if (xo_parse_fields(xop, fields, max_fields, fmt)) return -1; /* Warning already displayed */ if (flags & XOEF_RETAIN) { /* Retain the info */ xo_retain_add(fmt, fields, max_fields); } } return xo_do_emit_fields(xop, fields, max_fields, fmt); } /* * Rebuild a format string in a gettext-friendly format. This function * is exposed to tools can perform this function. See xo(1). */ char * xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers, xo_simplify_field_func_t field_cb) { xop = xo_default(xop); xop->xo_columns = 0; /* Always reset it */ xop->xo_errno = errno; /* Save for "%m" */ unsigned max_fields = xo_count_fields(xop, fmt); xo_field_info_t fields[max_fields]; bzero(fields, max_fields * sizeof(fields[0])); if (xo_parse_fields(xop, fields, max_fields, fmt)) return NULL; /* Warning already displayed */ xo_buffer_t xb; xo_buf_init(&xb); if (with_numbers) xo_gettext_finish_numbering_fields(xop, fmt, fields); if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb)) return NULL; return xb.xb_bufp; } xo_ssize_t xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap) { ssize_t rc; xop = xo_default(xop); va_copy(xop->xo_vap, vap); rc = xo_do_emit(xop, 0, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } xo_ssize_t xo_emit_h (xo_handle_t *xop, const char *fmt, ...) { ssize_t rc; xop = xo_default(xop); va_start(xop->xo_vap, fmt); rc = xo_do_emit(xop, 0, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } xo_ssize_t xo_emit (const char *fmt, ...) { xo_handle_t *xop = xo_default(NULL); ssize_t rc; va_start(xop->xo_vap, fmt); rc = xo_do_emit(xop, 0, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } xo_ssize_t xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, va_list vap) { ssize_t rc; xop = xo_default(xop); va_copy(xop->xo_vap, vap); rc = xo_do_emit(xop, flags, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } xo_ssize_t xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...) { ssize_t rc; xop = xo_default(xop); va_start(xop->xo_vap, fmt); rc = xo_do_emit(xop, flags, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } xo_ssize_t xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...) { xo_handle_t *xop = xo_default(NULL); ssize_t rc; va_start(xop->xo_vap, fmt); rc = xo_do_emit(xop, flags, fmt); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); return rc; } /* * Emit a single field by providing the info information typically provided * inside the field description (role, modifiers, and formats). This is * a convenience function to avoid callers using snprintf to build field * descriptions. */ xo_ssize_t xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, va_list vap) { ssize_t rc; xop = xo_default(xop); if (rolmod == NULL) rolmod = "V"; xo_field_info_t xfi; bzero(&xfi, sizeof(xfi)); const char *cp; cp = xo_parse_roles(xop, rolmod, rolmod, &xfi); if (cp == NULL) return -1; xfi.xfi_start = fmt; xfi.xfi_content = contents; xfi.xfi_format = fmt; xfi.xfi_encoding = efmt; xfi.xfi_clen = contents ? strlen(contents) : 0; xfi.xfi_flen = fmt ? strlen(fmt) : 0; xfi.xfi_elen = efmt ? strlen(efmt) : 0; /* If we have content, then we have a default format */ if (contents && fmt == NULL && xo_role_wants_default_format(xfi.xfi_ftype)) { xfi.xfi_format = xo_default_format; xfi.xfi_flen = 2; } va_copy(xop->xo_vap, vap); rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field"); va_end(xop->xo_vap); return rc; } xo_ssize_t xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...) { ssize_t rc; va_list vap; va_start(vap, efmt); rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap); va_end(vap); return rc; } xo_ssize_t xo_emit_field (const char *rolmod, const char *contents, const char *fmt, const char *efmt, ...) { ssize_t rc; va_list vap; va_start(vap, efmt); rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap); va_end(vap); return rc; } xo_ssize_t xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap) { const ssize_t extra = 5; /* space, equals, quote, quote, and nul */ xop = xo_default(xop); ssize_t rc = 0; ssize_t nlen = strlen(name); xo_buffer_t *xbp = &xop->xo_attrs; ssize_t name_offset, value_offset; switch (xo_style(xop)) { case XO_STYLE_XML: if (!xo_buf_has_room(xbp, nlen + extra)) return -1; *xbp->xb_curp++ = ' '; memcpy(xbp->xb_curp, name, nlen); xbp->xb_curp += nlen; *xbp->xb_curp++ = '='; *xbp->xb_curp++ = '"'; rc = xo_vsnprintf(xop, xbp, fmt, vap); if (rc >= 0) { rc = xo_escape_xml(xbp, rc, 1); xbp->xb_curp += rc; } if (!xo_buf_has_room(xbp, 2)) return -1; *xbp->xb_curp++ = '"'; *xbp->xb_curp = '\0'; rc += nlen + extra; break; case XO_STYLE_ENCODER: name_offset = xo_buf_offset(xbp); xo_buf_append(xbp, name, nlen); xo_buf_append(xbp, "", 1); value_offset = xo_buf_offset(xbp); rc = xo_vsnprintf(xop, xbp, fmt, vap); if (rc >= 0) { xbp->xb_curp += rc; *xbp->xb_curp = '\0'; rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE, xo_buf_data(xbp, name_offset), xo_buf_data(xbp, value_offset), 0); } } return rc; } xo_ssize_t xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...) { ssize_t rc; va_list vap; va_start(vap, fmt); rc = xo_attr_hv(xop, name, fmt, vap); va_end(vap); return rc; } xo_ssize_t xo_attr (const char *name, const char *fmt, ...) { ssize_t rc; va_list vap; va_start(vap, fmt); rc = xo_attr_hv(NULL, name, fmt, vap); va_end(vap); return rc; } static void xo_stack_set_flags (xo_handle_t *xop) { if (XOF_ISSET(xop, XOF_NOT_FIRST)) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; xsp->xs_flags |= XSF_NOT_FIRST; XOF_CLEAR(xop, XOF_NOT_FIRST); } } static void xo_depth_change (xo_handle_t *xop, const char *name, int delta, int indent, xo_state_t state, xo_xsf_flags_t flags) { if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT) indent = 0; if (XOF_ISSET(xop, XOF_DTRT)) flags |= XSF_DTRT; if (delta >= 0) { /* Push operation */ if (xo_depth_check(xop, xop->xo_depth + delta)) return; xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta]; xsp->xs_flags = flags; xsp->xs_state = state; xo_stack_set_flags(xop); if (name == NULL) name = XO_FAILURE_NAME; xsp->xs_name = xo_strndup(name, -1); } else { /* Pop operation */ if (xop->xo_depth == 0) { if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE)) xo_failure(xop, "close with empty stack: '%s'", name); return; } xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; if (XOF_ISSET(xop, XOF_WARN)) { const char *top = xsp->xs_name; if (top != NULL && name != NULL && strcmp(name, top) != 0) { xo_failure(xop, "incorrect close: '%s' .vs. '%s'", name, top); return; } if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) { xo_failure(xop, "list close on list confict: '%s'", name); return; } if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) { xo_failure(xop, "list close on instance confict: '%s'", name); return; } } if (xsp->xs_name) { xo_free(xsp->xs_name); xsp->xs_name = NULL; } if (xsp->xs_keys) { xo_free(xsp->xs_keys); xsp->xs_keys = NULL; } } xop->xo_depth += delta; /* Record new depth */ xop->xo_indent += indent; } void xo_set_depth (xo_handle_t *xop, int depth) { xop = xo_default(xop); if (xo_depth_check(xop, depth)) return; xop->xo_depth += depth; xop->xo_indent += depth; } static xo_xsf_flags_t xo_stack_flags (xo_xof_flags_t xflags) { if (xflags & XOF_DTRT) return XSF_DTRT; return 0; } static void xo_emit_top (xo_handle_t *xop, const char *ppn) { xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); XOIF_SET(xop, XOIF_TOP_EMITTED); if (xop->xo_version) { xo_printf(xop, "%*s\"__version\": \"%s\", %s", xo_indent(xop), "", xop->xo_version, ppn); xo_free(xop->xo_version); xop->xo_version = NULL; } } static ssize_t xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) { ssize_t rc = 0; const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; if (name == NULL) { xo_failure(xop, "NULL passed for container name"); name = XO_FAILURE_NAME; } flags |= xop->xo_flags; /* Pick up handle flags */ switch (xo_style(xop)) { case XO_STYLE_XML: rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name); if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp; xo_data_append(xop, xop->xo_attrs.xb_bufp, xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; } rc += xo_printf(xop, ">%s", ppn); break; case XO_STYLE_JSON: xo_stack_set_flags(xop); if (!XOF_ISSET(xop, XOF_NO_TOP) && !XOIF_ISSET(xop, XOIF_TOP_EMITTED)) xo_emit_top(xop, ppn); if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; rc = xo_printf(xop, "%s%*s\"%s\": {%s", pre_nl, xo_indent(xop), "", name, ppn); break; case XO_STYLE_SDPARAMS: break; case XO_STYLE_ENCODER: rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL, flags); break; } xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER, xo_stack_flags(flags)); return rc; } static int xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) { return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER); } xo_ssize_t xo_open_container_h (xo_handle_t *xop, const char *name) { return xo_open_container_hf(xop, 0, name); } xo_ssize_t xo_open_container (const char *name) { return xo_open_container_hf(NULL, 0, name); } xo_ssize_t xo_open_container_hd (xo_handle_t *xop, const char *name) { return xo_open_container_hf(xop, XOF_DTRT, name); } xo_ssize_t xo_open_container_d (const char *name) { return xo_open_container_hf(NULL, XOF_DTRT, name); } static int xo_do_close_container (xo_handle_t *xop, const char *name) { xop = xo_default(xop); ssize_t rc = 0; const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; name = xsp->xs_name; if (name) { ssize_t len = strlen(name) + 1; /* We need to make a local copy; xo_depth_change will free it */ char *cp = alloca(len); memcpy(cp, name, len); name = cp; } else if (!(xsp->xs_flags & XSF_DTRT)) { xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; } } switch (xo_style(xop)) { case XO_STYLE_XML: xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0); rc = xo_printf(xop, "%*s%s", xo_indent(xop), "", name, ppn); break; case XO_STYLE_JSON: pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; ppn = (xop->xo_depth <= 1) ? "\n" : ""; xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0); rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; case XO_STYLE_HTML: case XO_STYLE_TEXT: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0); break; case XO_STYLE_SDPARAMS: break; case XO_STYLE_ENCODER: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0); rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL, 0); break; } return rc; } xo_ssize_t xo_close_container_h (xo_handle_t *xop, const char *name) { return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER); } xo_ssize_t xo_close_container (const char *name) { return xo_close_container_h(NULL, name); } xo_ssize_t xo_close_container_hd (xo_handle_t *xop) { return xo_close_container_h(xop, NULL); } xo_ssize_t xo_close_container_d (void) { return xo_close_container_h(NULL, NULL); } static int xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { ssize_t rc = 0; int indent = 0; xop = xo_default(xop); const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; switch (xo_style(xop)) { case XO_STYLE_JSON: indent = 1; if (!XOF_ISSET(xop, XOF_NO_TOP) && !XOIF_ISSET(xop, XOIF_TOP_EMITTED)) xo_emit_top(xop, ppn); if (name == NULL) { xo_failure(xop, "NULL passed for list name"); name = XO_FAILURE_NAME; } xo_stack_set_flags(xop); if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; rc = xo_printf(xop, "%s%*s\"%s\": [%s", pre_nl, xo_indent(xop), "", name, ppn); break; case XO_STYLE_ENCODER: rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL, flags); break; } xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST, XSF_LIST | xo_stack_flags(flags)); return rc; } static int xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { return xo_transition(xop, flags, name, XSS_OPEN_LIST); } xo_ssize_t xo_open_list_h (xo_handle_t *xop, const char *name) { return xo_open_list_hf(xop, 0, name); } xo_ssize_t xo_open_list (const char *name) { return xo_open_list_hf(NULL, 0, name); } xo_ssize_t xo_open_list_hd (xo_handle_t *xop, const char *name) { return xo_open_list_hf(xop, XOF_DTRT, name); } xo_ssize_t xo_open_list_d (const char *name) { return xo_open_list_hf(NULL, XOF_DTRT, name); } static int xo_do_close_list (xo_handle_t *xop, const char *name) { ssize_t rc = 0; const char *pre_nl = ""; if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; name = xsp->xs_name; if (name) { ssize_t len = strlen(name) + 1; /* We need to make a local copy; xo_depth_change will free it */ char *cp = alloca(len); memcpy(cp, name, len); name = cp; } else if (!(xsp->xs_flags & XSF_DTRT)) { xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; } } switch (xo_style(xop)) { case XO_STYLE_JSON: if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST); rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; case XO_STYLE_ENCODER: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST); rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL, 0); break; default: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; } return rc; } xo_ssize_t xo_close_list_h (xo_handle_t *xop, const char *name) { return xo_transition(xop, 0, name, XSS_CLOSE_LIST); } xo_ssize_t xo_close_list (const char *name) { return xo_close_list_h(NULL, name); } xo_ssize_t xo_close_list_hd (xo_handle_t *xop) { return xo_close_list_h(xop, NULL); } xo_ssize_t xo_close_list_d (void) { return xo_close_list_h(NULL, NULL); } static int xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { ssize_t rc = 0; int indent = 0; xop = xo_default(xop); const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; switch (xo_style(xop)) { case XO_STYLE_JSON: indent = 1; if (!XOF_ISSET(xop, XOF_NO_TOP)) { if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) { xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); XOIF_SET(xop, XOIF_TOP_EMITTED); } } if (name == NULL) { xo_failure(xop, "NULL passed for list name"); name = XO_FAILURE_NAME; } xo_stack_set_flags(xop); if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; rc = xo_printf(xop, "%s%*s\"%s\": [%s", pre_nl, xo_indent(xop), "", name, ppn); break; case XO_STYLE_ENCODER: rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL, flags); break; } xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST, XSF_LIST | xo_stack_flags(flags)); return rc; } static int xo_do_close_leaf_list (xo_handle_t *xop, const char *name) { ssize_t rc = 0; const char *pre_nl = ""; if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; name = xsp->xs_name; if (name) { ssize_t len = strlen(name) + 1; /* We need to make a local copy; xo_depth_change will free it */ char *cp = alloca(len); memcpy(cp, name, len); name = cp; } else if (!(xsp->xs_flags & XSF_DTRT)) { xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; } } switch (xo_style(xop)) { case XO_STYLE_JSON: if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST); rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; case XO_STYLE_ENCODER: rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL, 0); /* FALLTHRU */ default: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; } return rc; } static int xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { xop = xo_default(xop); ssize_t rc = 0; const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; flags |= xop->xo_flags; if (name == NULL) { xo_failure(xop, "NULL passed for instance name"); name = XO_FAILURE_NAME; } switch (xo_style(xop)) { case XO_STYLE_XML: rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name); if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) { rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp; xo_data_append(xop, xop->xo_attrs.xb_bufp, xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp); xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp; } rc += xo_printf(xop, ">%s", ppn); break; case XO_STYLE_JSON: xo_stack_set_flags(xop); if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", "; xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; rc = xo_printf(xop, "%s%*s{%s", pre_nl, xo_indent(xop), "", ppn); break; case XO_STYLE_SDPARAMS: break; case XO_STYLE_ENCODER: rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL, flags); break; } xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags)); return rc; } static int xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE); } xo_ssize_t xo_open_instance_h (xo_handle_t *xop, const char *name) { return xo_open_instance_hf(xop, 0, name); } xo_ssize_t xo_open_instance (const char *name) { return xo_open_instance_hf(NULL, 0, name); } xo_ssize_t xo_open_instance_hd (xo_handle_t *xop, const char *name) { return xo_open_instance_hf(xop, XOF_DTRT, name); } xo_ssize_t xo_open_instance_d (const char *name) { return xo_open_instance_hf(NULL, XOF_DTRT, name); } static int xo_do_close_instance (xo_handle_t *xop, const char *name) { xop = xo_default(xop); ssize_t rc = 0; const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; name = xsp->xs_name; if (name) { ssize_t len = strlen(name) + 1; /* We need to make a local copy; xo_depth_change will free it */ char *cp = alloca(len); memcpy(cp, name, len); name = cp; } else if (!(xsp->xs_flags & XSF_DTRT)) { xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; } } switch (xo_style(xop)) { case XO_STYLE_XML: xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0); rc = xo_printf(xop, "%*s%s", xo_indent(xop), "", name, ppn); break; case XO_STYLE_JSON: pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : ""; xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0); rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), ""); xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; break; case XO_STYLE_HTML: case XO_STYLE_TEXT: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0); break; case XO_STYLE_SDPARAMS: break; case XO_STYLE_ENCODER: xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0); rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL, 0); break; } return rc; } xo_ssize_t xo_close_instance_h (xo_handle_t *xop, const char *name) { return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE); } xo_ssize_t xo_close_instance (const char *name) { return xo_close_instance_h(NULL, name); } xo_ssize_t xo_close_instance_hd (xo_handle_t *xop) { return xo_close_instance_h(xop, NULL); } xo_ssize_t xo_close_instance_d (void) { return xo_close_instance_h(NULL, NULL); } static int xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit) { xo_stack_t *xsp; ssize_t rc = 0; xo_xsf_flags_t flags; for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) { switch (xsp->xs_state) { case XSS_INIT: /* Nothing */ rc = 0; break; case XSS_OPEN_CONTAINER: rc = xo_do_close_container(xop, NULL); break; case XSS_OPEN_LIST: rc = xo_do_close_list(xop, NULL); break; case XSS_OPEN_INSTANCE: rc = xo_do_close_instance(xop, NULL); break; case XSS_OPEN_LEAF_LIST: rc = xo_do_close_leaf_list(xop, NULL); break; case XSS_MARKER: flags = xsp->xs_flags & XSF_MARKER_FLAGS; xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0); xop->xo_stack[xop->xo_depth].xs_flags |= flags; rc = 0; break; } if (rc < 0) xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc); } return 0; } /* * This function is responsible for clearing out whatever is needed * to get to the desired state, if possible. */ static int xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state) { xo_stack_t *xsp, *limit = NULL; ssize_t rc; xo_state_t need_state = new_state; if (new_state == XSS_CLOSE_CONTAINER) need_state = XSS_OPEN_CONTAINER; else if (new_state == XSS_CLOSE_LIST) need_state = XSS_OPEN_LIST; else if (new_state == XSS_CLOSE_INSTANCE) need_state = XSS_OPEN_INSTANCE; else if (new_state == XSS_CLOSE_LEAF_LIST) need_state = XSS_OPEN_LEAF_LIST; else if (new_state == XSS_MARKER) need_state = XSS_MARKER; else return 0; /* Unknown or useless new states are ignored */ for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) { /* * Marker's normally stop us from going any further, unless * we are popping a marker (new_state == XSS_MARKER). */ if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) { if (name) { xo_failure(xop, "close (xo_%s) fails at marker '%s'; " "not found '%s'", xo_state_name(new_state), xsp->xs_name, name); return 0; } else { limit = xsp; xo_failure(xop, "close stops at marker '%s'", xsp->xs_name); } break; } if (xsp->xs_state != need_state) continue; if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0) continue; limit = xsp; break; } if (limit == NULL) { xo_failure(xop, "xo_%s can't find match for '%s'", xo_state_name(new_state), name); return 0; } rc = xo_do_close_all(xop, limit); return rc; } /* * We are in a given state and need to transition to the new state. */ static ssize_t xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, xo_state_t new_state) { xo_stack_t *xsp; ssize_t rc = 0; int old_state, on_marker; xop = xo_default(xop); xsp = &xop->xo_stack[xop->xo_depth]; old_state = xsp->xs_state; on_marker = (old_state == XSS_MARKER); /* If there's a marker on top of the stack, we need to find a real state */ while (old_state == XSS_MARKER) { if (xsp == xop->xo_stack) break; xsp -= 1; old_state = xsp->xs_state; } /* * At this point, the list of possible states are: * XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST, * XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING */ switch (XSS_TRANSITION(old_state, new_state)) { open_container: case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER): case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER): rc = xo_do_open_container(xop, flags, name); break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER): if (on_marker) goto marker_prevents_close; rc = xo_do_close_list(xop, NULL); if (rc >= 0) goto open_container; break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); if (rc >= 0) goto open_container; break; /*close_container:*/ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, name, new_state); break; case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER): /* This is an exception for "xo --close" */ rc = xo_do_close_container(xop, name); break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, name, new_state); break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); if (rc >= 0) rc = xo_do_close(xop, name, new_state); break; open_list: case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST): case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST): rc = xo_do_open_list(xop, flags, name); break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close_list(xop, NULL); if (rc >= 0) goto open_list; break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); if (rc >= 0) goto open_list; break; /*close_list:*/ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, name, new_state); break; case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST): case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST): case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST): rc = xo_do_close(xop, name, new_state); break; open_instance: case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE): rc = xo_do_open_instance(xop, flags, name); break; case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE): case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE): rc = xo_do_open_list(xop, flags, name); if (rc >= 0) goto open_instance; break; case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE): if (on_marker) { rc = xo_do_open_list(xop, flags, name); } else { rc = xo_do_close_instance(xop, NULL); } if (rc >= 0) goto open_instance; break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); if (rc >= 0) goto open_instance; break; /*close_instance:*/ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE): if (on_marker) goto marker_prevents_close; rc = xo_do_close_instance(xop, name); break; case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE): /* This one makes no sense; ignore it */ xo_failure(xop, "xo_close_instance ignored when called from " "initial state ('%s')", name ?: "(unknown)"); break; case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE): case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, name, new_state); break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); if (rc >= 0) rc = xo_do_close(xop, name, new_state); break; open_leaf_list: case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST): case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST): rc = xo_do_open_leaf_list(xop, flags, name); break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close_list(xop, NULL); if (rc >= 0) goto open_leaf_list; break; /*close_leaf_list:*/ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, name); break; case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST): /* Makes no sense; ignore */ xo_failure(xop, "xo_close_leaf_list ignored when called from " "initial state ('%s')", name ?: "(unknown)"); break; case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, name, new_state); break; /*emit:*/ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT): break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT): if (on_marker) goto marker_prevents_close; rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST); break; case XSS_TRANSITION(XSS_INIT, XSS_EMIT): break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT): if (on_marker) goto marker_prevents_close; rc = xo_do_close_leaf_list(xop, NULL); break; /*emit_leaf_list:*/ case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST): case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST): rc = xo_do_open_leaf_list(xop, flags, name); break; case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST): break; case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST): /* * We need to be backward compatible with the pre-xo_open_leaf_list * API, where both lists and leaf-lists were opened as lists. So * if we find an open list that hasn't had anything written to it, * we'll accept it. */ break; default: xo_failure(xop, "unknown transition: (%u -> %u)", xsp->xs_state, new_state); } /* Handle the flush flag */ if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH)) if (xo_flush_h(xop)) rc = -1; return rc; marker_prevents_close: xo_failure(xop, "marker '%s' prevents transition from %s to %s", xop->xo_stack[xop->xo_depth].xs_name, xo_state_name(old_state), xo_state_name(new_state)); return -1; } xo_ssize_t xo_open_marker_h (xo_handle_t *xop, const char *name) { xop = xo_default(xop); xo_depth_change(xop, name, 1, 0, XSS_MARKER, xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS); return 0; } xo_ssize_t xo_open_marker (const char *name) { return xo_open_marker_h(NULL, name); } xo_ssize_t xo_close_marker_h (xo_handle_t *xop, const char *name) { xop = xo_default(xop); return xo_do_close(xop, name, XSS_MARKER); } xo_ssize_t xo_close_marker (const char *name) { return xo_close_marker_h(NULL, name); } /* * Record custom output functions into the xo handle, allowing * integration with a variety of output frameworks. */ void xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func, xo_close_func_t close_func, xo_flush_func_t flush_func) { xop = xo_default(xop); xop->xo_opaque = opaque; xop->xo_write = write_func; xop->xo_close = close_func; xop->xo_flush = flush_func; } void xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func) { xo_realloc = realloc_func; xo_free = free_func; } xo_ssize_t xo_flush_h (xo_handle_t *xop) { ssize_t rc; xop = xo_default(xop); switch (xo_style(xop)) { case XO_STYLE_ENCODER: xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL, 0); } rc = xo_write(xop); if (rc >= 0 && xop->xo_flush) if (xop->xo_flush(xop->xo_opaque) < 0) return -1; return rc; } xo_ssize_t xo_flush (void) { return xo_flush_h(NULL); } xo_ssize_t xo_finish_h (xo_handle_t *xop) { const char *cp = ""; xop = xo_default(xop); if (!XOF_ISSET(xop, XOF_NO_CLOSE)) xo_do_close_all(xop, xop->xo_stack); switch (xo_style(xop)) { case XO_STYLE_JSON: if (!XOF_ISSET(xop, XOF_NO_TOP)) { if (XOIF_ISSET(xop, XOIF_TOP_EMITTED)) XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */ else cp = "{ "; xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp); } break; case XO_STYLE_ENCODER: xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL, 0); break; } return xo_flush_h(xop); } xo_ssize_t xo_finish (void) { return xo_finish_h(NULL); } /* * xo_finish_atexit is suitable for atexit() calls, to force clear up * and finalizing output. */ void xo_finish_atexit (void) { (void) xo_finish_h(NULL); } /* * Generate an error message, such as would be displayed on stderr */ void xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap) { xop = xo_default(xop); /* * If the format string doesn't end with a newline, we pop * one on ourselves. */ ssize_t len = strlen(fmt); if (len > 0 && fmt[len - 1] != '\n') { char *newfmt = alloca(len + 2); memcpy(newfmt, fmt, len); newfmt[len] = '\n'; newfmt[len] = '\0'; fmt = newfmt; } switch (xo_style(xop)) { case XO_STYLE_TEXT: vfprintf(stderr, fmt, vap); break; case XO_STYLE_HTML: va_copy(xop->xo_vap, vap); xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0); if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) xo_line_close(xop); xo_write(xop); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); break; case XO_STYLE_XML: case XO_STYLE_JSON: va_copy(xop->xo_vap, vap); xo_open_container_h(xop, "error"); xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0); xo_close_container_h(xop, "error"); va_end(xop->xo_vap); bzero(&xop->xo_vap, sizeof(xop->xo_vap)); break; case XO_STYLE_SDPARAMS: case XO_STYLE_ENCODER: break; } } void xo_error_h (xo_handle_t *xop, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_error_hv(xop, fmt, vap); va_end(vap); } /* * Generate an error message, such as would be displayed on stderr */ void xo_error (const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_error_hv(NULL, fmt, vap); va_end(vap); } /* * Parse any libxo-specific options from the command line, removing them * so the main() argument parsing won't see them. We return the new value * for argc or -1 for error. If an error occurred, the program should * exit. A suitable error message has already been displayed. */ int xo_parse_args (int argc, char **argv) { static char libxo_opt[] = "--libxo"; char *cp; int i, save; /* Save our program name for xo_err and friends */ xo_program = argv[0]; cp = strrchr(xo_program, '/'); if (cp) xo_program = cp + 1; xo_handle_t *xop = xo_default(NULL); for (save = i = 1; i < argc; i++) { if (argv[i] == NULL || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) { if (save != i) argv[save] = argv[i]; save += 1; continue; } cp = argv[i] + sizeof(libxo_opt) - 1; if (*cp == '\0') { cp = argv[++i]; if (cp == NULL) { xo_warnx("missing libxo option"); return -1; } if (xo_set_options(xop, cp) < 0) return -1; } else if (*cp == ':') { if (xo_set_options(xop, cp) < 0) return -1; } else if (*cp == '=') { if (xo_set_options(xop, ++cp) < 0) return -1; } else if (*cp == '-') { cp += 1; if (strcmp(cp, "check") == 0) { exit(XO_HAS_LIBXO); } else { xo_warnx("unknown libxo option: '%s'", argv[i]); return -1; } } else { xo_warnx("unknown libxo option: '%s'", argv[i]); return -1; } } /* * We only want to do color output on terminals, but we only want * to do this if the user has asked for color. */ if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1)) XOF_SET(xop, XOF_COLOR); argv[save] = NULL; return save; } /* * Debugging function that dumps the current stack of open libxo constructs, * suitable for calling from the debugger. */ void xo_dump_stack (xo_handle_t *xop) { int i; xo_stack_t *xsp; xop = xo_default(xop); fprintf(stderr, "Stack dump:\n"); xsp = xop->xo_stack; for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) { fprintf(stderr, " [%d] %s '%s' [%x]\n", i, xo_state_name(xsp->xs_state), xsp->xs_name ?: "--", xsp->xs_flags); } } /* * Record the program name used for error messages */ void xo_set_program (const char *name) { xo_program = name; } void xo_set_version_h (xo_handle_t *xop, const char *version) { xop = xo_default(xop); if (version == NULL || strchr(version, '"') != NULL) return; if (!xo_style_is_encoding(xop)) return; switch (xo_style(xop)) { case XO_STYLE_XML: /* For XML, we record this as an attribute for the first tag */ xo_attr_h(xop, "version", "%s", version); break; case XO_STYLE_JSON: /* * For JSON, we record the version string in our handle, and emit * it in xo_emit_top. */ xop->xo_version = xo_strndup(version, -1); break; case XO_STYLE_ENCODER: xo_encoder_handle(xop, XO_OP_VERSION, NULL, version, 0); break; } } /* * Set the version number for the API content being carried through * the xo handle. */ void xo_set_version (const char *version) { xo_set_version_h(NULL, version); } /* * Generate a warning. Normally, this is a text message written to * standard error. If the XOF_WARN_XML flag is set, then we generate * XMLified content on standard output. */ void xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code, const char *fmt, va_list vap) { xop = xo_default(xop); if (fmt == NULL) return; xo_open_marker_h(xop, "xo_emit_warn_hcv"); xo_open_container_h(xop, as_warning ? "__warning" : "__error"); if (xo_program) xo_emit("{wc:program}", xo_program); if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) { va_list ap; xo_handle_t temp; bzero(&temp, sizeof(temp)); temp.xo_style = XO_STYLE_TEXT; xo_buf_init(&temp.xo_data); xo_depth_check(&temp, XO_DEPTH); va_copy(ap, vap); (void) xo_emit_hv(&temp, fmt, ap); va_end(ap); xo_buffer_t *src = &temp.xo_data; xo_format_value(xop, "message", 7, src->xb_bufp, src->xb_curp - src->xb_bufp, NULL, 0, 0); xo_free(temp.xo_stack); xo_buf_cleanup(src); } (void) xo_emit_hv(xop, fmt, vap); ssize_t len = strlen(fmt); if (len > 0 && fmt[len - 1] != '\n') { if (code > 0) { const char *msg = strerror(code); if (msg) xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg); } xo_emit("\n"); } xo_close_marker_h(xop, "xo_emit_warn_hcv"); xo_flush_h(xop); } void xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_emit_warn_hcv(xop, 1, code, fmt, vap); va_end(vap); } void xo_emit_warn_c (int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_emit_warn_hcv(NULL, 1, code, fmt, vap); va_end(vap); } void xo_emit_warn (const char *fmt, ...) { int code = errno; va_list vap; va_start(vap, fmt); xo_emit_warn_hcv(NULL, 1, code, fmt, vap); va_end(vap); } void xo_emit_warnx (const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_emit_warn_hcv(NULL, 1, -1, fmt, vap); va_end(vap); } void xo_emit_err_v (int eval, int code, const char *fmt, va_list vap) { xo_emit_warn_hcv(NULL, 0, code, fmt, vap); xo_finish(); exit(eval); } void xo_emit_err (int eval, const char *fmt, ...) { int code = errno; va_list vap; va_start(vap, fmt); xo_emit_err_v(0, code, fmt, vap); va_end(vap); exit(eval); } void xo_emit_errx (int eval, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_emit_err_v(0, -1, fmt, vap); va_end(vap); xo_finish(); exit(eval); } void xo_emit_errc (int eval, int code, const char *fmt, ...) { va_list vap; va_start(vap, fmt); xo_emit_warn_hcv(NULL, 0, code, fmt, vap); va_end(vap); xo_finish(); exit(eval); } /* * Get the opaque private pointer for an xo handle */ void * xo_get_private (xo_handle_t *xop) { xop = xo_default(xop); return xop->xo_private; } /* * Set the opaque private pointer for an xo handle. */ void xo_set_private (xo_handle_t *xop, void *opaque) { xop = xo_default(xop); xop->xo_private = opaque; } /* * Get the encoder function */ xo_encoder_func_t xo_get_encoder (xo_handle_t *xop) { xop = xo_default(xop); return xop->xo_encoder; } /* * Record an encoder callback function in an xo handle. */ void xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder) { xop = xo_default(xop); xop->xo_style = XO_STYLE_ENCODER; xop->xo_encoder = encoder; }