From c40e45b75eb76d79a05c7fa85c1fa9b5c728a12f Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Fri, 7 Oct 2016 15:10:20 +0200 Subject: Update to FreeBSD head 2016-08-23 Git mirror commit 9fe7c416e6abb28b1398fd3e5687099846800cfd. --- freebsd/contrib/libxo/libxo/libxo.c | 8080 +++++++++++++++++++++++++++++++++++ 1 file changed, 8080 insertions(+) create mode 100644 freebsd/contrib/libxo/libxo/libxo.c (limited to 'freebsd/contrib/libxo/libxo/libxo.c') diff --git a/freebsd/contrib/libxo/libxo/libxo.c b/freebsd/contrib/libxo/libxo/libxo.c new file mode 100644 index 00000000..fc7c3209 --- /dev/null +++ b/freebsd/contrib/libxo/libxo/libxo.c @@ -0,0 +1,8080 @@ +#include + +/* + * 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. + */ +#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 */ + unsigned xo_anchor_offset; /* Start of anchored text */ + unsigned xo_anchor_columns; /* Number of columns since the start anchor */ + int xo_anchor_min_width; /* Desired width of anchored text */ + unsigned xo_units_offset; /* Start of units insertion point */ + unsigned xo_columns; /* Columns emitted during this xo_emit call */ + uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */ + uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */ + 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 */ + unsigned xfi_len; /* Length of field */ + unsigned xfi_clen; /* Content length */ + unsigned xfi_flen; /* Format length */ + unsigned 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 int +xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, + xo_state_t new_state); + +static void +xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, + const char *name, int nlen, + const char *value, int vlen, + const char *encoding, int 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 int +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 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); + + /* + * 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); + +#if !defined(NO_LIBXO_OPTIONS) + if (!XOF_ISSET(xop, XOF_NO_ENV)) { + char *env = getenv("LIBXO_OPTIONS"); + if (env) + xo_set_options(xop, env); + + } +#endif /* NO_GETENV */ + + 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); + + 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 int +xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags) +{ + int slen; + unsigned delta = 0; + char *cp, *ep, *ip; + const char *sp; + int attr = (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 int +xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED) +{ + unsigned 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 int +xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED) +{ + unsigned 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, int 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 int +xo_write (xo_handle_t *xop) +{ + int 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 int +xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) +{ + va_list va_local; + int rc; + int 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 int +xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap) +{ + xo_buffer_t *xbp = &xop->xo_data; + int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); + int 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 int +xo_printf (xo_handle_t *xop, const char *fmt, ...) +{ + int 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 int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + +static int +xo_is_utf8 (char ch) +{ + return (ch & 0x80); +} + +static inline int +xo_utf8_to_wc_len (const char *buf) +{ + unsigned b = (unsigned char) *buf; + int len; + + if ((b & 0x80) == 0x0) + len = 1; + else if ((b & 0xe0) == 0xc0) + len = 2; + else if ((b & 0xf0) == 0xe0) + len = 3; + else if ((b & 0xf8) == 0xf0) + len = 4; + else if ((b & 0xfc) == 0xf8) + len = 5; + else if ((b & 0xfe) == 0xfc) + len = 6; + else + len = -1; + + return len; +} + +static int +xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz) +{ + + unsigned b = (unsigned char) *buf; + int len, i; + + len = xo_utf8_to_wc_len(buf); + if (len == -1) { + 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, int len) +{ + /* Most common case: singleton byte */ + if (len == 1) + return (unsigned char) buf[0]; + + int i; + wchar_t wc; + const unsigned char *cp = (const unsigned char *) buf; + + wc = *cp & xo_utf8_bits[len]; + for (i = 1; i < len; i++) { + wc <<= 6; + 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 int +xo_utf8_emit_len (wchar_t wc) +{ + int 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 if ((wc & ((1<<26) - 1)) == wc) + len = 5; + else + len = 6; + + return len; +} + +static void +xo_utf8_emit_char (char *buf, int len, wchar_t wc) +{ + int i; + + if (len == 1) { /* Simple case */ + buf[0] = wc & 0x7f; + return; + } + + for (i = len - 1; i >= 0; i--) { + buf[i] = 0x80 | (wc & 0x3f); + wc >>= 6; + } + + buf[0] &= xo_utf8_bits[len]; + buf[0] |= ~xo_utf8_bits[len] << 1; +} + +static int +xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp, + const char *ibuf, int ilen) +{ + wchar_t wc; + int 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); +} + +static void +xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp, + const char *cp, int len) +{ + const char *sp = cp, *ep = cp + len; + unsigned save_off = xbp->xb_bufp - xbp->xb_curp; + int 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, int 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, int 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; + unsigned 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; + + int len = strlen(fmt); + int 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); + + int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); + int 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; + int 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); + + int 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; + int bufsiz = sizeof(buf); + int 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. + * + * @style Style of output desired (XO_STYLE_* value) + * @flags Set of XOF_* flags in use with this handle + */ +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(). + * @fp FILE pointer to use + * @style Style of output desired (XO_STYLE_* value) + * @flags Set of XOF_* flags to use with this handle + */ +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. + * @xop libxo handle + * @fp FILE pointer to use + */ +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. + * @fp FILE pointer to use + */ +int +xo_set_file (FILE *fp) +{ + return xo_set_file_h(NULL, fp); +} + +/** + * Release any resources held by the handle. + * @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. + * + * @xop XO handle to alter (or NULL for default handle) + * @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; +} + +xo_style_t +xo_get_style (xo_handle_t *xop) +{ + xop = xo_default(xop); + return xo_style(xop); +} + +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; + const char *xm_name; +} xo_mapping_t; + +static xo_xff_flags_t +xo_name_lookup (xo_mapping_t *map, const char *value, int 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_COLUMNS, "columns" }, + { XOF_DTRT, "dtrt" }, + { XOF_FLUSH, "flush" }, + { 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 } +}; + +/* + * 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); +} + +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; +} + +/* + * 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" + */ +int +xo_set_options (xo_handle_t *xop, const char *input) +{ + char *cp, *ep, *vp, *np, *bp; + int style = -1, new_style, len, rc = 0; + 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 == ':') { + int 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) { + /* XXX Look for colors=red-blue+green-yellow */ + 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. + * + * @xop XO handle to alter (or NULL for default handle) + * @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); +} + +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 strlen + */ +static char * +xo_strndup (const char *str, int 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. + * + * @xop XO handle to alter (or NULL for default handle) + * @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 + * + * @xop XO handle to alter (or NULL for default handle) + * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED) + * @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. + * + * @xop XO handle to alter (or NULL for default handle) + * @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, int 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, int len, int max, + int need_enc, int have_enc) +{ + int cols = 0; + wchar_t wc = 0; + int ilen, olen, width; + int attr = (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; + + int 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 int +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; + int len, cols = 0, rc = 0; + int 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, int 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 int +xo_count_utf8_cols (const char *str, int len) +{ + int tlen; + wchar_t wc; + int 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. + */ + int 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 int +xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags, + int start_offset, int 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; + int 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. + */ + int 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, int len, + xo_xff_flags_t flags) +{ + int cols; + int need_enc = xo_needed_encoding(xop); + int 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; +} + +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 int +xo_trim_ws (xo_buffer_t *xbp, int len) +{ + char *cp, *sp, *ep; + int 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 int +xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp, + const char *fmt, int flen, xo_xff_flags_t flags) +{ + xo_format_t xf; + const char *cp, *ep, *sp, *xp = NULL; + int rc, cols; + int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop); + unsigned make_output = !(flags & XFF_NO_OUTPUT); + int need_enc = xo_needed_encoding(xop); + int real_need_enc = need_enc; + int 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; + + unsigned 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; + int 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 { + int 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. + */ + int 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 int +xo_humanize (char *buf, int 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 { + unsigned xhs_offset; /* Saved xo_offset */ + unsigned xhs_columns; /* Saved xo_columns */ + unsigned 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; + + unsigned 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; + + int rc; + int 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, int nlen, + const char *value, int vlen, + const char *encoding, int 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)); + + 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]; + int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0; + int 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; + unsigned 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=\""; + int div_len = sizeof(div_number) - 1; + + unsigned end_offset = xbp->xb_curp - xbp->xb_bufp; + int 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)) { + unsigned 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, int 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, unsigned len) +{ + const char *fmt = xfip->xfi_format; + unsigned 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; + int start = xbp->xb_curp - xbp->xb_bufp; + int left = xbp->xb_size - start; + int 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, int nlen, + const char *format, int flen, + const char *encoding, int 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'; + + int 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'; + + int 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'; + + int 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); + + 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("diouxXDOUeEfFgGaAcCp", 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; + int off = xbp->xb_curp - xbp->xb_bufp; + + xo_data_escape(xop, name, nlen); + + if (XOF_ISSET(xop, XOF_UNDERSCORES)) { + int now = xbp->xb_curp - xbp->xb_bufp; + for ( ; off < now; 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; + } + + unsigned name_offset = xo_buf_offset(&xop->xo_data); + xo_data_append(xop, name, nlen); + xo_data_append(xop, "", 1); + + unsigned 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)); + 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, unsigned len) +{ + const char *fmt = xfip->xfi_format; + unsigned 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; + + int 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); + + int 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, int len, const char *fmt, int 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; + int 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 */ +} + +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; + } + } + + if (newp->xoc_col_fg != oldp->xoc_col_fg) { + cp += snprintf(cp, ep - cp, ";3%u", + (newp->xoc_col_fg != XO_COL_DEFAULT) + ? newp->xoc_col_fg - 1 : 9); + } + + if (newp->xoc_col_bg != oldp->xoc_col_bg) { + cp += snprintf(cp, ep - cp, ";4%u", + (newp->xoc_col_bg != XO_COL_DEFAULT) + ? newp->xoc_col_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, unsigned len) +{ + const char *fmt = xfip->xfi_format; + unsigned 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); + + 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, unsigned len) +{ + const char *fmt = xfip->xfi_format; + unsigned 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; + int start = xop->xo_units_offset; + int 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); + + int now = xbp->xb_curp - xbp->xb_bufp; + int 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 int +xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip, + const char *str, unsigned len) +{ + const char *fmt = xfip->xfi_format; + unsigned 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, unsigned 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, unsigned 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); + + int 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; + int start = xop->xo_anchor_offset; + int stop = xbp->xb_curp - xbp->xb_bufp; + int abswidth = (width > 0) ? width : -width; + int 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; + + int now = xbp->xb_curp - xbp->xb_bufp; + int 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; + + int 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; + int 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, + xfip->xfi_clen, xfip->xfi_content ?: "", + xfip->xfi_flen, xfip->xfi_format ?: "", + 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; + + xo_buf_cleanup(&xb); + + char *new_fmt = xo_strndup(gtfmt, -1); + if (new_fmt == NULL) + goto bail2; + + *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, + unsigned *fstart, unsigned min_fstart, + unsigned *fend, unsigned max_fend) +{ + xo_field_info_t *xfip; + char *buf; + unsigned base = fstart[min_fstart]; + unsigned 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, soff, doff = base, len, fnum; + 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, + unsigned *fstart UNUSED, unsigned min_fstart UNUSED, + unsigned *fend UNUSED, unsigned max_fend UNUSED) +{ + return; +} +#endif /* HAVE_GETTEXT */ + +/* + * Emit a set of fields. This is really the core of libxo. + */ +static int +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; + int 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 */ + unsigned fstart[flimit]; + bzero(fstart, flimit * sizeof(fstart[0])); + unsigned fend[flimit]; + bzero(fend, flimit * sizeof(fend[0])); + + for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields; + 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; + int 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 */ + int 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 : (int) 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; +} + +int +xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap) +{ + int 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; +} + +int +xo_emit_h (xo_handle_t *xop, const char *fmt, ...) +{ + int 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; +} + +int +xo_emit (const char *fmt, ...) +{ + xo_handle_t *xop = xo_default(NULL); + int 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; +} + +int +xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags, + const char *fmt, va_list vap) +{ + int 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; +} + +int +xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...) +{ + int 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; +} + +int +xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...) +{ + xo_handle_t *xop = xo_default(NULL); + int 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. + */ +int +xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents, + const char *fmt, const char *efmt, + va_list vap) +{ + int 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; +} + +int +xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents, + const char *fmt, const char *efmt, ...) +{ + int rc; + va_list vap; + + va_start(vap, efmt); + rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap); + va_end(vap); + + return rc; +} + +int +xo_emit_field (const char *rolmod, const char *contents, + const char *fmt, const char *efmt, ...) +{ + int rc; + va_list vap; + + va_start(vap, efmt); + rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap); + va_end(vap); + + return rc; +} + +int +xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap) +{ + const int extra = 5; /* space, equals, quote, quote, and nul */ + xop = xo_default(xop); + + int rc = 0; + int nlen = strlen(name); + xo_buffer_t *xbp = &xop->xo_attrs; + unsigned 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)); + } + } + + return rc; +} + +int +xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...) +{ + int rc; + va_list vap; + + va_start(vap, fmt); + rc = xo_attr_hv(xop, name, fmt, vap); + va_end(vap); + + return rc; +} + +int +xo_attr (const char *name, const char *fmt, ...) +{ + int 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 && 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 (unsigned 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 int +xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) +{ + int 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); + 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); +} + +int +xo_open_container_h (xo_handle_t *xop, const char *name) +{ + return xo_open_container_hf(xop, 0, name); +} + +int +xo_open_container (const char *name) +{ + return xo_open_container_hf(NULL, 0, name); +} + +int +xo_open_container_hd (xo_handle_t *xop, const char *name) +{ + return xo_open_container_hf(xop, XOF_DTRT, name); +} + +int +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); + + int 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) { + int 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); + break; + } + + return rc; +} + +int +xo_close_container_h (xo_handle_t *xop, const char *name) +{ + return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER); +} + +int +xo_close_container (const char *name) +{ + return xo_close_container_h(NULL, name); +} + +int +xo_close_container_hd (xo_handle_t *xop) +{ + return xo_close_container_h(xop, NULL); +} + +int +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) +{ + int 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); + 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); +} + +int +xo_open_list_h (xo_handle_t *xop, const char *name) +{ + return xo_open_list_hf(xop, 0, name); +} + +int +xo_open_list (const char *name) +{ + return xo_open_list_hf(NULL, 0, name); +} + +int +xo_open_list_hd (xo_handle_t *xop, const char *name) +{ + return xo_open_list_hf(xop, XOF_DTRT, name); +} + +int +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) +{ + int 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) { + int 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); + 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; +} + +int +xo_close_list_h (xo_handle_t *xop, const char *name) +{ + return xo_transition(xop, 0, name, XSS_CLOSE_LIST); +} + +int +xo_close_list (const char *name) +{ + return xo_close_list_h(NULL, name); +} + +int +xo_close_list_hd (xo_handle_t *xop) +{ + return xo_close_list_h(xop, NULL); +} + +int +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) +{ + int 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); + 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) +{ + int 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) { + int 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); + /* 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); + + int 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); + 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); +} + +int +xo_open_instance_h (xo_handle_t *xop, const char *name) +{ + return xo_open_instance_hf(xop, 0, name); +} + +int +xo_open_instance (const char *name) +{ + return xo_open_instance_hf(NULL, 0, name); +} + +int +xo_open_instance_hd (xo_handle_t *xop, const char *name) +{ + return xo_open_instance_hf(xop, XOF_DTRT, name); +} + +int +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); + + int 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) { + int 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); + break; + } + + return rc; +} + +int +xo_close_instance_h (xo_handle_t *xop, const char *name) +{ + return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE); +} + +int +xo_close_instance (const char *name) +{ + return xo_close_instance_h(NULL, name); +} + +int +xo_close_instance_hd (xo_handle_t *xop) +{ + return xo_close_instance_h(xop, NULL); +} + +int +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; + int 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; + int 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 int +xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name, + xo_state_t new_state) +{ + xo_stack_t *xsp; + int rc; + int old_state, on_marker; + + xop = xo_default(xop); + + rc = 0; + 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; +} + +int +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; +} + +int +xo_open_marker (const char *name) +{ + return xo_open_marker_h(NULL, name); +} + +int +xo_close_marker_h (xo_handle_t *xop, const char *name) +{ + xop = xo_default(xop); + + return xo_do_close(xop, name, XSS_MARKER); +} + +int +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; +} + +int +xo_flush_h (xo_handle_t *xop) +{ + int rc; + + xop = xo_default(xop); + + switch (xo_style(xop)) { + case XO_STYLE_ENCODER: + xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL); + } + + rc = xo_write(xop); + if (rc >= 0 && xop->xo_flush) + if (xop->xo_flush(xop->xo_opaque) < 0) + return -1; + + return rc; +} + +int +xo_flush (void) +{ + return xo_flush_h(NULL); +} + +int +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); + break; + } + + return xo_flush_h(xop); +} + +int +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. + */ + int 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; + + 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(NULL, cp) < 0) + return -1; + } else if (*cp == ':') { + if (xo_set_options(NULL, cp) < 0) + return -1; + + } else if (*cp == '=') { + if (xo_set_options(NULL, ++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; + } + } + + 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); + 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); + + int 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; +} -- cgit v1.2.3