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