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