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