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