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