]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libxo/libxo/libxo.c
Merge from head
[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                 wc = L'?';
2081                 ilen = 1;
2082             }
2083             if (ilen == 0) {            /* Hit a wide NUL character */
2084                 len = 0;
2085                 continue;
2086             }
2087
2088             cp += ilen;
2089             break;
2090         }
2091
2092         /* Reduce len, but not below zero */
2093         if (len > 0) {
2094             len -= ilen;
2095             if (len < 0)
2096                 len = 0;
2097         }
2098
2099         /*
2100          * Find the width-in-columns of this character, which must be done
2101          * in wide characters, since we lack a mbswidth() function.  If
2102          * it doesn't fit
2103          */
2104         width = wcwidth(wc);
2105         if (width < 0)
2106             width = iswcntrl(wc) ? 0 : 1;
2107
2108         if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
2109             if (max > 0 && cols + width > max)
2110                 break;
2111         }
2112
2113         switch (need_enc) {
2114         case XF_ENC_UTF8:
2115
2116             /* Output in UTF-8 needs to be escaped, based on the style */
2117             switch (xo_style(xop)) {
2118             case XO_STYLE_XML:
2119             case XO_STYLE_HTML:
2120                 if (wc == '<')
2121                     sp = xo_xml_lt;
2122                 else if (wc == '>')
2123                     sp = xo_xml_gt;
2124                 else if (wc == '&')
2125                     sp = xo_xml_amp;
2126                 else if (attr && wc == '"')
2127                     sp = xo_xml_quot;
2128                 else
2129                     break;
2130
2131                 int slen = strlen(sp);
2132                 if (!xo_buf_has_room(xbp, slen - 1))
2133                     return -1;
2134
2135                 memcpy(xbp->xb_curp, sp, slen);
2136                 xbp->xb_curp += slen;
2137                 goto done_with_encoding; /* Need multi-level 'break' */
2138
2139             case XO_STYLE_JSON:
2140                 if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
2141                     break;
2142
2143                 if (!xo_buf_has_room(xbp, 2))
2144                     return -1;
2145
2146                 *xbp->xb_curp++ = '\\';
2147                 if (wc == '\n')
2148                     wc = 'n';
2149                 else if (wc == '\r')
2150                     wc = 'r';
2151                 else wc = wc & 0x7f;
2152
2153                 *xbp->xb_curp++ = wc;
2154                 goto done_with_encoding;
2155             }
2156
2157             olen = xo_utf8_emit_len(wc);
2158             if (olen < 0) {
2159                 xo_failure(xop, "ignoring bad length");
2160                 continue;
2161             }
2162
2163             if (!xo_buf_has_room(xbp, olen))
2164                 return -1;
2165
2166             xo_utf8_emit_char(xbp->xb_curp, olen, wc);
2167             xbp->xb_curp += olen;
2168             break;
2169
2170         case XF_ENC_LOCALE:
2171             if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
2172                 return -1;
2173
2174             olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
2175             if (olen <= 0) {
2176                 xo_failure(xop, "could not convert wide char: %lx",
2177                            (unsigned long) wc);
2178                 olen = 1;
2179                 width = 1;
2180                 *xbp->xb_curp++ = '?';
2181             } else
2182                 xbp->xb_curp += olen;
2183             break;
2184         }
2185
2186     done_with_encoding:
2187         cols += width;
2188     }
2189
2190     return cols;
2191 }
2192
2193 static int
2194 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
2195                   xo_format_t *xfp)
2196 {
2197     static char null[] = "(null)";
2198
2199     char *cp = NULL;
2200     wchar_t *wcp = NULL;
2201     int len, cols = 0, rc = 0;
2202     int off = xbp->xb_curp - xbp->xb_bufp, off2;
2203     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2204         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2205
2206     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
2207         return 0;
2208
2209     len = xfp->xf_width[XF_WIDTH_SIZE];
2210
2211     if (xfp->xf_enc == XF_ENC_WIDE) {
2212         wcp = va_arg(xop->xo_vap, wchar_t *);
2213         if (xfp->xf_skip)
2214             return 0;
2215
2216         /*
2217          * Dont' deref NULL; use the traditional "(null)" instead
2218          * of the more accurate "who's been a naughty boy, then?".
2219          */
2220         if (wcp == NULL) {
2221             cp = null;
2222             len = sizeof(null) - 1;
2223         }
2224
2225     } else {
2226         cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2227         if (xfp->xf_skip)
2228             return 0;
2229
2230         /* Echo "Dont' deref NULL" logic */
2231         if (cp == NULL) {
2232             cp = null;
2233             len = sizeof(null) - 1;
2234         }
2235
2236         /*
2237          * Optimize the most common case, which is "%s".  We just
2238          * need to copy the complete string to the output buffer.
2239          */
2240         if (xfp->xf_enc == need_enc
2241                 && xfp->xf_width[XF_WIDTH_MIN] < 0
2242                 && xfp->xf_width[XF_WIDTH_SIZE] < 0
2243                 && xfp->xf_width[XF_WIDTH_MAX] < 0
2244                 && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) {
2245             len = strlen(cp);
2246             xo_buf_escape(xop, xbp, cp, len, flags);
2247
2248             /*
2249              * Our caller expects xb_curp left untouched, so we have
2250              * to reset it and return the number of bytes written to
2251              * the buffer.
2252              */
2253             off2 = xbp->xb_curp - xbp->xb_bufp;
2254             rc = off2 - off;
2255             xbp->xb_curp = xbp->xb_bufp + off;
2256
2257             return rc;
2258         }
2259     }
2260
2261     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
2262                                    xfp->xf_width[XF_WIDTH_MAX],
2263                                    need_enc, xfp->xf_enc);
2264     if (cols < 0)
2265         goto bail;
2266
2267     /*
2268      * xo_buf_append* will move xb_curp, so we save/restore it.
2269      */
2270     off2 = xbp->xb_curp - xbp->xb_bufp;
2271     rc = off2 - off;
2272     xbp->xb_curp = xbp->xb_bufp + off;
2273
2274     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
2275         /*
2276          * Find the number of columns needed to display the string.
2277          * If we have the original wide string, we just call wcswidth,
2278          * but if we did the work ourselves, then we need to do it.
2279          */
2280         int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2281         if (!xo_buf_has_room(xbp, delta))
2282             goto bail;
2283
2284         /*
2285          * If seen_minus, then pad on the right; otherwise move it so
2286          * we can pad on the left.
2287          */
2288         if (xfp->xf_seen_minus) {
2289             cp = xbp->xb_curp + rc;
2290         } else {
2291             cp = xbp->xb_curp;
2292             memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
2293         }
2294
2295         /* Set the padding */
2296         memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
2297         rc += delta;
2298         cols += delta;
2299     }
2300
2301     if (xop->xo_flags & XOF_COLUMNS)
2302         xop->xo_columns += cols;
2303     if (xop->xo_flags & XOF_ANCHOR)
2304         xop->xo_anchor_columns += cols;
2305
2306     return rc;
2307
2308  bail:
2309     xbp->xb_curp = xbp->xb_bufp + off;
2310     return 0;
2311 }
2312
2313 static void
2314 xo_data_append_content (xo_handle_t *xop, const char *str, int len)
2315 {
2316     int cols;
2317     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2318         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2319
2320     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE,
2321                                    NULL, str, len, -1,
2322                                    need_enc, XF_ENC_UTF8);
2323
2324     if (xop->xo_flags & XOF_COLUMNS)
2325         xop->xo_columns += cols;
2326     if (xop->xo_flags & XOF_ANCHOR)
2327         xop->xo_anchor_columns += cols;
2328 }
2329
2330 static void
2331 xo_bump_width (xo_format_t *xfp, int digit)
2332 {
2333     int *ip = &xfp->xf_width[xfp->xf_dots];
2334
2335     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
2336 }
2337
2338 static int
2339 xo_trim_ws (xo_buffer_t *xbp, int len)
2340 {
2341     char *cp, *sp, *ep;
2342     int delta;
2343
2344     /* First trim leading space */
2345     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
2346         if (*cp != ' ')
2347             break;
2348     }
2349
2350     delta = cp - sp;
2351     if (delta) {
2352         len -= delta;
2353         memmove(sp, cp, len);
2354     }
2355
2356     /* Then trim off the end */
2357     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
2358         if (ep[-1] != ' ')
2359             break;
2360     }
2361
2362     delta = sp - ep;
2363     if (delta) {
2364         len -= delta;
2365         cp[len] = '\0';
2366     }
2367
2368     return len;
2369 }
2370
2371 static int
2372 xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
2373                 const char *fmt, int flen, xo_xff_flags_t flags)
2374 {
2375     xo_format_t xf;
2376     const char *cp, *ep, *sp, *xp = NULL;
2377     int rc, cols;
2378     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
2379     unsigned make_output = !(flags & XFF_NO_OUTPUT);
2380     int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
2381         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2382     
2383     if (xbp == NULL)
2384         xbp = &xop->xo_data;
2385
2386     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
2387         if (*cp != '%') {
2388         add_one:
2389             if (xp == NULL)
2390                 xp = cp;
2391
2392             if (*cp == '\\' && cp[1] != '\0')
2393                 cp += 1;
2394             continue;
2395
2396         } if (cp + 1 < ep && cp[1] == '%') {
2397             cp += 1;
2398             goto add_one;
2399         }
2400
2401         if (xp) {
2402             if (make_output) {
2403                 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2404                                                NULL, xp, cp - xp, -1,
2405                                                need_enc, XF_ENC_UTF8);
2406                 if (xop->xo_flags & XOF_COLUMNS)
2407                     xop->xo_columns += cols;
2408                 if (xop->xo_flags & XOF_ANCHOR)
2409                     xop->xo_anchor_columns += cols;
2410             }
2411
2412             xp = NULL;
2413         }
2414
2415         bzero(&xf, sizeof(xf));
2416         xf.xf_leading_zero = -1;
2417         xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
2418
2419         /*
2420          * "%@" starts an XO-specific set of flags:
2421          *   @X@ - XML-only field; ignored if style isn't XML
2422          */
2423         if (cp[1] == '@') {
2424             for (cp += 2; cp < ep; cp++) {
2425                 if (*cp == '@') {
2426                     break;
2427                 }
2428                 if (*cp == '*') {
2429                     /*
2430                      * '*' means there's a "%*.*s" value in vap that
2431                      * we want to ignore
2432                      */
2433                     if (!(xop->xo_flags & XOF_NO_VA_ARG))
2434                         va_arg(xop->xo_vap, int);
2435                 }
2436             }
2437         }
2438
2439         /* Hidden fields are only visible to JSON and XML */
2440         if (xop->xo_flags & XFF_ENCODE_ONLY) {
2441             if (style != XO_STYLE_XML
2442                     && xo_style(xop) != XO_STYLE_JSON)
2443                 xf.xf_skip = 1;
2444         } else if (xop->xo_flags & XFF_DISPLAY_ONLY) {
2445             if (style != XO_STYLE_TEXT
2446                     && xo_style(xop) != XO_STYLE_HTML)
2447                 xf.xf_skip = 1;
2448         }
2449
2450         if (!make_output)
2451             xf.xf_skip = 1;
2452
2453         /*
2454          * Looking at one piece of a format; find the end and
2455          * call snprintf.  Then advance xo_vap on our own.
2456          *
2457          * Note that 'n', 'v', and '$' are not supported.
2458          */
2459         sp = cp;                /* Save start pointer */
2460         for (cp += 1; cp < ep; cp++) {
2461             if (*cp == 'l')
2462                 xf.xf_lflag += 1;
2463             else if (*cp == 'h')
2464                 xf.xf_hflag += 1;
2465             else if (*cp == 'j')
2466                 xf.xf_jflag += 1;
2467             else if (*cp == 't')
2468                 xf.xf_tflag += 1;
2469             else if (*cp == 'z')
2470                 xf.xf_zflag += 1;
2471             else if (*cp == 'q')
2472                 xf.xf_qflag += 1;
2473             else if (*cp == '.') {
2474                 if (++xf.xf_dots >= XF_WIDTH_NUM) {
2475                     xo_failure(xop, "Too many dots in format: '%s'", fmt);
2476                     return -1;
2477                 }
2478             } else if (*cp == '-')
2479                 xf.xf_seen_minus = 1;
2480             else if (isdigit((int) *cp)) {
2481                 if (xf.xf_leading_zero < 0)
2482                     xf.xf_leading_zero = (*cp == '0');
2483                 xo_bump_width(&xf, *cp - '0');
2484             } else if (*cp == '*') {
2485                 xf.xf_stars += 1;
2486                 xf.xf_star[xf.xf_dots] = 1;
2487             } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
2488                 break;
2489             else if (*cp == 'n' || *cp == 'v') {
2490                 xo_failure(xop, "unsupported format: '%s'", fmt);
2491                 return -1;
2492             }
2493         }
2494
2495         if (cp == ep)
2496             xo_failure(xop, "field format missing format character: %s",
2497                           fmt);
2498
2499         xf.xf_fc = *cp;
2500
2501         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2502             if (*cp == 's' || *cp == 'S') {
2503                 /* Handle "%*.*.*s" */
2504                 int s;
2505                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2506                     if (xf.xf_star[s]) {
2507                         xf.xf_width[s] = va_arg(xop->xo_vap, int);
2508                         
2509                         /* Normalize a negative width value */
2510                         if (xf.xf_width[s] < 0) {
2511                             if (s == 0) {
2512                                 xf.xf_width[0] = -xf.xf_width[0];
2513                                 xf.xf_seen_minus = 1;
2514                             } else
2515                                 xf.xf_width[s] = -1; /* Ignore negative values */
2516                         }
2517                     }
2518                 }
2519             }
2520         }
2521
2522         /* If no max is given, it defaults to size */
2523         if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
2524             xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
2525
2526         if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
2527             xf.xf_lflag = 1;
2528
2529         if (!xf.xf_skip) {
2530             xo_buffer_t *fbp = &xop->xo_fmt;
2531             int len = cp - sp + 1;
2532             if (!xo_buf_has_room(fbp, len + 1))
2533                 return -1;
2534
2535             char *newfmt = fbp->xb_curp;
2536             memcpy(newfmt, sp, len);
2537             newfmt[0] = '%';    /* If we skipped over a "%@...@s" format */
2538             newfmt[len] = '\0';
2539
2540             /*
2541              * Bad news: our strings are UTF-8, but the stock printf
2542              * functions won't handle field widths for wide characters
2543              * correctly.  So we have to handle this ourselves.
2544              */
2545             if (xop->xo_formatter == NULL
2546                     && (xf.xf_fc == 's' || xf.xf_fc == 'S')) {
2547                 xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S'))
2548                     ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
2549                 rc = xo_format_string(xop, xbp, flags, &xf);
2550
2551                 if ((flags & XFF_TRIM_WS)
2552                         && (xo_style(xop) == XO_STYLE_XML
2553                                 || xo_style(xop) == XO_STYLE_JSON))
2554                     rc = xo_trim_ws(xbp, rc);
2555
2556             } else {
2557                 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
2558
2559                 /*
2560                  * For XML and HTML, we need "&<>" processing; for JSON,
2561                  * it's quotes.  Text gets nothing.
2562                  */
2563                 switch (style) {
2564                 case XO_STYLE_XML:
2565                     if (flags & XFF_TRIM_WS)
2566                         columns = rc = xo_trim_ws(xbp, rc);
2567                     /* fall thru */
2568                 case XO_STYLE_HTML:
2569                     rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
2570                     break;
2571
2572                 case XO_STYLE_JSON:
2573                     if (flags & XFF_TRIM_WS)
2574                         columns = rc = xo_trim_ws(xbp, rc);
2575                     rc = xo_escape_json(xbp, rc);
2576                     break;
2577                 }
2578
2579                 /*
2580                  * We can assume all the data we've added is ASCII, so
2581                  * the columns and bytes are the same.  xo_format_string
2582                  * handles all the fancy string conversions and updates
2583                  * xo_anchor_columns accordingly.
2584                  */
2585                 if (xop->xo_flags & XOF_COLUMNS)
2586                     xop->xo_columns += columns;
2587                 if (xop->xo_flags & XOF_ANCHOR)
2588                     xop->xo_anchor_columns += columns;
2589             }
2590
2591             xbp->xb_curp += rc;
2592         }
2593
2594         /*
2595          * Now for the tricky part: we need to move the argument pointer
2596          * along by the amount needed.
2597          */
2598         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2599
2600             if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
2601                 /*
2602                  * The 'S' and 's' formats are normally handled in
2603                  * xo_format_string, but if we skipped it, then we
2604                  * need to pop it.
2605                  */
2606                 if (xf.xf_skip)
2607                     va_arg(xop->xo_vap, char *);
2608
2609             } else {
2610                 int s;
2611                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2612                     if (xf.xf_star[s])
2613                         va_arg(xop->xo_vap, int);
2614                 }
2615
2616                 if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
2617                     if (xf.xf_hflag > 1) {
2618                         va_arg(xop->xo_vap, int);
2619
2620                     } else if (xf.xf_hflag > 0) {
2621                         va_arg(xop->xo_vap, int);
2622
2623                     } else if (xf.xf_lflag > 1) {
2624                         va_arg(xop->xo_vap, unsigned long long);
2625
2626                     } else if (xf.xf_lflag > 0) {
2627                         va_arg(xop->xo_vap, unsigned long);
2628
2629                     } else if (xf.xf_jflag > 0) {
2630                         va_arg(xop->xo_vap, intmax_t);
2631
2632                     } else if (xf.xf_tflag > 0) {
2633                         va_arg(xop->xo_vap, ptrdiff_t);
2634
2635                     } else if (xf.xf_zflag > 0) {
2636                         va_arg(xop->xo_vap, size_t);
2637
2638                     } else if (xf.xf_qflag > 0) {
2639                         va_arg(xop->xo_vap, quad_t);
2640
2641                     } else {
2642                         va_arg(xop->xo_vap, int);
2643                     }
2644                 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
2645                     if (xf.xf_lflag)
2646                         va_arg(xop->xo_vap, long double);
2647                     else
2648                         va_arg(xop->xo_vap, double);
2649
2650                 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
2651                     va_arg(xop->xo_vap, wint_t);
2652
2653                 else if (xf.xf_fc == 'c')
2654                     va_arg(xop->xo_vap, int);
2655
2656                 else if (xf.xf_fc == 'p')
2657                     va_arg(xop->xo_vap, void *);
2658             }
2659         }
2660     }
2661
2662     if (xp) {
2663         if (make_output) {
2664             cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2665                                            NULL, xp, cp - xp, -1,
2666                                            need_enc, XF_ENC_UTF8);
2667             if (xop->xo_flags & XOF_COLUMNS)
2668                 xop->xo_columns += cols;
2669             if (xop->xo_flags & XOF_ANCHOR)
2670                 xop->xo_anchor_columns += cols;
2671         }
2672
2673         xp = NULL;
2674     }
2675
2676     return 0;
2677 }
2678
2679 static char *
2680 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
2681 {
2682     char *cp = encoding;
2683
2684     if (cp[0] != '%' || !isdigit((int) cp[1]))
2685         return encoding;
2686
2687     for (cp += 2; *cp; cp++) {
2688         if (!isdigit((int) *cp))
2689             break;
2690     }
2691
2692     cp -= 1;
2693     *cp = '%';
2694
2695     return cp;
2696 }
2697
2698 static void
2699 xo_color_append_html (xo_handle_t *xop)
2700 {
2701     /*
2702      * If the color buffer has content, we add it now.  It's already
2703      * prebuilt and ready, since we want to add it to every <div>.
2704      */
2705     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
2706         xo_buffer_t *xbp = &xop->xo_color_buf;
2707
2708         xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
2709     }
2710 }
2711
2712 static void
2713 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
2714                    const char *name, int nlen,
2715                    const char *value, int vlen,
2716                    const char *encoding, int elen)
2717 {
2718     static char div_start[] = "<div class=\"";
2719     static char div_tag[] = "\" data-tag=\"";
2720     static char div_xpath[] = "\" data-xpath=\"";
2721     static char div_key[] = "\" data-key=\"key";
2722     static char div_end[] = "\">";
2723     static char div_close[] = "</div>";
2724
2725     /*
2726      * To build our XPath predicate, we need to save the va_list before
2727      * we format our data, and then restore it before we format the
2728      * xpath expression.
2729      * Display-only keys implies that we've got an encode-only key
2730      * elsewhere, so we don't use them from making predicates.
2731      */
2732     int need_predidate = 
2733         (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
2734          && (xop->xo_flags & XOF_XPATH));
2735
2736     if (need_predidate) {
2737         va_list va_local;
2738
2739         va_copy(va_local, xop->xo_vap);
2740         if (xop->xo_checkpointer)
2741             xop->xo_checkpointer(xop, xop->xo_vap, 0);
2742
2743         /*
2744          * Build an XPath predicate expression to match this key.
2745          * We use the format buffer.
2746          */
2747         xo_buffer_t *pbp = &xop->xo_predicate;
2748         pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
2749
2750         xo_buf_append(pbp, "[", 1);
2751         xo_buf_escape(xop, pbp, name, nlen, 0);
2752         if (xop->xo_flags & XOF_PRETTY)
2753             xo_buf_append(pbp, " = '", 4);
2754         else
2755             xo_buf_append(pbp, "='", 2);
2756
2757         /* The encoding format defaults to the normal format */
2758         if (encoding == NULL) {
2759             char *enc  = alloca(vlen + 1);
2760             memcpy(enc, value, vlen);
2761             enc[vlen] = '\0';
2762             encoding = xo_fix_encoding(xop, enc);
2763             elen = strlen(encoding);
2764         }
2765
2766         xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR);
2767
2768         xo_buf_append(pbp, "']", 2);
2769
2770         /* Now we record this predicate expression in the stack */
2771         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
2772         int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
2773         int dlen = pbp->xb_curp - pbp->xb_bufp;
2774
2775         char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
2776         if (cp) {
2777             memcpy(cp + olen, pbp->xb_bufp, dlen);
2778             cp[olen + dlen] = '\0';
2779             xsp->xs_keys = cp;
2780         }
2781
2782         /* Now we reset the xo_vap as if we were never here */
2783         va_end(xop->xo_vap);
2784         va_copy(xop->xo_vap, va_local);
2785         va_end(va_local);
2786         if (xop->xo_checkpointer)
2787             xop->xo_checkpointer(xop, xop->xo_vap, 1);
2788     }
2789
2790     if (flags & XFF_ENCODE_ONLY) {
2791         /*
2792          * Even if this is encode-only, we need to go thru the
2793          * work of formatting it to make sure the args are cleared
2794          * from xo_vap.
2795          */
2796         xo_format_data(xop, &xop->xo_data, encoding, elen,
2797                        flags | XFF_NO_OUTPUT);
2798         return;
2799     }
2800
2801     xo_line_ensure_open(xop, 0);
2802
2803     if (xop->xo_flags & XOF_PRETTY)
2804         xo_buf_indent(xop, xop->xo_indent_by);
2805
2806     xo_data_append(xop, div_start, sizeof(div_start) - 1);
2807     xo_data_append(xop, class, strlen(class));
2808
2809     /*
2810      * If the color buffer has content, we add it now.  It's already
2811      * prebuilt and ready, since we want to add it to every <div>.
2812      */
2813     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
2814         xo_buffer_t *xbp = &xop->xo_color_buf;
2815
2816         xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
2817     }
2818
2819     if (name) {
2820         xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
2821         xo_data_escape(xop, name, nlen);
2822
2823         /*
2824          * Save the offset at which we'd place units.  See xo_format_units.
2825          */
2826         if (xop->xo_flags & XOF_UNITS) {
2827             xop->xo_flags |= XOF_UNITS_PENDING;
2828             /*
2829              * Note: We need the '+1' here because we know we've not
2830              * added the closing quote.  We add one, knowing the quote
2831              * will be added shortly.
2832              */
2833             xop->xo_units_offset =
2834                 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
2835         }
2836     }
2837
2838     if (name) {
2839         if (xop->xo_flags & XOF_XPATH) {
2840             int i;
2841             xo_stack_t *xsp;
2842
2843             xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
2844             if (xop->xo_leading_xpath)
2845                 xo_data_append(xop, xop->xo_leading_xpath,
2846                                strlen(xop->xo_leading_xpath));
2847
2848             for (i = 0; i <= xop->xo_depth; i++) {
2849                 xsp = &xop->xo_stack[i];
2850                 if (xsp->xs_name == NULL)
2851                     continue;
2852
2853                 /*
2854                  * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
2855                  * are directly under XSS_OPEN_INSTANCE frames so we
2856                  * don't need to put these in our XPath expressions.
2857                  */
2858                 if (xsp->xs_state == XSS_OPEN_LIST
2859                         || xsp->xs_state == XSS_OPEN_LEAF_LIST)
2860                     continue;
2861
2862                 xo_data_append(xop, "/", 1);
2863                 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
2864                 if (xsp->xs_keys) {
2865                     /* Don't show keys for the key field */
2866                     if (i != xop->xo_depth || !(flags & XFF_KEY))
2867                         xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
2868                 }
2869             }
2870
2871             xo_data_append(xop, "/", 1);
2872             xo_data_escape(xop, name, nlen);
2873         }
2874
2875         if ((xop->xo_flags & XOF_INFO) && xop->xo_info) {
2876             static char in_type[] = "\" data-type=\"";
2877             static char in_help[] = "\" data-help=\"";
2878
2879             xo_info_t *xip = xo_info_find(xop, name, nlen);
2880             if (xip) {
2881                 if (xip->xi_type) {
2882                     xo_data_append(xop, in_type, sizeof(in_type) - 1);
2883                     xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
2884                 }
2885                 if (xip->xi_help) {
2886                     xo_data_append(xop, in_help, sizeof(in_help) - 1);
2887                     xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
2888                 }
2889             }
2890         }
2891
2892         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS))
2893             xo_data_append(xop, div_key, sizeof(div_key) - 1);
2894     }
2895
2896     xo_data_append(xop, div_end, sizeof(div_end) - 1);
2897
2898     xo_format_data(xop, NULL, value, vlen, 0);
2899
2900     xo_data_append(xop, div_close, sizeof(div_close) - 1);
2901
2902     if (xop->xo_flags & XOF_PRETTY)
2903         xo_data_append(xop, "\n", 1);
2904 }
2905
2906 static void
2907 xo_format_text (xo_handle_t *xop, const char *str, int len)
2908 {
2909     switch (xo_style(xop)) {
2910     case XO_STYLE_TEXT:
2911         xo_buf_append_locale(xop, &xop->xo_data, str, len);
2912         break;
2913
2914     case XO_STYLE_HTML:
2915         xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
2916         break;
2917     }
2918 }
2919
2920 static void
2921 xo_format_title (xo_handle_t *xop, const char *str, int len,
2922                  const char *fmt, int flen)
2923 {
2924     static char div_open[] = "<div class=\"title";
2925     static char div_middle[] = "\">";
2926     static char div_close[] = "</div>";
2927
2928     if (flen == 0) {
2929         fmt = "%s";
2930         flen = 2;
2931     }
2932
2933     switch (xo_style(xop)) {
2934     case XO_STYLE_XML:
2935     case XO_STYLE_JSON:
2936         /*
2937          * Even though we don't care about text, we need to do
2938          * enough parsing work to skip over the right bits of xo_vap.
2939          */
2940         if (len == 0)
2941             xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2942         return;
2943     }
2944
2945     xo_buffer_t *xbp = &xop->xo_data;
2946     int start = xbp->xb_curp - xbp->xb_bufp;
2947     int left = xbp->xb_size - start;
2948     int rc;
2949     int need_enc = XF_ENC_LOCALE;
2950
2951     if (xo_style(xop) == XO_STYLE_HTML) {
2952         need_enc = XF_ENC_UTF8;
2953         xo_line_ensure_open(xop, 0);
2954         if (xop->xo_flags & XOF_PRETTY)
2955             xo_buf_indent(xop, xop->xo_indent_by);
2956         xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
2957         xo_color_append_html(xop);
2958         xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
2959     }
2960
2961     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
2962     if (len) {
2963         char *newfmt = alloca(flen + 1);
2964         memcpy(newfmt, fmt, flen);
2965         newfmt[flen] = '\0';
2966
2967         /* If len is non-zero, the format string apply to the name */
2968         char *newstr = alloca(len + 1);
2969         memcpy(newstr, str, len);
2970         newstr[len] = '\0';
2971
2972         if (newstr[len - 1] == 's') {
2973             int cols;
2974             char *bp;
2975
2976             rc = snprintf(NULL, 0, newfmt, newstr);
2977             if (rc > 0) {
2978                 /*
2979                  * We have to do this the hard way, since we might need
2980                  * the columns.
2981                  */
2982                 bp = alloca(rc + 1);
2983                 rc = snprintf(bp, rc + 1, newfmt, newstr);
2984                 cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1,
2985                                                need_enc, XF_ENC_UTF8);
2986                 if (cols > 0) {
2987                     if (xop->xo_flags & XOF_COLUMNS)
2988                         xop->xo_columns += cols;
2989                     if (xop->xo_flags & XOF_ANCHOR)
2990                         xop->xo_anchor_columns += cols;
2991                 }
2992             }
2993             goto move_along;
2994
2995         } else {
2996             rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2997             if (rc > left) {
2998                 if (!xo_buf_has_room(xbp, rc))
2999                     return;
3000                 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
3001                 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3002             }
3003
3004             if (rc > 0) {
3005                 if (xop->xo_flags & XOF_COLUMNS)
3006                     xop->xo_columns += rc;
3007                 if (xop->xo_flags & XOF_ANCHOR)
3008                     xop->xo_anchor_columns += rc;
3009             }
3010         }
3011
3012     } else {
3013         xo_format_data(xop, NULL, fmt, flen, 0);
3014
3015         /* xo_format_data moved curp, so we need to reset it */
3016         rc = xbp->xb_curp - (xbp->xb_bufp + start);
3017         xbp->xb_curp = xbp->xb_bufp + start;
3018     }
3019
3020     /* If we're styling HTML, then we need to escape it */
3021     if (xo_style(xop) == XO_STYLE_HTML) {
3022         rc = xo_escape_xml(xbp, rc, 0);
3023     }
3024
3025     if (rc > 0)
3026         xbp->xb_curp += rc;
3027
3028  move_along:
3029     if (xo_style(xop) == XO_STYLE_HTML) {
3030         xo_data_append(xop, div_close, sizeof(div_close) - 1);
3031         if (xop->xo_flags & XOF_PRETTY)
3032             xo_data_append(xop, "\n", 1);
3033     }
3034 }
3035
3036 static void
3037 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
3038 {
3039     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
3040         xo_data_append(xop, ",", 1);
3041         if (!(flags & XFF_LEAF_LIST) && (xop->xo_flags & XOF_PRETTY))
3042             xo_data_append(xop, "\n", 1);
3043     } else
3044         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3045 }
3046
3047 #if 0
3048 /* Useful debugging function */
3049 void
3050 xo_arg (xo_handle_t *xop);
3051 void
3052 xo_arg (xo_handle_t *xop)
3053 {
3054     xop = xo_default(xop);
3055     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
3056 }
3057 #endif /* 0 */
3058
3059 static void
3060 xo_format_value (xo_handle_t *xop, const char *name, int nlen,
3061                  const char *format, int flen,
3062                  const char *encoding, int elen, xo_xff_flags_t flags)
3063 {
3064     int pretty = (xop->xo_flags & XOF_PRETTY);
3065     int quote;
3066     xo_buffer_t *xbp;
3067
3068     /*
3069      * Before we emit a value, we need to know that the frame is ready.
3070      */
3071     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3072
3073     if (flags & XFF_LEAF_LIST) {
3074         /*
3075          * Check if we've already started to emit normal leafs
3076          * or if we're not in a leaf list.
3077          */
3078         if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
3079             || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
3080             char nbuf[nlen + 1];
3081             memcpy(nbuf, name, nlen);
3082             nbuf[nlen] = '\0';
3083
3084             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
3085             if (rc < 0)
3086                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3087             else
3088                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
3089         }
3090
3091         xsp = &xop->xo_stack[xop->xo_depth];
3092         if (xsp->xs_name) {
3093             name = xsp->xs_name;
3094             nlen = strlen(name);
3095         }
3096
3097     } else if (flags & XFF_KEY) {
3098         /* Emitting a 'k' (key) field */
3099         if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
3100             xo_failure(xop, "key field emitted after normal value field: '%.*s'",
3101                        nlen, name);
3102
3103         } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
3104             char nbuf[nlen + 1];
3105             memcpy(nbuf, name, nlen);
3106             nbuf[nlen] = '\0';
3107
3108             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3109             if (rc < 0)
3110                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3111             else
3112                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
3113
3114             xsp = &xop->xo_stack[xop->xo_depth];
3115             xsp->xs_flags |= XSF_EMIT_KEY;
3116         }
3117
3118     } else {
3119         /* Emitting a normal value field */
3120         if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
3121             || !(xsp->xs_flags & XSF_EMIT)) {
3122             char nbuf[nlen + 1];
3123             memcpy(nbuf, name, nlen);
3124             nbuf[nlen] = '\0';
3125
3126             int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3127             if (rc < 0)
3128                 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3129             else
3130                 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
3131
3132             xsp = &xop->xo_stack[xop->xo_depth];
3133             xsp->xs_flags |= XSF_EMIT;
3134         }
3135     }
3136
3137     switch (xo_style(xop)) {
3138     case XO_STYLE_TEXT:
3139         if (flags & XFF_ENCODE_ONLY)
3140             flags |= XFF_NO_OUTPUT;
3141         xo_format_data(xop, NULL, format, flen, flags);
3142         break;
3143
3144     case XO_STYLE_HTML:
3145         if (flags & XFF_ENCODE_ONLY)
3146             flags |= XFF_NO_OUTPUT;
3147         xo_buf_append_div(xop, "data", flags, name, nlen,
3148                           format, flen, encoding, elen);
3149         break;
3150
3151     case XO_STYLE_XML:
3152         /*
3153          * Even though we're not making output, we still need to
3154          * let the formatting code handle the va_arg popping.
3155          */
3156         if (flags & XFF_DISPLAY_ONLY) {
3157             flags |= XFF_NO_OUTPUT;
3158             xo_format_data(xop, NULL, format, flen, flags);
3159             break;
3160         }
3161
3162         if (encoding) {
3163             format = encoding;
3164             flen = elen;
3165         } else {
3166             char *enc  = alloca(flen + 1);
3167             memcpy(enc, format, flen);
3168             enc[flen] = '\0';
3169             format = xo_fix_encoding(xop, enc);
3170             flen = strlen(format);
3171         }
3172
3173         if (nlen == 0) {
3174             static char missing[] = "missing-field-name";
3175             xo_failure(xop, "missing field name: %s", format);
3176             name = missing;
3177             nlen = sizeof(missing) - 1;
3178         }
3179
3180         if (pretty)
3181             xo_buf_indent(xop, -1);
3182         xo_data_append(xop, "<", 1);
3183         xo_data_escape(xop, name, nlen);
3184
3185         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
3186             xo_data_append(xop, xop->xo_attrs.xb_bufp,
3187                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
3188             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
3189         }
3190
3191         /*
3192          * We indicate 'key' fields using the 'key' attribute.  While
3193          * this is really committing the crime of mixing meta-data with
3194          * data, it's often useful.  Especially when format meta-data is
3195          * difficult to come by.
3196          */
3197         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) {
3198             static char attr[] = " key=\"key\"";
3199             xo_data_append(xop, attr, sizeof(attr) - 1);
3200         }
3201
3202         /*
3203          * Save the offset at which we'd place units.  See xo_format_units.
3204          */
3205         if (xop->xo_flags & XOF_UNITS) {
3206             xop->xo_flags |= XOF_UNITS_PENDING;
3207             xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
3208         }
3209
3210         xo_data_append(xop, ">", 1);
3211         xo_format_data(xop, NULL, format, flen, flags);
3212         xo_data_append(xop, "</", 2);
3213         xo_data_escape(xop, name, nlen);
3214         xo_data_append(xop, ">", 1);
3215         if (pretty)
3216             xo_data_append(xop, "\n", 1);
3217         break;
3218
3219     case XO_STYLE_JSON:
3220         if (flags & XFF_DISPLAY_ONLY) {
3221             flags |= XFF_NO_OUTPUT;
3222             xo_format_data(xop, NULL, format, flen, flags);
3223             break;
3224         }
3225
3226         if (encoding) {
3227             format = encoding;
3228             flen = elen;
3229         } else {
3230             char *enc  = alloca(flen + 1);
3231             memcpy(enc, format, flen);
3232             enc[flen] = '\0';
3233             format = xo_fix_encoding(xop, enc);
3234             flen = strlen(format);
3235         }
3236
3237         int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
3238
3239         xo_format_prep(xop, flags);
3240
3241         if (flags & XFF_QUOTE)
3242             quote = 1;
3243         else if (flags & XFF_NOQUOTE)
3244             quote = 0;
3245         else if (flen == 0) {
3246             quote = 0;
3247             format = "true";    /* JSON encodes empty tags as a boolean true */
3248             flen = 4;
3249         } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
3250             quote = 1;
3251         else
3252             quote = 0;
3253
3254         if (nlen == 0) {
3255             static char missing[] = "missing-field-name";
3256             xo_failure(xop, "missing field name: %s", format);
3257             name = missing;
3258             nlen = sizeof(missing) - 1;
3259         }
3260
3261         if (flags & XFF_LEAF_LIST) {
3262             if (!first && pretty)
3263                 xo_data_append(xop, "\n", 1);
3264             if (pretty)
3265                 xo_buf_indent(xop, -1);
3266         } else {
3267             if (pretty)
3268                 xo_buf_indent(xop, -1);
3269             xo_data_append(xop, "\"", 1);
3270
3271             xbp = &xop->xo_data;
3272             int off = xbp->xb_curp - xbp->xb_bufp;
3273
3274             xo_data_escape(xop, name, nlen);
3275
3276             if (xop->xo_flags & XOF_UNDERSCORES) {
3277                 int now = xbp->xb_curp - xbp->xb_bufp;
3278                 for ( ; off < now; off++)
3279                     if (xbp->xb_bufp[off] == '-')
3280                         xbp->xb_bufp[off] = '_';
3281             }
3282             xo_data_append(xop, "\":", 2);
3283             if (pretty)
3284                 xo_data_append(xop, " ", 1);
3285         }
3286
3287         if (quote)
3288             xo_data_append(xop, "\"", 1);
3289
3290         xo_format_data(xop, NULL, format, flen, flags);
3291
3292         if (quote)
3293             xo_data_append(xop, "\"", 1);
3294         break;
3295     }
3296 }
3297
3298 static void
3299 xo_format_content (xo_handle_t *xop, const char *class_name,
3300                    const char *xml_tag, int display_only,
3301                    const char *str, int len, const char *fmt, int flen)
3302 {
3303     switch (xo_style(xop)) {
3304     case XO_STYLE_TEXT:
3305         if (len) {
3306             xo_data_append_content(xop, str, len);
3307         } else
3308             xo_format_data(xop, NULL, fmt, flen, 0);
3309         break;
3310
3311     case XO_STYLE_HTML:
3312         if (len == 0) {
3313             str = fmt;
3314             len = flen;
3315         }
3316
3317         xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0);
3318         break;
3319
3320     case XO_STYLE_XML:
3321         if (xml_tag) {
3322             if (len == 0) {
3323                 str = fmt;
3324                 len = flen;
3325             }
3326
3327             xo_open_container_h(xop, xml_tag);
3328             xo_format_value(xop, "message", 7, str, len, NULL, 0, 0);
3329             xo_close_container_h(xop, xml_tag);
3330
3331         } else {
3332             /*
3333              * Even though we don't care about labels, we need to do
3334              * enough parsing work to skip over the right bits of xo_vap.
3335              */
3336             if (len == 0)
3337                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
3338         }
3339         break;
3340
3341     case XO_STYLE_JSON:
3342         /*
3343          * Even though we don't care about labels, we need to do
3344          * enough parsing work to skip over the right bits of xo_vap.
3345          */
3346         if (display_only) {
3347             if (len == 0)
3348                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
3349             break;
3350         }
3351         /* XXX need schem for representing errors in JSON */
3352         break;
3353     }
3354 }
3355
3356 static const char *xo_color_names[] = {
3357     "default",  /* XO_COL_DEFAULT */
3358     "black",    /* XO_COL_BLACK */
3359     "red",      /* XO_CLOR_RED */
3360     "green",    /* XO_COL_GREEN */
3361     "yellow",   /* XO_COL_YELLOW */
3362     "blue",     /* XO_COL_BLUE */
3363     "magenta",  /* XO_COL_MAGENTA */
3364     "cyan",     /* XO_COL_CYAN */
3365     "white",    /* XO_COL_WHITE */
3366     NULL
3367 };
3368
3369 static int
3370 xo_color_find (const char *str)
3371 {
3372     int i;
3373
3374     for (i = 0; xo_color_names[i]; i++) {
3375         if (strcmp(xo_color_names[i], str) == 0)
3376             return i;
3377     }
3378
3379     return -1;
3380 }
3381
3382 static const char *xo_effect_names[] = {
3383     "reset",                    /* XO_EFF_RESET */
3384     "normal",                   /* XO_EFF_NORMAL */
3385     "bold",                     /* XO_EFF_BOLD */
3386     "underline",                /* XO_EFF_UNDERLINE */
3387     "inverse",                  /* XO_EFF_INVERSE */
3388     NULL
3389 };
3390
3391 static const char *xo_effect_on_codes[] = {
3392     "0",                        /* XO_EFF_RESET */
3393     "0",                        /* XO_EFF_NORMAL */
3394     "1",                        /* XO_EFF_BOLD */
3395     "4",                        /* XO_EFF_UNDERLINE */
3396     "7",                        /* XO_EFF_INVERSE */
3397     NULL
3398 };
3399
3400 #if 0
3401 /*
3402  * See comment below re: joy of terminal standards.  These can
3403  * be use by just adding:
3404  *      if (newp->xoc_effects & bit)
3405  *          code = xo_effect_on_codes[i];
3406  * +    else
3407  * +        code = xo_effect_off_codes[i];
3408  * in xo_color_handle_text.
3409  */
3410 static const char *xo_effect_off_codes[] = {
3411     "0",                        /* XO_EFF_RESET */
3412     "0",                        /* XO_EFF_NORMAL */
3413     "21",                       /* XO_EFF_BOLD */
3414     "24",                       /* XO_EFF_UNDERLINE */
3415     "27",                       /* XO_EFF_INVERSE */
3416     NULL
3417 };
3418 #endif /* 0 */
3419
3420 static int
3421 xo_effect_find (const char *str)
3422 {
3423     int i;
3424
3425     for (i = 0; xo_effect_names[i]; i++) {
3426         if (strcmp(xo_effect_names[i], str) == 0)
3427             return i;
3428     }
3429
3430     return -1;
3431 }
3432
3433 static void
3434 xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
3435 {
3436 #ifdef LIBXO_TEXT_ONLY
3437     return;
3438 #endif /* LIBXO_TEXT_ONLY */
3439
3440     char *cp, *ep, *np, *xp;
3441     int len = strlen(str);
3442     int rc;
3443
3444     /*
3445      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
3446      */
3447     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
3448         /* Trim leading whitespace */
3449         while (isspace((int) *cp))
3450             cp += 1;
3451
3452         np = strchr(cp, ',');
3453         if (np)
3454             *np++ = '\0';
3455
3456         /* Trim trailing whitespace */
3457         xp = cp + strlen(cp) - 1;
3458         while (isspace(*xp) && xp > cp)
3459             *xp-- = '\0';
3460
3461         if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
3462             rc = xo_color_find(cp + 3);
3463             if (rc < 0)
3464                 goto unknown;
3465
3466             xocp->xoc_col_fg = rc;
3467
3468         } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
3469             rc = xo_color_find(cp + 3);
3470             if (rc < 0)
3471                 goto unknown;
3472             xocp->xoc_col_bg = rc;
3473
3474         } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
3475             rc = xo_effect_find(cp + 3);
3476             if (rc < 0)
3477                 goto unknown;
3478             xocp->xoc_effects &= ~(1 << rc);
3479
3480         } else {
3481             rc = xo_effect_find(cp);
3482             if (rc < 0)
3483                 goto unknown;
3484             xocp->xoc_effects |= 1 << rc;
3485
3486             switch (1 << rc) {
3487             case XO_EFF_RESET:
3488                 xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
3489                 /* Note: not "|=" since we want to wipe out the old value */
3490                 xocp->xoc_effects = XO_EFF_RESET;
3491                 break;
3492
3493             case XO_EFF_NORMAL:
3494                 xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
3495                                       | XO_EFF_INVERSE | XO_EFF_NORMAL);
3496                 break;
3497             }
3498         }
3499         continue;
3500
3501     unknown:
3502         if (xop->xo_flags & XOF_WARN)
3503             xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
3504     }
3505 }
3506
3507 static inline int
3508 xo_colors_enabled (xo_handle_t *xop UNUSED)
3509 {
3510 #ifdef LIBXO_TEXT_ONLY
3511     return 0;
3512 #else /* LIBXO_TEXT_ONLY */
3513     return ((xop->xo_flags & XOF_COLOR) ? 1 : 0);
3514 #endif /* LIBXO_TEXT_ONLY */
3515 }
3516
3517 static void
3518 xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
3519 {
3520     char buf[BUFSIZ];
3521     char *cp = buf, *ep = buf + sizeof(buf);
3522     unsigned i, bit;
3523     xo_colors_t *oldp = &xop->xo_colors;
3524     const char *code = NULL;
3525
3526     /*
3527      * Start the buffer with an escape.  We don't want to add the '['
3528      * now, since we let xo_effect_text_add unconditionally add the ';'.
3529      * We'll replace the first ';' with a '[' when we're done.
3530      */
3531     *cp++ = 0x1b;               /* Escape */
3532
3533     /*
3534      * Terminals were designed back in the age before "certainty" was
3535      * invented, when standards were more what you'd call "guidelines"
3536      * than actual rules.  Anyway we can't depend on them to operate
3537      * correctly.  So when display attributes are changed, we punt,
3538      * reseting them all and turning back on the ones we want to keep.
3539      * Longer, but should be completely reliable.  Savvy?
3540      */
3541     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
3542         newp->xoc_effects |= XO_EFF_RESET;
3543         oldp->xoc_effects = 0;
3544     }
3545
3546     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
3547         if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
3548             continue;
3549
3550         if (newp->xoc_effects & bit)
3551             code = xo_effect_on_codes[i];
3552
3553         cp += snprintf(cp, ep - cp, ";%s", code);
3554         if (cp >= ep)
3555             return;             /* Should not occur */
3556
3557         if (bit == XO_EFF_RESET) {
3558             /* Mark up the old value so we can detect current values as new */
3559             oldp->xoc_effects = 0;
3560             oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
3561         }
3562     }
3563
3564     if (newp->xoc_col_fg != oldp->xoc_col_fg) {
3565         cp += snprintf(cp, ep - cp, ";3%u",
3566                        (newp->xoc_col_fg != XO_COL_DEFAULT)
3567                        ? newp->xoc_col_fg - 1 : 9);
3568     }
3569
3570     if (newp->xoc_col_bg != oldp->xoc_col_bg) {
3571         cp += snprintf(cp, ep - cp, ";4%u",
3572                        (newp->xoc_col_bg != XO_COL_DEFAULT)
3573                        ? newp->xoc_col_bg - 1 : 9);
3574     }
3575
3576     if (cp - buf != 1 && cp < ep - 3) {
3577         buf[1] = '[';           /* Overwrite leading ';' */
3578         *cp++ = 'm';
3579         *cp = '\0';
3580         xo_buf_append(&xop->xo_data, buf, cp - buf);
3581     }
3582 }
3583
3584 static void
3585 xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
3586 {
3587     xo_colors_t *oldp = &xop->xo_colors;
3588
3589     /*
3590      * HTML colors are mostly trivial: fill in xo_color_buf with
3591      * a set of class tags representing the colors and effects.
3592      */
3593
3594     /* If nothing changed, then do nothing */
3595     if (oldp->xoc_effects == newp->xoc_effects
3596         && oldp->xoc_col_fg == newp->xoc_col_fg
3597         && oldp->xoc_col_bg == newp->xoc_col_bg)
3598         return;
3599
3600     unsigned i, bit;
3601     xo_buffer_t *xbp = &xop->xo_color_buf;
3602
3603     xo_buf_reset(xbp);          /* We rebuild content after each change */
3604
3605     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
3606         if (!(newp->xoc_effects & bit))
3607             continue;
3608
3609         xo_buf_append_str(xbp, " effect-");
3610         xo_buf_append_str(xbp, xo_effect_names[i]);
3611     }
3612
3613     const char *fg = NULL;
3614     const char *bg = NULL;
3615
3616     if (newp->xoc_col_fg != XO_COL_DEFAULT)
3617         fg = xo_color_names[newp->xoc_col_fg];
3618     if (newp->xoc_col_bg != XO_COL_DEFAULT)
3619         bg = xo_color_names[newp->xoc_col_bg];
3620
3621     if (newp->xoc_effects & XO_EFF_INVERSE) {
3622         const char *tmp = fg;
3623         fg = bg;
3624         bg = tmp;
3625         if (fg == NULL)
3626             fg = "inverse";
3627         if (bg == NULL)
3628             bg = "inverse";
3629
3630     }
3631
3632     if (fg) {
3633         xo_buf_append_str(xbp, " color-fg-");
3634         xo_buf_append_str(xbp, fg);
3635     }
3636
3637     if (bg) {
3638         xo_buf_append_str(xbp, " color-bg-");
3639         xo_buf_append_str(xbp, bg);
3640     }
3641 }
3642
3643 static void
3644 xo_format_colors (xo_handle_t *xop, const char *str, int len,
3645                   const char *fmt, int flen)
3646 {
3647     xo_buffer_t xb;
3648
3649     /* If the string is static and we've in an encoding style, bail */
3650     if (len != 0
3651         && (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON))
3652         return;
3653
3654     xo_buf_init(&xb);
3655
3656     if (len)
3657         xo_buf_append(&xb, str, len);
3658     else if (flen)
3659         xo_format_data(xop, &xb, fmt, flen, 0);
3660     else
3661         xo_buf_append(&xb, "reset", 6); /* Default if empty */
3662
3663     if (xo_colors_enabled(xop)) {
3664         switch (xo_style(xop)) {
3665         case XO_STYLE_TEXT:
3666         case XO_STYLE_HTML:
3667             xo_buf_append(&xb, "", 1);
3668
3669             xo_colors_t xoc = xop->xo_colors;
3670             xo_colors_parse(xop, &xoc, xb.xb_bufp);
3671
3672             if (xo_style(xop) == XO_STYLE_TEXT) {
3673                 /*
3674                  * Text mode means emitting the colors as ANSI character
3675                  * codes.  This will allow people who like colors to have
3676                  * colors.  The issue is, of course conflicting with the
3677                  * user's perfectly reasonable color scheme.  Which leads
3678                  * to the hell of LSCOLORS, where even app need to have
3679                  * customization hooks for adjusting colors.  Instead we
3680                  * provide a simpler-but-still-annoying answer where one
3681                  * can map colors to other colors.
3682                  */
3683                 xo_colors_handle_text(xop, &xoc);
3684                 xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
3685
3686             } else {
3687                 /*
3688                  * HTML output is wrapped in divs, so the color information
3689                  * must appear in every div until cleared.  Most pathetic.
3690                  * Most unavoidable.
3691                  */
3692                 xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
3693                 xo_colors_handle_html(xop, &xoc);
3694             }
3695
3696             xop->xo_colors = xoc;
3697             break;
3698
3699         case XO_STYLE_XML:
3700         case XO_STYLE_JSON:
3701             /*
3702              * Nothing to do; we did all that work just to clear the stack of
3703              * formatting arguments.
3704              */
3705             break;
3706         }
3707     }
3708
3709     xo_buf_cleanup(&xb);
3710 }
3711
3712 static void
3713 xo_format_units (xo_handle_t *xop, const char *str, int len,
3714                  const char *fmt, int flen)
3715 {
3716     static char units_start_xml[] = " units=\"";
3717     static char units_start_html[] = " data-units=\"";
3718
3719     if (!(xop->xo_flags & XOF_UNITS_PENDING)) {
3720         xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen);
3721         return;
3722     }
3723
3724     xo_buffer_t *xbp = &xop->xo_data;
3725     int start = xop->xo_units_offset;
3726     int stop = xbp->xb_curp - xbp->xb_bufp;
3727
3728     if (xo_style(xop) == XO_STYLE_XML)
3729         xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
3730     else if (xo_style(xop) == XO_STYLE_HTML)
3731         xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
3732     else
3733         return;
3734
3735     if (len)
3736         xo_data_append(xop, str, len);
3737     else
3738         xo_format_data(xop, NULL, fmt, flen, 0);
3739
3740     xo_buf_append(xbp, "\"", 1);
3741
3742     int now = xbp->xb_curp - xbp->xb_bufp;
3743     int delta = now - stop;
3744     if (delta < 0) {            /* Strange; no output to move */
3745         xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
3746         return;
3747     }
3748
3749     /*
3750      * Now we're in it alright.  We've need to insert the unit value
3751      * we just created into the right spot.  We make a local copy,
3752      * move it and then insert our copy.  We know there's room in the
3753      * buffer, since we're just moving this around.
3754      */
3755     char *buf = alloca(delta);
3756
3757     memcpy(buf, xbp->xb_bufp + stop, delta);
3758     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3759     memmove(xbp->xb_bufp + start, buf, delta);
3760 }
3761
3762 static int
3763 xo_find_width (xo_handle_t *xop, const char *str, int len,
3764                  const char *fmt, int flen)
3765 {
3766     long width = 0;
3767     char *bp;
3768     char *cp;
3769
3770     if (len) {
3771         bp = alloca(len + 1);   /* Make local NUL-terminated copy of str */
3772         memcpy(bp, str, len);
3773         bp[len] = '\0';
3774
3775         width = strtol(bp, &cp, 0);
3776         if (width == LONG_MIN || width == LONG_MAX
3777             || bp == cp || *cp != '\0' ) {
3778             width = 0;
3779             xo_failure(xop, "invalid width for anchor: '%s'", bp);
3780         }
3781     } else if (flen) {
3782         if (flen != 2 || strncmp("%d", fmt, flen) != 0)
3783             xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
3784         if (!(xop->xo_flags & XOF_NO_VA_ARG))
3785             width = va_arg(xop->xo_vap, int);
3786     }
3787
3788     return width;
3789 }
3790
3791 static void
3792 xo_anchor_clear (xo_handle_t *xop)
3793 {
3794     xop->xo_flags &= ~XOF_ANCHOR;
3795     xop->xo_anchor_offset = 0;
3796     xop->xo_anchor_columns = 0;
3797     xop->xo_anchor_min_width = 0;
3798 }
3799
3800 /*
3801  * An anchor is a marker used to delay field width implications.
3802  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
3803  * We are looking for output like "     1/4/5"
3804  *
3805  * To make this work, we record the anchor and then return to
3806  * format it when the end anchor tag is seen.
3807  */
3808 static void
3809 xo_anchor_start (xo_handle_t *xop, const char *str, int len,
3810                  const char *fmt, int flen)
3811 {
3812     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
3813         return;
3814
3815     if (xop->xo_flags & XOF_ANCHOR)
3816         xo_failure(xop, "the anchor already recording is discarded");
3817
3818     xop->xo_flags |= XOF_ANCHOR;
3819     xo_buffer_t *xbp = &xop->xo_data;
3820     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
3821     xop->xo_anchor_columns = 0;
3822
3823     /*
3824      * Now we find the width, if possible.  If it's not there,
3825      * we'll get it on the end anchor.
3826      */
3827     xop->xo_anchor_min_width = xo_find_width(xop, str, len, fmt, flen);
3828 }
3829
3830 static void
3831 xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
3832                  const char *fmt, int flen)
3833 {
3834     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
3835         return;
3836
3837     if (!(xop->xo_flags & XOF_ANCHOR)) {
3838         xo_failure(xop, "no start anchor");
3839         return;
3840     }
3841
3842     xop->xo_flags &= ~XOF_UNITS_PENDING;
3843
3844     int width = xo_find_width(xop, str, len, fmt, flen);
3845     if (width == 0)
3846         width = xop->xo_anchor_min_width;
3847
3848     if (width == 0)             /* No width given; nothing to do */
3849         goto done;
3850
3851     xo_buffer_t *xbp = &xop->xo_data;
3852     int start = xop->xo_anchor_offset;
3853     int stop = xbp->xb_curp - xbp->xb_bufp;
3854     int abswidth = (width > 0) ? width : -width;
3855     int blen = abswidth - xop->xo_anchor_columns;
3856
3857     if (blen <= 0)              /* Already over width */
3858         goto done;
3859
3860     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
3861         xo_failure(xop, "width over %u are not supported",
3862                    XO_MAX_ANCHOR_WIDTH);
3863         goto done;
3864     }
3865
3866     /* Make a suitable padding field and emit it */
3867     char *buf = alloca(blen);
3868     memset(buf, ' ', blen);
3869     xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0);
3870
3871     if (width < 0)              /* Already left justified */
3872         goto done;
3873
3874     int now = xbp->xb_curp - xbp->xb_bufp;
3875     int delta = now - stop;
3876     if (delta < 0)              /* Strange; no output to move */
3877         goto done;
3878
3879     /*
3880      * Now we're in it alright.  We've need to insert the padding data
3881      * we just created (which might be an HTML <div> or text) before
3882      * the formatted data.  We make a local copy, move it and then
3883      * insert our copy.  We know there's room in the buffer, since
3884      * we're just moving this around.
3885      */
3886     if (delta > blen)
3887         buf = alloca(delta);    /* Expand buffer if needed */
3888
3889     memcpy(buf, xbp->xb_bufp + stop, delta);
3890     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3891     memmove(xbp->xb_bufp + start, buf, delta);
3892
3893  done:
3894     xo_anchor_clear(xop);
3895 }
3896
3897 static int
3898 xo_do_emit (xo_handle_t *xop, const char *fmt)
3899 {
3900     int rc = 0;
3901     const char *cp, *sp, *ep, *basep;
3902     char *newp = NULL;
3903     int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0;
3904     int flush_line = (xop->xo_flags & XOF_FLUSH_LINE) ? 1 : 0;
3905
3906     xop->xo_columns = 0;        /* Always reset it */
3907
3908     for (cp = fmt; *cp; ) {
3909         if (*cp == '\n') {
3910             xo_line_close(xop);
3911             if (flush_line && xo_flush_h(xop) < 0)
3912                 return -1;
3913             cp += 1;
3914             continue;
3915
3916         } else if (*cp == '{') {
3917             if (cp[1] == '{') { /* Start of {{escaped braces}} */
3918
3919                 cp += 2;        /* Skip over _both_ characters */
3920                 for (sp = cp; *sp; sp++) {
3921                     if (*sp == '}' && sp[1] == '}')
3922                         break;
3923                 }
3924                 if (*sp == '\0') {
3925                     xo_failure(xop, "missing closing '}}': %s", fmt);
3926                     return -1;
3927                 }
3928
3929                 xo_format_text(xop, cp, sp - cp);
3930
3931                 /* Move along the string, but don't run off the end */
3932                 if (*sp == '}' && sp[1] == '}')
3933                     sp += 2;
3934                 cp = *sp ? sp + 1 : sp;
3935                 continue;
3936             }
3937             /* Else fall thru to the code below */
3938
3939         } else {
3940             /* Normal text */
3941             for (sp = cp; *sp; sp++) {
3942                 if (*sp == '{' || *sp == '\n')
3943                     break;
3944             }
3945             xo_format_text(xop, cp, sp - cp);
3946
3947             cp = sp;
3948             continue;
3949         }
3950
3951         basep = cp + 1;
3952
3953         /*
3954          * We are looking at the start of a field definition.  The format is:
3955          *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
3956          * Modifiers are optional and include the following field types:
3957          *   'D': decoration; something non-text and non-data (colons, commmas)
3958          *   'E': error message
3959          *   'L': label; text preceding data
3960          *   'N': note; text following data
3961          *   'P': padding; whitespace
3962          *   'T': Title, where 'content' is a column title
3963          *   'U': Units, where 'content' is the unit label
3964          *   'V': value, where 'content' is the name of the field (the default)
3965          *   'W': warning message
3966          *   '[': start a section of anchored text
3967          *   ']': end a section of anchored text
3968          * The following flags are also supported:
3969          *   'c': flag: emit a colon after the label
3970          *   'd': field is only emitted for display formats (text and html)
3971          *   'e': field is only emitted for encoding formats (xml and json)
3972          *   'k': this field is a key, suitable for XPath predicates
3973          *   'l': a leaf-list, a simple list of values
3974          *   'n': no quotes around this field
3975          *   'q': add quotes around this field
3976          *   't': trim whitespace around the value
3977          *   'w': emit a blank after the label
3978          * The print-fmt and encode-fmt strings is the printf-style formating
3979          * for this data.  JSON and XML will use the encoding-fmt, if present.
3980          * If the encode-fmt is not provided, it defaults to the print-fmt.
3981          * If the print-fmt is not provided, it defaults to 's'.
3982          */
3983         unsigned ftype = 0, flags = 0;
3984         const char *content = NULL, *format = NULL, *encoding = NULL;
3985         int clen = 0, flen = 0, elen = 0;
3986
3987         for (sp = basep; sp; sp++) {
3988             if (*sp == ':' || *sp == '/' || *sp == '}')
3989                 break;
3990
3991             if (*sp == '\\') {
3992                 if (sp[1] == '\0') {
3993                     xo_failure(xop, "backslash at the end of string");
3994                     return -1;
3995                 }
3996                 sp += 1;
3997                 continue;
3998             }
3999
4000             switch (*sp) {
4001             case 'C':
4002             case 'D':
4003             case 'E':
4004             case 'L':
4005             case 'N':
4006             case 'P':
4007             case 'T':
4008             case 'U':
4009             case 'V':
4010             case 'W':
4011             case '[':
4012             case ']':
4013                 if (ftype != 0) {
4014                     xo_failure(xop, "field descriptor uses multiple types: %s",
4015                                   fmt);
4016                     return -1;
4017                 }
4018                 ftype = *sp;
4019                 break;
4020
4021             case 'c':
4022                 flags |= XFF_COLON;
4023                 break;
4024
4025             case 'd':
4026                 flags |= XFF_DISPLAY_ONLY;
4027                 break;
4028
4029             case 'e':
4030                 flags |= XFF_ENCODE_ONLY;
4031                 break;
4032
4033             case 'k':
4034                 flags |= XFF_KEY;
4035                 break;
4036
4037             case 'l':
4038                 flags |= XFF_LEAF_LIST;
4039                 break;
4040
4041             case 'n':
4042                 flags |= XFF_NOQUOTE;
4043                 break;
4044
4045             case 'q':
4046                 flags |= XFF_QUOTE;
4047                 break;
4048
4049             case 't':
4050                 flags |= XFF_TRIM_WS;
4051                 break;
4052
4053             case 'w':
4054                 flags |= XFF_WS;
4055                 break;
4056
4057             default:
4058                 xo_failure(xop, "field descriptor uses unknown modifier: %s",
4059                               fmt);
4060                 /*
4061                  * No good answer here; a bad format will likely
4062                  * mean a core file.  We just return and hope
4063                  * the caller notices there's no output, and while
4064                  * that seems, well, bad.  There's nothing better.
4065                  */
4066                 return -1;
4067             }
4068         }
4069
4070         if (*sp == ':') {
4071             for (ep = ++sp; *sp; sp++) {
4072                 if (*sp == '}' || *sp == '/')
4073                     break;
4074                 if (*sp == '\\') {
4075                     if (sp[1] == '\0') {
4076                         xo_failure(xop, "backslash at the end of string");
4077                         return -1;
4078                     }
4079                     sp += 1;
4080                     continue;
4081                 }
4082             }
4083             if (ep != sp) {
4084                 clen = sp - ep;
4085                 content = ep;
4086             }
4087         } else {
4088             xo_failure(xop, "missing content (':'): %s", fmt);
4089             return -1;
4090         }
4091
4092         if (*sp == '/') {
4093             for (ep = ++sp; *sp; sp++) {
4094                 if (*sp == '}' || *sp == '/')
4095                     break;
4096                 if (*sp == '\\') {
4097                     if (sp[1] == '\0') {
4098                         xo_failure(xop, "backslash at the end of string");
4099                         return -1;
4100                     }
4101                     sp += 1;
4102                     continue;
4103                 }
4104             }
4105             flen = sp - ep;
4106             format = ep;
4107         }
4108
4109         if (*sp == '/') {
4110             for (ep = ++sp; *sp; sp++) {
4111                 if (*sp == '}')
4112                     break;
4113             }
4114             elen = sp - ep;
4115             encoding = ep;
4116         }
4117
4118         if (*sp == '}') {
4119             sp += 1;
4120         } else {
4121             xo_failure(xop, "missing closing '}': %s", fmt);
4122             return -1;
4123         }
4124
4125         if (ftype == 0 || ftype == 'V') {
4126             if (format == NULL) {
4127                 /* Default format for value fields is '%s' */
4128                 format = "%s";
4129                 flen = 2;
4130             }
4131
4132             xo_format_value(xop, content, clen, format, flen,
4133                             encoding, elen, flags);
4134
4135         } else if (ftype == '[')
4136                 xo_anchor_start(xop, content, clen, format, flen);
4137         else if (ftype == ']')
4138                 xo_anchor_stop(xop, content, clen, format, flen);
4139         else if (ftype == 'C')
4140                 xo_format_colors(xop, content, clen, format, flen);
4141
4142         else  if (clen || format) { /* Need either content or format */
4143             if (format == NULL) {
4144                 /* Default format for value fields is '%s' */
4145                 format = "%s";
4146                 flen = 2;
4147             }
4148
4149             if (ftype == 'D')
4150                 xo_format_content(xop, "decoration", NULL, 1,
4151                                   content, clen, format, flen);
4152             else if (ftype == 'E')
4153                 xo_format_content(xop, "error", "error", 0,
4154                                   content, clen, format, flen);
4155             else if (ftype == 'L')
4156                 xo_format_content(xop, "label", NULL, 1,
4157                                   content, clen, format, flen);
4158             else if (ftype == 'N')
4159                 xo_format_content(xop, "note", NULL, 1,
4160                                   content, clen, format, flen);
4161             else if (ftype == 'P')
4162                 xo_format_content(xop, "padding", NULL, 1,
4163                                   content, clen, format, flen);
4164             else if (ftype == 'T')
4165                 xo_format_title(xop, content, clen, format, flen);
4166             else if (ftype == 'U') {
4167                 if (flags & XFF_WS)
4168                     xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
4169                 xo_format_units(xop, content, clen, format, flen);
4170             } else if (ftype == 'W')
4171                 xo_format_content(xop, "warning", "warning", 0,
4172                                   content, clen, format, flen);
4173         }
4174
4175         if (flags & XFF_COLON)
4176             xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0);
4177         if (ftype != 'U' && (flags & XFF_WS))
4178             xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
4179
4180         cp += sp - basep + 1;
4181         if (newp) {
4182             xo_free(newp);
4183             newp = NULL;
4184         }
4185     }
4186
4187     /* If we don't have an anchor, write the text out */
4188     if (flush && !(xop->xo_flags & XOF_ANCHOR)) {
4189         if (xo_write(xop) < 0) 
4190             rc = -1;            /* Report failure */
4191         else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0)
4192             rc = -1;
4193     }
4194
4195     return (rc < 0) ? rc : (int) xop->xo_columns;
4196 }
4197
4198 int
4199 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
4200 {
4201     int rc;
4202
4203     xop = xo_default(xop);
4204     va_copy(xop->xo_vap, vap);
4205     rc = xo_do_emit(xop, fmt);
4206     va_end(xop->xo_vap);
4207     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4208
4209     return rc;
4210 }
4211
4212 int
4213 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
4214 {
4215     int rc;
4216
4217     xop = xo_default(xop);
4218     va_start(xop->xo_vap, fmt);
4219     rc = xo_do_emit(xop, fmt);
4220     va_end(xop->xo_vap);
4221     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4222
4223     return rc;
4224 }
4225
4226 int
4227 xo_emit (const char *fmt, ...)
4228 {
4229     xo_handle_t *xop = xo_default(NULL);
4230     int rc;
4231
4232     va_start(xop->xo_vap, fmt);
4233     rc = xo_do_emit(xop, fmt);
4234     va_end(xop->xo_vap);
4235     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4236
4237     return rc;
4238 }
4239
4240 int
4241 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
4242 {
4243     const int extra = 5;        /* space, equals, quote, quote, and nul */
4244     xop = xo_default(xop);
4245
4246     if (xo_style(xop) != XO_STYLE_XML)
4247         return 0;
4248
4249     int nlen = strlen(name);
4250     xo_buffer_t *xbp = &xop->xo_attrs;
4251
4252     if (!xo_buf_has_room(xbp, nlen + extra))
4253         return -1;
4254
4255     *xbp->xb_curp++ = ' ';
4256     memcpy(xbp->xb_curp, name, nlen);
4257     xbp->xb_curp += nlen;
4258     *xbp->xb_curp++ = '=';
4259     *xbp->xb_curp++ = '"';
4260
4261     int rc = xo_vsnprintf(xop, xbp, fmt, vap);
4262
4263     if (rc > 0) {
4264         rc = xo_escape_xml(xbp, rc, 1);
4265         xbp->xb_curp += rc;
4266     }
4267
4268     if (!xo_buf_has_room(xbp, 2))
4269         return -1;
4270
4271     *xbp->xb_curp++ = '"';
4272     *xbp->xb_curp = '\0';
4273
4274     return rc + nlen + extra;
4275 }
4276
4277 int
4278 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
4279 {
4280     int rc;
4281     va_list vap;
4282
4283     va_start(vap, fmt);
4284     rc = xo_attr_hv(xop, name, fmt, vap);
4285     va_end(vap);
4286
4287     return rc;
4288 }
4289
4290 int
4291 xo_attr (const char *name, const char *fmt, ...)
4292 {
4293     int rc;
4294     va_list vap;
4295
4296     va_start(vap, fmt);
4297     rc = xo_attr_hv(NULL, name, fmt, vap);
4298     va_end(vap);
4299
4300     return rc;
4301 }
4302
4303 static void
4304 xo_stack_set_flags (xo_handle_t *xop)
4305 {
4306     if (xop->xo_flags & XOF_NOT_FIRST) {
4307         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4308
4309         xsp->xs_flags |= XSF_NOT_FIRST;
4310         xop->xo_flags &= ~XOF_NOT_FIRST;
4311     }
4312 }
4313
4314 static void
4315 xo_depth_change (xo_handle_t *xop, const char *name,
4316                  int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
4317 {
4318     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
4319         indent = 0;
4320
4321     if (xop->xo_flags & XOF_DTRT)
4322         flags |= XSF_DTRT;
4323
4324     if (delta >= 0) {                   /* Push operation */
4325         if (xo_depth_check(xop, xop->xo_depth + delta))
4326             return;
4327
4328         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
4329         xsp->xs_flags = flags;
4330         xsp->xs_state = state;
4331         xo_stack_set_flags(xop);
4332
4333         if (name == NULL)
4334             name = XO_FAILURE_NAME;
4335
4336         int len = strlen(name) + 1;
4337         char *cp = xo_realloc(NULL, len);
4338         if (cp) {
4339             memcpy(cp, name, len);
4340             xsp->xs_name = cp;
4341         }
4342
4343     } else {                    /* Pop operation */
4344         if (xop->xo_depth == 0) {
4345             if (!(xop->xo_flags & XOF_IGNORE_CLOSE))
4346                 xo_failure(xop, "close with empty stack: '%s'", name);
4347             return;
4348         }
4349
4350         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4351         if (xop->xo_flags & XOF_WARN) {
4352             const char *top = xsp->xs_name;
4353             if (top && strcmp(name, top) != 0) {
4354                 xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
4355                               name, top);
4356                 return;
4357             } 
4358             if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
4359                 xo_failure(xop, "list close on list confict: '%s'",
4360                               name);
4361                 return;
4362             }
4363             if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
4364                 xo_failure(xop, "list close on instance confict: '%s'",
4365                               name);
4366                 return;
4367             }
4368         }
4369
4370         if (xsp->xs_name) {
4371             xo_free(xsp->xs_name);
4372             xsp->xs_name = NULL;
4373         }
4374         if (xsp->xs_keys) {
4375             xo_free(xsp->xs_keys);
4376             xsp->xs_keys = NULL;
4377         }
4378     }
4379
4380     xop->xo_depth += delta;     /* Record new depth */
4381     xop->xo_indent += indent;
4382 }
4383
4384 void
4385 xo_set_depth (xo_handle_t *xop, int depth)
4386 {
4387     xop = xo_default(xop);
4388
4389     if (xo_depth_check(xop, depth))
4390         return;
4391
4392     xop->xo_depth += depth;
4393     xop->xo_indent += depth;
4394 }
4395
4396 static xo_xsf_flags_t
4397 xo_stack_flags (unsigned xflags)
4398 {
4399     if (xflags & XOF_DTRT)
4400         return XSF_DTRT;
4401     return 0;
4402 }
4403
4404 static void
4405 xo_emit_top (xo_handle_t *xop, const char *ppn)
4406 {
4407     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
4408     xop->xo_flags |= XOF_TOP_EMITTED;
4409
4410     if (xop->xo_version) {
4411         xo_printf(xop, "%*s\"__version\": \"%s\", %s",
4412                   xo_indent(xop), "", xop->xo_version, ppn);
4413         xo_free(xop->xo_version);
4414         xop->xo_version = NULL;
4415     }
4416 }
4417
4418 static int
4419 xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
4420 {
4421     int rc = 0;
4422     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4423     const char *pre_nl = "";
4424
4425     if (name == NULL) {
4426         xo_failure(xop, "NULL passed for container name");
4427         name = XO_FAILURE_NAME;
4428     }
4429
4430     flags |= xop->xo_flags;     /* Pick up handle flags */
4431
4432     switch (xo_style(xop)) {
4433     case XO_STYLE_XML:
4434         rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
4435
4436         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4437             rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
4438             xo_data_append(xop, xop->xo_attrs.xb_bufp,
4439                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4440             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4441         }
4442
4443         rc += xo_printf(xop, ">%s", ppn);
4444         break;
4445
4446     case XO_STYLE_JSON:
4447         xo_stack_set_flags(xop);
4448
4449         if (!(xop->xo_flags & XOF_NO_TOP)
4450                 && !(xop->xo_flags & XOF_TOP_EMITTED))
4451             xo_emit_top(xop, ppn);
4452
4453         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4454             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4455         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4456
4457         rc = xo_printf(xop, "%s%*s\"%s\": {%s",
4458                        pre_nl, xo_indent(xop), "", name, ppn);
4459         break;
4460     }
4461
4462     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
4463                     xo_stack_flags(flags));
4464
4465     return rc;
4466 }
4467
4468 static int
4469 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
4470 {
4471     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
4472 }
4473
4474 int
4475 xo_open_container_h (xo_handle_t *xop, const char *name)
4476 {
4477     return xo_open_container_hf(xop, 0, name);
4478 }
4479
4480 int
4481 xo_open_container (const char *name)
4482 {
4483     return xo_open_container_hf(NULL, 0, name);
4484 }
4485
4486 int
4487 xo_open_container_hd (xo_handle_t *xop, const char *name)
4488 {
4489     return xo_open_container_hf(xop, XOF_DTRT, name);
4490 }
4491
4492 int
4493 xo_open_container_d (const char *name)
4494 {
4495     return xo_open_container_hf(NULL, XOF_DTRT, name);
4496 }
4497
4498 static int
4499 xo_do_close_container (xo_handle_t *xop, const char *name)
4500 {
4501     xop = xo_default(xop);
4502
4503     int rc = 0;
4504     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4505     const char *pre_nl = "";
4506
4507     if (name == NULL) {
4508         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4509
4510         name = xsp->xs_name;
4511         if (name) {
4512             int len = strlen(name) + 1;
4513             /* We need to make a local copy; xo_depth_change will free it */
4514             char *cp = alloca(len);
4515             memcpy(cp, name, len);
4516             name = cp;
4517         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4518             xo_failure(xop, "missing name without 'dtrt' mode");
4519             name = XO_FAILURE_NAME;
4520         }
4521     }
4522
4523     switch (xo_style(xop)) {
4524     case XO_STYLE_XML:
4525         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
4526         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
4527         break;
4528
4529     case XO_STYLE_JSON:
4530         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4531         ppn = (xop->xo_depth <= 1) ? "\n" : "";
4532
4533         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
4534         rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
4535         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4536         break;
4537
4538     case XO_STYLE_HTML:
4539     case XO_STYLE_TEXT:
4540         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
4541         break;
4542     }
4543
4544     return rc;
4545 }
4546
4547 int
4548 xo_close_container_h (xo_handle_t *xop, const char *name)
4549 {
4550     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
4551 }
4552
4553 int
4554 xo_close_container (const char *name)
4555 {
4556     return xo_close_container_h(NULL, name);
4557 }
4558
4559 int
4560 xo_close_container_hd (xo_handle_t *xop)
4561 {
4562     return xo_close_container_h(xop, NULL);
4563 }
4564
4565 int
4566 xo_close_container_d (void)
4567 {
4568     return xo_close_container_h(NULL, NULL);
4569 }
4570
4571 static int
4572 xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4573 {
4574     int rc = 0;
4575     int indent = 0;
4576
4577     xop = xo_default(xop);
4578
4579     if (xo_style(xop) == XO_STYLE_JSON) {
4580         const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4581         const char *pre_nl = "";
4582
4583         indent = 1;
4584         if (!(xop->xo_flags & XOF_NO_TOP)
4585                 && !(xop->xo_flags & XOF_TOP_EMITTED))
4586             xo_emit_top(xop, ppn);
4587
4588         if (name == NULL) {
4589             xo_failure(xop, "NULL passed for list name");
4590             name = XO_FAILURE_NAME;
4591         }
4592
4593         xo_stack_set_flags(xop);
4594
4595         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4596             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4597         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4598
4599         rc = xo_printf(xop, "%s%*s\"%s\": [%s",
4600                        pre_nl, xo_indent(xop), "", name, ppn);
4601     }
4602
4603     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
4604                     XSF_LIST | xo_stack_flags(flags));
4605
4606     return rc;
4607 }
4608
4609 static int
4610 xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4611 {
4612     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
4613 }
4614
4615 int
4616 xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
4617 {
4618     return xo_open_list_hf(xop, 0, name);
4619 }
4620
4621 int
4622 xo_open_list (const char *name)
4623 {
4624     return xo_open_list_hf(NULL, 0, name);
4625 }
4626
4627 int
4628 xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
4629 {
4630     return xo_open_list_hf(xop, XOF_DTRT, name);
4631 }
4632
4633 int
4634 xo_open_list_d (const char *name)
4635 {
4636     return xo_open_list_hf(NULL, XOF_DTRT, name);
4637 }
4638
4639 static int
4640 xo_do_close_list (xo_handle_t *xop, const char *name)
4641 {
4642     int rc = 0;
4643     const char *pre_nl = "";
4644
4645     if (name == NULL) {
4646         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4647
4648         name = xsp->xs_name;
4649         if (name) {
4650             int len = strlen(name) + 1;
4651             /* We need to make a local copy; xo_depth_change will free it */
4652             char *cp = alloca(len);
4653             memcpy(cp, name, len);
4654             name = cp;
4655         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4656             xo_failure(xop, "missing name without 'dtrt' mode");
4657             name = XO_FAILURE_NAME;
4658         }
4659     }
4660
4661     if (xo_style(xop) == XO_STYLE_JSON) {
4662         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4663             pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4664         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4665
4666         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
4667         rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
4668         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4669
4670     } else {
4671         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
4672         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4673     }
4674
4675     return rc;
4676 }
4677
4678 int
4679 xo_close_list_h (xo_handle_t *xop, const char *name)
4680 {
4681     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
4682 }
4683
4684 int
4685 xo_close_list (const char *name)
4686 {
4687     return xo_close_list_h(NULL, name);
4688 }
4689
4690 int
4691 xo_close_list_hd (xo_handle_t *xop)
4692 {
4693     return xo_close_list_h(xop, NULL);
4694 }
4695
4696 int
4697 xo_close_list_d (void)
4698 {
4699     return xo_close_list_h(NULL, NULL);
4700 }
4701
4702 static int
4703 xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4704 {
4705     int rc = 0;
4706     int indent = 0;
4707
4708     xop = xo_default(xop);
4709
4710     if (xo_style(xop) == XO_STYLE_JSON) {
4711         const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4712         const char *pre_nl = "";
4713
4714         indent = 1;
4715
4716         if (!(xop->xo_flags & XOF_NO_TOP)) {
4717             if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
4718                 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
4719                 xop->xo_flags |= XOF_TOP_EMITTED;
4720             }
4721         }
4722
4723         if (name == NULL) {
4724             xo_failure(xop, "NULL passed for list name");
4725             name = XO_FAILURE_NAME;
4726         }
4727
4728         xo_stack_set_flags(xop);
4729
4730         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4731             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4732         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4733
4734         rc = xo_printf(xop, "%s%*s\"%s\": [%s",
4735                        pre_nl, xo_indent(xop), "", name, ppn);
4736     }
4737
4738     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
4739                     XSF_LIST | xo_stack_flags(flags));
4740
4741     return rc;
4742 }
4743
4744 static int
4745 xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
4746 {
4747     int rc = 0;
4748     const char *pre_nl = "";
4749
4750     if (name == NULL) {
4751         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4752
4753         name = xsp->xs_name;
4754         if (name) {
4755             int len = strlen(name) + 1;
4756             /* We need to make a local copy; xo_depth_change will free it */
4757             char *cp = alloca(len);
4758             memcpy(cp, name, len);
4759             name = cp;
4760         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4761             xo_failure(xop, "missing name without 'dtrt' mode");
4762             name = XO_FAILURE_NAME;
4763         }
4764     }
4765
4766     if (xo_style(xop) == XO_STYLE_JSON) {
4767         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4768             pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4769         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4770
4771         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
4772         rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
4773         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4774
4775     } else {
4776         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
4777         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4778     }
4779
4780     return rc;
4781 }
4782
4783 static int
4784 xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4785 {
4786     xop = xo_default(xop);
4787
4788     int rc = 0;
4789     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4790     const char *pre_nl = "";
4791
4792     flags |= xop->xo_flags;
4793
4794     if (name == NULL) {
4795         xo_failure(xop, "NULL passed for instance name");
4796         name = XO_FAILURE_NAME;
4797     }
4798
4799     switch (xo_style(xop)) {
4800     case XO_STYLE_XML:
4801         rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
4802
4803         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4804             rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
4805             xo_data_append(xop, xop->xo_attrs.xb_bufp,
4806                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4807             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4808         }
4809
4810         rc += xo_printf(xop, ">%s", ppn);
4811         break;
4812
4813     case XO_STYLE_JSON:
4814         xo_stack_set_flags(xop);
4815
4816         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4817             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
4818         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4819
4820         rc = xo_printf(xop, "%s%*s{%s",
4821                        pre_nl, xo_indent(xop), "", ppn);
4822         break;
4823     }
4824
4825     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
4826
4827     return rc;
4828 }
4829
4830 static int
4831 xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
4832 {
4833     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
4834 }
4835
4836 int
4837 xo_open_instance_h (xo_handle_t *xop, const char *name)
4838 {
4839     return xo_open_instance_hf(xop, 0, name);
4840 }
4841
4842 int
4843 xo_open_instance (const char *name)
4844 {
4845     return xo_open_instance_hf(NULL, 0, name);
4846 }
4847
4848 int
4849 xo_open_instance_hd (xo_handle_t *xop, const char *name)
4850 {
4851     return xo_open_instance_hf(xop, XOF_DTRT, name);
4852 }
4853
4854 int
4855 xo_open_instance_d (const char *name)
4856 {
4857     return xo_open_instance_hf(NULL, XOF_DTRT, name);
4858 }
4859
4860 static int
4861 xo_do_close_instance (xo_handle_t *xop, const char *name)
4862 {
4863     xop = xo_default(xop);
4864
4865     int rc = 0;
4866     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4867     const char *pre_nl = "";
4868
4869     if (name == NULL) {
4870         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4871
4872         name = xsp->xs_name;
4873         if (name) {
4874             int len = strlen(name) + 1;
4875             /* We need to make a local copy; xo_depth_change will free it */
4876             char *cp = alloca(len);
4877             memcpy(cp, name, len);
4878             name = cp;
4879         } else if (!(xsp->xs_flags & XSF_DTRT)) {
4880             xo_failure(xop, "missing name without 'dtrt' mode");
4881             name = XO_FAILURE_NAME;
4882         }
4883     }
4884
4885     switch (xo_style(xop)) {
4886     case XO_STYLE_XML:
4887         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
4888         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
4889         break;
4890
4891     case XO_STYLE_JSON:
4892         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
4893
4894         xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
4895         rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
4896         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4897         break;
4898
4899     case XO_STYLE_HTML:
4900     case XO_STYLE_TEXT:
4901         xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
4902         break;
4903     }
4904
4905     return rc;
4906 }
4907
4908 int
4909 xo_close_instance_h (xo_handle_t *xop, const char *name)
4910 {
4911     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
4912 }
4913
4914 int
4915 xo_close_instance (const char *name)
4916 {
4917     return xo_close_instance_h(NULL, name);
4918 }
4919
4920 int
4921 xo_close_instance_hd (xo_handle_t *xop)
4922 {
4923     return xo_close_instance_h(xop, NULL);
4924 }
4925
4926 int
4927 xo_close_instance_d (void)
4928 {
4929     return xo_close_instance_h(NULL, NULL);
4930 }
4931
4932 static int
4933 xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
4934 {
4935     xo_stack_t *xsp;
4936     int rc = 0;
4937     xo_xsf_flags_t flags;
4938
4939     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
4940         switch (xsp->xs_state) {
4941         case XSS_INIT:
4942             /* Nothing */
4943             rc = 0;
4944             break;
4945
4946         case XSS_OPEN_CONTAINER:
4947             rc = xo_do_close_container(xop, NULL);
4948             break;
4949
4950         case XSS_OPEN_LIST:
4951             rc = xo_do_close_list(xop, NULL);
4952             break;
4953
4954         case XSS_OPEN_INSTANCE:
4955             rc = xo_do_close_instance(xop, NULL);
4956             break;
4957
4958         case XSS_OPEN_LEAF_LIST:
4959             rc = xo_do_close_leaf_list(xop, NULL);
4960             break;
4961
4962         case XSS_MARKER:
4963             flags = xsp->xs_flags & XSF_MARKER_FLAGS;
4964             xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
4965             xop->xo_stack[xop->xo_depth].xs_flags |= flags;
4966             rc = 0;
4967             break;
4968         }
4969
4970         if (rc < 0)
4971             xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
4972     }
4973
4974     return 0;
4975 }
4976
4977 /*
4978  * This function is responsible for clearing out whatever is needed
4979  * to get to the desired state, if possible.
4980  */
4981 static int
4982 xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
4983 {
4984     xo_stack_t *xsp, *limit = NULL;
4985     int rc;
4986     xo_state_t need_state = new_state;
4987
4988     if (new_state == XSS_CLOSE_CONTAINER)
4989         need_state = XSS_OPEN_CONTAINER;
4990     else if (new_state == XSS_CLOSE_LIST)
4991         need_state = XSS_OPEN_LIST;
4992     else if (new_state == XSS_CLOSE_INSTANCE)
4993         need_state = XSS_OPEN_INSTANCE;
4994     else if (new_state == XSS_CLOSE_LEAF_LIST)
4995         need_state = XSS_OPEN_LEAF_LIST;
4996     else if (new_state == XSS_MARKER)
4997         need_state = XSS_MARKER;
4998     else
4999         return 0; /* Unknown or useless new states are ignored */
5000
5001     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
5002         /*
5003          * Marker's normally stop us from going any further, unless
5004          * we are popping a marker (new_state == XSS_MARKER).
5005          */
5006         if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
5007             if (name) {
5008                 xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
5009                            "not found '%s'",
5010                            xo_state_name(new_state),
5011                            xsp->xs_name, name);
5012                 return 0;
5013
5014             } else {
5015                 limit = xsp;
5016                 xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
5017             }
5018             break;
5019         }
5020         
5021         if (xsp->xs_state != need_state)
5022             continue;
5023
5024         if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
5025             continue;
5026
5027         limit = xsp;
5028         break;
5029     }
5030
5031     if (limit == NULL) {
5032         xo_failure(xop, "xo_%s can't find match for '%s'",
5033                    xo_state_name(new_state), name);
5034         return 0;
5035     }
5036
5037     rc = xo_do_close_all(xop, limit);
5038
5039     return rc;
5040 }
5041
5042 /*
5043  * We are in a given state and need to transition to the new state.
5044  */
5045 static int
5046 xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
5047                xo_state_t new_state)
5048 {
5049     xo_stack_t *xsp;
5050     int rc;
5051     int old_state, on_marker;
5052
5053     xop = xo_default(xop);
5054
5055     rc = 0;
5056     xsp = &xop->xo_stack[xop->xo_depth];
5057     old_state = xsp->xs_state;
5058     on_marker = (old_state == XSS_MARKER);
5059
5060     /* If there's a marker on top of the stack, we need to find a real state */
5061     while (old_state == XSS_MARKER) {
5062         if (xsp == xop->xo_stack)
5063             break;
5064         xsp -= 1;
5065         old_state = xsp->xs_state;
5066     }
5067
5068     /*
5069      * At this point, the list of possible states are:
5070      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
5071      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
5072      */
5073     switch (XSS_TRANSITION(old_state, new_state)) {
5074
5075     open_container:
5076     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
5077     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
5078     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
5079        rc = xo_do_open_container(xop, flags, name);
5080        break;
5081
5082     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
5083         if (on_marker)
5084             goto marker_prevents_close;
5085         rc = xo_do_close_list(xop, NULL);
5086         if (rc >= 0)
5087             goto open_container;
5088         break;
5089
5090     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
5091         if (on_marker)
5092             goto marker_prevents_close;
5093         rc = xo_do_close_leaf_list(xop, NULL);
5094         if (rc >= 0)
5095             goto open_container;
5096         break;
5097
5098     /*close_container:*/
5099     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
5100         if (on_marker)
5101             goto marker_prevents_close;
5102         rc = xo_do_close(xop, name, new_state);
5103         break;
5104
5105     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
5106         /* This is an exception for "xo --close" */
5107         rc = xo_do_close_container(xop, name);
5108         break;
5109
5110     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
5111     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
5112         if (on_marker)
5113             goto marker_prevents_close;
5114         rc = xo_do_close(xop, name, new_state);
5115         break;
5116
5117     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
5118         if (on_marker)
5119             goto marker_prevents_close;
5120         rc = xo_do_close_leaf_list(xop, NULL);
5121         if (rc >= 0)
5122             rc = xo_do_close(xop, name, new_state);
5123         break;
5124
5125     open_list:
5126     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
5127     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
5128     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
5129         rc = xo_do_open_list(xop, flags, name);
5130         break;
5131
5132     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
5133         if (on_marker)
5134             goto marker_prevents_close;
5135         rc = xo_do_close_list(xop, NULL);
5136         if (rc >= 0)
5137             goto open_list;
5138         break;
5139
5140     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
5141         if (on_marker)
5142             goto marker_prevents_close;
5143         rc = xo_do_close_leaf_list(xop, NULL);
5144         if (rc >= 0)
5145             goto open_list;
5146         break;
5147
5148     /*close_list:*/
5149     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
5150         if (on_marker)
5151             goto marker_prevents_close;
5152         rc = xo_do_close(xop, name, new_state);
5153         break;
5154
5155     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
5156     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
5157     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
5158     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
5159         rc = xo_do_close(xop, name, new_state);
5160         break;
5161
5162     open_instance:
5163     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
5164         rc = xo_do_open_instance(xop, flags, name);
5165         break;
5166
5167     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
5168     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
5169         rc = xo_do_open_list(xop, flags, name);
5170         if (rc >= 0)
5171             goto open_instance;
5172         break;
5173
5174     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
5175         if (on_marker) {
5176             rc = xo_do_open_list(xop, flags, name);
5177         } else {
5178             rc = xo_do_close_instance(xop, NULL);
5179         }
5180         if (rc >= 0)
5181             goto open_instance;
5182         break;
5183
5184     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
5185         if (on_marker)
5186             goto marker_prevents_close;
5187         rc = xo_do_close_leaf_list(xop, NULL);
5188         if (rc >= 0)
5189             goto open_instance;
5190         break;
5191
5192     /*close_instance:*/
5193     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
5194         if (on_marker)
5195             goto marker_prevents_close;
5196         rc = xo_do_close_instance(xop, name);
5197         break;
5198
5199     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
5200         /* This one makes no sense; ignore it */
5201         xo_failure(xop, "xo_close_instance ignored when called from "
5202                    "initial state ('%s')", name ?: "(unknown)");
5203         break;
5204
5205     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
5206     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
5207         if (on_marker)
5208             goto marker_prevents_close;
5209         rc = xo_do_close(xop, name, new_state);
5210         break;
5211
5212     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
5213         if (on_marker)
5214             goto marker_prevents_close;
5215         rc = xo_do_close_leaf_list(xop, NULL);
5216         if (rc >= 0)
5217             rc = xo_do_close(xop, name, new_state);
5218         break;
5219
5220     open_leaf_list:
5221     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
5222     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
5223     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
5224         rc = xo_do_open_leaf_list(xop, flags, name);
5225         break;
5226
5227     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
5228     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
5229         if (on_marker)
5230             goto marker_prevents_close;
5231         rc = xo_do_close_list(xop, NULL);
5232         if (rc >= 0)
5233             goto open_leaf_list;
5234         break;
5235
5236     /*close_leaf_list:*/
5237     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
5238         if (on_marker)
5239             goto marker_prevents_close;
5240         rc = xo_do_close_leaf_list(xop, name);
5241         break;
5242
5243     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
5244         /* Makes no sense; ignore */
5245         xo_failure(xop, "xo_close_leaf_list ignored when called from "
5246                    "initial state ('%s')", name ?: "(unknown)");
5247         break;
5248
5249     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
5250     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
5251     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
5252         if (on_marker)
5253             goto marker_prevents_close;
5254         rc = xo_do_close(xop, name, new_state);
5255         break;
5256
5257     /*emit:*/
5258     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
5259     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
5260         break;
5261
5262     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
5263         if (on_marker)
5264             goto marker_prevents_close;
5265         rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
5266         break;
5267
5268     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
5269         break;
5270
5271     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
5272         if (on_marker)
5273             goto marker_prevents_close;
5274         rc = xo_do_close_leaf_list(xop, NULL);
5275         break;
5276
5277     /*emit_leaf_list:*/
5278     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
5279     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
5280     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
5281         rc = xo_do_open_leaf_list(xop, flags, name);
5282         break;
5283
5284     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
5285         break;
5286
5287     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
5288         /*
5289          * We need to be backward compatible with the pre-xo_open_leaf_list
5290          * API, where both lists and leaf-lists were opened as lists.  So
5291          * if we find an open list that hasn't had anything written to it,
5292          * we'll accept it.
5293          */
5294         break;
5295
5296     default:
5297         xo_failure(xop, "unknown transition: (%u -> %u)",
5298                    xsp->xs_state, new_state);
5299     }
5300
5301     return rc;
5302
5303  marker_prevents_close:
5304     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
5305                xop->xo_stack[xop->xo_depth].xs_name,
5306                xo_state_name(old_state), xo_state_name(new_state));
5307     return -1;
5308 }
5309
5310 int
5311 xo_open_marker_h (xo_handle_t *xop, const char *name)
5312 {
5313     xop = xo_default(xop);
5314
5315     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
5316                     xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
5317
5318     return 0;
5319 }
5320
5321 int
5322 xo_open_marker (const char *name)
5323 {
5324     return xo_open_marker_h(NULL, name);
5325 }
5326
5327 int
5328 xo_close_marker_h (xo_handle_t *xop, const char *name)
5329 {
5330     xop = xo_default(xop);
5331
5332     return xo_do_close(xop, name, XSS_MARKER);
5333 }
5334
5335 int
5336 xo_close_marker (const char *name)
5337 {
5338     return xo_close_marker_h(NULL, name);
5339 }
5340
5341 void
5342 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
5343                xo_close_func_t close_func, xo_flush_func_t flush_func)
5344 {
5345     xop = xo_default(xop);
5346
5347     xop->xo_opaque = opaque;
5348     xop->xo_write = write_func;
5349     xop->xo_close = close_func;
5350     xop->xo_flush = flush_func;
5351 }
5352
5353 void
5354 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
5355 {
5356     xo_realloc = realloc_func;
5357     xo_free = free_func;
5358 }
5359
5360 int
5361 xo_flush_h (xo_handle_t *xop)
5362 {
5363     static char div_close[] = "</div>";
5364     int rc;
5365
5366     xop = xo_default(xop);
5367
5368     switch (xo_style(xop)) {
5369     case XO_STYLE_HTML:
5370         if (xop->xo_flags & XOF_DIV_OPEN) {
5371             xop->xo_flags &= ~XOF_DIV_OPEN;
5372             xo_data_append(xop, div_close, sizeof(div_close) - 1);
5373
5374             if (xop->xo_flags & XOF_PRETTY)
5375                 xo_data_append(xop, "\n", 1);
5376         }
5377         break;
5378     }
5379
5380     rc = xo_write(xop);
5381     if (rc >= 0 && xop->xo_flush)
5382         if (xop->xo_flush(xop->xo_opaque) < 0)
5383             return -1;
5384
5385     return rc;
5386 }
5387
5388 int
5389 xo_flush (void)
5390 {
5391     return xo_flush_h(NULL);
5392 }
5393
5394 int
5395 xo_finish_h (xo_handle_t *xop)
5396 {
5397     const char *cp = "";
5398     xop = xo_default(xop);
5399
5400     if (!(xop->xo_flags & XOF_NO_CLOSE))
5401         xo_do_close_all(xop, xop->xo_stack);
5402
5403     switch (xo_style(xop)) {
5404     case XO_STYLE_JSON:
5405         if (!(xop->xo_flags & XOF_NO_TOP)) {
5406             if (xop->xo_flags & XOF_TOP_EMITTED)
5407                 xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */
5408             else
5409                 cp = "{ ";
5410             xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
5411         }
5412         break;
5413     }
5414
5415     return xo_flush_h(xop);
5416 }
5417
5418 int
5419 xo_finish (void)
5420 {
5421     return xo_finish_h(NULL);
5422 }
5423
5424 /*
5425  * Generate an error message, such as would be displayed on stderr
5426  */
5427 void
5428 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
5429 {
5430     xop = xo_default(xop);
5431
5432     /*
5433      * If the format string doesn't end with a newline, we pop
5434      * one on ourselves.
5435      */
5436     int len = strlen(fmt);
5437     if (len > 0 && fmt[len - 1] != '\n') {
5438         char *newfmt = alloca(len + 2);
5439         memcpy(newfmt, fmt, len);
5440         newfmt[len] = '\n';
5441         newfmt[len] = '\0';
5442         fmt = newfmt;
5443     }
5444
5445     switch (xo_style(xop)) {
5446     case XO_STYLE_TEXT:
5447         vfprintf(stderr, fmt, vap);
5448         break;
5449
5450     case XO_STYLE_HTML:
5451         va_copy(xop->xo_vap, vap);
5452         
5453         xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
5454
5455         if (xop->xo_flags & XOF_DIV_OPEN)
5456             xo_line_close(xop);
5457
5458         xo_write(xop);
5459
5460         va_end(xop->xo_vap);
5461         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
5462         break;
5463
5464     case XO_STYLE_XML:
5465     case XO_STYLE_JSON:
5466         va_copy(xop->xo_vap, vap);
5467
5468         xo_open_container_h(xop, "error");
5469         xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
5470         xo_close_container_h(xop, "error");
5471
5472         va_end(xop->xo_vap);
5473         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
5474         break;
5475     }
5476 }
5477
5478 void
5479 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
5480 {
5481     va_list vap;
5482
5483     va_start(vap, fmt);
5484     xo_error_hv(xop, fmt, vap);
5485     va_end(vap);
5486 }
5487
5488 /*
5489  * Generate an error message, such as would be displayed on stderr
5490  */
5491 void
5492 xo_error (const char *fmt, ...)
5493 {
5494     va_list vap;
5495
5496     va_start(vap, fmt);
5497     xo_error_hv(NULL, fmt, vap);
5498     va_end(vap);
5499 }
5500
5501 int
5502 xo_parse_args (int argc, char **argv)
5503 {
5504     static char libxo_opt[] = "--libxo";
5505     char *cp;
5506     int i, save;
5507
5508     /* Save our program name for xo_err and friends */
5509     xo_program = argv[0];
5510     cp = strrchr(xo_program, '/');
5511     if (cp)
5512         xo_program = cp + 1;
5513
5514     for (save = i = 1; i < argc; i++) {
5515         if (argv[i] == NULL
5516             || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
5517             if (save != i)
5518                 argv[save] = argv[i];
5519             save += 1;
5520             continue;
5521         }
5522
5523         cp = argv[i] + sizeof(libxo_opt) - 1;
5524         if (*cp == 0) {
5525             cp = argv[++i];
5526             if (cp == 0) {
5527                 xo_warnx("missing libxo option");
5528                 return -1;
5529             }
5530                 
5531             if (xo_set_options(NULL, cp) < 0)
5532                 return -1;
5533         } else if (*cp == ':') {
5534             if (xo_set_options(NULL, cp) < 0)
5535                 return -1;
5536
5537         } else if (*cp == '=') {
5538             if (xo_set_options(NULL, ++cp) < 0)
5539                 return -1;
5540
5541         } else if (*cp == '-') {
5542             cp += 1;
5543             if (strcmp(cp, "check") == 0) {
5544                 exit(XO_HAS_LIBXO);
5545
5546             } else {
5547                 xo_warnx("unknown libxo option: '%s'", argv[i]);
5548                 return -1;
5549             }
5550         } else {
5551                 xo_warnx("unknown libxo option: '%s'", argv[i]);
5552             return -1;
5553         }
5554     }
5555
5556     argv[save] = NULL;
5557     return save;
5558 }
5559
5560 void
5561 xo_dump_stack (xo_handle_t *xop)
5562 {
5563     int i;
5564     xo_stack_t *xsp;
5565
5566     xop = xo_default(xop);
5567
5568     fprintf(stderr, "Stack dump:\n");
5569
5570     xsp = xop->xo_stack;
5571     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
5572         fprintf(stderr, "   [%d] %s '%s' [%x]\n",
5573                 i, xo_state_name(xsp->xs_state),
5574                 xsp->xs_name ?: "--", xsp->xs_flags);
5575     }
5576 }
5577
5578 void
5579 xo_set_program (const char *name)
5580 {
5581     xo_program = name;
5582 }
5583
5584 void
5585 xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
5586 {
5587     xop = xo_default(xop);
5588
5589     if (version == NULL || strchr(version, '"') != NULL)
5590         return;
5591
5592     switch (xo_style(xop)) {
5593     case XO_STYLE_XML:
5594         /* For XML, we record this as an attribute for the first tag */
5595         xo_attr_h(xop, "__version", "%s", version);
5596         break;
5597
5598     case XO_STYLE_JSON:
5599         {
5600             /*
5601              * For XML, we record the version string in our handle, and emit
5602              * it in xo_emit_top.
5603              */
5604             int len = strlen(version) + 1;
5605             xop->xo_version = xo_realloc(NULL, len);
5606             if (xop->xo_version)
5607                 memcpy(xop->xo_version, version, len);
5608         }
5609         break;
5610     }
5611 }
5612
5613 void
5614 xo_set_version (const char *version)
5615 {
5616     xo_set_version_h(NULL, version);
5617 }
5618
5619 #ifdef UNIT_TEST
5620 int
5621 main (int argc, char **argv)
5622 {
5623     static char base_grocery[] = "GRO";
5624     static char base_hardware[] = "HRD";
5625     struct item {
5626         const char *i_title;
5627         int i_sold;
5628         int i_instock;
5629         int i_onorder;
5630         const char *i_sku_base;
5631         int i_sku_num;
5632     };
5633     struct item list[] = {
5634         { "gum&this&that", 1412, 54, 10, base_grocery, 415 },
5635         { "<rope>", 85, 4, 2, base_hardware, 212 },
5636         { "ladder", 0, 2, 1, base_hardware, 517 },
5637         { "\"bolt\"", 4123, 144, 42, base_hardware, 632 },
5638         { "water\\blue", 17, 14, 2, base_grocery, 2331 },
5639         { NULL, 0, 0, 0, NULL, 0 }
5640     };
5641     struct item list2[] = {
5642         { "fish", 1321, 45, 1, base_grocery, 533 },
5643         { NULL, 0, 0, 0, NULL, 0 }
5644     };
5645     struct item *ip;
5646     xo_info_t info[] = {
5647         { "in-stock", "number", "Number of items in stock" },
5648         { "name", "string", "Name of the item" },
5649         { "on-order", "number", "Number of items on order" },
5650         { "sku", "string", "Stock Keeping Unit" },
5651         { "sold", "number", "Number of items sold" },
5652         { NULL, NULL, NULL },
5653     };
5654     int info_count = (sizeof(info) / sizeof(info[0])) - 1;
5655     
5656     argc = xo_parse_args(argc, argv);
5657     if (argc < 0)
5658         exit(1);
5659
5660     xo_set_info(NULL, info, info_count);
5661
5662     xo_open_container_h(NULL, "top");
5663
5664     xo_open_container("data");
5665     xo_open_list("item");
5666
5667     xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}"
5668             "{T:On Order/%12s}{T:SKU/%5s}\n");
5669
5670     for (ip = list; ip->i_title; ip++) {
5671         xo_open_instance("item");
5672
5673         xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}"
5674                 "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n",
5675                 ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder,
5676                 ip->i_sku_base, ip->i_sku_num);
5677
5678         xo_close_instance("item");
5679     }
5680
5681     xo_close_list("item");
5682     xo_close_container("data");
5683
5684     xo_emit("\n\n");
5685
5686     xo_open_container("data");
5687     xo_open_list("item");
5688
5689     for (ip = list; ip->i_title; ip++) {
5690         xo_open_instance("item");
5691
5692         xo_attr("fancy", "%s%d", "item", ip - list);
5693         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
5694         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n",
5695                 ip->i_sold, ip->i_sold ? ".0" : "", 44);
5696         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
5697         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
5698         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
5699                 ip->i_sku_base, ip->i_sku_num);
5700
5701         xo_close_instance("item");
5702     }
5703
5704     xo_close_list("item");
5705     xo_close_container("data");
5706
5707     xo_open_container("data");
5708     xo_open_list("item");
5709
5710     for (ip = list2; ip->i_title; ip++) {
5711         xo_open_instance("item");
5712
5713         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
5714         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}\n",
5715                 ip->i_sold, ip->i_sold ? ".0" : "");
5716         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
5717         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
5718         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
5719                 ip->i_sku_base, ip->i_sku_num);
5720
5721         xo_open_list("month");
5722
5723         const char *months[] = { "Jan", "Feb", "Mar", NULL };
5724         int discounts[] = { 10, 20, 25, 0 };
5725         int i;
5726         for (i = 0; months[i]; i++) {
5727             xo_open_instance("month");
5728             xo_emit("{P:       }"
5729                     "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n",
5730                     months[i], discounts[i]);
5731             xo_close_instance("month");
5732         }
5733         
5734         xo_close_list("month");
5735
5736         xo_close_instance("item");
5737     }
5738
5739     xo_close_list("item");
5740     xo_close_container("data");
5741
5742     xo_close_container_h(NULL, "top");
5743
5744     xo_finish();
5745
5746     return 0;
5747 }
5748 #endif /* UNIT_TEST */