libxo.c revision 274672
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
30const char xo_version[] = LIBXO_VERSION;
31const 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 */
48typedef 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 */
55typedef 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 */
66typedef 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 */
76struct 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 */
107typedef 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 */
174typedef 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 */
197static xo_handle_t xo_default_handle;
198static int xo_default_inited;
199static int xo_locale_inited;
200static 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 */
206static xo_realloc_func_t xo_realloc = realloc;
207static xo_free_func_t xo_free = free;
208
209/* Forward declarations */
210static void
211xo_failure (xo_handle_t *xop, const char *fmt, ...);
212
213static void
214xo_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
219static void
220xo_anchor_clear (xo_handle_t *xop);
221
222/*
223 * Callback to write data to a FILE pointer
224 */
225static int
226xo_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 */
235static void
236xo_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 */
245static void
246xo_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 */
256static void
257xo_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
264static int
265xo_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
287void
288xo_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 */
298static void
299xo_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 */
345static void
346xo_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 */
360static int
361xo_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 */
389static xo_handle_t *
390xo_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 */
405static int
406xo_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
421static void
422xo_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
436static char xo_xml_amp[] = "&amp;";
437static char xo_xml_lt[] = "&lt;";
438static char xo_xml_gt[] = "&gt;";
439static char xo_xml_quot[] = "&quot;";
440
441static int
442xo_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
496static int
497xo_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 */
538static void
539xo_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
548static void
549xo_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 */
575static void
576xo_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 */
595static int
596xo_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 */
640static int
641xo_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
673static int
674xo_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 */
691static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
692
693static int
694xo_is_utf8 (char ch)
695{
696    return (ch & 0x80);
697}
698
699static int
700xo_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
723static int
724xo_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 */
758static wchar_t
759xo_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 */
779static int
780xo_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
800static void
801xo_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
819static int
820xo_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
862static void
863xo_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 */
917static void
918xo_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 */
926static void
927xo_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 */
937static void
938xo_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
1016void
1017xo_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
1026void
1027xo_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
1036void
1037xo_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
1047void
1048xo_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
1057void
1058xo_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
1070void
1071xo_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
1082void
1083xo_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 */
1099void
1100xo_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
1219void
1220xo_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
1229void
1230xo_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
1239void
1240xo_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
1250static void
1251xo_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 */
1272xo_handle_t *
1273xo_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 */
1295xo_handle_t *
1296xo_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 */
1313void
1314xo_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 */
1341void
1342xo_set_style (xo_handle_t *xop, xo_style_t style)
1343{
1344    xop = xo_default(xop);
1345    xop->xo_style = style;
1346}
1347
1348xo_style_t
1349xo_get_style (xo_handle_t *xop)
1350{
1351    xop = xo_default(xop);
1352    return xop->xo_style;
1353}
1354
1355static int
1356xo_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 */
1374static unsigned
1375xo_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
1411int
1412xo_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 */
1430int
1431xo_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 */
1559void
1560xo_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
1567xo_xof_flags_t
1568xo_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 */
1583void
1584xo_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 */
1610void
1611xo_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 */
1631void
1632xo_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 */
1648void
1649xo_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
1656static void
1657xo_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
1678static void
1679xo_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
1701static int
1702xo_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
1711static xo_info_t *
1712xo_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 */
1730static int
1731xo_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
1748static int
1749xo_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
1919static int
1920xo_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
2039static void
2040xo_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
2056static void
2057xo_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
2064static int
2065xo_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
2097static int
2098xo_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
2405static char *
2406xo_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
2424static void
2425xo_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
2599static void
2600xo_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
2613static void
2614xo_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
2721static void
2722xo_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 */
2734void
2735xo_arg (xo_handle_t *xop);
2736void
2737xo_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
2744static void
2745xo_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
2912static void
2913xo_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
2970static void
2971xo_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
3020static int
3021xo_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
3049static void
3050xo_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 */
3066static void
3067xo_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
3088static void
3089xo_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
3155static int
3156xo_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
3436int
3437xo_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
3450int
3451xo_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
3464int
3465xo_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
3478int
3479xo_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
3515int
3516xo_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
3528int
3529xo_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
3541static void
3542xo_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
3552static void
3553xo_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
3620void
3621xo_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
3632static xo_xsf_flags_t
3633xo_stack_flags (unsigned xflags)
3634{
3635    if (xflags & XOF_DTRT)
3636	return XSF_DTRT;
3637    return 0;
3638}
3639
3640static int
3641xo_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
3691int
3692xo_open_container_h (xo_handle_t *xop, const char *name)
3693{
3694    return xo_open_container_hf(xop, 0, name);
3695}
3696
3697int
3698xo_open_container (const char *name)
3699{
3700    return xo_open_container_hf(NULL, 0, name);
3701}
3702
3703int
3704xo_open_container_hd (xo_handle_t *xop, const char *name)
3705{
3706    return xo_open_container_hf(xop, XOF_DTRT, name);
3707}
3708
3709int
3710xo_open_container_d (const char *name)
3711{
3712    return xo_open_container_hf(NULL, XOF_DTRT, name);
3713}
3714
3715int
3716xo_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
3764int
3765xo_close_container (const char *name)
3766{
3767    return xo_close_container_h(NULL, name);
3768}
3769
3770int
3771xo_close_container_hd (xo_handle_t *xop)
3772{
3773    return xo_close_container_h(xop, NULL);
3774}
3775
3776int
3777xo_close_container_d (void)
3778{
3779    return xo_close_container_h(NULL, NULL);
3780}
3781
3782static int
3783xo_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
3819int
3820xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
3821{
3822    return xo_open_list_hf(xop, 0, name);
3823}
3824
3825int
3826xo_open_list (const char *name)
3827{
3828    return xo_open_list_hf(NULL, 0, name);
3829}
3830
3831int
3832xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
3833{
3834    return xo_open_list_hf(xop, XOF_DTRT, name);
3835}
3836
3837int
3838xo_open_list_d (const char *name)
3839{
3840    return xo_open_list_hf(NULL, XOF_DTRT, name);
3841}
3842
3843int
3844xo_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
3881int
3882xo_close_list (const char *name)
3883{
3884    return xo_close_list_h(NULL, name);
3885}
3886
3887int
3888xo_close_list_hd (xo_handle_t *xop)
3889{
3890    return xo_close_list_h(xop, NULL);
3891}
3892
3893int
3894xo_close_list_d (void)
3895{
3896    return xo_close_list_h(NULL, NULL);
3897}
3898
3899static int
3900xo_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
3942int
3943xo_open_instance_h (xo_handle_t *xop, const char *name)
3944{
3945    return xo_open_instance_hf(xop, 0, name);
3946}
3947
3948int
3949xo_open_instance (const char *name)
3950{
3951    return xo_open_instance_hf(NULL, 0, name);
3952}
3953
3954int
3955xo_open_instance_hd (xo_handle_t *xop, const char *name)
3956{
3957    return xo_open_instance_hf(xop, XOF_DTRT, name);
3958}
3959
3960int
3961xo_open_instance_d (const char *name)
3962{
3963    return xo_open_instance_hf(NULL, XOF_DTRT, name);
3964}
3965
3966int
3967xo_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
4014int
4015xo_close_instance (const char *name)
4016{
4017    return xo_close_instance_h(NULL, name);
4018}
4019
4020int
4021xo_close_instance_hd (xo_handle_t *xop)
4022{
4023    return xo_close_instance_h(xop, NULL);
4024}
4025
4026int
4027xo_close_instance_d (void)
4028{
4029    return xo_close_instance_h(NULL, NULL);
4030}
4031
4032void
4033xo_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
4043void
4044xo_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
4050void
4051xo_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
4072void
4073xo_flush (void)
4074{
4075    xo_flush_h(NULL);
4076}
4077
4078void
4079xo_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
4099void
4100xo_finish (void)
4101{
4102    xo_finish_h(NULL);
4103}
4104
4105/*
4106 * Generate an error message, such as would be displayed on stderr
4107 */
4108void
4109xo_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
4158void
4159xo_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 */
4171void
4172xo_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
4181int
4182xo_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
4241int
4242main (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 */
4370