]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libxo/libxo/libxo.c
Upgrade our copy of clang and llvm to 3.5.1 release. This is a bugfix
[FreeBSD/FreeBSD.git] / contrib / libxo / libxo / libxo.c
1 /*
2  * Copyright (c) 2014, 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 <stddef.h>
15 #include <wchar.h>
16 #include <locale.h>
17 #include <sys/types.h>
18 #include <stdarg.h>
19 #include <string.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <ctype.h>
23 #include <wctype.h>
24 #include <getopt.h>
25
26 #include "xoconfig.h"
27 #include "xo.h"
28 #include "xoversion.h"
29
30 const char xo_version[] = LIBXO_VERSION;
31 const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
32
33 #ifndef UNUSED
34 #define UNUSED __attribute__ ((__unused__))
35 #endif /* UNUSED */
36
37 #define XO_INDENT_BY 2  /* Amount to indent when pretty printing */
38 #define XO_BUFSIZ       (8*1024) /* Initial buffer size */
39 #define XO_DEPTH        512      /* Default stack depth */
40 #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
41
42 #define XO_FAILURE_NAME "failure"
43
44 /*
45  * xo_buffer_t: a memory buffer that can be grown as needed.  We
46  * use them for building format strings and output data.
47  */
48 typedef struct xo_buffer_s {
49     char *xb_bufp;              /* Buffer memory */
50     char *xb_curp;              /* Current insertion point */
51     int xb_size;                /* Size of buffer */
52 } xo_buffer_t;
53
54 /* Flags for the stack frame */
55 typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
56 #define XSF_NOT_FIRST   (1<<0)  /* Not the first element */
57 #define XSF_LIST        (1<<1)  /* Frame is a list */
58 #define XSF_INSTANCE    (1<<2)  /* Frame is an instance */
59 #define XSF_DTRT        (1<<3)  /* Save the name for DTRT mode */
60
61 /*
62  * xo_stack_t: As we open and close containers and levels, we
63  * create a stack of frames to track them.  This is needed for
64  * XOF_WARN and XOF_XPATH.
65  */
66 typedef struct xo_stack_s {
67     xo_xsf_flags_t xs_flags;    /* Flags for this frame */
68     char *xs_name;              /* Name (for XPath value) */
69     char *xs_keys;              /* XPath predicate for any key fields */
70 } xo_stack_t;
71
72 /*
73  * xo_handle_t: this is the principle data structure for libxo.
74  * It's used as a store for state, options, and content.
75  */
76 struct xo_handle_s {
77     unsigned long xo_flags;     /* Flags */
78     unsigned short xo_style;    /* XO_STYLE_* value */
79     unsigned short xo_indent;   /* Indent level (if pretty) */
80     unsigned short xo_indent_by; /* Indent amount (tab stop) */
81     xo_write_func_t xo_write;   /* Write callback */
82     xo_close_func_t xo_close;   /* Close callback */
83     xo_formatter_t xo_formatter; /* Custom formating function */
84     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
85     void *xo_opaque;            /* Opaque data for write function */
86     FILE *xo_fp;                /* XXX File pointer */
87     xo_buffer_t xo_data;        /* Output data */
88     xo_buffer_t xo_fmt;         /* Work area for building format strings */
89     xo_buffer_t xo_attrs;       /* Work area for building XML attributes */
90     xo_buffer_t xo_predicate;   /* Work area for building XPath predicates */
91     xo_stack_t *xo_stack;       /* Stack pointer */
92     int xo_depth;               /* Depth of stack */
93     int xo_stack_size;          /* Size of the stack */
94     xo_info_t *xo_info;         /* Info fields for all elements */
95     int xo_info_count;          /* Number of info entries */
96     va_list xo_vap;             /* Variable arguments (stdargs) */
97     char *xo_leading_xpath;     /* A leading XPath expression */
98     mbstate_t xo_mbstate;       /* Multi-byte character conversion state */
99     unsigned xo_anchor_offset;  /* Start of anchored text */
100     unsigned xo_anchor_columns; /* Number of columns since the start anchor */
101     int xo_anchor_min_width;    /* Desired width of anchored text */
102     unsigned xo_units_offset;   /* Start of units insertion point */
103     unsigned xo_columns;        /* Columns emitted during this xo_emit call */
104 };
105
106 /* Flags for formatting functions */
107 typedef unsigned long xo_xff_flags_t;
108 #define XFF_COLON       (1<<0)  /* Append a ":" */
109 #define XFF_COMMA       (1<<1)  /* Append a "," iff there's more output */
110 #define XFF_WS          (1<<2)  /* Append a blank */
111 #define XFF_ENCODE_ONLY (1<<3)  /* Only emit for encoding formats (xml and json) */
112
113 #define XFF_QUOTE       (1<<4)  /* Force quotes */
114 #define XFF_NOQUOTE     (1<<5)  /* Force no quotes */
115 #define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display formats (text and html) */
116 #define XFF_KEY         (1<<7)  /* Field is a key (for XPath) */
117
118 #define XFF_XML         (1<<8)  /* Force XML encoding style (for XPath) */
119 #define XFF_ATTR        (1<<9)  /* Escape value using attribute rules (XML) */
120 #define XFF_BLANK_LINE  (1<<10) /* Emit a blank line */
121 #define XFF_NO_OUTPUT   (1<<11) /* Do not make any output */
122
123 #define XFF_TRIM_WS     (1<<12) /* Trim whitespace off encoded values */
124 #define XFF_LEAF_LIST   (1<<13) /* A leaf-list (list of values) */
125 #define XFF_UNESCAPE    (1<<14) /* Need to printf-style unescape the value */
126
127 /*
128  * Normal printf has width and precision, which for strings operate as
129  * min and max number of columns.  But this depends on the idea that
130  * one byte means one column, which UTF-8 and multi-byte characters
131  * pitches on its ear.  It may take 40 bytes of data to populate 14
132  * columns, but we can't go off looking at 40 bytes of data without the
133  * caller's permission for fear/knowledge that we'll generate core files.
134  * 
135  * So we make three values, distinguishing between "max column" and
136  * "number of bytes that we will inspect inspect safely" We call the
137  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
138  *
139  * Under the "first do no harm" theory, we default "max" to "size".
140  * This is a reasonable assumption for folks that don't grok the
141  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
142  * be evil.
143  *
144  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
145  * columns of output, but will never look at more than 14 bytes of the
146  * input buffer.  This is mostly compatible with printf and caller's
147  * expectations.
148  *
149  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
150  * many bytes (or until a NUL is seen) are needed to fill 14 columns
151  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
152  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
153  * of output.
154  *
155  * It's fairly amazing how a good idea (handle all languages of the
156  * world) blows such a big hole in the bottom of the fairly weak boat
157  * that is C string handling.  The simplicity and completenesss are
158  * sunk in ways we haven't even begun to understand.
159  */
160
161 #define XF_WIDTH_MIN    0       /* Minimal width */
162 #define XF_WIDTH_SIZE   1       /* Maximum number of bytes to examine */
163 #define XF_WIDTH_MAX    2       /* Maximum width */
164 #define XF_WIDTH_NUM    3       /* Numeric fields in printf (min.size.max) */
165
166 /* Input and output string encodings */
167 #define XF_ENC_WIDE     1       /* Wide characters (wchar_t) */
168 #define XF_ENC_UTF8     2       /* UTF-8 */
169 #define XF_ENC_LOCALE   3       /* Current locale */
170
171 /*
172  * A place to parse printf-style format flags for each field
173  */
174 typedef struct xo_format_s {
175     unsigned char xf_fc;        /* Format character */
176     unsigned char xf_enc;       /* Encoding of the string (XF_ENC_*) */
177     unsigned char xf_skip;      /* Skip this field */
178     unsigned char xf_lflag;     /* 'l' (long) */
179     unsigned char xf_hflag;;    /* 'h' (half) */
180     unsigned char xf_jflag;     /* 'j' (intmax_t) */
181     unsigned char xf_tflag;     /* 't' (ptrdiff_t) */
182     unsigned char xf_zflag;     /* 'z' (size_t) */
183     unsigned char xf_qflag;     /* 'q' (quad_t) */
184     unsigned char xf_seen_minus; /* Seen a minus */
185     int xf_leading_zero;        /* Seen a leading zero (zero fill)  */
186     unsigned xf_dots;           /* Seen one or more '.'s */
187     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
188     unsigned xf_stars;          /* Seen one or more '*'s */
189     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
190 } xo_format_t;
191
192 /*
193  * We keep a default handle to allow callers to avoid having to
194  * allocate one.  Passing NULL to any of our functions will use
195  * this default handle.
196  */
197 static xo_handle_t xo_default_handle;
198 static int xo_default_inited;
199 static int xo_locale_inited;
200 static char *xo_program;
201
202 /*
203  * To allow libxo to be used in diverse environment, we allow the
204  * caller to give callbacks for memory allocation.
205  */
206 static xo_realloc_func_t xo_realloc = realloc;
207 static xo_free_func_t xo_free = free;
208
209 /* Forward declarations */
210 static void
211 xo_failure (xo_handle_t *xop, const char *fmt, ...);
212
213 static void
214 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
215                    const char *name, int nlen,
216                    const char *value, int vlen,
217                    const char *encoding, int elen);
218
219 static void
220 xo_anchor_clear (xo_handle_t *xop);
221
222 /*
223  * Callback to write data to a FILE pointer
224  */
225 static int
226 xo_write_to_file (void *opaque, const char *data)
227 {
228     FILE *fp = (FILE *) opaque;
229     return fprintf(fp, "%s", data);
230 }
231
232 /*
233  * Callback to close a file
234  */
235 static void
236 xo_close_file (void *opaque)
237 {
238     FILE *fp = (FILE *) opaque;
239     fclose(fp);
240 }
241
242 /*
243  * Initialize the contents of an xo_buffer_t.
244  */
245 static void
246 xo_buf_init (xo_buffer_t *xbp)
247 {
248     xbp->xb_size = XO_BUFSIZ;
249     xbp->xb_bufp = xo_realloc(NULL, xbp->xb_size);
250     xbp->xb_curp = xbp->xb_bufp;
251 }
252
253 /*
254  * Initialize the contents of an xo_buffer_t.
255  */
256 static void
257 xo_buf_cleanup (xo_buffer_t *xbp)
258 {
259     if (xbp->xb_bufp)
260         xo_free(xbp->xb_bufp);
261     bzero(xbp, sizeof(*xbp));
262 }
263
264 static int
265 xo_depth_check (xo_handle_t *xop, int depth)
266 {
267     xo_stack_t *xsp;
268
269     if (depth >= xop->xo_stack_size) {
270         depth += 16;
271         xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
272         if (xsp == NULL) {
273             xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
274             return 0;
275         }
276
277         int count = depth - xop->xo_stack_size;
278
279         bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
280         xop->xo_stack_size = depth;
281         xop->xo_stack = xsp;
282     }
283
284     return 0;
285 }
286
287 void
288 xo_no_setlocale (void)
289 {
290     xo_locale_inited = 1;       /* Skip initialization */
291 }
292
293 /*
294  * Initialize an xo_handle_t, using both static defaults and
295  * the global settings from the LIBXO_OPTIONS environment
296  * variable.
297  */
298 static void
299 xo_init_handle (xo_handle_t *xop)
300 {
301     xop->xo_opaque = stdout;
302     xop->xo_write = xo_write_to_file;
303
304     /*
305      * We need to initialize the locale, which isn't really pretty.
306      * Libraries should depend on their caller to set up the
307      * environment.  But we really can't count on the caller to do
308      * this, because well, they won't.  Trust me.
309      */
310     if (!xo_locale_inited) {
311         xo_locale_inited = 1;   /* Only do this once */
312
313         const char *cp = getenv("LC_CTYPE");
314         if (cp == NULL)
315             cp = getenv("LANG");
316         if (cp == NULL)
317             cp = getenv("LC_ALL");
318         if (cp == NULL)
319             cp = "UTF-8";       /* Optimistic? */
320         (void) setlocale(LC_CTYPE, cp);
321     }
322
323     /*
324      * Initialize only the xo_buffers we know we'll need; the others
325      * can be allocated as needed.
326      */
327     xo_buf_init(&xop->xo_data);
328     xo_buf_init(&xop->xo_fmt);
329
330     xop->xo_indent_by = XO_INDENT_BY;
331     xo_depth_check(xop, XO_DEPTH);
332
333 #if !defined(NO_LIBXO_OPTIONS)
334     if (!(xop->xo_flags & XOF_NO_ENV)) {
335         char *env = getenv("LIBXO_OPTIONS");
336         if (env)
337             xo_set_options(xop, env);
338     }
339 #endif /* NO_GETENV */
340 }
341
342 /*
343  * Initialize the default handle.
344  */
345 static void
346 xo_default_init (void)
347 {
348     xo_handle_t *xop = &xo_default_handle;
349
350     xo_init_handle(xop);
351
352     xo_default_inited = 1;
353 }
354
355 /*
356  * Does the buffer have room for the given number of bytes of data?
357  * If not, realloc the buffer to make room.  If that fails, we
358  * return 0 to tell the caller they are in trouble.
359  */
360 static int
361 xo_buf_has_room (xo_buffer_t *xbp, int len)
362 {
363     if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) {
364         int sz = xbp->xb_size + XO_BUFSIZ;
365         char *bp = xo_realloc(xbp->xb_bufp, sz);
366         if (bp == NULL) {
367             /*
368              * XXX If we wanted to put a stick XOF_ENOMEM on xop,
369              * this would be the place to do it.  But we'd need
370              * to churn the code to pass xop in here....
371              */
372             return 0;
373         }
374
375         xbp->xb_curp = bp + (xbp->xb_curp - xbp->xb_bufp);
376         xbp->xb_bufp = bp;
377         xbp->xb_size = sz;
378     }
379
380     return 1;
381 }
382
383 /*
384  * Cheap convenience function to return either the argument, or
385  * the internal handle, after it has been initialized.  The usage
386  * is:
387  *    xop = xo_default(xop);
388  */
389 static xo_handle_t *
390 xo_default (xo_handle_t *xop)
391 {
392     if (xop == NULL) {
393         if (xo_default_inited == 0)
394             xo_default_init();
395         xop = &xo_default_handle;
396     }
397
398     return xop;
399 }
400
401 /*
402  * Return the number of spaces we should be indenting.  If
403  * we are pretty-printing, theis is indent * indent_by.
404  */
405 static int
406 xo_indent (xo_handle_t *xop)
407 {
408     int rc = 0;
409
410     xop = xo_default(xop);
411
412     if (xop->xo_flags & XOF_PRETTY) {
413         rc = xop->xo_indent * xop->xo_indent_by;
414         if (xop->xo_flags & XOF_TOP_EMITTED)
415             rc += xop->xo_indent_by;
416     }
417
418     return rc;
419 }
420
421 static void
422 xo_buf_indent (xo_handle_t *xop, int indent)
423 {
424     xo_buffer_t *xbp = &xop->xo_data;
425
426     if (indent <= 0)
427         indent = xo_indent(xop);
428
429     if (!xo_buf_has_room(xbp, indent))
430         return;
431
432     memset(xbp->xb_curp, ' ', indent);
433     xbp->xb_curp += indent;
434 }
435
436 static char xo_xml_amp[] = "&amp;";
437 static char xo_xml_lt[] = "&lt;";
438 static char xo_xml_gt[] = "&gt;";
439 static char xo_xml_quot[] = "&quot;";
440
441 static int
442 xo_escape_xml (xo_buffer_t *xbp, int len, int attr)
443 {
444     int slen;
445     unsigned delta = 0;
446     char *cp, *ep, *ip;
447     const char *sp;
448
449     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
450         /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
451         if (*cp == '<')
452             delta += sizeof(xo_xml_lt) - 2;
453         else if (*cp == '>')
454             delta += sizeof(xo_xml_gt) - 2;
455         else if (*cp == '&')
456             delta += sizeof(xo_xml_amp) - 2;
457         else if (attr && *cp == '"')
458             delta += sizeof(xo_xml_quot) - 2;
459     }
460
461     if (delta == 0)             /* Nothing to escape; bail */
462         return len;
463
464     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
465         return 0;
466
467     ep = xbp->xb_curp;
468     cp = ep + len;
469     ip = cp + delta;
470     do {
471         cp -= 1;
472         ip -= 1;
473
474         if (*cp == '<')
475             sp = xo_xml_lt;
476         else if (*cp == '>')
477             sp = xo_xml_gt;
478         else if (*cp == '&')
479             sp = xo_xml_amp;
480         else if (attr && *cp == '"')
481             sp = xo_xml_quot;
482         else {
483             *ip = *cp;
484             continue;
485         }
486
487         slen = strlen(sp);
488         ip -= slen - 1;
489         memcpy(ip, sp, slen);
490         
491     } while (cp > ep && cp != ip);
492
493     return len + delta;
494 }
495
496 static int
497 xo_escape_json (xo_buffer_t *xbp, int len)
498 {
499     unsigned delta = 0;
500     char *cp, *ep, *ip;
501
502     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
503         if (*cp == '\\')
504             delta += 1;
505         else if (*cp == '"')
506             delta += 1;
507     }
508
509     if (delta == 0)             /* Nothing to escape; bail */
510         return len;
511
512     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
513         return 0;
514
515     ep = xbp->xb_curp;
516     cp = ep + len;
517     ip = cp + delta;
518     do {
519         cp -= 1;
520         ip -= 1;
521
522         if (*cp != '\\' && *cp != '"') {
523             *ip = *cp;
524             continue;
525         }
526
527         *ip-- = *cp;
528         *ip = '\\';
529         
530     } while (cp > ep && cp != ip);
531
532     return len + delta;
533 }
534
535 /*
536  * Append the given string to the given buffer
537  */
538 static void
539 xo_buf_append (xo_buffer_t *xbp, const char *str, int len)
540 {
541     if (!xo_buf_has_room(xbp, len))
542         return;
543
544     memcpy(xbp->xb_curp, str, len);
545     xbp->xb_curp += len;
546 }
547
548 static void
549 xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
550                const char *str, int len, xo_xff_flags_t flags)
551 {
552     if (!xo_buf_has_room(xbp, len))
553         return;
554
555     memcpy(xbp->xb_curp, str, len);
556
557     switch (xop->xo_style) {
558     case XO_STYLE_XML:
559     case XO_STYLE_HTML:
560         len = xo_escape_xml(xbp, len, (flags & XFF_ATTR));
561         break;
562
563     case XO_STYLE_JSON:
564         len = xo_escape_json(xbp, len);
565         break;
566     }
567
568     xbp->xb_curp += len;
569 }
570
571 /*
572  * Write the current contents of the data buffer using the handle's
573  * xo_write function.
574  */
575 static void
576 xo_write (xo_handle_t *xop)
577 {
578     xo_buffer_t *xbp = &xop->xo_data;
579
580     if (xbp->xb_curp != xbp->xb_bufp) {
581         xo_buf_append(xbp, "", 1); /* Append ending NUL */
582         xo_anchor_clear(xop);
583         xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
584         xbp->xb_curp = xbp->xb_bufp;
585     }
586
587     /* Turn off the flags that don't survive across writes */
588     xop->xo_flags &= ~(XOF_UNITS_PENDING);
589 }
590
591 /*
592  * Format arguments into our buffer.  If a custom formatter has been set,
593  * we use that to do the work; otherwise we vsnprintf().
594  */
595 static int
596 xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
597 {
598     va_list va_local;
599     int rc;
600     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
601
602     va_copy(va_local, vap);
603
604     if (xop->xo_formatter)
605         rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
606     else
607         rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
608
609     if (rc > xbp->xb_size) {
610         if (!xo_buf_has_room(xbp, rc)) {
611             va_end(va_local);
612             return -1;
613         }
614
615         /*
616          * After we call vsnprintf(), the stage of vap is not defined.
617          * We need to copy it before we pass.  Then we have to do our
618          * own logic below to move it along.  This is because the
619          * implementation can have va_list be a point (bsd) or a
620          * structure (macosx) or anything in between.
621          */
622
623         va_end(va_local);       /* Reset vap to the start */
624         va_copy(va_local, vap);
625
626         left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
627         if (xop->xo_formatter)
628             xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
629         else
630             rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
631     }
632     va_end(va_local);
633
634     return rc;
635 }
636
637 /*
638  * Print some data thru the handle.
639  */
640 static int
641 xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
642 {
643     xo_buffer_t *xbp = &xop->xo_data;
644     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
645     int rc;
646     va_list va_local;
647
648     va_copy(va_local, vap);
649
650     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
651
652     if (rc > xbp->xb_size) {
653         if (!xo_buf_has_room(xbp, rc)) {
654             va_end(va_local);
655             return -1;
656         }
657
658         va_end(va_local);       /* Reset vap to the start */
659         va_copy(va_local, vap);
660
661         left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
662         rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
663     }
664
665     va_end(va_local);
666
667     if (rc > 0)
668         xbp->xb_curp += rc;
669
670     return rc;
671 }
672
673 static int
674 xo_printf (xo_handle_t *xop, const char *fmt, ...)
675 {
676     int rc;
677     va_list vap;
678
679     va_start(vap, fmt);
680
681     rc = xo_printf_v(xop, fmt, vap);
682
683     va_end(vap);
684     return rc;
685 }
686
687 /*
688  * These next few function are make The Essential UTF-8 Ginsu Knife.
689  * Identify an input and output character, and convert it.
690  */
691 static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
692
693 static int
694 xo_is_utf8 (char ch)
695 {
696     return (ch & 0x80);
697 }
698
699 static int
700 xo_utf8_to_wc_len (const char *buf)
701 {
702     unsigned b = (unsigned char) *buf;
703     int len;
704
705     if ((b & 0x80) == 0x0)
706         len = 1;
707     else if ((b & 0xe0) == 0xc0)
708         len = 2;
709     else if ((b & 0xf0) == 0xe0)
710         len = 3;
711     else if ((b & 0xf8) == 0xf0)
712         len = 4;
713     else if ((b & 0xfc) == 0xf8)
714         len = 5;
715     else if ((b & 0xfe) == 0xfc)
716         len = 6;
717     else
718         len = -1;
719
720     return len;
721 }
722
723 static int
724 xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
725 {
726
727     unsigned b = (unsigned char) *buf;
728     int len, i;
729
730     len = xo_utf8_to_wc_len(buf);
731     if (len == -1) {
732         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
733         return -1;
734     }
735
736     if (len > bufsiz) {
737         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
738                    b, len, bufsiz);
739         return -1;
740     }
741
742     for (i = 2; i < len; i++) {
743         b = (unsigned char ) buf[i];
744         if ((b & 0xc0) != 0x80) {
745             xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
746             return -1;
747         }
748     }
749
750     return len;
751 }
752
753 /*
754  * Build a wide character from the input buffer; the number of
755  * bits we pull off the first character is dependent on the length,
756  * but we put 6 bits off all other bytes.
757  */
758 static wchar_t
759 xo_utf8_char (const char *buf, int len)
760 {
761     int i;
762     wchar_t wc;
763     const unsigned char *cp = (const unsigned char *) buf;
764
765     wc = *cp & xo_utf8_bits[len];
766     for (i = 1; i < len; i++) {
767         wc <<= 6;
768         wc |= cp[i] & 0x3f;
769         if ((cp[i] & 0xc0) != 0x80)
770             return (wchar_t) -1;
771     }
772
773     return wc;
774 }
775
776 /*
777  * Determine the number of bytes needed to encode a wide character.
778  */
779 static int
780 xo_utf8_emit_len (wchar_t wc)
781 {
782     int len;
783
784     if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
785         len = 1;
786     else if ((wc & ((1<<11) - 1)) == wc)
787         len = 2;
788     else if ((wc & ((1<<16) - 1)) == wc)
789         len = 3;
790     else if ((wc & ((1<<21) - 1)) == wc)
791         len = 4;
792     else if ((wc & ((1<<26) - 1)) == wc)
793         len = 5;
794     else
795         len = 6;
796
797     return len;
798 }
799
800 static void
801 xo_utf8_emit_char (char *buf, int len, wchar_t wc)
802 {
803     int i;
804
805     if (len == 1) { /* Simple case */
806         buf[0] = wc & 0x7f;
807         return;
808     }
809
810     for (i = len - 1; i >= 0; i--) {
811         buf[i] = 0x80 | (wc & 0x3f);
812         wc >>= 6;
813     }
814
815     buf[0] &= xo_utf8_bits[len];
816     buf[0] |= ~xo_utf8_bits[len] << 1;
817 }
818
819 static int
820 xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
821                                 const char *ibuf, int ilen)
822 {
823     wchar_t wc;
824     int len;
825
826     /*
827      * Build our wide character from the input buffer; the number of
828      * bits we pull off the first character is dependent on the length,
829      * but we put 6 bits off all other bytes.
830      */
831     wc = xo_utf8_char(ibuf, ilen);
832     if (wc == (wchar_t) -1) {
833         xo_failure(xop, "invalid utf-8 byte sequence");
834         return 0;
835     }
836
837     if (xop->xo_flags & XOF_NO_LOCALE) {
838         if (!xo_buf_has_room(xbp, ilen))
839             return 0;
840
841         memcpy(xbp->xb_curp, ibuf, ilen);
842         xbp->xb_curp += ilen;
843
844     } else {
845         if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
846             return 0;
847
848         bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
849         len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
850
851         if (len <= 0) {
852             xo_failure(xop, "could not convert wide char: %lx",
853                        (unsigned long) wc);
854             return 0;
855         }
856         xbp->xb_curp += len;
857     }
858
859     return wcwidth(wc);
860 }
861
862 static void
863 xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
864                       const char *cp, int len)
865 {
866     const char *sp = cp, *ep = cp + len;
867     unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
868     int slen;
869     int cols = 0;
870
871     for ( ; cp < ep; cp++) {
872         if (!xo_is_utf8(*cp)) {
873             cols += 1;
874             continue;
875         }
876
877         /*
878          * We're looking at a non-ascii UTF-8 character.
879          * First we copy the previous data.
880          * Then we need find the length and validate it.
881          * Then we turn it into a wide string.
882          * Then we turn it into a localized string.
883          * Then we repeat.  Isn't i18n fun?
884          */
885         if (sp != cp)
886             xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
887
888         slen = xo_buf_utf8_len(xop, cp, ep - cp);
889         if (slen <= 0) {
890             /* Bad data; back it all out */
891             xbp->xb_curp = xbp->xb_bufp + save_off;
892             return;
893         }
894
895         cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
896
897         /* Next time thru, we'll start at the next character */
898         cp += slen - 1;
899         sp = cp + 1;
900     }
901
902     /* Update column values */
903     if (xop->xo_flags & XOF_COLUMNS)
904         xop->xo_columns += cols;
905     if (xop->xo_flags & XOF_ANCHOR)
906         xop->xo_anchor_columns += cols;
907
908     /* Before we fall into the basic logic below, we need reset len */
909     len = ep - sp;
910     if (len != 0) /* Append trailing data */
911         xo_buf_append(xbp, sp, len);
912 }
913
914 /*
915  * Append the given string to the given buffer
916  */
917 static void
918 xo_data_append (xo_handle_t *xop, const char *str, int len)
919 {
920     xo_buf_append(&xop->xo_data, str, len);
921 }
922
923 /*
924  * Append the given string to the given buffer
925  */
926 static void
927 xo_data_escape (xo_handle_t *xop, const char *str, int len)
928 {
929     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
930 }
931
932 /*
933  * Generate a warning.  Normally, this is a text message written to
934  * standard error.  If the XOF_WARN_XML flag is set, then we generate
935  * XMLified content on standard output.
936  */
937 static void
938 xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
939              const char *fmt, va_list vap)
940 {
941     xop = xo_default(xop);
942     if (check_warn && !(xop->xo_flags & XOF_WARN))
943         return;
944
945     if (fmt == NULL)
946         return;
947
948     int len = strlen(fmt);
949     int plen = xo_program ? strlen(xo_program) : 0;
950     char *newfmt = alloca(len + 2 + plen + 2); /* newline, NUL, and ": " */
951
952     if (plen) {
953         memcpy(newfmt, xo_program, plen);
954         newfmt[plen++] = ':';
955         newfmt[plen++] = ' ';
956     }
957     memcpy(newfmt + plen, fmt, len);
958
959     newfmt[len + plen] = '\0';
960
961     if (xop->xo_flags & XOF_WARN_XML) {
962         static char err_open[] = "<error>";
963         static char err_close[] = "</error>";
964         static char msg_open[] = "<message>";
965         static char msg_close[] = "</message>";
966
967         xo_buffer_t *xbp = &xop->xo_data;
968
969         xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
970         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
971
972         va_list va_local;
973         va_copy(va_local, vap);
974
975         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
976         int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
977         if (rc > xbp->xb_size) {
978             if (!xo_buf_has_room(xbp, rc)) {
979                 va_end(va_local);
980                 return;
981             }
982
983             va_end(vap);        /* Reset vap to the start */
984             va_copy(vap, va_local);
985
986             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
987             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
988         }
989         va_end(va_local);
990
991         rc = xo_escape_xml(xbp, rc, 1);
992         xbp->xb_curp += rc;
993
994         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
995         xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
996
997         if (code > 0) {
998             const char *msg = strerror(code);
999             if (msg) {
1000                 xo_buf_append(xbp, ": ", 2);
1001                 xo_buf_append(xbp, msg, strlen(msg));
1002             }
1003         }
1004
1005         xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1006         xo_write(xop);
1007
1008     } else {
1009         vfprintf(stderr, newfmt, vap);
1010         fprintf(stderr, ": %s\n", strerror(code));
1011     }
1012 }
1013
1014 void
1015 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1016 {
1017     va_list vap;
1018
1019     va_start(vap, fmt);
1020     xo_warn_hcv(xop, code, 0, fmt, vap);
1021     va_end(vap);
1022 }
1023
1024 void
1025 xo_warn_c (int code, const char *fmt, ...)
1026 {
1027     va_list vap;
1028
1029     va_start(vap, fmt);
1030     xo_warn_hcv(NULL, 0, code, fmt, vap);
1031     va_end(vap);
1032 }
1033
1034 void
1035 xo_warn (const char *fmt, ...)
1036 {
1037     int code = errno;
1038     va_list vap;
1039
1040     va_start(vap, fmt);
1041     xo_warn_hcv(NULL, code, 0, fmt, vap);
1042     va_end(vap);
1043 }
1044
1045 void
1046 xo_warnx (const char *fmt, ...)
1047 {
1048     va_list vap;
1049
1050     va_start(vap, fmt);
1051     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1052     va_end(vap);
1053 }
1054
1055 void
1056 xo_err (int eval, const char *fmt, ...)
1057 {
1058     int code = errno;
1059     va_list vap;
1060
1061     va_start(vap, fmt);
1062     xo_warn_hcv(NULL, code, 0, fmt, vap);
1063     va_end(vap);
1064     xo_finish();
1065     exit(eval);
1066 }
1067
1068 void
1069 xo_errx (int eval, const char *fmt, ...)
1070 {
1071     va_list vap;
1072
1073     va_start(vap, fmt);
1074     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1075     va_end(vap);
1076     xo_finish();
1077     exit(eval);
1078 }
1079
1080 void
1081 xo_errc (int eval, int code, const char *fmt, ...)
1082 {
1083     va_list vap;
1084
1085     va_start(vap, fmt);
1086     xo_warn_hcv(NULL, code, 0, fmt, vap);
1087     va_end(vap);
1088     xo_finish();
1089     exit(eval);
1090 }
1091
1092 /*
1093  * Generate a warning.  Normally, this is a text message written to
1094  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1095  * XMLified content on standard output.
1096  */
1097 void
1098 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1099 {
1100     static char msg_open[] = "<message>";
1101     static char msg_close[] = "</message>";
1102     xo_buffer_t *xbp;
1103     int rc;
1104     va_list va_local;
1105
1106     xop = xo_default(xop);
1107
1108     if (fmt == NULL || *fmt == '\0')
1109         return;
1110
1111     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1112
1113     switch (xop->xo_style) {
1114     case XO_STYLE_XML:
1115         xbp = &xop->xo_data;
1116         if (xop->xo_flags & XOF_PRETTY)
1117             xo_buf_indent(xop, xop->xo_indent_by);
1118         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1119
1120         va_copy(va_local, vap);
1121
1122         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1123         rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1124         if (rc > xbp->xb_size) {
1125             if (!xo_buf_has_room(xbp, rc)) {
1126                 va_end(va_local);
1127                 return;
1128             }
1129
1130             va_end(vap);        /* Reset vap to the start */
1131             va_copy(vap, va_local);
1132
1133             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1134             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1135         }
1136         va_end(va_local);
1137
1138         rc = xo_escape_xml(xbp, rc, 1);
1139         xbp->xb_curp += rc;
1140
1141         if (need_nl && code > 0) {
1142             const char *msg = strerror(code);
1143             if (msg) {
1144                 xo_buf_append(xbp, ": ", 2);
1145                 xo_buf_append(xbp, msg, strlen(msg));
1146             }
1147         }
1148
1149         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1150         if (need_nl)
1151             xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1152         xo_write(xop);
1153         break;
1154
1155     case XO_STYLE_HTML:
1156         {
1157             char buf[BUFSIZ], *bp = buf, *cp;
1158             int bufsiz = sizeof(buf);
1159             int rc2;
1160
1161             va_copy(va_local, vap);
1162
1163             rc = vsnprintf(bp, bufsiz, fmt, va_local);
1164             if (rc > bufsiz) {
1165                 bufsiz = rc + BUFSIZ;
1166                 bp = alloca(bufsiz);
1167                 va_end(va_local);
1168                 va_copy(va_local, vap);
1169                 rc = vsnprintf(bp, bufsiz, fmt, va_local);
1170             }
1171             va_end(va_local);
1172             cp = bp + rc;
1173
1174             if (need_nl) {
1175                 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1176                                (code > 0) ? ": " : "",
1177                                (code > 0) ? strerror(code) : "");
1178                 if (rc2 > 0)
1179                     rc += rc2;
1180             }
1181
1182             xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
1183         }
1184         break;
1185
1186     case XO_STYLE_JSON:
1187         /* No meanings of representing messages in JSON */
1188         break;
1189
1190     case XO_STYLE_TEXT:
1191         rc = xo_printf_v(xop, fmt, vap);
1192         /*
1193          * XXX need to handle UTF-8 widths
1194          */
1195         if (rc > 0) {
1196             if (xop->xo_flags & XOF_COLUMNS)
1197                 xop->xo_columns += rc;
1198             if (xop->xo_flags & XOF_ANCHOR)
1199                 xop->xo_anchor_columns += rc;
1200         }
1201
1202         if (need_nl && code > 0) {
1203             const char *msg = strerror(code);
1204             if (msg) {
1205                 xo_printf(xop, ": %s", msg);
1206             }
1207         }
1208         if (need_nl)
1209             xo_printf(xop, "\n");
1210
1211         break;
1212     }
1213
1214     xo_flush_h(xop);
1215 }
1216
1217 void
1218 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1219 {
1220     va_list vap;
1221
1222     va_start(vap, fmt);
1223     xo_message_hcv(xop, code, fmt, vap);
1224     va_end(vap);
1225 }
1226
1227 void
1228 xo_message_c (int code, const char *fmt, ...)
1229 {
1230     va_list vap;
1231
1232     va_start(vap, fmt);
1233     xo_message_hcv(NULL, code, fmt, vap);
1234     va_end(vap);
1235 }
1236
1237 void
1238 xo_message (const char *fmt, ...)
1239 {
1240     int code = errno;
1241     va_list vap;
1242
1243     va_start(vap, fmt);
1244     xo_message_hcv(NULL, code, fmt, vap);
1245     va_end(vap);
1246 }
1247
1248 static void
1249 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1250 {
1251     if (!(xop->xo_flags & XOF_WARN))
1252         return;
1253
1254     va_list vap;
1255
1256     va_start(vap, fmt);
1257     xo_warn_hcv(xop, -1, 1, fmt, vap);
1258     va_end(vap);
1259 }
1260
1261 /**
1262  * Create a handle for use by later libxo functions.
1263  *
1264  * Note: normal use of libxo does not require a distinct handle, since
1265  * the default handle (used when NULL is passed) generates text on stdout.
1266  *
1267  * @style Style of output desired (XO_STYLE_* value)
1268  * @flags Set of XOF_* flags in use with this handle
1269  */
1270 xo_handle_t *
1271 xo_create (xo_style_t style, xo_xof_flags_t flags)
1272 {
1273     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1274
1275     if (xop) {
1276         bzero(xop, sizeof(*xop));
1277
1278         xop->xo_style  = style;
1279         xop->xo_flags = flags;
1280         xo_init_handle(xop);
1281     }
1282
1283     return xop;
1284 }
1285
1286 /**
1287  * Create a handle that will write to the given file.  Use
1288  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1289  * @fp FILE pointer to use
1290  * @style Style of output desired (XO_STYLE_* value)
1291  * @flags Set of XOF_* flags to use with this handle
1292  */
1293 xo_handle_t *
1294 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1295 {
1296     xo_handle_t *xop = xo_create(style, flags);
1297
1298     if (xop) {
1299         xop->xo_opaque = fp;
1300         xop->xo_write = xo_write_to_file;
1301         xop->xo_close = xo_close_file;
1302     }
1303
1304     return xop;
1305 }
1306
1307 /**
1308  * Release any resources held by the handle.
1309  * @xop XO handle to alter (or NULL for default handle)
1310  */
1311 void
1312 xo_destroy (xo_handle_t *xop_arg)
1313 {
1314     xo_handle_t *xop = xo_default(xop_arg);
1315
1316     if (xop->xo_close && (xop->xo_flags & XOF_CLOSE_FP))
1317         xop->xo_close(xop->xo_opaque);
1318
1319     xo_free(xop->xo_stack);
1320     xo_buf_cleanup(&xop->xo_data);
1321     xo_buf_cleanup(&xop->xo_fmt);
1322     xo_buf_cleanup(&xop->xo_predicate);
1323     xo_buf_cleanup(&xop->xo_attrs);
1324
1325     if (xop_arg == NULL) {
1326         bzero(&xo_default_handle, sizeof(&xo_default_handle));
1327         xo_default_inited = 0;
1328     } else
1329         xo_free(xop);
1330 }
1331
1332 /**
1333  * Record a new output style to use for the given handle (or default if
1334  * handle is NULL).  This output style will be used for any future output.
1335  *
1336  * @xop XO handle to alter (or NULL for default handle)
1337  * @style new output style (XO_STYLE_*)
1338  */
1339 void
1340 xo_set_style (xo_handle_t *xop, xo_style_t style)
1341 {
1342     xop = xo_default(xop);
1343     xop->xo_style = style;
1344 }
1345
1346 xo_style_t
1347 xo_get_style (xo_handle_t *xop)
1348 {
1349     xop = xo_default(xop);
1350     return xop->xo_style;
1351 }
1352
1353 static int
1354 xo_name_to_style (const char *name)
1355 {
1356     if (strcmp(name, "xml") == 0)
1357         return XO_STYLE_XML;
1358     else if (strcmp(name, "json") == 0)
1359         return XO_STYLE_JSON;
1360     else if (strcmp(name, "text") == 0)
1361         return XO_STYLE_TEXT;
1362     else if (strcmp(name, "html") == 0)
1363         return XO_STYLE_HTML;
1364
1365     return -1;
1366 }
1367
1368 /*
1369  * Convert string name to XOF_* flag value.
1370  * Not all are useful.  Or safe.  Or sane.
1371  */
1372 static unsigned
1373 xo_name_to_flag (const char *name)
1374 {
1375     if (strcmp(name, "pretty") == 0)
1376         return XOF_PRETTY;
1377     if (strcmp(name, "warn") == 0)
1378         return XOF_WARN;
1379     if (strcmp(name, "xpath") == 0)
1380         return XOF_XPATH;
1381     if (strcmp(name, "info") == 0)
1382         return XOF_INFO;
1383     if (strcmp(name, "warn-xml") == 0)
1384         return XOF_WARN_XML;
1385     if (strcmp(name, "columns") == 0)
1386         return XOF_COLUMNS;
1387     if (strcmp(name, "dtrt") == 0)
1388         return XOF_DTRT;
1389     if (strcmp(name, "flush") == 0)
1390         return XOF_FLUSH;
1391     if (strcmp(name, "keys") == 0)
1392         return XOF_KEYS;
1393     if (strcmp(name, "ignore-close") == 0)
1394         return XOF_IGNORE_CLOSE;
1395     if (strcmp(name, "not-first") == 0)
1396         return XOF_NOT_FIRST;
1397     if (strcmp(name, "no-locale") == 0)
1398         return XOF_NO_LOCALE;
1399     if (strcmp(name, "no-top") == 0)
1400         return XOF_NO_TOP;
1401     if (strcmp(name, "units") == 0)
1402         return XOF_UNITS;
1403     if (strcmp(name, "underscores") == 0)
1404         return XOF_UNDERSCORES;
1405
1406     return 0;
1407 }
1408
1409 int
1410 xo_set_style_name (xo_handle_t *xop, const char *name)
1411 {
1412     if (name == NULL)
1413         return -1;
1414
1415     int style = xo_name_to_style(name);
1416     if (style < 0)
1417         return -1;
1418
1419     xo_set_style(xop, style);
1420     return 0;
1421 }
1422
1423 /*
1424  * Set the options for a handle using a string of options
1425  * passed in.  The input is a comma-separated set of names
1426  * and optional values: "xml,pretty,indent=4"
1427  */
1428 int
1429 xo_set_options (xo_handle_t *xop, const char *input)
1430 {
1431     char *cp, *ep, *vp, *np, *bp;
1432     int style = -1, new_style, len, rc = 0;
1433     xo_xof_flags_t new_flag;
1434
1435     if (input == NULL)
1436         return 0;
1437
1438     xop = xo_default(xop);
1439
1440     /*
1441      * We support a simpler, old-school style of giving option
1442      * also, using a single character for each option.  It's
1443      * ideal for lazy people, such as myself.
1444      */
1445     if (*input == ':') {
1446         int sz;
1447
1448         for (input++ ; *input; input++) {
1449             switch (*input) {
1450             case 'f':
1451                 xop->xo_flags |= XOF_FLUSH;
1452                 break;
1453
1454             case 'H':
1455                 xop->xo_style = XO_STYLE_HTML;
1456                 break;
1457
1458             case 'I':
1459                 xop->xo_flags |= XOF_INFO;
1460                 break;
1461
1462             case 'i':
1463                 sz = strspn(input + 1, "0123456789");
1464                 if (sz > 0) {
1465                     xop->xo_indent_by = atoi(input + 1);
1466                     input += sz - 1;    /* Skip value */
1467                 }
1468                 break;
1469
1470             case 'k':
1471                 xop->xo_flags |= XOF_KEYS;
1472                 break;
1473
1474             case 'J':
1475                 xop->xo_style = XO_STYLE_JSON;
1476                 break;
1477
1478             case 'P':
1479                 xop->xo_flags |= XOF_PRETTY;
1480                 break;
1481
1482             case 'T':
1483                 xop->xo_style = XO_STYLE_TEXT;
1484                 break;
1485
1486             case 'U':
1487                 xop->xo_flags |= XOF_UNITS;
1488                 break;
1489
1490             case 'u':
1491                 xop->xo_flags |= XOF_UNDERSCORES;
1492                 break;
1493
1494             case 'W':
1495                 xop->xo_flags |= XOF_WARN;
1496                 break;
1497
1498             case 'X':
1499                 xop->xo_style = XO_STYLE_XML;
1500                 break;
1501
1502             case 'x':
1503                 xop->xo_flags |= XOF_XPATH;
1504                 break;
1505             }
1506         }
1507         return 0;
1508     }
1509
1510     len = strlen(input) + 1;
1511     bp = alloca(len);
1512     memcpy(bp, input, len);
1513
1514     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
1515         np = strchr(cp, ',');
1516         if (np)
1517             *np++ = '\0';
1518
1519         vp = strchr(cp, '=');
1520         if (vp)
1521             *vp++ = '\0';
1522
1523         new_style = xo_name_to_style(cp);
1524         if (new_style >= 0) {
1525             if (style >= 0)
1526                 xo_warnx("ignoring multiple styles: '%s'", cp);
1527             else
1528                 style = new_style;
1529         } else {
1530             new_flag = xo_name_to_flag(cp);
1531             if (new_flag != 0)
1532                 xop->xo_flags |= new_flag;
1533             else {
1534                 if (strcmp(cp, "indent") == 0) {
1535                     xop->xo_indent_by = atoi(vp);
1536                 } else {
1537                     xo_warnx("unknown option: '%s'", cp);
1538                     rc = -1;
1539                 }
1540             }
1541         }
1542     }
1543
1544     if (style > 0)
1545         xop->xo_style= style;
1546
1547     return rc;
1548 }
1549
1550 /**
1551  * Set one or more flags for a given handle (or default if handle is NULL).
1552  * These flags will affect future output.
1553  *
1554  * @xop XO handle to alter (or NULL for default handle)
1555  * @flags Flags to be set (XOF_*)
1556  */
1557 void
1558 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1559 {
1560     xop = xo_default(xop);
1561
1562     xop->xo_flags |= flags;
1563 }
1564
1565 xo_xof_flags_t
1566 xo_get_flags (xo_handle_t *xop)
1567 {
1568     xop = xo_default(xop);
1569
1570     return xop->xo_flags;
1571 }
1572
1573 /**
1574  * Record a leading prefix for the XPath we generate.  This allows the
1575  * generated data to be placed within an XML hierarchy but still have
1576  * accurate XPath expressions.
1577  *
1578  * @xop XO handle to alter (or NULL for default handle)
1579  * @path The XPath expression
1580  */
1581 void
1582 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
1583 {
1584     xop = xo_default(xop);
1585
1586     if (xop->xo_leading_xpath) {
1587         xo_free(xop->xo_leading_xpath);
1588         xop->xo_leading_xpath = NULL;
1589     }
1590
1591     if (path == NULL)
1592         return;
1593
1594     int len = strlen(path);
1595     xop->xo_leading_xpath = xo_realloc(NULL, len + 1);
1596     if (xop->xo_leading_xpath) {
1597         memcpy(xop->xo_leading_xpath, path, len + 1);
1598     }
1599 }
1600
1601 /**
1602  * Record the info data for a set of tags
1603  *
1604  * @xop XO handle to alter (or NULL for default handle)
1605  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
1606  * @count Number of entries in info (or -1 to count them ourselves)
1607  */
1608 void
1609 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
1610 {
1611     xop = xo_default(xop);
1612
1613     if (count < 0 && infop) {
1614         xo_info_t *xip;
1615
1616         for (xip = infop, count = 0; xip->xi_name; xip++, count++)
1617             continue;
1618     }
1619
1620     xop->xo_info = infop;
1621     xop->xo_info_count = count;
1622 }
1623
1624 /**
1625  * Set the formatter callback for a handle.  The callback should
1626  * return a newly formatting contents of a formatting instruction,
1627  * meaning the bits inside the braces.
1628  */
1629 void
1630 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
1631                   xo_checkpointer_t cfunc)
1632 {
1633     xop = xo_default(xop);
1634
1635     xop->xo_formatter = func;
1636     xop->xo_checkpointer = cfunc;
1637 }
1638
1639 /**
1640  * Clear one or more flags for a given handle (or default if handle is NULL).
1641  * These flags will affect future output.
1642  *
1643  * @xop XO handle to alter (or NULL for default handle)
1644  * @flags Flags to be cleared (XOF_*)
1645  */
1646 void
1647 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1648 {
1649     xop = xo_default(xop);
1650
1651     xop->xo_flags &= ~flags;
1652 }
1653
1654 static void
1655 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
1656 {
1657     static char div_open[] = "<div class=\"line\">";
1658     static char div_open_blank[] = "<div class=\"blank-line\">";
1659
1660     if (xop->xo_flags & XOF_DIV_OPEN)
1661         return;
1662
1663     if (xop->xo_style != XO_STYLE_HTML)
1664         return;
1665
1666     xop->xo_flags |= XOF_DIV_OPEN;
1667     if (flags & XFF_BLANK_LINE)
1668         xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
1669     else
1670         xo_data_append(xop, div_open, sizeof(div_open) - 1);
1671
1672     if (xop->xo_flags & XOF_PRETTY)
1673         xo_data_append(xop, "\n", 1);
1674 }
1675
1676 static void
1677 xo_line_close (xo_handle_t *xop)
1678 {
1679     static char div_close[] = "</div>";
1680
1681     switch (xop->xo_style) {
1682     case XO_STYLE_HTML:
1683         if (!(xop->xo_flags & XOF_DIV_OPEN))
1684             xo_line_ensure_open(xop, 0);
1685
1686         xop->xo_flags &= ~XOF_DIV_OPEN;
1687         xo_data_append(xop, div_close, sizeof(div_close) - 1);
1688
1689         if (xop->xo_flags & XOF_PRETTY)
1690             xo_data_append(xop, "\n", 1);
1691         break;
1692
1693     case XO_STYLE_TEXT:
1694         xo_data_append(xop, "\n", 1);
1695         break;
1696     }
1697 }
1698
1699 static int
1700 xo_info_compare (const void *key, const void *data)
1701 {
1702     const char *name = key;
1703     const xo_info_t *xip = data;
1704
1705     return strcmp(name, xip->xi_name);
1706 }
1707
1708
1709 static xo_info_t *
1710 xo_info_find (xo_handle_t *xop, const char *name, int nlen)
1711 {
1712     xo_info_t *xip;
1713     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
1714
1715     memcpy(cp, name, nlen);
1716     cp[nlen] = '\0';
1717
1718     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
1719                   sizeof(xop->xo_info[0]), xo_info_compare);
1720     return xip;
1721 }
1722
1723 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
1724
1725 /*
1726  * Check to see that the conversion is safe and sane.
1727  */
1728 static int
1729 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
1730 {
1731     switch (CONVERT(have_enc, need_enc)) {
1732     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
1733     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
1734     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
1735     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
1736     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
1737     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
1738         return 0;
1739
1740     default:
1741         xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
1742         return 1;
1743     }
1744 }
1745
1746 static int
1747 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
1748                          xo_xff_flags_t flags,
1749                          const wchar_t *wcp, const char *cp, int len, int max,
1750                          int need_enc, int have_enc)
1751 {
1752     int cols = 0;
1753     wchar_t wc = 0;
1754     int ilen, olen, width;
1755     int attr = (flags & XFF_ATTR);
1756     const char *sp;
1757
1758     if (len > 0 && !xo_buf_has_room(xbp, len))
1759         return 0;
1760
1761     for (;;) {
1762         if (len == 0)
1763             break;
1764
1765         if (cp) {
1766             if (*cp == '\0')
1767                 break;
1768             if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
1769                 cp += 1;
1770                 len -= 1;
1771             }
1772         }
1773
1774         if (wcp && *wcp == L'\0')
1775             break;
1776
1777         ilen = 0;
1778
1779         switch (have_enc) {
1780         case XF_ENC_WIDE:               /* Wide character */
1781             wc = *wcp++;
1782             ilen = 1;
1783             break;
1784
1785         case XF_ENC_UTF8:               /* UTF-8 */
1786             ilen = xo_utf8_to_wc_len(cp);
1787             if (ilen < 0) {
1788                 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
1789                 return -1;
1790             }
1791
1792             if (len > 0 && len < ilen) {
1793                 len = 0;        /* Break out of the loop */
1794                 continue;
1795             }
1796
1797             wc = xo_utf8_char(cp, ilen);
1798             if (wc == (wchar_t) -1) {
1799                 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
1800                            *cp, ilen);
1801                 return -1;
1802             }
1803             cp += ilen;
1804             break;
1805
1806         case XF_ENC_LOCALE:             /* Native locale */
1807             ilen = (len > 0) ? len : MB_LEN_MAX;
1808             ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
1809             if (ilen < 0) {             /* Invalid data; skip */
1810                 xo_failure(xop, "invalid mbs char: %02hhx", *cp);
1811                 continue;
1812             }
1813             if (ilen == 0) {            /* Hit a wide NUL character */
1814                 len = 0;
1815                 continue;
1816             }
1817
1818             cp += ilen;
1819             break;
1820         }
1821
1822         /* Reduce len, but not below zero */
1823         if (len > 0) {
1824             len -= ilen;
1825             if (len < 0)
1826                 len = 0;
1827         }
1828
1829         /*
1830          * Find the width-in-columns of this character, which must be done
1831          * in wide characters, since we lack a mbswidth() function.  If
1832          * it doesn't fit
1833          */
1834         width = wcwidth(wc);
1835         if (width < 0)
1836             width = iswcntrl(wc) ? 0 : 1;
1837
1838         if (xop->xo_style == XO_STYLE_TEXT || xop->xo_style == XO_STYLE_HTML) {
1839             if (max > 0 && cols + width > max)
1840                 break;
1841         }
1842
1843         switch (need_enc) {
1844         case XF_ENC_UTF8:
1845
1846             /* Output in UTF-8 needs to be escaped, based on the style */
1847             switch (xop->xo_style) {
1848             case XO_STYLE_XML:
1849             case XO_STYLE_HTML:
1850                 if (wc == '<')
1851                     sp = xo_xml_lt;
1852                 else if (wc == '>')
1853                     sp = xo_xml_gt;
1854                 else if (wc == '&')
1855                     sp = xo_xml_amp;
1856                 else if (attr && wc == '"')
1857                     sp = xo_xml_quot;
1858                 else
1859                     break;
1860
1861                 int slen = strlen(sp);
1862                 if (!xo_buf_has_room(xbp, slen - 1))
1863                     return -1;
1864
1865                 memcpy(xbp->xb_curp, sp, slen);
1866                 xbp->xb_curp += slen;
1867                 goto done_with_encoding; /* Need multi-level 'break' */
1868
1869             case XO_STYLE_JSON:
1870                 if (wc != '\\' && wc != '"')
1871                     break;
1872
1873                 if (!xo_buf_has_room(xbp, 2))
1874                     return -1;
1875
1876                 *xbp->xb_curp++ = '\\';
1877                 *xbp->xb_curp++ = wc & 0x7f;
1878                 goto done_with_encoding;
1879             }
1880
1881             olen = xo_utf8_emit_len(wc);
1882             if (olen < 0) {
1883                 xo_failure(xop, "ignoring bad length");
1884                 continue;
1885             }
1886
1887             if (!xo_buf_has_room(xbp, olen))
1888                 return -1;
1889
1890             xo_utf8_emit_char(xbp->xb_curp, olen, wc);
1891             xbp->xb_curp += olen;
1892             break;
1893
1894         case XF_ENC_LOCALE:
1895             if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1896                 return -1;
1897
1898             olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1899             if (olen <= 0) {
1900                 xo_failure(xop, "could not convert wide char: %lx",
1901                            (unsigned long) wc);
1902                 olen = 1;
1903                 width = 1;
1904                 *xbp->xb_curp++ = '?';
1905             } else
1906                 xbp->xb_curp += olen;
1907             break;
1908         }
1909
1910     done_with_encoding:
1911         cols += width;
1912     }
1913
1914     return cols;
1915 }
1916
1917 static int
1918 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
1919                   xo_format_t *xfp)
1920 {
1921     static char null[] = "(null)";
1922
1923     char *cp = NULL;
1924     wchar_t *wcp = NULL;
1925     int len, cols = 0, rc = 0;
1926     int off = xbp->xb_curp - xbp->xb_bufp, off2;
1927     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
1928         ? XF_ENC_LOCALE : XF_ENC_UTF8;
1929
1930     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
1931         return 0;
1932
1933     len = xfp->xf_width[XF_WIDTH_SIZE];
1934
1935     if (xfp->xf_enc == XF_ENC_WIDE) {
1936         wcp = va_arg(xop->xo_vap, wchar_t *);
1937         if (xfp->xf_skip)
1938             return 0;
1939
1940         /*
1941          * Dont' deref NULL; use the traditional "(null)" instead
1942          * of the more accurate "who's been a naughty boy, then?".
1943          */
1944         if (wcp == NULL) {
1945             cp = null;
1946             len = sizeof(null) - 1;
1947         }
1948
1949     } else {
1950         cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
1951         if (xfp->xf_skip)
1952             return 0;
1953
1954         /* Echo "Dont' deref NULL" logic */
1955         if (cp == NULL) {
1956             cp = null;
1957             len = sizeof(null) - 1;
1958         }
1959
1960         /*
1961          * Optimize the most common case, which is "%s".  We just
1962          * need to copy the complete string to the output buffer.
1963          */
1964         if (xfp->xf_enc == need_enc
1965                 && xfp->xf_width[XF_WIDTH_MIN] < 0
1966                 && xfp->xf_width[XF_WIDTH_SIZE] < 0
1967                 && xfp->xf_width[XF_WIDTH_MAX] < 0
1968                 && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) {
1969             len = strlen(cp);
1970             xo_buf_escape(xop, xbp, cp, len, flags);
1971
1972             /*
1973              * Our caller expects xb_curp left untouched, so we have
1974              * to reset it and return the number of bytes written to
1975              * the buffer.
1976              */
1977             off2 = xbp->xb_curp - xbp->xb_bufp;
1978             rc = off2 - off;
1979             xbp->xb_curp = xbp->xb_bufp + off;
1980
1981             return rc;
1982         }
1983     }
1984
1985     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
1986                                    xfp->xf_width[XF_WIDTH_MAX],
1987                                    need_enc, xfp->xf_enc);
1988     if (cols < 0)
1989         goto bail;
1990
1991     /*
1992      * xo_buf_append* will move xb_curp, so we save/restore it.
1993      */
1994     off2 = xbp->xb_curp - xbp->xb_bufp;
1995     rc = off2 - off;
1996     xbp->xb_curp = xbp->xb_bufp + off;
1997
1998     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
1999         /*
2000          * Find the number of columns needed to display the string.
2001          * If we have the original wide string, we just call wcswidth,
2002          * but if we did the work ourselves, then we need to do it.
2003          */
2004         int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2005         if (!xo_buf_has_room(xbp, delta))
2006             goto bail;
2007
2008         /*
2009          * If seen_minus, then pad on the right; otherwise move it so
2010          * we can pad on the left.
2011          */
2012         if (xfp->xf_seen_minus) {
2013             cp = xbp->xb_curp + rc;
2014         } else {
2015             cp = xbp->xb_curp;
2016             memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
2017         }
2018
2019         /* Set the padding */
2020         memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
2021         rc += delta;
2022         cols += delta;
2023     }
2024
2025     if (xop->xo_flags & XOF_COLUMNS)
2026         xop->xo_columns += cols;
2027     if (xop->xo_flags & XOF_ANCHOR)
2028         xop->xo_anchor_columns += cols;
2029
2030     return rc;
2031
2032  bail:
2033     xbp->xb_curp = xbp->xb_bufp + off;
2034     return 0;
2035 }
2036
2037 static void
2038 xo_data_append_content (xo_handle_t *xop, const char *str, int len)
2039 {
2040     int cols;
2041     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
2042         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2043
2044     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE,
2045                                    NULL, str, len, -1,
2046                                    need_enc, XF_ENC_UTF8);
2047
2048     if (xop->xo_flags & XOF_COLUMNS)
2049         xop->xo_columns += cols;
2050     if (xop->xo_flags & XOF_ANCHOR)
2051         xop->xo_anchor_columns += cols;
2052 }
2053
2054 static void
2055 xo_bump_width (xo_format_t *xfp, int digit)
2056 {
2057     int *ip = &xfp->xf_width[xfp->xf_dots];
2058
2059     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
2060 }
2061
2062 static int
2063 xo_trim_ws (xo_buffer_t *xbp, int len)
2064 {
2065     char *cp, *sp, *ep;
2066     int delta;
2067
2068     /* First trim leading space */
2069     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
2070         if (*cp != ' ')
2071             break;
2072     }
2073
2074     delta = cp - sp;
2075     if (delta) {
2076         len -= delta;
2077         memmove(sp, cp, len);
2078     }
2079
2080     /* Then trim off the end */
2081     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
2082         if (ep[-1] != ' ')
2083             break;
2084     }
2085
2086     delta = sp - ep;
2087     if (delta) {
2088         len -= delta;
2089         cp[len] = '\0';
2090     }
2091
2092     return len;
2093 }
2094
2095 static int
2096 xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
2097                 const char *fmt, int flen, xo_xff_flags_t flags)
2098 {
2099     xo_format_t xf;
2100     const char *cp, *ep, *sp, *xp = NULL;
2101     int rc, cols;
2102     int style = (flags & XFF_XML) ? XO_STYLE_XML : xop->xo_style;
2103     unsigned make_output = !(flags & XFF_NO_OUTPUT);
2104     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
2105         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2106     
2107     if (xbp == NULL)
2108         xbp = &xop->xo_data;
2109
2110     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
2111         if (*cp != '%') {
2112         add_one:
2113             if (xp == NULL)
2114                 xp = cp;
2115
2116             if (*cp == '\\' && cp[1] != '\0')
2117                 cp += 1;
2118             continue;
2119
2120         } if (cp + 1 < ep && cp[1] == '%') {
2121             cp += 1;
2122             goto add_one;
2123         }
2124
2125         if (xp) {
2126             if (make_output) {
2127                 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2128                                                NULL, xp, cp - xp, -1,
2129                                                need_enc, XF_ENC_UTF8);
2130                 if (xop->xo_flags & XOF_COLUMNS)
2131                     xop->xo_columns += cols;
2132                 if (xop->xo_flags & XOF_ANCHOR)
2133                     xop->xo_anchor_columns += cols;
2134             }
2135
2136             xp = NULL;
2137         }
2138
2139         bzero(&xf, sizeof(xf));
2140         xf.xf_leading_zero = -1;
2141         xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
2142
2143         /*
2144          * "%@" starts an XO-specific set of flags:
2145          *   @X@ - XML-only field; ignored if style isn't XML
2146          */
2147         if (cp[1] == '@') {
2148             for (cp += 2; cp < ep; cp++) {
2149                 if (*cp == '@') {
2150                     break;
2151                 }
2152                 if (*cp == '*') {
2153                     /*
2154                      * '*' means there's a "%*.*s" value in vap that
2155                      * we want to ignore
2156                      */
2157                     if (!(xop->xo_flags & XOF_NO_VA_ARG))
2158                         va_arg(xop->xo_vap, int);
2159                 }
2160             }
2161         }
2162
2163         /* Hidden fields are only visible to JSON and XML */
2164         if (xop->xo_flags & XFF_ENCODE_ONLY) {
2165             if (style != XO_STYLE_XML
2166                     && xop->xo_style != XO_STYLE_JSON)
2167                 xf.xf_skip = 1;
2168         } else if (xop->xo_flags & XFF_DISPLAY_ONLY) {
2169             if (style != XO_STYLE_TEXT
2170                     && xop->xo_style != XO_STYLE_HTML)
2171                 xf.xf_skip = 1;
2172         }
2173
2174         if (!make_output)
2175             xf.xf_skip = 1;
2176
2177         /*
2178          * Looking at one piece of a format; find the end and
2179          * call snprintf.  Then advance xo_vap on our own.
2180          *
2181          * Note that 'n', 'v', and '$' are not supported.
2182          */
2183         sp = cp;                /* Save start pointer */
2184         for (cp += 1; cp < ep; cp++) {
2185             if (*cp == 'l')
2186                 xf.xf_lflag += 1;
2187             else if (*cp == 'h')
2188                 xf.xf_hflag += 1;
2189             else if (*cp == 'j')
2190                 xf.xf_jflag += 1;
2191             else if (*cp == 't')
2192                 xf.xf_tflag += 1;
2193             else if (*cp == 'z')
2194                 xf.xf_zflag += 1;
2195             else if (*cp == 'q')
2196                 xf.xf_qflag += 1;
2197             else if (*cp == '.') {
2198                 if (++xf.xf_dots >= XF_WIDTH_NUM) {
2199                     xo_failure(xop, "Too many dots in format: '%s'", fmt);
2200                     return -1;
2201                 }
2202             } else if (*cp == '-')
2203                 xf.xf_seen_minus = 1;
2204             else if (isdigit((int) *cp)) {
2205                 if (xf.xf_leading_zero < 0)
2206                     xf.xf_leading_zero = (*cp == '0');
2207                 xo_bump_width(&xf, *cp - '0');
2208             } else if (*cp == '*') {
2209                 xf.xf_stars += 1;
2210                 xf.xf_star[xf.xf_dots] = 1;
2211             } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
2212                 break;
2213             else if (*cp == 'n' || *cp == 'v') {
2214                 xo_failure(xop, "unsupported format: '%s'", fmt);
2215                 return -1;
2216             }
2217         }
2218
2219         if (cp == ep)
2220             xo_failure(xop, "field format missing format character: %s",
2221                           fmt);
2222
2223         xf.xf_fc = *cp;
2224
2225         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2226             if (*cp == 's' || *cp == 'S') {
2227                 /* Handle "%*.*.*s" */
2228                 int s;
2229                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2230                     if (xf.xf_star[s]) {
2231                         xf.xf_width[s] = va_arg(xop->xo_vap, int);
2232                         
2233                         /* Normalize a negative width value */
2234                         if (xf.xf_width[s] < 0) {
2235                             if (s == 0) {
2236                                 xf.xf_width[0] = -xf.xf_width[0];
2237                                 xf.xf_seen_minus = 1;
2238                             } else
2239                                 xf.xf_width[s] = -1; /* Ignore negative values */
2240                         }
2241                     }
2242                 }
2243             }
2244         }
2245
2246         /* If no max is given, it defaults to size */
2247         if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
2248             xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
2249
2250         if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
2251             xf.xf_lflag = 1;
2252
2253         if (!xf.xf_skip) {
2254             xo_buffer_t *fbp = &xop->xo_fmt;
2255             int len = cp - sp + 1;
2256             if (!xo_buf_has_room(fbp, len + 1))
2257                 return -1;
2258
2259             char *newfmt = fbp->xb_curp;
2260             memcpy(newfmt, sp, len);
2261             newfmt[0] = '%';    /* If we skipped over a "%@...@s" format */
2262             newfmt[len] = '\0';
2263
2264             /*
2265              * Bad news: our strings are UTF-8, but the stock printf
2266              * functions won't handle field widths for wide characters
2267              * correctly.  So we have to handle this ourselves.
2268              */
2269             if (xop->xo_formatter == NULL
2270                     && (xf.xf_fc == 's' || xf.xf_fc == 'S')) {
2271                 xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S'))
2272                     ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
2273                 rc = xo_format_string(xop, xbp, flags, &xf);
2274
2275                 if ((flags & XFF_TRIM_WS)
2276                         && (xop->xo_style == XO_STYLE_XML
2277                                 || xop->xo_style == XO_STYLE_JSON))
2278                     rc = xo_trim_ws(xbp, rc);
2279
2280             } else {
2281                 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
2282
2283                 /*
2284                  * For XML and HTML, we need "&<>" processing; for JSON,
2285                  * it's quotes.  Text gets nothing.
2286                  */
2287                 switch (style) {
2288                 case XO_STYLE_XML:
2289                     if (flags & XFF_TRIM_WS)
2290                         columns = rc = xo_trim_ws(xbp, rc);
2291                     /* fall thru */
2292                 case XO_STYLE_HTML:
2293                     rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
2294                     break;
2295
2296                 case XO_STYLE_JSON:
2297                     if (flags & XFF_TRIM_WS)
2298                         columns = rc = xo_trim_ws(xbp, rc);
2299                     rc = xo_escape_json(xbp, rc);
2300                     break;
2301                 }
2302
2303                 /*
2304                  * We can assume all the data we've added is ASCII, so
2305                  * the columns and bytes are the same.  xo_format_string
2306                  * handles all the fancy string conversions and updates
2307                  * xo_anchor_columns accordingly.
2308                  */
2309                 if (xop->xo_flags & XOF_COLUMNS)
2310                     xop->xo_columns += columns;
2311                 if (xop->xo_flags & XOF_ANCHOR)
2312                     xop->xo_anchor_columns += columns;
2313             }
2314
2315             xbp->xb_curp += rc;
2316         }
2317
2318         /*
2319          * Now for the tricky part: we need to move the argument pointer
2320          * along by the amount needed.
2321          */
2322         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2323
2324             if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
2325                 /*
2326                  * The 'S' and 's' formats are normally handled in
2327                  * xo_format_string, but if we skipped it, then we
2328                  * need to pop it.
2329                  */
2330                 if (xf.xf_skip)
2331                     va_arg(xop->xo_vap, char *);
2332
2333             } else {
2334                 int s;
2335                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2336                     if (xf.xf_star[s])
2337                         va_arg(xop->xo_vap, int);
2338                 }
2339
2340                 if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
2341                     if (xf.xf_hflag > 1) {
2342                         va_arg(xop->xo_vap, int);
2343
2344                     } else if (xf.xf_hflag > 0) {
2345                         va_arg(xop->xo_vap, int);
2346
2347                     } else if (xf.xf_lflag > 1) {
2348                         va_arg(xop->xo_vap, unsigned long long);
2349
2350                     } else if (xf.xf_lflag > 0) {
2351                         va_arg(xop->xo_vap, unsigned long);
2352
2353                     } else if (xf.xf_jflag > 0) {
2354                         va_arg(xop->xo_vap, intmax_t);
2355
2356                     } else if (xf.xf_tflag > 0) {
2357                         va_arg(xop->xo_vap, ptrdiff_t);
2358
2359                     } else if (xf.xf_zflag > 0) {
2360                         va_arg(xop->xo_vap, size_t);
2361
2362                     } else if (xf.xf_qflag > 0) {
2363                         va_arg(xop->xo_vap, quad_t);
2364
2365                     } else {
2366                         va_arg(xop->xo_vap, int);
2367                     }
2368                 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
2369                     if (xf.xf_lflag)
2370                         va_arg(xop->xo_vap, long double);
2371                     else
2372                         va_arg(xop->xo_vap, double);
2373
2374                 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
2375                     va_arg(xop->xo_vap, wint_t);
2376
2377                 else if (xf.xf_fc == 'c')
2378                     va_arg(xop->xo_vap, int);
2379
2380                 else if (xf.xf_fc == 'p')
2381                     va_arg(xop->xo_vap, void *);
2382             }
2383         }
2384     }
2385
2386     if (xp) {
2387         if (make_output) {
2388             cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2389                                            NULL, xp, cp - xp, -1,
2390                                            need_enc, XF_ENC_UTF8);
2391             if (xop->xo_flags & XOF_COLUMNS)
2392                 xop->xo_columns += cols;
2393             if (xop->xo_flags & XOF_ANCHOR)
2394                 xop->xo_anchor_columns += cols;
2395         }
2396
2397         xp = NULL;
2398     }
2399
2400     return 0;
2401 }
2402
2403 static char *
2404 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
2405 {
2406     char *cp = encoding;
2407
2408     if (cp[0] != '%' || !isdigit((int) cp[1]))
2409         return encoding;
2410
2411     for (cp += 2; *cp; cp++) {
2412         if (!isdigit((int) *cp))
2413             break;
2414     }
2415
2416     cp -= 1;
2417     *cp = '%';
2418
2419     return cp;
2420 }
2421
2422 static void
2423 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
2424                    const char *name, int nlen,
2425                    const char *value, int vlen,
2426                    const char *encoding, int elen)
2427 {
2428     static char div_start[] = "<div class=\"";
2429     static char div_tag[] = "\" data-tag=\"";
2430     static char div_xpath[] = "\" data-xpath=\"";
2431     static char div_key[] = "\" data-key=\"key";
2432     static char div_end[] = "\">";
2433     static char div_close[] = "</div>";
2434
2435     /*
2436      * To build our XPath predicate, we need to save the va_list before
2437      * we format our data, and then restore it before we format the
2438      * xpath expression.
2439      * Display-only keys implies that we've got an encode-only key
2440      * elsewhere, so we don't use them from making predicates.
2441      */
2442     int need_predidate = 
2443         (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
2444          && (xop->xo_flags & XOF_XPATH));
2445
2446     if (need_predidate) {
2447         va_list va_local;
2448
2449         va_copy(va_local, xop->xo_vap);
2450         if (xop->xo_checkpointer)
2451             xop->xo_checkpointer(xop, xop->xo_vap, 0);
2452
2453         /*
2454          * Build an XPath predicate expression to match this key.
2455          * We use the format buffer.
2456          */
2457         xo_buffer_t *pbp = &xop->xo_predicate;
2458         pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
2459
2460         xo_buf_append(pbp, "[", 1);
2461         xo_buf_escape(xop, pbp, name, nlen, 0);
2462         if (xop->xo_flags & XOF_PRETTY)
2463             xo_buf_append(pbp, " = '", 4);
2464         else
2465             xo_buf_append(pbp, "='", 2);
2466
2467         /* The encoding format defaults to the normal format */
2468         if (encoding == NULL) {
2469             char *enc  = alloca(vlen + 1);
2470             memcpy(enc, value, vlen);
2471             enc[vlen] = '\0';
2472             encoding = xo_fix_encoding(xop, enc);
2473             elen = strlen(encoding);
2474         }
2475
2476         xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR);
2477
2478         xo_buf_append(pbp, "']", 2);
2479
2480         /* Now we record this predicate expression in the stack */
2481         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
2482         int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
2483         int dlen = pbp->xb_curp - pbp->xb_bufp;
2484
2485         char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
2486         if (cp) {
2487             memcpy(cp + olen, pbp->xb_bufp, dlen);
2488             cp[olen + dlen] = '\0';
2489             xsp->xs_keys = cp;
2490         }
2491
2492         /* Now we reset the xo_vap as if we were never here */
2493         va_end(xop->xo_vap);
2494         va_copy(xop->xo_vap, va_local);
2495         va_end(va_local);
2496         if (xop->xo_checkpointer)
2497             xop->xo_checkpointer(xop, xop->xo_vap, 1);
2498     }
2499
2500     if (flags & XFF_ENCODE_ONLY) {
2501         /*
2502          * Even if this is encode-only, we need to go thru the
2503          * work of formatting it to make sure the args are cleared
2504          * from xo_vap.
2505          */
2506         xo_format_data(xop, &xop->xo_data, encoding, elen,
2507                        flags | XFF_NO_OUTPUT);
2508         return;
2509     }
2510
2511     xo_line_ensure_open(xop, 0);
2512
2513     if (xop->xo_flags & XOF_PRETTY)
2514         xo_buf_indent(xop, xop->xo_indent_by);
2515
2516     xo_data_append(xop, div_start, sizeof(div_start) - 1);
2517     xo_data_append(xop, class, strlen(class));
2518
2519     if (name) {
2520         xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
2521         xo_data_escape(xop, name, nlen);
2522
2523         /*
2524          * Save the offset at which we'd place units.  See xo_format_units.
2525          */
2526         if (xop->xo_flags & XOF_UNITS) {
2527             xop->xo_flags |= XOF_UNITS_PENDING;
2528             /*
2529              * Note: We need the '+1' here because we know we've not
2530              * added the closing quote.  We add one, knowing the quote
2531              * will be added shortly.
2532              */
2533             xop->xo_units_offset =
2534                 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
2535         }
2536     }
2537
2538     if (name) {
2539         if (xop->xo_flags & XOF_XPATH) {
2540             int i;
2541             xo_stack_t *xsp;
2542
2543             xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
2544             if (xop->xo_leading_xpath)
2545                 xo_data_append(xop, xop->xo_leading_xpath,
2546                                strlen(xop->xo_leading_xpath));
2547
2548             for (i = 0; i <= xop->xo_depth; i++) {
2549                 xsp = &xop->xo_stack[i];
2550                 if (xsp->xs_name == NULL)
2551                     continue;
2552
2553                 xo_data_append(xop, "/", 1);
2554                 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
2555                 if (xsp->xs_keys) {
2556                     /* Don't show keys for the key field */
2557                     if (i != xop->xo_depth || !(flags & XFF_KEY))
2558                         xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
2559                 }
2560             }
2561
2562             xo_data_append(xop, "/", 1);
2563             xo_data_escape(xop, name, nlen);
2564         }
2565
2566         if ((xop->xo_flags & XOF_INFO) && xop->xo_info) {
2567             static char in_type[] = "\" data-type=\"";
2568             static char in_help[] = "\" data-help=\"";
2569
2570             xo_info_t *xip = xo_info_find(xop, name, nlen);
2571             if (xip) {
2572                 if (xip->xi_type) {
2573                     xo_data_append(xop, in_type, sizeof(in_type) - 1);
2574                     xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
2575                 }
2576                 if (xip->xi_help) {
2577                     xo_data_append(xop, in_help, sizeof(in_help) - 1);
2578                     xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
2579                 }
2580             }
2581         }
2582
2583         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS))
2584             xo_data_append(xop, div_key, sizeof(div_key) - 1);
2585     }
2586
2587     xo_data_append(xop, div_end, sizeof(div_end) - 1);
2588
2589     xo_format_data(xop, NULL, value, vlen, 0);
2590
2591     xo_data_append(xop, div_close, sizeof(div_close) - 1);
2592
2593     if (xop->xo_flags & XOF_PRETTY)
2594         xo_data_append(xop, "\n", 1);
2595 }
2596
2597 static void
2598 xo_format_text (xo_handle_t *xop, const char *str, int len)
2599 {
2600     switch (xop->xo_style) {
2601     case XO_STYLE_TEXT:
2602         xo_buf_append_locale(xop, &xop->xo_data, str, len);
2603         break;
2604
2605     case XO_STYLE_HTML:
2606         xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
2607         break;
2608     }
2609 }
2610
2611 static void
2612 xo_format_title (xo_handle_t *xop, const char *str, int len,
2613                  const char *fmt, int flen)
2614 {
2615     static char div_open[] = "<div class=\"title\">";
2616     static char div_close[] = "</div>";
2617
2618     switch (xop->xo_style) {
2619     case XO_STYLE_XML:
2620     case XO_STYLE_JSON:
2621         /*
2622          * Even though we don't care about text, we need to do
2623          * enough parsing work to skip over the right bits of xo_vap.
2624          */
2625         if (len == 0)
2626             xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2627         return;
2628     }
2629
2630     xo_buffer_t *xbp = &xop->xo_data;
2631     int start = xbp->xb_curp - xbp->xb_bufp;
2632     int left = xbp->xb_size - start;
2633     int rc;
2634     int need_enc = XF_ENC_LOCALE;
2635
2636     if (xop->xo_style == XO_STYLE_HTML) {
2637         need_enc = XF_ENC_UTF8;
2638         xo_line_ensure_open(xop, 0);
2639         if (xop->xo_flags & XOF_PRETTY)
2640             xo_buf_indent(xop, xop->xo_indent_by);
2641         xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
2642     }
2643
2644     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
2645     if (len) {
2646         char *newfmt = alloca(flen + 1);
2647         memcpy(newfmt, fmt, flen);
2648         newfmt[flen] = '\0';
2649
2650         /* If len is non-zero, the format string apply to the name */
2651         char *newstr = alloca(len + 1);
2652         memcpy(newstr, str, len);
2653         newstr[len] = '\0';
2654
2655         if (newstr[len - 1] == 's') {
2656             int cols;
2657             char *bp;
2658
2659             rc = snprintf(NULL, 0, newfmt, newstr);
2660             if (rc > 0) {
2661                 /*
2662                  * We have to do this the hard way, since we might need
2663                  * the columns.
2664                  */
2665                 bp = alloca(rc + 1);
2666                 rc = snprintf(bp, rc + 1, newfmt, newstr);
2667                 cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1,
2668                                                need_enc, XF_ENC_UTF8);
2669                 if (cols > 0) {
2670                     if (xop->xo_flags & XOF_COLUMNS)
2671                         xop->xo_columns += cols;
2672                     if (xop->xo_flags & XOF_ANCHOR)
2673                         xop->xo_anchor_columns += cols;
2674                 }
2675             }
2676             goto move_along;
2677
2678         } else {
2679             rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2680             if (rc > left) {
2681                 if (!xo_buf_has_room(xbp, rc))
2682                     return;
2683                 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
2684                 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2685             }
2686
2687             if (rc > 0) {
2688                 if (xop->xo_flags & XOF_COLUMNS)
2689                     xop->xo_columns += rc;
2690                 if (xop->xo_flags & XOF_ANCHOR)
2691                     xop->xo_anchor_columns += rc;
2692             }
2693         }
2694
2695     } else {
2696         xo_format_data(xop, NULL, fmt, flen, 0);
2697
2698         /* xo_format_data moved curp, so we need to reset it */
2699         rc = xbp->xb_curp - (xbp->xb_bufp + start);
2700         xbp->xb_curp = xbp->xb_bufp + start;
2701     }
2702
2703     /* If we're styling HTML, then we need to escape it */
2704     if (xop->xo_style == XO_STYLE_HTML) {
2705         rc = xo_escape_xml(xbp, rc, 0);
2706     }
2707
2708     if (rc > 0)
2709         xbp->xb_curp += rc;
2710
2711  move_along:
2712     if (xop->xo_style == XO_STYLE_HTML) {
2713         xo_data_append(xop, div_close, sizeof(div_close) - 1);
2714         if (xop->xo_flags & XOF_PRETTY)
2715             xo_data_append(xop, "\n", 1);
2716     }
2717 }
2718
2719 static void
2720 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
2721 {
2722     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
2723         xo_data_append(xop, ",", 1);
2724         if (!(flags & XFF_LEAF_LIST) && (xop->xo_flags & XOF_PRETTY))
2725             xo_data_append(xop, "\n", 1);
2726     } else
2727         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
2728 }
2729
2730 #if 0
2731 /* Useful debugging function */
2732 void
2733 xo_arg (xo_handle_t *xop);
2734 void
2735 xo_arg (xo_handle_t *xop)
2736 {
2737     xop = xo_default(xop);
2738     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
2739 }
2740 #endif /* 0 */
2741
2742 static void
2743 xo_format_value (xo_handle_t *xop, const char *name, int nlen,
2744                  const char *format, int flen,
2745                  const char *encoding, int elen, xo_xff_flags_t flags)
2746 {
2747     int pretty = (xop->xo_flags & XOF_PRETTY);
2748     int quote;
2749     xo_buffer_t *xbp;
2750
2751     switch (xop->xo_style) {
2752     case XO_STYLE_TEXT:
2753         if (flags & XFF_ENCODE_ONLY)
2754             flags |= XFF_NO_OUTPUT;
2755         xo_format_data(xop, NULL, format, flen, flags);
2756         break;
2757
2758     case XO_STYLE_HTML:
2759         if (flags & XFF_ENCODE_ONLY)
2760             flags |= XFF_NO_OUTPUT;
2761         xo_buf_append_div(xop, "data", flags, name, nlen,
2762                           format, flen, encoding, elen);
2763         break;
2764
2765     case XO_STYLE_XML:
2766         /*
2767          * Even though we're not making output, we still need to
2768          * let the formatting code handle the va_arg popping.
2769          */
2770         if (flags & XFF_DISPLAY_ONLY) {
2771             flags |= XFF_NO_OUTPUT;
2772             xo_format_data(xop, NULL, format, flen, flags);
2773             break;
2774         }
2775
2776         if (encoding) {
2777             format = encoding;
2778             flen = elen;
2779         } else {
2780             char *enc  = alloca(flen + 1);
2781             memcpy(enc, format, flen);
2782             enc[flen] = '\0';
2783             format = xo_fix_encoding(xop, enc);
2784             flen = strlen(format);
2785         }
2786
2787         if (nlen == 0) {
2788             static char missing[] = "missing-field-name";
2789             xo_failure(xop, "missing field name: %s", format);
2790             name = missing;
2791             nlen = sizeof(missing) - 1;
2792         }
2793
2794         if (pretty)
2795             xo_buf_indent(xop, -1);
2796         xo_data_append(xop, "<", 1);
2797         xo_data_escape(xop, name, nlen);
2798
2799         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
2800             xo_data_append(xop, xop->xo_attrs.xb_bufp,
2801                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
2802             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
2803         }
2804
2805         /*
2806          * We indicate 'key' fields using the 'key' attribute.  While
2807          * this is really committing the crime of mixing meta-data with
2808          * data, it's often useful.  Especially when format meta-data is
2809          * difficult to come by.
2810          */
2811         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) {
2812             static char attr[] = " key=\"key\"";
2813             xo_data_append(xop, attr, sizeof(attr) - 1);
2814         }
2815
2816         /*
2817          * Save the offset at which we'd place units.  See xo_format_units.
2818          */
2819         if (xop->xo_flags & XOF_UNITS) {
2820             xop->xo_flags |= XOF_UNITS_PENDING;
2821             xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
2822         }
2823
2824         xo_data_append(xop, ">", 1);
2825         xo_format_data(xop, NULL, format, flen, flags);
2826         xo_data_append(xop, "</", 2);
2827         xo_data_escape(xop, name, nlen);
2828         xo_data_append(xop, ">", 1);
2829         if (pretty)
2830             xo_data_append(xop, "\n", 1);
2831         break;
2832
2833     case XO_STYLE_JSON:
2834         if (flags & XFF_DISPLAY_ONLY) {
2835             flags |= XFF_NO_OUTPUT;
2836             xo_format_data(xop, NULL, format, flen, flags);
2837             break;
2838         }
2839
2840         if (encoding) {
2841             format = encoding;
2842             flen = elen;
2843         } else {
2844             char *enc  = alloca(flen + 1);
2845             memcpy(enc, format, flen);
2846             enc[flen] = '\0';
2847             format = xo_fix_encoding(xop, enc);
2848             flen = strlen(format);
2849         }
2850
2851         int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
2852
2853         xo_format_prep(xop, flags);
2854
2855         if (flags & XFF_QUOTE)
2856             quote = 1;
2857         else if (flags & XFF_NOQUOTE)
2858             quote = 0;
2859         else if (flen == 0) {
2860             quote = 0;
2861             format = "true";    /* JSON encodes empty tags as a boolean true */
2862             flen = 4;
2863         } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
2864             quote = 1;
2865         else
2866             quote = 0;
2867
2868         if (nlen == 0) {
2869             static char missing[] = "missing-field-name";
2870             xo_failure(xop, "missing field name: %s", format);
2871             name = missing;
2872             nlen = sizeof(missing) - 1;
2873         }
2874
2875         if (flags & XFF_LEAF_LIST) {
2876             if (first && pretty)
2877                 xo_buf_indent(xop, -1);
2878         } else {
2879             if (pretty)
2880                 xo_buf_indent(xop, -1);
2881             xo_data_append(xop, "\"", 1);
2882
2883             xbp = &xop->xo_data;
2884             int off = xbp->xb_curp - xbp->xb_bufp;
2885
2886             xo_data_escape(xop, name, nlen);
2887
2888             if (xop->xo_flags & XOF_UNDERSCORES) {
2889                 int now = xbp->xb_curp - xbp->xb_bufp;
2890                 for ( ; off < now; off++)
2891                     if (xbp->xb_bufp[off] == '-')
2892                         xbp->xb_bufp[off] = '_';
2893             }
2894             xo_data_append(xop, "\":", 2);
2895         }
2896
2897         if (pretty)
2898             xo_data_append(xop, " ", 1);
2899         if (quote)
2900             xo_data_append(xop, "\"", 1);
2901
2902         xo_format_data(xop, NULL, format, flen, flags);
2903
2904         if (quote)
2905             xo_data_append(xop, "\"", 1);
2906         break;
2907     }
2908 }
2909
2910 static void
2911 xo_format_content (xo_handle_t *xop, const char *class_name,
2912                    const char *xml_tag, int display_only,
2913                    const char *str, int len, const char *fmt, int flen)
2914 {
2915     switch (xop->xo_style) {
2916     case XO_STYLE_TEXT:
2917         if (len) {
2918             xo_data_append_content(xop, str, len);
2919         } else
2920             xo_format_data(xop, NULL, fmt, flen, 0);
2921         break;
2922
2923     case XO_STYLE_HTML:
2924         if (len == 0) {
2925             str = fmt;
2926             len = flen;
2927         }
2928
2929         xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0);
2930         break;
2931
2932     case XO_STYLE_XML:
2933         if (xml_tag) {
2934             if (len == 0) {
2935                 str = fmt;
2936                 len = flen;
2937             }
2938
2939             xo_open_container_h(xop, xml_tag);
2940             xo_format_value(xop, "message", 7, str, len, NULL, 0, 0);
2941             xo_close_container_h(xop, xml_tag);
2942
2943         } else {
2944             /*
2945              * Even though we don't care about labels, we need to do
2946              * enough parsing work to skip over the right bits of xo_vap.
2947              */
2948             if (len == 0)
2949                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2950         }
2951         break;
2952
2953     case XO_STYLE_JSON:
2954         /*
2955          * Even though we don't care about labels, we need to do
2956          * enough parsing work to skip over the right bits of xo_vap.
2957          */
2958         if (display_only) {
2959             if (len == 0)
2960                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2961             break;
2962         }
2963         /* XXX need schem for representing errors in JSON */
2964         break;
2965     }
2966 }
2967
2968 static void
2969 xo_format_units (xo_handle_t *xop, const char *str, int len,
2970                  const char *fmt, int flen)
2971 {
2972     static char units_start_xml[] = " units=\"";
2973     static char units_start_html[] = " data-units=\"";
2974
2975     if (!(xop->xo_flags & XOF_UNITS_PENDING)) {
2976         xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen);
2977         return;
2978     }
2979
2980     xo_buffer_t *xbp = &xop->xo_data;
2981     int start = xop->xo_units_offset;
2982     int stop = xbp->xb_curp - xbp->xb_bufp;
2983
2984     if (xop->xo_style == XO_STYLE_XML)
2985         xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
2986     else if (xop->xo_style == XO_STYLE_HTML)
2987         xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
2988     else
2989         return;
2990
2991     if (len)
2992         xo_data_append(xop, str, len);
2993     else
2994         xo_format_data(xop, NULL, fmt, flen, 0);
2995
2996     xo_buf_append(xbp, "\"", 1);
2997
2998     int now = xbp->xb_curp - xbp->xb_bufp;
2999     int delta = now - stop;
3000     if (delta < 0) {            /* Strange; no output to move */
3001         xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
3002         return;
3003     }
3004
3005     /*
3006      * Now we're in it alright.  We've need to insert the unit value
3007      * we just created into the right spot.  We make a local copy,
3008      * move it and then insert our copy.  We know there's room in the
3009      * buffer, since we're just moving this around.
3010      */
3011     char *buf = alloca(delta);
3012
3013     memcpy(buf, xbp->xb_bufp + stop, delta);
3014     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3015     memmove(xbp->xb_bufp + start, buf, delta);
3016 }
3017
3018 static int
3019 xo_find_width (xo_handle_t *xop, const char *str, int len,
3020                  const char *fmt, int flen)
3021 {
3022     long width = 0;
3023     char *bp;
3024     char *cp;
3025
3026     if (len) {
3027         bp = alloca(len + 1);   /* Make local NUL-terminated copy of str */
3028         memcpy(bp, str, len);
3029         bp[len] = '\0';
3030
3031         width = strtol(bp, &cp, 0);
3032         if (width == LONG_MIN || width == LONG_MAX
3033             || bp == cp || *cp != '\0' ) {
3034             width = 0;
3035             xo_failure(xop, "invalid width for anchor: '%s'", bp);
3036         }
3037     } else if (flen) {
3038         if (flen != 2 || strncmp("%d", fmt, flen) != 0)
3039             xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
3040         if (!(xop->xo_flags & XOF_NO_VA_ARG))
3041             width = va_arg(xop->xo_vap, int);
3042     }
3043
3044     return width;
3045 }
3046
3047 static void
3048 xo_anchor_clear (xo_handle_t *xop)
3049 {
3050     xop->xo_flags &= ~XOF_ANCHOR;
3051     xop->xo_anchor_offset = 0;
3052     xop->xo_anchor_columns = 0;
3053     xop->xo_anchor_min_width = 0;
3054 }
3055
3056 /*
3057  * An anchor is a marker used to delay field width implications.
3058  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
3059  * We are looking for output like "     1/4/5"
3060  *
3061  * To make this work, we record the anchor and then return to
3062  * format it when the end anchor tag is seen.
3063  */
3064 static void
3065 xo_anchor_start (xo_handle_t *xop, const char *str, int len,
3066                  const char *fmt, int flen)
3067 {
3068     if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML)
3069         return;
3070
3071     if (xop->xo_flags & XOF_ANCHOR)
3072         xo_failure(xop, "the anchor already recording is discarded");
3073
3074     xop->xo_flags |= XOF_ANCHOR;
3075     xo_buffer_t *xbp = &xop->xo_data;
3076     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
3077     xop->xo_anchor_columns = 0;
3078
3079     /*
3080      * Now we find the width, if possible.  If it's not there,
3081      * we'll get it on the end anchor.
3082      */
3083     xop->xo_anchor_min_width = xo_find_width(xop, str, len, fmt, flen);
3084 }
3085
3086 static void
3087 xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
3088                  const char *fmt, int flen)
3089 {
3090     if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML)
3091         return;
3092
3093     if (!(xop->xo_flags & XOF_ANCHOR)) {
3094         xo_failure(xop, "no start anchor");
3095         return;
3096     }
3097
3098     xop->xo_flags &= ~XOF_UNITS_PENDING;
3099
3100     int width = xo_find_width(xop, str, len, fmt, flen);
3101     if (width == 0)
3102         width = xop->xo_anchor_min_width;
3103
3104     if (width == 0)             /* No width given; nothing to do */
3105         goto done;
3106
3107     xo_buffer_t *xbp = &xop->xo_data;
3108     int start = xop->xo_anchor_offset;
3109     int stop = xbp->xb_curp - xbp->xb_bufp;
3110     int abswidth = (width > 0) ? width : -width;
3111     int blen = abswidth - xop->xo_anchor_columns;
3112
3113     if (blen <= 0)              /* Already over width */
3114         goto done;
3115
3116     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
3117         xo_failure(xop, "width over %u are not supported",
3118                    XO_MAX_ANCHOR_WIDTH);
3119         goto done;
3120     }
3121
3122     /* Make a suitable padding field and emit it */
3123     char *buf = alloca(blen);
3124     memset(buf, ' ', blen);
3125     xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0);
3126
3127     if (width < 0)              /* Already left justified */
3128         goto done;
3129
3130     int now = xbp->xb_curp - xbp->xb_bufp;
3131     int delta = now - stop;
3132     if (delta < 0)              /* Strange; no output to move */
3133         goto done;
3134
3135     /*
3136      * Now we're in it alright.  We've need to insert the padding data
3137      * we just created (which might be an HTML <div> or text) before
3138      * the formatted data.  We make a local copy, move it and then
3139      * insert our copy.  We know there's room in the buffer, since
3140      * we're just moving this around.
3141      */
3142     if (delta > blen)
3143         buf = alloca(delta);    /* Expand buffer if needed */
3144
3145     memcpy(buf, xbp->xb_bufp + stop, delta);
3146     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3147     memmove(xbp->xb_bufp + start, buf, delta);
3148
3149  done:
3150     xo_anchor_clear(xop);
3151 }
3152
3153 static int
3154 xo_do_emit (xo_handle_t *xop, const char *fmt)
3155 {
3156     int rc = 0;
3157     const char *cp, *sp, *ep, *basep;
3158     char *newp = NULL;
3159     int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0;
3160
3161     xop->xo_columns = 0;        /* Always reset it */
3162
3163     for (cp = fmt; *cp; ) {
3164         if (*cp == '\n') {
3165             xo_line_close(xop);
3166             xo_flush_h(xop);
3167             cp += 1;
3168             continue;
3169
3170         } else if (*cp == '{') {
3171             if (cp[1] == '{') { /* Start of {{escaped braces}} */
3172
3173                 cp += 2;        /* Skip over _both_ characters */
3174                 for (sp = cp; *sp; sp++) {
3175                     if (*sp == '}' && sp[1] == '}')
3176                         break;
3177                 }
3178                 if (*sp == '\0') {
3179                     xo_failure(xop, "missing closing '}}': %s", fmt);
3180                     return -1;
3181                 }
3182
3183                 xo_format_text(xop, cp, sp - cp);
3184
3185                 /* Move along the string, but don't run off the end */
3186                 if (*sp == '}' && sp[1] == '}')
3187                     sp += 2;
3188                 cp = *sp ? sp + 1 : sp;
3189                 continue;
3190             }
3191             /* Else fall thru to the code below */
3192
3193         } else {
3194             /* Normal text */
3195             for (sp = cp; *sp; sp++) {
3196                 if (*sp == '{' || *sp == '\n')
3197                     break;
3198             }
3199             xo_format_text(xop, cp, sp - cp);
3200
3201             cp = sp;
3202             continue;
3203         }
3204
3205         basep = cp + 1;
3206
3207         /*
3208          * We are looking at the start of a field definition.  The format is:
3209          *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
3210          * Modifiers are optional and include the following field types:
3211          *   'D': decoration; something non-text and non-data (colons, commmas)
3212          *   'E': error message
3213          *   'L': label; text preceding data
3214          *   'N': note; text following data
3215          *   'P': padding; whitespace
3216          *   'T': Title, where 'content' is a column title
3217          *   'U': Units, where 'content' is the unit label
3218          *   'V': value, where 'content' is the name of the field (the default)
3219          *   'W': warning message
3220          *   '[': start a section of anchored text
3221          *   ']': end a section of anchored text
3222          * The following flags are also supported:
3223          *   'c': flag: emit a colon after the label
3224          *   'd': field is only emitted for display formats (text and html)
3225          *   'e': field is only emitted for encoding formats (xml and json)
3226          *   'k': this field is a key, suitable for XPath predicates
3227          *   'l': a leaf-list, a simple list of values
3228          *   'n': no quotes around this field
3229          *   'q': add quotes around this field
3230          *   't': trim whitespace around the value
3231          *   'w': emit a blank after the label
3232          * The print-fmt and encode-fmt strings is the printf-style formating
3233          * for this data.  JSON and XML will use the encoding-fmt, if present.
3234          * If the encode-fmt is not provided, it defaults to the print-fmt.
3235          * If the print-fmt is not provided, it defaults to 's'.
3236          */
3237         unsigned ftype = 0, flags = 0;
3238         const char *content = NULL, *format = NULL, *encoding = NULL;
3239         int clen = 0, flen = 0, elen = 0;
3240
3241         for (sp = basep; sp; sp++) {
3242             if (*sp == ':' || *sp == '/' || *sp == '}')
3243                 break;
3244
3245             if (*sp == '\\') {
3246                 if (sp[1] == '\0') {
3247                     xo_failure(xop, "backslash at the end of string");
3248                     return -1;
3249                 }
3250                 sp += 1;
3251                 continue;
3252             }
3253
3254             switch (*sp) {
3255             case 'D':
3256             case 'E':
3257             case 'L':
3258             case 'N':
3259             case 'P':
3260             case 'T':
3261             case 'U':
3262             case 'V':
3263             case 'W':
3264             case '[':
3265             case ']':
3266                 if (ftype != 0) {
3267                     xo_failure(xop, "field descriptor uses multiple types: %s",
3268                                   fmt);
3269                     return -1;
3270                 }
3271                 ftype = *sp;
3272                 break;
3273
3274             case 'c':
3275                 flags |= XFF_COLON;
3276                 break;
3277
3278             case 'd':
3279                 flags |= XFF_DISPLAY_ONLY;
3280                 break;
3281
3282             case 'e':
3283                 flags |= XFF_ENCODE_ONLY;
3284                 break;
3285
3286             case 'k':
3287                 flags |= XFF_KEY;
3288                 break;
3289
3290             case 'l':
3291                 flags |= XFF_LEAF_LIST;
3292                 break;
3293
3294             case 'n':
3295                 flags |= XFF_NOQUOTE;
3296                 break;
3297
3298             case 'q':
3299                 flags |= XFF_QUOTE;
3300                 break;
3301
3302             case 't':
3303                 flags |= XFF_TRIM_WS;
3304                 break;
3305
3306             case 'w':
3307                 flags |= XFF_WS;
3308                 break;
3309
3310             default:
3311                 xo_failure(xop, "field descriptor uses unknown modifier: %s",
3312                               fmt);
3313                 /*
3314                  * No good answer here; a bad format will likely
3315                  * mean a core file.  We just return and hope
3316                  * the caller notices there's no output, and while
3317                  * that seems, well, bad.  There's nothing better.
3318                  */
3319                 return -1;
3320             }
3321         }
3322
3323         if (*sp == ':') {
3324             for (ep = ++sp; *sp; sp++) {
3325                 if (*sp == '}' || *sp == '/')
3326                     break;
3327                 if (*sp == '\\') {
3328                     if (sp[1] == '\0') {
3329                         xo_failure(xop, "backslash at the end of string");
3330                         return -1;
3331                     }
3332                     sp += 1;
3333                     continue;
3334                 }
3335             }
3336             if (ep != sp) {
3337                 clen = sp - ep;
3338                 content = ep;
3339             }
3340         } else {
3341             xo_failure(xop, "missing content (':'): %s", fmt);
3342             return -1;
3343         }
3344
3345         if (*sp == '/') {
3346             for (ep = ++sp; *sp; sp++) {
3347                 if (*sp == '}' || *sp == '/')
3348                     break;
3349                 if (*sp == '\\') {
3350                     if (sp[1] == '\0') {
3351                         xo_failure(xop, "backslash at the end of string");
3352                         return -1;
3353                     }
3354                     sp += 1;
3355                     continue;
3356                 }
3357             }
3358             flen = sp - ep;
3359             format = ep;
3360         }
3361
3362         if (*sp == '/') {
3363             for (ep = ++sp; *sp; sp++) {
3364                 if (*sp == '}')
3365                     break;
3366             }
3367             elen = sp - ep;
3368             encoding = ep;
3369         }
3370
3371         if (*sp == '}') {
3372             sp += 1;
3373         } else {
3374             xo_failure(xop, "missing closing '}': %s", fmt);
3375             return -1;
3376         }
3377
3378         if (format == NULL && ftype != '[' && ftype != ']' ) {
3379             format = "%s";
3380             flen = 2;
3381         }
3382
3383         if (ftype == 0 || ftype == 'V')
3384             xo_format_value(xop, content, clen, format, flen,
3385                             encoding, elen, flags);
3386         else if (ftype == 'D')
3387             xo_format_content(xop, "decoration", NULL, 1,
3388                               content, clen, format, flen);
3389         else if (ftype == 'E')
3390             xo_format_content(xop, "error", "error", 0,
3391                               content, clen, format, flen);
3392         else if (ftype == 'L')
3393             xo_format_content(xop, "label", NULL, 1,
3394                               content, clen, format, flen);
3395         else if (ftype == 'N')
3396             xo_format_content(xop, "note", NULL, 1,
3397                               content, clen, format, flen);
3398         else if (ftype == 'P')
3399             xo_format_content(xop, "padding", NULL, 1,
3400                               content, clen, format, flen);
3401         else if (ftype == 'T')
3402             xo_format_title(xop, content, clen, format, flen);
3403         else if (ftype == 'U') {
3404             if (flags & XFF_WS)
3405                 xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
3406             xo_format_units(xop, content, clen, format, flen);
3407         } else if (ftype == 'W')
3408             xo_format_content(xop, "warning", "warning", 0,
3409                               content, clen, format, flen);
3410         else if (ftype == '[')
3411             xo_anchor_start(xop, content, clen, format, flen);
3412         else if (ftype == ']')
3413             xo_anchor_stop(xop, content, clen, format, flen);
3414
3415         if (flags & XFF_COLON)
3416             xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0);
3417         if (ftype != 'U' && (flags & XFF_WS))
3418             xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
3419
3420         cp += sp - basep + 1;
3421         if (newp) {
3422             xo_free(newp);
3423             newp = NULL;
3424         }
3425     }
3426
3427     /* If we don't have an anchor, write the text out */
3428     if (flush && !(xop->xo_flags & XOF_ANCHOR))
3429         xo_write(xop);
3430
3431     return (rc < 0) ? rc : (int) xop->xo_columns;
3432 }
3433
3434 int
3435 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
3436 {
3437     int rc;
3438
3439     xop = xo_default(xop);
3440     va_copy(xop->xo_vap, vap);
3441     rc = xo_do_emit(xop, fmt);
3442     va_end(xop->xo_vap);
3443     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3444
3445     return rc;
3446 }
3447
3448 int
3449 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
3450 {
3451     int rc;
3452
3453     xop = xo_default(xop);
3454     va_start(xop->xo_vap, fmt);
3455     rc = xo_do_emit(xop, fmt);
3456     va_end(xop->xo_vap);
3457     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3458
3459     return rc;
3460 }
3461
3462 int
3463 xo_emit (const char *fmt, ...)
3464 {
3465     xo_handle_t *xop = xo_default(NULL);
3466     int rc;
3467
3468     va_start(xop->xo_vap, fmt);
3469     rc = xo_do_emit(xop, fmt);
3470     va_end(xop->xo_vap);
3471     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3472
3473     return rc;
3474 }
3475
3476 int
3477 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
3478 {
3479     const int extra = 5;        /* space, equals, quote, quote, and nul */
3480     xop = xo_default(xop);
3481
3482     if (xop->xo_style != XO_STYLE_XML)
3483         return 0;
3484
3485     int nlen = strlen(name);
3486     xo_buffer_t *xbp = &xop->xo_attrs;
3487
3488     if (!xo_buf_has_room(xbp, nlen + extra))
3489         return -1;
3490
3491     *xbp->xb_curp++ = ' ';
3492     memcpy(xbp->xb_curp, name, nlen);
3493     xbp->xb_curp += nlen;
3494     *xbp->xb_curp++ = '=';
3495     *xbp->xb_curp++ = '"';
3496
3497     int rc = xo_vsnprintf(xop, xbp, fmt, vap);
3498
3499     if (rc > 0) {
3500         rc = xo_escape_xml(xbp, rc, 1);
3501         xbp->xb_curp += rc;
3502     }
3503
3504     if (!xo_buf_has_room(xbp, 2))
3505         return -1;
3506
3507     *xbp->xb_curp++ = '"';
3508     *xbp->xb_curp = '\0';
3509
3510     return rc + nlen + extra;
3511 }
3512
3513 int
3514 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
3515 {
3516     int rc;
3517     va_list vap;
3518
3519     va_start(vap, fmt);
3520     rc = xo_attr_hv(xop, name, fmt, vap);
3521     va_end(vap);
3522
3523     return rc;
3524 }
3525
3526 int
3527 xo_attr (const char *name, const char *fmt, ...)
3528 {
3529     int rc;
3530     va_list vap;
3531
3532     va_start(vap, fmt);
3533     rc = xo_attr_hv(NULL, name, fmt, vap);
3534     va_end(vap);
3535
3536     return rc;
3537 }
3538
3539 static void
3540 xo_stack_set_flags (xo_handle_t *xop)
3541 {
3542     if (xop->xo_flags & XOF_NOT_FIRST) {
3543         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3544
3545         xsp->xs_flags |= XSF_NOT_FIRST;
3546         xop->xo_flags &= ~XOF_NOT_FIRST;
3547     }
3548 }
3549
3550 static void
3551 xo_depth_change (xo_handle_t *xop, const char *name,
3552                  int delta, int indent, xo_xsf_flags_t flags)
3553 {
3554     if (xop->xo_flags & XOF_DTRT)
3555         flags |= XSF_DTRT;
3556
3557     if (delta >= 0) {                   /* Push operation */
3558         if (xo_depth_check(xop, xop->xo_depth + delta))
3559             return;
3560
3561         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
3562         xsp->xs_flags = flags;
3563         xo_stack_set_flags(xop);
3564
3565         unsigned save = (xop->xo_flags & (XOF_XPATH | XOF_WARN | XOF_DTRT));
3566         save |= (flags & XSF_DTRT);
3567
3568         if (name && save) {
3569             int len = strlen(name) + 1;
3570             char *cp = xo_realloc(NULL, len);
3571             if (cp) {
3572                 memcpy(cp, name, len);
3573                 xsp->xs_name = cp;
3574             }
3575         }
3576
3577     } else {                    /* Pop operation */
3578         if (xop->xo_depth == 0) {
3579             if (!(xop->xo_flags & XOF_IGNORE_CLOSE))
3580                 xo_failure(xop, "close with empty stack: '%s'", name);
3581             return;
3582         }
3583
3584         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3585         if (xop->xo_flags & XOF_WARN) {
3586             const char *top = xsp->xs_name;
3587             if (top && strcmp(name, top) != 0) {
3588                 xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
3589                               name, top);
3590                 return;
3591             } 
3592             if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
3593                 xo_failure(xop, "list close on list confict: '%s'",
3594                               name);
3595                 return;
3596             }
3597             if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
3598                 xo_failure(xop, "list close on instance confict: '%s'",
3599                               name);
3600                 return;
3601             }
3602         }
3603
3604         if (xsp->xs_name) {
3605             xo_free(xsp->xs_name);
3606             xsp->xs_name = NULL;
3607         }
3608         if (xsp->xs_keys) {
3609             xo_free(xsp->xs_keys);
3610             xsp->xs_keys = NULL;
3611         }
3612     }
3613
3614     xop->xo_depth += delta;     /* Record new depth */
3615     xop->xo_indent += indent;
3616 }
3617
3618 void
3619 xo_set_depth (xo_handle_t *xop, int depth)
3620 {
3621     xop = xo_default(xop);
3622
3623     if (xo_depth_check(xop, depth))
3624         return;
3625
3626     xop->xo_depth += depth;
3627     xop->xo_indent += depth;
3628 }
3629
3630 static xo_xsf_flags_t
3631 xo_stack_flags (unsigned xflags)
3632 {
3633     if (xflags & XOF_DTRT)
3634         return XSF_DTRT;
3635     return 0;
3636 }
3637
3638 static int
3639 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
3640 {
3641     xop = xo_default(xop);
3642
3643     int rc = 0;
3644     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3645     const char *pre_nl = "";
3646
3647     if (name == NULL) {
3648         xo_failure(xop, "NULL passed for container name");
3649         name = XO_FAILURE_NAME;
3650     }
3651
3652     flags |= xop->xo_flags;     /* Pick up handle flags */
3653
3654     switch (xop->xo_style) {
3655     case XO_STYLE_XML:
3656         rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "",
3657                      name, ppn);
3658         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3659         break;
3660
3661     case XO_STYLE_JSON:
3662         xo_stack_set_flags(xop);
3663
3664         if (!(xop->xo_flags & XOF_NO_TOP)) {
3665             if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
3666                 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
3667                 xop->xo_flags |= XOF_TOP_EMITTED;
3668             }
3669         }
3670
3671         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3672             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3673         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3674
3675         rc = xo_printf(xop, "%s%*s\"%s\": {%s",
3676                        pre_nl, xo_indent(xop), "", name, ppn);
3677         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3678         break;
3679
3680     case XO_STYLE_HTML:
3681     case XO_STYLE_TEXT:
3682         xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags));
3683         break;
3684     }
3685
3686     return rc;
3687 }
3688
3689 int
3690 xo_open_container_h (xo_handle_t *xop, const char *name)
3691 {
3692     return xo_open_container_hf(xop, 0, name);
3693 }
3694
3695 int
3696 xo_open_container (const char *name)
3697 {
3698     return xo_open_container_hf(NULL, 0, name);
3699 }
3700
3701 int
3702 xo_open_container_hd (xo_handle_t *xop, const char *name)
3703 {
3704     return xo_open_container_hf(xop, XOF_DTRT, name);
3705 }
3706
3707 int
3708 xo_open_container_d (const char *name)
3709 {
3710     return xo_open_container_hf(NULL, XOF_DTRT, name);
3711 }
3712
3713 int
3714 xo_close_container_h (xo_handle_t *xop, const char *name)
3715 {
3716     xop = xo_default(xop);
3717
3718     int rc = 0;
3719     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3720     const char *pre_nl = "";
3721
3722     if (name == NULL) {
3723         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3724         if (!(xsp->xs_flags & XSF_DTRT))
3725             xo_failure(xop, "missing name without 'dtrt' mode");
3726
3727         name = xsp->xs_name;
3728         if (name) {
3729             int len = strlen(name) + 1;
3730             /* We need to make a local copy; xo_depth_change will free it */
3731             char *cp = alloca(len);
3732             memcpy(cp, name, len);
3733             name = cp;
3734         } else
3735             name = XO_FAILURE_NAME;
3736     }
3737
3738     switch (xop->xo_style) {
3739     case XO_STYLE_XML:
3740         xo_depth_change(xop, name, -1, -1, 0);
3741         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
3742         break;
3743
3744     case XO_STYLE_JSON:
3745         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3746         ppn = (xop->xo_depth <= 1) ? "\n" : "";
3747
3748         xo_depth_change(xop, name, -1, -1, 0);
3749         rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
3750         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3751         break;
3752
3753     case XO_STYLE_HTML:
3754     case XO_STYLE_TEXT:
3755         xo_depth_change(xop, name, -1, 0, 0);
3756         break;
3757     }
3758
3759     return rc;
3760 }
3761
3762 int
3763 xo_close_container (const char *name)
3764 {
3765     return xo_close_container_h(NULL, name);
3766 }
3767
3768 int
3769 xo_close_container_hd (xo_handle_t *xop)
3770 {
3771     return xo_close_container_h(xop, NULL);
3772 }
3773
3774 int
3775 xo_close_container_d (void)
3776 {
3777     return xo_close_container_h(NULL, NULL);
3778 }
3779
3780 static int
3781 xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
3782 {
3783     xop = xo_default(xop);
3784
3785     if (xop->xo_style != XO_STYLE_JSON)
3786         return 0;
3787
3788     int rc = 0;
3789     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3790     const char *pre_nl = "";
3791
3792     if (!(xop->xo_flags & XOF_NO_TOP)) {
3793         if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
3794             xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
3795             xop->xo_flags |= XOF_TOP_EMITTED;
3796         }
3797     }
3798
3799     if (name == NULL) {
3800         xo_failure(xop, "NULL passed for list name");
3801         name = XO_FAILURE_NAME;
3802     }
3803
3804     xo_stack_set_flags(xop);
3805
3806     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3807         pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3808     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3809
3810     rc = xo_printf(xop, "%s%*s\"%s\": [%s",
3811                    pre_nl, xo_indent(xop), "", name, ppn);
3812     xo_depth_change(xop, name, 1, 1, XSF_LIST | xo_stack_flags(flags));
3813
3814     return rc;
3815 }
3816
3817 int
3818 xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
3819 {
3820     return xo_open_list_hf(xop, 0, name);
3821 }
3822
3823 int
3824 xo_open_list (const char *name)
3825 {
3826     return xo_open_list_hf(NULL, 0, name);
3827 }
3828
3829 int
3830 xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
3831 {
3832     return xo_open_list_hf(xop, XOF_DTRT, name);
3833 }
3834
3835 int
3836 xo_open_list_d (const char *name)
3837 {
3838     return xo_open_list_hf(NULL, XOF_DTRT, name);
3839 }
3840
3841 int
3842 xo_close_list_h (xo_handle_t *xop, const char *name)
3843 {
3844     int rc = 0;
3845     const char *pre_nl = "";
3846
3847     xop = xo_default(xop);
3848
3849     if (xop->xo_style != XO_STYLE_JSON)
3850         return 0;
3851
3852     if (name == NULL) {
3853         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3854         if (!(xsp->xs_flags & XSF_DTRT))
3855             xo_failure(xop, "missing name without 'dtrt' mode");
3856
3857         name = xsp->xs_name;
3858         if (name) {
3859             int len = strlen(name) + 1;
3860             /* We need to make a local copy; xo_depth_change will free it */
3861             char *cp = alloca(len);
3862             memcpy(cp, name, len);
3863             name = cp;
3864         } else
3865             name = XO_FAILURE_NAME;
3866     }
3867
3868     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3869         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3870     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3871
3872     xo_depth_change(xop, name, -1, -1, XSF_LIST);
3873     rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
3874     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3875
3876     return rc;
3877 }
3878
3879 int
3880 xo_close_list (const char *name)
3881 {
3882     return xo_close_list_h(NULL, name);
3883 }
3884
3885 int
3886 xo_close_list_hd (xo_handle_t *xop)
3887 {
3888     return xo_close_list_h(xop, NULL);
3889 }
3890
3891 int
3892 xo_close_list_d (void)
3893 {
3894     return xo_close_list_h(NULL, NULL);
3895 }
3896
3897 static int
3898 xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
3899 {
3900     xop = xo_default(xop);
3901
3902     int rc = 0;
3903     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3904     const char *pre_nl = "";
3905
3906     flags |= xop->xo_flags;
3907
3908     if (name == NULL) {
3909         xo_failure(xop, "NULL passed for instance name");
3910         name = XO_FAILURE_NAME;
3911     }
3912
3913     switch (xop->xo_style) {
3914     case XO_STYLE_XML:
3915         rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", name, ppn);
3916         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3917         break;
3918
3919     case XO_STYLE_JSON:
3920         xo_stack_set_flags(xop);
3921
3922         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3923             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3924         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3925
3926         rc = xo_printf(xop, "%s%*s{%s",
3927                        pre_nl, xo_indent(xop), "", ppn);
3928         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3929         break;
3930
3931     case XO_STYLE_HTML:
3932     case XO_STYLE_TEXT:
3933         xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags));
3934         break;
3935     }
3936
3937     return rc;
3938 }
3939
3940 int
3941 xo_open_instance_h (xo_handle_t *xop, const char *name)
3942 {
3943     return xo_open_instance_hf(xop, 0, name);
3944 }
3945
3946 int
3947 xo_open_instance (const char *name)
3948 {
3949     return xo_open_instance_hf(NULL, 0, name);
3950 }
3951
3952 int
3953 xo_open_instance_hd (xo_handle_t *xop, const char *name)
3954 {
3955     return xo_open_instance_hf(xop, XOF_DTRT, name);
3956 }
3957
3958 int
3959 xo_open_instance_d (const char *name)
3960 {
3961     return xo_open_instance_hf(NULL, XOF_DTRT, name);
3962 }
3963
3964 int
3965 xo_close_instance_h (xo_handle_t *xop, const char *name)
3966 {
3967     xop = xo_default(xop);
3968
3969     int rc = 0;
3970     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3971     const char *pre_nl = "";
3972
3973     if (name == NULL) {
3974         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3975         if (!(xsp->xs_flags & XSF_DTRT))
3976             xo_failure(xop, "missing name without 'dtrt' mode");
3977
3978         name = xsp->xs_name;
3979         if (name) {
3980             int len = strlen(name) + 1;
3981             /* We need to make a local copy; xo_depth_change will free it */
3982             char *cp = alloca(len);
3983             memcpy(cp, name, len);
3984             name = cp;
3985         } else
3986             name = XO_FAILURE_NAME;
3987     }
3988
3989     switch (xop->xo_style) {
3990     case XO_STYLE_XML:
3991         xo_depth_change(xop, name, -1, -1, 0);
3992         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
3993         break;
3994
3995     case XO_STYLE_JSON:
3996         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3997
3998         xo_depth_change(xop, name, -1, -1, 0);
3999         rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
4000         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4001         break;
4002
4003     case XO_STYLE_HTML:
4004     case XO_STYLE_TEXT:
4005         xo_depth_change(xop, name, -1, 0, 0);
4006         break;
4007     }
4008
4009     return rc;
4010 }
4011
4012 int
4013 xo_close_instance (const char *name)
4014 {
4015     return xo_close_instance_h(NULL, name);
4016 }
4017
4018 int
4019 xo_close_instance_hd (xo_handle_t *xop)
4020 {
4021     return xo_close_instance_h(xop, NULL);
4022 }
4023
4024 int
4025 xo_close_instance_d (void)
4026 {
4027     return xo_close_instance_h(NULL, NULL);
4028 }
4029
4030 void
4031 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
4032                xo_close_func_t close_func)
4033 {
4034     xop = xo_default(xop);
4035
4036     xop->xo_opaque = opaque;
4037     xop->xo_write = write_func;
4038     xop->xo_close = close_func;
4039 }
4040
4041 void
4042 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
4043 {
4044     xo_realloc = realloc_func;
4045     xo_free = free_func;
4046 }
4047
4048 void
4049 xo_flush_h (xo_handle_t *xop)
4050 {
4051     static char div_close[] = "</div>";
4052
4053     xop = xo_default(xop);
4054
4055     switch (xop->xo_style) {
4056     case XO_STYLE_HTML:
4057         if (xop->xo_flags & XOF_DIV_OPEN) {
4058             xop->xo_flags &= ~XOF_DIV_OPEN;
4059             xo_data_append(xop, div_close, sizeof(div_close) - 1);
4060
4061             if (xop->xo_flags & XOF_PRETTY)
4062                 xo_data_append(xop, "\n", 1);
4063         }
4064         break;
4065     }
4066
4067     xo_write(xop);
4068 }
4069
4070 void
4071 xo_flush (void)
4072 {
4073     xo_flush_h(NULL);
4074 }
4075
4076 void
4077 xo_finish_h (xo_handle_t *xop)
4078 {
4079     const char *cp = "";
4080     xop = xo_default(xop);
4081
4082     switch (xop->xo_style) {
4083     case XO_STYLE_JSON:
4084         if (!(xop->xo_flags & XOF_NO_TOP)) {
4085             if (xop->xo_flags & XOF_TOP_EMITTED)
4086                 xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */
4087             else
4088                 cp = "{ ";
4089             xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
4090         }
4091         break;
4092     }
4093
4094     xo_flush_h(xop);
4095 }
4096
4097 void
4098 xo_finish (void)
4099 {
4100     xo_finish_h(NULL);
4101 }
4102
4103 /*
4104  * Generate an error message, such as would be displayed on stderr
4105  */
4106 void
4107 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
4108 {
4109     xop = xo_default(xop);
4110
4111     /*
4112      * If the format string doesn't end with a newline, we pop
4113      * one on ourselves.
4114      */
4115     int len = strlen(fmt);
4116     if (len > 0 && fmt[len - 1] != '\n') {
4117         char *newfmt = alloca(len + 2);
4118         memcpy(newfmt, fmt, len);
4119         newfmt[len] = '\n';
4120         newfmt[len] = '\0';
4121         fmt = newfmt;
4122     }
4123
4124     switch (xop->xo_style) {
4125     case XO_STYLE_TEXT:
4126         vfprintf(stderr, fmt, vap);
4127         break;
4128
4129     case XO_STYLE_HTML:
4130         va_copy(xop->xo_vap, vap);
4131         
4132         xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
4133
4134         if (xop->xo_flags & XOF_DIV_OPEN)
4135             xo_line_close(xop);
4136
4137         xo_write(xop);
4138
4139         va_end(xop->xo_vap);
4140         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4141         break;
4142
4143     case XO_STYLE_XML:
4144         va_copy(xop->xo_vap, vap);
4145
4146         xo_open_container_h(xop, "error");
4147         xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
4148         xo_close_container_h(xop, "error");
4149
4150         va_end(xop->xo_vap);
4151         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4152         break;
4153     }
4154 }
4155
4156 void
4157 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
4158 {
4159     va_list vap;
4160
4161     va_start(vap, fmt);
4162     xo_error_hv(xop, fmt, vap);
4163     va_end(vap);
4164 }
4165
4166 /*
4167  * Generate an error message, such as would be displayed on stderr
4168  */
4169 void
4170 xo_error (const char *fmt, ...)
4171 {
4172     va_list vap;
4173
4174     va_start(vap, fmt);
4175     xo_error_hv(NULL, fmt, vap);
4176     va_end(vap);
4177 }
4178
4179 int
4180 xo_parse_args (int argc, char **argv)
4181 {
4182     static char libxo_opt[] = "--libxo";
4183     char *cp;
4184     int i, save;
4185
4186     /* Save our program name for xo_err and friends */
4187     xo_program = argv[0];
4188     cp = strrchr(xo_program, '/');
4189     if (cp)
4190         xo_program = cp + 1;
4191
4192     for (save = i = 1; i < argc; i++) {
4193         if (argv[i] == NULL
4194             || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
4195             if (save != i)
4196                 argv[save] = argv[i];
4197             save += 1;
4198             continue;
4199         }
4200
4201         cp = argv[i] + sizeof(libxo_opt) - 1;
4202         if (*cp == 0) {
4203             cp = argv[++i];
4204             if (cp == 0) {
4205                 xo_warnx("missing libxo option");
4206                 return -1;
4207             }
4208                 
4209             if (xo_set_options(NULL, cp) < 0)
4210                 return -1;
4211         } else if (*cp == ':') {
4212             if (xo_set_options(NULL, cp) < 0)
4213                 return -1;
4214
4215         } else if (*cp == '=') {
4216             if (xo_set_options(NULL, ++cp) < 0)
4217                 return -1;
4218
4219         } else if (*cp == '-') {
4220             cp += 1;
4221             if (strcmp(cp, "check") == 0) {
4222                 exit(XO_HAS_LIBXO);
4223
4224             } else {
4225                 xo_warnx("unknown libxo option: '%s'", argv[i]);
4226                 return -1;
4227             }
4228         } else {
4229                 xo_warnx("unknown libxo option: '%s'", argv[i]);
4230             return -1;
4231         }
4232     }
4233
4234     argv[save] = NULL;
4235     return save;
4236 }
4237
4238 #ifdef UNIT_TEST
4239 int
4240 main (int argc, char **argv)
4241 {
4242     static char base_grocery[] = "GRO";
4243     static char base_hardware[] = "HRD";
4244     struct item {
4245         const char *i_title;
4246         int i_sold;
4247         int i_instock;
4248         int i_onorder;
4249         const char *i_sku_base;
4250         int i_sku_num;
4251     };
4252     struct item list[] = {
4253         { "gum&this&that", 1412, 54, 10, base_grocery, 415 },
4254         { "<rope>", 85, 4, 2, base_hardware, 212 },
4255         { "ladder", 0, 2, 1, base_hardware, 517 },
4256         { "\"bolt\"", 4123, 144, 42, base_hardware, 632 },
4257         { "water\\blue", 17, 14, 2, base_grocery, 2331 },
4258         { NULL, 0, 0, 0, NULL, 0 }
4259     };
4260     struct item list2[] = {
4261         { "fish", 1321, 45, 1, base_grocery, 533 },
4262         { NULL, 0, 0, 0, NULL, 0 }
4263     };
4264     struct item *ip;
4265     xo_info_t info[] = {
4266         { "in-stock", "number", "Number of items in stock" },
4267         { "name", "string", "Name of the item" },
4268         { "on-order", "number", "Number of items on order" },
4269         { "sku", "string", "Stock Keeping Unit" },
4270         { "sold", "number", "Number of items sold" },
4271         { NULL, NULL, NULL },
4272     };
4273     int info_count = (sizeof(info) / sizeof(info[0])) - 1;
4274     
4275     argc = xo_parse_args(argc, argv);
4276     if (argc < 0)
4277         exit(1);
4278
4279     xo_set_info(NULL, info, info_count);
4280
4281     xo_open_container_h(NULL, "top");
4282
4283     xo_open_container("data");
4284     xo_open_list("item");
4285
4286     xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}"
4287             "{T:On Order/%12s}{T:SKU/%5s}\n");
4288
4289     for (ip = list; ip->i_title; ip++) {
4290         xo_open_instance("item");
4291
4292         xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}"
4293                 "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n",
4294                 ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder,
4295                 ip->i_sku_base, ip->i_sku_num);
4296
4297         xo_close_instance("item");
4298     }
4299
4300     xo_close_list("item");
4301     xo_close_container("data");
4302
4303     xo_emit("\n\n");
4304
4305     xo_open_container("data");
4306     xo_open_list("item");
4307
4308     for (ip = list; ip->i_title; ip++) {
4309         xo_open_instance("item");
4310
4311         xo_attr("fancy", "%s%d", "item", ip - list);
4312         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
4313         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n",
4314                 ip->i_sold, ip->i_sold ? ".0" : "", 44);
4315         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
4316         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
4317         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
4318                 ip->i_sku_base, ip->i_sku_num);
4319
4320         xo_close_instance("item");
4321     }
4322
4323     xo_close_list("item");
4324     xo_close_container("data");
4325
4326     xo_open_container("data");
4327     xo_open_list("item");
4328
4329     for (ip = list2; ip->i_title; ip++) {
4330         xo_open_instance("item");
4331
4332         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
4333         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}\n",
4334                 ip->i_sold, ip->i_sold ? ".0" : "");
4335         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
4336         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
4337         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
4338                 ip->i_sku_base, ip->i_sku_num);
4339
4340         xo_open_list("month");
4341
4342         const char *months[] = { "Jan", "Feb", "Mar", NULL };
4343         int discounts[] = { 10, 20, 25, 0 };
4344         int i;
4345         for (i = 0; months[i]; i++) {
4346             xo_open_instance("month");
4347             xo_emit("{P:       }"
4348                     "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n",
4349                     months[i], discounts[i]);
4350             xo_close_instance("month");
4351         }
4352         
4353         xo_close_list("month");
4354
4355         xo_close_instance("item");
4356     }
4357
4358     xo_close_list("item");
4359     xo_close_container("data");
4360
4361     xo_close_container_h(NULL, "top");
4362
4363     xo_finish();
4364
4365     return 0;
4366 }
4367 #endif /* UNIT_TEST */