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