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