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