]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libxo/libxo/libxo.c
MFV r282150
[FreeBSD/FreeBSD.git] / contrib / libxo / libxo / libxo.c
1 /*
2  * Copyright (c) 2014-2015, Juniper Networks, Inc.
3  * All rights reserved.
4  * This SOFTWARE is licensed under the LICENSE provided in the
5  * ../Copyright file. By downloading, installing, copying, or otherwise
6  * using the SOFTWARE, you agree to be bound by the terms of that
7  * LICENSE.
8  * Phil Shafer, July 2014
9  */
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdint.h>
14 #include <unistd.h>
15 #include <stddef.h>
16 #include <wchar.h>
17 #include <locale.h>
18 #include <sys/types.h>
19 #include <stdarg.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <limits.h>
23 #include <ctype.h>
24 #include <wctype.h>
25 #include <getopt.h>
26
27 #include "xoconfig.h"
28 #include "xo.h"
29 #include "xoversion.h"
30
31 #ifdef HAVE_STDIO_EXT_H
32 #include <stdio_ext.h>
33 #endif /* HAVE_STDIO_EXT_H */
34
35 const char xo_version[] = LIBXO_VERSION;
36 const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
37
38 #ifndef UNUSED
39 #define UNUSED __attribute__ ((__unused__))
40 #endif /* UNUSED */
41
42 #define XO_INDENT_BY 2  /* Amount to indent when pretty printing */
43 #define XO_BUFSIZ       (8*1024) /* Initial buffer size */
44 #define XO_DEPTH        512      /* Default stack depth */
45 #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
46
47 #define XO_FAILURE_NAME "failure"
48
49 /*
50  * xo_buffer_t: a memory buffer that can be grown as needed.  We
51  * use them for building format strings and output data.
52  */
53 typedef struct xo_buffer_s {
54     char *xb_bufp;              /* Buffer memory */
55     char *xb_curp;              /* Current insertion point */
56     int xb_size;                /* Size of buffer */
57 } xo_buffer_t;
58
59 /* Flags for the stack frame */
60 typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
61 #define XSF_NOT_FIRST   (1<<0)  /* Not the first element */
62 #define XSF_LIST        (1<<1)  /* Frame is a list */
63 #define XSF_INSTANCE    (1<<2)  /* Frame is an instance */
64 #define XSF_DTRT        (1<<3)  /* Save the name for DTRT mode */
65
66 #define XSF_CONTENT     (1<<4)  /* Some content has been emitted */
67 #define XSF_EMIT        (1<<5)  /* Some field has been emitted */
68 #define XSF_EMIT_KEY    (1<<6)  /* A key has been emitted */
69 #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
70
71 /* These are the flags we propagate between markers and their parents */
72 #define XSF_MARKER_FLAGS \
73  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
74
75 /*
76  * A word about states:  We're moving to a finite state machine (FMS)
77  * approach to help remove fragility from the caller's code.  Instead
78  * of requiring a specific order of calls, we'll allow the caller more
79  * flexibility and make the library responsible for recovering from
80  * missed steps.  The goal is that the library should not be capable of
81  * emitting invalid xml or json, but the developer shouldn't need
82  * to know or understand all the details about these encodings.
83  *
84  * You can think of states as either states or event, since they
85  * function rather like both.  None of the XO_CLOSE_* events will
86  * persist as states, since their stack frame will be popped.
87  * Same is true of XSS_EMIT, which is an event that asks us to
88  * prep for emitting output fields.
89  */
90
91 /* Stack frame states */
92 typedef unsigned xo_state_t;
93 #define XSS_INIT                0       /* Initial stack state */
94 #define XSS_OPEN_CONTAINER      1
95 #define XSS_CLOSE_CONTAINER     2
96 #define XSS_OPEN_LIST           3
97 #define XSS_CLOSE_LIST          4
98 #define XSS_OPEN_INSTANCE       5
99 #define XSS_CLOSE_INSTANCE      6
100 #define XSS_OPEN_LEAF_LIST      7
101 #define XSS_CLOSE_LEAF_LIST     8
102 #define XSS_DISCARDING          9       /* Discarding data until recovered */
103 #define XSS_MARKER              10      /* xo_open_marker's marker */
104 #define XSS_EMIT                11      /* xo_emit has a leaf field */
105 #define XSS_EMIT_LEAF_LIST      12      /* xo_emit has a leaf-list ({l:}) */
106 #define XSS_FINISH              13      /* xo_finish was called */
107
108 #define XSS_MAX                 13
109
110 #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
111
112 /*
113  * xo_stack_t: As we open and close containers and levels, we
114  * create a stack of frames to track them.  This is needed for
115  * XOF_WARN and XOF_XPATH.
116  */
117 typedef struct xo_stack_s {
118     xo_xsf_flags_t xs_flags;    /* Flags for this frame */
119     xo_state_t xs_state;        /* State for this stack frame */
120     char *xs_name;              /* Name (for XPath value) */
121     char *xs_keys;              /* XPath predicate for any key fields */
122 } xo_stack_t;
123
124 /* "colors" refers to fancy ansi codes */
125 #define XO_COL_DEFAULT          0
126 #define XO_COL_BLACK            1
127 #define XO_COL_RED              2
128 #define XO_COL_GREEN            3
129 #define XO_COL_YELLOW           4
130 #define XO_COL_BLUE             5
131 #define XO_COL_MAGENTA          6
132 #define XO_COL_CYAN             7
133 #define XO_COL_WHITE            8
134
135 #define XO_NUM_COLORS           9
136
137 /* "effects" refers to fancy ansi codes */
138 /*
139  * Yes, there's no blink.  We're civilized.  We like users.  Blink
140  * isn't something one does to someone you like.  Friends don't let
141  * friends use blink.  On friends.  You know what I mean.  Blink is
142  * like, well, it's like bursting into show tunes at a funeral.  It's
143  * just not done.  Not something anyone wants.  And on those rare
144  * instances where it might actually be appropriate, it's still wrong.
145  * It's likely done my the wrong person for the wrong reason.  Just
146  * like blink.  And if I implemented blink, I'd be like a funeral
147  * director who adds "Would you like us to burst into show tunes?" on
148  * the list of questions asking while making funeral arrangements.
149  * It's formalizing wrongness in the wrong way.  And we're just too
150  * civilized to do that.   Hhhmph!
151  */
152 #define XO_EFF_RESET            (1<<0)
153 #define XO_EFF_NORMAL           (1<<1)
154 #define XO_EFF_BOLD             (1<<2)
155 #define XO_EFF_UNDERLINE        (1<<3)
156 #define XO_EFF_INVERSE          (1<<4)
157
158 #define XO_EFF_CLEAR_BITS       XO_EFF_RESET
159
160 typedef uint8_t xo_effect_t;
161 typedef uint8_t xo_color_t;
162 typedef struct xo_colors_s {
163     xo_effect_t xoc_effects;    /* Current effect set */
164     xo_color_t xoc_col_fg;      /* Foreground color */
165     xo_color_t xoc_col_bg;      /* Background color */
166 } xo_colors_t;
167
168 /*
169  * xo_handle_t: this is the principle data structure for libxo.
170  * It's used as a store for state, options, and content.
171  */
172 struct xo_handle_s {
173     xo_xof_flags_t xo_flags;    /* Flags */
174     unsigned short xo_style;    /* XO_STYLE_* value */
175     unsigned short xo_indent;   /* Indent level (if pretty) */
176     unsigned short xo_indent_by; /* Indent amount (tab stop) */
177     xo_write_func_t xo_write;   /* Write callback */
178     xo_close_func_t xo_close;   /* Close callback */
179     xo_flush_func_t xo_flush;   /* Flush callback */
180     xo_formatter_t xo_formatter; /* Custom formating function */
181     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
182     void *xo_opaque;            /* Opaque data for write function */
183     xo_buffer_t xo_data;        /* Output data */
184     xo_buffer_t xo_fmt;         /* Work area for building format strings */
185     xo_buffer_t xo_attrs;       /* Work area for building XML attributes */
186     xo_buffer_t xo_predicate;   /* Work area for building XPath predicates */
187     xo_stack_t *xo_stack;       /* Stack pointer */
188     int xo_depth;               /* Depth of stack */
189     int xo_stack_size;          /* Size of the stack */
190     xo_info_t *xo_info;         /* Info fields for all elements */
191     int xo_info_count;          /* Number of info entries */
192     va_list xo_vap;             /* Variable arguments (stdargs) */
193     char *xo_leading_xpath;     /* A leading XPath expression */
194     mbstate_t xo_mbstate;       /* Multi-byte character conversion state */
195     unsigned xo_anchor_offset;  /* Start of anchored text */
196     unsigned xo_anchor_columns; /* Number of columns since the start anchor */
197     int xo_anchor_min_width;    /* Desired width of anchored text */
198     unsigned xo_units_offset;   /* Start of units insertion point */
199     unsigned xo_columns;        /* Columns emitted during this xo_emit call */
200     uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
201     uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
202     xo_colors_t xo_colors;      /* Current color and effect values */
203     xo_buffer_t xo_color_buf;   /* HTML: buffer of colors and effects */
204     char *xo_version;           /* Version string */
205 };
206
207 /* Flags for formatting functions */
208 typedef unsigned long xo_xff_flags_t;
209 #define XFF_COLON       (1<<0)  /* Append a ":" */
210 #define XFF_COMMA       (1<<1)  /* Append a "," iff there's more output */
211 #define XFF_WS          (1<<2)  /* Append a blank */
212 #define XFF_ENCODE_ONLY (1<<3)  /* Only emit for encoding formats (xml, json) */
213
214 #define XFF_QUOTE       (1<<4)  /* Force quotes */
215 #define XFF_NOQUOTE     (1<<5)  /* Force no quotes */
216 #define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display formats (text and html) */
217 #define XFF_KEY         (1<<7)  /* Field is a key (for XPath) */
218
219 #define XFF_XML         (1<<8)  /* Force XML encoding style (for XPath) */
220 #define XFF_ATTR        (1<<9)  /* Escape value using attribute rules (XML) */
221 #define XFF_BLANK_LINE  (1<<10) /* Emit a blank line */
222 #define XFF_NO_OUTPUT   (1<<11) /* Do not make any output */
223
224 #define XFF_TRIM_WS     (1<<12) /* Trim whitespace off encoded values */
225 #define XFF_LEAF_LIST   (1<<13) /* A leaf-list (list of values) */
226 #define XFF_UNESCAPE    (1<<14) /* Need to printf-style unescape the value */
227
228 /*
229  * Normal printf has width and precision, which for strings operate as
230  * min and max number of columns.  But this depends on the idea that
231  * one byte means one column, which UTF-8 and multi-byte characters
232  * pitches on its ear.  It may take 40 bytes of data to populate 14
233  * columns, but we can't go off looking at 40 bytes of data without the
234  * caller's permission for fear/knowledge that we'll generate core files.
235  * 
236  * So we make three values, distinguishing between "max column" and
237  * "number of bytes that we will inspect inspect safely" We call the
238  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
239  *
240  * Under the "first do no harm" theory, we default "max" to "size".
241  * This is a reasonable assumption for folks that don't grok the
242  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
243  * be evil.
244  *
245  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
246  * columns of output, but will never look at more than 14 bytes of the
247  * input buffer.  This is mostly compatible with printf and caller's
248  * expectations.
249  *
250  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
251  * many bytes (or until a NUL is seen) are needed to fill 14 columns
252  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
253  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
254  * of output.
255  *
256  * It's fairly amazing how a good idea (handle all languages of the
257  * world) blows such a big hole in the bottom of the fairly weak boat
258  * that is C string handling.  The simplicity and completenesss are
259  * sunk in ways we haven't even begun to understand.
260  */
261
262 #define XF_WIDTH_MIN    0       /* Minimal width */
263 #define XF_WIDTH_SIZE   1       /* Maximum number of bytes to examine */
264 #define XF_WIDTH_MAX    2       /* Maximum width */
265 #define XF_WIDTH_NUM    3       /* Numeric fields in printf (min.size.max) */
266
267 /* Input and output string encodings */
268 #define XF_ENC_WIDE     1       /* Wide characters (wchar_t) */
269 #define XF_ENC_UTF8     2       /* UTF-8 */
270 #define XF_ENC_LOCALE   3       /* Current locale */
271
272 /*
273  * A place to parse printf-style format flags for each field
274  */
275 typedef struct xo_format_s {
276     unsigned char xf_fc;        /* Format character */
277     unsigned char xf_enc;       /* Encoding of the string (XF_ENC_*) */
278     unsigned char xf_skip;      /* Skip this field */
279     unsigned char xf_lflag;     /* 'l' (long) */
280     unsigned char xf_hflag;;    /* 'h' (half) */
281     unsigned char xf_jflag;     /* 'j' (intmax_t) */
282     unsigned char xf_tflag;     /* 't' (ptrdiff_t) */
283     unsigned char xf_zflag;     /* 'z' (size_t) */
284     unsigned char xf_qflag;     /* 'q' (quad_t) */
285     unsigned char xf_seen_minus; /* Seen a minus */
286     int xf_leading_zero;        /* Seen a leading zero (zero fill)  */
287     unsigned xf_dots;           /* Seen one or more '.'s */
288     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
289     unsigned xf_stars;          /* Seen one or more '*'s */
290     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
291 } xo_format_t;
292
293 /*
294  * We keep a default handle to allow callers to avoid having to
295  * allocate one.  Passing NULL to any of our functions will use
296  * this default handle.
297  */
298 static xo_handle_t xo_default_handle;
299 static int xo_default_inited;
300 static int xo_locale_inited;
301 static const char *xo_program;
302
303 /*
304  * To allow libxo to be used in diverse environment, we allow the
305  * caller to give callbacks for memory allocation.
306  */
307 static xo_realloc_func_t xo_realloc = realloc;
308 static xo_free_func_t xo_free = free;
309
310 /* Forward declarations */
311 static void
312 xo_failure (xo_handle_t *xop, const char *fmt, ...);
313
314 static int
315 xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
316                xo_state_t new_state);
317
318 static void
319 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
320                    const char *name, int nlen,
321                    const char *value, int vlen,
322                    const char *encoding, int elen);
323
324 static void
325 xo_anchor_clear (xo_handle_t *xop);
326
327 /*
328  * xo_style is used to retrieve the current style.  When we're built
329  * for "text only" mode, we use this function to drive the removal
330  * of most of the code in libxo.  We return a constant and the compiler
331  * happily removes the non-text code that is not longer executed.  This
332  * trims our code nicely without needing to trampel perfectly readable
333  * code with ifdefs.
334  */
335 static inline unsigned short
336 xo_style (xo_handle_t *xop UNUSED)
337 {
338 #ifdef LIBXO_TEXT_ONLY
339     return XO_STYLE_TEXT;
340 #else /* LIBXO_TEXT_ONLY */
341     return xop->xo_style;
342 #endif /* LIBXO_TEXT_ONLY */
343 }
344
345 /*
346  * Callback to write data to a FILE pointer
347  */
348 static int
349 xo_write_to_file (void *opaque, const char *data)
350 {
351     FILE *fp = (FILE *) opaque;
352
353     return fprintf(fp, "%s", data);
354 }
355
356 /*
357  * Callback to close a file
358  */
359 static void
360 xo_close_file (void *opaque)
361 {
362     FILE *fp = (FILE *) opaque;
363
364     fclose(fp);
365 }
366
367 /*
368  * Callback to flush a FILE pointer
369  */
370 static int
371 xo_flush_file (void *opaque)
372 {
373     FILE *fp = (FILE *) opaque;
374
375     return fflush(fp);
376 }
377
378 /*
379  * Initialize the contents of an xo_buffer_t.
380  */
381 static void
382 xo_buf_init (xo_buffer_t *xbp)
383 {
384     xbp->xb_size = XO_BUFSIZ;
385     xbp->xb_bufp = xo_realloc(NULL, xbp->xb_size);
386     xbp->xb_curp = xbp->xb_bufp;
387 }
388
389 /*
390  * Reset the buffer to empty
391  */
392 static void
393 xo_buf_reset (xo_buffer_t *xbp)
394 {
395     xbp->xb_curp = xbp->xb_bufp;
396 }
397
398 /*
399  * Reset the buffer to empty
400  */
401 static int
402 xo_buf_is_empty (xo_buffer_t *xbp)
403 {
404     return (xbp->xb_curp == xbp->xb_bufp);
405 }
406
407 /*
408  * Initialize the contents of an xo_buffer_t.
409  */
410 static void
411 xo_buf_cleanup (xo_buffer_t *xbp)
412 {
413     if (xbp->xb_bufp)
414         xo_free(xbp->xb_bufp);
415     bzero(xbp, sizeof(*xbp));
416 }
417
418 static int
419 xo_depth_check (xo_handle_t *xop, int depth)
420 {
421     xo_stack_t *xsp;
422
423     if (depth >= xop->xo_stack_size) {
424         depth += 16;
425         xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
426         if (xsp == NULL) {
427             xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
428             return 0;
429         }
430
431         int count = depth - xop->xo_stack_size;
432
433         bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
434         xop->xo_stack_size = depth;
435         xop->xo_stack = xsp;
436     }
437
438     return 0;
439 }
440
441 void
442 xo_no_setlocale (void)
443 {
444     xo_locale_inited = 1;       /* Skip initialization */
445 }
446
447 /*
448  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
449  * standard way to decide this (e.g. getlinebuf()), we have configure
450  * look to find __flbf, which glibc supported.  If not, we'll rely on
451  * isatty, with the assumption that terminals are the only thing
452  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
453  * which is all __flbf does, but that's even tackier.  Like a
454  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
455  * something we're willing to do.
456  */
457 static int
458 xo_is_line_buffered (FILE *stream)
459 {
460 #if HAVE___FLBF
461     if (__flbf(stream))
462         return 1;
463 #else /* HAVE___FLBF */
464     if (isatty(fileno(stream)))
465         return 1;
466 #endif /* HAVE___FLBF */
467     return 0;
468 }
469
470 /*
471  * Initialize an xo_handle_t, using both static defaults and
472  * the global settings from the LIBXO_OPTIONS environment
473  * variable.
474  */
475 static void
476 xo_init_handle (xo_handle_t *xop)
477 {
478     xop->xo_opaque = stdout;
479     xop->xo_write = xo_write_to_file;
480     xop->xo_flush = xo_flush_file;
481
482     if (xo_is_line_buffered(stdout))
483         xop->xo_flags |= XOF_FLUSH_LINE;
484
485     /*
486      * We only want to do color output on terminals, but we only want
487      * to do this if the user has asked for color.
488      */
489     if ((xop->xo_flags & XOF_COLOR_ALLOWED) && isatty(1))
490         xop->xo_flags |= XOF_COLOR;
491
492     /*
493      * We need to initialize the locale, which isn't really pretty.
494      * Libraries should depend on their caller to set up the
495      * environment.  But we really can't count on the caller to do
496      * this, because well, they won't.  Trust me.
497      */
498     if (!xo_locale_inited) {
499         xo_locale_inited = 1;   /* Only do this once */
500
501         const char *cp = getenv("LC_CTYPE");
502         if (cp == NULL)
503             cp = getenv("LANG");
504         if (cp == NULL)
505             cp = getenv("LC_ALL");
506         if (cp == NULL)
507             cp = "UTF-8";       /* Optimistic? */
508         (void) setlocale(LC_CTYPE, cp);
509     }
510
511     /*
512      * Initialize only the xo_buffers we know we'll need; the others
513      * can be allocated as needed.
514      */
515     xo_buf_init(&xop->xo_data);
516     xo_buf_init(&xop->xo_fmt);
517
518     xop->xo_indent_by = XO_INDENT_BY;
519     xo_depth_check(xop, XO_DEPTH);
520
521 #if !defined(NO_LIBXO_OPTIONS)
522     if (!(xop->xo_flags & XOF_NO_ENV)) {
523         char *env = getenv("LIBXO_OPTIONS");
524         if (env)
525             xo_set_options(xop, env);
526     }
527 #endif /* NO_GETENV */
528 }
529
530 /*
531  * Initialize the default handle.
532  */
533 static void
534 xo_default_init (void)
535 {
536     xo_handle_t *xop = &xo_default_handle;
537
538     xo_init_handle(xop);
539
540     xo_default_inited = 1;
541 }
542
543 /*
544  * Does the buffer have room for the given number of bytes of data?
545  * If not, realloc the buffer to make room.  If that fails, we
546  * return 0 to tell the caller they are in trouble.
547  */
548 static int
549 xo_buf_has_room (xo_buffer_t *xbp, int len)
550 {
551     if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) {
552         int sz = xbp->xb_size + XO_BUFSIZ;
553         char *bp = xo_realloc(xbp->xb_bufp, sz);
554         if (bp == NULL) {
555             /*
556              * XXX If we wanted to put a stick XOF_ENOMEM on xop,
557              * this would be the place to do it.  But we'd need
558              * to churn the code to pass xop in here....
559              */
560             return 0;
561         }
562
563         xbp->xb_curp = bp + (xbp->xb_curp - xbp->xb_bufp);
564         xbp->xb_bufp = bp;
565         xbp->xb_size = sz;
566     }
567
568     return 1;
569 }
570
571 /*
572  * Cheap convenience function to return either the argument, or
573  * the internal handle, after it has been initialized.  The usage
574  * is:
575  *    xop = xo_default(xop);
576  */
577 static xo_handle_t *
578 xo_default (xo_handle_t *xop)
579 {
580     if (xop == NULL) {
581         if (xo_default_inited == 0)
582             xo_default_init();
583         xop = &xo_default_handle;
584     }
585
586     return xop;
587 }
588
589 /*
590  * Return the number of spaces we should be indenting.  If
591  * we are pretty-printing, this is indent * indent_by.
592  */
593 static int
594 xo_indent (xo_handle_t *xop)
595 {
596     int rc = 0;
597
598     xop = xo_default(xop);
599
600     if (xop->xo_flags & XOF_PRETTY) {
601         rc = xop->xo_indent * xop->xo_indent_by;
602         if (xop->xo_flags & XOF_TOP_EMITTED)
603             rc += xop->xo_indent_by;
604     }
605
606     return (rc > 0) ? rc : 0;
607 }
608
609 static void
610 xo_buf_indent (xo_handle_t *xop, int indent)
611 {
612     xo_buffer_t *xbp = &xop->xo_data;
613
614     if (indent <= 0)
615         indent = xo_indent(xop);
616
617     if (!xo_buf_has_room(xbp, indent))
618         return;
619
620     memset(xbp->xb_curp, ' ', indent);
621     xbp->xb_curp += indent;
622 }
623
624 static char xo_xml_amp[] = "&amp;";
625 static char xo_xml_lt[] = "&lt;";
626 static char xo_xml_gt[] = "&gt;";
627 static char xo_xml_quot[] = "&quot;";
628
629 static int
630 xo_escape_xml (xo_buffer_t *xbp, int len, int attr)
631 {
632     int slen;
633     unsigned delta = 0;
634     char *cp, *ep, *ip;
635     const char *sp;
636
637     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
638         /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
639         if (*cp == '<')
640             delta += sizeof(xo_xml_lt) - 2;
641         else if (*cp == '>')
642             delta += sizeof(xo_xml_gt) - 2;
643         else if (*cp == '&')
644             delta += sizeof(xo_xml_amp) - 2;
645         else if (attr && *cp == '"')
646             delta += sizeof(xo_xml_quot) - 2;
647     }
648
649     if (delta == 0)             /* Nothing to escape; bail */
650         return len;
651
652     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
653         return 0;
654
655     ep = xbp->xb_curp;
656     cp = ep + len;
657     ip = cp + delta;
658     do {
659         cp -= 1;
660         ip -= 1;
661
662         if (*cp == '<')
663             sp = xo_xml_lt;
664         else if (*cp == '>')
665             sp = xo_xml_gt;
666         else if (*cp == '&')
667             sp = xo_xml_amp;
668         else if (attr && *cp == '"')
669             sp = xo_xml_quot;
670         else {
671             *ip = *cp;
672             continue;
673         }
674
675         slen = strlen(sp);
676         ip -= slen - 1;
677         memcpy(ip, sp, slen);
678         
679     } while (cp > ep && cp != ip);
680
681     return len + delta;
682 }
683
684 static int
685 xo_escape_json (xo_buffer_t *xbp, int len)
686 {
687     unsigned delta = 0;
688     char *cp, *ep, *ip;
689
690     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
691         if (*cp == '\\' || *cp == '"')
692             delta += 1;
693         else if (*cp == '\n' || *cp == '\r')
694             delta += 1;
695     }
696
697     if (delta == 0)             /* Nothing to escape; bail */
698         return len;
699
700     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
701         return 0;
702
703     ep = xbp->xb_curp;
704     cp = ep + len;
705     ip = cp + delta;
706     do {
707         cp -= 1;
708         ip -= 1;
709
710         if (*cp == '\\' || *cp == '"') {
711             *ip-- = *cp;
712             *ip = '\\';
713         } else if (*cp == '\n') {
714             *ip-- = 'n';
715             *ip = '\\';
716         } else if (*cp == '\r') {
717             *ip-- = 'r';
718             *ip = '\\';
719         } else {
720             *ip = *cp;
721         }
722         
723     } while (cp > ep && cp != ip);
724
725     return len + delta;
726 }
727
728 /*
729  * Append the given string to the given buffer
730  */
731 static void
732 xo_buf_append (xo_buffer_t *xbp, const char *str, int len)
733 {
734     if (!xo_buf_has_room(xbp, len))
735         return;
736
737     memcpy(xbp->xb_curp, str, len);
738     xbp->xb_curp += len;
739 }
740
741 /*
742  * Append the given NUL-terminated string to the given buffer
743  */
744 static void
745 xo_buf_append_str (xo_buffer_t *xbp, const char *str)
746 {
747     int len = strlen(str);
748
749     if (!xo_buf_has_room(xbp, len))
750         return;
751
752     memcpy(xbp->xb_curp, str, len);
753     xbp->xb_curp += len;
754 }
755
756 static void
757 xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
758                const char *str, int len, xo_xff_flags_t flags)
759 {
760     if (!xo_buf_has_room(xbp, len))
761         return;
762
763     memcpy(xbp->xb_curp, str, len);
764
765     switch (xo_style(xop)) {
766     case XO_STYLE_XML:
767     case XO_STYLE_HTML:
768         len = xo_escape_xml(xbp, len, (flags & XFF_ATTR));
769         break;
770
771     case XO_STYLE_JSON:
772         len = xo_escape_json(xbp, len);
773         break;
774     }
775
776     xbp->xb_curp += len;
777 }
778
779 /*
780  * Write the current contents of the data buffer using the handle's
781  * xo_write function.
782  */
783 static int
784 xo_write (xo_handle_t *xop)
785 {
786     int rc = 0;
787     xo_buffer_t *xbp = &xop->xo_data;
788
789     if (xbp->xb_curp != xbp->xb_bufp) {
790         xo_buf_append(xbp, "", 1); /* Append ending NUL */
791         xo_anchor_clear(xop);
792         rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
793         xbp->xb_curp = xbp->xb_bufp;
794     }
795
796     /* Turn off the flags that don't survive across writes */
797     xop->xo_flags &= ~(XOF_UNITS_PENDING);
798
799     return rc;
800 }
801
802 /*
803  * Format arguments into our buffer.  If a custom formatter has been set,
804  * we use that to do the work; otherwise we vsnprintf().
805  */
806 static int
807 xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
808 {
809     va_list va_local;
810     int rc;
811     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
812
813     va_copy(va_local, vap);
814
815     if (xop->xo_formatter)
816         rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
817     else
818         rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
819
820     if (rc >= left) {
821         if (!xo_buf_has_room(xbp, rc)) {
822             va_end(va_local);
823             return -1;
824         }
825
826         /*
827          * After we call vsnprintf(), the stage of vap is not defined.
828          * We need to copy it before we pass.  Then we have to do our
829          * own logic below to move it along.  This is because the
830          * implementation can have va_list be a pointer (bsd) or a
831          * structure (macosx) or anything in between.
832          */
833
834         va_end(va_local);       /* Reset vap to the start */
835         va_copy(va_local, vap);
836
837         left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
838         if (xop->xo_formatter)
839             rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
840         else
841             rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
842     }
843     va_end(va_local);
844
845     return rc;
846 }
847
848 /*
849  * Print some data thru the handle.
850  */
851 static int
852 xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
853 {
854     xo_buffer_t *xbp = &xop->xo_data;
855     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
856     int rc;
857     va_list va_local;
858
859     va_copy(va_local, vap);
860
861     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
862
863     if (rc > xbp->xb_size) {
864         if (!xo_buf_has_room(xbp, rc)) {
865             va_end(va_local);
866             return -1;
867         }
868
869         va_end(va_local);       /* Reset vap to the start */
870         va_copy(va_local, vap);
871
872         left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
873         rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
874     }
875
876     va_end(va_local);
877
878     if (rc > 0)
879         xbp->xb_curp += rc;
880
881     return rc;
882 }
883
884 static int
885 xo_printf (xo_handle_t *xop, const char *fmt, ...)
886 {
887     int rc;
888     va_list vap;
889
890     va_start(vap, fmt);
891
892     rc = xo_printf_v(xop, fmt, vap);
893
894     va_end(vap);
895     return rc;
896 }
897
898 /*
899  * These next few function are make The Essential UTF-8 Ginsu Knife.
900  * Identify an input and output character, and convert it.
901  */
902 static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
903
904 static int
905 xo_is_utf8 (char ch)
906 {
907     return (ch & 0x80);
908 }
909
910 static int
911 xo_utf8_to_wc_len (const char *buf)
912 {
913     unsigned b = (unsigned char) *buf;
914     int len;
915
916     if ((b & 0x80) == 0x0)
917         len = 1;
918     else if ((b & 0xe0) == 0xc0)
919         len = 2;
920     else if ((b & 0xf0) == 0xe0)
921         len = 3;
922     else if ((b & 0xf8) == 0xf0)
923         len = 4;
924     else if ((b & 0xfc) == 0xf8)
925         len = 5;
926     else if ((b & 0xfe) == 0xfc)
927         len = 6;
928     else
929         len = -1;
930
931     return len;
932 }
933
934 static int
935 xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
936 {
937
938     unsigned b = (unsigned char) *buf;
939     int len, i;
940
941     len = xo_utf8_to_wc_len(buf);
942     if (len == -1) {
943         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
944         return -1;
945     }
946
947     if (len > bufsiz) {
948         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
949                    b, len, bufsiz);
950         return -1;
951     }
952
953     for (i = 2; i < len; i++) {
954         b = (unsigned char ) buf[i];
955         if ((b & 0xc0) != 0x80) {
956             xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
957             return -1;
958         }
959     }
960
961     return len;
962 }
963
964 /*
965  * Build a wide character from the input buffer; the number of
966  * bits we pull off the first character is dependent on the length,
967  * but we put 6 bits off all other bytes.
968  */
969 static wchar_t
970 xo_utf8_char (const char *buf, int len)
971 {
972     int i;
973     wchar_t wc;
974     const unsigned char *cp = (const unsigned char *) buf;
975
976     wc = *cp & xo_utf8_bits[len];
977     for (i = 1; i < len; i++) {
978         wc <<= 6;
979         wc |= cp[i] & 0x3f;
980         if ((cp[i] & 0xc0) != 0x80)
981             return (wchar_t) -1;
982     }
983
984     return wc;
985 }
986
987 /*
988  * Determine the number of bytes needed to encode a wide character.
989  */
990 static int
991 xo_utf8_emit_len (wchar_t wc)
992 {
993     int len;
994
995     if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
996         len = 1;
997     else if ((wc & ((1<<11) - 1)) == wc)
998         len = 2;
999     else if ((wc & ((1<<16) - 1)) == wc)
1000         len = 3;
1001     else if ((wc & ((1<<21) - 1)) == wc)
1002         len = 4;
1003     else if ((wc & ((1<<26) - 1)) == wc)
1004         len = 5;
1005     else
1006         len = 6;
1007
1008     return len;
1009 }
1010
1011 static void
1012 xo_utf8_emit_char (char *buf, int len, wchar_t wc)
1013 {
1014     int i;
1015
1016     if (len == 1) { /* Simple case */
1017         buf[0] = wc & 0x7f;
1018         return;
1019     }
1020
1021     for (i = len - 1; i >= 0; i--) {
1022         buf[i] = 0x80 | (wc & 0x3f);
1023         wc >>= 6;
1024     }
1025
1026     buf[0] &= xo_utf8_bits[len];
1027     buf[0] |= ~xo_utf8_bits[len] << 1;
1028 }
1029
1030 static int
1031 xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
1032                                 const char *ibuf, int ilen)
1033 {
1034     wchar_t wc;
1035     int len;
1036
1037     /*
1038      * Build our wide character from the input buffer; the number of
1039      * bits we pull off the first character is dependent on the length,
1040      * but we put 6 bits off all other bytes.
1041      */
1042     wc = xo_utf8_char(ibuf, ilen);
1043     if (wc == (wchar_t) -1) {
1044         xo_failure(xop, "invalid utf-8 byte sequence");
1045         return 0;
1046     }
1047
1048     if (xop->xo_flags & XOF_NO_LOCALE) {
1049         if (!xo_buf_has_room(xbp, ilen))
1050             return 0;
1051
1052         memcpy(xbp->xb_curp, ibuf, ilen);
1053         xbp->xb_curp += ilen;
1054
1055     } else {
1056         if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1057             return 0;
1058
1059         bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
1060         len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1061
1062         if (len <= 0) {
1063             xo_failure(xop, "could not convert wide char: %lx",
1064                        (unsigned long) wc);
1065             return 0;
1066         }
1067         xbp->xb_curp += len;
1068     }
1069
1070     return wcwidth(wc);
1071 }
1072
1073 static void
1074 xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
1075                       const char *cp, int len)
1076 {
1077     const char *sp = cp, *ep = cp + len;
1078     unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
1079     int slen;
1080     int cols = 0;
1081
1082     for ( ; cp < ep; cp++) {
1083         if (!xo_is_utf8(*cp)) {
1084             cols += 1;
1085             continue;
1086         }
1087
1088         /*
1089          * We're looking at a non-ascii UTF-8 character.
1090          * First we copy the previous data.
1091          * Then we need find the length and validate it.
1092          * Then we turn it into a wide string.
1093          * Then we turn it into a localized string.
1094          * Then we repeat.  Isn't i18n fun?
1095          */
1096         if (sp != cp)
1097             xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
1098
1099         slen = xo_buf_utf8_len(xop, cp, ep - cp);
1100         if (slen <= 0) {
1101             /* Bad data; back it all out */
1102             xbp->xb_curp = xbp->xb_bufp + save_off;
1103             return;
1104         }
1105
1106         cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
1107
1108         /* Next time thru, we'll start at the next character */
1109         cp += slen - 1;
1110         sp = cp + 1;
1111     }
1112
1113     /* Update column values */
1114     if (xop->xo_flags & XOF_COLUMNS)
1115         xop->xo_columns += cols;
1116     if (xop->xo_flags & XOF_ANCHOR)
1117         xop->xo_anchor_columns += cols;
1118
1119     /* Before we fall into the basic logic below, we need reset len */
1120     len = ep - sp;
1121     if (len != 0) /* Append trailing data */
1122         xo_buf_append(xbp, sp, len);
1123 }
1124
1125 /*
1126  * Append the given string to the given buffer
1127  */
1128 static void
1129 xo_data_append (xo_handle_t *xop, const char *str, int len)
1130 {
1131     xo_buf_append(&xop->xo_data, str, len);
1132 }
1133
1134 /*
1135  * Append the given string to the given buffer
1136  */
1137 static void
1138 xo_data_escape (xo_handle_t *xop, const char *str, int len)
1139 {
1140     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
1141 }
1142
1143 /*
1144  * Generate a warning.  Normally, this is a text message written to
1145  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1146  * XMLified content on standard output.
1147  */
1148 static void
1149 xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
1150              const char *fmt, va_list vap)
1151 {
1152     xop = xo_default(xop);
1153     if (check_warn && !(xop->xo_flags & XOF_WARN))
1154         return;
1155
1156     if (fmt == NULL)
1157         return;
1158
1159     int len = strlen(fmt);
1160     int plen = xo_program ? strlen(xo_program) : 0;
1161     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
1162
1163     if (plen) {
1164         memcpy(newfmt, xo_program, plen);
1165         newfmt[plen++] = ':';
1166         newfmt[plen++] = ' ';
1167     }
1168     memcpy(newfmt + plen, fmt, len);
1169     newfmt[len + plen] = '\0';
1170
1171     if (xop->xo_flags & XOF_WARN_XML) {
1172         static char err_open[] = "<error>";
1173         static char err_close[] = "</error>";
1174         static char msg_open[] = "<message>";
1175         static char msg_close[] = "</message>";
1176
1177         xo_buffer_t *xbp = &xop->xo_data;
1178
1179         xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
1180         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1181
1182         va_list va_local;
1183         va_copy(va_local, vap);
1184
1185         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1186         int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1187         if (rc > xbp->xb_size) {
1188             if (!xo_buf_has_room(xbp, rc)) {
1189                 va_end(va_local);
1190                 return;
1191             }
1192
1193             va_end(vap);        /* Reset vap to the start */
1194             va_copy(vap, va_local);
1195
1196             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1197             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1198         }
1199         va_end(va_local);
1200
1201         rc = xo_escape_xml(xbp, rc, 1);
1202         xbp->xb_curp += rc;
1203
1204         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1205         xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
1206
1207         if (code >= 0) {
1208             const char *msg = strerror(code);
1209             if (msg) {
1210                 xo_buf_append(xbp, ": ", 2);
1211                 xo_buf_append(xbp, msg, strlen(msg));
1212             }
1213         }
1214
1215         xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1216         (void) xo_write(xop);
1217
1218     } else {
1219         vfprintf(stderr, newfmt, vap);
1220         if (code >= 0) {
1221             const char *msg = strerror(code);
1222             if (msg)
1223                 fprintf(stderr, ": %s", msg);
1224         }
1225         fprintf(stderr, "\n");
1226     }
1227 }
1228
1229 void
1230 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1231 {
1232     va_list vap;
1233
1234     va_start(vap, fmt);
1235     xo_warn_hcv(xop, code, 0, fmt, vap);
1236     va_end(vap);
1237 }
1238
1239 void
1240 xo_warn_c (int code, const char *fmt, ...)
1241 {
1242     va_list vap;
1243
1244     va_start(vap, fmt);
1245     xo_warn_hcv(NULL, code, 0, fmt, vap);
1246     va_end(vap);
1247 }
1248
1249 void
1250 xo_warn (const char *fmt, ...)
1251 {
1252     int code = errno;
1253     va_list vap;
1254
1255     va_start(vap, fmt);
1256     xo_warn_hcv(NULL, code, 0, fmt, vap);
1257     va_end(vap);
1258 }
1259
1260 void
1261 xo_warnx (const char *fmt, ...)
1262 {
1263     va_list vap;
1264
1265     va_start(vap, fmt);
1266     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1267     va_end(vap);
1268 }
1269
1270 void
1271 xo_err (int eval, const char *fmt, ...)
1272 {
1273     int code = errno;
1274     va_list vap;
1275
1276     va_start(vap, fmt);
1277     xo_warn_hcv(NULL, code, 0, fmt, vap);
1278     va_end(vap);
1279     xo_finish();
1280     exit(eval);
1281 }
1282
1283 void
1284 xo_errx (int eval, const char *fmt, ...)
1285 {
1286     va_list vap;
1287
1288     va_start(vap, fmt);
1289     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1290     va_end(vap);
1291     xo_finish();
1292     exit(eval);
1293 }
1294
1295 void
1296 xo_errc (int eval, int code, const char *fmt, ...)
1297 {
1298     va_list vap;
1299
1300     va_start(vap, fmt);
1301     xo_warn_hcv(NULL, code, 0, fmt, vap);
1302     va_end(vap);
1303     xo_finish();
1304     exit(eval);
1305 }
1306
1307 /*
1308  * Generate a warning.  Normally, this is a text message written to
1309  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1310  * XMLified content on standard output.
1311  */
1312 void
1313 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1314 {
1315     static char msg_open[] = "<message>";
1316     static char msg_close[] = "</message>";
1317     xo_buffer_t *xbp;
1318     int rc;
1319     va_list va_local;
1320
1321     xop = xo_default(xop);
1322
1323     if (fmt == NULL || *fmt == '\0')
1324         return;
1325
1326     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1327
1328     switch (xo_style(xop)) {
1329     case XO_STYLE_XML:
1330         xbp = &xop->xo_data;
1331         if (xop->xo_flags & XOF_PRETTY)
1332             xo_buf_indent(xop, xop->xo_indent_by);
1333         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1334
1335         va_copy(va_local, vap);
1336
1337         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1338         rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1339         if (rc > xbp->xb_size) {
1340             if (!xo_buf_has_room(xbp, rc)) {
1341                 va_end(va_local);
1342                 return;
1343             }
1344
1345             va_end(vap);        /* Reset vap to the start */
1346             va_copy(vap, va_local);
1347
1348             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1349             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1350         }
1351         va_end(va_local);
1352
1353         rc = xo_escape_xml(xbp, rc, 1);
1354         xbp->xb_curp += rc;
1355
1356         if (need_nl && code > 0) {
1357             const char *msg = strerror(code);
1358             if (msg) {
1359                 xo_buf_append(xbp, ": ", 2);
1360                 xo_buf_append(xbp, msg, strlen(msg));
1361             }
1362         }
1363
1364         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1365         if (need_nl)
1366             xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1367         (void) xo_write(xop);
1368         break;
1369
1370     case XO_STYLE_HTML:
1371         {
1372             char buf[BUFSIZ], *bp = buf, *cp;
1373             int bufsiz = sizeof(buf);
1374             int rc2;
1375
1376             va_copy(va_local, vap);
1377
1378             rc = vsnprintf(bp, bufsiz, fmt, va_local);
1379             if (rc > bufsiz) {
1380                 bufsiz = rc + BUFSIZ;
1381                 bp = alloca(bufsiz);
1382                 va_end(va_local);
1383                 va_copy(va_local, vap);
1384                 rc = vsnprintf(bp, bufsiz, fmt, va_local);
1385             }
1386             va_end(va_local);
1387             cp = bp + rc;
1388
1389             if (need_nl) {
1390                 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1391                                (code > 0) ? ": " : "",
1392                                (code > 0) ? strerror(code) : "");
1393                 if (rc2 > 0)
1394                     rc += rc2;
1395             }
1396
1397             xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
1398         }
1399         break;
1400
1401     case XO_STYLE_JSON:
1402         /* No meanings of representing messages in JSON */
1403         break;
1404
1405     case XO_STYLE_TEXT:
1406         rc = xo_printf_v(xop, fmt, vap);
1407         /*
1408          * XXX need to handle UTF-8 widths
1409          */
1410         if (rc > 0) {
1411             if (xop->xo_flags & XOF_COLUMNS)
1412                 xop->xo_columns += rc;
1413             if (xop->xo_flags & XOF_ANCHOR)
1414                 xop->xo_anchor_columns += rc;
1415         }
1416
1417         if (need_nl && code > 0) {
1418             const char *msg = strerror(code);
1419             if (msg) {
1420                 xo_printf(xop, ": %s", msg);
1421             }
1422         }
1423         if (need_nl)
1424             xo_printf(xop, "\n");
1425
1426         break;
1427     }
1428
1429     (void) xo_flush_h(xop);
1430 }
1431
1432 void
1433 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1434 {
1435     va_list vap;
1436
1437     va_start(vap, fmt);
1438     xo_message_hcv(xop, code, fmt, vap);
1439     va_end(vap);
1440 }
1441
1442 void
1443 xo_message_c (int code, const char *fmt, ...)
1444 {
1445     va_list vap;
1446
1447     va_start(vap, fmt);
1448     xo_message_hcv(NULL, code, fmt, vap);
1449     va_end(vap);
1450 }
1451
1452 void
1453 xo_message (const char *fmt, ...)
1454 {
1455     int code = errno;
1456     va_list vap;
1457
1458     va_start(vap, fmt);
1459     xo_message_hcv(NULL, code, fmt, vap);
1460     va_end(vap);
1461 }
1462
1463 static void
1464 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1465 {
1466     if (!(xop->xo_flags & XOF_WARN))
1467         return;
1468
1469     va_list vap;
1470
1471     va_start(vap, fmt);
1472     xo_warn_hcv(xop, -1, 1, fmt, vap);
1473     va_end(vap);
1474 }
1475
1476 /**
1477  * Create a handle for use by later libxo functions.
1478  *
1479  * Note: normal use of libxo does not require a distinct handle, since
1480  * the default handle (used when NULL is passed) generates text on stdout.
1481  *
1482  * @style Style of output desired (XO_STYLE_* value)
1483  * @flags Set of XOF_* flags in use with this handle
1484  */
1485 xo_handle_t *
1486 xo_create (xo_style_t style, xo_xof_flags_t flags)
1487 {
1488     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1489
1490     if (xop) {
1491         bzero(xop, sizeof(*xop));
1492
1493         xop->xo_style  = style;
1494         xop->xo_flags = flags;
1495         xo_init_handle(xop);
1496     }
1497
1498     return xop;
1499 }
1500
1501 /**
1502  * Create a handle that will write to the given file.  Use
1503  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1504  * @fp FILE pointer to use
1505  * @style Style of output desired (XO_STYLE_* value)
1506  * @flags Set of XOF_* flags to use with this handle
1507  */
1508 xo_handle_t *
1509 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1510 {
1511     xo_handle_t *xop = xo_create(style, flags);
1512
1513     if (xop) {
1514         xop->xo_opaque = fp;
1515         xop->xo_write = xo_write_to_file;
1516         xop->xo_close = xo_close_file;
1517         xop->xo_flush = xo_flush_file;
1518     }
1519
1520     return xop;
1521 }
1522
1523 /**
1524  * Release any resources held by the handle.
1525  * @xop XO handle to alter (or NULL for default handle)
1526  */
1527 void
1528 xo_destroy (xo_handle_t *xop_arg)
1529 {
1530     xo_handle_t *xop = xo_default(xop_arg);
1531
1532     if (xop->xo_close && (xop->xo_flags & XOF_CLOSE_FP))
1533         xop->xo_close(xop->xo_opaque);
1534
1535     xo_free(xop->xo_stack);
1536     xo_buf_cleanup(&xop->xo_data);
1537     xo_buf_cleanup(&xop->xo_fmt);
1538     xo_buf_cleanup(&xop->xo_predicate);
1539     xo_buf_cleanup(&xop->xo_attrs);
1540     xo_buf_cleanup(&xop->xo_color_buf);
1541
1542     if (xop->xo_version)
1543         xo_free(xop->xo_version);
1544
1545     if (xop_arg == NULL) {
1546         bzero(&xo_default_handle, sizeof(xo_default_handle));
1547         xo_default_inited = 0;
1548     } else
1549         xo_free(xop);
1550 }
1551
1552 /**
1553  * Record a new output style to use for the given handle (or default if
1554  * handle is NULL).  This output style will be used for any future output.
1555  *
1556  * @xop XO handle to alter (or NULL for default handle)
1557  * @style new output style (XO_STYLE_*)
1558  */
1559 void
1560 xo_set_style (xo_handle_t *xop, xo_style_t style)
1561 {
1562     xop = xo_default(xop);
1563     xop->xo_style = style;
1564 }
1565
1566 xo_style_t
1567 xo_get_style (xo_handle_t *xop)
1568 {
1569     xop = xo_default(xop);
1570     return xo_style(xop);
1571 }
1572
1573 static int
1574 xo_name_to_style (const char *name)
1575 {
1576     if (strcmp(name, "xml") == 0)
1577         return XO_STYLE_XML;
1578     else if (strcmp(name, "json") == 0)
1579         return XO_STYLE_JSON;
1580     else if (strcmp(name, "text") == 0)
1581         return XO_STYLE_TEXT;
1582     else if (strcmp(name, "html") == 0)
1583         return XO_STYLE_HTML;
1584
1585     return -1;
1586 }
1587
1588 /*
1589  * Convert string name to XOF_* flag value.
1590  * Not all are useful.  Or safe.  Or sane.
1591  */
1592 static unsigned
1593 xo_name_to_flag (const char *name)
1594 {
1595     if (strcmp(name, "pretty") == 0)
1596         return XOF_PRETTY;
1597     if (strcmp(name, "warn") == 0)
1598         return XOF_WARN;
1599     if (strcmp(name, "xpath") == 0)
1600         return XOF_XPATH;
1601     if (strcmp(name, "info") == 0)
1602         return XOF_INFO;
1603     if (strcmp(name, "warn-xml") == 0)
1604         return XOF_WARN_XML;
1605     if (strcmp(name, "color") == 0)
1606         return XOF_COLOR_ALLOWED;
1607     if (strcmp(name, "columns") == 0)
1608         return XOF_COLUMNS;
1609     if (strcmp(name, "dtrt") == 0)
1610         return XOF_DTRT;
1611     if (strcmp(name, "flush") == 0)
1612         return XOF_FLUSH;
1613     if (strcmp(name, "keys") == 0)
1614         return XOF_KEYS;
1615     if (strcmp(name, "ignore-close") == 0)
1616         return XOF_IGNORE_CLOSE;
1617     if (strcmp(name, "not-first") == 0)
1618         return XOF_NOT_FIRST;
1619     if (strcmp(name, "no-locale") == 0)
1620         return XOF_NO_LOCALE;
1621     if (strcmp(name, "no-top") == 0)
1622         return XOF_NO_TOP;
1623     if (strcmp(name, "units") == 0)
1624         return XOF_UNITS;
1625     if (strcmp(name, "underscores") == 0)
1626         return XOF_UNDERSCORES;
1627
1628     return 0;
1629 }
1630
1631 int
1632 xo_set_style_name (xo_handle_t *xop, const char *name)
1633 {
1634     if (name == NULL)
1635         return -1;
1636
1637     int style = xo_name_to_style(name);
1638     if (style < 0)
1639         return -1;
1640
1641     xo_set_style(xop, style);
1642     return 0;
1643 }
1644
1645 /*
1646  * Set the options for a handle using a string of options
1647  * passed in.  The input is a comma-separated set of names
1648  * and optional values: "xml,pretty,indent=4"
1649  */
1650 int
1651 xo_set_options (xo_handle_t *xop, const char *input)
1652 {
1653     char *cp, *ep, *vp, *np, *bp;
1654     int style = -1, new_style, len, rc = 0;
1655     xo_xof_flags_t new_flag;
1656
1657     if (input == NULL)
1658         return 0;
1659
1660     xop = xo_default(xop);
1661
1662 #ifdef LIBXO_COLOR_ON_BY_DEFAULT
1663     /* If the installer used --enable-color-on-by-default, then we allow it */
1664     xop->xo_flags |= XOF_COLOR_ALLOWED;
1665 #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
1666
1667     /*
1668      * We support a simpler, old-school style of giving option
1669      * also, using a single character for each option.  It's
1670      * ideal for lazy people, such as myself.
1671      */
1672     if (*input == ':') {
1673         int sz;
1674
1675         for (input++ ; *input; input++) {
1676             switch (*input) {
1677             case 'c':
1678                 xop->xo_flags |= XOF_COLOR_ALLOWED;
1679                 break;
1680
1681             case 'f':
1682                 xop->xo_flags |= XOF_FLUSH;
1683                 break;
1684
1685             case 'F':
1686                 xop->xo_flags |= XOF_FLUSH_LINE;
1687                 break;
1688
1689             case 'H':
1690                 xop->xo_style = XO_STYLE_HTML;
1691                 break;
1692
1693             case 'I':
1694                 xop->xo_flags |= XOF_INFO;
1695                 break;
1696
1697             case 'i':
1698                 sz = strspn(input + 1, "0123456789");
1699                 if (sz > 0) {
1700                     xop->xo_indent_by = atoi(input + 1);
1701                     input += sz - 1;    /* Skip value */
1702                 }
1703                 break;
1704
1705             case 'k':
1706                 xop->xo_flags |= XOF_KEYS;
1707                 break;
1708
1709             case 'J':
1710                 xop->xo_style = XO_STYLE_JSON;
1711                 break;
1712
1713             case 'P':
1714                 xop->xo_flags |= XOF_PRETTY;
1715                 break;
1716
1717             case 'T':
1718                 xop->xo_style = XO_STYLE_TEXT;
1719                 break;
1720
1721             case 'U':
1722                 xop->xo_flags |= XOF_UNITS;
1723                 break;
1724
1725             case 'u':
1726                 xop->xo_flags |= XOF_UNDERSCORES;
1727                 break;
1728
1729             case 'W':
1730                 xop->xo_flags |= XOF_WARN;
1731                 break;
1732
1733             case 'X':
1734                 xop->xo_style = XO_STYLE_XML;
1735                 break;
1736
1737             case 'x':
1738                 xop->xo_flags |= XOF_XPATH;
1739                 break;
1740             }
1741         }
1742         return 0;
1743     }
1744
1745     len = strlen(input) + 1;
1746     bp = alloca(len);
1747     memcpy(bp, input, len);
1748
1749     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
1750         np = strchr(cp, ',');
1751         if (np)
1752             *np++ = '\0';
1753
1754         vp = strchr(cp, '=');
1755         if (vp)
1756             *vp++ = '\0';
1757
1758         if (strcmp("colors", cp) == 0) {
1759             /* XXX Look for colors=red-blue+green-yellow */
1760             continue;
1761         }
1762
1763         new_style = xo_name_to_style(cp);
1764         if (new_style >= 0) {
1765             if (style >= 0)
1766                 xo_warnx("ignoring multiple styles: '%s'", cp);
1767             else
1768                 style = new_style;
1769         } else {
1770             new_flag = xo_name_to_flag(cp);
1771             if (new_flag != 0)
1772                 xop->xo_flags |= new_flag;
1773             else {
1774                 if (strcmp(cp, "no-color") == 0) {
1775                     xop->xo_flags &= ~XOF_COLOR_ALLOWED;
1776                 } else if (strcmp(cp, "indent") == 0) {
1777                     xop->xo_indent_by = atoi(vp);
1778                 } else {
1779                     xo_warnx("unknown option: '%s'", cp);
1780                     rc = -1;
1781                 }
1782             }
1783         }
1784     }
1785
1786     if (style > 0)
1787         xop->xo_style= style;
1788
1789     return rc;
1790 }
1791
1792 /**
1793  * Set one or more flags for a given handle (or default if handle is NULL).
1794  * These flags will affect future output.
1795  *
1796  * @xop XO handle to alter (or NULL for default handle)
1797  * @flags Flags to be set (XOF_*)
1798  */
1799 void
1800 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1801 {
1802     xop = xo_default(xop);
1803
1804     xop->xo_flags |= flags;
1805 }
1806
1807 xo_xof_flags_t
1808 xo_get_flags (xo_handle_t *xop)
1809 {
1810     xop = xo_default(xop);
1811
1812     return xop->xo_flags;
1813 }
1814
1815 /**
1816  * Record a leading prefix for the XPath we generate.  This allows the
1817  * generated data to be placed within an XML hierarchy but still have
1818  * accurate XPath expressions.
1819  *
1820  * @xop XO handle to alter (or NULL for default handle)
1821  * @path The XPath expression
1822  */
1823 void
1824 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
1825 {
1826     xop = xo_default(xop);
1827
1828     if (xop->xo_leading_xpath) {
1829         xo_free(xop->xo_leading_xpath);
1830         xop->xo_leading_xpath = NULL;
1831     }
1832
1833     if (path == NULL)
1834         return;
1835
1836     int len = strlen(path);
1837     xop->xo_leading_xpath = xo_realloc(NULL, len + 1);
1838     if (xop->xo_leading_xpath) {
1839         memcpy(xop->xo_leading_xpath, path, len + 1);
1840     }
1841 }
1842
1843 /**
1844  * Record the info data for a set of tags
1845  *
1846  * @xop XO handle to alter (or NULL for default handle)
1847  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
1848  * @count Number of entries in info (or -1 to count them ourselves)
1849  */
1850 void
1851 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
1852 {
1853     xop = xo_default(xop);
1854
1855     if (count < 0 && infop) {
1856         xo_info_t *xip;
1857
1858         for (xip = infop, count = 0; xip->xi_name; xip++, count++)
1859             continue;
1860     }
1861
1862     xop->xo_info = infop;
1863     xop->xo_info_count = count;
1864 }
1865
1866 /**
1867  * Set the formatter callback for a handle.  The callback should
1868  * return a newly formatting contents of a formatting instruction,
1869  * meaning the bits inside the braces.
1870  */
1871 void
1872 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
1873                   xo_checkpointer_t cfunc)
1874 {
1875     xop = xo_default(xop);
1876
1877     xop->xo_formatter = func;
1878     xop->xo_checkpointer = cfunc;
1879 }
1880
1881 /**
1882  * Clear one or more flags for a given handle (or default if handle is NULL).
1883  * These flags will affect future output.
1884  *
1885  * @xop XO handle to alter (or NULL for default handle)
1886  * @flags Flags to be cleared (XOF_*)
1887  */
1888 void
1889 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1890 {
1891     xop = xo_default(xop);
1892
1893     xop->xo_flags &= ~flags;
1894 }
1895
1896 static const char *
1897 xo_state_name (xo_state_t state)
1898 {
1899     static const char *names[] = {
1900         "init",
1901         "open_container",
1902         "close_container",
1903         "open_list",
1904         "close_list",
1905         "open_instance",
1906         "close_instance",
1907         "open_leaf_list",
1908         "close_leaf_list",
1909         "discarding",
1910         "marker",
1911         "emit",
1912         "emit_leaf_list",
1913         "finish",
1914         NULL
1915     };
1916
1917     if (state < (sizeof(names) / sizeof(names[0])))
1918         return names[state];
1919
1920     return "unknown";
1921 }
1922
1923 static void
1924 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
1925 {
1926     static char div_open[] = "<div class=\"line\">";
1927     static char div_open_blank[] = "<div class=\"blank-line\">";
1928
1929     if (xop->xo_flags & XOF_DIV_OPEN)
1930         return;
1931
1932     if (xo_style(xop) != XO_STYLE_HTML)
1933         return;
1934
1935     xop->xo_flags |= XOF_DIV_OPEN;
1936     if (flags & XFF_BLANK_LINE)
1937         xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
1938     else
1939         xo_data_append(xop, div_open, sizeof(div_open) - 1);
1940
1941     if (xop->xo_flags & XOF_PRETTY)
1942         xo_data_append(xop, "\n", 1);
1943 }
1944
1945 static void
1946 xo_line_close (xo_handle_t *xop)
1947 {
1948     static char div_close[] = "</div>";
1949
1950     switch (xo_style(xop)) {
1951     case XO_STYLE_HTML:
1952         if (!(xop->xo_flags & XOF_DIV_OPEN))
1953             xo_line_ensure_open(xop, 0);
1954
1955         xop->xo_flags &= ~XOF_DIV_OPEN;
1956         xo_data_append(xop, div_close, sizeof(div_close) - 1);
1957
1958         if (xop->xo_flags & XOF_PRETTY)
1959             xo_data_append(xop, "\n", 1);
1960         break;
1961
1962     case XO_STYLE_TEXT:
1963         xo_data_append(xop, "\n", 1);
1964         break;
1965     }
1966 }
1967
1968 static int
1969 xo_info_compare (const void *key, const void *data)
1970 {
1971     const char *name = key;
1972     const xo_info_t *xip = data;
1973
1974     return strcmp(name, xip->xi_name);
1975 }
1976
1977
1978 static xo_info_t *
1979 xo_info_find (xo_handle_t *xop, const char *name, int nlen)
1980 {
1981     xo_info_t *xip;
1982     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
1983
1984     memcpy(cp, name, nlen);
1985     cp[nlen] = '\0';
1986
1987     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
1988                   sizeof(xop->xo_info[0]), xo_info_compare);
1989     return xip;
1990 }
1991
1992 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
1993
1994 /*
1995  * Check to see that the conversion is safe and sane.
1996  */
1997 static int
1998 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
1999 {
2000     switch (CONVERT(have_enc, need_enc)) {
2001     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
2002     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
2003     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
2004     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
2005     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
2006     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
2007         return 0;
2008
2009     default:
2010         xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
2011         return 1;
2012     }
2013 }
2014
2015 static int
2016 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
2017                          xo_xff_flags_t flags,
2018                          const wchar_t *wcp, const char *cp, int len, int max,
2019                          int need_enc, int have_enc)
2020 {
2021     int cols = 0;
2022     wchar_t wc = 0;
2023     int ilen, olen, width;
2024     int attr = (flags & XFF_ATTR);
2025     const char *sp;
2026
2027     if (len > 0 && !xo_buf_has_room(xbp, len))
2028         return 0;
2029
2030     for (;;) {
2031         if (len == 0)
2032             break;
2033
2034         if (cp) {
2035             if (*cp == '\0')
2036                 break;
2037             if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
2038                 cp += 1;
2039                 len -= 1;
2040             }
2041         }
2042
2043         if (wcp && *wcp == L'\0')
2044             break;
2045
2046         ilen = 0;
2047
2048         switch (have_enc) {
2049         case XF_ENC_WIDE:               /* Wide character */
2050             wc = *wcp++;
2051             ilen = 1;
2052             break;
2053
2054         case XF_ENC_UTF8:               /* UTF-8 */
2055             ilen = xo_utf8_to_wc_len(cp);
2056             if (ilen < 0) {
2057                 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2058                 return -1;
2059             }
2060
2061             if (len > 0 && len < ilen) {
2062                 len = 0;        /* Break out of the loop */
2063                 continue;
2064             }
2065
2066             wc = xo_utf8_char(cp, ilen);
2067             if (wc == (wchar_t) -1) {
2068                 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
2069                            *cp, ilen);
2070                 return -1;
2071             }
2072             cp += ilen;
2073             break;
2074
2075         case XF_ENC_LOCALE:             /* Native locale */
2076             ilen = (len > 0) ? len : MB_LEN_MAX;
2077             ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
2078             if (ilen < 0) {             /* Invalid data; skip */
2079                 xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2080                 continue;
2081             }
2082             if (ilen == 0) {            /* Hit a wide NUL character */
2083                 len = 0;
2084                 continue;
2085             }
2086
2087             cp += ilen;
2088             break;
2089         }
2090
2091         /* Reduce len, but not below zero */
2092         if (len > 0) {
2093             len -= ilen;
2094             if (len < 0)
2095                 len = 0;
2096         }
2097
2098         /*
2099          * Find the width-in-columns of this character, which must be done
2100          * in wide characters, since we lack a mbswidth() function.  If
2101          * it doesn't fit
2102          */
2103         width = wcwidth(wc);
2104         if (width < 0)
2105             width = iswcntrl(wc) ? 0 : 1;
2106
2107         if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
2108             if (max > 0 && cols + width > max)
2109                 break;
2110         }
2111
2112         switch (need_enc) {
2113         case XF_ENC_UTF8:
2114
2115             /* Output in UTF-8 needs to be escaped, based on the style */
2116             switch (xo_style(xop)) {
2117             case XO_STYLE_XML:
2118             case XO_STYLE_HTML:
2119                 if (wc == '<')
2120                     sp = xo_xml_lt;
2121                 else if (wc == '>')
2122                     sp = xo_xml_gt;
2123                 else if (wc == '&')
2124                     sp = xo_xml_amp;
2125                 else if (attr && wc == '"')
2126                     sp = xo_xml_quot;
2127                 else
2128                     break;
2129
2130                 int slen = strlen(sp);
2131                 if (!xo_buf_has_room(xbp, slen - 1))
2132                     return -1;
2133
2134                 memcpy(xbp->xb_curp, sp, slen);
2135                 xbp->xb_curp += slen;
2136                 goto done_with_encoding; /* Need multi-level 'break' */
2137
2138             case XO_STYLE_JSON:
2139                 if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
2140                     break;
2141
2142                 if (!xo_buf_has_room(xbp, 2))
2143                     return -1;
2144
2145                 *xbp->xb_curp++ = '\\';
2146                 if (wc == '\n')
2147                     wc = 'n';
2148                 else if (wc == '\r')
2149                     wc = 'r';
2150                 else wc = wc & 0x7f;
2151
2152                 *xbp->xb_curp++ = wc;
2153                 goto done_with_encoding;
2154             }
2155
2156             olen = xo_utf8_emit_len(wc);
2157             if (olen < 0) {
2158                 xo_failure(xop, "ignoring bad length");
2159                 continue;
2160             }
2161
2162             if (!xo_buf_has_room(xbp, olen))
2163                 return -1;
2164
2165             xo_utf8_emit_char(xbp->xb_curp, olen, wc);
2166             xbp->xb_curp += olen;
2167             break;
2168
2169         case XF_ENC_LOCALE:
2170             if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
2171                 return -1;
2172
2173             olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
2174             if (olen <= 0) {
2175                 xo_failure(xop, "could not convert wide char: %lx",
2176                            (unsigned long) wc);
2177                 olen = 1;
2178                 width = 1;
2179                 *xbp->xb_curp++ = '?';
2180             } else
2181                 xbp->xb_curp += olen;
2182             break;
2183         }
2184
2185     done_with_encoding:
2186         cols += width;
2187     }
2188
2189     return cols;
2190 }
2191
2192 static int
2193 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
2194                   xo_format_t *xfp)
2195 {
2196     static char null[] = "(null)";
2197
2198     char *cp = NULL;
2199     wchar_t *wcp = NULL;
2200     int len, cols = 0, rc = 0;
2201     int off = xbp->xb_curp - xbp->xb_bufp, off2;
2202     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2203         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2204
2205     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
2206         return 0;
2207
2208     len = xfp->xf_width[XF_WIDTH_SIZE];
2209
2210     if (xfp->xf_enc == XF_ENC_WIDE) {
2211         wcp = va_arg(xop->xo_vap, wchar_t *);
2212         if (xfp->xf_skip)
2213             return 0;
2214
2215         /*
2216          * Dont' deref NULL; use the traditional "(null)" instead
2217          * of the more accurate "who's been a naughty boy, then?".
2218          */
2219         if (wcp == NULL) {
2220             cp = null;
2221             len = sizeof(null) - 1;
2222         }
2223
2224     } else {
2225         cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2226         if (xfp->xf_skip)
2227             return 0;
2228
2229         /* Echo "Dont' deref NULL" logic */
2230         if (cp == NULL) {
2231             cp = null;
2232             len = sizeof(null) - 1;
2233         }
2234
2235         /*
2236          * Optimize the most common case, which is "%s".  We just
2237          * need to copy the complete string to the output buffer.
2238          */
2239         if (xfp->xf_enc == need_enc
2240                 && xfp->xf_width[XF_WIDTH_MIN] < 0
2241                 && xfp->xf_width[XF_WIDTH_SIZE] < 0
2242                 && xfp->xf_width[XF_WIDTH_MAX] < 0
2243                 && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) {
2244             len = strlen(cp);
2245             xo_buf_escape(xop, xbp, cp, len, flags);
2246
2247             /*
2248              * Our caller expects xb_curp left untouched, so we have
2249              * to reset it and return the number of bytes written to
2250              * the buffer.
2251              */
2252             off2 = xbp->xb_curp - xbp->xb_bufp;
2253             rc = off2 - off;
2254             xbp->xb_curp = xbp->xb_bufp + off;
2255
2256             return rc;
2257         }
2258     }
2259
2260     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
2261                                    xfp->xf_width[XF_WIDTH_MAX],
2262                                    need_enc, xfp->xf_enc);
2263     if (cols < 0)
2264         goto bail;
2265
2266     /*
2267      * xo_buf_append* will move xb_curp, so we save/restore it.
2268      */
2269     off2 = xbp->xb_curp - xbp->xb_bufp;
2270     rc = off2 - off;
2271     xbp->xb_curp = xbp->xb_bufp + off;
2272
2273     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
2274         /*
2275          * Find the number of columns needed to display the string.
2276          * If we have the original wide string, we just call wcswidth,
2277          * but if we did the work ourselves, then we need to do it.
2278          */
2279         int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2280         if (!xo_buf_has_room(xbp, delta))
2281             goto bail;
2282
2283         /*
2284          * If seen_minus, then pad on the right; otherwise move it so
2285          * we can pad on the left.
2286          */
2287         if (xfp->xf_seen_minus) {
2288             cp = xbp->xb_curp + rc;
2289         } else {
2290             cp = xbp->xb_curp;
2291             memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
2292         }
2293
2294         /* Set the padding */
2295         memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
2296         rc += delta;
2297         cols += delta;
2298     }
2299
2300     if (xop->xo_flags & XOF_COLUMNS)
2301         xop->xo_columns += cols;
2302     if (xop->xo_flags & XOF_ANCHOR)
2303         xop->xo_anchor_columns += cols;
2304
2305     return rc;
2306
2307  bail:
2308     xbp->xb_curp = xbp->xb_bufp + off;
2309     return 0;
2310 }
2311
2312 static void
2313 xo_data_append_content (xo_handle_t *xop, const char *str, int len)
2314 {
2315     int cols;
2316     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2317         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2318
2319     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE,
2320                                    NULL, str, len, -1,
2321                                    need_enc, XF_ENC_UTF8);
2322
2323     if (xop->xo_flags & XOF_COLUMNS)
2324         xop->xo_columns += cols;
2325     if (xop->xo_flags & XOF_ANCHOR)
2326         xop->xo_anchor_columns += cols;
2327 }
2328
2329 static void
2330 xo_bump_width (xo_format_t *xfp, int digit)
2331 {
2332     int *ip = &xfp->xf_width[xfp->xf_dots];
2333
2334     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
2335 }
2336
2337 static int
2338 xo_trim_ws (xo_buffer_t *xbp, int len)
2339 {
2340     char *cp, *sp, *ep;
2341     int delta;
2342
2343     /* First trim leading space */
2344     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
2345         if (*cp != ' ')
2346             break;
2347     }
2348
2349     delta = cp - sp;
2350     if (delta) {
2351         len -= delta;
2352         memmove(sp, cp, len);
2353     }
2354
2355     /* Then trim off the end */
2356     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
2357         if (ep[-1] != ' ')
2358             break;
2359     }
2360
2361     delta = sp - ep;
2362     if (delta) {
2363         len -= delta;
2364         cp[len] = '\0';
2365     }
2366
2367     return len;
2368 }
2369
2370 static int
2371 xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
2372                 const char *fmt, int flen, xo_xff_flags_t flags)
2373 {
2374     xo_format_t xf;
2375     const char *cp, *ep, *sp, *xp = NULL;
2376     int rc, cols;
2377     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
2378     unsigned make_output = !(flags & XFF_NO_OUTPUT);
2379     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2380         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2381     
2382     if (xbp == NULL)
2383         xbp = &xop->xo_data;
2384
2385     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
2386         if (*cp != '%') {
2387         add_one:
2388             if (xp == NULL)
2389                 xp = cp;
2390
2391             if (*cp == '\\' && cp[1] != '\0')
2392                 cp += 1;
2393             continue;
2394
2395         } if (cp + 1 < ep && cp[1] == '%') {
2396             cp += 1;
2397             goto add_one;
2398         }
2399
2400         if (xp) {
2401             if (make_output) {
2402                 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2403                                                NULL, xp, cp - xp, -1,
2404                                                need_enc, XF_ENC_UTF8);
2405                 if (xop->xo_flags & XOF_COLUMNS)
2406                     xop->xo_columns += cols;
2407                 if (xop->xo_flags & XOF_ANCHOR)
2408                     xop->xo_anchor_columns += cols;
2409             }
2410
2411             xp = NULL;
2412         }
2413
2414         bzero(&xf, sizeof(xf));
2415         xf.xf_leading_zero = -1;
2416         xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
2417
2418         /*
2419          * "%@" starts an XO-specific set of flags:
2420          *   @X@ - XML-only field; ignored if style isn't XML
2421          */
2422         if (cp[1] == '@') {
2423             for (cp += 2; cp < ep; cp++) {
2424                 if (*cp == '@') {
2425                     break;
2426                 }
2427                 if (*cp == '*') {
2428                     /*
2429                      * '*' means there's a "%*.*s" value in vap that
2430                      * we want to ignore
2431                      */
2432                     if (!(xop->xo_flags & XOF_NO_VA_ARG))
2433                         va_arg(xop->xo_vap, int);
2434                 }
2435             }
2436         }
2437
2438         /* Hidden fields are only visible to JSON and XML */
2439         if (xop->xo_flags & XFF_ENCODE_ONLY) {
2440             if (style != XO_STYLE_XML
2441                     && xo_style(xop) != XO_STYLE_JSON)
2442                 xf.xf_skip = 1;
2443         } else if (xop->xo_flags & XFF_DISPLAY_ONLY) {
2444             if (style != XO_STYLE_TEXT
2445                     && xo_style(xop) != XO_STYLE_HTML)
2446                 xf.xf_skip = 1;
2447         }
2448
2449         if (!make_output)
2450             xf.xf_skip = 1;
2451
2452         /*
2453          * Looking at one piece of a format; find the end and
2454          * call snprintf.  Then advance xo_vap on our own.
2455          *
2456          * Note that 'n', 'v', and '$' are not supported.
2457          */
2458         sp = cp;                /* Save start pointer */
2459         for (cp += 1; cp < ep; cp++) {
2460             if (*cp == 'l')
2461                 xf.xf_lflag += 1;
2462             else if (*cp == 'h')
2463                 xf.xf_hflag += 1;
2464             else if (*cp == 'j')
2465                 xf.xf_jflag += 1;
2466             else if (*cp == 't')
2467                 xf.xf_tflag += 1;
2468             else if (*cp == 'z')
2469                 xf.xf_zflag += 1;
2470             else if (*cp == 'q')
2471                 xf.xf_qflag += 1;
2472             else if (*cp == '.') {
2473                 if (++xf.xf_dots >= XF_WIDTH_NUM) {
2474                     xo_failure(xop, "Too many dots in format: '%s'", fmt);
2475                     return -1;
2476                 }
2477             } else if (*cp == '-')
2478                 xf.xf_seen_minus = 1;
2479             else if (isdigit((int) *cp)) {
2480                 if (xf.xf_leading_zero < 0)
2481                     xf.xf_leading_zero = (*cp == '0');
2482                 xo_bump_width(&xf, *cp - '0');
2483             } else if (*cp == '*') {
2484                 xf.xf_stars += 1;
2485                 xf.xf_star[xf.xf_dots] = 1;
2486             } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
2487                 break;
2488             else if (*cp == 'n' || *cp == 'v') {
2489                 xo_failure(xop, "unsupported format: '%s'", fmt);
2490                 return -1;
2491             }
2492         }
2493
2494         if (cp == ep)
2495             xo_failure(xop, "field format missing format character: %s",
2496                           fmt);
2497
2498         xf.xf_fc = *cp;
2499
2500         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2501             if (*cp == 's' || *cp == 'S') {
2502                 /* Handle "%*.*.*s" */
2503                 int s;
2504                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2505                     if (xf.xf_star[s]) {
2506                         xf.xf_width[s] = va_arg(xop->xo_vap, int);
2507                         
2508                         /* Normalize a negative width value */
2509                         if (xf.xf_width[s] < 0) {
2510                             if (s == 0) {
2511                                 xf.xf_width[0] = -xf.xf_width[0];
2512                                 xf.xf_seen_minus = 1;
2513                             } else
2514                                 xf.xf_width[s] = -1; /* Ignore negative values */
2515                         }
2516                     }
2517                 }
2518             }
2519         }
2520
2521         /* If no max is given, it defaults to size */
2522         if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
2523             xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
2524
2525         if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
2526             xf.xf_lflag = 1;
2527
2528         if (!xf.xf_skip) {
2529             xo_buffer_t *fbp = &xop->xo_fmt;
2530             int len = cp - sp + 1;
2531             if (!xo_buf_has_room(fbp, len + 1))
2532                 return -1;
2533
2534             char *newfmt = fbp->xb_curp;
2535             memcpy(newfmt, sp, len);
2536             newfmt[0] = '%';    /* If we skipped over a "%@...@s" format */
2537             newfmt[len] = '\0';
2538
2539             /*
2540              * Bad news: our strings are UTF-8, but the stock printf
2541              * functions won't handle field widths for wide characters
2542              * correctly.  So we have to handle this ourselves.
2543              */
2544             if (xop->xo_formatter == NULL
2545                     && (xf.xf_fc == 's' || xf.xf_fc == 'S')) {
2546                 xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S'))
2547                     ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
2548                 rc = xo_format_string(xop, xbp, flags, &xf);
2549
2550                 if ((flags & XFF_TRIM_WS)
2551                         && (xo_style(xop) == XO_STYLE_XML
2552                                 || xo_style(xop) == XO_STYLE_JSON))
2553                     rc = xo_trim_ws(xbp, rc);
2554
2555             } else {
2556                 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
2557
2558                 /*
2559                  * For XML and HTML, we need "&<>" processing; for JSON,
2560                  * it's quotes.  Text gets nothing.
2561                  */
2562                 switch (style) {
2563                 case XO_STYLE_XML:
2564                     if (flags & XFF_TRIM_WS)
2565                         columns = rc = xo_trim_ws(xbp, rc);
2566                     /* fall thru */
2567                 case XO_STYLE_HTML:
2568                     rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
2569                     break;
2570
2571                 case XO_STYLE_JSON:
2572                     if (flags & XFF_TRIM_WS)
2573                         columns = rc = xo_trim_ws(xbp, rc);
2574                     rc = xo_escape_json(xbp, rc);
2575                     break;
2576                 }
2577
2578                 /*
2579                  * We can assume all the data we've added is ASCII, so
2580                  * the columns and bytes are the same.  xo_format_string
2581                  * handles all the fancy string conversions and updates
2582                  * xo_anchor_columns accordingly.
2583                  */
2584                 if (xop->xo_flags & XOF_COLUMNS)
2585                     xop->xo_columns += columns;
2586                 if (xop->xo_flags & XOF_ANCHOR)
2587                     xop->xo_anchor_columns += columns;
2588             }
2589
2590             xbp->xb_curp += rc;
2591         }
2592
2593         /*
2594          * Now for the tricky part: we need to move the argument pointer
2595          * along by the amount needed.
2596          */
2597         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2598
2599             if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
2600                 /*
2601                  * The 'S' and 's' formats are normally handled in
2602                  * xo_format_string, but if we skipped it, then we
2603                  * need to pop it.
2604                  */
2605                 if (xf.xf_skip)
2606                     va_arg(xop->xo_vap, char *);
2607
2608             } else {
2609                 int s;
2610                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2611                     if (xf.xf_star[s])
2612                         va_arg(xop->xo_vap, int);
2613                 }
2614
2615                 if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
2616                     if (xf.xf_hflag > 1) {
2617                         va_arg(xop->xo_vap, int);
2618
2619                     } else if (xf.xf_hflag > 0) {
2620                         va_arg(xop->xo_vap, int);
2621
2622                     } else if (xf.xf_lflag > 1) {
2623                         va_arg(xop->xo_vap, unsigned long long);
2624
2625                     } else if (xf.xf_lflag > 0) {
2626                         va_arg(xop->xo_vap, unsigned long);
2627
2628                     } else if (xf.xf_jflag > 0) {
2629                         va_arg(xop->xo_vap, intmax_t);
2630
2631                     } else if (xf.xf_tflag > 0) {
2632                         va_arg(xop->xo_vap, ptrdiff_t);
2633
2634                     } else if (xf.xf_zflag > 0) {
2635                         va_arg(xop->xo_vap, size_t);
2636
2637                     } else if (xf.xf_qflag > 0) {
2638                         va_arg(xop->xo_vap, quad_t);
2639
2640                     } else {
2641                         va_arg(xop->xo_vap, int);
2642                     }
2643                 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
2644                     if (xf.xf_lflag)
2645                         va_arg(xop->xo_vap, long double);
2646                     else
2647                         va_arg(xop->xo_vap, double);
2648
2649                 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
2650                     va_arg(xop->xo_vap, wint_t);
2651
2652                 else if (xf.xf_fc == 'c')
2653                     va_arg(xop->xo_vap, int);
2654
2655                 else if (xf.xf_fc == 'p')
2656                     va_arg(xop->xo_vap, void *);
2657             }
2658         }
2659     }
2660
2661     if (xp) {
2662         if (make_output) {
2663             cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2664                                            NULL, xp, cp - xp, -1,
2665                                            need_enc, XF_ENC_UTF8);
2666             if (xop->xo_flags & XOF_COLUMNS)
2667                 xop->xo_columns += cols;
2668             if (xop->xo_flags & XOF_ANCHOR)
2669                 xop->xo_anchor_columns += cols;
2670         }
2671
2672         xp = NULL;
2673     }
2674
2675     return 0;
2676 }
2677
2678 static char *
2679 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
2680 {
2681     char *cp = encoding;
2682
2683     if (cp[0] != '%' || !isdigit((int) cp[1]))
2684         return encoding;
2685
2686     for (cp += 2; *cp; cp++) {
2687         if (!isdigit((int) *cp))
2688             break;
2689     }
2690
2691     cp -= 1;
2692     *cp = '%';
2693
2694     return cp;
2695 }
2696
2697 static void
2698 xo_color_append_html (xo_handle_t *xop)
2699 {
2700     /*
2701      * If the color buffer has content, we add it now.  It's already
2702      * prebuilt and ready, since we want to add it to every <div>.
2703      */
2704     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
2705         xo_buffer_t *xbp = &xop->xo_color_buf;
2706
2707         xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
2708     }
2709 }
2710
2711 static void
2712 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
2713                    const char *name, int nlen,
2714                    const char *value, int vlen,
2715                    const char *encoding, int elen)
2716 {
2717     static char div_start[] = "<div class=\"";
2718     static char div_tag[] = "\" data-tag=\"";
2719     static char div_xpath[] = "\" data-xpath=\"";
2720     static char div_key[] = "\" data-key=\"key";
2721     static char div_end[] = "\">";
2722     static char div_close[] = "</div>";
2723
2724     /*
2725      * To build our XPath predicate, we need to save the va_list before
2726      * we format our data, and then restore it before we format the
2727      * xpath expression.
2728      * Display-only keys implies that we've got an encode-only key
2729      * elsewhere, so we don't use them from making predicates.
2730      */
2731     int need_predidate = 
2732         (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
2733          && (xop->xo_flags & XOF_XPATH));
2734
2735     if (need_predidate) {
2736         va_list va_local;
2737
2738         va_copy(va_local, xop->xo_vap);
2739         if (xop->xo_checkpointer)
2740             xop->xo_checkpointer(xop, xop->xo_vap, 0);
2741
2742         /*
2743          * Build an XPath predicate expression to match this key.
2744          * We use the format buffer.
2745          */
2746         xo_buffer_t *pbp = &xop->xo_predicate;
2747         pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
2748
2749         xo_buf_append(pbp, "[", 1);
2750         xo_buf_escape(xop, pbp, name, nlen, 0);
2751         if (xop->xo_flags & XOF_PRETTY)
2752             xo_buf_append(pbp, " = '", 4);
2753         else
2754             xo_buf_append(pbp, "='", 2);
2755
2756         /* The encoding format defaults to the normal format */
2757         if (encoding == NULL) {
2758             char *enc  = alloca(vlen + 1);
2759             memcpy(enc, value, vlen);
2760             enc[vlen] = '\0';
2761             encoding = xo_fix_encoding(xop, enc);
2762             elen = strlen(encoding);
2763         }
2764
2765         xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR);
2766
2767         xo_buf_append(pbp, "']", 2);
2768
2769         /* Now we record this predicate expression in the stack */
2770         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
2771         int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
2772         int dlen = pbp->xb_curp - pbp->xb_bufp;
2773
2774         char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
2775         if (cp) {
2776             memcpy(cp + olen, pbp->xb_bufp, dlen);
2777             cp[olen + dlen] = '\0';
2778             xsp->xs_keys = cp;
2779         }
2780
2781         /* Now we reset the xo_vap as if we were never here */
2782         va_end(xop->xo_vap);
2783         va_copy(xop->xo_vap, va_local);
2784         va_end(va_local);
2785         if (xop->xo_checkpointer)
2786             xop->xo_checkpointer(xop, xop->xo_vap, 1);
2787     }
2788
2789     if (flags & XFF_ENCODE_ONLY) {
2790         /*
2791          * Even if this is encode-only, we need to go thru the
2792          * work of formatting it to make sure the args are cleared
2793          * from xo_vap.
2794          */
2795         xo_format_data(xop, &xop->xo_data, encoding, elen,
2796                        flags | XFF_NO_OUTPUT);
2797         return;
2798     }
2799
2800     xo_line_ensure_open(xop, 0);
2801
2802     if (xop->xo_flags & XOF_PRETTY)
2803         xo_buf_indent(xop, xop->xo_indent_by);
2804
2805     xo_data_append(xop, div_start, sizeof(div_start) - 1);
2806     xo_data_append(xop, class, strlen(class));
2807
2808     /*
2809      * If the color buffer has content, we add it now.  It's already
2810      * prebuilt and ready, since we want to add it to every <div>.
2811      */
2812     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
2813         xo_buffer_t *xbp = &xop->xo_color_buf;
2814
2815         xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
2816     }
2817
2818     if (name) {
2819         xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
2820         xo_data_escape(xop, name, nlen);
2821
2822         /*
2823          * Save the offset at which we'd place units.  See xo_format_units.
2824          */
2825         if (xop->xo_flags & XOF_UNITS) {
2826             xop->xo_flags |= XOF_UNITS_PENDING;
2827             /*
2828              * Note: We need the '+1' here because we know we've not
2829              * added the closing quote.  We add one, knowing the quote
2830              * will be added shortly.
2831              */
2832             xop->xo_units_offset =
2833                 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
2834         }
2835     }
2836
2837     if (name) {
2838         if (xop->xo_flags & XOF_XPATH) {
2839             int i;
2840             xo_stack_t *xsp;
2841
2842             xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
2843             if (xop->xo_leading_xpath)
2844                 xo_data_append(xop, xop->xo_leading_xpath,
2845                                strlen(xop->xo_leading_xpath));
2846
2847             for (i = 0; i <= xop->xo_depth; i++) {
2848                 xsp = &xop->xo_stack[i];
2849                 if (xsp->xs_name == NULL)
2850                     continue;
2851
2852                 /*
2853                  * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
2854                  * are directly under XSS_OPEN_INSTANCE frames so we
2855                  * don't need to put these in our XPath expressions.
2856                  */
2857                 if (xsp->xs_state == XSS_OPEN_LIST
2858                         || xsp->xs_state == XSS_OPEN_LEAF_LIST)
2859                     continue;
2860
2861                 xo_data_append(xop, "/", 1);
2862                 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
2863                 if (xsp->xs_keys) {
2864                     /* Don't show keys for the key field */
2865                     if (i != xop->xo_depth || !(flags & XFF_KEY))
2866                         xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
2867                 }
2868             }
2869
2870             xo_data_append(xop, "/", 1);
2871             xo_data_escape(xop, name, nlen);
2872         }
2873
2874         if ((xop->xo_flags & XOF_INFO) && xop->xo_info) {
2875             static char in_type[] = "\" data-type=\"";
2876             static char in_help[] = "\" data-help=\"";
2877
2878             xo_info_t *xip = xo_info_find(xop, name, nlen);
2879             if (xip) {
2880                 if (xip->xi_type) {
2881                     xo_data_append(xop, in_type, sizeof(in_type) - 1);
2882                     xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
2883                 }
2884                 if (xip->xi_help) {
2885                     xo_data_append(xop, in_help, sizeof(in_help) - 1);
2886                     xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
2887                 }
2888             }
2889         }
2890
2891         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS))
2892             xo_data_append(xop, div_key, sizeof(div_key) - 1);
2893     }
2894
2895     xo_data_append(xop, div_end, sizeof(div_end) - 1);
2896
2897     xo_format_data(xop, NULL, value, vlen, 0);
2898
2899     xo_data_append(xop, div_close, sizeof(div_close) - 1);
2900
2901     if (xop->xo_flags & XOF_PRETTY)
2902         xo_data_append(xop, "\n", 1);
2903 }
2904
2905 static void
2906 xo_format_text (xo_handle_t *xop, const char *str, int len)
2907 {
2908     switch (xo_style(xop)) {
2909     case XO_STYLE_TEXT:
2910         xo_buf_append_locale(xop, &xop->xo_data, str, len);
2911         break;
2912
2913     case XO_STYLE_HTML:
2914         xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
2915         break;
2916     }
2917 }
2918
2919 static void
2920 xo_format_title (xo_handle_t *xop, const char *str, int len,
2921                  const char *fmt, int flen)
2922 {
2923     static char div_open[] = "<div class=\"title";
2924     static char div_middle[] = "\">";
2925     static char div_close[] = "</div>";
2926
2927     if (flen == 0) {
2928         fmt = "%s";
2929         flen = 2;
2930     }
2931
2932     switch (xo_style(xop)) {
2933     case XO_STYLE_XML:
2934     case XO_STYLE_JSON:
2935         /*
2936          * Even though we don't care about text, we need to do
2937          * enough parsing work to skip over the right bits of xo_vap.
2938          */
2939         if (len == 0)
2940             xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2941         return;
2942     }
2943
2944     xo_buffer_t *xbp = &xop->xo_data;
2945     int start = xbp->xb_curp - xbp->xb_bufp;
2946     int left = xbp->xb_size - start;
2947     int rc;
2948     int need_enc = XF_ENC_LOCALE;
2949
2950     if (xo_style(xop) == XO_STYLE_HTML) {
2951         need_enc = XF_ENC_UTF8;
2952         xo_line_ensure_open(xop, 0);
2953         if (xop->xo_flags & XOF_PRETTY)
2954             xo_buf_indent(xop, xop->xo_indent_by);
2955         xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
2956         xo_color_append_html(xop);
2957         xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
2958     }
2959
2960     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
2961     if (len) {
2962         char *newfmt = alloca(flen + 1);
2963         memcpy(newfmt, fmt, flen);
2964         newfmt[flen] = '\0';
2965
2966         /* If len is non-zero, the format string apply to the name */
2967         char *newstr = alloca(len + 1);
2968         memcpy(newstr, str, len);
2969         newstr[len] = '\0';
2970
2971         if (newstr[len - 1] == 's') {
2972             int cols;
2973             char *bp;
2974
2975             rc = snprintf(NULL, 0, newfmt, newstr);
2976             if (rc > 0) {
2977                 /*
2978                  * We have to do this the hard way, since we might need
2979                  * the columns.
2980                  */
2981                 bp = alloca(rc + 1);
2982                 rc = snprintf(bp, rc + 1, newfmt, newstr);
2983                 cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1,
2984                                                need_enc, XF_ENC_UTF8);
2985                 if (cols > 0) {
2986                     if (xop->xo_flags & XOF_COLUMNS)
2987                         xop->xo_columns += cols;
2988                     if (xop->xo_flags & XOF_ANCHOR)
2989                         xop->xo_anchor_columns += cols;
2990                 }
2991             }
2992             goto move_along;
2993
2994         } else {
2995             rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2996             if (rc > left) {
2997                 if (!xo_buf_has_room(xbp, rc))
2998                     return;
2999                 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
3000                 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3001             }
3002
3003             if (rc > 0) {
3004                 if (xop->xo_flags & XOF_COLUMNS)
3005                     xop->xo_columns += rc;
3006                 if (xop->xo_flags & XOF_ANCHOR)
3007                     xop->xo_anchor_columns += rc;
3008             }
3009         }
3010
3011     } else {
3012         xo_format_data(xop, NULL, fmt, flen, 0);
3013
3014         /* xo_format_data moved curp, so we need to reset it */
3015         rc = xbp->xb_curp - (xbp->xb_bufp + start);
3016         xbp->xb_curp = xbp->xb_bufp + start;
3017     }
3018
3019     /* If we're styling HTML, then we need to escape it */
3020     if (xo_style(xop) == XO_STYLE_HTML) {
3021         rc = xo_escape_xml(xbp, rc, 0);
3022     }
3023
3024     if (rc > 0)
3025         xbp->xb_curp += rc;
3026
3027  move_along:
3028     if (xo_style(xop) == XO_STYLE_HTML) {
3029         xo_data_append(xop, div_close, sizeof(div_close) - 1);
3030         if (xop->xo_flags & XOF_PRETTY)
3031             xo_data_append(xop, "\n", 1);
3032     }
3033 }
3034
3035 static void
3036 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
3037 {
3038     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
3039         xo_data_append(xop, ",", 1);
3040         if (!(flags & XFF_LEAF_LIST) && (xop->xo_flags & XOF_PRETTY))
3041             xo_data_append(xop, "\n", 1);
3042     } else
3043         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3044 }
3045
3046 #if 0
3047 /* Useful debugging function */
3048 void
3049 xo_arg (xo_handle_t *xop);
3050 void
3051 xo_arg (xo_handle_t *xop)
3052 {
3053     xop = xo_default(xop);
3054     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
3055 }
3056 #endif /* 0 */
3057
3058 static void
3059 xo_format_value (xo_handle_t *xop, const char *name, int nlen,
3060                  const char *format, int flen,
3061                  const char *encoding, int elen, xo_xff_flags_t flags)
3062 {
3063     int pretty = (xop->xo_flags & XOF_PRETTY);
3064     int quote;
3065     xo_buffer_t *xbp;
3066
3067     /*
3068      * Before we emit a value, we need to know that the frame is ready.
3069      */
3070     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3071
3072     if (flags & XFF_LEAF_LIST) {
3073         /*
3074          * Check if we've already started to emit normal leafs
3075          * or if we're not in a leaf list.
3076          */
3077         if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
3078             || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
3079             char nbuf[nlen + 1];
3080             memcpy(nbuf, name, nlen);
3081             nbuf[nlen] = '\0';
3082
3083             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
3084             if (rc < 0)
3085                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3086             else
3087                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
3088         }
3089
3090         xsp = &xop->xo_stack[xop->xo_depth];
3091         if (xsp->xs_name) {
3092             name = xsp->xs_name;
3093             nlen = strlen(name);
3094         }
3095
3096     } else if (flags & XFF_KEY) {
3097         /* Emitting a 'k' (key) field */
3098         if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
3099             xo_failure(xop, "key field emitted after normal value field: '%.*s'",
3100                        nlen, name);
3101
3102         } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
3103             char nbuf[nlen + 1];
3104             memcpy(nbuf, name, nlen);
3105             nbuf[nlen] = '\0';
3106
3107             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3108             if (rc < 0)
3109                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3110             else
3111                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
3112
3113             xsp = &xop->xo_stack[xop->xo_depth];
3114             xsp->xs_flags |= XSF_EMIT_KEY;
3115         }
3116
3117     } else {
3118         /* Emitting a normal value field */
3119         if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
3120             || !(xsp->xs_flags & XSF_EMIT)) {
3121             char nbuf[nlen + 1];
3122             memcpy(nbuf, name, nlen);
3123             nbuf[nlen] = '\0';
3124
3125             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3126             if (rc < 0)
3127                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3128             else
3129                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
3130
3131             xsp = &xop->xo_stack[xop->xo_depth];
3132             xsp->xs_flags |= XSF_EMIT;
3133         }
3134     }
3135
3136     switch (xo_style(xop)) {
3137     case XO_STYLE_TEXT:
3138         if (flags & XFF_ENCODE_ONLY)
3139             flags |= XFF_NO_OUTPUT;
3140         xo_format_data(xop, NULL, format, flen, flags);
3141         break;
3142
3143     case XO_STYLE_HTML:
3144         if (flags & XFF_ENCODE_ONLY)
3145             flags |= XFF_NO_OUTPUT;
3146         xo_buf_append_div(xop, "data", flags, name, nlen,
3147                           format, flen, encoding, elen);
3148         break;
3149
3150     case XO_STYLE_XML:
3151         /*
3152          * Even though we're not making output, we still need to
3153          * let the formatting code handle the va_arg popping.
3154          */
3155         if (flags & XFF_DISPLAY_ONLY) {
3156             flags |= XFF_NO_OUTPUT;
3157             xo_format_data(xop, NULL, format, flen, flags);
3158             break;
3159         }
3160
3161         if (encoding) {
3162             format = encoding;
3163             flen = elen;
3164         } else {
3165             char *enc  = alloca(flen + 1);
3166             memcpy(enc, format, flen);
3167             enc[flen] = '\0';
3168             format = xo_fix_encoding(xop, enc);
3169             flen = strlen(format);
3170         }
3171
3172         if (nlen == 0) {
3173             static char missing[] = "missing-field-name";
3174             xo_failure(xop, "missing field name: %s", format);
3175             name = missing;
3176             nlen = sizeof(missing) - 1;
3177         }
3178
3179         if (pretty)
3180             xo_buf_indent(xop, -1);
3181         xo_data_append(xop, "<", 1);
3182         xo_data_escape(xop, name, nlen);
3183
3184         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
3185             xo_data_append(xop, xop->xo_attrs.xb_bufp,
3186                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
3187             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
3188         }
3189
3190         /*
3191          * We indicate 'key' fields using the 'key' attribute.  While
3192          * this is really committing the crime of mixing meta-data with
3193          * data, it's often useful.  Especially when format meta-data is
3194          * difficult to come by.
3195          */
3196         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) {
3197             static char attr[] = " key=\"key\"";
3198             xo_data_append(xop, attr, sizeof(attr) - 1);
3199         }
3200
3201         /*
3202          * Save the offset at which we'd place units.  See xo_format_units.
3203          */
3204         if (xop->xo_flags & XOF_UNITS) {
3205             xop->xo_flags |= XOF_UNITS_PENDING;
3206             xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
3207         }
3208
3209         xo_data_append(xop, ">", 1);
3210         xo_format_data(xop, NULL, format, flen, flags);
3211         xo_data_append(xop, "</", 2);
3212         xo_data_escape(xop, name, nlen);
3213         xo_data_append(xop, ">", 1);
3214         if (pretty)
3215             xo_data_append(xop, "\n", 1);
3216         break;
3217
3218     case XO_STYLE_JSON:
3219         if (flags & XFF_DISPLAY_ONLY) {
3220             flags |= XFF_NO_OUTPUT;
3221             xo_format_data(xop, NULL, format, flen, flags);
3222             break;
3223         }
3224
3225         if (encoding) {
3226             format = encoding;
3227             flen = elen;
3228         } else {
3229             char *enc  = alloca(flen + 1);
3230             memcpy(enc, format, flen);
3231             enc[flen] = '\0';
3232             format = xo_fix_encoding(xop, enc);
3233             flen = strlen(format);
3234         }
3235
3236         int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
3237
3238         xo_format_prep(xop, flags);
3239
3240         if (flags & XFF_QUOTE)
3241             quote = 1;
3242         else if (flags & XFF_NOQUOTE)
3243             quote = 0;
3244         else if (flen == 0) {
3245             quote = 0;
3246             format = "true";    /* JSON encodes empty tags as a boolean true */
3247             flen = 4;
3248         } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
3249             quote = 1;
3250         else
3251             quote = 0;
3252
3253         if (nlen == 0) {
3254             static char missing[] = "missing-field-name";
3255             xo_failure(xop, "missing field name: %s", format);
3256             name = missing;
3257             nlen = sizeof(missing) - 1;
3258         }
3259
3260         if (flags & XFF_LEAF_LIST) {
3261             if (!first && pretty)
3262                 xo_data_append(xop, "\n", 1);
3263             if (pretty)
3264                 xo_buf_indent(xop, -1);
3265         } else {
3266             if (pretty)
3267                 xo_buf_indent(xop, -1);
3268             xo_data_append(xop, "\"", 1);
3269
3270             xbp = &xop->xo_data;
3271             int off = xbp->xb_curp - xbp->xb_bufp;
3272
3273             xo_data_escape(xop, name, nlen);
3274
3275             if (xop->xo_flags & XOF_UNDERSCORES) {
3276                 int now = xbp->xb_curp - xbp->xb_bufp;
3277                 for ( ; off < now; off++)
3278                     if (xbp->xb_bufp[off] == '-')
3279                         xbp->xb_bufp[off] = '_';
3280             }
3281             xo_data_append(xop, "\":", 2);
3282             if (pretty)
3283                 xo_data_append(xop, " ", 1);
3284         }
3285
3286         if (quote)
3287             xo_data_append(xop, "\"", 1);
3288
3289         xo_format_data(xop, NULL, format, flen, flags);
3290
3291         if (quote)
3292             xo_data_append(xop, "\"", 1);
3293         break;
3294     }
3295 }
3296
3297 static void
3298 xo_format_content (xo_handle_t *xop, const char *class_name,
3299                    const char *xml_tag, int display_only,
3300                    const char *str, int len, const char *fmt, int flen)
3301 {
3302     switch (xo_style(xop)) {
3303     case XO_STYLE_TEXT:
3304         if (len) {
3305             xo_data_append_content(xop, str, len);
3306         } else
3307             xo_format_data(xop, NULL, fmt, flen, 0);
3308         break;
3309
3310     case XO_STYLE_HTML:
3311         if (len == 0) {
3312             str = fmt;
3313             len = flen;
3314         }
3315
3316         xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0);
3317         break;
3318
3319     case XO_STYLE_XML:
3320         if (xml_tag) {
3321             if (len == 0) {
3322                 str = fmt;
3323                 len = flen;
3324             }
3325
3326             xo_open_container_h(xop, xml_tag);
3327             xo_format_value(xop, "message", 7, str, len, NULL, 0, 0);
3328             xo_close_container_h(xop, xml_tag);
3329
3330         } else {
3331             /*
3332              * Even though we don't care about labels, we need to do
3333              * enough parsing work to skip over the right bits of xo_vap.
3334              */
3335             if (len == 0)
3336                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
3337         }
3338         break;
3339
3340     case XO_STYLE_JSON:
3341         /*
3342          * Even though we don't care about labels, we need to do
3343          * enough parsing work to skip over the right bits of xo_vap.
3344          */
3345         if (display_only) {
3346             if (len == 0)
3347                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
3348             break;
3349         }
3350         /* XXX need schem for representing errors in JSON */
3351         break;
3352     }
3353 }
3354
3355 static const char *xo_color_names[] = {
3356     "default",  /* XO_COL_DEFAULT */
3357     "black",    /* XO_COL_BLACK */
3358     "red",      /* XO_CLOR_RED */
3359     "green",    /* XO_COL_GREEN */
3360     "yellow",   /* XO_COL_YELLOW */
3361     "blue",     /* XO_COL_BLUE */
3362     "magenta",  /* XO_COL_MAGENTA */
3363     "cyan",     /* XO_COL_CYAN */
3364     "white",    /* XO_COL_WHITE */
3365     NULL
3366 };
3367
3368 static int
3369 xo_color_find (const char *str)
3370 {
3371     int i;
3372
3373     for (i = 0; xo_color_names[i]; i++) {
3374         if (strcmp(xo_color_names[i], str) == 0)
3375             return i;
3376     }
3377
3378     return -1;
3379 }
3380
3381 static const char *xo_effect_names[] = {
3382     "reset",                    /* XO_EFF_RESET */
3383     "normal",                   /* XO_EFF_NORMAL */
3384     "bold",                     /* XO_EFF_BOLD */
3385     "underline",                /* XO_EFF_UNDERLINE */
3386     "inverse",                  /* XO_EFF_INVERSE */
3387     NULL
3388 };
3389
3390 static const char *xo_effect_on_codes[] = {
3391     "0",                        /* XO_EFF_RESET */
3392     "0",                        /* XO_EFF_NORMAL */
3393     "1",                        /* XO_EFF_BOLD */
3394     "4",                        /* XO_EFF_UNDERLINE */
3395     "7",                        /* XO_EFF_INVERSE */
3396     NULL
3397 };
3398
3399 #if 0
3400 /*
3401  * See comment below re: joy of terminal standards.  These can
3402  * be use by just adding:
3403  *      if (newp->xoc_effects & bit)
3404  *          code = xo_effect_on_codes[i];
3405  * +    else
3406  * +        code = xo_effect_off_codes[i];
3407  * in xo_color_handle_text.
3408  */
3409 static const char *xo_effect_off_codes[] = {
3410     "0",                        /* XO_EFF_RESET */
3411     "0",                        /* XO_EFF_NORMAL */
3412     "21",                       /* XO_EFF_BOLD */
3413     "24",                       /* XO_EFF_UNDERLINE */
3414     "27",                       /* XO_EFF_INVERSE */
3415     NULL
3416 };
3417 #endif /* 0 */
3418
3419 static int
3420 xo_effect_find (const char *str)
3421 {
3422     int i;
3423
3424     for (i = 0; xo_effect_names[i]; i++) {
3425         if (strcmp(xo_effect_names[i], str) == 0)
3426             return i;
3427     }
3428
3429     return -1;
3430 }
3431
3432 static void
3433 xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
3434 {
3435 #ifdef LIBXO_TEXT_ONLY
3436     return;
3437 #endif /* LIBXO_TEXT_ONLY */
3438
3439     char *cp, *ep, *np, *xp;
3440     int len = strlen(str);
3441     int rc;
3442
3443     /*
3444      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
3445      */
3446     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
3447         /* Trim leading whitespace */
3448         while (isspace((int) *cp))
3449             cp += 1;
3450
3451         np = strchr(cp, ',');
3452         if (np)
3453             *np++ = '\0';
3454
3455         /* Trim trailing whitespace */
3456         xp = cp + strlen(cp) - 1;
3457         while (isspace(*xp) && xp > cp)
3458             *xp-- = '\0';
3459
3460         if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
3461             rc = xo_color_find(cp + 3);
3462             if (rc < 0)
3463                 goto unknown;
3464
3465             xocp->xoc_col_fg = rc;
3466
3467         } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
3468             rc = xo_color_find(cp + 3);
3469             if (rc < 0)
3470                 goto unknown;
3471             xocp->xoc_col_bg = rc;
3472
3473         } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
3474             rc = xo_effect_find(cp + 3);
3475             if (rc < 0)
3476                 goto unknown;
3477             xocp->xoc_effects &= ~(1 << rc);
3478
3479         } else {
3480             rc = xo_effect_find(cp);
3481             if (rc < 0)
3482                 goto unknown;
3483             xocp->xoc_effects |= 1 << rc;
3484
3485             switch (1 << rc) {
3486             case XO_EFF_RESET:
3487                 xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
3488                 /* Note: not "|=" since we want to wipe out the old value */
3489                 xocp->xoc_effects = XO_EFF_RESET;
3490                 break;
3491
3492             case XO_EFF_NORMAL:
3493                 xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
3494                                       | XO_EFF_INVERSE | XO_EFF_NORMAL);
3495                 break;
3496             }
3497         }
3498         continue;
3499
3500     unknown:
3501         if (xop->xo_flags & XOF_WARN)
3502             xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
3503     }
3504 }
3505
3506 static inline int
3507 xo_colors_enabled (xo_handle_t *xop UNUSED)
3508 {
3509 #ifdef LIBXO_TEXT_ONLY
3510     return 0;
3511 #else /* LIBXO_TEXT_ONLY */
3512     return ((xop->xo_flags & XOF_COLOR) ? 1 : 0);
3513 #endif /* LIBXO_TEXT_ONLY */
3514 }
3515
3516 static void
3517 xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
3518 {
3519     char buf[BUFSIZ];
3520     char *cp = buf, *ep = buf + sizeof(buf);
3521     unsigned i, bit;
3522     xo_colors_t *oldp = &xop->xo_colors;
3523     const char *code = NULL;
3524
3525     /*
3526      * Start the buffer with an escape.  We don't want to add the '['
3527      * now, since we let xo_effect_text_add unconditionally add the ';'.
3528      * We'll replace the first ';' with a '[' when we're done.
3529      */
3530     *cp++ = 0x1b;               /* Escape */
3531
3532     /*
3533      * Terminals were designed back in the age before "certainty" was
3534      * invented, when standards were more what you'd call "guidelines"
3535      * than actual rules.  Anyway we can't depend on them to operate
3536      * correctly.  So when display attributes are changed, we punt,
3537      * reseting them all and turning back on the ones we want to keep.
3538      * Longer, but should be completely reliable.  Savvy?
3539      */
3540     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
3541         newp->xoc_effects |= XO_EFF_RESET;
3542         oldp->xoc_effects = 0;
3543     }
3544
3545     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
3546         if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
3547             continue;
3548
3549         if (newp->xoc_effects & bit)
3550             code = xo_effect_on_codes[i];
3551
3552         cp += snprintf(cp, ep - cp, ";%s", code);
3553         if (cp >= ep)
3554             return;             /* Should not occur */
3555
3556         if (bit == XO_EFF_RESET) {
3557             /* Mark up the old value so we can detect current values as new */
3558             oldp->xoc_effects = 0;
3559             oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
3560         }
3561     }
3562
3563     if (newp->xoc_col_fg != oldp->xoc_col_fg) {
3564         cp += snprintf(cp, ep - cp, ";3%u",
3565                        (newp->xoc_col_fg != XO_COL_DEFAULT)
3566                        ? newp->xoc_col_fg - 1 : 9);
3567     }
3568
3569     if (newp->xoc_col_bg != oldp->xoc_col_bg) {
3570         cp += snprintf(cp, ep - cp, ";4%u",
3571                        (newp->xoc_col_bg != XO_COL_DEFAULT)
3572                        ? newp->xoc_col_bg - 1 : 9);
3573     }
3574
3575     if (cp - buf != 1 && cp < ep - 3) {
3576         buf[1] = '[';           /* Overwrite leading ';' */
3577         *cp++ = 'm';
3578         *cp = '\0';
3579         xo_buf_append(&xop->xo_data, buf, cp - buf);
3580     }
3581 }
3582
3583 static void
3584 xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
3585 {
3586     xo_colors_t *oldp = &xop->xo_colors;
3587
3588     /*
3589      * HTML colors are mostly trivial: fill in xo_color_buf with
3590      * a set of class tags representing the colors and effects.
3591      */
3592
3593     /* If nothing changed, then do nothing */
3594     if (oldp->xoc_effects == newp->xoc_effects
3595         && oldp->xoc_col_fg == newp->xoc_col_fg
3596         && oldp->xoc_col_bg == newp->xoc_col_bg)
3597         return;
3598
3599     unsigned i, bit;
3600     xo_buffer_t *xbp = &xop->xo_color_buf;
3601
3602     xo_buf_reset(xbp);          /* We rebuild content after each change */
3603
3604     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
3605         if (!(newp->xoc_effects & bit))
3606             continue;
3607
3608         xo_buf_append_str(xbp, " effect-");
3609         xo_buf_append_str(xbp, xo_effect_names[i]);
3610     }
3611
3612     const char *fg = NULL;
3613     const char *bg = NULL;
3614
3615     if (newp->xoc_col_fg != XO_COL_DEFAULT)
3616         fg = xo_color_names[newp->xoc_col_fg];
3617     if (newp->xoc_col_bg != XO_COL_DEFAULT)
3618         bg = xo_color_names[newp->xoc_col_bg];
3619
3620     if (newp->xoc_effects & XO_EFF_INVERSE) {
3621         const char *tmp = fg;
3622         fg = bg;
3623         bg = tmp;
3624         if (fg == NULL)
3625             fg = "inverse";
3626         if (bg == NULL)
3627             bg = "inverse";
3628
3629     }
3630
3631     if (fg) {
3632         xo_buf_append_str(xbp, " color-fg-");
3633         xo_buf_append_str(xbp, fg);
3634     }
3635
3636     if (bg) {
3637         xo_buf_append_str(xbp, " color-bg-");
3638         xo_buf_append_str(xbp, bg);
3639     }
3640 }
3641
3642 static void
3643 xo_format_colors (xo_handle_t *xop, const char *str, int len,
3644                   const char *fmt, int flen)
3645 {
3646     xo_buffer_t xb;
3647
3648     /* If the string is static and we've in an encoding style, bail */
3649     if (len != 0
3650         && (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON))
3651         return;
3652
3653     xo_buf_init(&xb);
3654
3655     if (len)
3656         xo_buf_append(&xb, str, len);
3657     else if (flen)
3658         xo_format_data(xop, &xb, fmt, flen, 0);
3659     else
3660         xo_buf_append(&xb, "reset", 6); /* Default if empty */
3661
3662     if (xo_colors_enabled(xop)) {
3663         switch (xo_style(xop)) {
3664         case XO_STYLE_TEXT:
3665         case XO_STYLE_HTML:
3666             xo_buf_append(&xb, "", 1);
3667
3668             xo_colors_t xoc = xop->xo_colors;
3669             xo_colors_parse(xop, &xoc, xb.xb_bufp);
3670
3671             if (xo_style(xop) == XO_STYLE_TEXT) {
3672                 /*
3673                  * Text mode means emitting the colors as ANSI character
3674                  * codes.  This will allow people who like colors to have
3675                  * colors.  The issue is, of course conflicting with the
3676                  * user's perfectly reasonable color scheme.  Which leads
3677                  * to the hell of LSCOLORS, where even app need to have
3678                  * customization hooks for adjusting colors.  Instead we
3679                  * provide a simpler-but-still-annoying answer where one
3680                  * can map colors to other colors.
3681                  */
3682                 xo_colors_handle_text(xop, &xoc);
3683                 xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
3684
3685             } else {
3686                 /*
3687                  * HTML output is wrapped in divs, so the color information
3688                  * must appear in every div until cleared.  Most pathetic.
3689                  * Most unavoidable.
3690                  */
3691                 xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
3692                 xo_colors_handle_html(xop, &xoc);
3693             }
3694
3695             xop->xo_colors = xoc;
3696             break;
3697
3698         case XO_STYLE_XML:
3699         case XO_STYLE_JSON:
3700             /*
3701              * Nothing to do; we did all that work just to clear the stack of
3702              * formatting arguments.
3703              */
3704             break;
3705         }
3706     }
3707
3708     xo_buf_cleanup(&xb);
3709 }
3710
3711 static void
3712 xo_format_units (xo_handle_t *xop, const char *str, int len,
3713                  const char *fmt, int flen)
3714 {
3715     static char units_start_xml[] = " units=\"";
3716     static char units_start_html[] = " data-units=\"";
3717
3718     if (!(xop->xo_flags & XOF_UNITS_PENDING)) {
3719         xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen);
3720         return;
3721     }
3722
3723     xo_buffer_t *xbp = &xop->xo_data;
3724     int start = xop->xo_units_offset;
3725     int stop = xbp->xb_curp - xbp->xb_bufp;
3726
3727     if (xo_style(xop) == XO_STYLE_XML)
3728         xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
3729     else if (xo_style(xop) == XO_STYLE_HTML)
3730         xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
3731     else
3732         return;
3733
3734     if (len)
3735         xo_data_append(xop, str, len);
3736     else
3737         xo_format_data(xop, NULL, fmt, flen, 0);
3738
3739     xo_buf_append(xbp, "\"", 1);
3740
3741     int now = xbp->xb_curp - xbp->xb_bufp;
3742     int delta = now - stop;
3743     if (delta < 0) {            /* Strange; no output to move */
3744         xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
3745         return;
3746     }
3747
3748     /*
3749      * Now we're in it alright.  We've need to insert the unit value
3750      * we just created into the right spot.  We make a local copy,
3751      * move it and then insert our copy.  We know there's room in the
3752      * buffer, since we're just moving this around.
3753      */
3754     char *buf = alloca(delta);
3755
3756     memcpy(buf, xbp->xb_bufp + stop, delta);
3757     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3758     memmove(xbp->xb_bufp + start, buf, delta);
3759 }
3760
3761 static int
3762 xo_find_width (xo_handle_t *xop, const char *str, int len,
3763                  const char *fmt, int flen)
3764 {
3765     long width = 0;
3766     char *bp;
3767     char *cp;
3768
3769     if (len) {
3770         bp = alloca(len + 1);   /* Make local NUL-terminated copy of str */
3771         memcpy(bp, str, len);
3772         bp[len] = '\0';
3773
3774         width = strtol(bp, &cp, 0);
3775         if (width == LONG_MIN || width == LONG_MAX
3776             || bp == cp || *cp != '\0' ) {
3777             width = 0;
3778             xo_failure(xop, "invalid width for anchor: '%s'", bp);
3779         }
3780     } else if (flen) {
3781         if (flen != 2 || strncmp("%d", fmt, flen) != 0)
3782             xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
3783         if (!(xop->xo_flags & XOF_NO_VA_ARG))
3784             width = va_arg(xop->xo_vap, int);
3785     }
3786
3787     return width;
3788 }
3789
3790 static void
3791 xo_anchor_clear (xo_handle_t *xop)
3792 {
3793     xop->xo_flags &= ~XOF_ANCHOR;
3794     xop->xo_anchor_offset = 0;
3795     xop->xo_anchor_columns = 0;
3796     xop->xo_anchor_min_width = 0;
3797 }
3798
3799 /*
3800  * An anchor is a marker used to delay field width implications.
3801  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
3802  * We are looking for output like "     1/4/5"
3803  *
3804  * To make this work, we record the anchor and then return to
3805  * format it when the end anchor tag is seen.
3806  */
3807 static void
3808 xo_anchor_start (xo_handle_t *xop, const char *str, int len,
3809                  const char *fmt, int flen)
3810 {
3811     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
3812         return;
3813
3814     if (xop->xo_flags & XOF_ANCHOR)
3815         xo_failure(xop, "the anchor already recording is discarded");
3816
3817     xop->xo_flags |= XOF_ANCHOR;
3818     xo_buffer_t *xbp = &xop->xo_data;
3819     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
3820     xop->xo_anchor_columns = 0;
3821
3822     /*
3823      * Now we find the width, if possible.  If it's not there,
3824      * we'll get it on the end anchor.
3825      */
3826     xop->xo_anchor_min_width = xo_find_width(xop, str, len, fmt, flen);
3827 }
3828
3829 static void
3830 xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
3831                  const char *fmt, int flen)
3832 {
3833     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
3834         return;
3835
3836     if (!(xop->xo_flags & XOF_ANCHOR)) {
3837         xo_failure(xop, "no start anchor");
3838         return;
3839     }
3840
3841     xop->xo_flags &= ~XOF_UNITS_PENDING;
3842
3843     int width = xo_find_width(xop, str, len, fmt, flen);
3844     if (width == 0)
3845         width = xop->xo_anchor_min_width;
3846
3847     if (width == 0)             /* No width given; nothing to do */
3848         goto done;
3849
3850     xo_buffer_t *xbp = &xop->xo_data;
3851     int start = xop->xo_anchor_offset;
3852     int stop = xbp->xb_curp - xbp->xb_bufp;
3853     int abswidth = (width > 0) ? width : -width;
3854     int blen = abswidth - xop->xo_anchor_columns;
3855
3856     if (blen <= 0)              /* Already over width */
3857         goto done;
3858
3859     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
3860         xo_failure(xop, "width over %u are not supported",
3861                    XO_MAX_ANCHOR_WIDTH);
3862         goto done;
3863     }
3864
3865     /* Make a suitable padding field and emit it */
3866     char *buf = alloca(blen);
3867     memset(buf, ' ', blen);
3868     xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0);
3869
3870     if (width < 0)              /* Already left justified */
3871         goto done;
3872
3873     int now = xbp->xb_curp - xbp->xb_bufp;
3874     int delta = now - stop;
3875     if (delta < 0)              /* Strange; no output to move */
3876         goto done;
3877
3878     /*
3879      * Now we're in it alright.  We've need to insert the padding data
3880      * we just created (which might be an HTML <div> or text) before
3881      * the formatted data.  We make a local copy, move it and then
3882      * insert our copy.  We know there's room in the buffer, since
3883      * we're just moving this around.
3884      */
3885     if (delta > blen)
3886         buf = alloca(delta);    /* Expand buffer if needed */
3887
3888     memcpy(buf, xbp->xb_bufp + stop, delta);
3889     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3890     memmove(xbp->xb_bufp + start, buf, delta);
3891
3892  done:
3893     xo_anchor_clear(xop);
3894 }
3895
3896 static int
3897 xo_do_emit (xo_handle_t *xop, const char *fmt)
3898 {
3899     int rc = 0;
3900     const char *cp, *sp, *ep, *basep;
3901     char *newp = NULL;
3902     int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0;
3903     int flush_line = (xop->xo_flags & XOF_FLUSH_LINE) ? 1 : 0;
3904
3905     xop->xo_columns = 0;        /* Always reset it */
3906
3907     for (cp = fmt; *cp; ) {
3908         if (*cp == '\n') {
3909             xo_line_close(xop);
3910             if (flush_line && xo_flush_h(xop) < 0)
3911                 return -1;
3912             cp += 1;
3913             continue;
3914
3915         } else if (*cp == '{') {
3916             if (cp[1] == '{') { /* Start of {{escaped braces}} */
3917
3918                 cp += 2;        /* Skip over _both_ characters */
3919                 for (sp = cp; *sp; sp++) {
3920                     if (*sp == '}' && sp[1] == '}')
3921                         break;
3922                 }
3923                 if (*sp == '\0') {
3924                     xo_failure(xop, "missing closing '}}': %s", fmt);
3925                     return -1;
3926                 }
3927
3928                 xo_format_text(xop, cp, sp - cp);
3929
3930                 /* Move along the string, but don't run off the end */
3931                 if (*sp == '}' && sp[1] == '}')
3932                     sp += 2;
3933                 cp = *sp ? sp + 1 : sp;
3934                 continue;
3935             }
3936             /* Else fall thru to the code below */
3937
3938         } else {
3939             /* Normal text */
3940             for (sp = cp; *sp; sp++) {
3941                 if (*sp == '{' || *sp == '\n')
3942                     break;
3943             }
3944             xo_format_text(xop, cp, sp - cp);
3945
3946             cp = sp;
3947             continue;
3948         }
3949
3950         basep = cp + 1;
3951
3952         /*
3953          * We are looking at the start of a field definition.  The format is:
3954          *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
3955          * Modifiers are optional and include the following field types:
3956          *   'D': decoration; something non-text and non-data (colons, commmas)
3957          *   'E': error message
3958          *   'L': label; text preceding data
3959          *   'N': note; text following data
3960          *   'P': padding; whitespace
3961          *   'T': Title, where 'content' is a column title
3962          *   'U': Units, where 'content' is the unit label
3963          *   'V': value, where 'content' is the name of the field (the default)
3964          *   'W': warning message
3965          *   '[': start a section of anchored text
3966          *   ']': end a section of anchored text
3967          * The following flags are also supported:
3968          *   'c': flag: emit a colon after the label
3969          *   'd': field is only emitted for display formats (text and html)
3970          *   'e': field is only emitted for encoding formats (xml and json)
3971          *   'k': this field is a key, suitable for XPath predicates
3972          *   'l': a leaf-list, a simple list of values
3973          *   'n': no quotes around this field
3974          *   'q': add quotes around this field
3975          *   't': trim whitespace around the value
3976          *   'w': emit a blank after the label
3977          * The print-fmt and encode-fmt strings is the printf-style formating
3978          * for this data.  JSON and XML will use the encoding-fmt, if present.
3979          * If the encode-fmt is not provided, it defaults to the print-fmt.
3980          * If the print-fmt is not provided, it defaults to 's'.
3981          */
3982         unsigned ftype = 0, flags = 0;
3983         const char *content = NULL, *format = NULL, *encoding = NULL;
3984         int clen = 0, flen = 0, elen = 0;
3985
3986         for (sp = basep; sp; sp++) {
3987             if (*sp == ':' || *sp == '/' || *sp == '}')
3988                 break;
3989
3990             if (*sp == '\\') {
3991                 if (sp[1] == '\0') {
3992                     xo_failure(xop, "backslash at the end of string");
3993                     return -1;
3994                 }
3995                 sp += 1;
3996                 continue;
3997             }
3998
3999             switch (*sp) {
4000             case 'C':
4001             case 'D':
4002             case 'E':
4003             case 'L':
4004             case 'N':
4005             case 'P':
4006             case 'T':
4007             case 'U':
4008             case 'V':
4009             case 'W':
4010             case '[':
4011             case ']':
4012                 if (ftype != 0) {
4013                     xo_failure(xop, "field descriptor uses multiple types: %s",
4014                                   fmt);
4015                     return -1;
4016                 }
4017                 ftype = *sp;
4018                 break;
4019
4020             case 'c':
4021                 flags |= XFF_COLON;
4022                 break;
4023
4024             case 'd':
4025                 flags |= XFF_DISPLAY_ONLY;
4026                 break;
4027
4028             case 'e':
4029                 flags |= XFF_ENCODE_ONLY;
4030                 break;
4031
4032             case 'k':
4033                 flags |= XFF_KEY;
4034                 break;
4035
4036             case 'l':
4037                 flags |= XFF_LEAF_LIST;
4038                 break;
4039
4040             case 'n':
4041                 flags |= XFF_NOQUOTE;
4042                 break;
4043
4044             case 'q':
4045                 flags |= XFF_QUOTE;
4046                 break;
4047
4048             case 't':
4049                 flags |= XFF_TRIM_WS;
4050                 break;
4051
4052             case 'w':
4053                 flags |= XFF_WS;
4054                 break;
4055
4056             default:
4057                 xo_failure(xop, "field descriptor uses unknown modifier: %s",
4058                               fmt);
4059                 /*
4060                  * No good answer here; a bad format will likely
4061                  * mean a core file.  We just return and hope
4062                  * the caller notices there's no output, and while
4063                  * that seems, well, bad.  There's nothing better.
4064                  */
4065                 return -1;
4066             }
4067         }
4068
4069         if (*sp == ':') {
4070             for (ep = ++sp; *sp; sp++) {
4071                 if (*sp == '}' || *sp == '/')
4072                     break;
4073                 if (*sp == '\\') {
4074                     if (sp[1] == '\0') {
4075                         xo_failure(xop, "backslash at the end of string");
4076                         return -1;
4077                     }
4078                     sp += 1;
4079                     continue;
4080                 }
4081             }
4082             if (ep != sp) {
4083                 clen = sp - ep;
4084                 content = ep;
4085             }
4086         } else {
4087             xo_failure(xop, "missing content (':'): %s", fmt);
4088             return -1;
4089         }
4090
4091         if (*sp == '/') {
4092             for (ep = ++sp; *sp; sp++) {
4093                 if (*sp == '}' || *sp == '/')
4094                     break;
4095                 if (*sp == '\\') {
4096                     if (sp[1] == '\0') {
4097                         xo_failure(xop, "backslash at the end of string");
4098                         return -1;
4099                     }
4100                     sp += 1;
4101                     continue;
4102                 }
4103             }
4104             flen = sp - ep;
4105             format = ep;
4106         }
4107
4108         if (*sp == '/') {
4109             for (ep = ++sp; *sp; sp++) {
4110                 if (*sp == '}')
4111                     break;
4112             }
4113             elen = sp - ep;
4114             encoding = ep;
4115         }
4116
4117         if (*sp == '}') {
4118             sp += 1;
4119         } else {
4120             xo_failure(xop, "missing closing '}': %s", fmt);
4121             return -1;
4122         }
4123
4124         if (ftype == 0 || ftype == 'V') {
4125             if (format == NULL) {
4126                 /* Default format for value fields is '%s' */
4127                 format = "%s";
4128                 flen = 2;
4129             }
4130
4131             xo_format_value(xop, content, clen, format, flen,
4132                             encoding, elen, flags);
4133
4134         } else if (ftype == '[')
4135                 xo_anchor_start(xop, content, clen, format, flen);
4136         else if (ftype == ']')
4137                 xo_anchor_stop(xop, content, clen, format, flen);
4138         else if (ftype == 'C')
4139                 xo_format_colors(xop, content, clen, format, flen);
4140
4141         else  if (clen || format) { /* Need either content or format */
4142             if (format == NULL) {
4143                 /* Default format for value fields is '%s' */
4144                 format = "%s";
4145                 flen = 2;
4146             }
4147
4148             if (ftype == 'D')
4149                 xo_format_content(xop, "decoration", NULL, 1,
4150                                   content, clen, format, flen);
4151             else if (ftype == 'E')
4152                 xo_format_content(xop, "error", "error", 0,
4153                                   content, clen, format, flen);
4154             else if (ftype == 'L')
4155                 xo_format_content(xop, "label", NULL, 1,
4156                                   content, clen, format, flen);
4157             else if (ftype == 'N')
4158                 xo_format_content(xop, "note", NULL, 1,
4159                                   content, clen, format, flen);
4160             else if (ftype == 'P')
4161                 xo_format_content(xop, "padding", NULL, 1,
4162                                   content, clen, format, flen);
4163             else if (ftype == 'T')
4164                 xo_format_title(xop, content, clen, format, flen);
4165             else if (ftype == 'U') {
4166                 if (flags & XFF_WS)
4167                     xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
4168                 xo_format_units(xop, content, clen, format, flen);
4169             } else if (ftype == 'W')
4170                 xo_format_content(xop, "warning", "warning", 0,
4171                                   content, clen, format, flen);
4172         }
4173
4174         if (flags & XFF_COLON)
4175             xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0);
4176         if (ftype != 'U' && (flags & XFF_WS))
4177             xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
4178
4179         cp += sp - basep + 1;
4180         if (newp) {
4181             xo_free(newp);
4182             newp = NULL;
4183         }
4184     }
4185
4186     /* If we don't have an anchor, write the text out */
4187     if (flush && !(xop->xo_flags & XOF_ANCHOR)) {
4188         if (xo_write(xop) < 0) 
4189             rc = -1;            /* Report failure */
4190         else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0)
4191             rc = -1;
4192     }
4193
4194     return (rc < 0) ? rc : (int) xop->xo_columns;
4195 }
4196
4197 int
4198 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
4199 {
4200     int rc;
4201
4202     xop = xo_default(xop);
4203     va_copy(xop->xo_vap, vap);
4204     rc = xo_do_emit(xop, fmt);
4205     va_end(xop->xo_vap);
4206     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4207
4208     return rc;
4209 }
4210
4211 int
4212 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
4213 {
4214     int rc;
4215
4216     xop = xo_default(xop);
4217     va_start(xop->xo_vap, fmt);
4218     rc = xo_do_emit(xop, fmt);
4219     va_end(xop->xo_vap);
4220     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4221
4222     return rc;
4223 }
4224
4225 int
4226 xo_emit (const char *fmt, ...)
4227 {
4228     xo_handle_t *xop = xo_default(NULL);
4229     int rc;
4230
4231     va_start(xop->xo_vap, fmt);
4232     rc = xo_do_emit(xop, fmt);
4233     va_end(xop->xo_vap);
4234     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4235
4236     return rc;
4237 }
4238
4239 int
4240 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
4241 {
4242     const int extra = 5;        /* space, equals, quote, quote, and nul */
4243     xop = xo_default(xop);
4244
4245     if (xo_style(xop) != XO_STYLE_XML)
4246         return 0;
4247
4248     int nlen = strlen(name);
4249     xo_buffer_t *xbp = &xop->xo_attrs;
4250
4251     if (!xo_buf_has_room(xbp, nlen + extra))
4252         return -1;
4253
4254     *xbp->xb_curp++ = ' ';
4255     memcpy(xbp->xb_curp, name, nlen);
4256     xbp->xb_curp += nlen;
4257     *xbp->xb_curp++ = '=';
4258     *xbp->xb_curp++ = '"';
4259
4260     int rc = xo_vsnprintf(xop, xbp, fmt, vap);
4261
4262     if (rc > 0) {
4263         rc = xo_escape_xml(xbp, rc, 1);
4264         xbp->xb_curp += rc;
4265     }
4266
4267     if (!xo_buf_has_room(xbp, 2))
4268         return -1;
4269
4270     *xbp->xb_curp++ = '"';
4271     *xbp->xb_curp = '\0';
4272
4273     return rc + nlen + extra;
4274 }
4275
4276 int
4277 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
4278 {
4279     int rc;
4280     va_list vap;
4281
4282     va_start(vap, fmt);
4283     rc = xo_attr_hv(xop, name, fmt, vap);
4284     va_end(vap);
4285
4286     return rc;
4287 }
4288
4289 int
4290 xo_attr (const char *name, const char *fmt, ...)
4291 {
4292     int rc;
4293     va_list vap;
4294
4295     va_start(vap, fmt);
4296     rc = xo_attr_hv(NULL, name, fmt, vap);
4297     va_end(vap);
4298
4299     return rc;
4300 }
4301
4302 static void
4303 xo_stack_set_flags (xo_handle_t *xop)
4304 {
4305     if (xop->xo_flags & XOF_NOT_FIRST) {
4306         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4307
4308         xsp->xs_flags |= XSF_NOT_FIRST;
4309         xop->xo_flags &= ~XOF_NOT_FIRST;
4310     }
4311 }
4312
4313 static void
4314 xo_depth_change (xo_handle_t *xop, const char *name,
4315                  int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
4316 {
4317     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
4318         indent = 0;
4319
4320     if (xop->xo_flags & XOF_DTRT)
4321         flags |= XSF_DTRT;
4322
4323     if (delta >= 0) {                   /* Push operation */
4324         if (xo_depth_check(xop, xop->xo_depth + delta))
4325             return;
4326
4327         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
4328         xsp->xs_flags = flags;
4329         xsp->xs_state = state;
4330         xo_stack_set_flags(xop);
4331
4332         if (name == NULL)
4333             name = XO_FAILURE_NAME;
4334
4335         int len = strlen(name) + 1;
4336         char *cp = xo_realloc(NULL, len);
4337         if (cp) {
4338             memcpy(cp, name, len);
4339             xsp->xs_name = cp;
4340         }
4341
4342     } else {                    /* Pop operation */
4343         if (xop->xo_depth == 0) {
4344             if (!(xop->xo_flags & XOF_IGNORE_CLOSE))
4345                 xo_failure(xop, "close with empty stack: '%s'", name);
4346             return;
4347         }
4348
4349         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4350         if (xop->xo_flags & XOF_WARN) {
4351             const char *top = xsp->xs_name;
4352             if (top && strcmp(name, top) != 0) {
4353                 xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
4354                               name, top);
4355                 return;
4356             } 
4357             if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
4358                 xo_failure(xop, "list close on list confict: '%s'",
4359                               name);
4360                 return;
4361             }
4362             if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
4363                 xo_failure(xop, "list close on instance confict: '%s'",
4364                               name);
4365                 return;
4366             }
4367         }
4368
4369         if (xsp->xs_name) {
4370             xo_free(xsp->xs_name);
4371             xsp->xs_name = NULL;
4372         }
4373         if (xsp->xs_keys) {
4374             xo_free(xsp->xs_keys);
4375             xsp->xs_keys = NULL;
4376         }
4377     }
4378
4379     xop->xo_depth += delta;     /* Record new depth */
4380     xop->xo_indent += indent;
4381 }
4382
4383 void
4384 xo_set_depth (xo_handle_t *xop, int depth)
4385 {
4386     xop = xo_default(xop);
4387
4388     if (xo_depth_check(xop, depth))
4389         return;
4390
4391     xop->xo_depth += depth;
4392     xop->xo_indent += depth;
4393 }
4394
4395 static xo_xsf_flags_t
4396 xo_stack_flags (unsigned xflags)
4397 {
4398     if (xflags & XOF_DTRT)
4399         return XSF_DTRT;
4400     return 0;
4401 }
4402
4403 static void
4404 xo_emit_top (xo_handle_t *xop, const char *ppn)
4405 {
4406     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
4407     xop->xo_flags |= XOF_TOP_EMITTED;
4408
4409     if (xop->xo_version) {
4410         xo_printf(xop, "%*s\"__version\": \"%s\", %s",
4411                   xo_indent(xop), "", xop->xo_version, ppn);
4412         xo_free(xop->xo_version);
4413         xop->xo_version = NULL;
4414     }
4415 }
4416
4417 static int
4418 xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
4419 {
4420     int rc = 0;
4421     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4422     const char *pre_nl = "";
4423
4424     if (name == NULL) {
4425         xo_failure(xop, "NULL passed for container name");
4426         name = XO_FAILURE_NAME;
4427     }
4428
4429     flags |= xop->xo_flags;     /* Pick up handle flags */
4430
4431     switch (xo_style(xop)) {
4432     case XO_STYLE_XML:
4433         rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
4434
4435         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4436             rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
4437             xo_data_append(xop, xop->xo_attrs.xb_bufp,
4438                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4439             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4440         }
4441
4442         rc += xo_printf(xop, ">%s", ppn);
4443         break;
4444
4445     case XO_STYLE_JSON:
4446         xo_stack_set_flags(xop);
4447
4448         if (!(xop->xo_flags & XOF_NO_TOP)
4449                 && !(xop->xo_flags & XOF_TOP_EMITTED))
4450             xo_emit_top(xop, ppn);
4451
4452         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4453             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4454         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4455
4456         rc = xo_printf(xop, "%s%*s\"%s\": {%s",
4457                        pre_nl, xo_indent(xop), "", name, ppn);
4458         break;
4459     }
4460
4461     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
4462                     xo_stack_flags(flags));
4463
4464     return rc;
4465 }
4466
4467 static int
4468 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
4469 {
4470     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
4471 }
4472
4473 int
4474 xo_open_container_h (xo_handle_t *xop, const char *name)
4475 {
4476     return xo_open_container_hf(xop, 0, name);
4477 }
4478
4479 int
4480 xo_open_container (const char *name)
4481 {
4482     return xo_open_container_hf(NULL, 0, name);
4483 }
4484
4485 int
4486 xo_open_container_hd (xo_handle_t *xop, const char *name)
4487 {
4488     return xo_open_container_hf(xop, XOF_DTRT, name);
4489 }
4490
4491 int
4492 xo_open_container_d (const char *name)
4493 {
4494     return xo_open_container_hf(NULL, XOF_DTRT, name);
4495 }
4496
4497 static int
4498 xo_do_close_container (xo_handle_t *xop, const char *name)
4499 {
4500     xop = xo_default(xop);
4501
4502     int rc = 0;
4503     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4504     const char *pre_nl = "";
4505
4506     if (name == NULL) {
4507         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4508
4509         name = xsp->xs_name;
4510         if (name) {
4511             int len = strlen(name) + 1;
4512             /* We need to make a local copy; xo_depth_change will free it */
4513             char *cp = alloca(len);
4514             memcpy(cp, name, len);
4515             name = cp;
4516         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4517             xo_failure(xop, "missing name without 'dtrt' mode");
4518             name = XO_FAILURE_NAME;
4519         }
4520     }
4521
4522     switch (xo_style(xop)) {
4523     case XO_STYLE_XML:
4524         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
4525         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
4526         break;
4527
4528     case XO_STYLE_JSON:
4529         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4530         ppn = (xop->xo_depth <= 1) ? "\n" : "";
4531
4532         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
4533         rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
4534         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4535         break;
4536
4537     case XO_STYLE_HTML:
4538     case XO_STYLE_TEXT:
4539         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
4540         break;
4541     }
4542
4543     return rc;
4544 }
4545
4546 int
4547 xo_close_container_h (xo_handle_t *xop, const char *name)
4548 {
4549     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
4550 }
4551
4552 int
4553 xo_close_container (const char *name)
4554 {
4555     return xo_close_container_h(NULL, name);
4556 }
4557
4558 int
4559 xo_close_container_hd (xo_handle_t *xop)
4560 {
4561     return xo_close_container_h(xop, NULL);
4562 }
4563
4564 int
4565 xo_close_container_d (void)
4566 {
4567     return xo_close_container_h(NULL, NULL);
4568 }
4569
4570 static int
4571 xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4572 {
4573     int rc = 0;
4574     int indent = 0;
4575
4576     xop = xo_default(xop);
4577
4578     if (xo_style(xop) == XO_STYLE_JSON) {
4579         const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4580         const char *pre_nl = "";
4581
4582         indent = 1;
4583         if (!(xop->xo_flags & XOF_NO_TOP)
4584                 && !(xop->xo_flags & XOF_TOP_EMITTED))
4585             xo_emit_top(xop, ppn);
4586
4587         if (name == NULL) {
4588             xo_failure(xop, "NULL passed for list name");
4589             name = XO_FAILURE_NAME;
4590         }
4591
4592         xo_stack_set_flags(xop);
4593
4594         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4595             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4596         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4597
4598         rc = xo_printf(xop, "%s%*s\"%s\": [%s",
4599                        pre_nl, xo_indent(xop), "", name, ppn);
4600     }
4601
4602     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
4603                     XSF_LIST | xo_stack_flags(flags));
4604
4605     return rc;
4606 }
4607
4608 static int
4609 xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4610 {
4611     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
4612 }
4613
4614 int
4615 xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
4616 {
4617     return xo_open_list_hf(xop, 0, name);
4618 }
4619
4620 int
4621 xo_open_list (const char *name)
4622 {
4623     return xo_open_list_hf(NULL, 0, name);
4624 }
4625
4626 int
4627 xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
4628 {
4629     return xo_open_list_hf(xop, XOF_DTRT, name);
4630 }
4631
4632 int
4633 xo_open_list_d (const char *name)
4634 {
4635     return xo_open_list_hf(NULL, XOF_DTRT, name);
4636 }
4637
4638 static int
4639 xo_do_close_list (xo_handle_t *xop, const char *name)
4640 {
4641     int rc = 0;
4642     const char *pre_nl = "";
4643
4644     if (name == NULL) {
4645         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4646
4647         name = xsp->xs_name;
4648         if (name) {
4649             int len = strlen(name) + 1;
4650             /* We need to make a local copy; xo_depth_change will free it */
4651             char *cp = alloca(len);
4652             memcpy(cp, name, len);
4653             name = cp;
4654         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4655             xo_failure(xop, "missing name without 'dtrt' mode");
4656             name = XO_FAILURE_NAME;
4657         }
4658     }
4659
4660     if (xo_style(xop) == XO_STYLE_JSON) {
4661         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4662             pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4663         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4664
4665         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
4666         rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
4667         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4668
4669     } else {
4670         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
4671         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4672     }
4673
4674     return rc;
4675 }
4676
4677 int
4678 xo_close_list_h (xo_handle_t *xop, const char *name)
4679 {
4680     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
4681 }
4682
4683 int
4684 xo_close_list (const char *name)
4685 {
4686     return xo_close_list_h(NULL, name);
4687 }
4688
4689 int
4690 xo_close_list_hd (xo_handle_t *xop)
4691 {
4692     return xo_close_list_h(xop, NULL);
4693 }
4694
4695 int
4696 xo_close_list_d (void)
4697 {
4698     return xo_close_list_h(NULL, NULL);
4699 }
4700
4701 static int
4702 xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4703 {
4704     int rc = 0;
4705     int indent = 0;
4706
4707     xop = xo_default(xop);
4708
4709     if (xo_style(xop) == XO_STYLE_JSON) {
4710         const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4711         const char *pre_nl = "";
4712
4713         indent = 1;
4714
4715         if (!(xop->xo_flags & XOF_NO_TOP)) {
4716             if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
4717                 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
4718                 xop->xo_flags |= XOF_TOP_EMITTED;
4719             }
4720         }
4721
4722         if (name == NULL) {
4723             xo_failure(xop, "NULL passed for list name");
4724             name = XO_FAILURE_NAME;
4725         }
4726
4727         xo_stack_set_flags(xop);
4728
4729         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4730             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4731         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4732
4733         rc = xo_printf(xop, "%s%*s\"%s\": [%s",
4734                        pre_nl, xo_indent(xop), "", name, ppn);
4735     }
4736
4737     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
4738                     XSF_LIST | xo_stack_flags(flags));
4739
4740     return rc;
4741 }
4742
4743 static int
4744 xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
4745 {
4746     int rc = 0;
4747     const char *pre_nl = "";
4748
4749     if (name == NULL) {
4750         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4751
4752         name = xsp->xs_name;
4753         if (name) {
4754             int len = strlen(name) + 1;
4755             /* We need to make a local copy; xo_depth_change will free it */
4756             char *cp = alloca(len);
4757             memcpy(cp, name, len);
4758             name = cp;
4759         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4760             xo_failure(xop, "missing name without 'dtrt' mode");
4761             name = XO_FAILURE_NAME;
4762         }
4763     }
4764
4765     if (xo_style(xop) == XO_STYLE_JSON) {
4766         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4767             pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4768         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4769
4770         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
4771         rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
4772         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4773
4774     } else {
4775         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
4776         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4777     }
4778
4779     return rc;
4780 }
4781
4782 static int
4783 xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4784 {
4785     xop = xo_default(xop);
4786
4787     int rc = 0;
4788     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4789     const char *pre_nl = "";
4790
4791     flags |= xop->xo_flags;
4792
4793     if (name == NULL) {
4794         xo_failure(xop, "NULL passed for instance name");
4795         name = XO_FAILURE_NAME;
4796     }
4797
4798     switch (xo_style(xop)) {
4799     case XO_STYLE_XML:
4800         rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
4801
4802         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4803             rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
4804             xo_data_append(xop, xop->xo_attrs.xb_bufp,
4805                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4806             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4807         }
4808
4809         rc += xo_printf(xop, ">%s", ppn);
4810         break;
4811
4812     case XO_STYLE_JSON:
4813         xo_stack_set_flags(xop);
4814
4815         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4816             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4817         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4818
4819         rc = xo_printf(xop, "%s%*s{%s",
4820                        pre_nl, xo_indent(xop), "", ppn);
4821         break;
4822     }
4823
4824     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
4825
4826     return rc;
4827 }
4828
4829 static int
4830 xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4831 {
4832     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
4833 }
4834
4835 int
4836 xo_open_instance_h (xo_handle_t *xop, const char *name)
4837 {
4838     return xo_open_instance_hf(xop, 0, name);
4839 }
4840
4841 int
4842 xo_open_instance (const char *name)
4843 {
4844     return xo_open_instance_hf(NULL, 0, name);
4845 }
4846
4847 int
4848 xo_open_instance_hd (xo_handle_t *xop, const char *name)
4849 {
4850     return xo_open_instance_hf(xop, XOF_DTRT, name);
4851 }
4852
4853 int
4854 xo_open_instance_d (const char *name)
4855 {
4856     return xo_open_instance_hf(NULL, XOF_DTRT, name);
4857 }
4858
4859 static int
4860 xo_do_close_instance (xo_handle_t *xop, const char *name)
4861 {
4862     xop = xo_default(xop);
4863
4864     int rc = 0;
4865     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4866     const char *pre_nl = "";
4867
4868     if (name == NULL) {
4869         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4870
4871         name = xsp->xs_name;
4872         if (name) {
4873             int len = strlen(name) + 1;
4874             /* We need to make a local copy; xo_depth_change will free it */
4875             char *cp = alloca(len);
4876             memcpy(cp, name, len);
4877             name = cp;
4878         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4879             xo_failure(xop, "missing name without 'dtrt' mode");
4880             name = XO_FAILURE_NAME;
4881         }
4882     }
4883
4884     switch (xo_style(xop)) {
4885     case XO_STYLE_XML:
4886         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
4887         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
4888         break;
4889
4890     case XO_STYLE_JSON:
4891         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4892
4893         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
4894         rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
4895         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4896         break;
4897
4898     case XO_STYLE_HTML:
4899     case XO_STYLE_TEXT:
4900         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
4901         break;
4902     }
4903
4904     return rc;
4905 }
4906
4907 int
4908 xo_close_instance_h (xo_handle_t *xop, const char *name)
4909 {
4910     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
4911 }
4912
4913 int
4914 xo_close_instance (const char *name)
4915 {
4916     return xo_close_instance_h(NULL, name);
4917 }
4918
4919 int
4920 xo_close_instance_hd (xo_handle_t *xop)
4921 {
4922     return xo_close_instance_h(xop, NULL);
4923 }
4924
4925 int
4926 xo_close_instance_d (void)
4927 {
4928     return xo_close_instance_h(NULL, NULL);
4929 }
4930
4931 static int
4932 xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
4933 {
4934     xo_stack_t *xsp;
4935     int rc = 0;
4936     xo_xsf_flags_t flags;
4937
4938     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
4939         switch (xsp->xs_state) {
4940         case XSS_INIT:
4941             /* Nothing */
4942             rc = 0;
4943             break;
4944
4945         case XSS_OPEN_CONTAINER:
4946             rc = xo_do_close_container(xop, NULL);
4947             break;
4948
4949         case XSS_OPEN_LIST:
4950             rc = xo_do_close_list(xop, NULL);
4951             break;
4952
4953         case XSS_OPEN_INSTANCE:
4954             rc = xo_do_close_instance(xop, NULL);
4955             break;
4956
4957         case XSS_OPEN_LEAF_LIST:
4958             rc = xo_do_close_leaf_list(xop, NULL);
4959             break;
4960
4961         case XSS_MARKER:
4962             flags = xsp->xs_flags & XSF_MARKER_FLAGS;
4963             xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
4964             xop->xo_stack[xop->xo_depth].xs_flags |= flags;
4965             rc = 0;
4966             break;
4967         }
4968
4969         if (rc < 0)
4970             xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
4971     }
4972
4973     return 0;
4974 }
4975
4976 /*
4977  * This function is responsible for clearing out whatever is needed
4978  * to get to the desired state, if possible.
4979  */
4980 static int
4981 xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
4982 {
4983     xo_stack_t *xsp, *limit = NULL;
4984     int rc;
4985     xo_state_t need_state = new_state;
4986
4987     if (new_state == XSS_CLOSE_CONTAINER)
4988         need_state = XSS_OPEN_CONTAINER;
4989     else if (new_state == XSS_CLOSE_LIST)
4990         need_state = XSS_OPEN_LIST;
4991     else if (new_state == XSS_CLOSE_INSTANCE)
4992         need_state = XSS_OPEN_INSTANCE;
4993     else if (new_state == XSS_CLOSE_LEAF_LIST)
4994         need_state = XSS_OPEN_LEAF_LIST;
4995     else if (new_state == XSS_MARKER)
4996         need_state = XSS_MARKER;
4997     else
4998         return 0; /* Unknown or useless new states are ignored */
4999
5000     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
5001         /*
5002          * Marker's normally stop us from going any further, unless
5003          * we are popping a marker (new_state == XSS_MARKER).
5004          */
5005         if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
5006             if (name) {
5007                 xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
5008                            "not found '%s'",
5009                            xo_state_name(new_state),
5010                            xsp->xs_name, name);
5011                 return 0;
5012
5013             } else {
5014                 limit = xsp;
5015                 xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
5016             }
5017             break;
5018         }
5019         
5020         if (xsp->xs_state != need_state)
5021             continue;
5022
5023         if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
5024             continue;
5025
5026         limit = xsp;
5027         break;
5028     }
5029
5030     if (limit == NULL) {
5031         xo_failure(xop, "xo_%s can't find match for '%s'",
5032                    xo_state_name(new_state), name);
5033         return 0;
5034     }
5035
5036     rc = xo_do_close_all(xop, limit);
5037
5038     return rc;
5039 }
5040
5041 /*
5042  * We are in a given state and need to transition to the new state.
5043  */
5044 static int
5045 xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
5046                xo_state_t new_state)
5047 {
5048     xo_stack_t *xsp;
5049     int rc;
5050     int old_state, on_marker;
5051
5052     xop = xo_default(xop);
5053
5054     rc = 0;
5055     xsp = &xop->xo_stack[xop->xo_depth];
5056     old_state = xsp->xs_state;
5057     on_marker = (old_state == XSS_MARKER);
5058
5059     /* If there's a marker on top of the stack, we need to find a real state */
5060     while (old_state == XSS_MARKER) {
5061         if (xsp == xop->xo_stack)
5062             break;
5063         xsp -= 1;
5064         old_state = xsp->xs_state;
5065     }
5066
5067     /*
5068      * At this point, the list of possible states are:
5069      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
5070      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
5071      */
5072     switch (XSS_TRANSITION(old_state, new_state)) {
5073
5074     open_container:
5075     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
5076     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
5077     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
5078        rc = xo_do_open_container(xop, flags, name);
5079        break;
5080
5081     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
5082         if (on_marker)
5083             goto marker_prevents_close;
5084         rc = xo_do_close_list(xop, NULL);
5085         if (rc >= 0)
5086             goto open_container;
5087         break;
5088
5089     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
5090         if (on_marker)
5091             goto marker_prevents_close;
5092         rc = xo_do_close_leaf_list(xop, NULL);
5093         if (rc >= 0)
5094             goto open_container;
5095         break;
5096
5097     /*close_container:*/
5098     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
5099         if (on_marker)
5100             goto marker_prevents_close;
5101         rc = xo_do_close(xop, name, new_state);
5102         break;
5103
5104     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
5105         /* This is an exception for "xo --close" */
5106         rc = xo_do_close_container(xop, name);
5107         break;
5108
5109     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
5110     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
5111         if (on_marker)
5112             goto marker_prevents_close;
5113         rc = xo_do_close(xop, name, new_state);
5114         break;
5115
5116     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
5117         if (on_marker)
5118             goto marker_prevents_close;
5119         rc = xo_do_close_leaf_list(xop, NULL);
5120         if (rc >= 0)
5121             rc = xo_do_close(xop, name, new_state);
5122         break;
5123
5124     open_list:
5125     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
5126     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
5127     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
5128         rc = xo_do_open_list(xop, flags, name);
5129         break;
5130
5131     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
5132         if (on_marker)
5133             goto marker_prevents_close;
5134         rc = xo_do_close_list(xop, NULL);
5135         if (rc >= 0)
5136             goto open_list;
5137         break;
5138
5139     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
5140         if (on_marker)
5141             goto marker_prevents_close;
5142         rc = xo_do_close_leaf_list(xop, NULL);
5143         if (rc >= 0)
5144             goto open_list;
5145         break;
5146
5147     /*close_list:*/
5148     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
5149         if (on_marker)
5150             goto marker_prevents_close;
5151         rc = xo_do_close(xop, name, new_state);
5152         break;
5153
5154     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
5155     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
5156     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
5157     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
5158         rc = xo_do_close(xop, name, new_state);
5159         break;
5160
5161     open_instance:
5162     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
5163         rc = xo_do_open_instance(xop, flags, name);
5164         break;
5165
5166     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
5167     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
5168         rc = xo_do_open_list(xop, flags, name);
5169         if (rc >= 0)
5170             goto open_instance;
5171         break;
5172
5173     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
5174         if (on_marker) {
5175             rc = xo_do_open_list(xop, flags, name);
5176         } else {
5177             rc = xo_do_close_instance(xop, NULL);
5178         }
5179         if (rc >= 0)
5180             goto open_instance;
5181         break;
5182
5183     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
5184         if (on_marker)
5185             goto marker_prevents_close;
5186         rc = xo_do_close_leaf_list(xop, NULL);
5187         if (rc >= 0)
5188             goto open_instance;
5189         break;
5190
5191     /*close_instance:*/
5192     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
5193         if (on_marker)
5194             goto marker_prevents_close;
5195         rc = xo_do_close_instance(xop, name);
5196         break;
5197
5198     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
5199         /* This one makes no sense; ignore it */
5200         xo_failure(xop, "xo_close_instance ignored when called from "
5201                    "initial state ('%s')", name ?: "(unknown)");
5202         break;
5203
5204     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
5205     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
5206         if (on_marker)
5207             goto marker_prevents_close;
5208         rc = xo_do_close(xop, name, new_state);
5209         break;
5210
5211     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
5212         if (on_marker)
5213             goto marker_prevents_close;
5214         rc = xo_do_close_leaf_list(xop, NULL);
5215         if (rc >= 0)
5216             rc = xo_do_close(xop, name, new_state);
5217         break;
5218
5219     open_leaf_list:
5220     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
5221     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
5222     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
5223         rc = xo_do_open_leaf_list(xop, flags, name);
5224         break;
5225
5226     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
5227     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
5228         if (on_marker)
5229             goto marker_prevents_close;
5230         rc = xo_do_close_list(xop, NULL);
5231         if (rc >= 0)
5232             goto open_leaf_list;
5233         break;
5234
5235     /*close_leaf_list:*/
5236     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
5237         if (on_marker)
5238             goto marker_prevents_close;
5239         rc = xo_do_close_leaf_list(xop, name);
5240         break;
5241
5242     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
5243         /* Makes no sense; ignore */
5244         xo_failure(xop, "xo_close_leaf_list ignored when called from "
5245                    "initial state ('%s')", name ?: "(unknown)");
5246         break;
5247
5248     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
5249     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
5250     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
5251         if (on_marker)
5252             goto marker_prevents_close;
5253         rc = xo_do_close(xop, name, new_state);
5254         break;
5255
5256     /*emit:*/
5257     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
5258     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
5259         break;
5260
5261     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
5262         if (on_marker)
5263             goto marker_prevents_close;
5264         rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
5265         break;
5266
5267     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
5268         break;
5269
5270     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
5271         if (on_marker)
5272             goto marker_prevents_close;
5273         rc = xo_do_close_leaf_list(xop, NULL);
5274         break;
5275
5276     /*emit_leaf_list:*/
5277     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
5278     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
5279     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
5280         rc = xo_do_open_leaf_list(xop, flags, name);
5281         break;
5282
5283     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
5284         break;
5285
5286     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
5287         /*
5288          * We need to be backward compatible with the pre-xo_open_leaf_list
5289          * API, where both lists and leaf-lists were opened as lists.  So
5290          * if we find an open list that hasn't had anything written to it,
5291          * we'll accept it.
5292          */
5293         break;
5294
5295     default:
5296         xo_failure(xop, "unknown transition: (%u -> %u)",
5297                    xsp->xs_state, new_state);
5298     }
5299
5300     return rc;
5301
5302  marker_prevents_close:
5303     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
5304                xop->xo_stack[xop->xo_depth].xs_name,
5305                xo_state_name(old_state), xo_state_name(new_state));
5306     return -1;
5307 }
5308
5309 int
5310 xo_open_marker_h (xo_handle_t *xop, const char *name)
5311 {
5312     xop = xo_default(xop);
5313
5314     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
5315                     xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
5316
5317     return 0;
5318 }
5319
5320 int
5321 xo_open_marker (const char *name)
5322 {
5323     return xo_open_marker_h(NULL, name);
5324 }
5325
5326 int
5327 xo_close_marker_h (xo_handle_t *xop, const char *name)
5328 {
5329     xop = xo_default(xop);
5330
5331     return xo_do_close(xop, name, XSS_MARKER);
5332 }
5333
5334 int
5335 xo_close_marker (const char *name)
5336 {
5337     return xo_close_marker_h(NULL, name);
5338 }
5339
5340 void
5341 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
5342                xo_close_func_t close_func, xo_flush_func_t flush_func)
5343 {
5344     xop = xo_default(xop);
5345
5346     xop->xo_opaque = opaque;
5347     xop->xo_write = write_func;
5348     xop->xo_close = close_func;
5349     xop->xo_flush = flush_func;
5350 }
5351
5352 void
5353 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
5354 {
5355     xo_realloc = realloc_func;
5356     xo_free = free_func;
5357 }
5358
5359 int
5360 xo_flush_h (xo_handle_t *xop)
5361 {
5362     static char div_close[] = "</div>";
5363     int rc;
5364
5365     xop = xo_default(xop);
5366
5367     switch (xo_style(xop)) {
5368     case XO_STYLE_HTML:
5369         if (xop->xo_flags & XOF_DIV_OPEN) {
5370             xop->xo_flags &= ~XOF_DIV_OPEN;
5371             xo_data_append(xop, div_close, sizeof(div_close) - 1);
5372
5373             if (xop->xo_flags & XOF_PRETTY)
5374                 xo_data_append(xop, "\n", 1);
5375         }
5376         break;
5377     }
5378
5379     rc = xo_write(xop);
5380     if (rc >= 0 && xop->xo_flush)
5381         if (xop->xo_flush(xop->xo_opaque) < 0)
5382             return -1;
5383
5384     return rc;
5385 }
5386
5387 int
5388 xo_flush (void)
5389 {
5390     return xo_flush_h(NULL);
5391 }
5392
5393 int
5394 xo_finish_h (xo_handle_t *xop)
5395 {
5396     const char *cp = "";
5397     xop = xo_default(xop);
5398
5399     if (!(xop->xo_flags & XOF_NO_CLOSE))
5400         xo_do_close_all(xop, xop->xo_stack);
5401
5402     switch (xo_style(xop)) {
5403     case XO_STYLE_JSON:
5404         if (!(xop->xo_flags & XOF_NO_TOP)) {
5405             if (xop->xo_flags & XOF_TOP_EMITTED)
5406                 xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */
5407             else
5408                 cp = "{ ";
5409             xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
5410         }
5411         break;
5412     }
5413
5414     return xo_flush_h(xop);
5415 }
5416
5417 int
5418 xo_finish (void)
5419 {
5420     return xo_finish_h(NULL);
5421 }
5422
5423 /*
5424  * Generate an error message, such as would be displayed on stderr
5425  */
5426 void
5427 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
5428 {
5429     xop = xo_default(xop);
5430
5431     /*
5432      * If the format string doesn't end with a newline, we pop
5433      * one on ourselves.
5434      */
5435     int len = strlen(fmt);
5436     if (len > 0 && fmt[len - 1] != '\n') {
5437         char *newfmt = alloca(len + 2);
5438         memcpy(newfmt, fmt, len);
5439         newfmt[len] = '\n';
5440         newfmt[len] = '\0';
5441         fmt = newfmt;
5442     }
5443
5444     switch (xo_style(xop)) {
5445     case XO_STYLE_TEXT:
5446         vfprintf(stderr, fmt, vap);
5447         break;
5448
5449     case XO_STYLE_HTML:
5450         va_copy(xop->xo_vap, vap);
5451         
5452         xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
5453
5454         if (xop->xo_flags & XOF_DIV_OPEN)
5455             xo_line_close(xop);
5456
5457         xo_write(xop);
5458
5459         va_end(xop->xo_vap);
5460         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
5461         break;
5462
5463     case XO_STYLE_XML:
5464     case XO_STYLE_JSON:
5465         va_copy(xop->xo_vap, vap);
5466
5467         xo_open_container_h(xop, "error");
5468         xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
5469         xo_close_container_h(xop, "error");
5470
5471         va_end(xop->xo_vap);
5472         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
5473         break;
5474     }
5475 }
5476
5477 void
5478 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
5479 {
5480     va_list vap;
5481
5482     va_start(vap, fmt);
5483     xo_error_hv(xop, fmt, vap);
5484     va_end(vap);
5485 }
5486
5487 /*
5488  * Generate an error message, such as would be displayed on stderr
5489  */
5490 void
5491 xo_error (const char *fmt, ...)
5492 {
5493     va_list vap;
5494
5495     va_start(vap, fmt);
5496     xo_error_hv(NULL, fmt, vap);
5497     va_end(vap);
5498 }
5499
5500 int
5501 xo_parse_args (int argc, char **argv)
5502 {
5503     static char libxo_opt[] = "--libxo";
5504     char *cp;
5505     int i, save;
5506
5507     /* Save our program name for xo_err and friends */
5508     xo_program = argv[0];
5509     cp = strrchr(xo_program, '/');
5510     if (cp)
5511         xo_program = cp + 1;
5512
5513     for (save = i = 1; i < argc; i++) {
5514         if (argv[i] == NULL
5515             || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
5516             if (save != i)
5517                 argv[save] = argv[i];
5518             save += 1;
5519             continue;
5520         }
5521
5522         cp = argv[i] + sizeof(libxo_opt) - 1;
5523         if (*cp == 0) {
5524             cp = argv[++i];
5525             if (cp == 0) {
5526                 xo_warnx("missing libxo option");
5527                 return -1;
5528             }
5529                 
5530             if (xo_set_options(NULL, cp) < 0)
5531                 return -1;
5532         } else if (*cp == ':') {
5533             if (xo_set_options(NULL, cp) < 0)
5534                 return -1;
5535
5536         } else if (*cp == '=') {
5537             if (xo_set_options(NULL, ++cp) < 0)
5538                 return -1;
5539
5540         } else if (*cp == '-') {
5541             cp += 1;
5542             if (strcmp(cp, "check") == 0) {
5543                 exit(XO_HAS_LIBXO);
5544
5545             } else {
5546                 xo_warnx("unknown libxo option: '%s'", argv[i]);
5547                 return -1;
5548             }
5549         } else {
5550                 xo_warnx("unknown libxo option: '%s'", argv[i]);
5551             return -1;
5552         }
5553     }
5554
5555     argv[save] = NULL;
5556     return save;
5557 }
5558
5559 void
5560 xo_dump_stack (xo_handle_t *xop)
5561 {
5562     int i;
5563     xo_stack_t *xsp;
5564
5565     xop = xo_default(xop);
5566
5567     fprintf(stderr, "Stack dump:\n");
5568
5569     xsp = xop->xo_stack;
5570     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
5571         fprintf(stderr, "   [%d] %s '%s' [%x]\n",
5572                 i, xo_state_name(xsp->xs_state),
5573                 xsp->xs_name ?: "--", xsp->xs_flags);
5574     }
5575 }
5576
5577 void
5578 xo_set_program (const char *name)
5579 {
5580     xo_program = name;
5581 }
5582
5583 void
5584 xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
5585 {
5586     xop = xo_default(xop);
5587
5588     if (version == NULL || strchr(version, '"') != NULL)
5589         return;
5590
5591     switch (xo_style(xop)) {
5592     case XO_STYLE_XML:
5593         /* For XML, we record this as an attribute for the first tag */
5594         xo_attr_h(xop, "__version", "%s", version);
5595         break;
5596
5597     case XO_STYLE_JSON:
5598         {
5599             /*
5600              * For XML, we record the version string in our handle, and emit
5601              * it in xo_emit_top.
5602              */
5603             int len = strlen(version) + 1;
5604             xop->xo_version = xo_realloc(NULL, len);
5605             if (xop->xo_version)
5606                 memcpy(xop->xo_version, version, len);
5607         }
5608         break;
5609     }
5610 }
5611
5612 void
5613 xo_set_version (const char *version)
5614 {
5615     xo_set_version_h(NULL, version);
5616 }
5617
5618 #ifdef UNIT_TEST
5619 int
5620 main (int argc, char **argv)
5621 {
5622     static char base_grocery[] = "GRO";
5623     static char base_hardware[] = "HRD";
5624     struct item {
5625         const char *i_title;
5626         int i_sold;
5627         int i_instock;
5628         int i_onorder;
5629         const char *i_sku_base;
5630         int i_sku_num;
5631     };
5632     struct item list[] = {
5633         { "gum&this&that", 1412, 54, 10, base_grocery, 415 },
5634         { "<rope>", 85, 4, 2, base_hardware, 212 },
5635         { "ladder", 0, 2, 1, base_hardware, 517 },
5636         { "\"bolt\"", 4123, 144, 42, base_hardware, 632 },
5637         { "water\\blue", 17, 14, 2, base_grocery, 2331 },
5638         { NULL, 0, 0, 0, NULL, 0 }
5639     };
5640     struct item list2[] = {
5641         { "fish", 1321, 45, 1, base_grocery, 533 },
5642         { NULL, 0, 0, 0, NULL, 0 }
5643     };
5644     struct item *ip;
5645     xo_info_t info[] = {
5646         { "in-stock", "number", "Number of items in stock" },
5647         { "name", "string", "Name of the item" },
5648         { "on-order", "number", "Number of items on order" },
5649         { "sku", "string", "Stock Keeping Unit" },
5650         { "sold", "number", "Number of items sold" },
5651         { NULL, NULL, NULL },
5652     };
5653     int info_count = (sizeof(info) / sizeof(info[0])) - 1;
5654     
5655     argc = xo_parse_args(argc, argv);
5656     if (argc < 0)
5657         exit(1);
5658
5659     xo_set_info(NULL, info, info_count);
5660
5661     xo_open_container_h(NULL, "top");
5662
5663     xo_open_container("data");
5664     xo_open_list("item");
5665
5666     xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}"
5667             "{T:On Order/%12s}{T:SKU/%5s}\n");
5668
5669     for (ip = list; ip->i_title; ip++) {
5670         xo_open_instance("item");
5671
5672         xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}"
5673                 "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n",
5674                 ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder,
5675                 ip->i_sku_base, ip->i_sku_num);
5676
5677         xo_close_instance("item");
5678     }
5679
5680     xo_close_list("item");
5681     xo_close_container("data");
5682
5683     xo_emit("\n\n");
5684
5685     xo_open_container("data");
5686     xo_open_list("item");
5687
5688     for (ip = list; ip->i_title; ip++) {
5689         xo_open_instance("item");
5690
5691         xo_attr("fancy", "%s%d", "item", ip - list);
5692         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
5693         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n",
5694                 ip->i_sold, ip->i_sold ? ".0" : "", 44);
5695         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
5696         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
5697         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
5698                 ip->i_sku_base, ip->i_sku_num);
5699
5700         xo_close_instance("item");
5701     }
5702
5703     xo_close_list("item");
5704     xo_close_container("data");
5705
5706     xo_open_container("data");
5707     xo_open_list("item");
5708
5709     for (ip = list2; ip->i_title; ip++) {
5710         xo_open_instance("item");
5711
5712         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
5713         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}\n",
5714                 ip->i_sold, ip->i_sold ? ".0" : "");
5715         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
5716         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
5717         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
5718                 ip->i_sku_base, ip->i_sku_num);
5719
5720         xo_open_list("month");
5721
5722         const char *months[] = { "Jan", "Feb", "Mar", NULL };
5723         int discounts[] = { 10, 20, 25, 0 };
5724         int i;
5725         for (i = 0; months[i]; i++) {
5726             xo_open_instance("month");
5727             xo_emit("{P:       }"
5728                     "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n",
5729                     months[i], discounts[i]);
5730             xo_close_instance("month");
5731         }
5732         
5733         xo_close_list("month");
5734
5735         xo_close_instance("item");
5736     }
5737
5738     xo_close_list("item");
5739     xo_close_container("data");
5740
5741     xo_close_container_h(NULL, "top");
5742
5743     xo_finish();
5744
5745     return 0;
5746 }
5747 #endif /* UNIT_TEST */