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