]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libxo/libxo/libxo.c
Merge from head
[FreeBSD/FreeBSD.git] / contrib / libxo / libxo / libxo.c
1 /*
2  * Copyright (c) 2014, 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     /* Add a newline to the fmt string */
960     if (!(xop->xo_flags & XOF_WARN_XML))
961         newfmt[len++ + plen] = '\n';
962     newfmt[len + plen] = '\0';
963
964     if (xop->xo_flags & XOF_WARN_XML) {
965         static char err_open[] = "<error>";
966         static char err_close[] = "</error>";
967         static char msg_open[] = "<message>";
968         static char msg_close[] = "</message>";
969
970         xo_buffer_t *xbp = &xop->xo_data;
971
972         xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
973         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
974
975         va_list va_local;
976         va_copy(va_local, vap);
977
978         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
979         int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
980         if (rc > xbp->xb_size) {
981             if (!xo_buf_has_room(xbp, rc)) {
982                 va_end(va_local);
983                 return;
984             }
985
986             va_end(vap);        /* Reset vap to the start */
987             va_copy(vap, va_local);
988
989             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
990             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
991         }
992         va_end(va_local);
993
994         rc = xo_escape_xml(xbp, rc, 1);
995         xbp->xb_curp += rc;
996
997         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
998         xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
999
1000         if (code > 0) {
1001             const char *msg = strerror(code);
1002             if (msg) {
1003                 xo_buf_append(xbp, ": ", 2);
1004                 xo_buf_append(xbp, msg, strlen(msg));
1005             }
1006         }
1007
1008         xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1009         xo_write(xop);
1010
1011     } else {
1012         vfprintf(stderr, newfmt, vap);
1013     }
1014 }
1015
1016 void
1017 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1018 {
1019     va_list vap;
1020
1021     va_start(vap, fmt);
1022     xo_warn_hcv(xop, code, 0, fmt, vap);
1023     va_end(vap);
1024 }
1025
1026 void
1027 xo_warn_c (int code, const char *fmt, ...)
1028 {
1029     va_list vap;
1030
1031     va_start(vap, fmt);
1032     xo_warn_hcv(NULL, 0, code, fmt, vap);
1033     va_end(vap);
1034 }
1035
1036 void
1037 xo_warn (const char *fmt, ...)
1038 {
1039     int code = errno;
1040     va_list vap;
1041
1042     va_start(vap, fmt);
1043     xo_warn_hcv(NULL, code, 0, fmt, vap);
1044     va_end(vap);
1045 }
1046
1047 void
1048 xo_warnx (const char *fmt, ...)
1049 {
1050     va_list vap;
1051
1052     va_start(vap, fmt);
1053     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1054     va_end(vap);
1055 }
1056
1057 void
1058 xo_err (int eval, const char *fmt, ...)
1059 {
1060     int code = errno;
1061     va_list vap;
1062
1063     va_start(vap, fmt);
1064     xo_warn_hcv(NULL, code, 0, fmt, vap);
1065     va_end(vap);
1066     xo_finish();
1067     exit(eval);
1068 }
1069
1070 void
1071 xo_errx (int eval, const char *fmt, ...)
1072 {
1073     va_list vap;
1074
1075     va_start(vap, fmt);
1076     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1077     va_end(vap);
1078     xo_finish();
1079     exit(eval);
1080 }
1081
1082 void
1083 xo_errc (int eval, int code, const char *fmt, ...)
1084 {
1085     va_list vap;
1086
1087     va_start(vap, fmt);
1088     xo_warn_hcv(NULL, code, 0, fmt, vap);
1089     va_end(vap);
1090     xo_finish();
1091     exit(eval);
1092 }
1093
1094 /*
1095  * Generate a warning.  Normally, this is a text message written to
1096  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1097  * XMLified content on standard output.
1098  */
1099 void
1100 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1101 {
1102     static char msg_open[] = "<message>";
1103     static char msg_close[] = "</message>";
1104     xo_buffer_t *xbp;
1105     int rc;
1106     va_list va_local;
1107
1108     xop = xo_default(xop);
1109
1110     if (fmt == NULL || *fmt == '\0')
1111         return;
1112
1113     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1114
1115     switch (xop->xo_style) {
1116     case XO_STYLE_XML:
1117         xbp = &xop->xo_data;
1118         if (xop->xo_flags & XOF_PRETTY)
1119             xo_buf_indent(xop, xop->xo_indent_by);
1120         xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1121
1122         va_copy(va_local, vap);
1123
1124         int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1125         rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1126         if (rc > xbp->xb_size) {
1127             if (!xo_buf_has_room(xbp, rc)) {
1128                 va_end(va_local);
1129                 return;
1130             }
1131
1132             va_end(vap);        /* Reset vap to the start */
1133             va_copy(vap, va_local);
1134
1135             left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1136             rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1137         }
1138         va_end(va_local);
1139
1140         rc = xo_escape_xml(xbp, rc, 1);
1141         xbp->xb_curp += rc;
1142
1143         if (need_nl && code > 0) {
1144             const char *msg = strerror(code);
1145             if (msg) {
1146                 xo_buf_append(xbp, ": ", 2);
1147                 xo_buf_append(xbp, msg, strlen(msg));
1148             }
1149         }
1150
1151         xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1152         if (need_nl)
1153             xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */
1154         xo_write(xop);
1155         break;
1156
1157     case XO_STYLE_HTML:
1158         {
1159             char buf[BUFSIZ], *bp = buf, *cp;
1160             int bufsiz = sizeof(buf);
1161             int rc2;
1162
1163             va_copy(va_local, vap);
1164
1165             rc = vsnprintf(bp, bufsiz, fmt, va_local);
1166             if (rc > bufsiz) {
1167                 bufsiz = rc + BUFSIZ;
1168                 bp = alloca(bufsiz);
1169                 va_end(va_local);
1170                 va_copy(va_local, vap);
1171                 rc = vsnprintf(bp, bufsiz, fmt, va_local);
1172             }
1173             va_end(va_local);
1174             cp = bp + rc;
1175
1176             if (need_nl) {
1177                 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1178                                (code > 0) ? ": " : "",
1179                                (code > 0) ? strerror(code) : "");
1180                 if (rc2 > 0)
1181                     rc += rc2;
1182             }
1183
1184             xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
1185         }
1186         break;
1187
1188     case XO_STYLE_JSON:
1189         /* No meanings of representing messages in JSON */
1190         break;
1191
1192     case XO_STYLE_TEXT:
1193         rc = xo_printf_v(xop, fmt, vap);
1194         /*
1195          * XXX need to handle UTF-8 widths
1196          */
1197         if (rc > 0) {
1198             if (xop->xo_flags & XOF_COLUMNS)
1199                 xop->xo_columns += rc;
1200             if (xop->xo_flags & XOF_ANCHOR)
1201                 xop->xo_anchor_columns += rc;
1202         }
1203
1204         if (need_nl && code > 0) {
1205             const char *msg = strerror(code);
1206             if (msg) {
1207                 xo_printf(xop, ": %s", msg);
1208             }
1209         }
1210         if (need_nl)
1211             xo_printf(xop, "\n");
1212
1213         break;
1214     }
1215
1216     xo_flush_h(xop);
1217 }
1218
1219 void
1220 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1221 {
1222     va_list vap;
1223
1224     va_start(vap, fmt);
1225     xo_message_hcv(xop, code, fmt, vap);
1226     va_end(vap);
1227 }
1228
1229 void
1230 xo_message_c (int code, const char *fmt, ...)
1231 {
1232     va_list vap;
1233
1234     va_start(vap, fmt);
1235     xo_message_hcv(NULL, code, fmt, vap);
1236     va_end(vap);
1237 }
1238
1239 void
1240 xo_message (const char *fmt, ...)
1241 {
1242     int code = errno;
1243     va_list vap;
1244
1245     va_start(vap, fmt);
1246     xo_message_hcv(NULL, code, fmt, vap);
1247     va_end(vap);
1248 }
1249
1250 static void
1251 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1252 {
1253     if (!(xop->xo_flags & XOF_WARN))
1254         return;
1255
1256     va_list vap;
1257
1258     va_start(vap, fmt);
1259     xo_warn_hcv(xop, -1, 1, fmt, vap);
1260     va_end(vap);
1261 }
1262
1263 /**
1264  * Create a handle for use by later libxo functions.
1265  *
1266  * Note: normal use of libxo does not require a distinct handle, since
1267  * the default handle (used when NULL is passed) generates text on stdout.
1268  *
1269  * @style Style of output desired (XO_STYLE_* value)
1270  * @flags Set of XOF_* flags in use with this handle
1271  */
1272 xo_handle_t *
1273 xo_create (xo_style_t style, xo_xof_flags_t flags)
1274 {
1275     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1276
1277     if (xop) {
1278         bzero(xop, sizeof(*xop));
1279
1280         xop->xo_style  = style;
1281         xop->xo_flags = flags;
1282         xo_init_handle(xop);
1283     }
1284
1285     return xop;
1286 }
1287
1288 /**
1289  * Create a handle that will write to the given file.  Use
1290  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1291  * @fp FILE pointer to use
1292  * @style Style of output desired (XO_STYLE_* value)
1293  * @flags Set of XOF_* flags to use with this handle
1294  */
1295 xo_handle_t *
1296 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1297 {
1298     xo_handle_t *xop = xo_create(style, flags);
1299
1300     if (xop) {
1301         xop->xo_opaque = fp;
1302         xop->xo_write = xo_write_to_file;
1303         xop->xo_close = xo_close_file;
1304     }
1305
1306     return xop;
1307 }
1308
1309 /**
1310  * Release any resources held by the handle.
1311  * @xop XO handle to alter (or NULL for default handle)
1312  */
1313 void
1314 xo_destroy (xo_handle_t *xop_arg)
1315 {
1316     xo_handle_t *xop = xo_default(xop_arg);
1317
1318     if (xop->xo_close && (xop->xo_flags & XOF_CLOSE_FP))
1319         xop->xo_close(xop->xo_opaque);
1320
1321     xo_free(xop->xo_stack);
1322     xo_buf_cleanup(&xop->xo_data);
1323     xo_buf_cleanup(&xop->xo_fmt);
1324     xo_buf_cleanup(&xop->xo_predicate);
1325     xo_buf_cleanup(&xop->xo_attrs);
1326
1327     if (xop_arg == NULL) {
1328         bzero(&xo_default_handle, sizeof(&xo_default_handle));
1329         xo_default_inited = 0;
1330     } else
1331         xo_free(xop);
1332 }
1333
1334 /**
1335  * Record a new output style to use for the given handle (or default if
1336  * handle is NULL).  This output style will be used for any future output.
1337  *
1338  * @xop XO handle to alter (or NULL for default handle)
1339  * @style new output style (XO_STYLE_*)
1340  */
1341 void
1342 xo_set_style (xo_handle_t *xop, xo_style_t style)
1343 {
1344     xop = xo_default(xop);
1345     xop->xo_style = style;
1346 }
1347
1348 xo_style_t
1349 xo_get_style (xo_handle_t *xop)
1350 {
1351     xop = xo_default(xop);
1352     return xop->xo_style;
1353 }
1354
1355 static int
1356 xo_name_to_style (const char *name)
1357 {
1358     if (strcmp(name, "xml") == 0)
1359         return XO_STYLE_XML;
1360     else if (strcmp(name, "json") == 0)
1361         return XO_STYLE_JSON;
1362     else if (strcmp(name, "text") == 0)
1363         return XO_STYLE_TEXT;
1364     else if (strcmp(name, "html") == 0)
1365         return XO_STYLE_HTML;
1366
1367     return -1;
1368 }
1369
1370 /*
1371  * Convert string name to XOF_* flag value.
1372  * Not all are useful.  Or safe.  Or sane.
1373  */
1374 static unsigned
1375 xo_name_to_flag (const char *name)
1376 {
1377     if (strcmp(name, "pretty") == 0)
1378         return XOF_PRETTY;
1379     if (strcmp(name, "warn") == 0)
1380         return XOF_WARN;
1381     if (strcmp(name, "xpath") == 0)
1382         return XOF_XPATH;
1383     if (strcmp(name, "info") == 0)
1384         return XOF_INFO;
1385     if (strcmp(name, "warn-xml") == 0)
1386         return XOF_WARN_XML;
1387     if (strcmp(name, "columns") == 0)
1388         return XOF_COLUMNS;
1389     if (strcmp(name, "dtrt") == 0)
1390         return XOF_DTRT;
1391     if (strcmp(name, "flush") == 0)
1392         return XOF_FLUSH;
1393     if (strcmp(name, "keys") == 0)
1394         return XOF_KEYS;
1395     if (strcmp(name, "ignore-close") == 0)
1396         return XOF_IGNORE_CLOSE;
1397     if (strcmp(name, "not-first") == 0)
1398         return XOF_NOT_FIRST;
1399     if (strcmp(name, "no-locale") == 0)
1400         return XOF_NO_LOCALE;
1401     if (strcmp(name, "no-top") == 0)
1402         return XOF_NO_TOP;
1403     if (strcmp(name, "units") == 0)
1404         return XOF_UNITS;
1405     if (strcmp(name, "underscores") == 0)
1406         return XOF_UNDERSCORES;
1407
1408     return 0;
1409 }
1410
1411 int
1412 xo_set_style_name (xo_handle_t *xop, const char *name)
1413 {
1414     if (name == NULL)
1415         return -1;
1416
1417     int style = xo_name_to_style(name);
1418     if (style < 0)
1419         return -1;
1420
1421     xo_set_style(xop, style);
1422     return 0;
1423 }
1424
1425 /*
1426  * Set the options for a handle using a string of options
1427  * passed in.  The input is a comma-separated set of names
1428  * and optional values: "xml,pretty,indent=4"
1429  */
1430 int
1431 xo_set_options (xo_handle_t *xop, const char *input)
1432 {
1433     char *cp, *ep, *vp, *np, *bp;
1434     int style = -1, new_style, len, rc = 0;
1435     xo_xof_flags_t new_flag;
1436
1437     if (input == NULL)
1438         return 0;
1439
1440     xop = xo_default(xop);
1441
1442     /*
1443      * We support a simpler, old-school style of giving option
1444      * also, using a single character for each option.  It's
1445      * ideal for lazy people, such as myself.
1446      */
1447     if (*input == ':') {
1448         int sz;
1449
1450         for (input++ ; *input; input++) {
1451             switch (*input) {
1452             case 'f':
1453                 xop->xo_flags |= XOF_FLUSH;
1454                 break;
1455
1456             case 'H':
1457                 xop->xo_style = XO_STYLE_HTML;
1458                 break;
1459
1460             case 'I':
1461                 xop->xo_flags |= XOF_INFO;
1462                 break;
1463
1464             case 'i':
1465                 sz = strspn(input + 1, "0123456789");
1466                 if (sz > 0) {
1467                     xop->xo_indent_by = atoi(input + 1);
1468                     input += sz - 1;    /* Skip value */
1469                 }
1470                 break;
1471
1472             case 'k':
1473                 xop->xo_flags |= XOF_KEYS;
1474                 break;
1475
1476             case 'J':
1477                 xop->xo_style = XO_STYLE_JSON;
1478                 break;
1479
1480             case 'P':
1481                 xop->xo_flags |= XOF_PRETTY;
1482                 break;
1483
1484             case 'T':
1485                 xop->xo_style = XO_STYLE_TEXT;
1486                 break;
1487
1488             case 'U':
1489                 xop->xo_flags |= XOF_UNITS;
1490                 break;
1491
1492             case 'u':
1493                 xop->xo_flags |= XOF_UNDERSCORES;
1494                 break;
1495
1496             case 'W':
1497                 xop->xo_flags |= XOF_WARN;
1498                 break;
1499
1500             case 'X':
1501                 xop->xo_style = XO_STYLE_XML;
1502                 break;
1503
1504             case 'x':
1505                 xop->xo_flags |= XOF_XPATH;
1506                 break;
1507             }
1508         }
1509         return 0;
1510     }
1511
1512     len = strlen(input) + 1;
1513     bp = alloca(len);
1514     memcpy(bp, input, len);
1515
1516     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
1517         np = strchr(cp, ',');
1518         if (np)
1519             *np++ = '\0';
1520
1521         vp = strchr(cp, '=');
1522         if (vp)
1523             *vp++ = '\0';
1524
1525         new_style = xo_name_to_style(cp);
1526         if (new_style >= 0) {
1527             if (style >= 0)
1528                 xo_warnx("ignoring multiple styles: '%s'", cp);
1529             else
1530                 style = new_style;
1531         } else {
1532             new_flag = xo_name_to_flag(cp);
1533             if (new_flag != 0)
1534                 xop->xo_flags |= new_flag;
1535             else {
1536                 if (strcmp(cp, "indent") == 0) {
1537                     xop->xo_indent_by = atoi(vp);
1538                 } else {
1539                     xo_warnx("unknown option: '%s'", cp);
1540                     rc = -1;
1541                 }
1542             }
1543         }
1544     }
1545
1546     if (style > 0)
1547         xop->xo_style= style;
1548
1549     return rc;
1550 }
1551
1552 /**
1553  * Set one or more flags for a given handle (or default if handle is NULL).
1554  * These flags will affect future output.
1555  *
1556  * @xop XO handle to alter (or NULL for default handle)
1557  * @flags Flags to be set (XOF_*)
1558  */
1559 void
1560 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1561 {
1562     xop = xo_default(xop);
1563
1564     xop->xo_flags |= flags;
1565 }
1566
1567 xo_xof_flags_t
1568 xo_get_flags (xo_handle_t *xop)
1569 {
1570     xop = xo_default(xop);
1571
1572     return xop->xo_flags;
1573 }
1574
1575 /**
1576  * Record a leading prefix for the XPath we generate.  This allows the
1577  * generated data to be placed within an XML hierarchy but still have
1578  * accurate XPath expressions.
1579  *
1580  * @xop XO handle to alter (or NULL for default handle)
1581  * @path The XPath expression
1582  */
1583 void
1584 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
1585 {
1586     xop = xo_default(xop);
1587
1588     if (xop->xo_leading_xpath) {
1589         xo_free(xop->xo_leading_xpath);
1590         xop->xo_leading_xpath = NULL;
1591     }
1592
1593     if (path == NULL)
1594         return;
1595
1596     int len = strlen(path);
1597     xop->xo_leading_xpath = xo_realloc(NULL, len + 1);
1598     if (xop->xo_leading_xpath) {
1599         memcpy(xop->xo_leading_xpath, path, len + 1);
1600     }
1601 }
1602
1603 /**
1604  * Record the info data for a set of tags
1605  *
1606  * @xop XO handle to alter (or NULL for default handle)
1607  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
1608  * @count Number of entries in info (or -1 to count them ourselves)
1609  */
1610 void
1611 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
1612 {
1613     xop = xo_default(xop);
1614
1615     if (count < 0 && infop) {
1616         xo_info_t *xip;
1617
1618         for (xip = infop, count = 0; xip->xi_name; xip++, count++)
1619             continue;
1620     }
1621
1622     xop->xo_info = infop;
1623     xop->xo_info_count = count;
1624 }
1625
1626 /**
1627  * Set the formatter callback for a handle.  The callback should
1628  * return a newly formatting contents of a formatting instruction,
1629  * meaning the bits inside the braces.
1630  */
1631 void
1632 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
1633                   xo_checkpointer_t cfunc)
1634 {
1635     xop = xo_default(xop);
1636
1637     xop->xo_formatter = func;
1638     xop->xo_checkpointer = cfunc;
1639 }
1640
1641 /**
1642  * Clear one or more flags for a given handle (or default if handle is NULL).
1643  * These flags will affect future output.
1644  *
1645  * @xop XO handle to alter (or NULL for default handle)
1646  * @flags Flags to be cleared (XOF_*)
1647  */
1648 void
1649 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
1650 {
1651     xop = xo_default(xop);
1652
1653     xop->xo_flags &= ~flags;
1654 }
1655
1656 static void
1657 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
1658 {
1659     static char div_open[] = "<div class=\"line\">";
1660     static char div_open_blank[] = "<div class=\"blank-line\">";
1661
1662     if (xop->xo_flags & XOF_DIV_OPEN)
1663         return;
1664
1665     if (xop->xo_style != XO_STYLE_HTML)
1666         return;
1667
1668     xop->xo_flags |= XOF_DIV_OPEN;
1669     if (flags & XFF_BLANK_LINE)
1670         xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
1671     else
1672         xo_data_append(xop, div_open, sizeof(div_open) - 1);
1673
1674     if (xop->xo_flags & XOF_PRETTY)
1675         xo_data_append(xop, "\n", 1);
1676 }
1677
1678 static void
1679 xo_line_close (xo_handle_t *xop)
1680 {
1681     static char div_close[] = "</div>";
1682
1683     switch (xop->xo_style) {
1684     case XO_STYLE_HTML:
1685         if (!(xop->xo_flags & XOF_DIV_OPEN))
1686             xo_line_ensure_open(xop, 0);
1687
1688         xop->xo_flags &= ~XOF_DIV_OPEN;
1689         xo_data_append(xop, div_close, sizeof(div_close) - 1);
1690
1691         if (xop->xo_flags & XOF_PRETTY)
1692             xo_data_append(xop, "\n", 1);
1693         break;
1694
1695     case XO_STYLE_TEXT:
1696         xo_data_append(xop, "\n", 1);
1697         break;
1698     }
1699 }
1700
1701 static int
1702 xo_info_compare (const void *key, const void *data)
1703 {
1704     const char *name = key;
1705     const xo_info_t *xip = data;
1706
1707     return strcmp(name, xip->xi_name);
1708 }
1709
1710
1711 static xo_info_t *
1712 xo_info_find (xo_handle_t *xop, const char *name, int nlen)
1713 {
1714     xo_info_t *xip;
1715     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
1716
1717     memcpy(cp, name, nlen);
1718     cp[nlen] = '\0';
1719
1720     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
1721                   sizeof(xop->xo_info[0]), xo_info_compare);
1722     return xip;
1723 }
1724
1725 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
1726
1727 /*
1728  * Check to see that the conversion is safe and sane.
1729  */
1730 static int
1731 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
1732 {
1733     switch (CONVERT(have_enc, need_enc)) {
1734     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
1735     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
1736     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
1737     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
1738     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
1739     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
1740         return 0;
1741
1742     default:
1743         xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
1744         return 1;
1745     }
1746 }
1747
1748 static int
1749 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
1750                          xo_xff_flags_t flags,
1751                          const wchar_t *wcp, const char *cp, int len, int max,
1752                          int need_enc, int have_enc)
1753 {
1754     int cols = 0;
1755     wchar_t wc = 0;
1756     int ilen, olen, width;
1757     int attr = (flags & XFF_ATTR);
1758     const char *sp;
1759
1760     if (len > 0 && !xo_buf_has_room(xbp, len))
1761         return 0;
1762
1763     for (;;) {
1764         if (len == 0)
1765             break;
1766
1767         if (cp) {
1768             if (*cp == '\0')
1769                 break;
1770             if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
1771                 cp += 1;
1772                 len -= 1;
1773             }
1774         }
1775
1776         if (wcp && *wcp == L'\0')
1777             break;
1778
1779         ilen = 0;
1780
1781         switch (have_enc) {
1782         case XF_ENC_WIDE:               /* Wide character */
1783             wc = *wcp++;
1784             ilen = 1;
1785             break;
1786
1787         case XF_ENC_UTF8:               /* UTF-8 */
1788             ilen = xo_utf8_to_wc_len(cp);
1789             if (ilen < 0) {
1790                 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
1791                 return -1;
1792             }
1793
1794             if (len > 0 && len < ilen) {
1795                 len = 0;        /* Break out of the loop */
1796                 continue;
1797             }
1798
1799             wc = xo_utf8_char(cp, ilen);
1800             if (wc == (wchar_t) -1) {
1801                 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
1802                            *cp, ilen);
1803                 return -1;
1804             }
1805             cp += ilen;
1806             break;
1807
1808         case XF_ENC_LOCALE:             /* Native locale */
1809             ilen = (len > 0) ? len : MB_LEN_MAX;
1810             ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
1811             if (ilen < 0) {             /* Invalid data; skip */
1812                 xo_failure(xop, "invalid mbs char: %02hhx", *cp);
1813                 continue;
1814             }
1815             if (ilen == 0) {            /* Hit a wide NUL character */
1816                 len = 0;
1817                 continue;
1818             }
1819
1820             cp += ilen;
1821             break;
1822         }
1823
1824         /* Reduce len, but not below zero */
1825         if (len > 0) {
1826             len -= ilen;
1827             if (len < 0)
1828                 len = 0;
1829         }
1830
1831         /*
1832          * Find the width-in-columns of this character, which must be done
1833          * in wide characters, since we lack a mbswidth() function.  If
1834          * it doesn't fit
1835          */
1836         width = wcwidth(wc);
1837         if (width < 0)
1838             width = iswcntrl(wc) ? 0 : 1;
1839
1840         if (xop->xo_style == XO_STYLE_TEXT || xop->xo_style == XO_STYLE_HTML) {
1841             if (max > 0 && cols + width > max)
1842                 break;
1843         }
1844
1845         switch (need_enc) {
1846         case XF_ENC_UTF8:
1847
1848             /* Output in UTF-8 needs to be escaped, based on the style */
1849             switch (xop->xo_style) {
1850             case XO_STYLE_XML:
1851             case XO_STYLE_HTML:
1852                 if (wc == '<')
1853                     sp = xo_xml_lt;
1854                 else if (wc == '>')
1855                     sp = xo_xml_gt;
1856                 else if (wc == '&')
1857                     sp = xo_xml_amp;
1858                 else if (attr && wc == '"')
1859                     sp = xo_xml_quot;
1860                 else
1861                     break;
1862
1863                 int slen = strlen(sp);
1864                 if (!xo_buf_has_room(xbp, slen - 1))
1865                     return -1;
1866
1867                 memcpy(xbp->xb_curp, sp, slen);
1868                 xbp->xb_curp += slen;
1869                 goto done_with_encoding; /* Need multi-level 'break' */
1870
1871             case XO_STYLE_JSON:
1872                 if (wc != '\\' && wc != '"')
1873                     break;
1874
1875                 if (!xo_buf_has_room(xbp, 2))
1876                     return -1;
1877
1878                 *xbp->xb_curp++ = '\\';
1879                 *xbp->xb_curp++ = wc & 0x7f;
1880                 goto done_with_encoding;
1881             }
1882
1883             olen = xo_utf8_emit_len(wc);
1884             if (olen < 0) {
1885                 xo_failure(xop, "ignoring bad length");
1886                 continue;
1887             }
1888
1889             if (!xo_buf_has_room(xbp, olen))
1890                 return -1;
1891
1892             xo_utf8_emit_char(xbp->xb_curp, olen, wc);
1893             xbp->xb_curp += olen;
1894             break;
1895
1896         case XF_ENC_LOCALE:
1897             if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1898                 return -1;
1899
1900             olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1901             if (olen <= 0) {
1902                 xo_failure(xop, "could not convert wide char: %lx",
1903                            (unsigned long) wc);
1904                 olen = 1;
1905                 width = 1;
1906                 *xbp->xb_curp++ = '?';
1907             } else
1908                 xbp->xb_curp += olen;
1909             break;
1910         }
1911
1912     done_with_encoding:
1913         cols += width;
1914     }
1915
1916     return cols;
1917 }
1918
1919 static int
1920 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
1921                   xo_format_t *xfp)
1922 {
1923     static char null[] = "(null)";
1924
1925     char *cp = NULL;
1926     wchar_t *wcp = NULL;
1927     int len, cols = 0, rc = 0;
1928     int off = xbp->xb_curp - xbp->xb_bufp, off2;
1929     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
1930         ? XF_ENC_LOCALE : XF_ENC_UTF8;
1931
1932     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
1933         return 0;
1934
1935     len = xfp->xf_width[XF_WIDTH_SIZE];
1936
1937     if (xfp->xf_enc == XF_ENC_WIDE) {
1938         wcp = va_arg(xop->xo_vap, wchar_t *);
1939         if (xfp->xf_skip)
1940             return 0;
1941
1942         /*
1943          * Dont' deref NULL; use the traditional "(null)" instead
1944          * of the more accurate "who's been a naughty boy, then?".
1945          */
1946         if (wcp == NULL) {
1947             cp = null;
1948             len = sizeof(null) - 1;
1949         }
1950
1951     } else {
1952         cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
1953         if (xfp->xf_skip)
1954             return 0;
1955
1956         /* Echo "Dont' deref NULL" logic */
1957         if (cp == NULL) {
1958             cp = null;
1959             len = sizeof(null) - 1;
1960         }
1961
1962         /*
1963          * Optimize the most common case, which is "%s".  We just
1964          * need to copy the complete string to the output buffer.
1965          */
1966         if (xfp->xf_enc == need_enc
1967                 && xfp->xf_width[XF_WIDTH_MIN] < 0
1968                 && xfp->xf_width[XF_WIDTH_SIZE] < 0
1969                 && xfp->xf_width[XF_WIDTH_MAX] < 0
1970                 && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) {
1971             len = strlen(cp);
1972             xo_buf_escape(xop, xbp, cp, len, flags);
1973
1974             /*
1975              * Our caller expects xb_curp left untouched, so we have
1976              * to reset it and return the number of bytes written to
1977              * the buffer.
1978              */
1979             off2 = xbp->xb_curp - xbp->xb_bufp;
1980             rc = off2 - off;
1981             xbp->xb_curp = xbp->xb_bufp + off;
1982
1983             return rc;
1984         }
1985     }
1986
1987     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
1988                                    xfp->xf_width[XF_WIDTH_MAX],
1989                                    need_enc, xfp->xf_enc);
1990     if (cols < 0)
1991         goto bail;
1992
1993     /*
1994      * xo_buf_append* will move xb_curp, so we save/restore it.
1995      */
1996     off2 = xbp->xb_curp - xbp->xb_bufp;
1997     rc = off2 - off;
1998     xbp->xb_curp = xbp->xb_bufp + off;
1999
2000     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
2001         /*
2002          * Find the number of columns needed to display the string.
2003          * If we have the original wide string, we just call wcswidth,
2004          * but if we did the work ourselves, then we need to do it.
2005          */
2006         int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2007         if (!xo_buf_has_room(xbp, delta))
2008             goto bail;
2009
2010         /*
2011          * If seen_minus, then pad on the right; otherwise move it so
2012          * we can pad on the left.
2013          */
2014         if (xfp->xf_seen_minus) {
2015             cp = xbp->xb_curp + rc;
2016         } else {
2017             cp = xbp->xb_curp;
2018             memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
2019         }
2020
2021         /* Set the padding */
2022         memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
2023         rc += delta;
2024         cols += delta;
2025     }
2026
2027     if (xop->xo_flags & XOF_COLUMNS)
2028         xop->xo_columns += cols;
2029     if (xop->xo_flags & XOF_ANCHOR)
2030         xop->xo_anchor_columns += cols;
2031
2032     return rc;
2033
2034  bail:
2035     xbp->xb_curp = xbp->xb_bufp + off;
2036     return 0;
2037 }
2038
2039 static void
2040 xo_data_append_content (xo_handle_t *xop, const char *str, int len)
2041 {
2042     int cols;
2043     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
2044         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2045
2046     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE,
2047                                    NULL, str, len, -1,
2048                                    need_enc, XF_ENC_UTF8);
2049
2050     if (xop->xo_flags & XOF_COLUMNS)
2051         xop->xo_columns += cols;
2052     if (xop->xo_flags & XOF_ANCHOR)
2053         xop->xo_anchor_columns += cols;
2054 }
2055
2056 static void
2057 xo_bump_width (xo_format_t *xfp, int digit)
2058 {
2059     int *ip = &xfp->xf_width[xfp->xf_dots];
2060
2061     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
2062 }
2063
2064 static int
2065 xo_trim_ws (xo_buffer_t *xbp, int len)
2066 {
2067     char *cp, *sp, *ep;
2068     int delta;
2069
2070     /* First trim leading space */
2071     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
2072         if (*cp != ' ')
2073             break;
2074     }
2075
2076     delta = cp - sp;
2077     if (delta) {
2078         len -= delta;
2079         memmove(sp, cp, len);
2080     }
2081
2082     /* Then trim off the end */
2083     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
2084         if (ep[-1] != ' ')
2085             break;
2086     }
2087
2088     delta = sp - ep;
2089     if (delta) {
2090         len -= delta;
2091         cp[len] = '\0';
2092     }
2093
2094     return len;
2095 }
2096
2097 static int
2098 xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
2099                 const char *fmt, int flen, xo_xff_flags_t flags)
2100 {
2101     xo_format_t xf;
2102     const char *cp, *ep, *sp, *xp = NULL;
2103     int rc, cols;
2104     int style = (flags & XFF_XML) ? XO_STYLE_XML : xop->xo_style;
2105     unsigned make_output = !(flags & XFF_NO_OUTPUT);
2106     int need_enc = (xop->xo_style == XO_STYLE_TEXT)
2107         ? XF_ENC_LOCALE : XF_ENC_UTF8;
2108     
2109     if (xbp == NULL)
2110         xbp = &xop->xo_data;
2111
2112     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
2113         if (*cp != '%') {
2114         add_one:
2115             if (xp == NULL)
2116                 xp = cp;
2117
2118             if (*cp == '\\' && cp[1] != '\0')
2119                 cp += 1;
2120             continue;
2121
2122         } if (cp + 1 < ep && cp[1] == '%') {
2123             cp += 1;
2124             goto add_one;
2125         }
2126
2127         if (xp) {
2128             if (make_output) {
2129                 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2130                                                NULL, xp, cp - xp, -1,
2131                                                need_enc, XF_ENC_UTF8);
2132                 if (xop->xo_flags & XOF_COLUMNS)
2133                     xop->xo_columns += cols;
2134                 if (xop->xo_flags & XOF_ANCHOR)
2135                     xop->xo_anchor_columns += cols;
2136             }
2137
2138             xp = NULL;
2139         }
2140
2141         bzero(&xf, sizeof(xf));
2142         xf.xf_leading_zero = -1;
2143         xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
2144
2145         /*
2146          * "%@" starts an XO-specific set of flags:
2147          *   @X@ - XML-only field; ignored if style isn't XML
2148          */
2149         if (cp[1] == '@') {
2150             for (cp += 2; cp < ep; cp++) {
2151                 if (*cp == '@') {
2152                     break;
2153                 }
2154                 if (*cp == '*') {
2155                     /*
2156                      * '*' means there's a "%*.*s" value in vap that
2157                      * we want to ignore
2158                      */
2159                     if (!(xop->xo_flags & XOF_NO_VA_ARG))
2160                         va_arg(xop->xo_vap, int);
2161                 }
2162             }
2163         }
2164
2165         /* Hidden fields are only visible to JSON and XML */
2166         if (xop->xo_flags & XFF_ENCODE_ONLY) {
2167             if (style != XO_STYLE_XML
2168                     && xop->xo_style != XO_STYLE_JSON)
2169                 xf.xf_skip = 1;
2170         } else if (xop->xo_flags & XFF_DISPLAY_ONLY) {
2171             if (style != XO_STYLE_TEXT
2172                     && xop->xo_style != XO_STYLE_HTML)
2173                 xf.xf_skip = 1;
2174         }
2175
2176         if (!make_output)
2177             xf.xf_skip = 1;
2178
2179         /*
2180          * Looking at one piece of a format; find the end and
2181          * call snprintf.  Then advance xo_vap on our own.
2182          *
2183          * Note that 'n', 'v', and '$' are not supported.
2184          */
2185         sp = cp;                /* Save start pointer */
2186         for (cp += 1; cp < ep; cp++) {
2187             if (*cp == 'l')
2188                 xf.xf_lflag += 1;
2189             else if (*cp == 'h')
2190                 xf.xf_hflag += 1;
2191             else if (*cp == 'j')
2192                 xf.xf_jflag += 1;
2193             else if (*cp == 't')
2194                 xf.xf_tflag += 1;
2195             else if (*cp == 'z')
2196                 xf.xf_zflag += 1;
2197             else if (*cp == 'q')
2198                 xf.xf_qflag += 1;
2199             else if (*cp == '.') {
2200                 if (++xf.xf_dots >= XF_WIDTH_NUM) {
2201                     xo_failure(xop, "Too many dots in format: '%s'", fmt);
2202                     return -1;
2203                 }
2204             } else if (*cp == '-')
2205                 xf.xf_seen_minus = 1;
2206             else if (isdigit((int) *cp)) {
2207                 if (xf.xf_leading_zero < 0)
2208                     xf.xf_leading_zero = (*cp == '0');
2209                 xo_bump_width(&xf, *cp - '0');
2210             } else if (*cp == '*') {
2211                 xf.xf_stars += 1;
2212                 xf.xf_star[xf.xf_dots] = 1;
2213             } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
2214                 break;
2215             else if (*cp == 'n' || *cp == 'v') {
2216                 xo_failure(xop, "unsupported format: '%s'", fmt);
2217                 return -1;
2218             }
2219         }
2220
2221         if (cp == ep)
2222             xo_failure(xop, "field format missing format character: %s",
2223                           fmt);
2224
2225         xf.xf_fc = *cp;
2226
2227         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2228             if (*cp == 's' || *cp == 'S') {
2229                 /* Handle "%*.*.*s" */
2230                 int s;
2231                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2232                     if (xf.xf_star[s]) {
2233                         xf.xf_width[s] = va_arg(xop->xo_vap, int);
2234                         
2235                         /* Normalize a negative width value */
2236                         if (xf.xf_width[s] < 0) {
2237                             if (s == 0) {
2238                                 xf.xf_width[0] = -xf.xf_width[0];
2239                                 xf.xf_seen_minus = 1;
2240                             } else
2241                                 xf.xf_width[s] = -1; /* Ignore negative values */
2242                         }
2243                     }
2244                 }
2245             }
2246         }
2247
2248         /* If no max is given, it defaults to size */
2249         if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
2250             xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
2251
2252         if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
2253             xf.xf_lflag = 1;
2254
2255         if (!xf.xf_skip) {
2256             xo_buffer_t *fbp = &xop->xo_fmt;
2257             int len = cp - sp + 1;
2258             if (!xo_buf_has_room(fbp, len + 1))
2259                 return -1;
2260
2261             char *newfmt = fbp->xb_curp;
2262             memcpy(newfmt, sp, len);
2263             newfmt[0] = '%';    /* If we skipped over a "%@...@s" format */
2264             newfmt[len] = '\0';
2265
2266             /*
2267              * Bad news: our strings are UTF-8, but the stock printf
2268              * functions won't handle field widths for wide characters
2269              * correctly.  So we have to handle this ourselves.
2270              */
2271             if (xop->xo_formatter == NULL
2272                     && (xf.xf_fc == 's' || xf.xf_fc == 'S')) {
2273                 xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S'))
2274                     ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
2275                 rc = xo_format_string(xop, xbp, flags, &xf);
2276
2277                 if ((flags & XFF_TRIM_WS)
2278                         && (xop->xo_style == XO_STYLE_XML
2279                                 || xop->xo_style == XO_STYLE_JSON))
2280                     rc = xo_trim_ws(xbp, rc);
2281
2282             } else {
2283                 int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
2284
2285                 /*
2286                  * For XML and HTML, we need "&<>" processing; for JSON,
2287                  * it's quotes.  Text gets nothing.
2288                  */
2289                 switch (style) {
2290                 case XO_STYLE_XML:
2291                     if (flags & XFF_TRIM_WS)
2292                         columns = rc = xo_trim_ws(xbp, rc);
2293                     /* fall thru */
2294                 case XO_STYLE_HTML:
2295                     rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
2296                     break;
2297
2298                 case XO_STYLE_JSON:
2299                     if (flags & XFF_TRIM_WS)
2300                         columns = rc = xo_trim_ws(xbp, rc);
2301                     rc = xo_escape_json(xbp, rc);
2302                     break;
2303                 }
2304
2305                 /*
2306                  * We can assume all the data we've added is ASCII, so
2307                  * the columns and bytes are the same.  xo_format_string
2308                  * handles all the fancy string conversions and updates
2309                  * xo_anchor_columns accordingly.
2310                  */
2311                 if (xop->xo_flags & XOF_COLUMNS)
2312                     xop->xo_columns += columns;
2313                 if (xop->xo_flags & XOF_ANCHOR)
2314                     xop->xo_anchor_columns += columns;
2315             }
2316
2317             xbp->xb_curp += rc;
2318         }
2319
2320         /*
2321          * Now for the tricky part: we need to move the argument pointer
2322          * along by the amount needed.
2323          */
2324         if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
2325
2326             if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
2327                 /*
2328                  * The 'S' and 's' formats are normally handled in
2329                  * xo_format_string, but if we skipped it, then we
2330                  * need to pop it.
2331                  */
2332                 if (xf.xf_skip)
2333                     va_arg(xop->xo_vap, char *);
2334
2335             } else {
2336                 int s;
2337                 for (s = 0; s < XF_WIDTH_NUM; s++) {
2338                     if (xf.xf_star[s])
2339                         va_arg(xop->xo_vap, int);
2340                 }
2341
2342                 if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
2343                     if (xf.xf_hflag > 1) {
2344                         va_arg(xop->xo_vap, int);
2345
2346                     } else if (xf.xf_hflag > 0) {
2347                         va_arg(xop->xo_vap, int);
2348
2349                     } else if (xf.xf_lflag > 1) {
2350                         va_arg(xop->xo_vap, unsigned long long);
2351
2352                     } else if (xf.xf_lflag > 0) {
2353                         va_arg(xop->xo_vap, unsigned long);
2354
2355                     } else if (xf.xf_jflag > 0) {
2356                         va_arg(xop->xo_vap, intmax_t);
2357
2358                     } else if (xf.xf_tflag > 0) {
2359                         va_arg(xop->xo_vap, ptrdiff_t);
2360
2361                     } else if (xf.xf_zflag > 0) {
2362                         va_arg(xop->xo_vap, size_t);
2363
2364                     } else if (xf.xf_qflag > 0) {
2365                         va_arg(xop->xo_vap, quad_t);
2366
2367                     } else {
2368                         va_arg(xop->xo_vap, int);
2369                     }
2370                 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
2371                     if (xf.xf_lflag)
2372                         va_arg(xop->xo_vap, long double);
2373                     else
2374                         va_arg(xop->xo_vap, double);
2375
2376                 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
2377                     va_arg(xop->xo_vap, wint_t);
2378
2379                 else if (xf.xf_fc == 'c')
2380                     va_arg(xop->xo_vap, int);
2381
2382                 else if (xf.xf_fc == 'p')
2383                     va_arg(xop->xo_vap, void *);
2384             }
2385         }
2386     }
2387
2388     if (xp) {
2389         if (make_output) {
2390             cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
2391                                            NULL, xp, cp - xp, -1,
2392                                            need_enc, XF_ENC_UTF8);
2393             if (xop->xo_flags & XOF_COLUMNS)
2394                 xop->xo_columns += cols;
2395             if (xop->xo_flags & XOF_ANCHOR)
2396                 xop->xo_anchor_columns += cols;
2397         }
2398
2399         xp = NULL;
2400     }
2401
2402     return 0;
2403 }
2404
2405 static char *
2406 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
2407 {
2408     char *cp = encoding;
2409
2410     if (cp[0] != '%' || !isdigit((int) cp[1]))
2411         return encoding;
2412
2413     for (cp += 2; *cp; cp++) {
2414         if (!isdigit((int) *cp))
2415             break;
2416     }
2417
2418     cp -= 1;
2419     *cp = '%';
2420
2421     return cp;
2422 }
2423
2424 static void
2425 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
2426                    const char *name, int nlen,
2427                    const char *value, int vlen,
2428                    const char *encoding, int elen)
2429 {
2430     static char div_start[] = "<div class=\"";
2431     static char div_tag[] = "\" data-tag=\"";
2432     static char div_xpath[] = "\" data-xpath=\"";
2433     static char div_key[] = "\" data-key=\"key";
2434     static char div_end[] = "\">";
2435     static char div_close[] = "</div>";
2436
2437     /*
2438      * To build our XPath predicate, we need to save the va_list before
2439      * we format our data, and then restore it before we format the
2440      * xpath expression.
2441      * Display-only keys implies that we've got an encode-only key
2442      * elsewhere, so we don't use them from making predicates.
2443      */
2444     int need_predidate = 
2445         (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
2446          && (xop->xo_flags & XOF_XPATH));
2447
2448     if (need_predidate) {
2449         va_list va_local;
2450
2451         va_copy(va_local, xop->xo_vap);
2452         if (xop->xo_checkpointer)
2453             xop->xo_checkpointer(xop, xop->xo_vap, 0);
2454
2455         /*
2456          * Build an XPath predicate expression to match this key.
2457          * We use the format buffer.
2458          */
2459         xo_buffer_t *pbp = &xop->xo_predicate;
2460         pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
2461
2462         xo_buf_append(pbp, "[", 1);
2463         xo_buf_escape(xop, pbp, name, nlen, 0);
2464         if (xop->xo_flags & XOF_PRETTY)
2465             xo_buf_append(pbp, " = '", 4);
2466         else
2467             xo_buf_append(pbp, "='", 2);
2468
2469         /* The encoding format defaults to the normal format */
2470         if (encoding == NULL) {
2471             char *enc  = alloca(vlen + 1);
2472             memcpy(enc, value, vlen);
2473             enc[vlen] = '\0';
2474             encoding = xo_fix_encoding(xop, enc);
2475             elen = strlen(encoding);
2476         }
2477
2478         xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR);
2479
2480         xo_buf_append(pbp, "']", 2);
2481
2482         /* Now we record this predicate expression in the stack */
2483         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
2484         int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
2485         int dlen = pbp->xb_curp - pbp->xb_bufp;
2486
2487         char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
2488         if (cp) {
2489             memcpy(cp + olen, pbp->xb_bufp, dlen);
2490             cp[olen + dlen] = '\0';
2491             xsp->xs_keys = cp;
2492         }
2493
2494         /* Now we reset the xo_vap as if we were never here */
2495         va_end(xop->xo_vap);
2496         va_copy(xop->xo_vap, va_local);
2497         va_end(va_local);
2498         if (xop->xo_checkpointer)
2499             xop->xo_checkpointer(xop, xop->xo_vap, 1);
2500     }
2501
2502     if (flags & XFF_ENCODE_ONLY) {
2503         /*
2504          * Even if this is encode-only, we need to go thru the
2505          * work of formatting it to make sure the args are cleared
2506          * from xo_vap.
2507          */
2508         xo_format_data(xop, &xop->xo_data, encoding, elen,
2509                        flags | XFF_NO_OUTPUT);
2510         return;
2511     }
2512
2513     xo_line_ensure_open(xop, 0);
2514
2515     if (xop->xo_flags & XOF_PRETTY)
2516         xo_buf_indent(xop, xop->xo_indent_by);
2517
2518     xo_data_append(xop, div_start, sizeof(div_start) - 1);
2519     xo_data_append(xop, class, strlen(class));
2520
2521     if (name) {
2522         xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
2523         xo_data_escape(xop, name, nlen);
2524
2525         /*
2526          * Save the offset at which we'd place units.  See xo_format_units.
2527          */
2528         if (xop->xo_flags & XOF_UNITS) {
2529             xop->xo_flags |= XOF_UNITS_PENDING;
2530             /*
2531              * Note: We need the '+1' here because we know we've not
2532              * added the closing quote.  We add one, knowing the quote
2533              * will be added shortly.
2534              */
2535             xop->xo_units_offset =
2536                 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
2537         }
2538     }
2539
2540     if (name) {
2541         if (xop->xo_flags & XOF_XPATH) {
2542             int i;
2543             xo_stack_t *xsp;
2544
2545             xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
2546             if (xop->xo_leading_xpath)
2547                 xo_data_append(xop, xop->xo_leading_xpath,
2548                                strlen(xop->xo_leading_xpath));
2549
2550             for (i = 0; i <= xop->xo_depth; i++) {
2551                 xsp = &xop->xo_stack[i];
2552                 if (xsp->xs_name == NULL)
2553                     continue;
2554
2555                 xo_data_append(xop, "/", 1);
2556                 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
2557                 if (xsp->xs_keys) {
2558                     /* Don't show keys for the key field */
2559                     if (i != xop->xo_depth || !(flags & XFF_KEY))
2560                         xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
2561                 }
2562             }
2563
2564             xo_data_append(xop, "/", 1);
2565             xo_data_escape(xop, name, nlen);
2566         }
2567
2568         if ((xop->xo_flags & XOF_INFO) && xop->xo_info) {
2569             static char in_type[] = "\" data-type=\"";
2570             static char in_help[] = "\" data-help=\"";
2571
2572             xo_info_t *xip = xo_info_find(xop, name, nlen);
2573             if (xip) {
2574                 if (xip->xi_type) {
2575                     xo_data_append(xop, in_type, sizeof(in_type) - 1);
2576                     xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
2577                 }
2578                 if (xip->xi_help) {
2579                     xo_data_append(xop, in_help, sizeof(in_help) - 1);
2580                     xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
2581                 }
2582             }
2583         }
2584
2585         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS))
2586             xo_data_append(xop, div_key, sizeof(div_key) - 1);
2587     }
2588
2589     xo_data_append(xop, div_end, sizeof(div_end) - 1);
2590
2591     xo_format_data(xop, NULL, value, vlen, 0);
2592
2593     xo_data_append(xop, div_close, sizeof(div_close) - 1);
2594
2595     if (xop->xo_flags & XOF_PRETTY)
2596         xo_data_append(xop, "\n", 1);
2597 }
2598
2599 static void
2600 xo_format_text (xo_handle_t *xop, const char *str, int len)
2601 {
2602     switch (xop->xo_style) {
2603     case XO_STYLE_TEXT:
2604         xo_buf_append_locale(xop, &xop->xo_data, str, len);
2605         break;
2606
2607     case XO_STYLE_HTML:
2608         xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
2609         break;
2610     }
2611 }
2612
2613 static void
2614 xo_format_title (xo_handle_t *xop, const char *str, int len,
2615                  const char *fmt, int flen)
2616 {
2617     static char div_open[] = "<div class=\"title\">";
2618     static char div_close[] = "</div>";
2619
2620     switch (xop->xo_style) {
2621     case XO_STYLE_XML:
2622     case XO_STYLE_JSON:
2623         /*
2624          * Even though we don't care about text, we need to do
2625          * enough parsing work to skip over the right bits of xo_vap.
2626          */
2627         if (len == 0)
2628             xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2629         return;
2630     }
2631
2632     xo_buffer_t *xbp = &xop->xo_data;
2633     int start = xbp->xb_curp - xbp->xb_bufp;
2634     int left = xbp->xb_size - start;
2635     int rc;
2636     int need_enc = XF_ENC_LOCALE;
2637
2638     if (xop->xo_style == XO_STYLE_HTML) {
2639         need_enc = XF_ENC_UTF8;
2640         xo_line_ensure_open(xop, 0);
2641         if (xop->xo_flags & XOF_PRETTY)
2642             xo_buf_indent(xop, xop->xo_indent_by);
2643         xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
2644     }
2645
2646     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
2647     if (len) {
2648         char *newfmt = alloca(flen + 1);
2649         memcpy(newfmt, fmt, flen);
2650         newfmt[flen] = '\0';
2651
2652         /* If len is non-zero, the format string apply to the name */
2653         char *newstr = alloca(len + 1);
2654         memcpy(newstr, str, len);
2655         newstr[len] = '\0';
2656
2657         if (newstr[len - 1] == 's') {
2658             int cols;
2659             char *bp;
2660
2661             rc = snprintf(NULL, 0, newfmt, newstr);
2662             if (rc > 0) {
2663                 /*
2664                  * We have to do this the hard way, since we might need
2665                  * the columns.
2666                  */
2667                 bp = alloca(rc + 1);
2668                 rc = snprintf(bp, rc + 1, newfmt, newstr);
2669                 cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1,
2670                                                need_enc, XF_ENC_UTF8);
2671                 if (cols > 0) {
2672                     if (xop->xo_flags & XOF_COLUMNS)
2673                         xop->xo_columns += cols;
2674                     if (xop->xo_flags & XOF_ANCHOR)
2675                         xop->xo_anchor_columns += cols;
2676                 }
2677             }
2678             goto move_along;
2679
2680         } else {
2681             rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2682             if (rc > left) {
2683                 if (!xo_buf_has_room(xbp, rc))
2684                     return;
2685                 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
2686                 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
2687             }
2688
2689             if (rc > 0) {
2690                 if (xop->xo_flags & XOF_COLUMNS)
2691                     xop->xo_columns += rc;
2692                 if (xop->xo_flags & XOF_ANCHOR)
2693                     xop->xo_anchor_columns += rc;
2694             }
2695         }
2696
2697     } else {
2698         xo_format_data(xop, NULL, fmt, flen, 0);
2699
2700         /* xo_format_data moved curp, so we need to reset it */
2701         rc = xbp->xb_curp - (xbp->xb_bufp + start);
2702         xbp->xb_curp = xbp->xb_bufp + start;
2703     }
2704
2705     /* If we're styling HTML, then we need to escape it */
2706     if (xop->xo_style == XO_STYLE_HTML) {
2707         rc = xo_escape_xml(xbp, rc, 0);
2708     }
2709
2710     if (rc > 0)
2711         xbp->xb_curp += rc;
2712
2713  move_along:
2714     if (xop->xo_style == XO_STYLE_HTML) {
2715         xo_data_append(xop, div_close, sizeof(div_close) - 1);
2716         if (xop->xo_flags & XOF_PRETTY)
2717             xo_data_append(xop, "\n", 1);
2718     }
2719 }
2720
2721 static void
2722 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
2723 {
2724     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
2725         xo_data_append(xop, ",", 1);
2726         if (!(flags & XFF_LEAF_LIST) && (xop->xo_flags & XOF_PRETTY))
2727             xo_data_append(xop, "\n", 1);
2728     } else
2729         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
2730 }
2731
2732 #if 0
2733 /* Useful debugging function */
2734 void
2735 xo_arg (xo_handle_t *xop);
2736 void
2737 xo_arg (xo_handle_t *xop)
2738 {
2739     xop = xo_default(xop);
2740     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
2741 }
2742 #endif /* 0 */
2743
2744 static void
2745 xo_format_value (xo_handle_t *xop, const char *name, int nlen,
2746                  const char *format, int flen,
2747                  const char *encoding, int elen, xo_xff_flags_t flags)
2748 {
2749     int pretty = (xop->xo_flags & XOF_PRETTY);
2750     int quote;
2751     xo_buffer_t *xbp;
2752
2753     switch (xop->xo_style) {
2754     case XO_STYLE_TEXT:
2755         if (flags & XFF_ENCODE_ONLY)
2756             flags |= XFF_NO_OUTPUT;
2757         xo_format_data(xop, NULL, format, flen, flags);
2758         break;
2759
2760     case XO_STYLE_HTML:
2761         if (flags & XFF_ENCODE_ONLY)
2762             flags |= XFF_NO_OUTPUT;
2763         xo_buf_append_div(xop, "data", flags, name, nlen,
2764                           format, flen, encoding, elen);
2765         break;
2766
2767     case XO_STYLE_XML:
2768         /*
2769          * Even though we're not making output, we still need to
2770          * let the formatting code handle the va_arg popping.
2771          */
2772         if (flags & XFF_DISPLAY_ONLY) {
2773             flags |= XFF_NO_OUTPUT;
2774             xo_format_data(xop, NULL, format, flen, flags);
2775             break;
2776         }
2777
2778         if (encoding) {
2779             format = encoding;
2780             flen = elen;
2781         } else {
2782             char *enc  = alloca(flen + 1);
2783             memcpy(enc, format, flen);
2784             enc[flen] = '\0';
2785             format = xo_fix_encoding(xop, enc);
2786             flen = strlen(format);
2787         }
2788
2789         if (nlen == 0) {
2790             static char missing[] = "missing-field-name";
2791             xo_failure(xop, "missing field name: %s", format);
2792             name = missing;
2793             nlen = sizeof(missing) - 1;
2794         }
2795
2796         if (pretty)
2797             xo_buf_indent(xop, -1);
2798         xo_data_append(xop, "<", 1);
2799         xo_data_escape(xop, name, nlen);
2800
2801         if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
2802             xo_data_append(xop, xop->xo_attrs.xb_bufp,
2803                            xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
2804             xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
2805         }
2806
2807         /*
2808          * We indicate 'key' fields using the 'key' attribute.  While
2809          * this is really committing the crime of mixing meta-data with
2810          * data, it's often useful.  Especially when format meta-data is
2811          * difficult to come by.
2812          */
2813         if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) {
2814             static char attr[] = " key=\"key\"";
2815             xo_data_append(xop, attr, sizeof(attr) - 1);
2816         }
2817
2818         /*
2819          * Save the offset at which we'd place units.  See xo_format_units.
2820          */
2821         if (xop->xo_flags & XOF_UNITS) {
2822             xop->xo_flags |= XOF_UNITS_PENDING;
2823             xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
2824         }
2825
2826         xo_data_append(xop, ">", 1);
2827         xo_format_data(xop, NULL, format, flen, flags);
2828         xo_data_append(xop, "</", 2);
2829         xo_data_escape(xop, name, nlen);
2830         xo_data_append(xop, ">", 1);
2831         if (pretty)
2832             xo_data_append(xop, "\n", 1);
2833         break;
2834
2835     case XO_STYLE_JSON:
2836         if (flags & XFF_DISPLAY_ONLY) {
2837             flags |= XFF_NO_OUTPUT;
2838             xo_format_data(xop, NULL, format, flen, flags);
2839             break;
2840         }
2841
2842         if (encoding) {
2843             format = encoding;
2844             flen = elen;
2845         } else {
2846             char *enc  = alloca(flen + 1);
2847             memcpy(enc, format, flen);
2848             enc[flen] = '\0';
2849             format = xo_fix_encoding(xop, enc);
2850             flen = strlen(format);
2851         }
2852
2853         int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
2854
2855         xo_format_prep(xop, flags);
2856
2857         if (flags & XFF_QUOTE)
2858             quote = 1;
2859         else if (flags & XFF_NOQUOTE)
2860             quote = 0;
2861         else if (flen == 0) {
2862             quote = 0;
2863             format = "true";    /* JSON encodes empty tags as a boolean true */
2864             flen = 4;
2865         } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
2866             quote = 1;
2867         else
2868             quote = 0;
2869
2870         if (nlen == 0) {
2871             static char missing[] = "missing-field-name";
2872             xo_failure(xop, "missing field name: %s", format);
2873             name = missing;
2874             nlen = sizeof(missing) - 1;
2875         }
2876
2877         if (flags & XFF_LEAF_LIST) {
2878             if (first && pretty)
2879                 xo_buf_indent(xop, -1);
2880         } else {
2881             if (pretty)
2882                 xo_buf_indent(xop, -1);
2883             xo_data_append(xop, "\"", 1);
2884
2885             xbp = &xop->xo_data;
2886             int off = xbp->xb_curp - xbp->xb_bufp;
2887
2888             xo_data_escape(xop, name, nlen);
2889
2890             if (xop->xo_flags & XOF_UNDERSCORES) {
2891                 int now = xbp->xb_curp - xbp->xb_bufp;
2892                 for ( ; off < now; off++)
2893                     if (xbp->xb_bufp[off] == '-')
2894                         xbp->xb_bufp[off] = '_';
2895             }
2896             xo_data_append(xop, "\":", 2);
2897         }
2898
2899         if (pretty)
2900             xo_data_append(xop, " ", 1);
2901         if (quote)
2902             xo_data_append(xop, "\"", 1);
2903
2904         xo_format_data(xop, NULL, format, flen, flags);
2905
2906         if (quote)
2907             xo_data_append(xop, "\"", 1);
2908         break;
2909     }
2910 }
2911
2912 static void
2913 xo_format_content (xo_handle_t *xop, const char *class_name,
2914                    const char *xml_tag, int display_only,
2915                    const char *str, int len, const char *fmt, int flen)
2916 {
2917     switch (xop->xo_style) {
2918     case XO_STYLE_TEXT:
2919         if (len) {
2920             xo_data_append_content(xop, str, len);
2921         } else
2922             xo_format_data(xop, NULL, fmt, flen, 0);
2923         break;
2924
2925     case XO_STYLE_HTML:
2926         if (len == 0) {
2927             str = fmt;
2928             len = flen;
2929         }
2930
2931         xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0);
2932         break;
2933
2934     case XO_STYLE_XML:
2935         if (xml_tag) {
2936             if (len == 0) {
2937                 str = fmt;
2938                 len = flen;
2939             }
2940
2941             xo_open_container_h(xop, xml_tag);
2942             xo_format_value(xop, "message", 7, str, len, NULL, 0, 0);
2943             xo_close_container_h(xop, xml_tag);
2944
2945         } else {
2946             /*
2947              * Even though we don't care about labels, we need to do
2948              * enough parsing work to skip over the right bits of xo_vap.
2949              */
2950             if (len == 0)
2951                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2952         }
2953         break;
2954
2955     case XO_STYLE_JSON:
2956         /*
2957          * Even though we don't care about labels, we need to do
2958          * enough parsing work to skip over the right bits of xo_vap.
2959          */
2960         if (display_only) {
2961             if (len == 0)
2962                 xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
2963             break;
2964         }
2965         /* XXX need schem for representing errors in JSON */
2966         break;
2967     }
2968 }
2969
2970 static void
2971 xo_format_units (xo_handle_t *xop, const char *str, int len,
2972                  const char *fmt, int flen)
2973 {
2974     static char units_start_xml[] = " units=\"";
2975     static char units_start_html[] = " data-units=\"";
2976
2977     if (!(xop->xo_flags & XOF_UNITS_PENDING)) {
2978         xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen);
2979         return;
2980     }
2981
2982     xo_buffer_t *xbp = &xop->xo_data;
2983     int start = xop->xo_units_offset;
2984     int stop = xbp->xb_curp - xbp->xb_bufp;
2985
2986     if (xop->xo_style == XO_STYLE_XML)
2987         xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
2988     else if (xop->xo_style == XO_STYLE_HTML)
2989         xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
2990     else
2991         return;
2992
2993     if (len)
2994         xo_data_append(xop, str, len);
2995     else
2996         xo_format_data(xop, NULL, fmt, flen, 0);
2997
2998     xo_buf_append(xbp, "\"", 1);
2999
3000     int now = xbp->xb_curp - xbp->xb_bufp;
3001     int delta = now - stop;
3002     if (delta < 0) {            /* Strange; no output to move */
3003         xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
3004         return;
3005     }
3006
3007     /*
3008      * Now we're in it alright.  We've need to insert the unit value
3009      * we just created into the right spot.  We make a local copy,
3010      * move it and then insert our copy.  We know there's room in the
3011      * buffer, since we're just moving this around.
3012      */
3013     char *buf = alloca(delta);
3014
3015     memcpy(buf, xbp->xb_bufp + stop, delta);
3016     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3017     memmove(xbp->xb_bufp + start, buf, delta);
3018 }
3019
3020 static int
3021 xo_find_width (xo_handle_t *xop, const char *str, int len,
3022                  const char *fmt, int flen)
3023 {
3024     long width = 0;
3025     char *bp;
3026     char *cp;
3027
3028     if (len) {
3029         bp = alloca(len + 1);   /* Make local NUL-terminated copy of str */
3030         memcpy(bp, str, len);
3031         bp[len] = '\0';
3032
3033         width = strtol(bp, &cp, 0);
3034         if (width == LONG_MIN || width == LONG_MAX
3035             || bp == cp || *cp != '\0' ) {
3036             width = 0;
3037             xo_failure(xop, "invalid width for anchor: '%s'", bp);
3038         }
3039     } else if (flen) {
3040         if (flen != 2 || strncmp("%d", fmt, flen) != 0)
3041             xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
3042         if (!(xop->xo_flags & XOF_NO_VA_ARG))
3043             width = va_arg(xop->xo_vap, int);
3044     }
3045
3046     return width;
3047 }
3048
3049 static void
3050 xo_anchor_clear (xo_handle_t *xop)
3051 {
3052     xop->xo_flags &= ~XOF_ANCHOR;
3053     xop->xo_anchor_offset = 0;
3054     xop->xo_anchor_columns = 0;
3055     xop->xo_anchor_min_width = 0;
3056 }
3057
3058 /*
3059  * An anchor is a marker used to delay field width implications.
3060  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
3061  * We are looking for output like "     1/4/5"
3062  *
3063  * To make this work, we record the anchor and then return to
3064  * format it when the end anchor tag is seen.
3065  */
3066 static void
3067 xo_anchor_start (xo_handle_t *xop, const char *str, int len,
3068                  const char *fmt, int flen)
3069 {
3070     if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML)
3071         return;
3072
3073     if (xop->xo_flags & XOF_ANCHOR)
3074         xo_failure(xop, "the anchor already recording is discarded");
3075
3076     xop->xo_flags |= XOF_ANCHOR;
3077     xo_buffer_t *xbp = &xop->xo_data;
3078     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
3079     xop->xo_anchor_columns = 0;
3080
3081     /*
3082      * Now we find the width, if possible.  If it's not there,
3083      * we'll get it on the end anchor.
3084      */
3085     xop->xo_anchor_min_width = xo_find_width(xop, str, len, fmt, flen);
3086 }
3087
3088 static void
3089 xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
3090                  const char *fmt, int flen)
3091 {
3092     if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML)
3093         return;
3094
3095     if (!(xop->xo_flags & XOF_ANCHOR)) {
3096         xo_failure(xop, "no start anchor");
3097         return;
3098     }
3099
3100     xop->xo_flags &= ~XOF_UNITS_PENDING;
3101
3102     int width = xo_find_width(xop, str, len, fmt, flen);
3103     if (width == 0)
3104         width = xop->xo_anchor_min_width;
3105
3106     if (width == 0)             /* No width given; nothing to do */
3107         goto done;
3108
3109     xo_buffer_t *xbp = &xop->xo_data;
3110     int start = xop->xo_anchor_offset;
3111     int stop = xbp->xb_curp - xbp->xb_bufp;
3112     int abswidth = (width > 0) ? width : -width;
3113     int blen = abswidth - xop->xo_anchor_columns;
3114
3115     if (blen <= 0)              /* Already over width */
3116         goto done;
3117
3118     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
3119         xo_failure(xop, "width over %u are not supported",
3120                    XO_MAX_ANCHOR_WIDTH);
3121         goto done;
3122     }
3123
3124     /* Make a suitable padding field and emit it */
3125     char *buf = alloca(blen);
3126     memset(buf, ' ', blen);
3127     xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0);
3128
3129     if (width < 0)              /* Already left justified */
3130         goto done;
3131
3132     int now = xbp->xb_curp - xbp->xb_bufp;
3133     int delta = now - stop;
3134     if (delta < 0)              /* Strange; no output to move */
3135         goto done;
3136
3137     /*
3138      * Now we're in it alright.  We've need to insert the padding data
3139      * we just created (which might be an HTML <div> or text) before
3140      * the formatted data.  We make a local copy, move it and then
3141      * insert our copy.  We know there's room in the buffer, since
3142      * we're just moving this around.
3143      */
3144     if (delta > blen)
3145         buf = alloca(delta);    /* Expand buffer if needed */
3146
3147     memcpy(buf, xbp->xb_bufp + stop, delta);
3148     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
3149     memmove(xbp->xb_bufp + start, buf, delta);
3150
3151  done:
3152     xo_anchor_clear(xop);
3153 }
3154
3155 static int
3156 xo_do_emit (xo_handle_t *xop, const char *fmt)
3157 {
3158     int rc = 0;
3159     const char *cp, *sp, *ep, *basep;
3160     char *newp = NULL;
3161     int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0;
3162
3163     xop->xo_columns = 0;        /* Always reset it */
3164
3165     for (cp = fmt; *cp; ) {
3166         if (*cp == '\n') {
3167             xo_line_close(xop);
3168             xo_flush_h(xop);
3169             cp += 1;
3170             continue;
3171
3172         } else if (*cp == '{') {
3173             if (cp[1] == '{') { /* Start of {{escaped braces}} */
3174
3175                 cp += 2;        /* Skip over _both_ characters */
3176                 for (sp = cp; *sp; sp++) {
3177                     if (*sp == '}' && sp[1] == '}')
3178                         break;
3179                 }
3180                 if (*sp == '\0') {
3181                     xo_failure(xop, "missing closing '}}': %s", fmt);
3182                     return -1;
3183                 }
3184
3185                 xo_format_text(xop, cp, sp - cp);
3186
3187                 /* Move along the string, but don't run off the end */
3188                 if (*sp == '}' && sp[1] == '}')
3189                     sp += 2;
3190                 cp = *sp ? sp + 1 : sp;
3191                 continue;
3192             }
3193             /* Else fall thru to the code below */
3194
3195         } else {
3196             /* Normal text */
3197             for (sp = cp; *sp; sp++) {
3198                 if (*sp == '{' || *sp == '\n')
3199                     break;
3200             }
3201             xo_format_text(xop, cp, sp - cp);
3202
3203             cp = sp;
3204             continue;
3205         }
3206
3207         basep = cp + 1;
3208
3209         /*
3210          * We are looking at the start of a field definition.  The format is:
3211          *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
3212          * Modifiers are optional and include the following field types:
3213          *   'D': decoration; something non-text and non-data (colons, commmas)
3214          *   'E': error message
3215          *   'L': label; text preceding data
3216          *   'N': note; text following data
3217          *   'P': padding; whitespace
3218          *   'T': Title, where 'content' is a column title
3219          *   'U': Units, where 'content' is the unit label
3220          *   'V': value, where 'content' is the name of the field (the default)
3221          *   'W': warning message
3222          *   '[': start a section of anchored text
3223          *   ']': end a section of anchored text
3224          * The following flags are also supported:
3225          *   'c': flag: emit a colon after the label
3226          *   'd': field is only emitted for display formats (text and html)
3227          *   'e': field is only emitted for encoding formats (xml and json)
3228          *   'k': this field is a key, suitable for XPath predicates
3229          *   'l': a leaf-list, a simple list of values
3230          *   'n': no quotes around this field
3231          *   'q': add quotes around this field
3232          *   't': trim whitespace around the value
3233          *   'w': emit a blank after the label
3234          * The print-fmt and encode-fmt strings is the printf-style formating
3235          * for this data.  JSON and XML will use the encoding-fmt, if present.
3236          * If the encode-fmt is not provided, it defaults to the print-fmt.
3237          * If the print-fmt is not provided, it defaults to 's'.
3238          */
3239         unsigned ftype = 0, flags = 0;
3240         const char *content = NULL, *format = NULL, *encoding = NULL;
3241         int clen = 0, flen = 0, elen = 0;
3242
3243         for (sp = basep; sp; sp++) {
3244             if (*sp == ':' || *sp == '/' || *sp == '}')
3245                 break;
3246
3247             if (*sp == '\\') {
3248                 if (sp[1] == '\0') {
3249                     xo_failure(xop, "backslash at the end of string");
3250                     return -1;
3251                 }
3252                 sp += 1;
3253                 continue;
3254             }
3255
3256             switch (*sp) {
3257             case 'D':
3258             case 'E':
3259             case 'L':
3260             case 'N':
3261             case 'P':
3262             case 'T':
3263             case 'U':
3264             case 'V':
3265             case 'W':
3266             case '[':
3267             case ']':
3268                 if (ftype != 0) {
3269                     xo_failure(xop, "field descriptor uses multiple types: %s",
3270                                   fmt);
3271                     return -1;
3272                 }
3273                 ftype = *sp;
3274                 break;
3275
3276             case 'c':
3277                 flags |= XFF_COLON;
3278                 break;
3279
3280             case 'd':
3281                 flags |= XFF_DISPLAY_ONLY;
3282                 break;
3283
3284             case 'e':
3285                 flags |= XFF_ENCODE_ONLY;
3286                 break;
3287
3288             case 'k':
3289                 flags |= XFF_KEY;
3290                 break;
3291
3292             case 'l':
3293                 flags |= XFF_LEAF_LIST;
3294                 break;
3295
3296             case 'n':
3297                 flags |= XFF_NOQUOTE;
3298                 break;
3299
3300             case 'q':
3301                 flags |= XFF_QUOTE;
3302                 break;
3303
3304             case 't':
3305                 flags |= XFF_TRIM_WS;
3306                 break;
3307
3308             case 'w':
3309                 flags |= XFF_WS;
3310                 break;
3311
3312             default:
3313                 xo_failure(xop, "field descriptor uses unknown modifier: %s",
3314                               fmt);
3315                 /*
3316                  * No good answer here; a bad format will likely
3317                  * mean a core file.  We just return and hope
3318                  * the caller notices there's no output, and while
3319                  * that seems, well, bad.  There's nothing better.
3320                  */
3321                 return -1;
3322             }
3323         }
3324
3325         if (*sp == ':') {
3326             for (ep = ++sp; *sp; sp++) {
3327                 if (*sp == '}' || *sp == '/')
3328                     break;
3329                 if (*sp == '\\') {
3330                     if (sp[1] == '\0') {
3331                         xo_failure(xop, "backslash at the end of string");
3332                         return -1;
3333                     }
3334                     sp += 1;
3335                     continue;
3336                 }
3337             }
3338             if (ep != sp) {
3339                 clen = sp - ep;
3340                 content = ep;
3341             }
3342         } else {
3343             xo_failure(xop, "missing content (':'): %s", fmt);
3344             return -1;
3345         }
3346
3347         if (*sp == '/') {
3348             for (ep = ++sp; *sp; sp++) {
3349                 if (*sp == '}' || *sp == '/')
3350                     break;
3351                 if (*sp == '\\') {
3352                     if (sp[1] == '\0') {
3353                         xo_failure(xop, "backslash at the end of string");
3354                         return -1;
3355                     }
3356                     sp += 1;
3357                     continue;
3358                 }
3359             }
3360             flen = sp - ep;
3361             format = ep;
3362         }
3363
3364         if (*sp == '/') {
3365             for (ep = ++sp; *sp; sp++) {
3366                 if (*sp == '}')
3367                     break;
3368             }
3369             elen = sp - ep;
3370             encoding = ep;
3371         }
3372
3373         if (*sp == '}') {
3374             sp += 1;
3375         } else {
3376             xo_failure(xop, "missing closing '}': %s", fmt);
3377             return -1;
3378         }
3379
3380         if (format == NULL && ftype != '[' && ftype != ']' ) {
3381             format = "%s";
3382             flen = 2;
3383         }
3384
3385         if (ftype == 0 || ftype == 'V')
3386             xo_format_value(xop, content, clen, format, flen,
3387                             encoding, elen, flags);
3388         else if (ftype == 'D')
3389             xo_format_content(xop, "decoration", NULL, 1,
3390                               content, clen, format, flen);
3391         else if (ftype == 'E')
3392             xo_format_content(xop, "error", "error", 0,
3393                               content, clen, format, flen);
3394         else if (ftype == 'L')
3395             xo_format_content(xop, "label", NULL, 1,
3396                               content, clen, format, flen);
3397         else if (ftype == 'N')
3398             xo_format_content(xop, "note", NULL, 1,
3399                               content, clen, format, flen);
3400         else if (ftype == 'P')
3401             xo_format_content(xop, "padding", NULL, 1,
3402                               content, clen, format, flen);
3403         else if (ftype == 'T')
3404             xo_format_title(xop, content, clen, format, flen);
3405         else if (ftype == 'U') {
3406             if (flags & XFF_WS)
3407                 xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
3408             xo_format_units(xop, content, clen, format, flen);
3409         } else if (ftype == 'W')
3410             xo_format_content(xop, "warning", "warning", 0,
3411                               content, clen, format, flen);
3412         else if (ftype == '[')
3413             xo_anchor_start(xop, content, clen, format, flen);
3414         else if (ftype == ']')
3415             xo_anchor_stop(xop, content, clen, format, flen);
3416
3417         if (flags & XFF_COLON)
3418             xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0);
3419         if (ftype != 'U' && (flags & XFF_WS))
3420             xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
3421
3422         cp += sp - basep + 1;
3423         if (newp) {
3424             xo_free(newp);
3425             newp = NULL;
3426         }
3427     }
3428
3429     /* If we don't have an anchor, write the text out */
3430     if (flush && !(xop->xo_flags & XOF_ANCHOR))
3431         xo_write(xop);
3432
3433     return (rc < 0) ? rc : (int) xop->xo_columns;
3434 }
3435
3436 int
3437 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
3438 {
3439     int rc;
3440
3441     xop = xo_default(xop);
3442     va_copy(xop->xo_vap, vap);
3443     rc = xo_do_emit(xop, fmt);
3444     va_end(xop->xo_vap);
3445     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3446
3447     return rc;
3448 }
3449
3450 int
3451 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
3452 {
3453     int rc;
3454
3455     xop = xo_default(xop);
3456     va_start(xop->xo_vap, fmt);
3457     rc = xo_do_emit(xop, fmt);
3458     va_end(xop->xo_vap);
3459     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3460
3461     return rc;
3462 }
3463
3464 int
3465 xo_emit (const char *fmt, ...)
3466 {
3467     xo_handle_t *xop = xo_default(NULL);
3468     int rc;
3469
3470     va_start(xop->xo_vap, fmt);
3471     rc = xo_do_emit(xop, fmt);
3472     va_end(xop->xo_vap);
3473     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
3474
3475     return rc;
3476 }
3477
3478 int
3479 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
3480 {
3481     const int extra = 5;        /* space, equals, quote, quote, and nul */
3482     xop = xo_default(xop);
3483
3484     if (xop->xo_style != XO_STYLE_XML)
3485         return 0;
3486
3487     int nlen = strlen(name);
3488     xo_buffer_t *xbp = &xop->xo_attrs;
3489
3490     if (!xo_buf_has_room(xbp, nlen + extra))
3491         return -1;
3492
3493     *xbp->xb_curp++ = ' ';
3494     memcpy(xbp->xb_curp, name, nlen);
3495     xbp->xb_curp += nlen;
3496     *xbp->xb_curp++ = '=';
3497     *xbp->xb_curp++ = '"';
3498
3499     int rc = xo_vsnprintf(xop, xbp, fmt, vap);
3500
3501     if (rc > 0) {
3502         rc = xo_escape_xml(xbp, rc, 1);
3503         xbp->xb_curp += rc;
3504     }
3505
3506     if (!xo_buf_has_room(xbp, 2))
3507         return -1;
3508
3509     *xbp->xb_curp++ = '"';
3510     *xbp->xb_curp = '\0';
3511
3512     return rc + nlen + extra;
3513 }
3514
3515 int
3516 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
3517 {
3518     int rc;
3519     va_list vap;
3520
3521     va_start(vap, fmt);
3522     rc = xo_attr_hv(xop, name, fmt, vap);
3523     va_end(vap);
3524
3525     return rc;
3526 }
3527
3528 int
3529 xo_attr (const char *name, const char *fmt, ...)
3530 {
3531     int rc;
3532     va_list vap;
3533
3534     va_start(vap, fmt);
3535     rc = xo_attr_hv(NULL, name, fmt, vap);
3536     va_end(vap);
3537
3538     return rc;
3539 }
3540
3541 static void
3542 xo_stack_set_flags (xo_handle_t *xop)
3543 {
3544     if (xop->xo_flags & XOF_NOT_FIRST) {
3545         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3546
3547         xsp->xs_flags |= XSF_NOT_FIRST;
3548         xop->xo_flags &= ~XOF_NOT_FIRST;
3549     }
3550 }
3551
3552 static void
3553 xo_depth_change (xo_handle_t *xop, const char *name,
3554                  int delta, int indent, xo_xsf_flags_t flags)
3555 {
3556     if (xop->xo_flags & XOF_DTRT)
3557         flags |= XSF_DTRT;
3558
3559     if (delta >= 0) {                   /* Push operation */
3560         if (xo_depth_check(xop, xop->xo_depth + delta))
3561             return;
3562
3563         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
3564         xsp->xs_flags = flags;
3565         xo_stack_set_flags(xop);
3566
3567         unsigned save = (xop->xo_flags & (XOF_XPATH | XOF_WARN | XOF_DTRT));
3568         save |= (flags & XSF_DTRT);
3569
3570         if (name && save) {
3571             int len = strlen(name) + 1;
3572             char *cp = xo_realloc(NULL, len);
3573             if (cp) {
3574                 memcpy(cp, name, len);
3575                 xsp->xs_name = cp;
3576             }
3577         }
3578
3579     } else {                    /* Pop operation */
3580         if (xop->xo_depth == 0) {
3581             if (!(xop->xo_flags & XOF_IGNORE_CLOSE))
3582                 xo_failure(xop, "close with empty stack: '%s'", name);
3583             return;
3584         }
3585
3586         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3587         if (xop->xo_flags & XOF_WARN) {
3588             const char *top = xsp->xs_name;
3589             if (top && strcmp(name, top) != 0) {
3590                 xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
3591                               name, top);
3592                 return;
3593             } 
3594             if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
3595                 xo_failure(xop, "list close on list confict: '%s'",
3596                               name);
3597                 return;
3598             }
3599             if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
3600                 xo_failure(xop, "list close on instance confict: '%s'",
3601                               name);
3602                 return;
3603             }
3604         }
3605
3606         if (xsp->xs_name) {
3607             xo_free(xsp->xs_name);
3608             xsp->xs_name = NULL;
3609         }
3610         if (xsp->xs_keys) {
3611             xo_free(xsp->xs_keys);
3612             xsp->xs_keys = NULL;
3613         }
3614     }
3615
3616     xop->xo_depth += delta;     /* Record new depth */
3617     xop->xo_indent += indent;
3618 }
3619
3620 void
3621 xo_set_depth (xo_handle_t *xop, int depth)
3622 {
3623     xop = xo_default(xop);
3624
3625     if (xo_depth_check(xop, depth))
3626         return;
3627
3628     xop->xo_depth += depth;
3629     xop->xo_indent += depth;
3630 }
3631
3632 static xo_xsf_flags_t
3633 xo_stack_flags (unsigned xflags)
3634 {
3635     if (xflags & XOF_DTRT)
3636         return XSF_DTRT;
3637     return 0;
3638 }
3639
3640 static int
3641 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
3642 {
3643     xop = xo_default(xop);
3644
3645     int rc = 0;
3646     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3647     const char *pre_nl = "";
3648
3649     if (name == NULL) {
3650         xo_failure(xop, "NULL passed for container name");
3651         name = XO_FAILURE_NAME;
3652     }
3653
3654     flags |= xop->xo_flags;     /* Pick up handle flags */
3655
3656     switch (xop->xo_style) {
3657     case XO_STYLE_XML:
3658         rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "",
3659                      name, ppn);
3660         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3661         break;
3662
3663     case XO_STYLE_JSON:
3664         xo_stack_set_flags(xop);
3665
3666         if (!(xop->xo_flags & XOF_NO_TOP)) {
3667             if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
3668                 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
3669                 xop->xo_flags |= XOF_TOP_EMITTED;
3670             }
3671         }
3672
3673         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3674             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3675         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3676
3677         rc = xo_printf(xop, "%s%*s\"%s\": {%s",
3678                        pre_nl, xo_indent(xop), "", name, ppn);
3679         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3680         break;
3681
3682     case XO_STYLE_HTML:
3683     case XO_STYLE_TEXT:
3684         xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags));
3685         break;
3686     }
3687
3688     return rc;
3689 }
3690
3691 int
3692 xo_open_container_h (xo_handle_t *xop, const char *name)
3693 {
3694     return xo_open_container_hf(xop, 0, name);
3695 }
3696
3697 int
3698 xo_open_container (const char *name)
3699 {
3700     return xo_open_container_hf(NULL, 0, name);
3701 }
3702
3703 int
3704 xo_open_container_hd (xo_handle_t *xop, const char *name)
3705 {
3706     return xo_open_container_hf(xop, XOF_DTRT, name);
3707 }
3708
3709 int
3710 xo_open_container_d (const char *name)
3711 {
3712     return xo_open_container_hf(NULL, XOF_DTRT, name);
3713 }
3714
3715 int
3716 xo_close_container_h (xo_handle_t *xop, const char *name)
3717 {
3718     xop = xo_default(xop);
3719
3720     int rc = 0;
3721     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3722     const char *pre_nl = "";
3723
3724     if (name == NULL) {
3725         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3726         if (!(xsp->xs_flags & XSF_DTRT))
3727             xo_failure(xop, "missing name without 'dtrt' mode");
3728
3729         name = xsp->xs_name;
3730         if (name) {
3731             int len = strlen(name) + 1;
3732             /* We need to make a local copy; xo_depth_change will free it */
3733             char *cp = alloca(len);
3734             memcpy(cp, name, len);
3735             name = cp;
3736         } else
3737             name = XO_FAILURE_NAME;
3738     }
3739
3740     switch (xop->xo_style) {
3741     case XO_STYLE_XML:
3742         xo_depth_change(xop, name, -1, -1, 0);
3743         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
3744         break;
3745
3746     case XO_STYLE_JSON:
3747         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3748         ppn = (xop->xo_depth <= 1) ? "\n" : "";
3749
3750         xo_depth_change(xop, name, -1, -1, 0);
3751         rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
3752         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3753         break;
3754
3755     case XO_STYLE_HTML:
3756     case XO_STYLE_TEXT:
3757         xo_depth_change(xop, name, -1, 0, 0);
3758         break;
3759     }
3760
3761     return rc;
3762 }
3763
3764 int
3765 xo_close_container (const char *name)
3766 {
3767     return xo_close_container_h(NULL, name);
3768 }
3769
3770 int
3771 xo_close_container_hd (xo_handle_t *xop)
3772 {
3773     return xo_close_container_h(xop, NULL);
3774 }
3775
3776 int
3777 xo_close_container_d (void)
3778 {
3779     return xo_close_container_h(NULL, NULL);
3780 }
3781
3782 static int
3783 xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
3784 {
3785     xop = xo_default(xop);
3786
3787     if (xop->xo_style != XO_STYLE_JSON)
3788         return 0;
3789
3790     int rc = 0;
3791     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3792     const char *pre_nl = "";
3793
3794     if (!(xop->xo_flags & XOF_NO_TOP)) {
3795         if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
3796             xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
3797             xop->xo_flags |= XOF_TOP_EMITTED;
3798         }
3799     }
3800
3801     if (name == NULL) {
3802         xo_failure(xop, "NULL passed for list name");
3803         name = XO_FAILURE_NAME;
3804     }
3805
3806     xo_stack_set_flags(xop);
3807
3808     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3809         pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3810     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3811
3812     rc = xo_printf(xop, "%s%*s\"%s\": [%s",
3813                    pre_nl, xo_indent(xop), "", name, ppn);
3814     xo_depth_change(xop, name, 1, 1, XSF_LIST | xo_stack_flags(flags));
3815
3816     return rc;
3817 }
3818
3819 int
3820 xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
3821 {
3822     return xo_open_list_hf(xop, 0, name);
3823 }
3824
3825 int
3826 xo_open_list (const char *name)
3827 {
3828     return xo_open_list_hf(NULL, 0, name);
3829 }
3830
3831 int
3832 xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
3833 {
3834     return xo_open_list_hf(xop, XOF_DTRT, name);
3835 }
3836
3837 int
3838 xo_open_list_d (const char *name)
3839 {
3840     return xo_open_list_hf(NULL, XOF_DTRT, name);
3841 }
3842
3843 int
3844 xo_close_list_h (xo_handle_t *xop, const char *name)
3845 {
3846     int rc = 0;
3847     const char *pre_nl = "";
3848
3849     xop = xo_default(xop);
3850
3851     if (xop->xo_style != XO_STYLE_JSON)
3852         return 0;
3853
3854     if (name == NULL) {
3855         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3856         if (!(xsp->xs_flags & XSF_DTRT))
3857             xo_failure(xop, "missing name without 'dtrt' mode");
3858
3859         name = xsp->xs_name;
3860         if (name) {
3861             int len = strlen(name) + 1;
3862             /* We need to make a local copy; xo_depth_change will free it */
3863             char *cp = alloca(len);
3864             memcpy(cp, name, len);
3865             name = cp;
3866         } else
3867             name = XO_FAILURE_NAME;
3868     }
3869
3870     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3871         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3872     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3873
3874     xo_depth_change(xop, name, -1, -1, XSF_LIST);
3875     rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
3876     xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3877
3878     return rc;
3879 }
3880
3881 int
3882 xo_close_list (const char *name)
3883 {
3884     return xo_close_list_h(NULL, name);
3885 }
3886
3887 int
3888 xo_close_list_hd (xo_handle_t *xop)
3889 {
3890     return xo_close_list_h(xop, NULL);
3891 }
3892
3893 int
3894 xo_close_list_d (void)
3895 {
3896     return xo_close_list_h(NULL, NULL);
3897 }
3898
3899 static int
3900 xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
3901 {
3902     xop = xo_default(xop);
3903
3904     int rc = 0;
3905     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3906     const char *pre_nl = "";
3907
3908     flags |= xop->xo_flags;
3909
3910     if (name == NULL) {
3911         xo_failure(xop, "NULL passed for instance name");
3912         name = XO_FAILURE_NAME;
3913     }
3914
3915     switch (xop->xo_style) {
3916     case XO_STYLE_XML:
3917         rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", name, ppn);
3918         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3919         break;
3920
3921     case XO_STYLE_JSON:
3922         xo_stack_set_flags(xop);
3923
3924         if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
3925             pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
3926         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3927
3928         rc = xo_printf(xop, "%s%*s{%s",
3929                        pre_nl, xo_indent(xop), "", ppn);
3930         xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags));
3931         break;
3932
3933     case XO_STYLE_HTML:
3934     case XO_STYLE_TEXT:
3935         xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags));
3936         break;
3937     }
3938
3939     return rc;
3940 }
3941
3942 int
3943 xo_open_instance_h (xo_handle_t *xop, const char *name)
3944 {
3945     return xo_open_instance_hf(xop, 0, name);
3946 }
3947
3948 int
3949 xo_open_instance (const char *name)
3950 {
3951     return xo_open_instance_hf(NULL, 0, name);
3952 }
3953
3954 int
3955 xo_open_instance_hd (xo_handle_t *xop, const char *name)
3956 {
3957     return xo_open_instance_hf(xop, XOF_DTRT, name);
3958 }
3959
3960 int
3961 xo_open_instance_d (const char *name)
3962 {
3963     return xo_open_instance_hf(NULL, XOF_DTRT, name);
3964 }
3965
3966 int
3967 xo_close_instance_h (xo_handle_t *xop, const char *name)
3968 {
3969     xop = xo_default(xop);
3970
3971     int rc = 0;
3972     const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3973     const char *pre_nl = "";
3974
3975     if (name == NULL) {
3976         xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3977         if (!(xsp->xs_flags & XSF_DTRT))
3978             xo_failure(xop, "missing name without 'dtrt' mode");
3979
3980         name = xsp->xs_name;
3981         if (name) {
3982             int len = strlen(name) + 1;
3983             /* We need to make a local copy; xo_depth_change will free it */
3984             char *cp = alloca(len);
3985             memcpy(cp, name, len);
3986             name = cp;
3987         } else
3988             name = XO_FAILURE_NAME;
3989     }
3990
3991     switch (xop->xo_style) {
3992     case XO_STYLE_XML:
3993         xo_depth_change(xop, name, -1, -1, 0);
3994         rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
3995         break;
3996
3997     case XO_STYLE_JSON:
3998         pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
3999
4000         xo_depth_change(xop, name, -1, -1, 0);
4001         rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
4002         xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4003         break;
4004
4005     case XO_STYLE_HTML:
4006     case XO_STYLE_TEXT:
4007         xo_depth_change(xop, name, -1, 0, 0);
4008         break;
4009     }
4010
4011     return rc;
4012 }
4013
4014 int
4015 xo_close_instance (const char *name)
4016 {
4017     return xo_close_instance_h(NULL, name);
4018 }
4019
4020 int
4021 xo_close_instance_hd (xo_handle_t *xop)
4022 {
4023     return xo_close_instance_h(xop, NULL);
4024 }
4025
4026 int
4027 xo_close_instance_d (void)
4028 {
4029     return xo_close_instance_h(NULL, NULL);
4030 }
4031
4032 void
4033 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
4034                xo_close_func_t close_func)
4035 {
4036     xop = xo_default(xop);
4037
4038     xop->xo_opaque = opaque;
4039     xop->xo_write = write_func;
4040     xop->xo_close = close_func;
4041 }
4042
4043 void
4044 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
4045 {
4046     xo_realloc = realloc_func;
4047     xo_free = free_func;
4048 }
4049
4050 void
4051 xo_flush_h (xo_handle_t *xop)
4052 {
4053     static char div_close[] = "</div>";
4054
4055     xop = xo_default(xop);
4056
4057     switch (xop->xo_style) {
4058     case XO_STYLE_HTML:
4059         if (xop->xo_flags & XOF_DIV_OPEN) {
4060             xop->xo_flags &= ~XOF_DIV_OPEN;
4061             xo_data_append(xop, div_close, sizeof(div_close) - 1);
4062
4063             if (xop->xo_flags & XOF_PRETTY)
4064                 xo_data_append(xop, "\n", 1);
4065         }
4066         break;
4067     }
4068
4069     xo_write(xop);
4070 }
4071
4072 void
4073 xo_flush (void)
4074 {
4075     xo_flush_h(NULL);
4076 }
4077
4078 void
4079 xo_finish_h (xo_handle_t *xop)
4080 {
4081     const char *cp = "";
4082     xop = xo_default(xop);
4083
4084     switch (xop->xo_style) {
4085     case XO_STYLE_JSON:
4086         if (!(xop->xo_flags & XOF_NO_TOP)) {
4087             if (xop->xo_flags & XOF_TOP_EMITTED)
4088                 xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */
4089             else
4090                 cp = "{ ";
4091             xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
4092         }
4093         break;
4094     }
4095
4096     xo_flush_h(xop);
4097 }
4098
4099 void
4100 xo_finish (void)
4101 {
4102     xo_finish_h(NULL);
4103 }
4104
4105 /*
4106  * Generate an error message, such as would be displayed on stderr
4107  */
4108 void
4109 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
4110 {
4111     xop = xo_default(xop);
4112
4113     /*
4114      * If the format string doesn't end with a newline, we pop
4115      * one on ourselves.
4116      */
4117     int len = strlen(fmt);
4118     if (len > 0 && fmt[len - 1] != '\n') {
4119         char *newfmt = alloca(len + 2);
4120         memcpy(newfmt, fmt, len);
4121         newfmt[len] = '\n';
4122         newfmt[len] = '\0';
4123         fmt = newfmt;
4124     }
4125
4126     switch (xop->xo_style) {
4127     case XO_STYLE_TEXT:
4128         vfprintf(stderr, fmt, vap);
4129         break;
4130
4131     case XO_STYLE_HTML:
4132         va_copy(xop->xo_vap, vap);
4133         
4134         xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
4135
4136         if (xop->xo_flags & XOF_DIV_OPEN)
4137             xo_line_close(xop);
4138
4139         xo_write(xop);
4140
4141         va_end(xop->xo_vap);
4142         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4143         break;
4144
4145     case XO_STYLE_XML:
4146         va_copy(xop->xo_vap, vap);
4147
4148         xo_open_container_h(xop, "error");
4149         xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
4150         xo_close_container_h(xop, "error");
4151
4152         va_end(xop->xo_vap);
4153         bzero(&xop->xo_vap, sizeof(xop->xo_vap));
4154         break;
4155     }
4156 }
4157
4158 void
4159 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
4160 {
4161     va_list vap;
4162
4163     va_start(vap, fmt);
4164     xo_error_hv(xop, fmt, vap);
4165     va_end(vap);
4166 }
4167
4168 /*
4169  * Generate an error message, such as would be displayed on stderr
4170  */
4171 void
4172 xo_error (const char *fmt, ...)
4173 {
4174     va_list vap;
4175
4176     va_start(vap, fmt);
4177     xo_error_hv(NULL, fmt, vap);
4178     va_end(vap);
4179 }
4180
4181 int
4182 xo_parse_args (int argc, char **argv)
4183 {
4184     static char libxo_opt[] = "--libxo";
4185     char *cp;
4186     int i, save;
4187
4188     /* Save our program name for xo_err and friends */
4189     xo_program = argv[0];
4190     cp = strrchr(xo_program, '/');
4191     if (cp)
4192         xo_program = cp + 1;
4193
4194     for (save = i = 1; i < argc; i++) {
4195         if (argv[i] == NULL
4196             || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
4197             if (save != i)
4198                 argv[save] = argv[i];
4199             save += 1;
4200             continue;
4201         }
4202
4203         cp = argv[i] + sizeof(libxo_opt) - 1;
4204         if (*cp == 0) {
4205             cp = argv[++i];
4206             if (cp == 0) {
4207                 xo_warnx("missing libxo option");
4208                 return -1;
4209             }
4210                 
4211             if (xo_set_options(NULL, cp) < 0)
4212                 return -1;
4213         } else if (*cp == ':') {
4214             if (xo_set_options(NULL, cp) < 0)
4215                 return -1;
4216
4217         } else if (*cp == '=') {
4218             if (xo_set_options(NULL, ++cp) < 0)
4219                 return -1;
4220
4221         } else if (*cp == '-') {
4222             cp += 1;
4223             if (strcmp(cp, "check") == 0) {
4224                 exit(XO_HAS_LIBXO);
4225
4226             } else {
4227                 xo_warnx("unknown libxo option: '%s'", argv[i]);
4228                 return -1;
4229             }
4230         } else {
4231                 xo_warnx("unknown libxo option: '%s'", argv[i]);
4232             return -1;
4233         }
4234     }
4235
4236     argv[save] = NULL;
4237     return save;
4238 }
4239
4240 #ifdef UNIT_TEST
4241 int
4242 main (int argc, char **argv)
4243 {
4244     static char base_grocery[] = "GRO";
4245     static char base_hardware[] = "HRD";
4246     struct item {
4247         const char *i_title;
4248         int i_sold;
4249         int i_instock;
4250         int i_onorder;
4251         const char *i_sku_base;
4252         int i_sku_num;
4253     };
4254     struct item list[] = {
4255         { "gum&this&that", 1412, 54, 10, base_grocery, 415 },
4256         { "<rope>", 85, 4, 2, base_hardware, 212 },
4257         { "ladder", 0, 2, 1, base_hardware, 517 },
4258         { "\"bolt\"", 4123, 144, 42, base_hardware, 632 },
4259         { "water\\blue", 17, 14, 2, base_grocery, 2331 },
4260         { NULL, 0, 0, 0, NULL, 0 }
4261     };
4262     struct item list2[] = {
4263         { "fish", 1321, 45, 1, base_grocery, 533 },
4264         { NULL, 0, 0, 0, NULL, 0 }
4265     };
4266     struct item *ip;
4267     xo_info_t info[] = {
4268         { "in-stock", "number", "Number of items in stock" },
4269         { "name", "string", "Name of the item" },
4270         { "on-order", "number", "Number of items on order" },
4271         { "sku", "string", "Stock Keeping Unit" },
4272         { "sold", "number", "Number of items sold" },
4273         { NULL, NULL, NULL },
4274     };
4275     int info_count = (sizeof(info) / sizeof(info[0])) - 1;
4276     
4277     argc = xo_parse_args(argc, argv);
4278     if (argc < 0)
4279         exit(1);
4280
4281     xo_set_info(NULL, info, info_count);
4282
4283     xo_open_container_h(NULL, "top");
4284
4285     xo_open_container("data");
4286     xo_open_list("item");
4287
4288     xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}"
4289             "{T:On Order/%12s}{T:SKU/%5s}\n");
4290
4291     for (ip = list; ip->i_title; ip++) {
4292         xo_open_instance("item");
4293
4294         xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}"
4295                 "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n",
4296                 ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder,
4297                 ip->i_sku_base, ip->i_sku_num);
4298
4299         xo_close_instance("item");
4300     }
4301
4302     xo_close_list("item");
4303     xo_close_container("data");
4304
4305     xo_emit("\n\n");
4306
4307     xo_open_container("data");
4308     xo_open_list("item");
4309
4310     for (ip = list; ip->i_title; ip++) {
4311         xo_open_instance("item");
4312
4313         xo_attr("fancy", "%s%d", "item", ip - list);
4314         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
4315         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n",
4316                 ip->i_sold, ip->i_sold ? ".0" : "", 44);
4317         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
4318         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
4319         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
4320                 ip->i_sku_base, ip->i_sku_num);
4321
4322         xo_close_instance("item");
4323     }
4324
4325     xo_close_list("item");
4326     xo_close_container("data");
4327
4328     xo_open_container("data");
4329     xo_open_list("item");
4330
4331     for (ip = list2; ip->i_title; ip++) {
4332         xo_open_instance("item");
4333
4334         xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
4335         xo_emit("{P:   }{L:Total sold}: {n:sold/%u%s}\n",
4336                 ip->i_sold, ip->i_sold ? ".0" : "");
4337         xo_emit("{P:   }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
4338         xo_emit("{P:   }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
4339         xo_emit("{P:   }{L:SKU}: {q:sku/%s-000-%u}\n",
4340                 ip->i_sku_base, ip->i_sku_num);
4341
4342         xo_open_list("month");
4343
4344         const char *months[] = { "Jan", "Feb", "Mar", NULL };
4345         int discounts[] = { 10, 20, 25, 0 };
4346         int i;
4347         for (i = 0; months[i]; i++) {
4348             xo_open_instance("month");
4349             xo_emit("{P:       }"
4350                     "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n",
4351                     months[i], discounts[i]);
4352             xo_close_instance("month");
4353         }
4354         
4355         xo_close_list("month");
4356
4357         xo_close_instance("item");
4358     }
4359
4360     xo_close_list("item");
4361     xo_close_container("data");
4362
4363     xo_close_container_h(NULL, "top");
4364
4365     xo_finish();
4366
4367     return 0;
4368 }
4369 #endif /* UNIT_TEST */