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