xref: /freebsd/contrib/libxo/libxo/libxo.c (revision 34b867ca)
131337658SMarcel Moolenaar /*
25e203a9dSPhil Shafer  * Copyright (c) 2014-2019, Juniper Networks, Inc.
331337658SMarcel Moolenaar  * All rights reserved.
431337658SMarcel Moolenaar  * This SOFTWARE is licensed under the LICENSE provided in the
531337658SMarcel Moolenaar  * ../Copyright file. By downloading, installing, copying, or otherwise
631337658SMarcel Moolenaar  * using the SOFTWARE, you agree to be bound by the terms of that
731337658SMarcel Moolenaar  * LICENSE.
831337658SMarcel Moolenaar  * Phil Shafer, July 2014
9d1a0d267SMarcel Moolenaar  *
10d1a0d267SMarcel Moolenaar  * This is the implementation of libxo, the formatting library that
11d1a0d267SMarcel Moolenaar  * generates multiple styles of output from a single code path.
12d1a0d267SMarcel Moolenaar  * Command line utilities can have their normal text output while
13d1a0d267SMarcel Moolenaar  * automation tools can see XML or JSON output, and web tools can use
14d1a0d267SMarcel Moolenaar  * HTML output that encodes the text output annotated with additional
15d1a0d267SMarcel Moolenaar  * information.  Specialized encoders can be built that allow custom
16d1a0d267SMarcel Moolenaar  * encoding including binary ones like CBOR, thrift, protobufs, etc.
17d1a0d267SMarcel Moolenaar  *
18d1a0d267SMarcel Moolenaar  * Full documentation is available in ./doc/libxo.txt or online at:
19d1a0d267SMarcel Moolenaar  *   http://juniper.github.io/libxo/libxo-manual.html
20d1a0d267SMarcel Moolenaar  *
21d1a0d267SMarcel Moolenaar  * For first time readers, the core bits of code to start looking at are:
2242ff34c3SPhil Shafer  * - xo_do_emit() -- parse and emit a set of fields
2342ff34c3SPhil Shafer  * - xo_do_emit_fields -- the central function of the library
24d1a0d267SMarcel Moolenaar  * - xo_do_format_field() -- handles formatting a single field
25d1a0d267SMarcel Moolenaar  * - xo_transiton() -- the state machine that keeps things sane
26d1a0d267SMarcel Moolenaar  * and of course the "xo_handle_t" data structure, which carries all
27d1a0d267SMarcel Moolenaar  * configuration and state.
2831337658SMarcel Moolenaar  */
2931337658SMarcel Moolenaar 
3031337658SMarcel Moolenaar #include <stdio.h>
3131337658SMarcel Moolenaar #include <stdlib.h>
3231337658SMarcel Moolenaar #include <stdint.h>
33545ddfbeSMarcel Moolenaar #include <unistd.h>
3431337658SMarcel Moolenaar #include <stddef.h>
3531337658SMarcel Moolenaar #include <wchar.h>
3631337658SMarcel Moolenaar #include <locale.h>
3731337658SMarcel Moolenaar #include <sys/types.h>
3831337658SMarcel Moolenaar #include <stdarg.h>
3931337658SMarcel Moolenaar #include <string.h>
4031337658SMarcel Moolenaar #include <errno.h>
4131337658SMarcel Moolenaar #include <limits.h>
4231337658SMarcel Moolenaar #include <ctype.h>
4331337658SMarcel Moolenaar #include <wctype.h>
4431337658SMarcel Moolenaar #include <getopt.h>
4531337658SMarcel Moolenaar 
46d1a0d267SMarcel Moolenaar #include "xo_config.h"
4731337658SMarcel Moolenaar #include "xo.h"
48d1a0d267SMarcel Moolenaar #include "xo_encoder.h"
49d1a0d267SMarcel Moolenaar #include "xo_buf.h"
50406a584dSPhil Shafer #include "xo_explicit.h"
51d1a0d267SMarcel Moolenaar 
52d1a0d267SMarcel Moolenaar /*
53d1a0d267SMarcel Moolenaar  * We ask wcwidth() to do an impossible job, really.  It's supposed to
54d1a0d267SMarcel Moolenaar  * need to tell us the number of columns consumed to display a unicode
55d1a0d267SMarcel Moolenaar  * character.  It returns that number without any sort of context, but
56d1a0d267SMarcel Moolenaar  * we know they are characters whose glyph differs based on placement
57d1a0d267SMarcel Moolenaar  * (end of word, middle of word, etc) and many that affect characters
58d1a0d267SMarcel Moolenaar  * previously emitted.  Without content, it can't hope to tell us.
59d1a0d267SMarcel Moolenaar  * But it's the only standard tool we've got, so we use it.  We would
60ee5cf116SPhil Shafer  * use wcswidth() but it typically just loops through adding the results
61d1a0d267SMarcel Moolenaar  * of wcwidth() calls in an entirely unhelpful way.
62d1a0d267SMarcel Moolenaar  *
63d1a0d267SMarcel Moolenaar  * Even then, there are many poor implementations (macosx), so we have
64d1a0d267SMarcel Moolenaar  * to carry our own.  We could have configure.ac test this (with
65d1a0d267SMarcel Moolenaar  * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
66d1a0d267SMarcel Moolenaar  * to run a binary, which breaks cross-compilation.  Hmm... I could
67d1a0d267SMarcel Moolenaar  * run this test at init time and make a warning for our dear user.
68d1a0d267SMarcel Moolenaar  *
69d1a0d267SMarcel Moolenaar  * Anyhow, it remains a best-effort sort of thing.  And it's all made
70d1a0d267SMarcel Moolenaar  * more hopeless because we assume the display code doing the rendering is
71d1a0d267SMarcel Moolenaar  * playing by the same rules we are.  If it display 0x200d as a square
72d1a0d267SMarcel Moolenaar  * box or a funky question mark, the output will be hosed.
73d1a0d267SMarcel Moolenaar  */
74d1a0d267SMarcel Moolenaar #ifdef LIBXO_WCWIDTH
75d1a0d267SMarcel Moolenaar #include "xo_wcwidth.h"
76d1a0d267SMarcel Moolenaar #else /* LIBXO_WCWIDTH */
77d1a0d267SMarcel Moolenaar #define xo_wcwidth(_x) wcwidth(_x)
78d1a0d267SMarcel Moolenaar #endif /* LIBXO_WCWIDTH */
7931337658SMarcel Moolenaar 
80545ddfbeSMarcel Moolenaar #ifdef HAVE_STDIO_EXT_H
81545ddfbeSMarcel Moolenaar #include <stdio_ext.h>
82545ddfbeSMarcel Moolenaar #endif /* HAVE_STDIO_EXT_H */
83545ddfbeSMarcel Moolenaar 
84d1a0d267SMarcel Moolenaar /*
85d1a0d267SMarcel Moolenaar  * humanize_number is a great function, unless you don't have it.  So
86d1a0d267SMarcel Moolenaar  * we carry one in our pocket.
87d1a0d267SMarcel Moolenaar  */
88d1a0d267SMarcel Moolenaar #ifdef HAVE_HUMANIZE_NUMBER
89d1a0d267SMarcel Moolenaar #include <libutil.h>
90d1a0d267SMarcel Moolenaar #define xo_humanize_number humanize_number
91d1a0d267SMarcel Moolenaar #else /* HAVE_HUMANIZE_NUMBER */
92d1a0d267SMarcel Moolenaar #include "xo_humanize.h"
93d1a0d267SMarcel Moolenaar #endif /* HAVE_HUMANIZE_NUMBER */
94d1a0d267SMarcel Moolenaar 
95d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
96d1a0d267SMarcel Moolenaar #include <libintl.h>
97d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
98d1a0d267SMarcel Moolenaar 
99264104f2SPhil Shafer /* Rather lame that we can't count on these... */
100264104f2SPhil Shafer #ifndef FALSE
101264104f2SPhil Shafer #define FALSE 0
102264104f2SPhil Shafer #endif
103264104f2SPhil Shafer #ifndef TRUE
104264104f2SPhil Shafer #define TRUE 1
105264104f2SPhil Shafer #endif
106264104f2SPhil Shafer 
107d1a0d267SMarcel Moolenaar /*
108d1a0d267SMarcel Moolenaar  * Three styles of specifying thread-local variables are supported.
109ee5cf116SPhil Shafer  * configure.ac has the brains to run each possibility through the
110d1a0d267SMarcel Moolenaar  * compiler and see what works; we are left to define the THREAD_LOCAL
111d1a0d267SMarcel Moolenaar  * macro to the right value.  Most toolchains (clang, gcc) use
112d1a0d267SMarcel Moolenaar  * "before", but some (borland) use "after" and I've heard of some
113d1a0d267SMarcel Moolenaar  * (ms) that use __declspec.  Any others out there?
114d1a0d267SMarcel Moolenaar  */
115d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_before 1
116d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_after 2
117d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_declspec 3
118d1a0d267SMarcel Moolenaar 
119d1a0d267SMarcel Moolenaar #ifndef HAVE_THREAD_LOCAL
120d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x
121d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
122d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __thread _x
123d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
124d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x __thread
125d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
126d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __declspec(_x)
127d1a0d267SMarcel Moolenaar #else
128d1a0d267SMarcel Moolenaar #error unknown thread-local setting
129d1a0d267SMarcel Moolenaar #endif /* HAVE_THREADS_H */
130d1a0d267SMarcel Moolenaar 
13131337658SMarcel Moolenaar const char xo_version[] = LIBXO_VERSION;
13231337658SMarcel Moolenaar const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
13342ff34c3SPhil Shafer static const char xo_default_format[] = "%s";
13431337658SMarcel Moolenaar 
13531337658SMarcel Moolenaar #ifndef UNUSED
13631337658SMarcel Moolenaar #define UNUSED __attribute__ ((__unused__))
13731337658SMarcel Moolenaar #endif /* UNUSED */
13831337658SMarcel Moolenaar 
13931337658SMarcel Moolenaar #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
140d1a0d267SMarcel Moolenaar #define XO_DEPTH	128	 /* Default stack depth */
141fd5e3f3eSPhil Shafer #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just silly */
14231337658SMarcel Moolenaar 
14331337658SMarcel Moolenaar #define XO_FAILURE_NAME	"failure"
14431337658SMarcel Moolenaar 
14531337658SMarcel Moolenaar /* Flags for the stack frame */
14631337658SMarcel Moolenaar typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
14731337658SMarcel Moolenaar #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
14831337658SMarcel Moolenaar #define XSF_LIST	(1<<1)	/* Frame is a list */
14931337658SMarcel Moolenaar #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
15031337658SMarcel Moolenaar #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
15131337658SMarcel Moolenaar 
152545ddfbeSMarcel Moolenaar #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
153545ddfbeSMarcel Moolenaar #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
154545ddfbeSMarcel Moolenaar #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
155545ddfbeSMarcel Moolenaar #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
156545ddfbeSMarcel Moolenaar 
157545ddfbeSMarcel Moolenaar /* These are the flags we propagate between markers and their parents */
158545ddfbeSMarcel Moolenaar #define XSF_MARKER_FLAGS \
159545ddfbeSMarcel Moolenaar  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
160545ddfbeSMarcel Moolenaar 
161545ddfbeSMarcel Moolenaar /*
162406a584dSPhil Shafer  * Turn the transition between two states into a number suitable for
163406a584dSPhil Shafer  * a "switch" statement.
164545ddfbeSMarcel Moolenaar  */
165545ddfbeSMarcel Moolenaar #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
166545ddfbeSMarcel Moolenaar 
16731337658SMarcel Moolenaar /*
16831337658SMarcel Moolenaar  * xo_stack_t: As we open and close containers and levels, we
16931337658SMarcel Moolenaar  * create a stack of frames to track them.  This is needed for
17031337658SMarcel Moolenaar  * XOF_WARN and XOF_XPATH.
17131337658SMarcel Moolenaar  */
17231337658SMarcel Moolenaar typedef struct xo_stack_s {
17331337658SMarcel Moolenaar     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
174545ddfbeSMarcel Moolenaar     xo_state_t xs_state;	/* State for this stack frame */
17531337658SMarcel Moolenaar     char *xs_name;		/* Name (for XPath value) */
17631337658SMarcel Moolenaar     char *xs_keys;		/* XPath predicate for any key fields */
17731337658SMarcel Moolenaar } xo_stack_t;
17831337658SMarcel Moolenaar 
179d1a0d267SMarcel Moolenaar /*
180d1a0d267SMarcel Moolenaar  * libxo supports colors and effects, for those who like them.
181d1a0d267SMarcel Moolenaar  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
182d1a0d267SMarcel Moolenaar  * ("effects") are bits since we need to maintain state.
183d1a0d267SMarcel Moolenaar  */
184f2b7bf8aSPhil Shafer typedef uint8_t xo_color_t;
185788ca347SMarcel Moolenaar #define XO_COL_DEFAULT		0
186788ca347SMarcel Moolenaar #define XO_COL_BLACK		1
187788ca347SMarcel Moolenaar #define XO_COL_RED		2
188788ca347SMarcel Moolenaar #define XO_COL_GREEN		3
189788ca347SMarcel Moolenaar #define XO_COL_YELLOW		4
190788ca347SMarcel Moolenaar #define XO_COL_BLUE		5
191788ca347SMarcel Moolenaar #define XO_COL_MAGENTA		6
192788ca347SMarcel Moolenaar #define XO_COL_CYAN		7
193788ca347SMarcel Moolenaar #define XO_COL_WHITE		8
194788ca347SMarcel Moolenaar 
195788ca347SMarcel Moolenaar #define XO_NUM_COLORS		9
196788ca347SMarcel Moolenaar 
197788ca347SMarcel Moolenaar /*
198788ca347SMarcel Moolenaar  * Yes, there's no blink.  We're civilized.  We like users.  Blink
199788ca347SMarcel Moolenaar  * isn't something one does to someone you like.  Friends don't let
200788ca347SMarcel Moolenaar  * friends use blink.  On friends.  You know what I mean.  Blink is
201788ca347SMarcel Moolenaar  * like, well, it's like bursting into show tunes at a funeral.  It's
202788ca347SMarcel Moolenaar  * just not done.  Not something anyone wants.  And on those rare
203d1a0d267SMarcel Moolenaar  * instances where it might actually be appropriate, it's still wrong,
204d1a0d267SMarcel Moolenaar  * since it's likely done by the wrong person for the wrong reason.
205d1a0d267SMarcel Moolenaar  * Just like blink.  And if I implemented blink, I'd be like a funeral
206788ca347SMarcel Moolenaar  * director who adds "Would you like us to burst into show tunes?" on
207d1a0d267SMarcel Moolenaar  * the list of questions asked while making funeral arrangements.
208788ca347SMarcel Moolenaar  * It's formalizing wrongness in the wrong way.  And we're just too
209788ca347SMarcel Moolenaar  * civilized to do that.  Hhhmph!
210788ca347SMarcel Moolenaar  */
211788ca347SMarcel Moolenaar #define XO_EFF_RESET		(1<<0)
212788ca347SMarcel Moolenaar #define XO_EFF_NORMAL		(1<<1)
213788ca347SMarcel Moolenaar #define XO_EFF_BOLD		(1<<2)
214788ca347SMarcel Moolenaar #define XO_EFF_UNDERLINE	(1<<3)
215788ca347SMarcel Moolenaar #define XO_EFF_INVERSE		(1<<4)
216788ca347SMarcel Moolenaar 
217d1a0d267SMarcel Moolenaar #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
218788ca347SMarcel Moolenaar 
219788ca347SMarcel Moolenaar typedef uint8_t xo_effect_t;
220788ca347SMarcel Moolenaar typedef struct xo_colors_s {
221788ca347SMarcel Moolenaar     xo_effect_t xoc_effects;	/* Current effect set */
222788ca347SMarcel Moolenaar     xo_color_t xoc_col_fg;	/* Foreground color */
223788ca347SMarcel Moolenaar     xo_color_t xoc_col_bg;	/* Background color */
224788ca347SMarcel Moolenaar } xo_colors_t;
225788ca347SMarcel Moolenaar 
22631337658SMarcel Moolenaar /*
22731337658SMarcel Moolenaar  * xo_handle_t: this is the principle data structure for libxo.
228d1a0d267SMarcel Moolenaar  * It's used as a store for state, options, content, and all manor
229d1a0d267SMarcel Moolenaar  * of other information.
23031337658SMarcel Moolenaar  */
23131337658SMarcel Moolenaar struct xo_handle_s {
232d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
233d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
234d1a0d267SMarcel Moolenaar     xo_style_t xo_style;	/* XO_STYLE_* value */
23531337658SMarcel Moolenaar     unsigned short xo_indent;	/* Indent level (if pretty) */
23631337658SMarcel Moolenaar     unsigned short xo_indent_by; /* Indent amount (tab stop) */
23731337658SMarcel Moolenaar     xo_write_func_t xo_write;	/* Write callback */
238a0f704ffSMarcel Moolenaar     xo_close_func_t xo_close;	/* Close callback */
239545ddfbeSMarcel Moolenaar     xo_flush_func_t xo_flush;	/* Flush callback */
24031337658SMarcel Moolenaar     xo_formatter_t xo_formatter; /* Custom formating function */
24131337658SMarcel Moolenaar     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
24231337658SMarcel Moolenaar     void *xo_opaque;		/* Opaque data for write function */
24331337658SMarcel Moolenaar     xo_buffer_t xo_data;	/* Output data */
24431337658SMarcel Moolenaar     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
24531337658SMarcel Moolenaar     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
24631337658SMarcel Moolenaar     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
24731337658SMarcel Moolenaar     xo_stack_t *xo_stack;	/* Stack pointer */
24831337658SMarcel Moolenaar     int xo_depth;		/* Depth of stack */
24931337658SMarcel Moolenaar     int xo_stack_size;		/* Size of the stack */
25031337658SMarcel Moolenaar     xo_info_t *xo_info;		/* Info fields for all elements */
25131337658SMarcel Moolenaar     int xo_info_count;		/* Number of info entries */
25231337658SMarcel Moolenaar     va_list xo_vap;		/* Variable arguments (stdargs) */
25331337658SMarcel Moolenaar     char *xo_leading_xpath;	/* A leading XPath expression */
25431337658SMarcel Moolenaar     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
2558a6eceffSPhil Shafer     ssize_t xo_anchor_offset;	/* Start of anchored text */
2568a6eceffSPhil Shafer     ssize_t xo_anchor_columns;	/* Number of columns since the start anchor */
2578a6eceffSPhil Shafer     ssize_t xo_anchor_min_width; /* Desired width of anchored text */
2588a6eceffSPhil Shafer     ssize_t xo_units_offset;	/* Start of units insertion point */
2598a6eceffSPhil Shafer     ssize_t xo_columns;	/* Columns emitted during this xo_emit call */
260f2b7bf8aSPhil Shafer #ifndef LIBXO_TEXT_ONLY
261406a584dSPhil Shafer     xo_color_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
262406a584dSPhil Shafer     xo_color_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
263f2b7bf8aSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
264788ca347SMarcel Moolenaar     xo_colors_t xo_colors;	/* Current color and effect values */
265788ca347SMarcel Moolenaar     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
266788ca347SMarcel Moolenaar     char *xo_version;		/* Version string */
267d1a0d267SMarcel Moolenaar     int xo_errno;		/* Saved errno for "%m" */
268d1a0d267SMarcel Moolenaar     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
269d1a0d267SMarcel Moolenaar     xo_encoder_func_t xo_encoder; /* Encoding function */
270d1a0d267SMarcel Moolenaar     void *xo_private;		/* Private data for external encoders */
27131337658SMarcel Moolenaar };
27231337658SMarcel Moolenaar 
273d1a0d267SMarcel Moolenaar /* Flag operations */
274d1a0d267SMarcel Moolenaar #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
275d1a0d267SMarcel Moolenaar #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
276d1a0d267SMarcel Moolenaar #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
277d1a0d267SMarcel Moolenaar 
278d1a0d267SMarcel Moolenaar #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
279d1a0d267SMarcel Moolenaar #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
280d1a0d267SMarcel Moolenaar #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
281d1a0d267SMarcel Moolenaar 
282d1a0d267SMarcel Moolenaar #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
283d1a0d267SMarcel Moolenaar #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
284d1a0d267SMarcel Moolenaar #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
285d1a0d267SMarcel Moolenaar 
286d1a0d267SMarcel Moolenaar /* Internal flags */
287d1a0d267SMarcel Moolenaar #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
288d1a0d267SMarcel Moolenaar #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
289d1a0d267SMarcel Moolenaar #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
290d1a0d267SMarcel Moolenaar #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
291d1a0d267SMarcel Moolenaar 
292d1a0d267SMarcel Moolenaar #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
293d1a0d267SMarcel Moolenaar #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
294406a584dSPhil Shafer #define XOIF_MADE_OUTPUT XOF_BIT(6)	 /* Have already made output */
295d1a0d267SMarcel Moolenaar 
29631337658SMarcel Moolenaar /*
29731337658SMarcel Moolenaar  * Normal printf has width and precision, which for strings operate as
29831337658SMarcel Moolenaar  * min and max number of columns.  But this depends on the idea that
29931337658SMarcel Moolenaar  * one byte means one column, which UTF-8 and multi-byte characters
30031337658SMarcel Moolenaar  * pitches on its ear.  It may take 40 bytes of data to populate 14
30131337658SMarcel Moolenaar  * columns, but we can't go off looking at 40 bytes of data without the
30231337658SMarcel Moolenaar  * caller's permission for fear/knowledge that we'll generate core files.
30331337658SMarcel Moolenaar  *
30431337658SMarcel Moolenaar  * So we make three values, distinguishing between "max column" and
30531337658SMarcel Moolenaar  * "number of bytes that we will inspect inspect safely" We call the
30631337658SMarcel Moolenaar  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
30731337658SMarcel Moolenaar  *
30831337658SMarcel Moolenaar  * Under the "first do no harm" theory, we default "max" to "size".
30931337658SMarcel Moolenaar  * This is a reasonable assumption for folks that don't grok the
31031337658SMarcel Moolenaar  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
31131337658SMarcel Moolenaar  * be evil.
31231337658SMarcel Moolenaar  *
31331337658SMarcel Moolenaar  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
31431337658SMarcel Moolenaar  * columns of output, but will never look at more than 14 bytes of the
31531337658SMarcel Moolenaar  * input buffer.  This is mostly compatible with printf and caller's
31631337658SMarcel Moolenaar  * expectations.
31731337658SMarcel Moolenaar  *
31831337658SMarcel Moolenaar  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
31931337658SMarcel Moolenaar  * many bytes (or until a NUL is seen) are needed to fill 14 columns
32031337658SMarcel Moolenaar  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
32131337658SMarcel Moolenaar  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
32231337658SMarcel Moolenaar  * of output.
32331337658SMarcel Moolenaar  *
32431337658SMarcel Moolenaar  * It's fairly amazing how a good idea (handle all languages of the
32531337658SMarcel Moolenaar  * world) blows such a big hole in the bottom of the fairly weak boat
32631337658SMarcel Moolenaar  * that is C string handling.  The simplicity and completenesss are
32731337658SMarcel Moolenaar  * sunk in ways we haven't even begun to understand.
32831337658SMarcel Moolenaar  */
32931337658SMarcel Moolenaar #define XF_WIDTH_MIN	0	/* Minimal width */
33031337658SMarcel Moolenaar #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
33131337658SMarcel Moolenaar #define XF_WIDTH_MAX	2	/* Maximum width */
33231337658SMarcel Moolenaar #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
33331337658SMarcel Moolenaar 
33431337658SMarcel Moolenaar /* Input and output string encodings */
33531337658SMarcel Moolenaar #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
33631337658SMarcel Moolenaar #define XF_ENC_UTF8	2	/* UTF-8 */
33731337658SMarcel Moolenaar #define XF_ENC_LOCALE	3	/* Current locale */
33831337658SMarcel Moolenaar 
33931337658SMarcel Moolenaar /*
34031337658SMarcel Moolenaar  * A place to parse printf-style format flags for each field
34131337658SMarcel Moolenaar  */
34231337658SMarcel Moolenaar typedef struct xo_format_s {
34331337658SMarcel Moolenaar     unsigned char xf_fc;	/* Format character */
34431337658SMarcel Moolenaar     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
34531337658SMarcel Moolenaar     unsigned char xf_skip;	/* Skip this field */
34631337658SMarcel Moolenaar     unsigned char xf_lflag;	/* 'l' (long) */
34731337658SMarcel Moolenaar     unsigned char xf_hflag;;	/* 'h' (half) */
34831337658SMarcel Moolenaar     unsigned char xf_jflag;	/* 'j' (intmax_t) */
34931337658SMarcel Moolenaar     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
35031337658SMarcel Moolenaar     unsigned char xf_zflag;	/* 'z' (size_t) */
35131337658SMarcel Moolenaar     unsigned char xf_qflag;	/* 'q' (quad_t) */
35231337658SMarcel Moolenaar     unsigned char xf_seen_minus; /* Seen a minus */
35331337658SMarcel Moolenaar     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
35431337658SMarcel Moolenaar     unsigned xf_dots;		/* Seen one or more '.'s */
35531337658SMarcel Moolenaar     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
35631337658SMarcel Moolenaar     unsigned xf_stars;		/* Seen one or more '*'s */
35731337658SMarcel Moolenaar     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
35831337658SMarcel Moolenaar } xo_format_t;
35931337658SMarcel Moolenaar 
36031337658SMarcel Moolenaar /*
361d1a0d267SMarcel Moolenaar  * This structure represents the parsed field information, suitable for
362d1a0d267SMarcel Moolenaar  * processing by xo_do_emit and anything else that needs to parse fields.
363d1a0d267SMarcel Moolenaar  * Note that all pointers point to the main format string.
364d1a0d267SMarcel Moolenaar  *
365d1a0d267SMarcel Moolenaar  * XXX This is a first step toward compilable or cachable format
366d1a0d267SMarcel Moolenaar  * strings.  We can also cache the results of dgettext when no format
367d1a0d267SMarcel Moolenaar  * is used, assuming the 'p' modifier has _not_ been set.
36831337658SMarcel Moolenaar  */
369d1a0d267SMarcel Moolenaar typedef struct xo_field_info_s {
370d1a0d267SMarcel Moolenaar     xo_xff_flags_t xfi_flags;	/* Flags for this field */
371d1a0d267SMarcel Moolenaar     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
372d1a0d267SMarcel Moolenaar     const char *xfi_start;   /* Start of field in the format string */
373d1a0d267SMarcel Moolenaar     const char *xfi_content;	/* Field's content */
374d1a0d267SMarcel Moolenaar     const char *xfi_format;	/* Field's Format */
375d1a0d267SMarcel Moolenaar     const char *xfi_encoding;	/* Field's encoding format */
376d1a0d267SMarcel Moolenaar     const char *xfi_next;	/* Next character in format string */
3778a6eceffSPhil Shafer     ssize_t xfi_len;		/* Length of field */
3788a6eceffSPhil Shafer     ssize_t xfi_clen;		/* Content length */
3798a6eceffSPhil Shafer     ssize_t xfi_flen;		/* Format length */
3808a6eceffSPhil Shafer     ssize_t xfi_elen;		/* Encoding length */
381d1a0d267SMarcel Moolenaar     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
382d1a0d267SMarcel Moolenaar     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
383d1a0d267SMarcel Moolenaar } xo_field_info_t;
384d1a0d267SMarcel Moolenaar 
385d1a0d267SMarcel Moolenaar /*
386d1a0d267SMarcel Moolenaar  * We keep a 'default' handle to allow callers to avoid having to
387d1a0d267SMarcel Moolenaar  * allocate one.  Passing NULL to any of our functions will use
388d1a0d267SMarcel Moolenaar  * this default handle.  Most functions have a variant that doesn't
389d1a0d267SMarcel Moolenaar  * require a handle at all, since most output is to stdout, which
390d1a0d267SMarcel Moolenaar  * the default handle handles handily.
391d1a0d267SMarcel Moolenaar  */
392d1a0d267SMarcel Moolenaar static THREAD_LOCAL(xo_handle_t) xo_default_handle;
393d1a0d267SMarcel Moolenaar static THREAD_LOCAL(int) xo_default_inited;
39431337658SMarcel Moolenaar static int xo_locale_inited;
395545ddfbeSMarcel Moolenaar static const char *xo_program;
39631337658SMarcel Moolenaar 
39731337658SMarcel Moolenaar /*
39831337658SMarcel Moolenaar  * To allow libxo to be used in diverse environment, we allow the
39931337658SMarcel Moolenaar  * caller to give callbacks for memory allocation.
40031337658SMarcel Moolenaar  */
401d1a0d267SMarcel Moolenaar xo_realloc_func_t xo_realloc = realloc;
402d1a0d267SMarcel Moolenaar xo_free_func_t xo_free = free;
40331337658SMarcel Moolenaar 
40431337658SMarcel Moolenaar /* Forward declarations */
4058a6eceffSPhil Shafer static ssize_t
406406a584dSPhil Shafer xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
407545ddfbeSMarcel Moolenaar 	       xo_state_t new_state);
408545ddfbeSMarcel Moolenaar 
409f2b7bf8aSPhil Shafer static int
410f2b7bf8aSPhil Shafer xo_set_options_simple (xo_handle_t *xop, const char *input);
411f2b7bf8aSPhil Shafer 
412f2b7bf8aSPhil Shafer static int
413f2b7bf8aSPhil Shafer xo_color_find (const char *str);
414f2b7bf8aSPhil Shafer 
41531337658SMarcel Moolenaar static void
41631337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
4178a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
4188a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
419264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
4208a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen);
42131337658SMarcel Moolenaar 
42231337658SMarcel Moolenaar static void
42331337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop);
42431337658SMarcel Moolenaar 
42531337658SMarcel Moolenaar /*
426788ca347SMarcel Moolenaar  * xo_style is used to retrieve the current style.  When we're built
427788ca347SMarcel Moolenaar  * for "text only" mode, we use this function to drive the removal
428788ca347SMarcel Moolenaar  * of most of the code in libxo.  We return a constant and the compiler
429788ca347SMarcel Moolenaar  * happily removes the non-text code that is not longer executed.  This
430788ca347SMarcel Moolenaar  * trims our code nicely without needing to trampel perfectly readable
431788ca347SMarcel Moolenaar  * code with ifdefs.
432788ca347SMarcel Moolenaar  */
433d1a0d267SMarcel Moolenaar static inline xo_style_t
xo_style(xo_handle_t * xop UNUSED)434788ca347SMarcel Moolenaar xo_style (xo_handle_t *xop UNUSED)
435788ca347SMarcel Moolenaar {
436788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
437788ca347SMarcel Moolenaar     return XO_STYLE_TEXT;
438788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
439788ca347SMarcel Moolenaar     return xop->xo_style;
440788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
441788ca347SMarcel Moolenaar }
442788ca347SMarcel Moolenaar 
443788ca347SMarcel Moolenaar /*
444406a584dSPhil Shafer  * Allow the compiler to optimize out non-text-only code while
445406a584dSPhil Shafer  * still compiling it.
446406a584dSPhil Shafer  */
447406a584dSPhil Shafer static inline int
xo_text_only(void)448406a584dSPhil Shafer xo_text_only (void)
449406a584dSPhil Shafer {
450406a584dSPhil Shafer #ifdef LIBXO_TEXT_ONLY
451406a584dSPhil Shafer     return TRUE;
452406a584dSPhil Shafer #else /* LIBXO_TEXT_ONLY */
453406a584dSPhil Shafer     return FALSE;
454406a584dSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
455406a584dSPhil Shafer }
456406a584dSPhil Shafer 
457406a584dSPhil Shafer /*
45831337658SMarcel Moolenaar  * Callback to write data to a FILE pointer
45931337658SMarcel Moolenaar  */
4608a6eceffSPhil Shafer static xo_ssize_t
xo_write_to_file(void * opaque,const char * data)46131337658SMarcel Moolenaar xo_write_to_file (void *opaque, const char *data)
46231337658SMarcel Moolenaar {
46331337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
464545ddfbeSMarcel Moolenaar 
46531337658SMarcel Moolenaar     return fprintf(fp, "%s", data);
46631337658SMarcel Moolenaar }
46731337658SMarcel Moolenaar 
46831337658SMarcel Moolenaar /*
46931337658SMarcel Moolenaar  * Callback to close a file
47031337658SMarcel Moolenaar  */
47131337658SMarcel Moolenaar static void
xo_close_file(void * opaque)47231337658SMarcel Moolenaar xo_close_file (void *opaque)
47331337658SMarcel Moolenaar {
47431337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
475545ddfbeSMarcel Moolenaar 
47631337658SMarcel Moolenaar     fclose(fp);
47731337658SMarcel Moolenaar }
47831337658SMarcel Moolenaar 
47931337658SMarcel Moolenaar /*
480545ddfbeSMarcel Moolenaar  * Callback to flush a FILE pointer
481545ddfbeSMarcel Moolenaar  */
482545ddfbeSMarcel Moolenaar static int
xo_flush_file(void * opaque)483545ddfbeSMarcel Moolenaar xo_flush_file (void *opaque)
484545ddfbeSMarcel Moolenaar {
485545ddfbeSMarcel Moolenaar     FILE *fp = (FILE *) opaque;
486545ddfbeSMarcel Moolenaar 
487545ddfbeSMarcel Moolenaar     return fflush(fp);
488545ddfbeSMarcel Moolenaar }
489545ddfbeSMarcel Moolenaar 
490545ddfbeSMarcel Moolenaar /*
491d1a0d267SMarcel Moolenaar  * Use a rotating stock of buffers to make a printable string
49231337658SMarcel Moolenaar  */
493d1a0d267SMarcel Moolenaar #define XO_NUMBUFS 8
494d1a0d267SMarcel Moolenaar #define XO_SMBUFSZ 128
495d1a0d267SMarcel Moolenaar 
496d1a0d267SMarcel Moolenaar static const char *
xo_printable(const char * str)497d1a0d267SMarcel Moolenaar xo_printable (const char *str)
49831337658SMarcel Moolenaar {
499d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
500d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(int) bufnum = 0;
501d1a0d267SMarcel Moolenaar 
502d1a0d267SMarcel Moolenaar     if (str == NULL)
503d1a0d267SMarcel Moolenaar 	return "";
504d1a0d267SMarcel Moolenaar 
505d1a0d267SMarcel Moolenaar     if (++bufnum == XO_NUMBUFS)
506d1a0d267SMarcel Moolenaar 	bufnum = 0;
507d1a0d267SMarcel Moolenaar 
508d1a0d267SMarcel Moolenaar     char *res = bufset[bufnum], *cp, *ep;
509d1a0d267SMarcel Moolenaar 
510d1a0d267SMarcel Moolenaar     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
511d1a0d267SMarcel Moolenaar 	if (*str == '\n') {
512d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
513d1a0d267SMarcel Moolenaar 	    *cp = 'n';
514d1a0d267SMarcel Moolenaar 	} else if (*str == '\r') {
515d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
516d1a0d267SMarcel Moolenaar 	    *cp = 'r';
517d1a0d267SMarcel Moolenaar 	} else if (*str == '\"') {
518d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
519d1a0d267SMarcel Moolenaar 	    *cp = '"';
520d1a0d267SMarcel Moolenaar 	} else
521d1a0d267SMarcel Moolenaar 	    *cp = *str;
52231337658SMarcel Moolenaar     }
52331337658SMarcel Moolenaar 
524d1a0d267SMarcel Moolenaar     *cp = '\0';
525d1a0d267SMarcel Moolenaar     return res;
52631337658SMarcel Moolenaar }
52731337658SMarcel Moolenaar 
52831337658SMarcel Moolenaar static int
xo_depth_check(xo_handle_t * xop,int depth)52931337658SMarcel Moolenaar xo_depth_check (xo_handle_t *xop, int depth)
53031337658SMarcel Moolenaar {
53131337658SMarcel Moolenaar     xo_stack_t *xsp;
53231337658SMarcel Moolenaar 
53331337658SMarcel Moolenaar     if (depth >= xop->xo_stack_size) {
534d1a0d267SMarcel Moolenaar 	depth += XO_DEPTH;	/* Extra room */
535d1a0d267SMarcel Moolenaar 
53631337658SMarcel Moolenaar 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
53731337658SMarcel Moolenaar 	if (xsp == NULL) {
53831337658SMarcel Moolenaar 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
539d1a0d267SMarcel Moolenaar 	    return -1;
54031337658SMarcel Moolenaar 	}
54131337658SMarcel Moolenaar 
54231337658SMarcel Moolenaar 	int count = depth - xop->xo_stack_size;
54331337658SMarcel Moolenaar 
54431337658SMarcel Moolenaar 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
54531337658SMarcel Moolenaar 	xop->xo_stack_size = depth;
54631337658SMarcel Moolenaar 	xop->xo_stack = xsp;
54731337658SMarcel Moolenaar     }
54831337658SMarcel Moolenaar 
54931337658SMarcel Moolenaar     return 0;
55031337658SMarcel Moolenaar }
55131337658SMarcel Moolenaar 
55231337658SMarcel Moolenaar void
xo_no_setlocale(void)55331337658SMarcel Moolenaar xo_no_setlocale (void)
55431337658SMarcel Moolenaar {
55531337658SMarcel Moolenaar     xo_locale_inited = 1;	/* Skip initialization */
55631337658SMarcel Moolenaar }
55731337658SMarcel Moolenaar 
55831337658SMarcel Moolenaar /*
559406a584dSPhil Shafer  * For XML, the first character of a tag cannot be numeric, but people
560406a584dSPhil Shafer  * will likely not notice.  So we people-proof them by forcing a leading
561406a584dSPhil Shafer  * underscore if they use invalid tags.  Note that this doesn't cover
562406a584dSPhil Shafer  * all broken tags, just this fairly specific case.
563406a584dSPhil Shafer  */
564406a584dSPhil Shafer static const char *
xo_xml_leader_len(xo_handle_t * xop,const char * name,xo_ssize_t nlen)565406a584dSPhil Shafer xo_xml_leader_len (xo_handle_t *xop, const char *name, xo_ssize_t nlen)
566406a584dSPhil Shafer {
567c703b76eSPhil Shafer     if (name == NULL || isalpha(name[0]) || name[0] == '_')
568406a584dSPhil Shafer         return "";
569406a584dSPhil Shafer 
570406a584dSPhil Shafer     xo_failure(xop, "invalid XML tag name: '%.*s'", nlen, name);
571406a584dSPhil Shafer     return "_";
572406a584dSPhil Shafer }
573406a584dSPhil Shafer 
574406a584dSPhil Shafer static const char *
xo_xml_leader(xo_handle_t * xop,const char * name)575406a584dSPhil Shafer xo_xml_leader (xo_handle_t *xop, const char *name)
576406a584dSPhil Shafer {
577406a584dSPhil Shafer     return xo_xml_leader_len(xop, name, strlen(name));
578406a584dSPhil Shafer }
579406a584dSPhil Shafer 
580406a584dSPhil Shafer /*
581545ddfbeSMarcel Moolenaar  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
582545ddfbeSMarcel Moolenaar  * standard way to decide this (e.g. getlinebuf()), we have configure
583788ca347SMarcel Moolenaar  * look to find __flbf, which glibc supported.  If not, we'll rely on
584788ca347SMarcel Moolenaar  * isatty, with the assumption that terminals are the only thing
585545ddfbeSMarcel Moolenaar  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
586545ddfbeSMarcel Moolenaar  * which is all __flbf does, but that's even tackier.  Like a
587545ddfbeSMarcel Moolenaar  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
588545ddfbeSMarcel Moolenaar  * something we're willing to do.
589545ddfbeSMarcel Moolenaar  */
590545ddfbeSMarcel Moolenaar static int
xo_is_line_buffered(FILE * stream)591545ddfbeSMarcel Moolenaar xo_is_line_buffered (FILE *stream)
592545ddfbeSMarcel Moolenaar {
593545ddfbeSMarcel Moolenaar #if HAVE___FLBF
594545ddfbeSMarcel Moolenaar     if (__flbf(stream))
595545ddfbeSMarcel Moolenaar 	return 1;
596545ddfbeSMarcel Moolenaar #else /* HAVE___FLBF */
597545ddfbeSMarcel Moolenaar     if (isatty(fileno(stream)))
598545ddfbeSMarcel Moolenaar 	return 1;
599545ddfbeSMarcel Moolenaar #endif /* HAVE___FLBF */
600545ddfbeSMarcel Moolenaar     return 0;
601545ddfbeSMarcel Moolenaar }
602545ddfbeSMarcel Moolenaar 
603545ddfbeSMarcel Moolenaar /*
60431337658SMarcel Moolenaar  * Initialize an xo_handle_t, using both static defaults and
60531337658SMarcel Moolenaar  * the global settings from the LIBXO_OPTIONS environment
60631337658SMarcel Moolenaar  * variable.
60731337658SMarcel Moolenaar  */
60831337658SMarcel Moolenaar static void
xo_init_handle(xo_handle_t * xop)60931337658SMarcel Moolenaar xo_init_handle (xo_handle_t *xop)
61031337658SMarcel Moolenaar {
61131337658SMarcel Moolenaar     xop->xo_opaque = stdout;
61231337658SMarcel Moolenaar     xop->xo_write = xo_write_to_file;
613545ddfbeSMarcel Moolenaar     xop->xo_flush = xo_flush_file;
614545ddfbeSMarcel Moolenaar 
615545ddfbeSMarcel Moolenaar     if (xo_is_line_buffered(stdout))
616d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_FLUSH_LINE);
61731337658SMarcel Moolenaar 
61831337658SMarcel Moolenaar     /*
61931337658SMarcel Moolenaar      * We need to initialize the locale, which isn't really pretty.
62031337658SMarcel Moolenaar      * Libraries should depend on their caller to set up the
62131337658SMarcel Moolenaar      * environment.  But we really can't count on the caller to do
62231337658SMarcel Moolenaar      * this, because well, they won't.  Trust me.
62331337658SMarcel Moolenaar      */
62431337658SMarcel Moolenaar     if (!xo_locale_inited) {
62531337658SMarcel Moolenaar 	xo_locale_inited = 1;	/* Only do this once */
62631337658SMarcel Moolenaar 
62776afb20cSPhil Shafer #ifdef __FreeBSD__		/* Who does The Right Thing */
62876afb20cSPhil Shafer 	const char *cp = "";
62976afb20cSPhil Shafer #else /* __FreeBSD__ */
63076afb20cSPhil Shafer 	const char *cp = getenv("LC_ALL");
63176afb20cSPhil Shafer 	if (cp == NULL)
63276afb20cSPhil Shafer 	    cp = getenv("LC_CTYPE");
63331337658SMarcel Moolenaar 	if (cp == NULL)
63431337658SMarcel Moolenaar 	    cp = getenv("LANG");
63531337658SMarcel Moolenaar 	if (cp == NULL)
636d1a0d267SMarcel Moolenaar 	    cp = "C";		/* Default for C programs */
63776afb20cSPhil Shafer #endif /* __FreeBSD__ */
63876afb20cSPhil Shafer 
639c600d307SMarcel Moolenaar 	(void) setlocale(LC_CTYPE, cp);
64031337658SMarcel Moolenaar     }
64131337658SMarcel Moolenaar 
64231337658SMarcel Moolenaar     /*
64331337658SMarcel Moolenaar      * Initialize only the xo_buffers we know we'll need; the others
64431337658SMarcel Moolenaar      * can be allocated as needed.
64531337658SMarcel Moolenaar      */
64631337658SMarcel Moolenaar     xo_buf_init(&xop->xo_data);
64731337658SMarcel Moolenaar     xo_buf_init(&xop->xo_fmt);
64831337658SMarcel Moolenaar 
649d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
650d1a0d267SMarcel Moolenaar 	return;
651d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
652d1a0d267SMarcel Moolenaar 
65331337658SMarcel Moolenaar     xop->xo_indent_by = XO_INDENT_BY;
65431337658SMarcel Moolenaar     xo_depth_check(xop, XO_DEPTH);
65531337658SMarcel Moolenaar 
656d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
65731337658SMarcel Moolenaar }
65831337658SMarcel Moolenaar 
65931337658SMarcel Moolenaar /*
66031337658SMarcel Moolenaar  * Initialize the default handle.
66131337658SMarcel Moolenaar  */
66231337658SMarcel Moolenaar static void
xo_default_init(void)66331337658SMarcel Moolenaar xo_default_init (void)
66431337658SMarcel Moolenaar {
66531337658SMarcel Moolenaar     xo_handle_t *xop = &xo_default_handle;
66631337658SMarcel Moolenaar 
66731337658SMarcel Moolenaar     xo_init_handle(xop);
66831337658SMarcel Moolenaar 
669f2b7bf8aSPhil Shafer #if !defined(NO_LIBXO_OPTIONS)
670f2b7bf8aSPhil Shafer     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
671f2b7bf8aSPhil Shafer        char *env = getenv("LIBXO_OPTIONS");
6722f784130SPhil Shafer 
673f2b7bf8aSPhil Shafer        if (env)
674f2b7bf8aSPhil Shafer            xo_set_options_simple(xop, env);
675f2b7bf8aSPhil Shafer 
676f2b7bf8aSPhil Shafer     }
677f2b7bf8aSPhil Shafer #endif /* NO_LIBXO_OPTIONS */
678f2b7bf8aSPhil Shafer 
67931337658SMarcel Moolenaar     xo_default_inited = 1;
68031337658SMarcel Moolenaar }
68131337658SMarcel Moolenaar 
68231337658SMarcel Moolenaar /*
68331337658SMarcel Moolenaar  * Cheap convenience function to return either the argument, or
68431337658SMarcel Moolenaar  * the internal handle, after it has been initialized.  The usage
68531337658SMarcel Moolenaar  * is:
68631337658SMarcel Moolenaar  *    xop = xo_default(xop);
68731337658SMarcel Moolenaar  */
68831337658SMarcel Moolenaar static xo_handle_t *
xo_default(xo_handle_t * xop)68931337658SMarcel Moolenaar xo_default (xo_handle_t *xop)
69031337658SMarcel Moolenaar {
69131337658SMarcel Moolenaar     if (xop == NULL) {
69231337658SMarcel Moolenaar 	if (xo_default_inited == 0)
69331337658SMarcel Moolenaar 	    xo_default_init();
69431337658SMarcel Moolenaar 	xop = &xo_default_handle;
69531337658SMarcel Moolenaar     }
69631337658SMarcel Moolenaar 
69731337658SMarcel Moolenaar     return xop;
69831337658SMarcel Moolenaar }
69931337658SMarcel Moolenaar 
70031337658SMarcel Moolenaar /*
70131337658SMarcel Moolenaar  * Return the number of spaces we should be indenting.  If
702788ca347SMarcel Moolenaar  * we are pretty-printing, this is indent * indent_by.
70331337658SMarcel Moolenaar  */
70431337658SMarcel Moolenaar static int
xo_indent(xo_handle_t * xop)70531337658SMarcel Moolenaar xo_indent (xo_handle_t *xop)
70631337658SMarcel Moolenaar {
70731337658SMarcel Moolenaar     int rc = 0;
70831337658SMarcel Moolenaar 
70931337658SMarcel Moolenaar     xop = xo_default(xop);
71031337658SMarcel Moolenaar 
711d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY)) {
71231337658SMarcel Moolenaar 	rc = xop->xo_indent * xop->xo_indent_by;
713d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
71431337658SMarcel Moolenaar 	    rc += xop->xo_indent_by;
71531337658SMarcel Moolenaar     }
71631337658SMarcel Moolenaar 
717545ddfbeSMarcel Moolenaar     return (rc > 0) ? rc : 0;
71831337658SMarcel Moolenaar }
71931337658SMarcel Moolenaar 
72031337658SMarcel Moolenaar static void
xo_buf_indent(xo_handle_t * xop,int indent)72131337658SMarcel Moolenaar xo_buf_indent (xo_handle_t *xop, int indent)
72231337658SMarcel Moolenaar {
72331337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
72431337658SMarcel Moolenaar 
72531337658SMarcel Moolenaar     if (indent <= 0)
72631337658SMarcel Moolenaar 	indent = xo_indent(xop);
72731337658SMarcel Moolenaar 
72831337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, indent))
72931337658SMarcel Moolenaar 	return;
73031337658SMarcel Moolenaar 
73131337658SMarcel Moolenaar     memset(xbp->xb_curp, ' ', indent);
73231337658SMarcel Moolenaar     xbp->xb_curp += indent;
73331337658SMarcel Moolenaar }
73431337658SMarcel Moolenaar 
73531337658SMarcel Moolenaar static char xo_xml_amp[] = "&amp;";
73631337658SMarcel Moolenaar static char xo_xml_lt[] = "&lt;";
73731337658SMarcel Moolenaar static char xo_xml_gt[] = "&gt;";
73831337658SMarcel Moolenaar static char xo_xml_quot[] = "&quot;";
73931337658SMarcel Moolenaar 
7408a6eceffSPhil Shafer static ssize_t
xo_escape_xml(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags)7418a6eceffSPhil Shafer xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags)
74231337658SMarcel Moolenaar {
7438a6eceffSPhil Shafer     ssize_t slen;
7448a6eceffSPhil Shafer     ssize_t delta = 0;
74531337658SMarcel Moolenaar     char *cp, *ep, *ip;
74631337658SMarcel Moolenaar     const char *sp;
7478a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
74831337658SMarcel Moolenaar 
74931337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
75031337658SMarcel Moolenaar 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
75131337658SMarcel Moolenaar 	if (*cp == '<')
75231337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_lt) - 2;
75331337658SMarcel Moolenaar 	else if (*cp == '>')
75431337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_gt) - 2;
75531337658SMarcel Moolenaar 	else if (*cp == '&')
75631337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_amp) - 2;
75731337658SMarcel Moolenaar 	else if (attr && *cp == '"')
75831337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_quot) - 2;
75931337658SMarcel Moolenaar     }
76031337658SMarcel Moolenaar 
76131337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
76231337658SMarcel Moolenaar 	return len;
76331337658SMarcel Moolenaar 
76431337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
76531337658SMarcel Moolenaar 	return 0;
76631337658SMarcel Moolenaar 
76731337658SMarcel Moolenaar     ep = xbp->xb_curp;
76831337658SMarcel Moolenaar     cp = ep + len;
76931337658SMarcel Moolenaar     ip = cp + delta;
77031337658SMarcel Moolenaar     do {
77131337658SMarcel Moolenaar 	cp -= 1;
77231337658SMarcel Moolenaar 	ip -= 1;
77331337658SMarcel Moolenaar 
77431337658SMarcel Moolenaar 	if (*cp == '<')
77531337658SMarcel Moolenaar 	    sp = xo_xml_lt;
77631337658SMarcel Moolenaar 	else if (*cp == '>')
77731337658SMarcel Moolenaar 	    sp = xo_xml_gt;
77831337658SMarcel Moolenaar 	else if (*cp == '&')
77931337658SMarcel Moolenaar 	    sp = xo_xml_amp;
78031337658SMarcel Moolenaar 	else if (attr && *cp == '"')
78131337658SMarcel Moolenaar 	    sp = xo_xml_quot;
78231337658SMarcel Moolenaar 	else {
78331337658SMarcel Moolenaar 	    *ip = *cp;
78431337658SMarcel Moolenaar 	    continue;
78531337658SMarcel Moolenaar 	}
78631337658SMarcel Moolenaar 
78731337658SMarcel Moolenaar 	slen = strlen(sp);
78831337658SMarcel Moolenaar 	ip -= slen - 1;
78931337658SMarcel Moolenaar 	memcpy(ip, sp, slen);
79031337658SMarcel Moolenaar 
79131337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
79231337658SMarcel Moolenaar 
79331337658SMarcel Moolenaar     return len + delta;
79431337658SMarcel Moolenaar }
79531337658SMarcel Moolenaar 
7968a6eceffSPhil Shafer static ssize_t
xo_escape_json(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags UNUSED)7978a6eceffSPhil Shafer xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
79831337658SMarcel Moolenaar {
7998a6eceffSPhil Shafer     ssize_t delta = 0;
80031337658SMarcel Moolenaar     char *cp, *ep, *ip;
80131337658SMarcel Moolenaar 
80231337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
803545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"')
80431337658SMarcel Moolenaar 	    delta += 1;
805545ddfbeSMarcel Moolenaar 	else if (*cp == '\n' || *cp == '\r')
80631337658SMarcel Moolenaar 	    delta += 1;
80731337658SMarcel Moolenaar     }
80831337658SMarcel Moolenaar 
80931337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
81031337658SMarcel Moolenaar 	return len;
81131337658SMarcel Moolenaar 
81231337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
81331337658SMarcel Moolenaar 	return 0;
81431337658SMarcel Moolenaar 
81531337658SMarcel Moolenaar     ep = xbp->xb_curp;
81631337658SMarcel Moolenaar     cp = ep + len;
81731337658SMarcel Moolenaar     ip = cp + delta;
81831337658SMarcel Moolenaar     do {
81931337658SMarcel Moolenaar 	cp -= 1;
82031337658SMarcel Moolenaar 	ip -= 1;
82131337658SMarcel Moolenaar 
822545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"') {
82331337658SMarcel Moolenaar 	    *ip-- = *cp;
82431337658SMarcel Moolenaar 	    *ip = '\\';
825545ddfbeSMarcel Moolenaar 	} else if (*cp == '\n') {
826545ddfbeSMarcel Moolenaar 	    *ip-- = 'n';
827545ddfbeSMarcel Moolenaar 	    *ip = '\\';
828545ddfbeSMarcel Moolenaar 	} else if (*cp == '\r') {
829545ddfbeSMarcel Moolenaar 	    *ip-- = 'r';
830545ddfbeSMarcel Moolenaar 	    *ip = '\\';
831545ddfbeSMarcel Moolenaar 	} else {
832545ddfbeSMarcel Moolenaar 	    *ip = *cp;
833545ddfbeSMarcel Moolenaar 	}
83431337658SMarcel Moolenaar 
83531337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
83631337658SMarcel Moolenaar 
83731337658SMarcel Moolenaar     return len + delta;
83831337658SMarcel Moolenaar }
83931337658SMarcel Moolenaar 
84031337658SMarcel Moolenaar /*
841d1a0d267SMarcel Moolenaar  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
842d1a0d267SMarcel Moolenaar  *                                ; ']' MUST be escaped.
84331337658SMarcel Moolenaar  */
8448a6eceffSPhil Shafer static ssize_t
xo_escape_sdparams(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags UNUSED)8458a6eceffSPhil Shafer xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
84631337658SMarcel Moolenaar {
8478a6eceffSPhil Shafer     ssize_t delta = 0;
848d1a0d267SMarcel Moolenaar     char *cp, *ep, *ip;
84931337658SMarcel Moolenaar 
850d1a0d267SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
851d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']')
852d1a0d267SMarcel Moolenaar 	    delta += 1;
85331337658SMarcel Moolenaar     }
85431337658SMarcel Moolenaar 
855d1a0d267SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
856d1a0d267SMarcel Moolenaar 	return len;
857788ca347SMarcel Moolenaar 
858d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
859d1a0d267SMarcel Moolenaar 	return 0;
860788ca347SMarcel Moolenaar 
861d1a0d267SMarcel Moolenaar     ep = xbp->xb_curp;
862d1a0d267SMarcel Moolenaar     cp = ep + len;
863d1a0d267SMarcel Moolenaar     ip = cp + delta;
864d1a0d267SMarcel Moolenaar     do {
865d1a0d267SMarcel Moolenaar 	cp -= 1;
866d1a0d267SMarcel Moolenaar 	ip -= 1;
867d1a0d267SMarcel Moolenaar 
868d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
869d1a0d267SMarcel Moolenaar 	    *ip-- = *cp;
870d1a0d267SMarcel Moolenaar 	    *ip = '\\';
871d1a0d267SMarcel Moolenaar 	} else {
872d1a0d267SMarcel Moolenaar 	    *ip = *cp;
873d1a0d267SMarcel Moolenaar 	}
874d1a0d267SMarcel Moolenaar 
875d1a0d267SMarcel Moolenaar     } while (cp > ep && cp != ip);
876d1a0d267SMarcel Moolenaar 
877d1a0d267SMarcel Moolenaar     return len + delta;
878788ca347SMarcel Moolenaar }
879788ca347SMarcel Moolenaar 
88031337658SMarcel Moolenaar static void
xo_buf_escape(xo_handle_t * xop,xo_buffer_t * xbp,const char * str,ssize_t len,xo_xff_flags_t flags)88131337658SMarcel Moolenaar xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
8828a6eceffSPhil Shafer 	       const char *str, ssize_t len, xo_xff_flags_t flags)
88331337658SMarcel Moolenaar {
88431337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, len))
88531337658SMarcel Moolenaar 	return;
88631337658SMarcel Moolenaar 
88731337658SMarcel Moolenaar     memcpy(xbp->xb_curp, str, len);
88831337658SMarcel Moolenaar 
889788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
89031337658SMarcel Moolenaar     case XO_STYLE_XML:
89131337658SMarcel Moolenaar     case XO_STYLE_HTML:
892d1a0d267SMarcel Moolenaar 	len = xo_escape_xml(xbp, len, flags);
89331337658SMarcel Moolenaar 	break;
89431337658SMarcel Moolenaar 
89531337658SMarcel Moolenaar     case XO_STYLE_JSON:
896d1a0d267SMarcel Moolenaar 	len = xo_escape_json(xbp, len, flags);
897d1a0d267SMarcel Moolenaar 	break;
898d1a0d267SMarcel Moolenaar 
899d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
900d1a0d267SMarcel Moolenaar 	len = xo_escape_sdparams(xbp, len, flags);
90131337658SMarcel Moolenaar 	break;
90231337658SMarcel Moolenaar     }
90331337658SMarcel Moolenaar 
90431337658SMarcel Moolenaar     xbp->xb_curp += len;
90531337658SMarcel Moolenaar }
90631337658SMarcel Moolenaar 
90731337658SMarcel Moolenaar /*
90831337658SMarcel Moolenaar  * Write the current contents of the data buffer using the handle's
90931337658SMarcel Moolenaar  * xo_write function.
91031337658SMarcel Moolenaar  */
9118a6eceffSPhil Shafer static ssize_t
xo_write(xo_handle_t * xop)91231337658SMarcel Moolenaar xo_write (xo_handle_t *xop)
91331337658SMarcel Moolenaar {
9148a6eceffSPhil Shafer     ssize_t rc = 0;
91531337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
91631337658SMarcel Moolenaar 
91731337658SMarcel Moolenaar     if (xbp->xb_curp != xbp->xb_bufp) {
91831337658SMarcel Moolenaar 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
91931337658SMarcel Moolenaar 	xo_anchor_clear(xop);
920d1a0d267SMarcel Moolenaar 	if (xop->xo_write)
921545ddfbeSMarcel Moolenaar 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
92231337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp;
92331337658SMarcel Moolenaar     }
92431337658SMarcel Moolenaar 
92531337658SMarcel Moolenaar     /* Turn off the flags that don't survive across writes */
926d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
927545ddfbeSMarcel Moolenaar 
928545ddfbeSMarcel Moolenaar     return rc;
92931337658SMarcel Moolenaar }
93031337658SMarcel Moolenaar 
93131337658SMarcel Moolenaar /*
93231337658SMarcel Moolenaar  * Format arguments into our buffer.  If a custom formatter has been set,
93331337658SMarcel Moolenaar  * we use that to do the work; otherwise we vsnprintf().
93431337658SMarcel Moolenaar  */
9358a6eceffSPhil Shafer static ssize_t
xo_vsnprintf(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,va_list vap)93631337658SMarcel Moolenaar xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
93731337658SMarcel Moolenaar {
93831337658SMarcel Moolenaar     va_list va_local;
9398a6eceffSPhil Shafer     ssize_t rc;
9408a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
94131337658SMarcel Moolenaar 
94231337658SMarcel Moolenaar     va_copy(va_local, vap);
94331337658SMarcel Moolenaar 
94431337658SMarcel Moolenaar     if (xop->xo_formatter)
94531337658SMarcel Moolenaar 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
94631337658SMarcel Moolenaar     else
94731337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
94831337658SMarcel Moolenaar 
949788ca347SMarcel Moolenaar     if (rc >= left) {
950c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
951c600d307SMarcel Moolenaar 	    va_end(va_local);
95231337658SMarcel Moolenaar 	    return -1;
953c600d307SMarcel Moolenaar 	}
95431337658SMarcel Moolenaar 
95531337658SMarcel Moolenaar 	/*
95631337658SMarcel Moolenaar 	 * After we call vsnprintf(), the stage of vap is not defined.
95731337658SMarcel Moolenaar 	 * We need to copy it before we pass.  Then we have to do our
95831337658SMarcel Moolenaar 	 * own logic below to move it along.  This is because the
959788ca347SMarcel Moolenaar 	 * implementation can have va_list be a pointer (bsd) or a
96031337658SMarcel Moolenaar 	 * structure (macosx) or anything in between.
96131337658SMarcel Moolenaar 	 */
96231337658SMarcel Moolenaar 
96331337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
96431337658SMarcel Moolenaar 	va_copy(va_local, vap);
96531337658SMarcel Moolenaar 
96631337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
96731337658SMarcel Moolenaar 	if (xop->xo_formatter)
968788ca347SMarcel Moolenaar 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
96931337658SMarcel Moolenaar 	else
97031337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
97131337658SMarcel Moolenaar     }
97231337658SMarcel Moolenaar     va_end(va_local);
97331337658SMarcel Moolenaar 
97431337658SMarcel Moolenaar     return rc;
97531337658SMarcel Moolenaar }
97631337658SMarcel Moolenaar 
97731337658SMarcel Moolenaar /*
978ee5cf116SPhil Shafer  * Print some data through the handle.
97931337658SMarcel Moolenaar  */
9808a6eceffSPhil Shafer static ssize_t
xo_printf_v(xo_handle_t * xop,const char * fmt,va_list vap)98131337658SMarcel Moolenaar xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
98231337658SMarcel Moolenaar {
98331337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
9848a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
9858a6eceffSPhil Shafer     ssize_t rc;
98631337658SMarcel Moolenaar     va_list va_local;
98731337658SMarcel Moolenaar 
98831337658SMarcel Moolenaar     va_copy(va_local, vap);
98931337658SMarcel Moolenaar 
99031337658SMarcel Moolenaar     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
99131337658SMarcel Moolenaar 
992d1a0d267SMarcel Moolenaar     if (rc >= left) {
993c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
994c600d307SMarcel Moolenaar 	    va_end(va_local);
99531337658SMarcel Moolenaar 	    return -1;
996c600d307SMarcel Moolenaar 	}
99731337658SMarcel Moolenaar 
99831337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
99931337658SMarcel Moolenaar 	va_copy(va_local, vap);
100031337658SMarcel Moolenaar 
100131337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
100231337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
100331337658SMarcel Moolenaar     }
100431337658SMarcel Moolenaar 
100531337658SMarcel Moolenaar     va_end(va_local);
100631337658SMarcel Moolenaar 
100731337658SMarcel Moolenaar     if (rc > 0)
100831337658SMarcel Moolenaar 	xbp->xb_curp += rc;
100931337658SMarcel Moolenaar 
101031337658SMarcel Moolenaar     return rc;
101131337658SMarcel Moolenaar }
101231337658SMarcel Moolenaar 
10138a6eceffSPhil Shafer static ssize_t
xo_printf(xo_handle_t * xop,const char * fmt,...)101431337658SMarcel Moolenaar xo_printf (xo_handle_t *xop, const char *fmt, ...)
101531337658SMarcel Moolenaar {
10168a6eceffSPhil Shafer     ssize_t rc;
101731337658SMarcel Moolenaar     va_list vap;
101831337658SMarcel Moolenaar 
101931337658SMarcel Moolenaar     va_start(vap, fmt);
102031337658SMarcel Moolenaar 
102131337658SMarcel Moolenaar     rc = xo_printf_v(xop, fmt, vap);
102231337658SMarcel Moolenaar 
102331337658SMarcel Moolenaar     va_end(vap);
102431337658SMarcel Moolenaar     return rc;
102531337658SMarcel Moolenaar }
102631337658SMarcel Moolenaar 
102731337658SMarcel Moolenaar /*
102831337658SMarcel Moolenaar  * These next few function are make The Essential UTF-8 Ginsu Knife.
102931337658SMarcel Moolenaar  * Identify an input and output character, and convert it.
103031337658SMarcel Moolenaar  */
1031f2b7bf8aSPhil Shafer static uint8_t xo_utf8_data_bits[5] = { 0, 0x7f, 0x1f, 0x0f, 0x07 };
1032f2b7bf8aSPhil Shafer static uint8_t xo_utf8_len_bits[5]  = { 0, 0x00, 0xc0, 0xe0, 0xf0 };
103331337658SMarcel Moolenaar 
1034f2b7bf8aSPhil Shafer /*
1035f2b7bf8aSPhil Shafer  * If the byte has a high-bit set, it's UTF-8, not ASCII.
1036f2b7bf8aSPhil Shafer  */
103731337658SMarcel Moolenaar static int
xo_is_utf8(char ch)103831337658SMarcel Moolenaar xo_is_utf8 (char ch)
103931337658SMarcel Moolenaar {
104031337658SMarcel Moolenaar     return (ch & 0x80);
104131337658SMarcel Moolenaar }
104231337658SMarcel Moolenaar 
1043f2b7bf8aSPhil Shafer /*
1044f2b7bf8aSPhil Shafer  * Look at the high bits of the first byte to determine the length
1045f2b7bf8aSPhil Shafer  * of the UTF-8 character.
1046f2b7bf8aSPhil Shafer  */
10478a6eceffSPhil Shafer static inline ssize_t
xo_utf8_to_wc_len(const char * buf)104831337658SMarcel Moolenaar xo_utf8_to_wc_len (const char *buf)
104931337658SMarcel Moolenaar {
1050f2b7bf8aSPhil Shafer     uint8_t bval = (uint8_t) *buf;
10518a6eceffSPhil Shafer     ssize_t len;
105231337658SMarcel Moolenaar 
1053f2b7bf8aSPhil Shafer     if ((bval & 0x80) == 0x0)
105431337658SMarcel Moolenaar 	len = 1;
1055f2b7bf8aSPhil Shafer     else if ((bval & 0xe0) == 0xc0)
105631337658SMarcel Moolenaar 	len = 2;
1057f2b7bf8aSPhil Shafer     else if ((bval & 0xf0) == 0xe0)
105831337658SMarcel Moolenaar 	len = 3;
1059f2b7bf8aSPhil Shafer     else if ((bval & 0xf8) == 0xf0)
106031337658SMarcel Moolenaar 	len = 4;
106131337658SMarcel Moolenaar     else
106231337658SMarcel Moolenaar 	len = -1;
106331337658SMarcel Moolenaar 
106431337658SMarcel Moolenaar     return len;
106531337658SMarcel Moolenaar }
106631337658SMarcel Moolenaar 
10678a6eceffSPhil Shafer static ssize_t
xo_buf_utf8_len(xo_handle_t * xop,const char * buf,ssize_t bufsiz)10688a6eceffSPhil Shafer xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz)
106931337658SMarcel Moolenaar {
107031337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
10718a6eceffSPhil Shafer     ssize_t len, i;
107231337658SMarcel Moolenaar 
107331337658SMarcel Moolenaar     len = xo_utf8_to_wc_len(buf);
1074f2b7bf8aSPhil Shafer     if (len < 0) {
107531337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
107631337658SMarcel Moolenaar 	return -1;
107731337658SMarcel Moolenaar     }
107831337658SMarcel Moolenaar 
107931337658SMarcel Moolenaar     if (len > bufsiz) {
108031337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
108131337658SMarcel Moolenaar 		   b, len, bufsiz);
108231337658SMarcel Moolenaar 	return -1;
108331337658SMarcel Moolenaar     }
108431337658SMarcel Moolenaar 
108531337658SMarcel Moolenaar     for (i = 2; i < len; i++) {
108631337658SMarcel Moolenaar 	b = (unsigned char ) buf[i];
108731337658SMarcel Moolenaar 	if ((b & 0xc0) != 0x80) {
108831337658SMarcel Moolenaar 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
108931337658SMarcel Moolenaar 	    return -1;
109031337658SMarcel Moolenaar 	}
109131337658SMarcel Moolenaar     }
109231337658SMarcel Moolenaar 
109331337658SMarcel Moolenaar     return len;
109431337658SMarcel Moolenaar }
109531337658SMarcel Moolenaar 
109631337658SMarcel Moolenaar /*
109731337658SMarcel Moolenaar  * Build a wide character from the input buffer; the number of
109831337658SMarcel Moolenaar  * bits we pull off the first character is dependent on the length,
109931337658SMarcel Moolenaar  * but we put 6 bits off all other bytes.
110031337658SMarcel Moolenaar  */
110142ff34c3SPhil Shafer static inline wchar_t
xo_utf8_char(const char * buf,ssize_t len)11028a6eceffSPhil Shafer xo_utf8_char (const char *buf, ssize_t len)
110331337658SMarcel Moolenaar {
110442ff34c3SPhil Shafer     /* Most common case: singleton byte */
110542ff34c3SPhil Shafer     if (len == 1)
110642ff34c3SPhil Shafer 	return (unsigned char) buf[0];
110742ff34c3SPhil Shafer 
11088a6eceffSPhil Shafer     ssize_t i;
110931337658SMarcel Moolenaar     wchar_t wc;
111031337658SMarcel Moolenaar     const unsigned char *cp = (const unsigned char *) buf;
111131337658SMarcel Moolenaar 
1112f2b7bf8aSPhil Shafer     wc = *cp & xo_utf8_data_bits[len];
111331337658SMarcel Moolenaar     for (i = 1; i < len; i++) {
1114f2b7bf8aSPhil Shafer 	wc <<= 6;		/* Low six bits have data */
111531337658SMarcel Moolenaar 	wc |= cp[i] & 0x3f;
111631337658SMarcel Moolenaar 	if ((cp[i] & 0xc0) != 0x80)
111731337658SMarcel Moolenaar 	    return (wchar_t) -1;
111831337658SMarcel Moolenaar     }
111931337658SMarcel Moolenaar 
112031337658SMarcel Moolenaar     return wc;
112131337658SMarcel Moolenaar }
112231337658SMarcel Moolenaar 
112331337658SMarcel Moolenaar /*
112431337658SMarcel Moolenaar  * Determine the number of bytes needed to encode a wide character.
112531337658SMarcel Moolenaar  */
11268a6eceffSPhil Shafer static ssize_t
xo_utf8_emit_len(wchar_t wc)112731337658SMarcel Moolenaar xo_utf8_emit_len (wchar_t wc)
112831337658SMarcel Moolenaar {
11298a6eceffSPhil Shafer     ssize_t len;
113031337658SMarcel Moolenaar 
113131337658SMarcel Moolenaar     if ((wc & ((1 << 7) - 1)) == wc) /* Simple case */
113231337658SMarcel Moolenaar 	len = 1;
113331337658SMarcel Moolenaar     else if ((wc & ((1 << 11) - 1)) == wc)
113431337658SMarcel Moolenaar 	len = 2;
113531337658SMarcel Moolenaar     else if ((wc & ((1 << 16) - 1)) == wc)
113631337658SMarcel Moolenaar 	len = 3;
113731337658SMarcel Moolenaar     else if ((wc & ((1 << 21) - 1)) == wc)
113831337658SMarcel Moolenaar 	len = 4;
113931337658SMarcel Moolenaar     else
1140f2b7bf8aSPhil Shafer 	len = -1;		/* Invalid */
114131337658SMarcel Moolenaar 
114231337658SMarcel Moolenaar     return len;
114331337658SMarcel Moolenaar }
114431337658SMarcel Moolenaar 
1145f2b7bf8aSPhil Shafer /*
11462f784130SPhil Shafer  * Emit one wide character into the given buffer
1147f2b7bf8aSPhil Shafer  */
114831337658SMarcel Moolenaar static void
xo_utf8_emit_char(char * buf,ssize_t len,wchar_t wc)11498a6eceffSPhil Shafer xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc)
115031337658SMarcel Moolenaar {
11518a6eceffSPhil Shafer     ssize_t i;
115231337658SMarcel Moolenaar 
115331337658SMarcel Moolenaar     if (len == 1) { /* Simple case */
115431337658SMarcel Moolenaar 	buf[0] = wc & 0x7f;
115531337658SMarcel Moolenaar 	return;
115631337658SMarcel Moolenaar     }
115731337658SMarcel Moolenaar 
11582f784130SPhil Shafer     /* Start with the low bits and insert them, six bits at a time */
115931337658SMarcel Moolenaar     for (i = len - 1; i >= 0; i--) {
116031337658SMarcel Moolenaar 	buf[i] = 0x80 | (wc & 0x3f);
1161f2b7bf8aSPhil Shafer 	wc >>= 6;		/* Drop the low six bits */
116231337658SMarcel Moolenaar     }
116331337658SMarcel Moolenaar 
1164f2b7bf8aSPhil Shafer     /* Finish off the first byte with the length bits */
1165f2b7bf8aSPhil Shafer     buf[0] &= xo_utf8_data_bits[len]; /* Clear out the length bits */
1166f2b7bf8aSPhil Shafer     buf[0] |= xo_utf8_len_bits[len]; /* Drop in new length bits */
116731337658SMarcel Moolenaar }
116831337658SMarcel Moolenaar 
1169f2b7bf8aSPhil Shafer /*
1170f2b7bf8aSPhil Shafer  * Append a single UTF-8 character to a buffer, converting it to locale
1171f2b7bf8aSPhil Shafer  * encoding.  Returns the number of columns consumed by that character,
1172f2b7bf8aSPhil Shafer  * as best we can determine it.
1173f2b7bf8aSPhil Shafer  */
11748a6eceffSPhil Shafer static ssize_t
xo_buf_append_locale_from_utf8(xo_handle_t * xop,xo_buffer_t * xbp,const char * ibuf,ssize_t ilen)117531337658SMarcel Moolenaar xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
11768a6eceffSPhil Shafer 				const char *ibuf, ssize_t ilen)
117731337658SMarcel Moolenaar {
117831337658SMarcel Moolenaar     wchar_t wc;
11798a6eceffSPhil Shafer     ssize_t len;
118031337658SMarcel Moolenaar 
118131337658SMarcel Moolenaar     /*
118231337658SMarcel Moolenaar      * Build our wide character from the input buffer; the number of
118331337658SMarcel Moolenaar      * bits we pull off the first character is dependent on the length,
118431337658SMarcel Moolenaar      * but we put 6 bits off all other bytes.
118531337658SMarcel Moolenaar      */
118631337658SMarcel Moolenaar     wc = xo_utf8_char(ibuf, ilen);
118731337658SMarcel Moolenaar     if (wc == (wchar_t) -1) {
1188f2b7bf8aSPhil Shafer 	xo_failure(xop, "invalid UTF-8 byte sequence");
118931337658SMarcel Moolenaar 	return 0;
119031337658SMarcel Moolenaar     }
119131337658SMarcel Moolenaar 
1192d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
119331337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, ilen))
119431337658SMarcel Moolenaar 	    return 0;
119531337658SMarcel Moolenaar 
119631337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, ibuf, ilen);
119731337658SMarcel Moolenaar 	xbp->xb_curp += ilen;
119831337658SMarcel Moolenaar 
119931337658SMarcel Moolenaar     } else {
120031337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
120131337658SMarcel Moolenaar 	    return 0;
120231337658SMarcel Moolenaar 
120331337658SMarcel Moolenaar 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
120431337658SMarcel Moolenaar 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
120531337658SMarcel Moolenaar 
120631337658SMarcel Moolenaar 	if (len <= 0) {
120731337658SMarcel Moolenaar 	    xo_failure(xop, "could not convert wide char: %lx",
120831337658SMarcel Moolenaar 		       (unsigned long) wc);
120931337658SMarcel Moolenaar 	    return 0;
121031337658SMarcel Moolenaar 	}
121131337658SMarcel Moolenaar 	xbp->xb_curp += len;
121231337658SMarcel Moolenaar     }
121331337658SMarcel Moolenaar 
1214d1a0d267SMarcel Moolenaar     return xo_wcwidth(wc);
121531337658SMarcel Moolenaar }
121631337658SMarcel Moolenaar 
1217f2b7bf8aSPhil Shafer /*
1218f2b7bf8aSPhil Shafer  * Append a UTF-8 string to a buffer, converting it into locale encoding
1219f2b7bf8aSPhil Shafer  */
122031337658SMarcel Moolenaar static void
xo_buf_append_locale(xo_handle_t * xop,xo_buffer_t * xbp,const char * cp,ssize_t len)122131337658SMarcel Moolenaar xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
12228a6eceffSPhil Shafer 		      const char *cp, ssize_t len)
122331337658SMarcel Moolenaar {
122431337658SMarcel Moolenaar     const char *sp = cp, *ep = cp + len;
12258a6eceffSPhil Shafer     ssize_t save_off = xbp->xb_bufp - xbp->xb_curp;
12268a6eceffSPhil Shafer     ssize_t slen;
122731337658SMarcel Moolenaar     int cols = 0;
122831337658SMarcel Moolenaar 
122931337658SMarcel Moolenaar     for ( ; cp < ep; cp++) {
123031337658SMarcel Moolenaar 	if (!xo_is_utf8(*cp)) {
123131337658SMarcel Moolenaar 	    cols += 1;
123231337658SMarcel Moolenaar 	    continue;
123331337658SMarcel Moolenaar 	}
123431337658SMarcel Moolenaar 
123531337658SMarcel Moolenaar 	/*
123631337658SMarcel Moolenaar 	 * We're looking at a non-ascii UTF-8 character.
123731337658SMarcel Moolenaar 	 * First we copy the previous data.
123831337658SMarcel Moolenaar 	 * Then we need find the length and validate it.
123931337658SMarcel Moolenaar 	 * Then we turn it into a wide string.
124031337658SMarcel Moolenaar 	 * Then we turn it into a localized string.
124131337658SMarcel Moolenaar 	 * Then we repeat.  Isn't i18n fun?
124231337658SMarcel Moolenaar 	 */
124331337658SMarcel Moolenaar 	if (sp != cp)
124431337658SMarcel Moolenaar 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
124531337658SMarcel Moolenaar 
124631337658SMarcel Moolenaar 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
124731337658SMarcel Moolenaar 	if (slen <= 0) {
124831337658SMarcel Moolenaar 	    /* Bad data; back it all out */
124931337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + save_off;
125031337658SMarcel Moolenaar 	    return;
125131337658SMarcel Moolenaar 	}
125231337658SMarcel Moolenaar 
125331337658SMarcel Moolenaar 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
125431337658SMarcel Moolenaar 
1255ee5cf116SPhil Shafer 	/* Next time through, we'll start at the next character */
125631337658SMarcel Moolenaar 	cp += slen - 1;
125731337658SMarcel Moolenaar 	sp = cp + 1;
125831337658SMarcel Moolenaar     }
125931337658SMarcel Moolenaar 
126031337658SMarcel Moolenaar     /* Update column values */
1261d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
126231337658SMarcel Moolenaar 	xop->xo_columns += cols;
1263d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
126431337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
126531337658SMarcel Moolenaar 
126631337658SMarcel Moolenaar     /* Before we fall into the basic logic below, we need reset len */
126731337658SMarcel Moolenaar     len = ep - sp;
126831337658SMarcel Moolenaar     if (len != 0) /* Append trailing data */
126931337658SMarcel Moolenaar 	xo_buf_append(xbp, sp, len);
127031337658SMarcel Moolenaar }
127131337658SMarcel Moolenaar 
127231337658SMarcel Moolenaar /*
1273d1a0d267SMarcel Moolenaar  * Append the given string to the given buffer, without escaping or
1274d1a0d267SMarcel Moolenaar  * character set conversion.  This is the straight copy to the data
1275d1a0d267SMarcel Moolenaar  * buffer with no fanciness.
127631337658SMarcel Moolenaar  */
127731337658SMarcel Moolenaar static void
xo_data_append(xo_handle_t * xop,const char * str,ssize_t len)12788a6eceffSPhil Shafer xo_data_append (xo_handle_t *xop, const char *str, ssize_t len)
127931337658SMarcel Moolenaar {
128031337658SMarcel Moolenaar     xo_buf_append(&xop->xo_data, str, len);
128131337658SMarcel Moolenaar }
128231337658SMarcel Moolenaar 
128331337658SMarcel Moolenaar /*
128431337658SMarcel Moolenaar  * Append the given string to the given buffer
128531337658SMarcel Moolenaar  */
128631337658SMarcel Moolenaar static void
xo_data_escape(xo_handle_t * xop,const char * str,ssize_t len)12878a6eceffSPhil Shafer xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len)
128831337658SMarcel Moolenaar {
128931337658SMarcel Moolenaar     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
129031337658SMarcel Moolenaar }
129131337658SMarcel Moolenaar 
129242ff34c3SPhil Shafer #ifdef LIBXO_NO_RETAIN
129342ff34c3SPhil Shafer /*
129442ff34c3SPhil Shafer  * Empty implementations of the retain logic
129542ff34c3SPhil Shafer  */
129642ff34c3SPhil Shafer 
129742ff34c3SPhil Shafer void
xo_retain_clear_all(void)129842ff34c3SPhil Shafer xo_retain_clear_all (void)
129942ff34c3SPhil Shafer {
130042ff34c3SPhil Shafer     return;
130142ff34c3SPhil Shafer }
130242ff34c3SPhil Shafer 
130342ff34c3SPhil Shafer void
xo_retain_clear(const char * fmt UNUSED)130442ff34c3SPhil Shafer xo_retain_clear (const char *fmt UNUSED)
130542ff34c3SPhil Shafer {
130642ff34c3SPhil Shafer     return;
130742ff34c3SPhil Shafer }
130842ff34c3SPhil Shafer static void
xo_retain_add(const char * fmt UNUSED,xo_field_info_t * fields UNUSED,unsigned num_fields UNUSED)130942ff34c3SPhil Shafer xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
131042ff34c3SPhil Shafer 		unsigned num_fields UNUSED)
131142ff34c3SPhil Shafer {
131242ff34c3SPhil Shafer     return;
131342ff34c3SPhil Shafer }
131442ff34c3SPhil Shafer 
131542ff34c3SPhil Shafer static int
xo_retain_find(const char * fmt UNUSED,xo_field_info_t ** valp UNUSED,unsigned * nump UNUSED)131642ff34c3SPhil Shafer xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
131742ff34c3SPhil Shafer 		 unsigned *nump UNUSED)
131842ff34c3SPhil Shafer {
131942ff34c3SPhil Shafer     return -1;
132042ff34c3SPhil Shafer }
132142ff34c3SPhil Shafer 
132242ff34c3SPhil Shafer #else /* !LIBXO_NO_RETAIN */
132342ff34c3SPhil Shafer /*
132442ff34c3SPhil Shafer  * Retain: We retain parsed field definitions to enhance performance,
132542ff34c3SPhil Shafer  * especially inside loops.  We depend on the caller treating the format
132642ff34c3SPhil Shafer  * strings as immutable, so that we can retain pointers into them.  We
132742ff34c3SPhil Shafer  * hold the pointers in a hash table, so allow quick access.  Retained
132842ff34c3SPhil Shafer  * information is retained until xo_retain_clear is called.
132942ff34c3SPhil Shafer  */
133042ff34c3SPhil Shafer 
133142ff34c3SPhil Shafer /*
133242ff34c3SPhil Shafer  * xo_retain_entry_t holds information about one retained set of
133342ff34c3SPhil Shafer  * parsed fields.
133442ff34c3SPhil Shafer  */
133542ff34c3SPhil Shafer typedef struct xo_retain_entry_s {
133642ff34c3SPhil Shafer     struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
133742ff34c3SPhil Shafer     unsigned long xre_hits;		 /* Number of times we've hit */
133842ff34c3SPhil Shafer     const char *xre_format;		 /* Pointer to format string */
133942ff34c3SPhil Shafer     unsigned xre_num_fields;		 /* Number of fields saved */
134042ff34c3SPhil Shafer     xo_field_info_t *xre_fields;	 /* Pointer to fields */
134142ff34c3SPhil Shafer } xo_retain_entry_t;
134242ff34c3SPhil Shafer 
134342ff34c3SPhil Shafer /*
134442ff34c3SPhil Shafer  * xo_retain_t holds a complete set of parsed fields as a hash table.
134542ff34c3SPhil Shafer  */
134642ff34c3SPhil Shafer #ifndef XO_RETAIN_SIZE
134742ff34c3SPhil Shafer #define XO_RETAIN_SIZE 6
134842ff34c3SPhil Shafer #endif /* XO_RETAIN_SIZE */
134942ff34c3SPhil Shafer #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
135042ff34c3SPhil Shafer 
135142ff34c3SPhil Shafer typedef struct xo_retain_s {
135242ff34c3SPhil Shafer     xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
135342ff34c3SPhil Shafer } xo_retain_t;
135442ff34c3SPhil Shafer 
135542ff34c3SPhil Shafer static THREAD_LOCAL(xo_retain_t) xo_retain;
135642ff34c3SPhil Shafer static THREAD_LOCAL(unsigned) xo_retain_count;
135742ff34c3SPhil Shafer 
135842ff34c3SPhil Shafer /*
135942ff34c3SPhil Shafer  * Simple hash function based on Thomas Wang's paper.  The original is
136042ff34c3SPhil Shafer  * gone, but an archive is available on the Way Back Machine:
136142ff34c3SPhil Shafer  *
136242ff34c3SPhil Shafer  * http://web.archive.org/web/20071223173210/\
136342ff34c3SPhil Shafer  *     http://www.concentric.net/~Ttwang/tech/inthash.htm
136442ff34c3SPhil Shafer  *
136542ff34c3SPhil Shafer  * For our purposes, we can assume the low four bits are uninteresting
136642ff34c3SPhil Shafer  * since any string less that 16 bytes wouldn't be worthy of
136742ff34c3SPhil Shafer  * retaining.  We toss the high bits also, since these bits are likely
136842ff34c3SPhil Shafer  * to be common among constant format strings.  We then run Wang's
136942ff34c3SPhil Shafer  * algorithm, and cap the result at RETAIN_HASH_SIZE.
137042ff34c3SPhil Shafer  */
137142ff34c3SPhil Shafer static unsigned
xo_retain_hash(const char * fmt)137242ff34c3SPhil Shafer xo_retain_hash (const char *fmt)
137342ff34c3SPhil Shafer {
137442ff34c3SPhil Shafer     volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
137542ff34c3SPhil Shafer 
137642ff34c3SPhil Shafer     /* Discard low four bits and high bits; they aren't interesting */
137742ff34c3SPhil Shafer     uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
137842ff34c3SPhil Shafer 
137942ff34c3SPhil Shafer     val = (val ^ 61) ^ (val >> 16);
138042ff34c3SPhil Shafer     val = val + (val << 3);
138142ff34c3SPhil Shafer     val = val ^ (val >> 4);
138242ff34c3SPhil Shafer     val = val * 0x3a8f05c5;	/* My large prime number */
138342ff34c3SPhil Shafer     val = val ^ (val >> 15);
138442ff34c3SPhil Shafer     val &= RETAIN_HASH_SIZE - 1;
138542ff34c3SPhil Shafer 
138642ff34c3SPhil Shafer     return val;
138742ff34c3SPhil Shafer }
138842ff34c3SPhil Shafer 
138942ff34c3SPhil Shafer /*
139042ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
139142ff34c3SPhil Shafer  */
139242ff34c3SPhil Shafer void
xo_retain_clear_all(void)139342ff34c3SPhil Shafer xo_retain_clear_all (void)
139442ff34c3SPhil Shafer {
139542ff34c3SPhil Shafer     int i;
139642ff34c3SPhil Shafer     xo_retain_entry_t *xrep, *next;
139742ff34c3SPhil Shafer 
139842ff34c3SPhil Shafer     for (i = 0; i < RETAIN_HASH_SIZE; i++) {
139942ff34c3SPhil Shafer 	for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
140042ff34c3SPhil Shafer 	    next = xrep->xre_next;
140142ff34c3SPhil Shafer 	    xo_free(xrep);
140242ff34c3SPhil Shafer 	}
140342ff34c3SPhil Shafer 	xo_retain.xr_bucket[i] = NULL;
140442ff34c3SPhil Shafer     }
140542ff34c3SPhil Shafer     xo_retain_count = 0;
140642ff34c3SPhil Shafer }
140742ff34c3SPhil Shafer 
140842ff34c3SPhil Shafer /*
140942ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
141042ff34c3SPhil Shafer  */
141142ff34c3SPhil Shafer void
xo_retain_clear(const char * fmt)141242ff34c3SPhil Shafer xo_retain_clear (const char *fmt)
141342ff34c3SPhil Shafer {
141442ff34c3SPhil Shafer     xo_retain_entry_t **xrepp;
141542ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
141642ff34c3SPhil Shafer 
141742ff34c3SPhil Shafer     for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
141842ff34c3SPhil Shafer 	 xrepp = &(*xrepp)->xre_next) {
141942ff34c3SPhil Shafer 	if ((*xrepp)->xre_format == fmt) {
142042ff34c3SPhil Shafer 	    *xrepp = (*xrepp)->xre_next;
142142ff34c3SPhil Shafer 	    xo_retain_count -= 1;
142242ff34c3SPhil Shafer 	    return;
142342ff34c3SPhil Shafer 	}
142442ff34c3SPhil Shafer     }
142542ff34c3SPhil Shafer }
142642ff34c3SPhil Shafer 
142742ff34c3SPhil Shafer /*
142842ff34c3SPhil Shafer  * Search the hash for an entry matching 'fmt'; return it's fields.
142942ff34c3SPhil Shafer  */
143042ff34c3SPhil Shafer static int
xo_retain_find(const char * fmt,xo_field_info_t ** valp,unsigned * nump)143142ff34c3SPhil Shafer xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
143242ff34c3SPhil Shafer {
143342ff34c3SPhil Shafer     if (xo_retain_count == 0)
143442ff34c3SPhil Shafer 	return -1;
143542ff34c3SPhil Shafer 
143642ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
143742ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
143842ff34c3SPhil Shafer 
143942ff34c3SPhil Shafer     for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
144042ff34c3SPhil Shafer 	 xrep = xrep->xre_next) {
144142ff34c3SPhil Shafer 	if (xrep->xre_format == fmt) {
144242ff34c3SPhil Shafer 	    *valp = xrep->xre_fields;
144342ff34c3SPhil Shafer 	    *nump = xrep->xre_num_fields;
144442ff34c3SPhil Shafer 	    xrep->xre_hits += 1;
144542ff34c3SPhil Shafer 	    return 0;
144642ff34c3SPhil Shafer 	}
144742ff34c3SPhil Shafer     }
144842ff34c3SPhil Shafer 
144942ff34c3SPhil Shafer     return -1;
145042ff34c3SPhil Shafer }
145142ff34c3SPhil Shafer 
145242ff34c3SPhil Shafer static void
xo_retain_add(const char * fmt,xo_field_info_t * fields,unsigned num_fields)145342ff34c3SPhil Shafer xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
145442ff34c3SPhil Shafer {
145542ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
145642ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
14578a6eceffSPhil Shafer     ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
145842ff34c3SPhil Shafer     xo_field_info_t *xfip;
145942ff34c3SPhil Shafer 
146042ff34c3SPhil Shafer     xrep = xo_realloc(NULL, sz);
146142ff34c3SPhil Shafer     if (xrep == NULL)
146242ff34c3SPhil Shafer 	return;
146342ff34c3SPhil Shafer 
146442ff34c3SPhil Shafer     xfip = (xo_field_info_t *) &xrep[1];
146542ff34c3SPhil Shafer     memcpy(xfip, fields, num_fields * sizeof(*fields));
146642ff34c3SPhil Shafer 
146742ff34c3SPhil Shafer     bzero(xrep, sizeof(*xrep));
146842ff34c3SPhil Shafer 
146942ff34c3SPhil Shafer     xrep->xre_format = fmt;
147042ff34c3SPhil Shafer     xrep->xre_fields = xfip;
147142ff34c3SPhil Shafer     xrep->xre_num_fields = num_fields;
147242ff34c3SPhil Shafer 
147342ff34c3SPhil Shafer     /* Record the field info in the retain bucket */
147442ff34c3SPhil Shafer     xrep->xre_next = xo_retain.xr_bucket[hash];
147542ff34c3SPhil Shafer     xo_retain.xr_bucket[hash] = xrep;
147642ff34c3SPhil Shafer     xo_retain_count += 1;
147742ff34c3SPhil Shafer }
147842ff34c3SPhil Shafer 
147942ff34c3SPhil Shafer #endif /* !LIBXO_NO_RETAIN */
148042ff34c3SPhil Shafer 
148131337658SMarcel Moolenaar /*
148231337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
148331337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
148431337658SMarcel Moolenaar  * XMLified content on standard output.
148531337658SMarcel Moolenaar  */
148631337658SMarcel Moolenaar static void
xo_warn_hcv(xo_handle_t * xop,int code,int check_warn,const char * fmt,va_list vap)148731337658SMarcel Moolenaar xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
148831337658SMarcel Moolenaar 	     const char *fmt, va_list vap)
148931337658SMarcel Moolenaar {
149031337658SMarcel Moolenaar     xop = xo_default(xop);
1491d1a0d267SMarcel Moolenaar     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
149231337658SMarcel Moolenaar 	return;
149331337658SMarcel Moolenaar 
149431337658SMarcel Moolenaar     if (fmt == NULL)
149531337658SMarcel Moolenaar 	return;
149631337658SMarcel Moolenaar 
14978a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
14988a6eceffSPhil Shafer     ssize_t plen = xo_program ? strlen(xo_program) : 0;
1499545ddfbeSMarcel Moolenaar     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
150031337658SMarcel Moolenaar 
150131337658SMarcel Moolenaar     if (plen) {
150231337658SMarcel Moolenaar 	memcpy(newfmt, xo_program, plen);
150331337658SMarcel Moolenaar 	newfmt[plen++] = ':';
150431337658SMarcel Moolenaar 	newfmt[plen++] = ' ';
150531337658SMarcel Moolenaar     }
15062f784130SPhil Shafer 
150731337658SMarcel Moolenaar     memcpy(newfmt + plen, fmt, len);
150831337658SMarcel Moolenaar     newfmt[len + plen] = '\0';
150931337658SMarcel Moolenaar 
1510d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_WARN_XML)) {
151131337658SMarcel Moolenaar 	static char err_open[] = "<error>";
151231337658SMarcel Moolenaar 	static char err_close[] = "</error>";
151331337658SMarcel Moolenaar 	static char msg_open[] = "<message>";
151431337658SMarcel Moolenaar 	static char msg_close[] = "</message>";
151531337658SMarcel Moolenaar 
151631337658SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_data;
151731337658SMarcel Moolenaar 
151831337658SMarcel Moolenaar 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
151931337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
152031337658SMarcel Moolenaar 
152131337658SMarcel Moolenaar 	va_list va_local;
152231337658SMarcel Moolenaar 	va_copy(va_local, vap);
152331337658SMarcel Moolenaar 
15248a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
15258a6eceffSPhil Shafer 	ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
15262f784130SPhil Shafer 
1527d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1528c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1529c600d307SMarcel Moolenaar 		va_end(va_local);
153031337658SMarcel Moolenaar 		return;
1531c600d307SMarcel Moolenaar 	    }
153231337658SMarcel Moolenaar 
153331337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
153431337658SMarcel Moolenaar 	    va_copy(vap, va_local);
153531337658SMarcel Moolenaar 
153631337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
153731337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
153831337658SMarcel Moolenaar 	}
15392f784130SPhil Shafer 
154031337658SMarcel Moolenaar 	va_end(va_local);
154131337658SMarcel Moolenaar 
154231337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 1);
154331337658SMarcel Moolenaar 	xbp->xb_curp += rc;
154431337658SMarcel Moolenaar 
154531337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
154631337658SMarcel Moolenaar 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
154731337658SMarcel Moolenaar 
1548545ddfbeSMarcel Moolenaar 	if (code >= 0) {
154931337658SMarcel Moolenaar 	    const char *msg = strerror(code);
15502f784130SPhil Shafer 
155131337658SMarcel Moolenaar 	    if (msg) {
155231337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
155331337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
155431337658SMarcel Moolenaar 	    }
155531337658SMarcel Moolenaar 	}
155631337658SMarcel Moolenaar 
1557d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1558545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
155931337658SMarcel Moolenaar 
156031337658SMarcel Moolenaar     } else {
156131337658SMarcel Moolenaar 	vfprintf(stderr, newfmt, vap);
1562545ddfbeSMarcel Moolenaar 	if (code >= 0) {
1563545ddfbeSMarcel Moolenaar 	    const char *msg = strerror(code);
15642f784130SPhil Shafer 
1565545ddfbeSMarcel Moolenaar 	    if (msg)
1566545ddfbeSMarcel Moolenaar 		fprintf(stderr, ": %s", msg);
1567545ddfbeSMarcel Moolenaar 	}
1568545ddfbeSMarcel Moolenaar 	fprintf(stderr, "\n");
156931337658SMarcel Moolenaar     }
157031337658SMarcel Moolenaar }
157131337658SMarcel Moolenaar 
157231337658SMarcel Moolenaar void
xo_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)157331337658SMarcel Moolenaar xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
157431337658SMarcel Moolenaar {
157531337658SMarcel Moolenaar     va_list vap;
157631337658SMarcel Moolenaar 
157731337658SMarcel Moolenaar     va_start(vap, fmt);
157831337658SMarcel Moolenaar     xo_warn_hcv(xop, code, 0, fmt, vap);
157931337658SMarcel Moolenaar     va_end(vap);
158031337658SMarcel Moolenaar }
158131337658SMarcel Moolenaar 
158231337658SMarcel Moolenaar void
xo_warn_c(int code,const char * fmt,...)158331337658SMarcel Moolenaar xo_warn_c (int code, const char *fmt, ...)
158431337658SMarcel Moolenaar {
158531337658SMarcel Moolenaar     va_list vap;
158631337658SMarcel Moolenaar 
158731337658SMarcel Moolenaar     va_start(vap, fmt);
1588545ddfbeSMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
158931337658SMarcel Moolenaar     va_end(vap);
159031337658SMarcel Moolenaar }
159131337658SMarcel Moolenaar 
159231337658SMarcel Moolenaar void
xo_warn(const char * fmt,...)159331337658SMarcel Moolenaar xo_warn (const char *fmt, ...)
159431337658SMarcel Moolenaar {
159531337658SMarcel Moolenaar     int code = errno;
159631337658SMarcel Moolenaar     va_list vap;
159731337658SMarcel Moolenaar 
159831337658SMarcel Moolenaar     va_start(vap, fmt);
159931337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
160031337658SMarcel Moolenaar     va_end(vap);
160131337658SMarcel Moolenaar }
160231337658SMarcel Moolenaar 
160331337658SMarcel Moolenaar void
xo_warnx(const char * fmt,...)160431337658SMarcel Moolenaar xo_warnx (const char *fmt, ...)
160531337658SMarcel Moolenaar {
160631337658SMarcel Moolenaar     va_list vap;
160731337658SMarcel Moolenaar 
160831337658SMarcel Moolenaar     va_start(vap, fmt);
160931337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
161031337658SMarcel Moolenaar     va_end(vap);
161131337658SMarcel Moolenaar }
161231337658SMarcel Moolenaar 
161331337658SMarcel Moolenaar void
xo_err(int eval,const char * fmt,...)161431337658SMarcel Moolenaar xo_err (int eval, const char *fmt, ...)
161531337658SMarcel Moolenaar {
161631337658SMarcel Moolenaar     int code = errno;
161731337658SMarcel Moolenaar     va_list vap;
161831337658SMarcel Moolenaar 
161931337658SMarcel Moolenaar     va_start(vap, fmt);
162031337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
162131337658SMarcel Moolenaar     va_end(vap);
162231337658SMarcel Moolenaar     xo_finish();
162331337658SMarcel Moolenaar     exit(eval);
162431337658SMarcel Moolenaar }
162531337658SMarcel Moolenaar 
162631337658SMarcel Moolenaar void
xo_errx(int eval,const char * fmt,...)162731337658SMarcel Moolenaar xo_errx (int eval, const char *fmt, ...)
162831337658SMarcel Moolenaar {
162931337658SMarcel Moolenaar     va_list vap;
163031337658SMarcel Moolenaar 
163131337658SMarcel Moolenaar     va_start(vap, fmt);
163231337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
163331337658SMarcel Moolenaar     va_end(vap);
163431337658SMarcel Moolenaar     xo_finish();
163531337658SMarcel Moolenaar     exit(eval);
163631337658SMarcel Moolenaar }
163731337658SMarcel Moolenaar 
163831337658SMarcel Moolenaar void
xo_errc(int eval,int code,const char * fmt,...)163931337658SMarcel Moolenaar xo_errc (int eval, int code, const char *fmt, ...)
164031337658SMarcel Moolenaar {
164131337658SMarcel Moolenaar     va_list vap;
164231337658SMarcel Moolenaar 
164331337658SMarcel Moolenaar     va_start(vap, fmt);
164431337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
164531337658SMarcel Moolenaar     va_end(vap);
164631337658SMarcel Moolenaar     xo_finish();
164731337658SMarcel Moolenaar     exit(eval);
164831337658SMarcel Moolenaar }
164931337658SMarcel Moolenaar 
165031337658SMarcel Moolenaar /*
165131337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
165231337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
165331337658SMarcel Moolenaar  * XMLified content on standard output.
165431337658SMarcel Moolenaar  */
165531337658SMarcel Moolenaar void
xo_message_hcv(xo_handle_t * xop,int code,const char * fmt,va_list vap)165631337658SMarcel Moolenaar xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
165731337658SMarcel Moolenaar {
165831337658SMarcel Moolenaar     static char msg_open[] = "<message>";
165931337658SMarcel Moolenaar     static char msg_close[] = "</message>";
166031337658SMarcel Moolenaar     xo_buffer_t *xbp;
16618a6eceffSPhil Shafer     ssize_t rc;
166231337658SMarcel Moolenaar     va_list va_local;
166331337658SMarcel Moolenaar 
166431337658SMarcel Moolenaar     xop = xo_default(xop);
166531337658SMarcel Moolenaar 
166631337658SMarcel Moolenaar     if (fmt == NULL || *fmt == '\0')
166731337658SMarcel Moolenaar 	return;
166831337658SMarcel Moolenaar 
166931337658SMarcel Moolenaar     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
167031337658SMarcel Moolenaar 
1671788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
167231337658SMarcel Moolenaar     case XO_STYLE_XML:
167331337658SMarcel Moolenaar 	xbp = &xop->xo_data;
1674d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
167531337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
167631337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
167731337658SMarcel Moolenaar 
167831337658SMarcel Moolenaar 	va_copy(va_local, vap);
167931337658SMarcel Moolenaar 
16808a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
16812f784130SPhil Shafer 
168231337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1683d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1684c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1685c600d307SMarcel Moolenaar 		va_end(va_local);
168631337658SMarcel Moolenaar 		return;
1687c600d307SMarcel Moolenaar 	    }
168831337658SMarcel Moolenaar 
168931337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
169031337658SMarcel Moolenaar 	    va_copy(vap, va_local);
169131337658SMarcel Moolenaar 
169231337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
169331337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
169431337658SMarcel Moolenaar 	}
16952f784130SPhil Shafer 
169631337658SMarcel Moolenaar 	va_end(va_local);
169731337658SMarcel Moolenaar 
1698d1a0d267SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
169931337658SMarcel Moolenaar 	xbp->xb_curp += rc;
170031337658SMarcel Moolenaar 
170131337658SMarcel Moolenaar 	if (need_nl && code > 0) {
170231337658SMarcel Moolenaar 	    const char *msg = strerror(code);
17032f784130SPhil Shafer 
170431337658SMarcel Moolenaar 	    if (msg) {
170531337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
170631337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
170731337658SMarcel Moolenaar 	    }
170831337658SMarcel Moolenaar 	}
170931337658SMarcel Moolenaar 
171031337658SMarcel Moolenaar 	if (need_nl)
1711d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1712d1a0d267SMarcel Moolenaar 
1713d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1714d1a0d267SMarcel Moolenaar 
1715d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
1716d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1717d1a0d267SMarcel Moolenaar 
1718545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
171931337658SMarcel Moolenaar 	break;
172031337658SMarcel Moolenaar 
172131337658SMarcel Moolenaar     case XO_STYLE_HTML:
172231337658SMarcel Moolenaar 	{
172331337658SMarcel Moolenaar 	    char buf[BUFSIZ], *bp = buf, *cp;
17248a6eceffSPhil Shafer 	    ssize_t bufsiz = sizeof(buf);
17258a6eceffSPhil Shafer 	    ssize_t rc2;
172631337658SMarcel Moolenaar 
172731337658SMarcel Moolenaar 	    va_copy(va_local, vap);
172831337658SMarcel Moolenaar 
1729c600d307SMarcel Moolenaar 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
173031337658SMarcel Moolenaar 	    if (rc > bufsiz) {
173131337658SMarcel Moolenaar 		bufsiz = rc + BUFSIZ;
173231337658SMarcel Moolenaar 		bp = alloca(bufsiz);
173331337658SMarcel Moolenaar 		va_end(va_local);
173431337658SMarcel Moolenaar 		va_copy(va_local, vap);
1735c600d307SMarcel Moolenaar 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
173631337658SMarcel Moolenaar 	    }
17372f784130SPhil Shafer 
1738c600d307SMarcel Moolenaar 	    va_end(va_local);
173931337658SMarcel Moolenaar 	    cp = bp + rc;
174031337658SMarcel Moolenaar 
174131337658SMarcel Moolenaar 	    if (need_nl) {
174231337658SMarcel Moolenaar 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
174331337658SMarcel Moolenaar 			       (code > 0) ? ": " : "",
174431337658SMarcel Moolenaar 			       (code > 0) ? strerror(code) : "");
174531337658SMarcel Moolenaar 		if (rc2 > 0)
174631337658SMarcel Moolenaar 		    rc += rc2;
174731337658SMarcel Moolenaar 	    }
174831337658SMarcel Moolenaar 
1749264104f2SPhil Shafer 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc,
1750264104f2SPhil Shafer 			      NULL, 0, NULL, 0);
175131337658SMarcel Moolenaar 	}
175231337658SMarcel Moolenaar 	break;
175331337658SMarcel Moolenaar 
175431337658SMarcel Moolenaar     case XO_STYLE_JSON:
1755d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
1756d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
1757d1a0d267SMarcel Moolenaar 	/* No means of representing messages */
1758d1a0d267SMarcel Moolenaar 	return;
175931337658SMarcel Moolenaar 
176031337658SMarcel Moolenaar     case XO_STYLE_TEXT:
176131337658SMarcel Moolenaar 	rc = xo_printf_v(xop, fmt, vap);
176231337658SMarcel Moolenaar 	/*
176331337658SMarcel Moolenaar 	 * XXX need to handle UTF-8 widths
176431337658SMarcel Moolenaar 	 */
176531337658SMarcel Moolenaar 	if (rc > 0) {
1766d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
176731337658SMarcel Moolenaar 		xop->xo_columns += rc;
1768d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
176931337658SMarcel Moolenaar 		xop->xo_anchor_columns += rc;
177031337658SMarcel Moolenaar 	}
177131337658SMarcel Moolenaar 
177231337658SMarcel Moolenaar 	if (need_nl && code > 0) {
177331337658SMarcel Moolenaar 	    const char *msg = strerror(code);
17742f784130SPhil Shafer 
177531337658SMarcel Moolenaar 	    if (msg) {
177631337658SMarcel Moolenaar 		xo_printf(xop, ": %s", msg);
177731337658SMarcel Moolenaar 	    }
177831337658SMarcel Moolenaar 	}
177931337658SMarcel Moolenaar 	if (need_nl)
178031337658SMarcel Moolenaar 	    xo_printf(xop, "\n");
178131337658SMarcel Moolenaar 
178231337658SMarcel Moolenaar 	break;
178331337658SMarcel Moolenaar     }
178431337658SMarcel Moolenaar 
178542ff34c3SPhil Shafer     switch (xo_style(xop)) {
178642ff34c3SPhil Shafer     case XO_STYLE_HTML:
178742ff34c3SPhil Shafer 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
178842ff34c3SPhil Shafer 	    static char div_close[] = "</div>";
17892f784130SPhil Shafer 
179042ff34c3SPhil Shafer 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
179142ff34c3SPhil Shafer 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
179242ff34c3SPhil Shafer 
179342ff34c3SPhil Shafer 	    if (XOF_ISSET(xop, XOF_PRETTY))
179442ff34c3SPhil Shafer 		xo_data_append(xop, "\n", 1);
179542ff34c3SPhil Shafer 	}
179642ff34c3SPhil Shafer 	break;
179742ff34c3SPhil Shafer     }
179842ff34c3SPhil Shafer 
1799545ddfbeSMarcel Moolenaar     (void) xo_flush_h(xop);
180031337658SMarcel Moolenaar }
180131337658SMarcel Moolenaar 
180231337658SMarcel Moolenaar void
xo_message_hc(xo_handle_t * xop,int code,const char * fmt,...)180331337658SMarcel Moolenaar xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
180431337658SMarcel Moolenaar {
180531337658SMarcel Moolenaar     va_list vap;
180631337658SMarcel Moolenaar 
180731337658SMarcel Moolenaar     va_start(vap, fmt);
180831337658SMarcel Moolenaar     xo_message_hcv(xop, code, fmt, vap);
180931337658SMarcel Moolenaar     va_end(vap);
181031337658SMarcel Moolenaar }
181131337658SMarcel Moolenaar 
181231337658SMarcel Moolenaar void
xo_message_c(int code,const char * fmt,...)181331337658SMarcel Moolenaar xo_message_c (int code, const char *fmt, ...)
181431337658SMarcel Moolenaar {
181531337658SMarcel Moolenaar     va_list vap;
181631337658SMarcel Moolenaar 
181731337658SMarcel Moolenaar     va_start(vap, fmt);
181831337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
181931337658SMarcel Moolenaar     va_end(vap);
182031337658SMarcel Moolenaar }
182131337658SMarcel Moolenaar 
182231337658SMarcel Moolenaar void
xo_message_e(const char * fmt,...)1823d1a0d267SMarcel Moolenaar xo_message_e (const char *fmt, ...)
182431337658SMarcel Moolenaar {
182531337658SMarcel Moolenaar     int code = errno;
182631337658SMarcel Moolenaar     va_list vap;
182731337658SMarcel Moolenaar 
182831337658SMarcel Moolenaar     va_start(vap, fmt);
182931337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
183031337658SMarcel Moolenaar     va_end(vap);
183131337658SMarcel Moolenaar }
183231337658SMarcel Moolenaar 
1833d1a0d267SMarcel Moolenaar void
xo_message(const char * fmt,...)1834d1a0d267SMarcel Moolenaar xo_message (const char *fmt, ...)
1835d1a0d267SMarcel Moolenaar {
1836d1a0d267SMarcel Moolenaar     va_list vap;
1837d1a0d267SMarcel Moolenaar 
1838d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
1839d1a0d267SMarcel Moolenaar     xo_message_hcv(NULL, 0, fmt, vap);
1840d1a0d267SMarcel Moolenaar     va_end(vap);
1841d1a0d267SMarcel Moolenaar }
1842d1a0d267SMarcel Moolenaar 
184376afb20cSPhil Shafer void
xo_failure(xo_handle_t * xop,const char * fmt,...)184431337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...)
184531337658SMarcel Moolenaar {
1846d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_WARN))
184731337658SMarcel Moolenaar 	return;
184831337658SMarcel Moolenaar 
184931337658SMarcel Moolenaar     va_list vap;
185031337658SMarcel Moolenaar 
185131337658SMarcel Moolenaar     va_start(vap, fmt);
185231337658SMarcel Moolenaar     xo_warn_hcv(xop, -1, 1, fmt, vap);
185331337658SMarcel Moolenaar     va_end(vap);
185431337658SMarcel Moolenaar }
185531337658SMarcel Moolenaar 
185631337658SMarcel Moolenaar /**
185731337658SMarcel Moolenaar  * Create a handle for use by later libxo functions.
185831337658SMarcel Moolenaar  *
185931337658SMarcel Moolenaar  * Note: normal use of libxo does not require a distinct handle, since
186031337658SMarcel Moolenaar  * the default handle (used when NULL is passed) generates text on stdout.
186131337658SMarcel Moolenaar  *
1862f2b7bf8aSPhil Shafer  * @param style Style of output desired (XO_STYLE_* value)
1863f2b7bf8aSPhil Shafer  * @param flags Set of XOF_* flags in use with this handle
1864f2b7bf8aSPhil Shafer  * @return Newly allocated handle
1865f2b7bf8aSPhil Shafer  * @see xo_destroy
186631337658SMarcel Moolenaar  */
186731337658SMarcel Moolenaar xo_handle_t *
xo_create(xo_style_t style,xo_xof_flags_t flags)186831337658SMarcel Moolenaar xo_create (xo_style_t style, xo_xof_flags_t flags)
186931337658SMarcel Moolenaar {
187031337658SMarcel Moolenaar     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
187131337658SMarcel Moolenaar 
187231337658SMarcel Moolenaar     if (xop) {
187331337658SMarcel Moolenaar 	bzero(xop, sizeof(*xop));
187431337658SMarcel Moolenaar 
187531337658SMarcel Moolenaar 	xop->xo_style = style;
1876d1a0d267SMarcel Moolenaar 	XOF_SET(xop, flags);
187731337658SMarcel Moolenaar 	xo_init_handle(xop);
1878d1a0d267SMarcel Moolenaar 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
187931337658SMarcel Moolenaar     }
188031337658SMarcel Moolenaar 
188131337658SMarcel Moolenaar     return xop;
188231337658SMarcel Moolenaar }
188331337658SMarcel Moolenaar 
188431337658SMarcel Moolenaar /**
188531337658SMarcel Moolenaar  * Create a handle that will write to the given file.  Use
188631337658SMarcel Moolenaar  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1887f2b7bf8aSPhil Shafer  *
1888f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1889f2b7bf8aSPhil Shafer  * @param style Style of output desired (XO_STYLE_* value)
1890f2b7bf8aSPhil Shafer  * @param flags Set of XOF_* flags to use with this handle
1891f2b7bf8aSPhil Shafer  * @return Newly allocated handle
1892f2b7bf8aSPhil Shafer  * @see xo_destroy
189331337658SMarcel Moolenaar  */
189431337658SMarcel Moolenaar xo_handle_t *
xo_create_to_file(FILE * fp,xo_style_t style,xo_xof_flags_t flags)189531337658SMarcel Moolenaar xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
189631337658SMarcel Moolenaar {
189731337658SMarcel Moolenaar     xo_handle_t *xop = xo_create(style, flags);
189831337658SMarcel Moolenaar 
189931337658SMarcel Moolenaar     if (xop) {
190031337658SMarcel Moolenaar 	xop->xo_opaque = fp;
190131337658SMarcel Moolenaar 	xop->xo_write = xo_write_to_file;
190231337658SMarcel Moolenaar 	xop->xo_close = xo_close_file;
1903545ddfbeSMarcel Moolenaar 	xop->xo_flush = xo_flush_file;
190431337658SMarcel Moolenaar     }
190531337658SMarcel Moolenaar 
190631337658SMarcel Moolenaar     return xop;
190731337658SMarcel Moolenaar }
190831337658SMarcel Moolenaar 
190931337658SMarcel Moolenaar /**
191042ff34c3SPhil Shafer  * Set the default handler to output to a file.
1911f2b7bf8aSPhil Shafer  *
1912f2b7bf8aSPhil Shafer  * @param xop libxo handle
1913f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1914f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
191542ff34c3SPhil Shafer  */
191642ff34c3SPhil Shafer int
xo_set_file_h(xo_handle_t * xop,FILE * fp)191742ff34c3SPhil Shafer xo_set_file_h (xo_handle_t *xop, FILE *fp)
191842ff34c3SPhil Shafer {
191942ff34c3SPhil Shafer     xop = xo_default(xop);
192042ff34c3SPhil Shafer 
192142ff34c3SPhil Shafer     if (fp == NULL) {
192242ff34c3SPhil Shafer 	xo_failure(xop, "xo_set_file: NULL fp");
192342ff34c3SPhil Shafer 	return -1;
192442ff34c3SPhil Shafer     }
192542ff34c3SPhil Shafer 
192642ff34c3SPhil Shafer     xop->xo_opaque = fp;
192742ff34c3SPhil Shafer     xop->xo_write = xo_write_to_file;
192842ff34c3SPhil Shafer     xop->xo_close = xo_close_file;
192942ff34c3SPhil Shafer     xop->xo_flush = xo_flush_file;
193042ff34c3SPhil Shafer 
193142ff34c3SPhil Shafer     return 0;
193242ff34c3SPhil Shafer }
193342ff34c3SPhil Shafer 
193442ff34c3SPhil Shafer /**
193542ff34c3SPhil Shafer  * Set the default handler to output to a file.
1936f2b7bf8aSPhil Shafer  *
1937f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1938f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
193942ff34c3SPhil Shafer  */
194042ff34c3SPhil Shafer int
xo_set_file(FILE * fp)194142ff34c3SPhil Shafer xo_set_file (FILE *fp)
194242ff34c3SPhil Shafer {
194342ff34c3SPhil Shafer     return xo_set_file_h(NULL, fp);
194442ff34c3SPhil Shafer }
194542ff34c3SPhil Shafer 
194642ff34c3SPhil Shafer /**
194731337658SMarcel Moolenaar  * Release any resources held by the handle.
1948f2b7bf8aSPhil Shafer  *
1949f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
195031337658SMarcel Moolenaar  */
195131337658SMarcel Moolenaar void
xo_destroy(xo_handle_t * xop_arg)1952c600d307SMarcel Moolenaar xo_destroy (xo_handle_t *xop_arg)
195331337658SMarcel Moolenaar {
1954c600d307SMarcel Moolenaar     xo_handle_t *xop = xo_default(xop_arg);
195531337658SMarcel Moolenaar 
1956d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
1957d1a0d267SMarcel Moolenaar 
1958d1a0d267SMarcel Moolenaar     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
195931337658SMarcel Moolenaar 	xop->xo_close(xop->xo_opaque);
196031337658SMarcel Moolenaar 
196131337658SMarcel Moolenaar     xo_free(xop->xo_stack);
196231337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_data);
196331337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_fmt);
196431337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_predicate);
196531337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_attrs);
1966788ca347SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_color_buf);
1967788ca347SMarcel Moolenaar 
1968788ca347SMarcel Moolenaar     if (xop->xo_version)
1969788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
197031337658SMarcel Moolenaar 
1971c600d307SMarcel Moolenaar     if (xop_arg == NULL) {
1972545ddfbeSMarcel Moolenaar 	bzero(&xo_default_handle, sizeof(xo_default_handle));
197331337658SMarcel Moolenaar 	xo_default_inited = 0;
197431337658SMarcel Moolenaar     } else
197531337658SMarcel Moolenaar 	xo_free(xop);
197631337658SMarcel Moolenaar }
197731337658SMarcel Moolenaar 
197831337658SMarcel Moolenaar /**
197931337658SMarcel Moolenaar  * Record a new output style to use for the given handle (or default if
198031337658SMarcel Moolenaar  * handle is NULL).  This output style will be used for any future output.
198131337658SMarcel Moolenaar  *
1982f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
1983f2b7bf8aSPhil Shafer  * @param style new output style (XO_STYLE_*)
198431337658SMarcel Moolenaar  */
198531337658SMarcel Moolenaar void
xo_set_style(xo_handle_t * xop,xo_style_t style)198631337658SMarcel Moolenaar xo_set_style (xo_handle_t *xop, xo_style_t style)
198731337658SMarcel Moolenaar {
198831337658SMarcel Moolenaar     xop = xo_default(xop);
198931337658SMarcel Moolenaar     xop->xo_style = style;
199031337658SMarcel Moolenaar }
199131337658SMarcel Moolenaar 
1992f2b7bf8aSPhil Shafer /**
1993f2b7bf8aSPhil Shafer  * Return the current style of a handle
1994f2b7bf8aSPhil Shafer  *
1995f2b7bf8aSPhil Shafer  * @param xop XO handle to access
1996f2b7bf8aSPhil Shafer  * @return The handle's current style
1997f2b7bf8aSPhil Shafer  */
199831337658SMarcel Moolenaar xo_style_t
xo_get_style(xo_handle_t * xop)199931337658SMarcel Moolenaar xo_get_style (xo_handle_t *xop)
200031337658SMarcel Moolenaar {
200131337658SMarcel Moolenaar     xop = xo_default(xop);
2002788ca347SMarcel Moolenaar     return xo_style(xop);
200331337658SMarcel Moolenaar }
200431337658SMarcel Moolenaar 
2005f2b7bf8aSPhil Shafer /**
2006f2b7bf8aSPhil Shafer  * Return the XO_STYLE_* value matching a given name
2007f2b7bf8aSPhil Shafer  *
2008f2b7bf8aSPhil Shafer  * @param name String name of a style
2009f2b7bf8aSPhil Shafer  * @return XO_STYLE_* value
2010f2b7bf8aSPhil Shafer  */
201131337658SMarcel Moolenaar static int
xo_name_to_style(const char * name)201231337658SMarcel Moolenaar xo_name_to_style (const char *name)
201331337658SMarcel Moolenaar {
201476afb20cSPhil Shafer     if (xo_streq(name, "xml"))
201531337658SMarcel Moolenaar 	return XO_STYLE_XML;
201676afb20cSPhil Shafer     else if (xo_streq(name, "json"))
201731337658SMarcel Moolenaar 	return XO_STYLE_JSON;
201876afb20cSPhil Shafer     else if (xo_streq(name, "encoder"))
2019d1a0d267SMarcel Moolenaar 	return XO_STYLE_ENCODER;
202076afb20cSPhil Shafer     else if (xo_streq(name, "text"))
202131337658SMarcel Moolenaar 	return XO_STYLE_TEXT;
202276afb20cSPhil Shafer     else if (xo_streq(name, "html"))
202331337658SMarcel Moolenaar 	return XO_STYLE_HTML;
202476afb20cSPhil Shafer     else if (xo_streq(name, "sdparams"))
2025d1a0d267SMarcel Moolenaar 	return XO_STYLE_SDPARAMS;
202631337658SMarcel Moolenaar 
202731337658SMarcel Moolenaar     return -1;
202831337658SMarcel Moolenaar }
202931337658SMarcel Moolenaar 
203031337658SMarcel Moolenaar /*
2031d1a0d267SMarcel Moolenaar  * Indicate if the style is an "encoding" one as opposed to a "display" one.
2032d1a0d267SMarcel Moolenaar  */
2033d1a0d267SMarcel Moolenaar static int
xo_style_is_encoding(xo_handle_t * xop)2034d1a0d267SMarcel Moolenaar xo_style_is_encoding (xo_handle_t *xop)
2035d1a0d267SMarcel Moolenaar {
2036d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_JSON
2037d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_XML
2038d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_SDPARAMS
2039d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_ENCODER)
2040d1a0d267SMarcel Moolenaar 	return 1;
2041d1a0d267SMarcel Moolenaar     return 0;
2042d1a0d267SMarcel Moolenaar }
2043d1a0d267SMarcel Moolenaar 
2044d1a0d267SMarcel Moolenaar /* Simple name-value mapping */
2045d1a0d267SMarcel Moolenaar typedef struct xo_mapping_s {
2046f2b7bf8aSPhil Shafer     xo_xff_flags_t xm_value;	/* Flag value */
2047f2b7bf8aSPhil Shafer     const char *xm_name;	/* String name */
2048d1a0d267SMarcel Moolenaar } xo_mapping_t;
2049d1a0d267SMarcel Moolenaar 
2050d1a0d267SMarcel Moolenaar static xo_xff_flags_t
xo_name_lookup(xo_mapping_t * map,const char * value,ssize_t len)20518a6eceffSPhil Shafer xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len)
2052d1a0d267SMarcel Moolenaar {
2053d1a0d267SMarcel Moolenaar     if (len == 0)
2054d1a0d267SMarcel Moolenaar 	return 0;
2055d1a0d267SMarcel Moolenaar 
2056d1a0d267SMarcel Moolenaar     if (len < 0)
2057d1a0d267SMarcel Moolenaar 	len = strlen(value);
2058d1a0d267SMarcel Moolenaar 
2059d1a0d267SMarcel Moolenaar     while (isspace((int) *value)) {
2060d1a0d267SMarcel Moolenaar 	value += 1;
2061d1a0d267SMarcel Moolenaar 	len -= 1;
2062d1a0d267SMarcel Moolenaar     }
2063d1a0d267SMarcel Moolenaar 
2064d1a0d267SMarcel Moolenaar     while (isspace((int) value[len]))
2065d1a0d267SMarcel Moolenaar 	len -= 1;
2066d1a0d267SMarcel Moolenaar 
2067d1a0d267SMarcel Moolenaar     if (*value == '\0')
2068d1a0d267SMarcel Moolenaar 	return 0;
2069d1a0d267SMarcel Moolenaar 
2070d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2071d1a0d267SMarcel Moolenaar 	if (strncmp(map->xm_name, value, len) == 0)
2072d1a0d267SMarcel Moolenaar 	    return map->xm_value;
2073d1a0d267SMarcel Moolenaar 
2074d1a0d267SMarcel Moolenaar     return 0;
2075d1a0d267SMarcel Moolenaar }
2076d1a0d267SMarcel Moolenaar 
2077d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
2078d1a0d267SMarcel Moolenaar static const char *
xo_value_lookup(xo_mapping_t * map,xo_xff_flags_t value)2079d1a0d267SMarcel Moolenaar xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2080d1a0d267SMarcel Moolenaar {
2081d1a0d267SMarcel Moolenaar     if (value == 0)
2082d1a0d267SMarcel Moolenaar 	return NULL;
2083d1a0d267SMarcel Moolenaar 
2084d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2085d1a0d267SMarcel Moolenaar 	if (map->xm_value == value)
2086d1a0d267SMarcel Moolenaar 	    return map->xm_name;
2087d1a0d267SMarcel Moolenaar 
2088d1a0d267SMarcel Moolenaar     return NULL;
2089d1a0d267SMarcel Moolenaar }
2090d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
2091d1a0d267SMarcel Moolenaar 
2092d1a0d267SMarcel Moolenaar static xo_mapping_t xo_xof_names[] = {
2093d1a0d267SMarcel Moolenaar     { XOF_COLOR_ALLOWED, "color" },
2094f2b7bf8aSPhil Shafer     { XOF_COLOR, "color-force" },
2095d1a0d267SMarcel Moolenaar     { XOF_COLUMNS, "columns" },
2096d1a0d267SMarcel Moolenaar     { XOF_DTRT, "dtrt" },
2097d1a0d267SMarcel Moolenaar     { XOF_FLUSH, "flush" },
20988a6eceffSPhil Shafer     { XOF_FLUSH_LINE, "flush-line" },
2099d1a0d267SMarcel Moolenaar     { XOF_IGNORE_CLOSE, "ignore-close" },
2100d1a0d267SMarcel Moolenaar     { XOF_INFO, "info" },
2101d1a0d267SMarcel Moolenaar     { XOF_KEYS, "keys" },
2102d1a0d267SMarcel Moolenaar     { XOF_LOG_GETTEXT, "log-gettext" },
2103d1a0d267SMarcel Moolenaar     { XOF_LOG_SYSLOG, "log-syslog" },
2104d1a0d267SMarcel Moolenaar     { XOF_NO_HUMANIZE, "no-humanize" },
2105d1a0d267SMarcel Moolenaar     { XOF_NO_LOCALE, "no-locale" },
210642ff34c3SPhil Shafer     { XOF_RETAIN_NONE, "no-retain" },
2107d1a0d267SMarcel Moolenaar     { XOF_NO_TOP, "no-top" },
2108d1a0d267SMarcel Moolenaar     { XOF_NOT_FIRST, "not-first" },
2109d1a0d267SMarcel Moolenaar     { XOF_PRETTY, "pretty" },
211042ff34c3SPhil Shafer     { XOF_RETAIN_ALL, "retain" },
2111d1a0d267SMarcel Moolenaar     { XOF_UNDERSCORES, "underscores" },
2112d1a0d267SMarcel Moolenaar     { XOF_UNITS, "units" },
2113d1a0d267SMarcel Moolenaar     { XOF_WARN, "warn" },
2114d1a0d267SMarcel Moolenaar     { XOF_WARN_XML, "warn-xml" },
2115d1a0d267SMarcel Moolenaar     { XOF_XPATH, "xpath" },
2116d1a0d267SMarcel Moolenaar     { 0, NULL }
2117d1a0d267SMarcel Moolenaar };
2118d1a0d267SMarcel Moolenaar 
2119f2b7bf8aSPhil Shafer /* Options available via the environment variable ($LIBXO_OPTIONS) */
2120f2b7bf8aSPhil Shafer static xo_mapping_t xo_xof_simple_names[] = {
2121f2b7bf8aSPhil Shafer     { XOF_COLOR_ALLOWED, "color" },
2122f2b7bf8aSPhil Shafer     { XOF_FLUSH, "flush" },
2123f2b7bf8aSPhil Shafer     { XOF_FLUSH_LINE, "flush-line" },
2124f2b7bf8aSPhil Shafer     { XOF_NO_HUMANIZE, "no-humanize" },
2125f2b7bf8aSPhil Shafer     { XOF_NO_LOCALE, "no-locale" },
2126f2b7bf8aSPhil Shafer     { XOF_RETAIN_NONE, "no-retain" },
2127f2b7bf8aSPhil Shafer     { XOF_PRETTY, "pretty" },
2128f2b7bf8aSPhil Shafer     { XOF_RETAIN_ALL, "retain" },
2129f2b7bf8aSPhil Shafer     { XOF_UNDERSCORES, "underscores" },
2130f2b7bf8aSPhil Shafer     { XOF_WARN, "warn" },
2131f2b7bf8aSPhil Shafer     { 0, NULL }
2132f2b7bf8aSPhil Shafer };
2133f2b7bf8aSPhil Shafer 
2134d1a0d267SMarcel Moolenaar /*
213531337658SMarcel Moolenaar  * Convert string name to XOF_* flag value.
213631337658SMarcel Moolenaar  * Not all are useful.  Or safe.  Or sane.
213731337658SMarcel Moolenaar  */
213831337658SMarcel Moolenaar static unsigned
xo_name_to_flag(const char * name)213931337658SMarcel Moolenaar xo_name_to_flag (const char *name)
214031337658SMarcel Moolenaar {
2141d1a0d267SMarcel Moolenaar     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
214231337658SMarcel Moolenaar }
214331337658SMarcel Moolenaar 
2144f2b7bf8aSPhil Shafer /**
2145f2b7bf8aSPhil Shafer  * Set the style of an libxo handle based on a string name
2146f2b7bf8aSPhil Shafer  *
2147f2b7bf8aSPhil Shafer  * @param xop XO handle
2148f2b7bf8aSPhil Shafer  * @param name String value of name
2149f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
2150f2b7bf8aSPhil Shafer  */
215131337658SMarcel Moolenaar int
xo_set_style_name(xo_handle_t * xop,const char * name)215231337658SMarcel Moolenaar xo_set_style_name (xo_handle_t *xop, const char *name)
215331337658SMarcel Moolenaar {
215431337658SMarcel Moolenaar     if (name == NULL)
215531337658SMarcel Moolenaar 	return -1;
215631337658SMarcel Moolenaar 
215731337658SMarcel Moolenaar     int style = xo_name_to_style(name);
21582f784130SPhil Shafer 
215931337658SMarcel Moolenaar     if (style < 0)
216031337658SMarcel Moolenaar 	return -1;
216131337658SMarcel Moolenaar 
216231337658SMarcel Moolenaar     xo_set_style(xop, style);
216331337658SMarcel Moolenaar     return 0;
216431337658SMarcel Moolenaar }
216531337658SMarcel Moolenaar 
216631337658SMarcel Moolenaar /*
2167f2b7bf8aSPhil Shafer  * Fill in the color map, based on the input string; currently unimplemented
2168f2b7bf8aSPhil Shafer  * Look for something like "colors=red/blue+green/yellow" as fg/bg pairs.
2169f2b7bf8aSPhil Shafer  */
2170f2b7bf8aSPhil Shafer static void
xo_set_color_map(xo_handle_t * xop,char * value)2171f2b7bf8aSPhil Shafer xo_set_color_map (xo_handle_t *xop, char *value)
2172f2b7bf8aSPhil Shafer {
2173406a584dSPhil Shafer     if (xo_text_only())
2174f2b7bf8aSPhil Shafer 	return;
2175f2b7bf8aSPhil Shafer 
2176f2b7bf8aSPhil Shafer     char *cp, *ep, *vp, *np;
2177f2b7bf8aSPhil Shafer     ssize_t len = value ? strlen(value) + 1 : 0;
2178f2b7bf8aSPhil Shafer     int num = 1, fg, bg;
2179f2b7bf8aSPhil Shafer 
2180f2b7bf8aSPhil Shafer     for (cp = value, ep = cp + len - 1; cp && *cp && cp < ep; cp = np) {
2181f2b7bf8aSPhil Shafer 	np = strchr(cp, '+');
2182f2b7bf8aSPhil Shafer 	if (np)
2183f2b7bf8aSPhil Shafer 	    *np++ = '\0';
2184f2b7bf8aSPhil Shafer 
2185f2b7bf8aSPhil Shafer 	vp = strchr(cp, '/');
2186f2b7bf8aSPhil Shafer 	if (vp)
2187f2b7bf8aSPhil Shafer 	    *vp++ = '\0';
2188f2b7bf8aSPhil Shafer 
2189f2b7bf8aSPhil Shafer 	fg = *cp ? xo_color_find(cp) : -1;
2190f2b7bf8aSPhil Shafer 	bg = (vp && *vp) ? xo_color_find(vp) : -1;
2191f2b7bf8aSPhil Shafer 
2192406a584dSPhil Shafer #ifndef LIBXO_TEXT_ONLY
2193f2b7bf8aSPhil Shafer 	xop->xo_color_map_fg[num] = (fg < 0) ? num : fg;
2194f2b7bf8aSPhil Shafer 	xop->xo_color_map_bg[num] = (bg < 0) ? num : bg;
2195406a584dSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
2196406a584dSPhil Shafer 
2197f2b7bf8aSPhil Shafer 	if (++num > XO_NUM_COLORS)
2198f2b7bf8aSPhil Shafer 	    break;
2199f2b7bf8aSPhil Shafer     }
2200f2b7bf8aSPhil Shafer 
2201f2b7bf8aSPhil Shafer     /* If no color initialization happened, then we don't need the map */
220276afb20cSPhil Shafer     if (num > 1)
2203f2b7bf8aSPhil Shafer 	XOF_SET(xop, XOF_COLOR_MAP);
2204f2b7bf8aSPhil Shafer     else
2205f2b7bf8aSPhil Shafer 	XOF_CLEAR(xop, XOF_COLOR_MAP);
2206f2b7bf8aSPhil Shafer 
2207406a584dSPhil Shafer #ifndef LIBXO_TEXT_ONLY
2208f2b7bf8aSPhil Shafer     /* Fill in the rest of the colors with the defaults */
2209f2b7bf8aSPhil Shafer     for ( ; num < XO_NUM_COLORS; num++)
2210f2b7bf8aSPhil Shafer 	xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num;
2211406a584dSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
2212f2b7bf8aSPhil Shafer }
2213f2b7bf8aSPhil Shafer 
2214f2b7bf8aSPhil Shafer static int
xo_set_options_simple(xo_handle_t * xop,const char * input)2215f2b7bf8aSPhil Shafer xo_set_options_simple (xo_handle_t *xop, const char *input)
2216f2b7bf8aSPhil Shafer {
2217f2b7bf8aSPhil Shafer     xo_xof_flags_t new_flag;
2218f2b7bf8aSPhil Shafer     char *cp, *ep, *vp, *np, *bp;
2219f2b7bf8aSPhil Shafer     ssize_t len = strlen(input) + 1;
2220f2b7bf8aSPhil Shafer 
2221f2b7bf8aSPhil Shafer     bp = alloca(len);
2222f2b7bf8aSPhil Shafer     memcpy(bp, input, len);
2223f2b7bf8aSPhil Shafer 
2224f2b7bf8aSPhil Shafer     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2225f2b7bf8aSPhil Shafer 	np = strchr(cp, ',');
2226f2b7bf8aSPhil Shafer 	if (np)
2227f2b7bf8aSPhil Shafer 	    *np++ = '\0';
2228f2b7bf8aSPhil Shafer 
2229f2b7bf8aSPhil Shafer 	vp = strchr(cp, '=');
2230f2b7bf8aSPhil Shafer 	if (vp)
2231f2b7bf8aSPhil Shafer 	    *vp++ = '\0';
2232f2b7bf8aSPhil Shafer 
223376afb20cSPhil Shafer 	if (xo_streq("colors", cp)) {
2234f2b7bf8aSPhil Shafer 	    xo_set_color_map(xop, vp);
2235f2b7bf8aSPhil Shafer 	    continue;
2236f2b7bf8aSPhil Shafer 	}
2237f2b7bf8aSPhil Shafer 
2238f2b7bf8aSPhil Shafer 	new_flag = xo_name_lookup(xo_xof_simple_names, cp, -1);
2239f2b7bf8aSPhil Shafer 	if (new_flag != 0) {
2240f2b7bf8aSPhil Shafer 	    XOF_SET(xop, new_flag);
224176afb20cSPhil Shafer 	} else if (xo_streq(cp, "no-color")) {
2242f2b7bf8aSPhil Shafer 	    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2243f2b7bf8aSPhil Shafer 	} else {
2244f2b7bf8aSPhil Shafer 	    xo_failure(xop, "unknown simple option: %s", cp);
2245f2b7bf8aSPhil Shafer 	    return -1;
2246f2b7bf8aSPhil Shafer 	}
2247f2b7bf8aSPhil Shafer     }
2248f2b7bf8aSPhil Shafer 
2249f2b7bf8aSPhil Shafer     return 0;
2250f2b7bf8aSPhil Shafer }
2251f2b7bf8aSPhil Shafer 
2252f2b7bf8aSPhil Shafer /**
225331337658SMarcel Moolenaar  * Set the options for a handle using a string of options
225431337658SMarcel Moolenaar  * passed in.  The input is a comma-separated set of names
225531337658SMarcel Moolenaar  * and optional values: "xml,pretty,indent=4"
2256f2b7bf8aSPhil Shafer  *
2257f2b7bf8aSPhil Shafer  * @param xop XO handle
2258f2b7bf8aSPhil Shafer  * @param input Comma-separated set of option values
2259f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
226031337658SMarcel Moolenaar  */
226131337658SMarcel Moolenaar int
xo_set_options(xo_handle_t * xop,const char * input)226231337658SMarcel Moolenaar xo_set_options (xo_handle_t *xop, const char *input)
226331337658SMarcel Moolenaar {
226431337658SMarcel Moolenaar     char *cp, *ep, *vp, *np, *bp;
22658a6eceffSPhil Shafer     int style = -1, new_style, rc = 0;
22668a6eceffSPhil Shafer     ssize_t len;
226731337658SMarcel Moolenaar     xo_xof_flags_t new_flag;
226831337658SMarcel Moolenaar 
226931337658SMarcel Moolenaar     if (input == NULL)
227031337658SMarcel Moolenaar 	return 0;
227131337658SMarcel Moolenaar 
227231337658SMarcel Moolenaar     xop = xo_default(xop);
227331337658SMarcel Moolenaar 
2274788ca347SMarcel Moolenaar #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2275788ca347SMarcel Moolenaar     /* If the installer used --enable-color-on-by-default, then we allow it */
2276d1a0d267SMarcel Moolenaar     XOF_SET(xop, XOF_COLOR_ALLOWED);
2277788ca347SMarcel Moolenaar #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2278788ca347SMarcel Moolenaar 
227931337658SMarcel Moolenaar     /*
228031337658SMarcel Moolenaar      * We support a simpler, old-school style of giving option
228131337658SMarcel Moolenaar      * also, using a single character for each option.  It's
228231337658SMarcel Moolenaar      * ideal for lazy people, such as myself.
228331337658SMarcel Moolenaar      */
228431337658SMarcel Moolenaar     if (*input == ':') {
22858a6eceffSPhil Shafer 	ssize_t sz;
228631337658SMarcel Moolenaar 
228731337658SMarcel Moolenaar 	for (input++ ; *input; input++) {
228831337658SMarcel Moolenaar 	    switch (*input) {
2289788ca347SMarcel Moolenaar 	    case 'c':
2290d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_COLOR_ALLOWED);
2291788ca347SMarcel Moolenaar 		break;
2292788ca347SMarcel Moolenaar 
229331337658SMarcel Moolenaar 	    case 'f':
2294d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH);
229531337658SMarcel Moolenaar 		break;
229631337658SMarcel Moolenaar 
2297545ddfbeSMarcel Moolenaar 	    case 'F':
2298d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH_LINE);
2299d1a0d267SMarcel Moolenaar 		break;
2300d1a0d267SMarcel Moolenaar 
2301d1a0d267SMarcel Moolenaar 	    case 'g':
2302d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_LOG_GETTEXT);
2303545ddfbeSMarcel Moolenaar 		break;
2304545ddfbeSMarcel Moolenaar 
230531337658SMarcel Moolenaar 	    case 'H':
230631337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_HTML;
230731337658SMarcel Moolenaar 		break;
230831337658SMarcel Moolenaar 
230931337658SMarcel Moolenaar 	    case 'I':
2310d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_INFO);
231131337658SMarcel Moolenaar 		break;
231231337658SMarcel Moolenaar 
231331337658SMarcel Moolenaar 	    case 'i':
231431337658SMarcel Moolenaar 		sz = strspn(input + 1, "0123456789");
231531337658SMarcel Moolenaar 		if (sz > 0) {
231631337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(input + 1);
231731337658SMarcel Moolenaar 		    input += sz - 1;	/* Skip value */
231831337658SMarcel Moolenaar 		}
231931337658SMarcel Moolenaar 		break;
232031337658SMarcel Moolenaar 
232131337658SMarcel Moolenaar 	    case 'J':
232231337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_JSON;
232331337658SMarcel Moolenaar 		break;
232431337658SMarcel Moolenaar 
2325d1a0d267SMarcel Moolenaar 	    case 'k':
2326d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_KEYS);
2327d1a0d267SMarcel Moolenaar 		break;
2328d1a0d267SMarcel Moolenaar 
2329d1a0d267SMarcel Moolenaar 	    case 'n':
2330d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_NO_HUMANIZE);
2331d1a0d267SMarcel Moolenaar 		break;
2332d1a0d267SMarcel Moolenaar 
233331337658SMarcel Moolenaar 	    case 'P':
2334d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_PRETTY);
233531337658SMarcel Moolenaar 		break;
233631337658SMarcel Moolenaar 
233731337658SMarcel Moolenaar 	    case 'T':
233831337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_TEXT;
233931337658SMarcel Moolenaar 		break;
234031337658SMarcel Moolenaar 
234131337658SMarcel Moolenaar 	    case 'U':
2342d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNITS);
234331337658SMarcel Moolenaar 		break;
234431337658SMarcel Moolenaar 
234531337658SMarcel Moolenaar 	    case 'u':
2346d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNDERSCORES);
234731337658SMarcel Moolenaar 		break;
234831337658SMarcel Moolenaar 
234931337658SMarcel Moolenaar 	    case 'W':
2350d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_WARN);
235131337658SMarcel Moolenaar 		break;
235231337658SMarcel Moolenaar 
235331337658SMarcel Moolenaar 	    case 'X':
235431337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_XML;
235531337658SMarcel Moolenaar 		break;
235631337658SMarcel Moolenaar 
235731337658SMarcel Moolenaar 	    case 'x':
2358d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_XPATH);
235931337658SMarcel Moolenaar 		break;
236031337658SMarcel Moolenaar 	    }
236131337658SMarcel Moolenaar 	}
236231337658SMarcel Moolenaar 	return 0;
236331337658SMarcel Moolenaar     }
236431337658SMarcel Moolenaar 
236531337658SMarcel Moolenaar     len = strlen(input) + 1;
236631337658SMarcel Moolenaar     bp = alloca(len);
236731337658SMarcel Moolenaar     memcpy(bp, input, len);
236831337658SMarcel Moolenaar 
236931337658SMarcel Moolenaar     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
237031337658SMarcel Moolenaar 	np = strchr(cp, ',');
237131337658SMarcel Moolenaar 	if (np)
237231337658SMarcel Moolenaar 	    *np++ = '\0';
237331337658SMarcel Moolenaar 
23745c5819b2SPhil Shafer 	/*
23755c5819b2SPhil Shafer 	 * "@foo" is a shorthand for "encoder=foo".  This is driven
23765c5819b2SPhil Shafer 	 * chiefly by a desire to make pluggable encoders not appear
23775c5819b2SPhil Shafer 	 * so distinct from built-in encoders.
23785c5819b2SPhil Shafer 	 */
23795c5819b2SPhil Shafer 	if (*cp == '@') {
23805c5819b2SPhil Shafer 	    vp = cp + 1;
23815c5819b2SPhil Shafer 
23825c5819b2SPhil Shafer 	    if (*vp == '\0')
23835c5819b2SPhil Shafer 		xo_failure(xop, "missing value for encoder option");
23845c5819b2SPhil Shafer 	    else {
23855c5819b2SPhil Shafer 		rc = xo_encoder_init(xop, vp);
23865c5819b2SPhil Shafer 		if (rc)
23875c5819b2SPhil Shafer 		    xo_warnx("error initializing encoder: %s", vp);
23885c5819b2SPhil Shafer 	    }
23895c5819b2SPhil Shafer 
23905c5819b2SPhil Shafer 	    continue;
23915c5819b2SPhil Shafer 	}
23925c5819b2SPhil Shafer 
239331337658SMarcel Moolenaar 	vp = strchr(cp, '=');
239431337658SMarcel Moolenaar 	if (vp)
239531337658SMarcel Moolenaar 	    *vp++ = '\0';
239631337658SMarcel Moolenaar 
239776afb20cSPhil Shafer 	if (xo_streq("colors", cp)) {
2398f2b7bf8aSPhil Shafer 	    xo_set_color_map(xop, vp);
2399788ca347SMarcel Moolenaar 	    continue;
2400788ca347SMarcel Moolenaar 	}
2401788ca347SMarcel Moolenaar 
2402d1a0d267SMarcel Moolenaar 	/*
2403d1a0d267SMarcel Moolenaar 	 * For options, we don't allow "encoder" since we want to
2404d1a0d267SMarcel Moolenaar 	 * handle it explicitly below as "encoder=xxx".
2405d1a0d267SMarcel Moolenaar 	 */
240631337658SMarcel Moolenaar 	new_style = xo_name_to_style(cp);
2407d1a0d267SMarcel Moolenaar 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
240831337658SMarcel Moolenaar 	    if (style >= 0)
240931337658SMarcel Moolenaar 		xo_warnx("ignoring multiple styles: '%s'", cp);
241031337658SMarcel Moolenaar 	    else
241131337658SMarcel Moolenaar 		style = new_style;
241231337658SMarcel Moolenaar 	} else {
241331337658SMarcel Moolenaar 	    new_flag = xo_name_to_flag(cp);
241431337658SMarcel Moolenaar 	    if (new_flag != 0)
2415d1a0d267SMarcel Moolenaar 		XOF_SET(xop, new_flag);
241676afb20cSPhil Shafer 	    else if (xo_streq(cp, "no-color"))
2417d1a0d267SMarcel Moolenaar 		XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
241876afb20cSPhil Shafer 	    else if (xo_streq(cp, "indent")) {
2419d1a0d267SMarcel Moolenaar 		if (vp)
242031337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(vp);
2421d1a0d267SMarcel Moolenaar 		else
2422d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "missing value for indent option");
242376afb20cSPhil Shafer 	    } else if (xo_streq(cp, "encoder")) {
2424d1a0d267SMarcel Moolenaar 		if (vp == NULL)
2425d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "missing value for encoder option");
2426d1a0d267SMarcel Moolenaar 		else {
242776afb20cSPhil Shafer 		    rc = xo_encoder_init(xop, vp);
242876afb20cSPhil Shafer 		    if (rc)
242976afb20cSPhil Shafer 			xo_warnx("error initializing encoder: %s", vp);
2430d1a0d267SMarcel Moolenaar 		}
2431d1a0d267SMarcel Moolenaar 
243231337658SMarcel Moolenaar 	    } else {
2433d1a0d267SMarcel Moolenaar 		xo_warnx("unknown libxo option value: '%s'", cp);
243431337658SMarcel Moolenaar 		rc = -1;
243531337658SMarcel Moolenaar 	    }
243631337658SMarcel Moolenaar 	}
243731337658SMarcel Moolenaar     }
243831337658SMarcel Moolenaar 
243931337658SMarcel Moolenaar     if (style > 0)
244031337658SMarcel Moolenaar 	xop->xo_style= style;
244131337658SMarcel Moolenaar 
244231337658SMarcel Moolenaar     return rc;
244331337658SMarcel Moolenaar }
244431337658SMarcel Moolenaar 
244531337658SMarcel Moolenaar /**
244631337658SMarcel Moolenaar  * Set one or more flags for a given handle (or default if handle is NULL).
244731337658SMarcel Moolenaar  * These flags will affect future output.
244831337658SMarcel Moolenaar  *
2449f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2450f2b7bf8aSPhil Shafer  * @param flags Flags to be set (XOF_*)
245131337658SMarcel Moolenaar  */
245231337658SMarcel Moolenaar void
xo_set_flags(xo_handle_t * xop,xo_xof_flags_t flags)245331337658SMarcel Moolenaar xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
245431337658SMarcel Moolenaar {
245531337658SMarcel Moolenaar     xop = xo_default(xop);
245631337658SMarcel Moolenaar 
2457d1a0d267SMarcel Moolenaar     XOF_SET(xop, flags);
245831337658SMarcel Moolenaar }
245931337658SMarcel Moolenaar 
2460f2b7bf8aSPhil Shafer /**
2461f2b7bf8aSPhil Shafer  * Accessor to return the current set of flags for a handle
2462f2b7bf8aSPhil Shafer  * @param xop XO handle
2463f2b7bf8aSPhil Shafer  * @return Current set of flags
2464f2b7bf8aSPhil Shafer  */
246531337658SMarcel Moolenaar xo_xof_flags_t
xo_get_flags(xo_handle_t * xop)246631337658SMarcel Moolenaar xo_get_flags (xo_handle_t *xop)
246731337658SMarcel Moolenaar {
246831337658SMarcel Moolenaar     xop = xo_default(xop);
246931337658SMarcel Moolenaar 
247031337658SMarcel Moolenaar     return xop->xo_flags;
247131337658SMarcel Moolenaar }
247231337658SMarcel Moolenaar 
2473f2b7bf8aSPhil Shafer /**
2474f2b7bf8aSPhil Shafer  * strndup with a twist: len < 0 means len = strlen(str)
2475d1a0d267SMarcel Moolenaar  */
2476d1a0d267SMarcel Moolenaar static char *
xo_strndup(const char * str,ssize_t len)24778a6eceffSPhil Shafer xo_strndup (const char *str, ssize_t len)
2478d1a0d267SMarcel Moolenaar {
2479d1a0d267SMarcel Moolenaar     if (len < 0)
2480d1a0d267SMarcel Moolenaar 	len = strlen(str);
2481d1a0d267SMarcel Moolenaar 
2482d1a0d267SMarcel Moolenaar     char *cp = xo_realloc(NULL, len + 1);
2483d1a0d267SMarcel Moolenaar     if (cp) {
2484d1a0d267SMarcel Moolenaar 	memcpy(cp, str, len);
2485d1a0d267SMarcel Moolenaar 	cp[len] = '\0';
2486d1a0d267SMarcel Moolenaar     }
2487d1a0d267SMarcel Moolenaar 
2488d1a0d267SMarcel Moolenaar     return cp;
2489d1a0d267SMarcel Moolenaar }
2490d1a0d267SMarcel Moolenaar 
249131337658SMarcel Moolenaar /**
249231337658SMarcel Moolenaar  * Record a leading prefix for the XPath we generate.  This allows the
249331337658SMarcel Moolenaar  * generated data to be placed within an XML hierarchy but still have
249431337658SMarcel Moolenaar  * accurate XPath expressions.
249531337658SMarcel Moolenaar  *
2496f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2497f2b7bf8aSPhil Shafer  * @param path The XPath expression
249831337658SMarcel Moolenaar  */
249931337658SMarcel Moolenaar void
xo_set_leading_xpath(xo_handle_t * xop,const char * path)250031337658SMarcel Moolenaar xo_set_leading_xpath (xo_handle_t *xop, const char *path)
250131337658SMarcel Moolenaar {
250231337658SMarcel Moolenaar     xop = xo_default(xop);
250331337658SMarcel Moolenaar 
250431337658SMarcel Moolenaar     if (xop->xo_leading_xpath) {
250531337658SMarcel Moolenaar 	xo_free(xop->xo_leading_xpath);
250631337658SMarcel Moolenaar 	xop->xo_leading_xpath = NULL;
250731337658SMarcel Moolenaar     }
250831337658SMarcel Moolenaar 
250931337658SMarcel Moolenaar     if (path == NULL)
251031337658SMarcel Moolenaar 	return;
251131337658SMarcel Moolenaar 
2512d1a0d267SMarcel Moolenaar     xop->xo_leading_xpath = xo_strndup(path, -1);
251331337658SMarcel Moolenaar }
251431337658SMarcel Moolenaar 
251531337658SMarcel Moolenaar /**
251631337658SMarcel Moolenaar  * Record the info data for a set of tags
251731337658SMarcel Moolenaar  *
2518f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2519f2b7bf8aSPhil Shafer  * @param info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
2520f2b7bf8aSPhil Shafer  * @pararm count Number of entries in info (or -1 to count them ourselves)
252131337658SMarcel Moolenaar  */
252231337658SMarcel Moolenaar void
xo_set_info(xo_handle_t * xop,xo_info_t * infop,int count)252331337658SMarcel Moolenaar xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
252431337658SMarcel Moolenaar {
252531337658SMarcel Moolenaar     xop = xo_default(xop);
252631337658SMarcel Moolenaar 
252731337658SMarcel Moolenaar     if (count < 0 && infop) {
252831337658SMarcel Moolenaar 	xo_info_t *xip;
252931337658SMarcel Moolenaar 
253031337658SMarcel Moolenaar 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
253131337658SMarcel Moolenaar 	    continue;
253231337658SMarcel Moolenaar     }
253331337658SMarcel Moolenaar 
253431337658SMarcel Moolenaar     xop->xo_info = infop;
253531337658SMarcel Moolenaar     xop->xo_info_count = count;
253631337658SMarcel Moolenaar }
253731337658SMarcel Moolenaar 
253831337658SMarcel Moolenaar /**
253931337658SMarcel Moolenaar  * Set the formatter callback for a handle.  The callback should
254031337658SMarcel Moolenaar  * return a newly formatting contents of a formatting instruction,
254131337658SMarcel Moolenaar  * meaning the bits inside the braces.
254231337658SMarcel Moolenaar  */
254331337658SMarcel Moolenaar void
xo_set_formatter(xo_handle_t * xop,xo_formatter_t func,xo_checkpointer_t cfunc)254431337658SMarcel Moolenaar xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
254531337658SMarcel Moolenaar 		  xo_checkpointer_t cfunc)
254631337658SMarcel Moolenaar {
254731337658SMarcel Moolenaar     xop = xo_default(xop);
254831337658SMarcel Moolenaar 
254931337658SMarcel Moolenaar     xop->xo_formatter = func;
255031337658SMarcel Moolenaar     xop->xo_checkpointer = cfunc;
255131337658SMarcel Moolenaar }
255231337658SMarcel Moolenaar 
255331337658SMarcel Moolenaar /**
255431337658SMarcel Moolenaar  * Clear one or more flags for a given handle (or default if handle is NULL).
255531337658SMarcel Moolenaar  * These flags will affect future output.
255631337658SMarcel Moolenaar  *
2557f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2558f2b7bf8aSPhil Shafer  * @param flags Flags to be cleared (XOF_*)
255931337658SMarcel Moolenaar  */
256031337658SMarcel Moolenaar void
xo_clear_flags(xo_handle_t * xop,xo_xof_flags_t flags)256131337658SMarcel Moolenaar xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
256231337658SMarcel Moolenaar {
256331337658SMarcel Moolenaar     xop = xo_default(xop);
256431337658SMarcel Moolenaar 
2565d1a0d267SMarcel Moolenaar     XOF_CLEAR(xop, flags);
256631337658SMarcel Moolenaar }
256731337658SMarcel Moolenaar 
2568545ddfbeSMarcel Moolenaar static const char *
xo_state_name(xo_state_t state)2569545ddfbeSMarcel Moolenaar xo_state_name (xo_state_t state)
2570545ddfbeSMarcel Moolenaar {
2571545ddfbeSMarcel Moolenaar     static const char *names[] = {
2572545ddfbeSMarcel Moolenaar 	"init",
2573545ddfbeSMarcel Moolenaar 	"open_container",
2574545ddfbeSMarcel Moolenaar 	"close_container",
2575545ddfbeSMarcel Moolenaar 	"open_list",
2576545ddfbeSMarcel Moolenaar 	"close_list",
2577545ddfbeSMarcel Moolenaar 	"open_instance",
2578545ddfbeSMarcel Moolenaar 	"close_instance",
2579545ddfbeSMarcel Moolenaar 	"open_leaf_list",
2580545ddfbeSMarcel Moolenaar 	"close_leaf_list",
2581545ddfbeSMarcel Moolenaar 	"discarding",
2582545ddfbeSMarcel Moolenaar 	"marker",
2583545ddfbeSMarcel Moolenaar 	"emit",
2584545ddfbeSMarcel Moolenaar 	"emit_leaf_list",
2585545ddfbeSMarcel Moolenaar 	"finish",
2586545ddfbeSMarcel Moolenaar 	NULL
2587545ddfbeSMarcel Moolenaar     };
2588545ddfbeSMarcel Moolenaar 
2589545ddfbeSMarcel Moolenaar     if (state < (sizeof(names) / sizeof(names[0])))
2590545ddfbeSMarcel Moolenaar 	return names[state];
2591545ddfbeSMarcel Moolenaar 
2592545ddfbeSMarcel Moolenaar     return "unknown";
2593545ddfbeSMarcel Moolenaar }
2594545ddfbeSMarcel Moolenaar 
259531337658SMarcel Moolenaar static void
xo_line_ensure_open(xo_handle_t * xop,xo_xff_flags_t flags UNUSED)259631337658SMarcel Moolenaar xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
259731337658SMarcel Moolenaar {
259831337658SMarcel Moolenaar     static char div_open[] = "<div class=\"line\">";
259931337658SMarcel Moolenaar     static char div_open_blank[] = "<div class=\"blank-line\">";
260031337658SMarcel Moolenaar 
2601406a584dSPhil Shafer     if (XOF_ISSET(xop, XOF_CONTINUATION)) {
2602406a584dSPhil Shafer 	XOF_CLEAR(xop, XOF_CONTINUATION);
2603406a584dSPhil Shafer 	XOIF_SET(xop, XOIF_DIV_OPEN);
2604406a584dSPhil Shafer 	return;
2605406a584dSPhil Shafer     }
2606406a584dSPhil Shafer 
2607d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
260831337658SMarcel Moolenaar 	return;
260931337658SMarcel Moolenaar 
2610788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_HTML)
261131337658SMarcel Moolenaar 	return;
261231337658SMarcel Moolenaar 
2613d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_DIV_OPEN);
261431337658SMarcel Moolenaar     if (flags & XFF_BLANK_LINE)
261531337658SMarcel Moolenaar 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
261631337658SMarcel Moolenaar     else
261731337658SMarcel Moolenaar 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
261831337658SMarcel Moolenaar 
2619d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
262031337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
262131337658SMarcel Moolenaar }
262231337658SMarcel Moolenaar 
262331337658SMarcel Moolenaar static void
xo_line_close(xo_handle_t * xop)262431337658SMarcel Moolenaar xo_line_close (xo_handle_t *xop)
262531337658SMarcel Moolenaar {
262631337658SMarcel Moolenaar     static char div_close[] = "</div>";
262731337658SMarcel Moolenaar 
2628788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
262931337658SMarcel Moolenaar     case XO_STYLE_HTML:
2630d1a0d267SMarcel Moolenaar 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
263131337658SMarcel Moolenaar 	    xo_line_ensure_open(xop, 0);
263231337658SMarcel Moolenaar 
2633d1a0d267SMarcel Moolenaar 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
263431337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
263531337658SMarcel Moolenaar 
2636d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
263731337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
263831337658SMarcel Moolenaar 	break;
263931337658SMarcel Moolenaar 
264031337658SMarcel Moolenaar     case XO_STYLE_TEXT:
264131337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
264231337658SMarcel Moolenaar 	break;
264331337658SMarcel Moolenaar     }
264431337658SMarcel Moolenaar }
264531337658SMarcel Moolenaar 
264631337658SMarcel Moolenaar static int
xo_info_compare(const void * key,const void * data)264731337658SMarcel Moolenaar xo_info_compare (const void *key, const void *data)
264831337658SMarcel Moolenaar {
264931337658SMarcel Moolenaar     const char *name = key;
265031337658SMarcel Moolenaar     const xo_info_t *xip = data;
265131337658SMarcel Moolenaar 
265231337658SMarcel Moolenaar     return strcmp(name, xip->xi_name);
265331337658SMarcel Moolenaar }
265431337658SMarcel Moolenaar 
265531337658SMarcel Moolenaar 
265631337658SMarcel Moolenaar static xo_info_t *
xo_info_find(xo_handle_t * xop,const char * name,ssize_t nlen)26578a6eceffSPhil Shafer xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen)
265831337658SMarcel Moolenaar {
265931337658SMarcel Moolenaar     xo_info_t *xip;
266031337658SMarcel Moolenaar     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
266131337658SMarcel Moolenaar 
266231337658SMarcel Moolenaar     memcpy(cp, name, nlen);
266331337658SMarcel Moolenaar     cp[nlen] = '\0';
266431337658SMarcel Moolenaar 
266531337658SMarcel Moolenaar     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
266631337658SMarcel Moolenaar 		  sizeof(xop->xo_info[0]), xo_info_compare);
266731337658SMarcel Moolenaar     return xip;
266831337658SMarcel Moolenaar }
266931337658SMarcel Moolenaar 
267031337658SMarcel Moolenaar #define CONVERT(_have, _need) (((_have) << 8) | (_need))
267131337658SMarcel Moolenaar 
267231337658SMarcel Moolenaar /*
267331337658SMarcel Moolenaar  * Check to see that the conversion is safe and sane.
267431337658SMarcel Moolenaar  */
267531337658SMarcel Moolenaar static int
xo_check_conversion(xo_handle_t * xop,int have_enc,int need_enc)267631337658SMarcel Moolenaar xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
267731337658SMarcel Moolenaar {
267831337658SMarcel Moolenaar     switch (CONVERT(have_enc, need_enc)) {
267931337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
268031337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
268131337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
268231337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
268331337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
268431337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
268531337658SMarcel Moolenaar 	return 0;
268631337658SMarcel Moolenaar 
268731337658SMarcel Moolenaar     default:
268831337658SMarcel Moolenaar 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
268931337658SMarcel Moolenaar 	return 1;
269031337658SMarcel Moolenaar     }
269131337658SMarcel Moolenaar }
269231337658SMarcel Moolenaar 
269331337658SMarcel Moolenaar static int
xo_format_string_direct(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,const wchar_t * wcp,const char * cp,ssize_t len,int max,int need_enc,int have_enc)269431337658SMarcel Moolenaar xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
269531337658SMarcel Moolenaar 			 xo_xff_flags_t flags,
26968a6eceffSPhil Shafer 			 const wchar_t *wcp, const char *cp,
26978a6eceffSPhil Shafer 			 ssize_t len, int max,
269831337658SMarcel Moolenaar 			 int need_enc, int have_enc)
269931337658SMarcel Moolenaar {
270031337658SMarcel Moolenaar     int cols = 0;
2701c600d307SMarcel Moolenaar     wchar_t wc = 0;
27028a6eceffSPhil Shafer     ssize_t ilen, olen;
27038a6eceffSPhil Shafer     ssize_t width;
27048a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
270531337658SMarcel Moolenaar     const char *sp;
270631337658SMarcel Moolenaar 
270731337658SMarcel Moolenaar     if (len > 0 && !xo_buf_has_room(xbp, len))
270831337658SMarcel Moolenaar 	return 0;
270931337658SMarcel Moolenaar 
271031337658SMarcel Moolenaar     for (;;) {
271131337658SMarcel Moolenaar 	if (len == 0)
271231337658SMarcel Moolenaar 	    break;
271331337658SMarcel Moolenaar 
271431337658SMarcel Moolenaar 	if (cp) {
271531337658SMarcel Moolenaar 	    if (*cp == '\0')
271631337658SMarcel Moolenaar 		break;
271731337658SMarcel Moolenaar 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
271831337658SMarcel Moolenaar 		cp += 1;
271931337658SMarcel Moolenaar 		len -= 1;
2720264104f2SPhil Shafer 		if (len == 0 || *cp == '\0')
2721264104f2SPhil Shafer 		    break;
272231337658SMarcel Moolenaar 	    }
272331337658SMarcel Moolenaar 	}
272431337658SMarcel Moolenaar 
272531337658SMarcel Moolenaar 	if (wcp && *wcp == L'\0')
272631337658SMarcel Moolenaar 	    break;
272731337658SMarcel Moolenaar 
272831337658SMarcel Moolenaar 	ilen = 0;
272931337658SMarcel Moolenaar 
273031337658SMarcel Moolenaar 	switch (have_enc) {
273131337658SMarcel Moolenaar 	case XF_ENC_WIDE:		/* Wide character */
273231337658SMarcel Moolenaar 	    wc = *wcp++;
273331337658SMarcel Moolenaar 	    ilen = 1;
273431337658SMarcel Moolenaar 	    break;
273531337658SMarcel Moolenaar 
273631337658SMarcel Moolenaar 	case XF_ENC_UTF8:		/* UTF-8 */
273731337658SMarcel Moolenaar 	    ilen = xo_utf8_to_wc_len(cp);
273831337658SMarcel Moolenaar 	    if (ilen < 0) {
273931337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2740d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
274131337658SMarcel Moolenaar 	    }
274231337658SMarcel Moolenaar 
274331337658SMarcel Moolenaar 	    if (len > 0 && len < ilen) {
274431337658SMarcel Moolenaar 		len = 0;	/* Break out of the loop */
274531337658SMarcel Moolenaar 		continue;
274631337658SMarcel Moolenaar 	    }
274731337658SMarcel Moolenaar 
274831337658SMarcel Moolenaar 	    wc = xo_utf8_char(cp, ilen);
274931337658SMarcel Moolenaar 	    if (wc == (wchar_t) -1) {
275031337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
275131337658SMarcel Moolenaar 			   *cp, ilen);
2752d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
275331337658SMarcel Moolenaar 	    }
275431337658SMarcel Moolenaar 	    cp += ilen;
275531337658SMarcel Moolenaar 	    break;
275631337658SMarcel Moolenaar 
275731337658SMarcel Moolenaar 	case XF_ENC_LOCALE:		/* Native locale */
275831337658SMarcel Moolenaar 	    ilen = (len > 0) ? len : MB_LEN_MAX;
275931337658SMarcel Moolenaar 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
276031337658SMarcel Moolenaar 	    if (ilen < 0) {		/* Invalid data; skip */
276131337658SMarcel Moolenaar 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2762dbf26257SAlexander Kabaev 		wc = L'?';
2763dbf26257SAlexander Kabaev 		ilen = 1;
276431337658SMarcel Moolenaar 	    }
2765d1a0d267SMarcel Moolenaar 
276631337658SMarcel Moolenaar 	    if (ilen == 0) {		/* Hit a wide NUL character */
276731337658SMarcel Moolenaar 		len = 0;
276831337658SMarcel Moolenaar 		continue;
276931337658SMarcel Moolenaar 	    }
277031337658SMarcel Moolenaar 
277131337658SMarcel Moolenaar 	    cp += ilen;
277231337658SMarcel Moolenaar 	    break;
277331337658SMarcel Moolenaar 	}
277431337658SMarcel Moolenaar 
277531337658SMarcel Moolenaar 	/* Reduce len, but not below zero */
277631337658SMarcel Moolenaar 	if (len > 0) {
277731337658SMarcel Moolenaar 	    len -= ilen;
277831337658SMarcel Moolenaar 	    if (len < 0)
277931337658SMarcel Moolenaar 		len = 0;
278031337658SMarcel Moolenaar 	}
278131337658SMarcel Moolenaar 
278231337658SMarcel Moolenaar 	/*
278331337658SMarcel Moolenaar 	 * Find the width-in-columns of this character, which must be done
278431337658SMarcel Moolenaar 	 * in wide characters, since we lack a mbswidth() function.  If
278531337658SMarcel Moolenaar 	 * it doesn't fit
278631337658SMarcel Moolenaar 	 */
2787d1a0d267SMarcel Moolenaar 	width = xo_wcwidth(wc);
278831337658SMarcel Moolenaar 	if (width < 0)
278931337658SMarcel Moolenaar 	    width = iswcntrl(wc) ? 0 : 1;
279031337658SMarcel Moolenaar 
2791788ca347SMarcel Moolenaar 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
279231337658SMarcel Moolenaar 	    if (max > 0 && cols + width > max)
279331337658SMarcel Moolenaar 		break;
279431337658SMarcel Moolenaar 	}
279531337658SMarcel Moolenaar 
279631337658SMarcel Moolenaar 	switch (need_enc) {
279731337658SMarcel Moolenaar 	case XF_ENC_UTF8:
279831337658SMarcel Moolenaar 
279931337658SMarcel Moolenaar 	    /* Output in UTF-8 needs to be escaped, based on the style */
2800788ca347SMarcel Moolenaar 	    switch (xo_style(xop)) {
280131337658SMarcel Moolenaar 	    case XO_STYLE_XML:
280231337658SMarcel Moolenaar 	    case XO_STYLE_HTML:
280331337658SMarcel Moolenaar 		if (wc == '<')
280431337658SMarcel Moolenaar 		    sp = xo_xml_lt;
280531337658SMarcel Moolenaar 		else if (wc == '>')
280631337658SMarcel Moolenaar 		    sp = xo_xml_gt;
280731337658SMarcel Moolenaar 		else if (wc == '&')
280831337658SMarcel Moolenaar 		    sp = xo_xml_amp;
280931337658SMarcel Moolenaar 		else if (attr && wc == '"')
281031337658SMarcel Moolenaar 		    sp = xo_xml_quot;
281131337658SMarcel Moolenaar 		else
281231337658SMarcel Moolenaar 		    break;
281331337658SMarcel Moolenaar 
28148a6eceffSPhil Shafer 		ssize_t slen = strlen(sp);
281531337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, slen - 1))
281631337658SMarcel Moolenaar 		    return -1;
281731337658SMarcel Moolenaar 
281831337658SMarcel Moolenaar 		memcpy(xbp->xb_curp, sp, slen);
281931337658SMarcel Moolenaar 		xbp->xb_curp += slen;
282031337658SMarcel Moolenaar 		goto done_with_encoding; /* Need multi-level 'break' */
282131337658SMarcel Moolenaar 
282231337658SMarcel Moolenaar 	    case XO_STYLE_JSON:
2823545ddfbeSMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
282431337658SMarcel Moolenaar 		    break;
282531337658SMarcel Moolenaar 
282631337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
282731337658SMarcel Moolenaar 		    return -1;
282831337658SMarcel Moolenaar 
282931337658SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2830545ddfbeSMarcel Moolenaar 		if (wc == '\n')
2831545ddfbeSMarcel Moolenaar 		    wc = 'n';
2832545ddfbeSMarcel Moolenaar 		else if (wc == '\r')
2833545ddfbeSMarcel Moolenaar 		    wc = 'r';
2834545ddfbeSMarcel Moolenaar 		else wc = wc & 0x7f;
2835545ddfbeSMarcel Moolenaar 
2836545ddfbeSMarcel Moolenaar 		*xbp->xb_curp++ = wc;
283731337658SMarcel Moolenaar 		goto done_with_encoding;
2838d1a0d267SMarcel Moolenaar 
2839d1a0d267SMarcel Moolenaar 	    case XO_STYLE_SDPARAMS:
2840d1a0d267SMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != ']')
2841d1a0d267SMarcel Moolenaar 		    break;
2842d1a0d267SMarcel Moolenaar 
2843d1a0d267SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
2844d1a0d267SMarcel Moolenaar 		    return -1;
2845d1a0d267SMarcel Moolenaar 
2846d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2847d1a0d267SMarcel Moolenaar 		wc = wc & 0x7f;
2848d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = wc;
2849d1a0d267SMarcel Moolenaar 		goto done_with_encoding;
285031337658SMarcel Moolenaar 	    }
285131337658SMarcel Moolenaar 
285231337658SMarcel Moolenaar 	    olen = xo_utf8_emit_len(wc);
285331337658SMarcel Moolenaar 	    if (olen < 0) {
285431337658SMarcel Moolenaar 		xo_failure(xop, "ignoring bad length");
285531337658SMarcel Moolenaar 		continue;
285631337658SMarcel Moolenaar 	    }
285731337658SMarcel Moolenaar 
285831337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, olen))
285931337658SMarcel Moolenaar 		return -1;
286031337658SMarcel Moolenaar 
286131337658SMarcel Moolenaar 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
286231337658SMarcel Moolenaar 	    xbp->xb_curp += olen;
286331337658SMarcel Moolenaar 	    break;
286431337658SMarcel Moolenaar 
286531337658SMarcel Moolenaar 	case XF_ENC_LOCALE:
286631337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
286731337658SMarcel Moolenaar 		return -1;
286831337658SMarcel Moolenaar 
286931337658SMarcel Moolenaar 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
287031337658SMarcel Moolenaar 	    if (olen <= 0) {
287131337658SMarcel Moolenaar 		xo_failure(xop, "could not convert wide char: %lx",
287231337658SMarcel Moolenaar 			   (unsigned long) wc);
287331337658SMarcel Moolenaar 		width = 1;
287431337658SMarcel Moolenaar 		*xbp->xb_curp++ = '?';
287531337658SMarcel Moolenaar 	    } else
287631337658SMarcel Moolenaar 		xbp->xb_curp += olen;
287731337658SMarcel Moolenaar 	    break;
287831337658SMarcel Moolenaar 	}
287931337658SMarcel Moolenaar 
288031337658SMarcel Moolenaar     done_with_encoding:
288131337658SMarcel Moolenaar 	cols += width;
288231337658SMarcel Moolenaar     }
288331337658SMarcel Moolenaar 
288431337658SMarcel Moolenaar     return cols;
288531337658SMarcel Moolenaar }
288631337658SMarcel Moolenaar 
288731337658SMarcel Moolenaar static int
xo_needed_encoding(xo_handle_t * xop)2888d1a0d267SMarcel Moolenaar xo_needed_encoding (xo_handle_t *xop)
2889d1a0d267SMarcel Moolenaar {
2890d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2891d1a0d267SMarcel Moolenaar 	return XF_ENC_UTF8;
2892d1a0d267SMarcel Moolenaar 
2893d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2894d1a0d267SMarcel Moolenaar 	return XF_ENC_LOCALE;
2895d1a0d267SMarcel Moolenaar 
2896d1a0d267SMarcel Moolenaar     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2897d1a0d267SMarcel Moolenaar }
2898d1a0d267SMarcel Moolenaar 
28998a6eceffSPhil Shafer static ssize_t
xo_format_string(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,xo_format_t * xfp)290031337658SMarcel Moolenaar xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
290131337658SMarcel Moolenaar 		  xo_format_t *xfp)
290231337658SMarcel Moolenaar {
290331337658SMarcel Moolenaar     static char null[] = "(null)";
2904d1a0d267SMarcel Moolenaar     static char null_no_quotes[] = "null";
2905a0f704ffSMarcel Moolenaar 
290631337658SMarcel Moolenaar     char *cp = NULL;
290731337658SMarcel Moolenaar     wchar_t *wcp = NULL;
29088a6eceffSPhil Shafer     ssize_t len;
29098a6eceffSPhil Shafer     ssize_t cols = 0, rc = 0;
29108a6eceffSPhil Shafer     ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2;
2911d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
291231337658SMarcel Moolenaar 
291331337658SMarcel Moolenaar     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
291431337658SMarcel Moolenaar 	return 0;
291531337658SMarcel Moolenaar 
2916a0f704ffSMarcel Moolenaar     len = xfp->xf_width[XF_WIDTH_SIZE];
2917a0f704ffSMarcel Moolenaar 
2918d1a0d267SMarcel Moolenaar     if (xfp->xf_fc == 'm') {
2919d1a0d267SMarcel Moolenaar 	cp = strerror(xop->xo_errno);
2920d1a0d267SMarcel Moolenaar 	if (len < 0)
2921d1a0d267SMarcel Moolenaar 	    len = cp ? strlen(cp) : 0;
2922d1a0d267SMarcel Moolenaar 	goto normal_string;
2923d1a0d267SMarcel Moolenaar 
2924d1a0d267SMarcel Moolenaar     } else if (xfp->xf_enc == XF_ENC_WIDE) {
292531337658SMarcel Moolenaar 	wcp = va_arg(xop->xo_vap, wchar_t *);
292631337658SMarcel Moolenaar 	if (xfp->xf_skip)
292731337658SMarcel Moolenaar 	    return 0;
292831337658SMarcel Moolenaar 
2929a0f704ffSMarcel Moolenaar 	/*
2930a0f704ffSMarcel Moolenaar 	 * Dont' deref NULL; use the traditional "(null)" instead
2931a0f704ffSMarcel Moolenaar 	 * of the more accurate "who's been a naughty boy, then?".
2932a0f704ffSMarcel Moolenaar 	 */
2933a0f704ffSMarcel Moolenaar 	if (wcp == NULL) {
2934a0f704ffSMarcel Moolenaar 	    cp = null;
2935a0f704ffSMarcel Moolenaar 	    len = sizeof(null) - 1;
2936a0f704ffSMarcel Moolenaar 	}
2937a0f704ffSMarcel Moolenaar 
293831337658SMarcel Moolenaar     } else {
293931337658SMarcel Moolenaar 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2940d1a0d267SMarcel Moolenaar 
2941d1a0d267SMarcel Moolenaar     normal_string:
294231337658SMarcel Moolenaar 	if (xfp->xf_skip)
294331337658SMarcel Moolenaar 	    return 0;
294431337658SMarcel Moolenaar 
2945a0f704ffSMarcel Moolenaar 	/* Echo "Dont' deref NULL" logic */
2946a0f704ffSMarcel Moolenaar 	if (cp == NULL) {
2947d1a0d267SMarcel Moolenaar 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2948d1a0d267SMarcel Moolenaar 		cp = null_no_quotes;
2949d1a0d267SMarcel Moolenaar 		len = sizeof(null_no_quotes) - 1;
2950d1a0d267SMarcel Moolenaar 	    } else {
2951a0f704ffSMarcel Moolenaar 		cp = null;
2952a0f704ffSMarcel Moolenaar 		len = sizeof(null) - 1;
2953a0f704ffSMarcel Moolenaar 	    }
2954d1a0d267SMarcel Moolenaar 	}
2955a0f704ffSMarcel Moolenaar 
295631337658SMarcel Moolenaar 	/*
295731337658SMarcel Moolenaar 	 * Optimize the most common case, which is "%s".  We just
295831337658SMarcel Moolenaar 	 * need to copy the complete string to the output buffer.
295931337658SMarcel Moolenaar 	 */
296031337658SMarcel Moolenaar 	if (xfp->xf_enc == need_enc
296131337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
296231337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
296331337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2964d1a0d267SMarcel Moolenaar 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2965d1a0d267SMarcel Moolenaar 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
296631337658SMarcel Moolenaar 	    len = strlen(cp);
296731337658SMarcel Moolenaar 	    xo_buf_escape(xop, xbp, cp, len, flags);
296831337658SMarcel Moolenaar 
296931337658SMarcel Moolenaar 	    /*
297031337658SMarcel Moolenaar 	     * Our caller expects xb_curp left untouched, so we have
297131337658SMarcel Moolenaar 	     * to reset it and return the number of bytes written to
297231337658SMarcel Moolenaar 	     * the buffer.
297331337658SMarcel Moolenaar 	     */
297431337658SMarcel Moolenaar 	    off2 = xbp->xb_curp - xbp->xb_bufp;
297531337658SMarcel Moolenaar 	    rc = off2 - off;
297631337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + off;
297731337658SMarcel Moolenaar 
297831337658SMarcel Moolenaar 	    return rc;
297931337658SMarcel Moolenaar 	}
298031337658SMarcel Moolenaar     }
298131337658SMarcel Moolenaar 
298231337658SMarcel Moolenaar     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
298331337658SMarcel Moolenaar 				   xfp->xf_width[XF_WIDTH_MAX],
298431337658SMarcel Moolenaar 				   need_enc, xfp->xf_enc);
298531337658SMarcel Moolenaar     if (cols < 0)
298631337658SMarcel Moolenaar 	goto bail;
298731337658SMarcel Moolenaar 
298831337658SMarcel Moolenaar     /*
298931337658SMarcel Moolenaar      * xo_buf_append* will move xb_curp, so we save/restore it.
299031337658SMarcel Moolenaar      */
299131337658SMarcel Moolenaar     off2 = xbp->xb_curp - xbp->xb_bufp;
299231337658SMarcel Moolenaar     rc = off2 - off;
299331337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
299431337658SMarcel Moolenaar 
299531337658SMarcel Moolenaar     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
299631337658SMarcel Moolenaar 	/*
299731337658SMarcel Moolenaar 	 * Find the number of columns needed to display the string.
299831337658SMarcel Moolenaar 	 * If we have the original wide string, we just call wcswidth,
299931337658SMarcel Moolenaar 	 * but if we did the work ourselves, then we need to do it.
300031337658SMarcel Moolenaar 	 */
300131337658SMarcel Moolenaar 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
3002ee5cf116SPhil Shafer 	if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN]))
300331337658SMarcel Moolenaar 	    goto bail;
300431337658SMarcel Moolenaar 
300531337658SMarcel Moolenaar 	/*
300631337658SMarcel Moolenaar 	 * If seen_minus, then pad on the right; otherwise move it so
300731337658SMarcel Moolenaar 	 * we can pad on the left.
300831337658SMarcel Moolenaar 	 */
300931337658SMarcel Moolenaar 	if (xfp->xf_seen_minus) {
301031337658SMarcel Moolenaar 	    cp = xbp->xb_curp + rc;
301131337658SMarcel Moolenaar 	} else {
301231337658SMarcel Moolenaar 	    cp = xbp->xb_curp;
301331337658SMarcel Moolenaar 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
301431337658SMarcel Moolenaar 	}
301531337658SMarcel Moolenaar 
301631337658SMarcel Moolenaar 	/* Set the padding */
301731337658SMarcel Moolenaar 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
301831337658SMarcel Moolenaar 	rc += delta;
301931337658SMarcel Moolenaar 	cols += delta;
302031337658SMarcel Moolenaar     }
302131337658SMarcel Moolenaar 
3022d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
302331337658SMarcel Moolenaar 	xop->xo_columns += cols;
3024d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
302531337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
302631337658SMarcel Moolenaar 
302731337658SMarcel Moolenaar     return rc;
302831337658SMarcel Moolenaar 
302931337658SMarcel Moolenaar  bail:
303031337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
303131337658SMarcel Moolenaar     return 0;
303231337658SMarcel Moolenaar }
303331337658SMarcel Moolenaar 
3034d1a0d267SMarcel Moolenaar /*
3035d1a0d267SMarcel Moolenaar  * Look backwards in a buffer to find a numeric value
3036d1a0d267SMarcel Moolenaar  */
3037d1a0d267SMarcel Moolenaar static int
xo_buf_find_last_number(xo_buffer_t * xbp,ssize_t start_offset)30388a6eceffSPhil Shafer xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset)
3039d1a0d267SMarcel Moolenaar {
3040d1a0d267SMarcel Moolenaar     int rc = 0;			/* Fail with zero */
3041d1a0d267SMarcel Moolenaar     int digit = 1;
3042d1a0d267SMarcel Moolenaar     char *sp = xbp->xb_bufp;
3043d1a0d267SMarcel Moolenaar     char *cp = sp + start_offset;
3044d1a0d267SMarcel Moolenaar 
3045d1a0d267SMarcel Moolenaar     while (--cp >= sp)
3046d1a0d267SMarcel Moolenaar 	if (isdigit((int) *cp))
3047d1a0d267SMarcel Moolenaar 	    break;
3048d1a0d267SMarcel Moolenaar 
3049d1a0d267SMarcel Moolenaar     for ( ; cp >= sp; cp--) {
3050d1a0d267SMarcel Moolenaar 	if (!isdigit((int) *cp))
3051d1a0d267SMarcel Moolenaar 	    break;
3052d1a0d267SMarcel Moolenaar 	rc += (*cp - '0') * digit;
3053d1a0d267SMarcel Moolenaar 	digit *= 10;
3054d1a0d267SMarcel Moolenaar     }
3055d1a0d267SMarcel Moolenaar 
3056d1a0d267SMarcel Moolenaar     return rc;
3057d1a0d267SMarcel Moolenaar }
3058d1a0d267SMarcel Moolenaar 
30598a6eceffSPhil Shafer static ssize_t
xo_count_utf8_cols(const char * str,ssize_t len)30608a6eceffSPhil Shafer xo_count_utf8_cols (const char *str, ssize_t len)
3061d1a0d267SMarcel Moolenaar {
30628a6eceffSPhil Shafer     ssize_t tlen;
3063d1a0d267SMarcel Moolenaar     wchar_t wc;
30648a6eceffSPhil Shafer     ssize_t cols = 0;
3065d1a0d267SMarcel Moolenaar     const char *ep = str + len;
3066d1a0d267SMarcel Moolenaar 
3067d1a0d267SMarcel Moolenaar     while (str < ep) {
3068d1a0d267SMarcel Moolenaar 	tlen = xo_utf8_to_wc_len(str);
3069d1a0d267SMarcel Moolenaar 	if (tlen < 0)		/* Broken input is very bad */
3070d1a0d267SMarcel Moolenaar 	    return cols;
3071d1a0d267SMarcel Moolenaar 
3072d1a0d267SMarcel Moolenaar 	wc = xo_utf8_char(str, tlen);
3073d1a0d267SMarcel Moolenaar 	if (wc == (wchar_t) -1)
3074d1a0d267SMarcel Moolenaar 	    return cols;
3075d1a0d267SMarcel Moolenaar 
3076d1a0d267SMarcel Moolenaar 	/* We only print printable characters */
3077d1a0d267SMarcel Moolenaar 	if (iswprint((wint_t) wc)) {
3078d1a0d267SMarcel Moolenaar 	    /*
3079d1a0d267SMarcel Moolenaar 	     * Find the width-in-columns of this character, which must be done
3080d1a0d267SMarcel Moolenaar 	     * in wide characters, since we lack a mbswidth() function.
3081d1a0d267SMarcel Moolenaar 	     */
30828a6eceffSPhil Shafer 	    ssize_t width = xo_wcwidth(wc);
3083d1a0d267SMarcel Moolenaar 	    if (width < 0)
3084d1a0d267SMarcel Moolenaar 		width = iswcntrl(wc) ? 0 : 1;
3085d1a0d267SMarcel Moolenaar 
3086d1a0d267SMarcel Moolenaar 	    cols += width;
3087d1a0d267SMarcel Moolenaar 	}
3088d1a0d267SMarcel Moolenaar 
3089d1a0d267SMarcel Moolenaar 	str += tlen;
3090d1a0d267SMarcel Moolenaar     }
3091d1a0d267SMarcel Moolenaar 
3092d1a0d267SMarcel Moolenaar     return cols;
3093d1a0d267SMarcel Moolenaar }
3094d1a0d267SMarcel Moolenaar 
3095d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
3096d1a0d267SMarcel Moolenaar static inline const char *
xo_dgettext(xo_handle_t * xop,const char * str)3097d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop, const char *str)
3098d1a0d267SMarcel Moolenaar {
3099d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
3100d1a0d267SMarcel Moolenaar     const char *res;
3101d1a0d267SMarcel Moolenaar 
3102d1a0d267SMarcel Moolenaar     res = dgettext(domainname, str);
3103d1a0d267SMarcel Moolenaar 
3104d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3105d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
3106d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "", xo_printable(domainname),
3107d1a0d267SMarcel Moolenaar 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
3108d1a0d267SMarcel Moolenaar 
3109d1a0d267SMarcel Moolenaar     return res;
3110d1a0d267SMarcel Moolenaar }
3111d1a0d267SMarcel Moolenaar 
3112d1a0d267SMarcel Moolenaar static inline const char *
xo_dngettext(xo_handle_t * xop,const char * sing,const char * plural,unsigned long int n)3113d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
3114d1a0d267SMarcel Moolenaar 	      unsigned long int n)
3115d1a0d267SMarcel Moolenaar {
3116d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
3117d1a0d267SMarcel Moolenaar     const char *res;
3118d1a0d267SMarcel Moolenaar 
3119d1a0d267SMarcel Moolenaar     res = dngettext(domainname, sing, plural, n);
3120d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3121d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%s"
3122d1a0d267SMarcel Moolenaar 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
3123d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "",
3124d1a0d267SMarcel Moolenaar 		xo_printable(domainname), domainname ? "\", " : "",
3125d1a0d267SMarcel Moolenaar 		xo_printable(sing),
3126d1a0d267SMarcel Moolenaar 		xo_printable(plural), n, xo_printable(res));
3127d1a0d267SMarcel Moolenaar 
3128d1a0d267SMarcel Moolenaar     return res;
3129d1a0d267SMarcel Moolenaar }
3130d1a0d267SMarcel Moolenaar #else /* HAVE_GETTEXT */
3131d1a0d267SMarcel Moolenaar static inline const char *
xo_dgettext(xo_handle_t * xop UNUSED,const char * str)3132d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
3133d1a0d267SMarcel Moolenaar {
3134d1a0d267SMarcel Moolenaar     return str;
3135d1a0d267SMarcel Moolenaar }
3136d1a0d267SMarcel Moolenaar 
3137d1a0d267SMarcel Moolenaar static inline const char *
xo_dngettext(xo_handle_t * xop UNUSED,const char * singular,const char * plural,unsigned long int n)3138d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
3139d1a0d267SMarcel Moolenaar 	      const char *plural, unsigned long int n)
3140d1a0d267SMarcel Moolenaar {
3141d1a0d267SMarcel Moolenaar     return (n == 1) ? singular : plural;
3142d1a0d267SMarcel Moolenaar }
3143d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
3144d1a0d267SMarcel Moolenaar 
3145d1a0d267SMarcel Moolenaar /*
3146d1a0d267SMarcel Moolenaar  * This is really _re_formatting, since the normal format code has
3147d1a0d267SMarcel Moolenaar  * generated a beautiful string into xo_data, starting at
3148d1a0d267SMarcel Moolenaar  * start_offset.  We need to see if it's plural, which means
3149d1a0d267SMarcel Moolenaar  * comma-separated options, or singular.  Then we make the appropriate
3150d1a0d267SMarcel Moolenaar  * call to d[n]gettext() to get the locale-based version.  Note that
3151d1a0d267SMarcel Moolenaar  * both input and output of gettext() this should be UTF-8.
3152d1a0d267SMarcel Moolenaar  */
31538a6eceffSPhil Shafer static ssize_t
xo_format_gettext(xo_handle_t * xop,xo_xff_flags_t flags,ssize_t start_offset,ssize_t cols,int need_enc)3154d1a0d267SMarcel Moolenaar xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
31558a6eceffSPhil Shafer 		   ssize_t start_offset, ssize_t cols, int need_enc)
3156d1a0d267SMarcel Moolenaar {
3157d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
3158d1a0d267SMarcel Moolenaar 
3159d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, 1))
3160d1a0d267SMarcel Moolenaar 	return cols;
3161d1a0d267SMarcel Moolenaar 
3162d1a0d267SMarcel Moolenaar     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
3163d1a0d267SMarcel Moolenaar 
3164d1a0d267SMarcel Moolenaar     char *cp = xbp->xb_bufp + start_offset;
31658a6eceffSPhil Shafer     ssize_t len = xbp->xb_curp - cp;
3166d1a0d267SMarcel Moolenaar     const char *newstr = NULL;
3167d1a0d267SMarcel Moolenaar 
3168d1a0d267SMarcel Moolenaar     /*
3169d1a0d267SMarcel Moolenaar      * The plural flag asks us to look backwards at the last numeric
3170d1a0d267SMarcel Moolenaar      * value rendered and disect the string into two pieces.
3171d1a0d267SMarcel Moolenaar      */
3172d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_PLURAL) {
3173d1a0d267SMarcel Moolenaar 	int n = xo_buf_find_last_number(xbp, start_offset);
3174d1a0d267SMarcel Moolenaar 	char *two = memchr(cp, (int) ',', len);
3175d1a0d267SMarcel Moolenaar 	if (two == NULL) {
3176d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
3177d1a0d267SMarcel Moolenaar 	    return cols;
3178d1a0d267SMarcel Moolenaar 	}
3179d1a0d267SMarcel Moolenaar 
3180d1a0d267SMarcel Moolenaar 	if (two == cp) {
3181d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing before comma in plural gettext "
3182d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3183d1a0d267SMarcel Moolenaar 	    return cols;
3184d1a0d267SMarcel Moolenaar 	}
3185d1a0d267SMarcel Moolenaar 
3186d1a0d267SMarcel Moolenaar 	if (two == xbp->xb_curp) {
3187d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing after comma in plural gettext "
3188d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3189d1a0d267SMarcel Moolenaar 	    return cols;
3190d1a0d267SMarcel Moolenaar 	}
3191d1a0d267SMarcel Moolenaar 
3192d1a0d267SMarcel Moolenaar 	*two++ = '\0';
3193d1a0d267SMarcel Moolenaar 	if (flags & XFF_GT_FIELD) {
3194d1a0d267SMarcel Moolenaar 	    newstr = xo_dngettext(xop, cp, two, n);
3195d1a0d267SMarcel Moolenaar 	} else {
3196d1a0d267SMarcel Moolenaar 	    /* Don't do a gettext() look up, just get the plural form */
3197d1a0d267SMarcel Moolenaar 	    newstr = (n == 1) ? cp : two;
3198d1a0d267SMarcel Moolenaar 	}
3199d1a0d267SMarcel Moolenaar 
3200d1a0d267SMarcel Moolenaar 	/*
3201d1a0d267SMarcel Moolenaar 	 * If we returned the first string, optimize a bit by
3202d1a0d267SMarcel Moolenaar 	 * backing up over comma
3203d1a0d267SMarcel Moolenaar 	 */
3204d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3205d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = two - 1; /* One for comma */
3206d1a0d267SMarcel Moolenaar 	    /*
3207d1a0d267SMarcel Moolenaar 	     * If the caller wanted UTF8, we're done; nothing changed,
3208d1a0d267SMarcel Moolenaar 	     * but we need to count the columns used.
3209d1a0d267SMarcel Moolenaar 	     */
3210d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3211d1a0d267SMarcel Moolenaar 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3212d1a0d267SMarcel Moolenaar 	}
3213d1a0d267SMarcel Moolenaar 
3214d1a0d267SMarcel Moolenaar     } else {
3215d1a0d267SMarcel Moolenaar 	/* The simple case (singular) */
3216d1a0d267SMarcel Moolenaar 	newstr = xo_dgettext(xop, cp);
3217d1a0d267SMarcel Moolenaar 
3218d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3219d1a0d267SMarcel Moolenaar 	    /* If the caller wanted UTF8, we're done; nothing changed */
3220d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3221d1a0d267SMarcel Moolenaar 		return cols;
3222d1a0d267SMarcel Moolenaar 	}
3223d1a0d267SMarcel Moolenaar     }
3224d1a0d267SMarcel Moolenaar 
3225d1a0d267SMarcel Moolenaar     /*
3226d1a0d267SMarcel Moolenaar      * Since the new string string might be in gettext's buffer or
3227d1a0d267SMarcel Moolenaar      * in the buffer (as the plural form), we make a copy.
3228d1a0d267SMarcel Moolenaar      */
32298a6eceffSPhil Shafer     ssize_t nlen = strlen(newstr);
3230d1a0d267SMarcel Moolenaar     char *newcopy = alloca(nlen + 1);
3231d1a0d267SMarcel Moolenaar     memcpy(newcopy, newstr, nlen + 1);
3232d1a0d267SMarcel Moolenaar 
3233d1a0d267SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3234d1a0d267SMarcel Moolenaar     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3235d1a0d267SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3236d1a0d267SMarcel Moolenaar }
3237d1a0d267SMarcel Moolenaar 
323831337658SMarcel Moolenaar static void
xo_data_append_content(xo_handle_t * xop,const char * str,ssize_t len,xo_xff_flags_t flags)32398a6eceffSPhil Shafer xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len,
3240d1a0d267SMarcel Moolenaar 			xo_xff_flags_t flags)
324131337658SMarcel Moolenaar {
324231337658SMarcel Moolenaar     int cols;
3243d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
32448a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(&xop->xo_data);
324531337658SMarcel Moolenaar 
3246d1a0d267SMarcel Moolenaar     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
324731337658SMarcel Moolenaar 				   NULL, str, len, -1,
324831337658SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3249d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS)
3250d1a0d267SMarcel Moolenaar 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
325131337658SMarcel Moolenaar 
3252d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
325331337658SMarcel Moolenaar 	xop->xo_columns += cols;
3254d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
325531337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
325631337658SMarcel Moolenaar }
325731337658SMarcel Moolenaar 
3258f2b7bf8aSPhil Shafer /**
3259f2b7bf8aSPhil Shafer  * Bump one of the 'width' values in a format strings (e.g. "%40.50.60s").
3260f2b7bf8aSPhil Shafer  * @param xfp Formatting instructions
3261f2b7bf8aSPhil Shafer  * @param digit Single digit (0-9) of input
3262f2b7bf8aSPhil Shafer  */
326331337658SMarcel Moolenaar static void
xo_bump_width(xo_format_t * xfp,int digit)326431337658SMarcel Moolenaar xo_bump_width (xo_format_t *xfp, int digit)
326531337658SMarcel Moolenaar {
326631337658SMarcel Moolenaar     int *ip = &xfp->xf_width[xfp->xf_dots];
326731337658SMarcel Moolenaar 
326831337658SMarcel Moolenaar     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
326931337658SMarcel Moolenaar }
327031337658SMarcel Moolenaar 
32718a6eceffSPhil Shafer static ssize_t
xo_trim_ws(xo_buffer_t * xbp,ssize_t len)32728a6eceffSPhil Shafer xo_trim_ws (xo_buffer_t *xbp, ssize_t len)
327331337658SMarcel Moolenaar {
327431337658SMarcel Moolenaar     char *cp, *sp, *ep;
32758a6eceffSPhil Shafer     ssize_t delta;
327631337658SMarcel Moolenaar 
327731337658SMarcel Moolenaar     /* First trim leading space */
327831337658SMarcel Moolenaar     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
327931337658SMarcel Moolenaar 	if (*cp != ' ')
328031337658SMarcel Moolenaar 	    break;
328131337658SMarcel Moolenaar     }
328231337658SMarcel Moolenaar 
328331337658SMarcel Moolenaar     delta = cp - sp;
328431337658SMarcel Moolenaar     if (delta) {
328531337658SMarcel Moolenaar 	len -= delta;
328631337658SMarcel Moolenaar 	memmove(sp, cp, len);
328731337658SMarcel Moolenaar     }
328831337658SMarcel Moolenaar 
328931337658SMarcel Moolenaar     /* Then trim off the end */
329031337658SMarcel Moolenaar     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
329131337658SMarcel Moolenaar 	if (ep[-1] != ' ')
329231337658SMarcel Moolenaar 	    break;
329331337658SMarcel Moolenaar     }
329431337658SMarcel Moolenaar 
329531337658SMarcel Moolenaar     delta = sp - ep;
329631337658SMarcel Moolenaar     if (delta) {
329731337658SMarcel Moolenaar 	len -= delta;
329831337658SMarcel Moolenaar 	cp[len] = '\0';
329931337658SMarcel Moolenaar     }
330031337658SMarcel Moolenaar 
330131337658SMarcel Moolenaar     return len;
330231337658SMarcel Moolenaar }
330331337658SMarcel Moolenaar 
3304d1a0d267SMarcel Moolenaar /*
3305d1a0d267SMarcel Moolenaar  * Interface to format a single field.  The arguments are in xo_vap,
3306d1a0d267SMarcel Moolenaar  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
3307d1a0d267SMarcel Moolenaar  * this is the most common case.
3308d1a0d267SMarcel Moolenaar  */
33098a6eceffSPhil Shafer static ssize_t
xo_do_format_field(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,ssize_t flen,xo_xff_flags_t flags)3310d1a0d267SMarcel Moolenaar xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
33118a6eceffSPhil Shafer 		const char *fmt, ssize_t flen, xo_xff_flags_t flags)
331231337658SMarcel Moolenaar {
331331337658SMarcel Moolenaar     xo_format_t xf;
331431337658SMarcel Moolenaar     const char *cp, *ep, *sp, *xp = NULL;
33158a6eceffSPhil Shafer     ssize_t rc, cols;
3316788ca347SMarcel Moolenaar     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
33178a6eceffSPhil Shafer     unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0;
3318d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
3319d1a0d267SMarcel Moolenaar     int real_need_enc = need_enc;
33208a6eceffSPhil Shafer     ssize_t old_cols = xop->xo_columns;
3321d1a0d267SMarcel Moolenaar 
3322d1a0d267SMarcel Moolenaar     /* The gettext interface is UTF-8, so we'll need that for now */
3323d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FIELD)
3324d1a0d267SMarcel Moolenaar 	need_enc = XF_ENC_UTF8;
332531337658SMarcel Moolenaar 
332631337658SMarcel Moolenaar     if (xbp == NULL)
332731337658SMarcel Moolenaar 	xbp = &xop->xo_data;
332831337658SMarcel Moolenaar 
33298a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(xbp);
3330d1a0d267SMarcel Moolenaar 
333131337658SMarcel Moolenaar     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3332d1a0d267SMarcel Moolenaar 	/*
3333d1a0d267SMarcel Moolenaar 	 * Since we're starting a new field, save the starting offset.
3334d1a0d267SMarcel Moolenaar 	 * We'll need this later for field-related operations.
3335d1a0d267SMarcel Moolenaar 	 */
3336d1a0d267SMarcel Moolenaar 
333731337658SMarcel Moolenaar 	if (*cp != '%') {
333831337658SMarcel Moolenaar 	add_one:
333931337658SMarcel Moolenaar 	    if (xp == NULL)
334031337658SMarcel Moolenaar 		xp = cp;
334131337658SMarcel Moolenaar 
334231337658SMarcel Moolenaar 	    if (*cp == '\\' && cp[1] != '\0')
334331337658SMarcel Moolenaar 		cp += 1;
334431337658SMarcel Moolenaar 	    continue;
334531337658SMarcel Moolenaar 
334676afb20cSPhil Shafer 	} else if (cp + 1 < ep && cp[1] == '%') {
334731337658SMarcel Moolenaar 	    cp += 1;
334831337658SMarcel Moolenaar 	    goto add_one;
334931337658SMarcel Moolenaar 	}
335031337658SMarcel Moolenaar 
335131337658SMarcel Moolenaar 	if (xp) {
335231337658SMarcel Moolenaar 	    if (make_output) {
335331337658SMarcel Moolenaar 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
335431337658SMarcel Moolenaar 					       NULL, xp, cp - xp, -1,
335531337658SMarcel Moolenaar 					       need_enc, XF_ENC_UTF8);
3356d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
335731337658SMarcel Moolenaar 		    xop->xo_columns += cols;
3358d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
335931337658SMarcel Moolenaar 		    xop->xo_anchor_columns += cols;
336031337658SMarcel Moolenaar 	    }
336131337658SMarcel Moolenaar 
336231337658SMarcel Moolenaar 	    xp = NULL;
336331337658SMarcel Moolenaar 	}
336431337658SMarcel Moolenaar 
336531337658SMarcel Moolenaar 	bzero(&xf, sizeof(xf));
336631337658SMarcel Moolenaar 	xf.xf_leading_zero = -1;
336731337658SMarcel Moolenaar 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
336831337658SMarcel Moolenaar 
336931337658SMarcel Moolenaar 	/*
337031337658SMarcel Moolenaar 	 * "%@" starts an XO-specific set of flags:
337131337658SMarcel Moolenaar 	 *   @X@ - XML-only field; ignored if style isn't XML
337231337658SMarcel Moolenaar 	 */
337331337658SMarcel Moolenaar 	if (cp[1] == '@') {
337431337658SMarcel Moolenaar 	    for (cp += 2; cp < ep; cp++) {
337531337658SMarcel Moolenaar 		if (*cp == '@') {
337631337658SMarcel Moolenaar 		    break;
337731337658SMarcel Moolenaar 		}
337831337658SMarcel Moolenaar 		if (*cp == '*') {
337931337658SMarcel Moolenaar 		    /*
338031337658SMarcel Moolenaar 		     * '*' means there's a "%*.*s" value in vap that
338131337658SMarcel Moolenaar 		     * we want to ignore
338231337658SMarcel Moolenaar 		     */
3383d1a0d267SMarcel Moolenaar 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
338431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
338531337658SMarcel Moolenaar 		}
338631337658SMarcel Moolenaar 	    }
338731337658SMarcel Moolenaar 	}
338831337658SMarcel Moolenaar 
338931337658SMarcel Moolenaar 	/* Hidden fields are only visible to JSON and XML */
3390d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
339131337658SMarcel Moolenaar 	    if (style != XO_STYLE_XML
3392d1a0d267SMarcel Moolenaar 		    && !xo_style_is_encoding(xop))
339331337658SMarcel Moolenaar 		xf.xf_skip = 1;
3394d1a0d267SMarcel Moolenaar 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
339531337658SMarcel Moolenaar 	    if (style != XO_STYLE_TEXT
3396788ca347SMarcel Moolenaar 		    && xo_style(xop) != XO_STYLE_HTML)
339731337658SMarcel Moolenaar 		xf.xf_skip = 1;
339831337658SMarcel Moolenaar 	}
339931337658SMarcel Moolenaar 
340031337658SMarcel Moolenaar 	if (!make_output)
340131337658SMarcel Moolenaar 	    xf.xf_skip = 1;
340231337658SMarcel Moolenaar 
340331337658SMarcel Moolenaar 	/*
340431337658SMarcel Moolenaar 	 * Looking at one piece of a format; find the end and
340531337658SMarcel Moolenaar 	 * call snprintf.  Then advance xo_vap on our own.
340631337658SMarcel Moolenaar 	 *
340731337658SMarcel Moolenaar 	 * Note that 'n', 'v', and '$' are not supported.
340831337658SMarcel Moolenaar 	 */
340931337658SMarcel Moolenaar 	sp = cp;		/* Save start pointer */
341031337658SMarcel Moolenaar 	for (cp += 1; cp < ep; cp++) {
341131337658SMarcel Moolenaar 	    if (*cp == 'l')
341231337658SMarcel Moolenaar 		xf.xf_lflag += 1;
341331337658SMarcel Moolenaar 	    else if (*cp == 'h')
341431337658SMarcel Moolenaar 		xf.xf_hflag += 1;
341531337658SMarcel Moolenaar 	    else if (*cp == 'j')
341631337658SMarcel Moolenaar 		xf.xf_jflag += 1;
341731337658SMarcel Moolenaar 	    else if (*cp == 't')
341831337658SMarcel Moolenaar 		xf.xf_tflag += 1;
341931337658SMarcel Moolenaar 	    else if (*cp == 'z')
342031337658SMarcel Moolenaar 		xf.xf_zflag += 1;
342131337658SMarcel Moolenaar 	    else if (*cp == 'q')
342231337658SMarcel Moolenaar 		xf.xf_qflag += 1;
342331337658SMarcel Moolenaar 	    else if (*cp == '.') {
342431337658SMarcel Moolenaar 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
342531337658SMarcel Moolenaar 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
342631337658SMarcel Moolenaar 		    return -1;
342731337658SMarcel Moolenaar 		}
342831337658SMarcel Moolenaar 	    } else if (*cp == '-')
342931337658SMarcel Moolenaar 		xf.xf_seen_minus = 1;
343031337658SMarcel Moolenaar 	    else if (isdigit((int) *cp)) {
343131337658SMarcel Moolenaar 		if (xf.xf_leading_zero < 0)
343231337658SMarcel Moolenaar 		    xf.xf_leading_zero = (*cp == '0');
343331337658SMarcel Moolenaar 		xo_bump_width(&xf, *cp - '0');
343431337658SMarcel Moolenaar 	    } else if (*cp == '*') {
343531337658SMarcel Moolenaar 		xf.xf_stars += 1;
343631337658SMarcel Moolenaar 		xf.xf_star[xf.xf_dots] = 1;
3437d1a0d267SMarcel Moolenaar 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
343831337658SMarcel Moolenaar 		break;
343931337658SMarcel Moolenaar 	    else if (*cp == 'n' || *cp == 'v') {
344031337658SMarcel Moolenaar 		xo_failure(xop, "unsupported format: '%s'", fmt);
344131337658SMarcel Moolenaar 		return -1;
344231337658SMarcel Moolenaar 	    }
344331337658SMarcel Moolenaar 	}
344431337658SMarcel Moolenaar 
344531337658SMarcel Moolenaar 	if (cp == ep)
344631337658SMarcel Moolenaar 	    xo_failure(xop, "field format missing format character: %s",
344731337658SMarcel Moolenaar 			  fmt);
344831337658SMarcel Moolenaar 
344931337658SMarcel Moolenaar 	xf.xf_fc = *cp;
345031337658SMarcel Moolenaar 
3451d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
345231337658SMarcel Moolenaar 	    if (*cp == 's' || *cp == 'S') {
345331337658SMarcel Moolenaar 		/* Handle "%*.*.*s" */
345431337658SMarcel Moolenaar 		int s;
345531337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
345631337658SMarcel Moolenaar 		    if (xf.xf_star[s]) {
345731337658SMarcel Moolenaar 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
345831337658SMarcel Moolenaar 
345931337658SMarcel Moolenaar 			/* Normalize a negative width value */
346031337658SMarcel Moolenaar 			if (xf.xf_width[s] < 0) {
346131337658SMarcel Moolenaar 			    if (s == 0) {
346231337658SMarcel Moolenaar 				xf.xf_width[0] = -xf.xf_width[0];
346331337658SMarcel Moolenaar 				xf.xf_seen_minus = 1;
346431337658SMarcel Moolenaar 			    } else
346531337658SMarcel Moolenaar 				xf.xf_width[s] = -1; /* Ignore negative values */
346631337658SMarcel Moolenaar 			}
346731337658SMarcel Moolenaar 		    }
346831337658SMarcel Moolenaar 		}
346931337658SMarcel Moolenaar 	    }
347031337658SMarcel Moolenaar 	}
347131337658SMarcel Moolenaar 
347231337658SMarcel Moolenaar 	/* If no max is given, it defaults to size */
347331337658SMarcel Moolenaar 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
347431337658SMarcel Moolenaar 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
347531337658SMarcel Moolenaar 
347631337658SMarcel Moolenaar 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
347731337658SMarcel Moolenaar 	    xf.xf_lflag = 1;
347831337658SMarcel Moolenaar 
347931337658SMarcel Moolenaar 	if (!xf.xf_skip) {
348031337658SMarcel Moolenaar 	    xo_buffer_t *fbp = &xop->xo_fmt;
34818a6eceffSPhil Shafer 	    ssize_t len = cp - sp + 1;
348231337658SMarcel Moolenaar 	    if (!xo_buf_has_room(fbp, len + 1))
348331337658SMarcel Moolenaar 		return -1;
348431337658SMarcel Moolenaar 
348531337658SMarcel Moolenaar 	    char *newfmt = fbp->xb_curp;
348631337658SMarcel Moolenaar 	    memcpy(newfmt, sp, len);
348731337658SMarcel Moolenaar 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
348831337658SMarcel Moolenaar 	    newfmt[len] = '\0';
348931337658SMarcel Moolenaar 
349031337658SMarcel Moolenaar 	    /*
349131337658SMarcel Moolenaar 	     * Bad news: our strings are UTF-8, but the stock printf
349231337658SMarcel Moolenaar 	     * functions won't handle field widths for wide characters
349331337658SMarcel Moolenaar 	     * correctly.  So we have to handle this ourselves.
349431337658SMarcel Moolenaar 	     */
349531337658SMarcel Moolenaar 	    if (xop->xo_formatter == NULL
3496d1a0d267SMarcel Moolenaar 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3497d1a0d267SMarcel Moolenaar 			|| xf.xf_fc == 'm')) {
3498d1a0d267SMarcel Moolenaar 
3499d1a0d267SMarcel Moolenaar 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3500d1a0d267SMarcel Moolenaar 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3501d1a0d267SMarcel Moolenaar 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3502d1a0d267SMarcel Moolenaar 
350331337658SMarcel Moolenaar 		rc = xo_format_string(xop, xbp, flags, &xf);
350431337658SMarcel Moolenaar 
3505d1a0d267SMarcel Moolenaar 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
350631337658SMarcel Moolenaar 		    rc = xo_trim_ws(xbp, rc);
350731337658SMarcel Moolenaar 
350831337658SMarcel Moolenaar 	    } else {
3509f2b7bf8aSPhil Shafer 		ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt,
3510f2b7bf8aSPhil Shafer 						    xop->xo_vap);
351131337658SMarcel Moolenaar 
3512406a584dSPhil Shafer 		if (rc > 0) {
351331337658SMarcel Moolenaar 		    /*
351431337658SMarcel Moolenaar 		     * For XML and HTML, we need "&<>" processing; for JSON,
351531337658SMarcel Moolenaar 		     * it's quotes.  Text gets nothing.
351631337658SMarcel Moolenaar 		     */
351731337658SMarcel Moolenaar 		    switch (style) {
351831337658SMarcel Moolenaar 		    case XO_STYLE_XML:
351931337658SMarcel Moolenaar 			if (flags & XFF_TRIM_WS)
352031337658SMarcel Moolenaar 			    columns = rc = xo_trim_ws(xbp, rc);
3521ee5cf116SPhil Shafer 			/* FALLTHRU */
352231337658SMarcel Moolenaar 		    case XO_STYLE_HTML:
352331337658SMarcel Moolenaar 			rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
352431337658SMarcel Moolenaar 			break;
352531337658SMarcel Moolenaar 
352631337658SMarcel Moolenaar 		    case XO_STYLE_JSON:
352731337658SMarcel Moolenaar 			if (flags & XFF_TRIM_WS)
352831337658SMarcel Moolenaar 			    columns = rc = xo_trim_ws(xbp, rc);
3529d1a0d267SMarcel Moolenaar 			rc = xo_escape_json(xbp, rc, 0);
3530d1a0d267SMarcel Moolenaar 			break;
3531d1a0d267SMarcel Moolenaar 
3532d1a0d267SMarcel Moolenaar 		    case XO_STYLE_SDPARAMS:
3533d1a0d267SMarcel Moolenaar 			if (flags & XFF_TRIM_WS)
3534d1a0d267SMarcel Moolenaar 			    columns = rc = xo_trim_ws(xbp, rc);
3535d1a0d267SMarcel Moolenaar 			rc = xo_escape_sdparams(xbp, rc, 0);
3536d1a0d267SMarcel Moolenaar 			break;
3537d1a0d267SMarcel Moolenaar 
3538d1a0d267SMarcel Moolenaar 		    case XO_STYLE_ENCODER:
3539d1a0d267SMarcel Moolenaar 			if (flags & XFF_TRIM_WS)
3540d1a0d267SMarcel Moolenaar 			    columns = rc = xo_trim_ws(xbp, rc);
354131337658SMarcel Moolenaar 			break;
354231337658SMarcel Moolenaar 		    }
354331337658SMarcel Moolenaar 
354431337658SMarcel Moolenaar 		    /*
3545d1a0d267SMarcel Moolenaar 		     * We can assume all the non-%s data we've
3546d1a0d267SMarcel Moolenaar 		     * added is ASCII, so the columns and bytes are the
3547d1a0d267SMarcel Moolenaar 		     * same.  xo_format_string handles all the fancy
3548d1a0d267SMarcel Moolenaar 		     * string conversions and updates xo_anchor_columns
3549d1a0d267SMarcel Moolenaar 		     * accordingly.
355031337658SMarcel Moolenaar 		     */
3551d1a0d267SMarcel Moolenaar 		    if (XOF_ISSET(xop, XOF_COLUMNS))
355231337658SMarcel Moolenaar 			xop->xo_columns += columns;
3553d1a0d267SMarcel Moolenaar 		    if (XOIF_ISSET(xop, XOIF_ANCHOR))
355431337658SMarcel Moolenaar 			xop->xo_anchor_columns += columns;
355531337658SMarcel Moolenaar 		}
3556406a584dSPhil Shafer 	    }
355731337658SMarcel Moolenaar 
3558406a584dSPhil Shafer 	    if (rc > 0)
355931337658SMarcel Moolenaar 		xbp->xb_curp += rc;
356031337658SMarcel Moolenaar 	}
356131337658SMarcel Moolenaar 
356231337658SMarcel Moolenaar 	/*
356331337658SMarcel Moolenaar 	 * Now for the tricky part: we need to move the argument pointer
356431337658SMarcel Moolenaar 	 * along by the amount needed.
356531337658SMarcel Moolenaar 	 */
3566d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
356731337658SMarcel Moolenaar 
356831337658SMarcel Moolenaar 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
356931337658SMarcel Moolenaar 		/*
357031337658SMarcel Moolenaar 		 * The 'S' and 's' formats are normally handled in
357131337658SMarcel Moolenaar 		 * xo_format_string, but if we skipped it, then we
357231337658SMarcel Moolenaar 		 * need to pop it.
357331337658SMarcel Moolenaar 		 */
357431337658SMarcel Moolenaar 		if (xf.xf_skip)
357531337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, char *);
357631337658SMarcel Moolenaar 
3577d1a0d267SMarcel Moolenaar 	    } else if (xf.xf_fc == 'm') {
3578d1a0d267SMarcel Moolenaar 		/* Nothing on the stack for "%m" */
3579d1a0d267SMarcel Moolenaar 
358031337658SMarcel Moolenaar 	    } else {
358131337658SMarcel Moolenaar 		int s;
358231337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
358331337658SMarcel Moolenaar 		    if (xf.xf_star[s])
358431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
358531337658SMarcel Moolenaar 		}
358631337658SMarcel Moolenaar 
358731337658SMarcel Moolenaar 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
358831337658SMarcel Moolenaar 		    if (xf.xf_hflag > 1) {
358931337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
359031337658SMarcel Moolenaar 
359131337658SMarcel Moolenaar 		    } else if (xf.xf_hflag > 0) {
359231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
359331337658SMarcel Moolenaar 
359431337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 1) {
359531337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long long);
359631337658SMarcel Moolenaar 
359731337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 0) {
359831337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long);
359931337658SMarcel Moolenaar 
360031337658SMarcel Moolenaar 		    } else if (xf.xf_jflag > 0) {
360131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, intmax_t);
360231337658SMarcel Moolenaar 
360331337658SMarcel Moolenaar 		    } else if (xf.xf_tflag > 0) {
360431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, ptrdiff_t);
360531337658SMarcel Moolenaar 
360631337658SMarcel Moolenaar 		    } else if (xf.xf_zflag > 0) {
360731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, size_t);
360831337658SMarcel Moolenaar 
360931337658SMarcel Moolenaar 		    } else if (xf.xf_qflag > 0) {
361031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, quad_t);
361131337658SMarcel Moolenaar 
361231337658SMarcel Moolenaar 		    } else {
361331337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
361431337658SMarcel Moolenaar 		    }
361531337658SMarcel Moolenaar 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
361631337658SMarcel Moolenaar 		    if (xf.xf_lflag)
361731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, long double);
361831337658SMarcel Moolenaar 		    else
361931337658SMarcel Moolenaar 			va_arg(xop->xo_vap, double);
362031337658SMarcel Moolenaar 
362131337658SMarcel Moolenaar 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
362231337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, wint_t);
362331337658SMarcel Moolenaar 
362431337658SMarcel Moolenaar 		else if (xf.xf_fc == 'c')
362531337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, int);
362631337658SMarcel Moolenaar 
362731337658SMarcel Moolenaar 		else if (xf.xf_fc == 'p')
362831337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, void *);
362931337658SMarcel Moolenaar 	    }
363031337658SMarcel Moolenaar 	}
363131337658SMarcel Moolenaar     }
363231337658SMarcel Moolenaar 
363331337658SMarcel Moolenaar     if (xp) {
363431337658SMarcel Moolenaar 	if (make_output) {
363531337658SMarcel Moolenaar 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
363631337658SMarcel Moolenaar 					   NULL, xp, cp - xp, -1,
363731337658SMarcel Moolenaar 					   need_enc, XF_ENC_UTF8);
3638d1a0d267SMarcel Moolenaar 
3639d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
364031337658SMarcel Moolenaar 		xop->xo_columns += cols;
3641d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
364231337658SMarcel Moolenaar 		xop->xo_anchor_columns += cols;
364331337658SMarcel Moolenaar 	}
364431337658SMarcel Moolenaar 
364531337658SMarcel Moolenaar 	xp = NULL;
364631337658SMarcel Moolenaar     }
364731337658SMarcel Moolenaar 
3648d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS) {
3649d1a0d267SMarcel Moolenaar 	/*
3650d1a0d267SMarcel Moolenaar 	 * Handle gettext()ing the field by looking up the value
3651d1a0d267SMarcel Moolenaar 	 * and then copying it in, while converting to locale, if
3652d1a0d267SMarcel Moolenaar 	 * needed.
3653d1a0d267SMarcel Moolenaar 	 */
36548a6eceffSPhil Shafer 	ssize_t new_cols = xo_format_gettext(xop, flags, start_offset,
3655d1a0d267SMarcel Moolenaar 					 old_cols, real_need_enc);
3656d1a0d267SMarcel Moolenaar 
3657d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_COLUMNS))
3658d1a0d267SMarcel Moolenaar 	    xop->xo_columns += new_cols - old_cols;
3659d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3660d1a0d267SMarcel Moolenaar 	    xop->xo_anchor_columns += new_cols - old_cols;
3661d1a0d267SMarcel Moolenaar     }
3662d1a0d267SMarcel Moolenaar 
366331337658SMarcel Moolenaar     return 0;
366431337658SMarcel Moolenaar }
366531337658SMarcel Moolenaar 
3666264104f2SPhil Shafer /*
3667264104f2SPhil Shafer  * Remove any numeric precision/width format from the format string by
3668264104f2SPhil Shafer  * inserting the "%" after the [0-9]+, returning the substring.
3669264104f2SPhil Shafer  */
367031337658SMarcel Moolenaar static char *
xo_fix_encoding(xo_handle_t * xop UNUSED,char * encoding)367131337658SMarcel Moolenaar xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
367231337658SMarcel Moolenaar {
367331337658SMarcel Moolenaar     char *cp = encoding;
367431337658SMarcel Moolenaar 
367531337658SMarcel Moolenaar     if (cp[0] != '%' || !isdigit((int) cp[1]))
367631337658SMarcel Moolenaar 	return encoding;
367731337658SMarcel Moolenaar 
367831337658SMarcel Moolenaar     for (cp += 2; *cp; cp++) {
367931337658SMarcel Moolenaar 	if (!isdigit((int) *cp))
368031337658SMarcel Moolenaar 	    break;
368131337658SMarcel Moolenaar     }
368231337658SMarcel Moolenaar 
3683264104f2SPhil Shafer     *--cp = '%';		/* Back off and insert the '%' */
368431337658SMarcel Moolenaar 
368531337658SMarcel Moolenaar     return cp;
368631337658SMarcel Moolenaar }
368731337658SMarcel Moolenaar 
368831337658SMarcel Moolenaar static void
xo_color_append_html(xo_handle_t * xop)3689788ca347SMarcel Moolenaar xo_color_append_html (xo_handle_t *xop)
3690788ca347SMarcel Moolenaar {
3691788ca347SMarcel Moolenaar     /*
3692788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3693788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3694788ca347SMarcel Moolenaar      */
3695788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3696788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3697788ca347SMarcel Moolenaar 
3698788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3699788ca347SMarcel Moolenaar     }
3700788ca347SMarcel Moolenaar }
3701788ca347SMarcel Moolenaar 
3702d1a0d267SMarcel Moolenaar /*
3703d1a0d267SMarcel Moolenaar  * A wrapper for humanize_number that autoscales, since the
3704d1a0d267SMarcel Moolenaar  * HN_AUTOSCALE flag scales as needed based on the size of
3705d1a0d267SMarcel Moolenaar  * the output buffer, not the size of the value.  I also
3706d1a0d267SMarcel Moolenaar  * wish HN_DECIMAL was more imperative, without the <10
3707d1a0d267SMarcel Moolenaar  * test.  But the boat only goes where we want when we hold
3708d1a0d267SMarcel Moolenaar  * the rudder, so xo_humanize fixes part of the problem.
3709d1a0d267SMarcel Moolenaar  */
37108a6eceffSPhil Shafer static ssize_t
xo_humanize(char * buf,ssize_t len,uint64_t value,int flags)37118a6eceffSPhil Shafer xo_humanize (char *buf, ssize_t len, uint64_t value, int flags)
3712d1a0d267SMarcel Moolenaar {
3713d1a0d267SMarcel Moolenaar     int scale = 0;
3714d1a0d267SMarcel Moolenaar 
3715d1a0d267SMarcel Moolenaar     if (value) {
3716d1a0d267SMarcel Moolenaar 	uint64_t left = value;
3717d1a0d267SMarcel Moolenaar 
3718d1a0d267SMarcel Moolenaar 	if (flags & HN_DIVISOR_1000) {
3719d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3720d1a0d267SMarcel Moolenaar 		left /= 1000;
3721d1a0d267SMarcel Moolenaar 	} else {
3722d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3723d1a0d267SMarcel Moolenaar 		left /= 1024;
3724d1a0d267SMarcel Moolenaar 	}
3725d1a0d267SMarcel Moolenaar 	scale -= 1;
3726d1a0d267SMarcel Moolenaar     }
3727d1a0d267SMarcel Moolenaar 
3728d1a0d267SMarcel Moolenaar     return xo_humanize_number(buf, len, value, "", scale, flags);
3729d1a0d267SMarcel Moolenaar }
3730d1a0d267SMarcel Moolenaar 
3731d1a0d267SMarcel Moolenaar /*
3732d1a0d267SMarcel Moolenaar  * This is an area where we can save information from the handle for
3733d1a0d267SMarcel Moolenaar  * later restoration.  We need to know what data was rendered to know
3734d1a0d267SMarcel Moolenaar  * what needs cleaned up.
3735d1a0d267SMarcel Moolenaar  */
3736d1a0d267SMarcel Moolenaar typedef struct xo_humanize_save_s {
37378a6eceffSPhil Shafer     ssize_t xhs_offset;		/* Saved xo_offset */
37388a6eceffSPhil Shafer     ssize_t xhs_columns;	/* Saved xo_columns */
37398a6eceffSPhil Shafer     ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */
3740d1a0d267SMarcel Moolenaar } xo_humanize_save_t;
3741d1a0d267SMarcel Moolenaar 
3742d1a0d267SMarcel Moolenaar /*
3743d1a0d267SMarcel Moolenaar  * Format a "humanized" value for a numeric, meaning something nice
3744d1a0d267SMarcel Moolenaar  * like "44M" instead of "44470272".  We autoscale, choosing the
3745d1a0d267SMarcel Moolenaar  * most appropriate value for K/M/G/T/P/E based on the value given.
3746d1a0d267SMarcel Moolenaar  */
3747d1a0d267SMarcel Moolenaar static void
xo_format_humanize(xo_handle_t * xop,xo_buffer_t * xbp,xo_humanize_save_t * savep,xo_xff_flags_t flags)3748d1a0d267SMarcel Moolenaar xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3749d1a0d267SMarcel Moolenaar 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3750d1a0d267SMarcel Moolenaar {
3751d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3752d1a0d267SMarcel Moolenaar 	return;
3753d1a0d267SMarcel Moolenaar 
37548a6eceffSPhil Shafer     ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
3755d1a0d267SMarcel Moolenaar     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3756d1a0d267SMarcel Moolenaar 	return;
3757d1a0d267SMarcel Moolenaar 
3758d1a0d267SMarcel Moolenaar     /*
3759d1a0d267SMarcel Moolenaar      * We have a string that's allegedly a number. We want to
3760d1a0d267SMarcel Moolenaar      * humanize it, which means turning it back into a number
3761d1a0d267SMarcel Moolenaar      * and calling xo_humanize_number on it.
3762d1a0d267SMarcel Moolenaar      */
3763d1a0d267SMarcel Moolenaar     uint64_t value;
3764d1a0d267SMarcel Moolenaar     char *ep;
3765d1a0d267SMarcel Moolenaar 
3766d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3767d1a0d267SMarcel Moolenaar 
3768d1a0d267SMarcel Moolenaar     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3769d1a0d267SMarcel Moolenaar     if (!(value == ULLONG_MAX && errno == ERANGE)
3770d1a0d267SMarcel Moolenaar 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3771d1a0d267SMarcel Moolenaar 	/*
3772d1a0d267SMarcel Moolenaar 	 * There are few values where humanize_number needs
3773d1a0d267SMarcel Moolenaar 	 * more bytes than the original value.  I've used
3774d1a0d267SMarcel Moolenaar 	 * 10 as a rectal number to cover those scenarios.
3775d1a0d267SMarcel Moolenaar 	 */
3776d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, 10)) {
3777d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3778d1a0d267SMarcel Moolenaar 
37798a6eceffSPhil Shafer 	    ssize_t rc;
37808a6eceffSPhil Shafer 	    ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3781d1a0d267SMarcel Moolenaar 	    int hn_flags = HN_NOSPACE; /* On by default */
3782d1a0d267SMarcel Moolenaar 
3783d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_SPACE)
3784d1a0d267SMarcel Moolenaar 		hn_flags &= ~HN_NOSPACE;
3785d1a0d267SMarcel Moolenaar 
3786d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_DECIMAL)
3787d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DECIMAL;
3788d1a0d267SMarcel Moolenaar 
3789d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_1000)
3790d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DIVISOR_1000;
3791d1a0d267SMarcel Moolenaar 
37928a6eceffSPhil Shafer 	    rc = xo_humanize(xbp->xb_curp, left, value, hn_flags);
3793d1a0d267SMarcel Moolenaar 	    if (rc > 0) {
3794d1a0d267SMarcel Moolenaar 		xbp->xb_curp += rc;
3795d1a0d267SMarcel Moolenaar 		xop->xo_columns = savep->xhs_columns + rc;
3796d1a0d267SMarcel Moolenaar 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3797d1a0d267SMarcel Moolenaar 	    }
3798d1a0d267SMarcel Moolenaar 	}
3799d1a0d267SMarcel Moolenaar     }
3800d1a0d267SMarcel Moolenaar }
3801d1a0d267SMarcel Moolenaar 
3802264104f2SPhil Shafer /*
3803264104f2SPhil Shafer  * Convenience function that either append a fixed value (if one is
3804264104f2SPhil Shafer  * given) or formats a field using a format string.  If it's
3805264104f2SPhil Shafer  * encode_only, then we can't skip formatting the field, since it may
3806264104f2SPhil Shafer  * be pulling arguments off the stack.
3807264104f2SPhil Shafer  */
3808264104f2SPhil Shafer static inline void
xo_simple_field(xo_handle_t * xop,unsigned encode_only,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,xo_xff_flags_t flags)3809264104f2SPhil Shafer xo_simple_field (xo_handle_t *xop, unsigned encode_only,
3810264104f2SPhil Shafer 		      const char *value, ssize_t vlen,
3811264104f2SPhil Shafer 		      const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3812264104f2SPhil Shafer {
3813264104f2SPhil Shafer     if (encode_only)
3814264104f2SPhil Shafer 	flags |= XFF_NO_OUTPUT;
3815264104f2SPhil Shafer 
3816264104f2SPhil Shafer     if (vlen == 0)
3817264104f2SPhil Shafer 	xo_do_format_field(xop, NULL, fmt, flen, flags);
3818264104f2SPhil Shafer     else if (!encode_only)
3819264104f2SPhil Shafer 	xo_data_append_content(xop, value, vlen, flags);
3820264104f2SPhil Shafer }
3821264104f2SPhil Shafer 
3822264104f2SPhil Shafer /*
3823264104f2SPhil Shafer  * Html mode: append a <div> to the output buffer contain a field
3824264104f2SPhil Shafer  * along with all the supporting information indicated by the flags.
3825264104f2SPhil Shafer  */
3826788ca347SMarcel Moolenaar static void
xo_buf_append_div(xo_handle_t * xop,const char * class,xo_xff_flags_t flags,const char * name,ssize_t nlen,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,const char * encoding,ssize_t elen)382731337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
38288a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
38298a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
3830264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
38318a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen)
383231337658SMarcel Moolenaar {
383331337658SMarcel Moolenaar     static char div_start[] = "<div class=\"";
383431337658SMarcel Moolenaar     static char div_tag[] = "\" data-tag=\"";
383531337658SMarcel Moolenaar     static char div_xpath[] = "\" data-xpath=\"";
383631337658SMarcel Moolenaar     static char div_key[] = "\" data-key=\"key";
383731337658SMarcel Moolenaar     static char div_end[] = "\">";
383831337658SMarcel Moolenaar     static char div_close[] = "</div>";
383931337658SMarcel Moolenaar 
3840a321cc5dSPhil Shafer     /* The encoding format defaults to the normal format */
3841264104f2SPhil Shafer     if (encoding == NULL && fmt != NULL) {
3842264104f2SPhil Shafer 	char *enc  = alloca(flen + 1);
3843264104f2SPhil Shafer 	memcpy(enc, fmt, flen);
3844264104f2SPhil Shafer 	enc[flen] = '\0';
3845a321cc5dSPhil Shafer 	encoding = xo_fix_encoding(xop, enc);
3846a321cc5dSPhil Shafer 	elen = strlen(encoding);
3847a321cc5dSPhil Shafer     }
3848a321cc5dSPhil Shafer 
384931337658SMarcel Moolenaar     /*
385031337658SMarcel Moolenaar      * To build our XPath predicate, we need to save the va_list before
385131337658SMarcel Moolenaar      * we format our data, and then restore it before we format the
385231337658SMarcel Moolenaar      * xpath expression.
385331337658SMarcel Moolenaar      * Display-only keys implies that we've got an encode-only key
385431337658SMarcel Moolenaar      * elsewhere, so we don't use them from making predicates.
385531337658SMarcel Moolenaar      */
385631337658SMarcel Moolenaar     int need_predidate =
385731337658SMarcel Moolenaar 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
38588a6eceffSPhil Shafer 	 && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0;
385931337658SMarcel Moolenaar 
386031337658SMarcel Moolenaar     if (need_predidate) {
386131337658SMarcel Moolenaar 	va_list va_local;
386231337658SMarcel Moolenaar 
386331337658SMarcel Moolenaar 	va_copy(va_local, xop->xo_vap);
386431337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
386531337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
386631337658SMarcel Moolenaar 
386731337658SMarcel Moolenaar 	/*
386831337658SMarcel Moolenaar 	 * Build an XPath predicate expression to match this key.
386931337658SMarcel Moolenaar 	 * We use the format buffer.
387031337658SMarcel Moolenaar 	 */
387131337658SMarcel Moolenaar 	xo_buffer_t *pbp = &xop->xo_predicate;
387231337658SMarcel Moolenaar 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
387331337658SMarcel Moolenaar 
387431337658SMarcel Moolenaar 	xo_buf_append(pbp, "[", 1);
387531337658SMarcel Moolenaar 	xo_buf_escape(xop, pbp, name, nlen, 0);
3876d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
387731337658SMarcel Moolenaar 	    xo_buf_append(pbp, " = '", 4);
387831337658SMarcel Moolenaar 	else
387931337658SMarcel Moolenaar 	    xo_buf_append(pbp, "='", 2);
388031337658SMarcel Moolenaar 
3881d1a0d267SMarcel Moolenaar 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3882d1a0d267SMarcel Moolenaar 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3883d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
388431337658SMarcel Moolenaar 
388531337658SMarcel Moolenaar 	xo_buf_append(pbp, "']", 2);
388631337658SMarcel Moolenaar 
388731337658SMarcel Moolenaar 	/* Now we record this predicate expression in the stack */
388831337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
38898a6eceffSPhil Shafer 	ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
38908a6eceffSPhil Shafer 	ssize_t dlen = pbp->xb_curp - pbp->xb_bufp;
389131337658SMarcel Moolenaar 
389231337658SMarcel Moolenaar 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
389331337658SMarcel Moolenaar 	if (cp) {
389431337658SMarcel Moolenaar 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
389531337658SMarcel Moolenaar 	    cp[olen + dlen] = '\0';
389631337658SMarcel Moolenaar 	    xsp->xs_keys = cp;
389731337658SMarcel Moolenaar 	}
389831337658SMarcel Moolenaar 
389931337658SMarcel Moolenaar 	/* Now we reset the xo_vap as if we were never here */
390031337658SMarcel Moolenaar 	va_end(xop->xo_vap);
390131337658SMarcel Moolenaar 	va_copy(xop->xo_vap, va_local);
390231337658SMarcel Moolenaar 	va_end(va_local);
390331337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
390431337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
390531337658SMarcel Moolenaar     }
390631337658SMarcel Moolenaar 
390731337658SMarcel Moolenaar     if (flags & XFF_ENCODE_ONLY) {
390831337658SMarcel Moolenaar 	/*
3909ee5cf116SPhil Shafer 	 * Even if this is encode-only, we need to go through the
391031337658SMarcel Moolenaar 	 * work of formatting it to make sure the args are cleared
3911264104f2SPhil Shafer 	 * from xo_vap.  This is not true when vlen is zero, since
3912264104f2SPhil Shafer 	 * that means our "value" isn't on the stack.
391331337658SMarcel Moolenaar 	 */
3914264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, NULL, 0, encoding, elen, flags);
391531337658SMarcel Moolenaar 	return;
391631337658SMarcel Moolenaar     }
391731337658SMarcel Moolenaar 
391831337658SMarcel Moolenaar     xo_line_ensure_open(xop, 0);
391931337658SMarcel Moolenaar 
3920d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
392131337658SMarcel Moolenaar 	xo_buf_indent(xop, xop->xo_indent_by);
392231337658SMarcel Moolenaar 
392331337658SMarcel Moolenaar     xo_data_append(xop, div_start, sizeof(div_start) - 1);
392431337658SMarcel Moolenaar     xo_data_append(xop, class, strlen(class));
392531337658SMarcel Moolenaar 
3926788ca347SMarcel Moolenaar     /*
3927788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3928788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3929788ca347SMarcel Moolenaar      */
3930788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3931788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3932788ca347SMarcel Moolenaar 
3933788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3934788ca347SMarcel Moolenaar     }
3935788ca347SMarcel Moolenaar 
393631337658SMarcel Moolenaar     if (name) {
393731337658SMarcel Moolenaar 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
393831337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
393931337658SMarcel Moolenaar 
394031337658SMarcel Moolenaar 	/*
394131337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
394231337658SMarcel Moolenaar 	 */
3943d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
3944d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
394531337658SMarcel Moolenaar 	    /*
394631337658SMarcel Moolenaar 	     * Note: We need the '+1' here because we know we've not
394731337658SMarcel Moolenaar 	     * added the closing quote.  We add one, knowing the quote
394831337658SMarcel Moolenaar 	     * will be added shortly.
394931337658SMarcel Moolenaar 	     */
395031337658SMarcel Moolenaar 	    xop->xo_units_offset =
395131337658SMarcel Moolenaar 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
395231337658SMarcel Moolenaar 	}
395331337658SMarcel Moolenaar 
3954d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_XPATH)) {
395531337658SMarcel Moolenaar 	    int i;
395631337658SMarcel Moolenaar 	    xo_stack_t *xsp;
395731337658SMarcel Moolenaar 
395831337658SMarcel Moolenaar 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
395931337658SMarcel Moolenaar 	    if (xop->xo_leading_xpath)
396031337658SMarcel Moolenaar 		xo_data_append(xop, xop->xo_leading_xpath,
396131337658SMarcel Moolenaar 			       strlen(xop->xo_leading_xpath));
396231337658SMarcel Moolenaar 
396331337658SMarcel Moolenaar 	    for (i = 0; i <= xop->xo_depth; i++) {
396431337658SMarcel Moolenaar 		xsp = &xop->xo_stack[i];
396531337658SMarcel Moolenaar 		if (xsp->xs_name == NULL)
396631337658SMarcel Moolenaar 		    continue;
396731337658SMarcel Moolenaar 
3968545ddfbeSMarcel Moolenaar 		/*
3969545ddfbeSMarcel Moolenaar 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3970545ddfbeSMarcel Moolenaar 		 * are directly under XSS_OPEN_INSTANCE frames so we
3971545ddfbeSMarcel Moolenaar 		 * don't need to put these in our XPath expressions.
3972545ddfbeSMarcel Moolenaar 		 */
3973545ddfbeSMarcel Moolenaar 		if (xsp->xs_state == XSS_OPEN_LIST
3974545ddfbeSMarcel Moolenaar 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3975545ddfbeSMarcel Moolenaar 		    continue;
3976545ddfbeSMarcel Moolenaar 
397731337658SMarcel Moolenaar 		xo_data_append(xop, "/", 1);
397831337658SMarcel Moolenaar 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
397931337658SMarcel Moolenaar 		if (xsp->xs_keys) {
398031337658SMarcel Moolenaar 		    /* Don't show keys for the key field */
398131337658SMarcel Moolenaar 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
398231337658SMarcel Moolenaar 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
398331337658SMarcel Moolenaar 		}
398431337658SMarcel Moolenaar 	    }
398531337658SMarcel Moolenaar 
398631337658SMarcel Moolenaar 	    xo_data_append(xop, "/", 1);
398731337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
398831337658SMarcel Moolenaar 	}
398931337658SMarcel Moolenaar 
3990d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
399131337658SMarcel Moolenaar 	    static char in_type[] = "\" data-type=\"";
399231337658SMarcel Moolenaar 	    static char in_help[] = "\" data-help=\"";
399331337658SMarcel Moolenaar 
399431337658SMarcel Moolenaar 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
399531337658SMarcel Moolenaar 	    if (xip) {
399631337658SMarcel Moolenaar 		if (xip->xi_type) {
399731337658SMarcel Moolenaar 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
399831337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
399931337658SMarcel Moolenaar 		}
400031337658SMarcel Moolenaar 		if (xip->xi_help) {
400131337658SMarcel Moolenaar 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
400231337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
400331337658SMarcel Moolenaar 		}
400431337658SMarcel Moolenaar 	    }
400531337658SMarcel Moolenaar 	}
400631337658SMarcel Moolenaar 
4007d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
400831337658SMarcel Moolenaar 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
400931337658SMarcel Moolenaar     }
401031337658SMarcel Moolenaar 
4011d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
40128a6eceffSPhil Shafer     ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp;
4013d1a0d267SMarcel Moolenaar 
401431337658SMarcel Moolenaar     xo_data_append(xop, div_end, sizeof(div_end) - 1);
401531337658SMarcel Moolenaar 
4016d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
4017d1a0d267SMarcel Moolenaar 
4018d1a0d267SMarcel Moolenaar     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4019d1a0d267SMarcel Moolenaar     save.xhs_columns = xop->xo_columns;
4020d1a0d267SMarcel Moolenaar     save.xhs_anchor_columns = xop->xo_anchor_columns;
4021d1a0d267SMarcel Moolenaar 
4022264104f2SPhil Shafer     xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4023d1a0d267SMarcel Moolenaar 
4024d1a0d267SMarcel Moolenaar     if (flags & XFF_HUMANIZE) {
4025d1a0d267SMarcel Moolenaar 	/*
4026d1a0d267SMarcel Moolenaar 	 * Unlike text style, we want to retain the original value and
4027d1a0d267SMarcel Moolenaar 	 * stuff it into the "data-number" attribute.
4028d1a0d267SMarcel Moolenaar 	 */
4029d1a0d267SMarcel Moolenaar 	static const char div_number[] = "\" data-number=\"";
40308a6eceffSPhil Shafer 	ssize_t div_len = sizeof(div_number) - 1;
4031d1a0d267SMarcel Moolenaar 
40328a6eceffSPhil Shafer 	ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
40338a6eceffSPhil Shafer 	ssize_t olen = end_offset - save.xhs_offset;
4034d1a0d267SMarcel Moolenaar 
4035d1a0d267SMarcel Moolenaar 	char *cp = alloca(olen + 1);
4036d1a0d267SMarcel Moolenaar 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
4037d1a0d267SMarcel Moolenaar 	cp[olen] = '\0';
4038d1a0d267SMarcel Moolenaar 
4039d1a0d267SMarcel Moolenaar 	xo_format_humanize(xop, xbp, &save, flags);
4040d1a0d267SMarcel Moolenaar 
4041d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, div_len + olen)) {
40428a6eceffSPhil Shafer 	    ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp;
4043d1a0d267SMarcel Moolenaar 
4044d1a0d267SMarcel Moolenaar 
4045d1a0d267SMarcel Moolenaar 	    /* Move the humanized string off to the left */
4046d1a0d267SMarcel Moolenaar 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
4047d1a0d267SMarcel Moolenaar 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
4048d1a0d267SMarcel Moolenaar 
4049d1a0d267SMarcel Moolenaar 	    /* Copy the data_number attribute name */
4050d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
4051d1a0d267SMarcel Moolenaar 
4052d1a0d267SMarcel Moolenaar 	    /* Copy the original long value */
4053d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
4054d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += div_len + olen;
4055d1a0d267SMarcel Moolenaar 	}
4056d1a0d267SMarcel Moolenaar     }
405731337658SMarcel Moolenaar 
405831337658SMarcel Moolenaar     xo_data_append(xop, div_close, sizeof(div_close) - 1);
405931337658SMarcel Moolenaar 
4060d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
406131337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
406231337658SMarcel Moolenaar }
406331337658SMarcel Moolenaar 
406431337658SMarcel Moolenaar static void
xo_format_text(xo_handle_t * xop,const char * str,ssize_t len)40658a6eceffSPhil Shafer xo_format_text (xo_handle_t *xop, const char *str, ssize_t len)
406631337658SMarcel Moolenaar {
4067788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
406831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
406931337658SMarcel Moolenaar 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
407031337658SMarcel Moolenaar 	break;
407131337658SMarcel Moolenaar 
407231337658SMarcel Moolenaar     case XO_STYLE_HTML:
4073264104f2SPhil Shafer 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0, NULL, 0);
407431337658SMarcel Moolenaar 	break;
407531337658SMarcel Moolenaar     }
407631337658SMarcel Moolenaar }
407731337658SMarcel Moolenaar 
407831337658SMarcel Moolenaar static void
xo_format_title(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)407942ff34c3SPhil Shafer xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
4080264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
408131337658SMarcel Moolenaar {
4082d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
40838a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4084d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
4085d1a0d267SMarcel Moolenaar 
4086788ca347SMarcel Moolenaar     static char div_open[] = "<div class=\"title";
4087788ca347SMarcel Moolenaar     static char div_middle[] = "\">";
408831337658SMarcel Moolenaar     static char div_close[] = "</div>";
408931337658SMarcel Moolenaar 
4090545ddfbeSMarcel Moolenaar     if (flen == 0) {
4091545ddfbeSMarcel Moolenaar 	fmt = "%s";
4092545ddfbeSMarcel Moolenaar 	flen = 2;
4093545ddfbeSMarcel Moolenaar     }
4094545ddfbeSMarcel Moolenaar 
4095788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
409631337658SMarcel Moolenaar     case XO_STYLE_XML:
409731337658SMarcel Moolenaar     case XO_STYLE_JSON:
4098d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4099d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
410031337658SMarcel Moolenaar 	/*
410131337658SMarcel Moolenaar 	 * Even though we don't care about text, we need to do
410231337658SMarcel Moolenaar 	 * enough parsing work to skip over the right bits of xo_vap.
410331337658SMarcel Moolenaar 	 */
4104264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
410531337658SMarcel Moolenaar 	return;
410631337658SMarcel Moolenaar     }
410731337658SMarcel Moolenaar 
410831337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
41098a6eceffSPhil Shafer     ssize_t start = xbp->xb_curp - xbp->xb_bufp;
41108a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - start;
41118a6eceffSPhil Shafer     ssize_t rc;
411231337658SMarcel Moolenaar 
4113788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
411431337658SMarcel Moolenaar 	xo_line_ensure_open(xop, 0);
4115d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
411631337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
411731337658SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
4118788ca347SMarcel Moolenaar 	xo_color_append_html(xop);
4119788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
412031337658SMarcel Moolenaar     }
412131337658SMarcel Moolenaar 
412231337658SMarcel Moolenaar     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
4123264104f2SPhil Shafer     if (vlen) {
412431337658SMarcel Moolenaar 	char *newfmt = alloca(flen + 1);
412531337658SMarcel Moolenaar 	memcpy(newfmt, fmt, flen);
412631337658SMarcel Moolenaar 	newfmt[flen] = '\0';
412731337658SMarcel Moolenaar 
412831337658SMarcel Moolenaar 	/* If len is non-zero, the format string apply to the name */
4129264104f2SPhil Shafer 	char *newstr = alloca(vlen + 1);
4130264104f2SPhil Shafer 	memcpy(newstr, value, vlen);
4131264104f2SPhil Shafer 	newstr[vlen] = '\0';
413231337658SMarcel Moolenaar 
4133264104f2SPhil Shafer 	if (newstr[vlen - 1] == 's') {
413431337658SMarcel Moolenaar 	    char *bp;
413531337658SMarcel Moolenaar 
413631337658SMarcel Moolenaar 	    rc = snprintf(NULL, 0, newfmt, newstr);
413731337658SMarcel Moolenaar 	    if (rc > 0) {
413831337658SMarcel Moolenaar 		/*
413931337658SMarcel Moolenaar 		 * We have to do this the hard way, since we might need
414031337658SMarcel Moolenaar 		 * the columns.
414131337658SMarcel Moolenaar 		 */
414231337658SMarcel Moolenaar 		bp = alloca(rc + 1);
414331337658SMarcel Moolenaar 		rc = snprintf(bp, rc + 1, newfmt, newstr);
4144d1a0d267SMarcel Moolenaar 
4145d1a0d267SMarcel Moolenaar 		xo_data_append_content(xop, bp, rc, flags);
414631337658SMarcel Moolenaar 	    }
414731337658SMarcel Moolenaar 	    goto move_along;
414831337658SMarcel Moolenaar 
414931337658SMarcel Moolenaar 	} else {
415031337658SMarcel Moolenaar 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4151d1a0d267SMarcel Moolenaar 	    if (rc >= left) {
415231337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, rc))
415331337658SMarcel Moolenaar 		    return;
415431337658SMarcel Moolenaar 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
415531337658SMarcel Moolenaar 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
415631337658SMarcel Moolenaar 	    }
415731337658SMarcel Moolenaar 
415831337658SMarcel Moolenaar 	    if (rc > 0) {
4159d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
416031337658SMarcel Moolenaar 		    xop->xo_columns += rc;
4161d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
416231337658SMarcel Moolenaar 		    xop->xo_anchor_columns += rc;
416331337658SMarcel Moolenaar 	    }
416431337658SMarcel Moolenaar 	}
416531337658SMarcel Moolenaar 
416631337658SMarcel Moolenaar     } else {
4167d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
416831337658SMarcel Moolenaar 
4169d1a0d267SMarcel Moolenaar 	/* xo_do_format_field moved curp, so we need to reset it */
417031337658SMarcel Moolenaar 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
417131337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + start;
417231337658SMarcel Moolenaar     }
417331337658SMarcel Moolenaar 
417431337658SMarcel Moolenaar     /* If we're styling HTML, then we need to escape it */
4175788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
417631337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
417731337658SMarcel Moolenaar     }
417831337658SMarcel Moolenaar 
417931337658SMarcel Moolenaar     if (rc > 0)
418031337658SMarcel Moolenaar 	xbp->xb_curp += rc;
418131337658SMarcel Moolenaar 
418231337658SMarcel Moolenaar  move_along:
4183788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
418431337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
4185d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
418631337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
418731337658SMarcel Moolenaar     }
418831337658SMarcel Moolenaar }
418931337658SMarcel Moolenaar 
4190983afe33SPhil Shafer /*
4191983afe33SPhil Shafer  * strspn() with a string length
4192983afe33SPhil Shafer  */
4193983afe33SPhil Shafer static ssize_t
xo_strnspn(const char * str,size_t len,const char * accept)4194983afe33SPhil Shafer xo_strnspn (const char *str, size_t len,  const char *accept)
4195983afe33SPhil Shafer {
4196983afe33SPhil Shafer     ssize_t i;
4197983afe33SPhil Shafer     const char *cp, *ep;
4198983afe33SPhil Shafer 
4199983afe33SPhil Shafer     for (i = 0, cp = str, ep = str + len; cp < ep && *cp != '\0'; i++, cp++) {
4200983afe33SPhil Shafer 	if (strchr(accept, *cp) == NULL)
4201983afe33SPhil Shafer 	    break;
4202983afe33SPhil Shafer     }
4203983afe33SPhil Shafer 
4204983afe33SPhil Shafer     return i;
4205983afe33SPhil Shafer }
4206983afe33SPhil Shafer 
4207983afe33SPhil Shafer /*
4208983afe33SPhil Shafer  * Decide if a format string should be considered "numeric",
4209983afe33SPhil Shafer  * in the sense that the number does not need to be quoted.
4210983afe33SPhil Shafer  * This means that it consists only of a single numeric field
4211983afe33SPhil Shafer  * with nothing exotic or "interesting".  This means that
4212983afe33SPhil Shafer  * static values are never considered numeric.
4213983afe33SPhil Shafer  */
4214983afe33SPhil Shafer static int
xo_format_is_numeric(const char * fmt,ssize_t flen)4215983afe33SPhil Shafer xo_format_is_numeric (const char *fmt, ssize_t flen)
4216983afe33SPhil Shafer {
4217983afe33SPhil Shafer     if (flen <= 0 || *fmt++ != '%') /* Must start with '%' */
4218983afe33SPhil Shafer 	return FALSE;
4219983afe33SPhil Shafer     flen -= 1;
4220983afe33SPhil Shafer 
4221983afe33SPhil Shafer     /* Handle leading flags; don't want "#" since JSON can't handle hex */
4222983afe33SPhil Shafer     ssize_t spn = xo_strnspn(fmt, flen, "0123456789.*+ -");
4223983afe33SPhil Shafer     if (spn >= flen)
4224983afe33SPhil Shafer 	return FALSE;
4225983afe33SPhil Shafer 
4226983afe33SPhil Shafer     fmt += spn;			/* Move along the input string */
4227983afe33SPhil Shafer     flen -= spn;
4228983afe33SPhil Shafer 
4229983afe33SPhil Shafer     /* Handle the length modifiers */
4230983afe33SPhil Shafer     spn = xo_strnspn(fmt, flen, "hljtqz");
4231983afe33SPhil Shafer     if (spn >= flen)
4232983afe33SPhil Shafer 	return FALSE;
4233983afe33SPhil Shafer 
4234983afe33SPhil Shafer     fmt += spn;			/* Move along the input string */
4235983afe33SPhil Shafer     flen -= spn;
4236983afe33SPhil Shafer 
4237983afe33SPhil Shafer     if (flen != 1)		/* Should only be one character left */
4238983afe33SPhil Shafer 	return FALSE;
4239983afe33SPhil Shafer 
4240983afe33SPhil Shafer     return (strchr("diouDOUeEfFgG", *fmt) == NULL) ? FALSE : TRUE;
4241983afe33SPhil Shafer }
4242983afe33SPhil Shafer 
4243406a584dSPhil Shafer /*
4244406a584dSPhil Shafer  * Update the stack flags using the object flags, allowing callers
4245406a584dSPhil Shafer  * to monkey with the stack flags without even knowing they exist.
4246406a584dSPhil Shafer  */
4247406a584dSPhil Shafer static void
xo_stack_set_flags(xo_handle_t * xop)4248406a584dSPhil Shafer xo_stack_set_flags (xo_handle_t *xop)
4249406a584dSPhil Shafer {
4250406a584dSPhil Shafer     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
4251406a584dSPhil Shafer 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4252406a584dSPhil Shafer 
4253406a584dSPhil Shafer 	xsp->xs_flags |= XSF_NOT_FIRST;
4254406a584dSPhil Shafer 	XOF_CLEAR(xop, XOF_NOT_FIRST);
4255406a584dSPhil Shafer     }
4256406a584dSPhil Shafer }
4257406a584dSPhil Shafer 
425831337658SMarcel Moolenaar static void
xo_format_prep(xo_handle_t * xop,xo_xff_flags_t flags)425931337658SMarcel Moolenaar xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
426031337658SMarcel Moolenaar {
426131337658SMarcel Moolenaar     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
426231337658SMarcel Moolenaar 	xo_data_append(xop, ",", 1);
4263d1a0d267SMarcel Moolenaar 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
426431337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
426531337658SMarcel Moolenaar     } else
426631337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
426731337658SMarcel Moolenaar }
426831337658SMarcel Moolenaar 
426931337658SMarcel Moolenaar #if 0
427031337658SMarcel Moolenaar /* Useful debugging function */
427131337658SMarcel Moolenaar void
427231337658SMarcel Moolenaar xo_arg (xo_handle_t *xop);
427331337658SMarcel Moolenaar void
427431337658SMarcel Moolenaar xo_arg (xo_handle_t *xop)
427531337658SMarcel Moolenaar {
427631337658SMarcel Moolenaar     xop = xo_default(xop);
427731337658SMarcel Moolenaar     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
427831337658SMarcel Moolenaar }
427931337658SMarcel Moolenaar #endif /* 0 */
428031337658SMarcel Moolenaar 
428131337658SMarcel Moolenaar static void
xo_format_value(xo_handle_t * xop,const char * name,ssize_t nlen,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,const char * encoding,ssize_t elen,xo_xff_flags_t flags)42828a6eceffSPhil Shafer xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
4283264104f2SPhil Shafer 		 const char *value, ssize_t vlen,
4284264104f2SPhil Shafer 		 const char *fmt, ssize_t flen,
42858a6eceffSPhil Shafer 		 const char *encoding, ssize_t elen, xo_xff_flags_t flags)
428631337658SMarcel Moolenaar {
4287d1a0d267SMarcel Moolenaar     int pretty = XOF_ISSET(xop, XOF_PRETTY);
428831337658SMarcel Moolenaar     int quote;
428931337658SMarcel Moolenaar 
4290545ddfbeSMarcel Moolenaar     /*
4291545ddfbeSMarcel Moolenaar      * Before we emit a value, we need to know that the frame is ready.
4292545ddfbeSMarcel Moolenaar      */
4293545ddfbeSMarcel Moolenaar     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4294545ddfbeSMarcel Moolenaar 
4295545ddfbeSMarcel Moolenaar     if (flags & XFF_LEAF_LIST) {
4296545ddfbeSMarcel Moolenaar 	/*
4297545ddfbeSMarcel Moolenaar 	 * Check if we've already started to emit normal leafs
4298545ddfbeSMarcel Moolenaar 	 * or if we're not in a leaf list.
4299545ddfbeSMarcel Moolenaar 	 */
4300545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4301545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4302545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4303545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4304545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4305545ddfbeSMarcel Moolenaar 
43068a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4307545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4308545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4309545ddfbeSMarcel Moolenaar 	    else
4310545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4311545ddfbeSMarcel Moolenaar 	}
4312545ddfbeSMarcel Moolenaar 
4313545ddfbeSMarcel Moolenaar 	xsp = &xop->xo_stack[xop->xo_depth];
4314545ddfbeSMarcel Moolenaar 	if (xsp->xs_name) {
4315545ddfbeSMarcel Moolenaar 	    name = xsp->xs_name;
4316545ddfbeSMarcel Moolenaar 	    nlen = strlen(name);
4317545ddfbeSMarcel Moolenaar 	}
4318545ddfbeSMarcel Moolenaar 
4319545ddfbeSMarcel Moolenaar     } else if (flags & XFF_KEY) {
4320545ddfbeSMarcel Moolenaar 	/* Emitting a 'k' (key) field */
4321545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4322545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4323545ddfbeSMarcel Moolenaar 		       nlen, name);
4324545ddfbeSMarcel Moolenaar 
4325545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4326545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4327545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4328545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4329545ddfbeSMarcel Moolenaar 
43308a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4331545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4332545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4333545ddfbeSMarcel Moolenaar 	    else
4334545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4335545ddfbeSMarcel Moolenaar 
4336545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4337545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT_KEY;
4338545ddfbeSMarcel Moolenaar 	}
4339545ddfbeSMarcel Moolenaar 
4340545ddfbeSMarcel Moolenaar     } else {
4341545ddfbeSMarcel Moolenaar 	/* Emitting a normal value field */
4342545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4343545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT)) {
4344545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4345545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4346545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4347545ddfbeSMarcel Moolenaar 
43488a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4349545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4350545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4351545ddfbeSMarcel Moolenaar 	    else
4352545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4353545ddfbeSMarcel Moolenaar 
4354545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4355545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT;
4356545ddfbeSMarcel Moolenaar 	}
4357545ddfbeSMarcel Moolenaar     }
4358545ddfbeSMarcel Moolenaar 
4359d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
4360d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
4361d1a0d267SMarcel Moolenaar 
4362406a584dSPhil Shafer     const char *leader = xo_xml_leader_len(xop, name, nlen);
4363406a584dSPhil Shafer 
4364788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
436531337658SMarcel Moolenaar     case XO_STYLE_TEXT:
436631337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
436731337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4368d1a0d267SMarcel Moolenaar 
4369d1a0d267SMarcel Moolenaar 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4370d1a0d267SMarcel Moolenaar 	save.xhs_columns = xop->xo_columns;
4371d1a0d267SMarcel Moolenaar 	save.xhs_anchor_columns = xop->xo_anchor_columns;
4372d1a0d267SMarcel Moolenaar 
4373264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4374d1a0d267SMarcel Moolenaar 
4375d1a0d267SMarcel Moolenaar 	if (flags & XFF_HUMANIZE)
4376d1a0d267SMarcel Moolenaar 	    xo_format_humanize(xop, xbp, &save, flags);
437731337658SMarcel Moolenaar 	break;
437831337658SMarcel Moolenaar 
437931337658SMarcel Moolenaar     case XO_STYLE_HTML:
438031337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
438131337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4382d1a0d267SMarcel Moolenaar 
4383264104f2SPhil Shafer 	xo_buf_append_div(xop, "data", flags, name, nlen, value, vlen,
4384264104f2SPhil Shafer 			  fmt, flen, encoding, elen);
438531337658SMarcel Moolenaar 	break;
438631337658SMarcel Moolenaar 
438731337658SMarcel Moolenaar     case XO_STYLE_XML:
438831337658SMarcel Moolenaar 	/*
438931337658SMarcel Moolenaar 	 * Even though we're not making output, we still need to
439031337658SMarcel Moolenaar 	 * let the formatting code handle the va_arg popping.
439131337658SMarcel Moolenaar 	 */
439231337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4393264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
439431337658SMarcel Moolenaar 	    break;
439531337658SMarcel Moolenaar 	}
439631337658SMarcel Moolenaar 
439731337658SMarcel Moolenaar 	if (encoding) {
4398264104f2SPhil Shafer    	    fmt = encoding;
439931337658SMarcel Moolenaar 	    flen = elen;
440031337658SMarcel Moolenaar 	} else {
440131337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4402264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
440331337658SMarcel Moolenaar 	    enc[flen] = '\0';
4404264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4405264104f2SPhil Shafer 	    flen = strlen(fmt);
440631337658SMarcel Moolenaar 	}
440731337658SMarcel Moolenaar 
440831337658SMarcel Moolenaar 	if (nlen == 0) {
440931337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4410264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
441131337658SMarcel Moolenaar 	    name = missing;
441231337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
441331337658SMarcel Moolenaar 	}
441431337658SMarcel Moolenaar 
441531337658SMarcel Moolenaar 	if (pretty)
441631337658SMarcel Moolenaar 	    xo_buf_indent(xop, -1);
441731337658SMarcel Moolenaar 	xo_data_append(xop, "<", 1);
4418406a584dSPhil Shafer         if (*leader)
4419406a584dSPhil Shafer             xo_data_append(xop, leader, 1);
442031337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
442131337658SMarcel Moolenaar 
442231337658SMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
442331337658SMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
442431337658SMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
442531337658SMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
442631337658SMarcel Moolenaar 	}
442731337658SMarcel Moolenaar 
442831337658SMarcel Moolenaar 	/*
442931337658SMarcel Moolenaar 	 * We indicate 'key' fields using the 'key' attribute.  While
443031337658SMarcel Moolenaar 	 * this is really committing the crime of mixing meta-data with
443131337658SMarcel Moolenaar 	 * data, it's often useful.  Especially when format meta-data is
443231337658SMarcel Moolenaar 	 * difficult to come by.
443331337658SMarcel Moolenaar 	 */
4434d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
443531337658SMarcel Moolenaar 	    static char attr[] = " key=\"key\"";
443631337658SMarcel Moolenaar 	    xo_data_append(xop, attr, sizeof(attr) - 1);
443731337658SMarcel Moolenaar 	}
443831337658SMarcel Moolenaar 
443931337658SMarcel Moolenaar 	/*
444031337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
444131337658SMarcel Moolenaar 	 */
4442d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
4443d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
444431337658SMarcel Moolenaar 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
444531337658SMarcel Moolenaar 	}
444631337658SMarcel Moolenaar 
444731337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
4448264104f2SPhil Shafer 
4449264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4450264104f2SPhil Shafer 
445131337658SMarcel Moolenaar 	xo_data_append(xop, "</", 2);
4452406a584dSPhil Shafer         if (*leader)
4453406a584dSPhil Shafer             xo_data_append(xop, leader, 1);
445431337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
445531337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
445631337658SMarcel Moolenaar 	if (pretty)
445731337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
445831337658SMarcel Moolenaar 	break;
445931337658SMarcel Moolenaar 
446031337658SMarcel Moolenaar     case XO_STYLE_JSON:
446131337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4462264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
446331337658SMarcel Moolenaar 	    break;
446431337658SMarcel Moolenaar 	}
446531337658SMarcel Moolenaar 
446631337658SMarcel Moolenaar 	if (encoding) {
4467264104f2SPhil Shafer 	    fmt = encoding;
446831337658SMarcel Moolenaar 	    flen = elen;
446931337658SMarcel Moolenaar 	} else {
447031337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4471264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
447231337658SMarcel Moolenaar 	    enc[flen] = '\0';
4473264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4474264104f2SPhil Shafer 	    flen = strlen(fmt);
447531337658SMarcel Moolenaar 	}
447631337658SMarcel Moolenaar 
4477406a584dSPhil Shafer 	xo_stack_set_flags(xop);
4478406a584dSPhil Shafer 
44798a6eceffSPhil Shafer 	int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
44808a6eceffSPhil Shafer 	    ? 0 : 1;
448131337658SMarcel Moolenaar 
448231337658SMarcel Moolenaar 	xo_format_prep(xop, flags);
448331337658SMarcel Moolenaar 
448431337658SMarcel Moolenaar 	if (flags & XFF_QUOTE)
448531337658SMarcel Moolenaar 	    quote = 1;
448631337658SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
448731337658SMarcel Moolenaar 	    quote = 0;
4488264104f2SPhil Shafer 	else if (vlen != 0)
4489264104f2SPhil Shafer 	    quote = 1;
449031337658SMarcel Moolenaar 	else if (flen == 0) {
449131337658SMarcel Moolenaar 	    quote = 0;
4492264104f2SPhil Shafer 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
449331337658SMarcel Moolenaar 	    flen = 4;
4494983afe33SPhil Shafer 	} else if (xo_format_is_numeric(fmt, flen))
449531337658SMarcel Moolenaar 	    quote = 0;
4496983afe33SPhil Shafer 	else
4497983afe33SPhil Shafer 	    quote = 1;
449831337658SMarcel Moolenaar 
449931337658SMarcel Moolenaar 	if (nlen == 0) {
450031337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4501264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
450231337658SMarcel Moolenaar 	    name = missing;
450331337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
450431337658SMarcel Moolenaar 	}
450531337658SMarcel Moolenaar 
450631337658SMarcel Moolenaar 	if (flags & XFF_LEAF_LIST) {
4507788ca347SMarcel Moolenaar 	    if (!first && pretty)
4508788ca347SMarcel Moolenaar 		xo_data_append(xop, "\n", 1);
4509788ca347SMarcel Moolenaar 	    if (pretty)
451031337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
451131337658SMarcel Moolenaar 	} else {
451231337658SMarcel Moolenaar 	    if (pretty)
451331337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
451431337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
451531337658SMarcel Moolenaar 
451631337658SMarcel Moolenaar 	    xbp = &xop->xo_data;
45178a6eceffSPhil Shafer 	    ssize_t off = xbp->xb_curp - xbp->xb_bufp;
451831337658SMarcel Moolenaar 
451931337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
452031337658SMarcel Moolenaar 
4521d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
45228a6eceffSPhil Shafer 		ssize_t coff = xbp->xb_curp - xbp->xb_bufp;
45238a6eceffSPhil Shafer 		for ( ; off < coff; off++)
452431337658SMarcel Moolenaar 		    if (xbp->xb_bufp[off] == '-')
452531337658SMarcel Moolenaar 			xbp->xb_bufp[off] = '_';
452631337658SMarcel Moolenaar 	    }
452731337658SMarcel Moolenaar 	    xo_data_append(xop, "\":", 2);
452831337658SMarcel Moolenaar 	    if (pretty)
452931337658SMarcel Moolenaar 	        xo_data_append(xop, " ", 1);
4530788ca347SMarcel Moolenaar 	}
4531788ca347SMarcel Moolenaar 
453231337658SMarcel Moolenaar 	if (quote)
453331337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
453431337658SMarcel Moolenaar 
4535264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
453631337658SMarcel Moolenaar 
453731337658SMarcel Moolenaar 	if (quote)
453831337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
453931337658SMarcel Moolenaar 	break;
4540d1a0d267SMarcel Moolenaar 
4541d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4542d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4543264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4544d1a0d267SMarcel Moolenaar 	    break;
4545d1a0d267SMarcel Moolenaar 	}
4546d1a0d267SMarcel Moolenaar 
4547d1a0d267SMarcel Moolenaar 	if (encoding) {
4548264104f2SPhil Shafer 	    fmt = encoding;
4549d1a0d267SMarcel Moolenaar 	    flen = elen;
4550d1a0d267SMarcel Moolenaar 	} else {
4551d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4552264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
4553d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4554264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4555264104f2SPhil Shafer 	    flen = strlen(fmt);
4556d1a0d267SMarcel Moolenaar 	}
4557d1a0d267SMarcel Moolenaar 
4558d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4559d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4560264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
4561d1a0d267SMarcel Moolenaar 	    name = missing;
4562d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4563d1a0d267SMarcel Moolenaar 	}
4564d1a0d267SMarcel Moolenaar 
4565d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
4566d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "=\"", 2);
4567264104f2SPhil Shafer 
4568264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4569264104f2SPhil Shafer 
4570d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "\" ", 2);
4571d1a0d267SMarcel Moolenaar 	break;
4572d1a0d267SMarcel Moolenaar 
4573d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4574d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4575264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4576d1a0d267SMarcel Moolenaar 	    break;
4577d1a0d267SMarcel Moolenaar 	}
4578d1a0d267SMarcel Moolenaar 
4579d1a0d267SMarcel Moolenaar 	if (flags & XFF_QUOTE)
4580d1a0d267SMarcel Moolenaar 	    quote = 1;
4581d1a0d267SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
4582d1a0d267SMarcel Moolenaar 	    quote = 0;
4583d1a0d267SMarcel Moolenaar 	else if (flen == 0) {
4584d1a0d267SMarcel Moolenaar 	    quote = 0;
4585264104f2SPhil Shafer 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
4586d1a0d267SMarcel Moolenaar 	    flen = 4;
4587264104f2SPhil Shafer 	} else if (strchr("diouxXDOUeEfFgGaAcCp", fmt[flen - 1]) == NULL)
4588d1a0d267SMarcel Moolenaar 	    quote = 1;
4589d1a0d267SMarcel Moolenaar 	else
4590d1a0d267SMarcel Moolenaar 	    quote = 0;
4591d1a0d267SMarcel Moolenaar 
4592d1a0d267SMarcel Moolenaar 	if (encoding) {
4593264104f2SPhil Shafer 	    fmt = encoding;
4594d1a0d267SMarcel Moolenaar 	    flen = elen;
4595d1a0d267SMarcel Moolenaar 	} else {
4596d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4597264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
4598d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4599264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4600264104f2SPhil Shafer 	    flen = strlen(fmt);
4601d1a0d267SMarcel Moolenaar 	}
4602d1a0d267SMarcel Moolenaar 
4603d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4604d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4605264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
4606d1a0d267SMarcel Moolenaar 	    name = missing;
4607d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4608d1a0d267SMarcel Moolenaar 	}
4609d1a0d267SMarcel Moolenaar 
46108a6eceffSPhil Shafer 	ssize_t name_offset = xo_buf_offset(&xop->xo_data);
4611d1a0d267SMarcel Moolenaar 	xo_data_append(xop, name, nlen);
4612d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4613d1a0d267SMarcel Moolenaar 
46148a6eceffSPhil Shafer 	ssize_t value_offset = xo_buf_offset(&xop->xo_data);
4615264104f2SPhil Shafer 
4616264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4617264104f2SPhil Shafer 
4618d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4619d1a0d267SMarcel Moolenaar 
4620d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4621d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, name_offset),
4622f2b7bf8aSPhil Shafer 			  xo_buf_data(&xop->xo_data, value_offset), flags);
4623d1a0d267SMarcel Moolenaar 	xo_buf_reset(&xop->xo_data);
4624d1a0d267SMarcel Moolenaar 	break;
462531337658SMarcel Moolenaar     }
462631337658SMarcel Moolenaar }
462731337658SMarcel Moolenaar 
462831337658SMarcel Moolenaar static void
xo_set_gettext_domain(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,ssize_t len)462942ff34c3SPhil Shafer xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
46308a6eceffSPhil Shafer 		       const char *str, ssize_t len)
4631d1a0d267SMarcel Moolenaar {
4632d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
46338a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4634d1a0d267SMarcel Moolenaar 
4635d1a0d267SMarcel Moolenaar     /* Start by discarding previous domain */
4636d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
4637d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
4638d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
4639d1a0d267SMarcel Moolenaar     }
4640d1a0d267SMarcel Moolenaar 
4641d1a0d267SMarcel Moolenaar     /* An empty {G:} means no domainname */
4642d1a0d267SMarcel Moolenaar     if (len == 0 && flen == 0)
4643d1a0d267SMarcel Moolenaar 	return;
4644d1a0d267SMarcel Moolenaar 
46458a6eceffSPhil Shafer     ssize_t start_offset = -1;
4646d1a0d267SMarcel Moolenaar     if (len == 0 && flen != 0) {
4647d1a0d267SMarcel Moolenaar 	/* Need to do format the data to get the domainname from args */
4648d1a0d267SMarcel Moolenaar 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4649d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4650d1a0d267SMarcel Moolenaar 
46518a6eceffSPhil Shafer 	ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4652d1a0d267SMarcel Moolenaar 	len = end_offset - start_offset;
4653d1a0d267SMarcel Moolenaar 	str = xop->xo_data.xb_bufp + start_offset;
4654d1a0d267SMarcel Moolenaar     }
4655d1a0d267SMarcel Moolenaar 
4656d1a0d267SMarcel Moolenaar     xop->xo_gt_domain = xo_strndup(str, len);
4657d1a0d267SMarcel Moolenaar 
4658d1a0d267SMarcel Moolenaar     /* Reset the current buffer point to avoid emitting the name as output */
4659d1a0d267SMarcel Moolenaar     if (start_offset >= 0)
4660d1a0d267SMarcel Moolenaar 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4661d1a0d267SMarcel Moolenaar }
4662d1a0d267SMarcel Moolenaar 
4663d1a0d267SMarcel Moolenaar static void
xo_format_content(xo_handle_t * xop,const char * class_name,const char * tag_name,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,xo_xff_flags_t flags)466431337658SMarcel Moolenaar xo_format_content (xo_handle_t *xop, const char *class_name,
4665d1a0d267SMarcel Moolenaar 		   const char *tag_name,
4666264104f2SPhil Shafer 		   const char *value, ssize_t vlen,
4667264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
4668d1a0d267SMarcel Moolenaar 		   xo_xff_flags_t flags)
466931337658SMarcel Moolenaar {
4670788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
467131337658SMarcel Moolenaar     case XO_STYLE_TEXT:
4672264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
467331337658SMarcel Moolenaar 	break;
467431337658SMarcel Moolenaar 
467531337658SMarcel Moolenaar     case XO_STYLE_HTML:
4676264104f2SPhil Shafer 	xo_buf_append_div(xop, class_name, flags, NULL, 0,
4677264104f2SPhil Shafer 			  value, vlen, fmt, flen, NULL, 0);
467831337658SMarcel Moolenaar 	break;
467931337658SMarcel Moolenaar 
468031337658SMarcel Moolenaar     case XO_STYLE_XML:
4681d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
4682d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4683d1a0d267SMarcel Moolenaar 	if (tag_name) {
4684d1a0d267SMarcel Moolenaar 	    xo_open_container_h(xop, tag_name);
4685264104f2SPhil Shafer 	    xo_format_value(xop, "message", 7, value, vlen,
4686264104f2SPhil Shafer 			    fmt, flen, NULL, 0, flags);
4687d1a0d267SMarcel Moolenaar 	    xo_close_container_h(xop, tag_name);
468831337658SMarcel Moolenaar 
468931337658SMarcel Moolenaar 	} else {
469031337658SMarcel Moolenaar 	    /*
469131337658SMarcel Moolenaar 	     * Even though we don't care about labels, we need to do
469231337658SMarcel Moolenaar 	     * enough parsing work to skip over the right bits of xo_vap.
469331337658SMarcel Moolenaar 	     */
4694264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
469531337658SMarcel Moolenaar 	}
469631337658SMarcel Moolenaar 	break;
469731337658SMarcel Moolenaar 
4698d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4699264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
470031337658SMarcel Moolenaar 	break;
470131337658SMarcel Moolenaar     }
470231337658SMarcel Moolenaar }
470331337658SMarcel Moolenaar 
4704788ca347SMarcel Moolenaar static const char *xo_color_names[] = {
4705788ca347SMarcel Moolenaar     "default",	/* XO_COL_DEFAULT */
4706788ca347SMarcel Moolenaar     "black",	/* XO_COL_BLACK */
4707788ca347SMarcel Moolenaar     "red",	/* XO_CLOR_RED */
4708788ca347SMarcel Moolenaar     "green",	/* XO_COL_GREEN */
4709788ca347SMarcel Moolenaar     "yellow",	/* XO_COL_YELLOW */
4710788ca347SMarcel Moolenaar     "blue",	/* XO_COL_BLUE */
4711788ca347SMarcel Moolenaar     "magenta",	/* XO_COL_MAGENTA */
4712788ca347SMarcel Moolenaar     "cyan",	/* XO_COL_CYAN */
4713788ca347SMarcel Moolenaar     "white",	/* XO_COL_WHITE */
4714788ca347SMarcel Moolenaar     NULL
4715788ca347SMarcel Moolenaar };
4716788ca347SMarcel Moolenaar 
4717788ca347SMarcel Moolenaar static int
xo_color_find(const char * str)4718788ca347SMarcel Moolenaar xo_color_find (const char *str)
4719788ca347SMarcel Moolenaar {
4720788ca347SMarcel Moolenaar     int i;
4721788ca347SMarcel Moolenaar 
4722788ca347SMarcel Moolenaar     for (i = 0; xo_color_names[i]; i++) {
472376afb20cSPhil Shafer 	if (xo_streq(xo_color_names[i], str))
4724788ca347SMarcel Moolenaar 	    return i;
4725788ca347SMarcel Moolenaar     }
4726788ca347SMarcel Moolenaar 
4727788ca347SMarcel Moolenaar     return -1;
4728788ca347SMarcel Moolenaar }
4729788ca347SMarcel Moolenaar 
4730788ca347SMarcel Moolenaar static const char *xo_effect_names[] = {
4731788ca347SMarcel Moolenaar     "reset",			/* XO_EFF_RESET */
4732788ca347SMarcel Moolenaar     "normal",			/* XO_EFF_NORMAL */
4733788ca347SMarcel Moolenaar     "bold",			/* XO_EFF_BOLD */
4734788ca347SMarcel Moolenaar     "underline",		/* XO_EFF_UNDERLINE */
4735788ca347SMarcel Moolenaar     "inverse",			/* XO_EFF_INVERSE */
4736788ca347SMarcel Moolenaar     NULL
4737788ca347SMarcel Moolenaar };
4738788ca347SMarcel Moolenaar 
4739788ca347SMarcel Moolenaar static const char *xo_effect_on_codes[] = {
4740788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4741788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4742788ca347SMarcel Moolenaar     "1",			/* XO_EFF_BOLD */
4743788ca347SMarcel Moolenaar     "4",			/* XO_EFF_UNDERLINE */
4744788ca347SMarcel Moolenaar     "7",			/* XO_EFF_INVERSE */
4745788ca347SMarcel Moolenaar     NULL
4746788ca347SMarcel Moolenaar };
4747788ca347SMarcel Moolenaar 
4748788ca347SMarcel Moolenaar #if 0
4749788ca347SMarcel Moolenaar /*
4750788ca347SMarcel Moolenaar  * See comment below re: joy of terminal standards.  These can
4751788ca347SMarcel Moolenaar  * be use by just adding:
4752d1a0d267SMarcel Moolenaar  * +	if (newp->xoc_effects & bit)
4753788ca347SMarcel Moolenaar  *	    code = xo_effect_on_codes[i];
4754788ca347SMarcel Moolenaar  * +	else
4755788ca347SMarcel Moolenaar  * +	    code = xo_effect_off_codes[i];
4756788ca347SMarcel Moolenaar  * in xo_color_handle_text.
4757788ca347SMarcel Moolenaar  */
4758788ca347SMarcel Moolenaar static const char *xo_effect_off_codes[] = {
4759788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4760788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4761788ca347SMarcel Moolenaar     "21",			/* XO_EFF_BOLD */
4762788ca347SMarcel Moolenaar     "24",			/* XO_EFF_UNDERLINE */
4763788ca347SMarcel Moolenaar     "27",			/* XO_EFF_INVERSE */
4764788ca347SMarcel Moolenaar     NULL
4765788ca347SMarcel Moolenaar };
4766788ca347SMarcel Moolenaar #endif /* 0 */
4767788ca347SMarcel Moolenaar 
4768788ca347SMarcel Moolenaar static int
xo_effect_find(const char * str)4769788ca347SMarcel Moolenaar xo_effect_find (const char *str)
4770788ca347SMarcel Moolenaar {
4771788ca347SMarcel Moolenaar     int i;
4772788ca347SMarcel Moolenaar 
4773788ca347SMarcel Moolenaar     for (i = 0; xo_effect_names[i]; i++) {
477476afb20cSPhil Shafer 	if (xo_streq(xo_effect_names[i], str))
4775788ca347SMarcel Moolenaar 	    return i;
4776788ca347SMarcel Moolenaar     }
4777788ca347SMarcel Moolenaar 
4778788ca347SMarcel Moolenaar     return -1;
4779788ca347SMarcel Moolenaar }
4780788ca347SMarcel Moolenaar 
4781788ca347SMarcel Moolenaar static void
xo_colors_parse(xo_handle_t * xop,xo_colors_t * xocp,char * str)4782788ca347SMarcel Moolenaar xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4783788ca347SMarcel Moolenaar {
4784406a584dSPhil Shafer     if (xo_text_only())
4785788ca347SMarcel Moolenaar 	return;
4786788ca347SMarcel Moolenaar 
4787788ca347SMarcel Moolenaar     char *cp, *ep, *np, *xp;
47888a6eceffSPhil Shafer     ssize_t len = strlen(str);
4789788ca347SMarcel Moolenaar     int rc;
4790788ca347SMarcel Moolenaar 
4791788ca347SMarcel Moolenaar     /*
4792788ca347SMarcel Moolenaar      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4793788ca347SMarcel Moolenaar      */
4794788ca347SMarcel Moolenaar     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4795788ca347SMarcel Moolenaar 	/* Trim leading whitespace */
4796788ca347SMarcel Moolenaar 	while (isspace((int) *cp))
4797788ca347SMarcel Moolenaar 	    cp += 1;
4798788ca347SMarcel Moolenaar 
4799788ca347SMarcel Moolenaar 	np = strchr(cp, ',');
4800788ca347SMarcel Moolenaar 	if (np)
4801788ca347SMarcel Moolenaar 	    *np++ = '\0';
4802788ca347SMarcel Moolenaar 
4803788ca347SMarcel Moolenaar 	/* Trim trailing whitespace */
4804788ca347SMarcel Moolenaar 	xp = cp + strlen(cp) - 1;
4805788ca347SMarcel Moolenaar 	while (isspace(*xp) && xp > cp)
4806788ca347SMarcel Moolenaar 	    *xp-- = '\0';
4807788ca347SMarcel Moolenaar 
4808788ca347SMarcel Moolenaar 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4809788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4810788ca347SMarcel Moolenaar 	    if (rc < 0)
4811788ca347SMarcel Moolenaar 		goto unknown;
4812788ca347SMarcel Moolenaar 
4813788ca347SMarcel Moolenaar 	    xocp->xoc_col_fg = rc;
4814788ca347SMarcel Moolenaar 
4815788ca347SMarcel Moolenaar 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4816788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4817788ca347SMarcel Moolenaar 	    if (rc < 0)
4818788ca347SMarcel Moolenaar 		goto unknown;
4819788ca347SMarcel Moolenaar 	    xocp->xoc_col_bg = rc;
4820788ca347SMarcel Moolenaar 
4821788ca347SMarcel Moolenaar 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4822788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp + 3);
4823788ca347SMarcel Moolenaar 	    if (rc < 0)
4824788ca347SMarcel Moolenaar 		goto unknown;
4825788ca347SMarcel Moolenaar 	    xocp->xoc_effects &= ~(1 << rc);
4826788ca347SMarcel Moolenaar 
4827788ca347SMarcel Moolenaar 	} else {
4828788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp);
4829788ca347SMarcel Moolenaar 	    if (rc < 0)
4830788ca347SMarcel Moolenaar 		goto unknown;
4831788ca347SMarcel Moolenaar 	    xocp->xoc_effects |= 1 << rc;
4832788ca347SMarcel Moolenaar 
4833788ca347SMarcel Moolenaar 	    switch (1 << rc) {
4834788ca347SMarcel Moolenaar 	    case XO_EFF_RESET:
4835788ca347SMarcel Moolenaar 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4836788ca347SMarcel Moolenaar 		/* Note: not "|=" since we want to wipe out the old value */
4837788ca347SMarcel Moolenaar 		xocp->xoc_effects = XO_EFF_RESET;
4838788ca347SMarcel Moolenaar 		break;
4839788ca347SMarcel Moolenaar 
4840788ca347SMarcel Moolenaar 	    case XO_EFF_NORMAL:
4841788ca347SMarcel Moolenaar 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4842788ca347SMarcel Moolenaar 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4843788ca347SMarcel Moolenaar 		break;
4844788ca347SMarcel Moolenaar 	    }
4845788ca347SMarcel Moolenaar 	}
4846788ca347SMarcel Moolenaar 	continue;
4847788ca347SMarcel Moolenaar 
4848788ca347SMarcel Moolenaar     unknown:
4849d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN))
4850788ca347SMarcel Moolenaar 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4851788ca347SMarcel Moolenaar     }
4852788ca347SMarcel Moolenaar }
4853788ca347SMarcel Moolenaar 
4854788ca347SMarcel Moolenaar static inline int
xo_colors_enabled(xo_handle_t * xop UNUSED)4855788ca347SMarcel Moolenaar xo_colors_enabled (xo_handle_t *xop UNUSED)
4856788ca347SMarcel Moolenaar {
4857788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4858788ca347SMarcel Moolenaar     return 0;
4859788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
4860d1a0d267SMarcel Moolenaar     return XOF_ISSET(xop, XOF_COLOR);
4861788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4862788ca347SMarcel Moolenaar }
4863788ca347SMarcel Moolenaar 
4864f2b7bf8aSPhil Shafer /*
4865f2b7bf8aSPhil Shafer  * If the color map is in use (--libxo colors=xxxx), then update
4866f2b7bf8aSPhil Shafer  * the incoming foreground and background colors from the map.
4867f2b7bf8aSPhil Shafer  */
4868f2b7bf8aSPhil Shafer static void
xo_colors_update(xo_handle_t * xop UNUSED,xo_colors_t * newp UNUSED)4869406a584dSPhil Shafer xo_colors_update (xo_handle_t *xop UNUSED, xo_colors_t *newp UNUSED)
4870f2b7bf8aSPhil Shafer {
4871406a584dSPhil Shafer #ifndef LIBXO_TEXT_ONLY
4872f2b7bf8aSPhil Shafer     xo_color_t fg = newp->xoc_col_fg;
4873f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS)
4874f2b7bf8aSPhil Shafer 	fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */
4875f2b7bf8aSPhil Shafer     newp->xoc_col_fg = fg;
4876f2b7bf8aSPhil Shafer 
4877f2b7bf8aSPhil Shafer     xo_color_t bg = newp->xoc_col_bg;
4878f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS)
4879f2b7bf8aSPhil Shafer 	bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */
4880f2b7bf8aSPhil Shafer     newp->xoc_col_bg = bg;
4881406a584dSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
4882f2b7bf8aSPhil Shafer }
4883f2b7bf8aSPhil Shafer 
4884788ca347SMarcel Moolenaar static void
xo_colors_handle_text(xo_handle_t * xop,xo_colors_t * newp)488542ff34c3SPhil Shafer xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4886788ca347SMarcel Moolenaar {
4887788ca347SMarcel Moolenaar     char buf[BUFSIZ];
4888788ca347SMarcel Moolenaar     char *cp = buf, *ep = buf + sizeof(buf);
4889788ca347SMarcel Moolenaar     unsigned i, bit;
4890788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
489142ff34c3SPhil Shafer     const char *code = NULL;
4892788ca347SMarcel Moolenaar 
4893788ca347SMarcel Moolenaar     /*
4894788ca347SMarcel Moolenaar      * Start the buffer with an escape.  We don't want to add the '['
4895788ca347SMarcel Moolenaar      * now, since we let xo_effect_text_add unconditionally add the ';'.
4896788ca347SMarcel Moolenaar      * We'll replace the first ';' with a '[' when we're done.
4897788ca347SMarcel Moolenaar      */
4898788ca347SMarcel Moolenaar     *cp++ = 0x1b;		/* Escape */
4899788ca347SMarcel Moolenaar 
4900788ca347SMarcel Moolenaar     /*
4901788ca347SMarcel Moolenaar      * Terminals were designed back in the age before "certainty" was
4902788ca347SMarcel Moolenaar      * invented, when standards were more what you'd call "guidelines"
4903788ca347SMarcel Moolenaar      * than actual rules.  Anyway we can't depend on them to operate
4904788ca347SMarcel Moolenaar      * correctly.  So when display attributes are changed, we punt,
4905788ca347SMarcel Moolenaar      * reseting them all and turning back on the ones we want to keep.
4906788ca347SMarcel Moolenaar      * Longer, but should be completely reliable.  Savvy?
4907788ca347SMarcel Moolenaar      */
4908788ca347SMarcel Moolenaar     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4909788ca347SMarcel Moolenaar 	newp->xoc_effects |= XO_EFF_RESET;
4910788ca347SMarcel Moolenaar 	oldp->xoc_effects = 0;
4911788ca347SMarcel Moolenaar     }
4912788ca347SMarcel Moolenaar 
4913788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4914788ca347SMarcel Moolenaar 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4915788ca347SMarcel Moolenaar 	    continue;
4916788ca347SMarcel Moolenaar 
4917788ca347SMarcel Moolenaar 	code = xo_effect_on_codes[i];
4918788ca347SMarcel Moolenaar 
4919788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";%s", code);
4920788ca347SMarcel Moolenaar 	if (cp >= ep)
4921788ca347SMarcel Moolenaar 	    return;		/* Should not occur */
4922788ca347SMarcel Moolenaar 
4923788ca347SMarcel Moolenaar 	if (bit == XO_EFF_RESET) {
4924788ca347SMarcel Moolenaar 	    /* Mark up the old value so we can detect current values as new */
4925788ca347SMarcel Moolenaar 	    oldp->xoc_effects = 0;
4926788ca347SMarcel Moolenaar 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4927788ca347SMarcel Moolenaar 	}
4928788ca347SMarcel Moolenaar     }
4929788ca347SMarcel Moolenaar 
4930f2b7bf8aSPhil Shafer     xo_color_t fg = newp->xoc_col_fg;
4931f2b7bf8aSPhil Shafer     if (fg != oldp->xoc_col_fg) {
4932788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";3%u",
4933f2b7bf8aSPhil Shafer 		       (fg != XO_COL_DEFAULT) ? fg - 1 : 9);
4934788ca347SMarcel Moolenaar     }
4935788ca347SMarcel Moolenaar 
4936f2b7bf8aSPhil Shafer     xo_color_t bg = newp->xoc_col_bg;
4937f2b7bf8aSPhil Shafer     if (bg != oldp->xoc_col_bg) {
4938788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";4%u",
4939f2b7bf8aSPhil Shafer 		       (bg != XO_COL_DEFAULT) ? bg - 1 : 9);
4940788ca347SMarcel Moolenaar     }
4941788ca347SMarcel Moolenaar 
4942788ca347SMarcel Moolenaar     if (cp - buf != 1 && cp < ep - 3) {
4943788ca347SMarcel Moolenaar 	buf[1] = '[';		/* Overwrite leading ';' */
4944788ca347SMarcel Moolenaar 	*cp++ = 'm';
4945788ca347SMarcel Moolenaar 	*cp = '\0';
4946788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4947788ca347SMarcel Moolenaar     }
4948788ca347SMarcel Moolenaar }
4949788ca347SMarcel Moolenaar 
4950788ca347SMarcel Moolenaar static void
xo_colors_handle_html(xo_handle_t * xop,xo_colors_t * newp)4951788ca347SMarcel Moolenaar xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4952788ca347SMarcel Moolenaar {
4953788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
4954788ca347SMarcel Moolenaar 
4955788ca347SMarcel Moolenaar     /*
4956788ca347SMarcel Moolenaar      * HTML colors are mostly trivial: fill in xo_color_buf with
4957788ca347SMarcel Moolenaar      * a set of class tags representing the colors and effects.
4958788ca347SMarcel Moolenaar      */
4959788ca347SMarcel Moolenaar 
4960788ca347SMarcel Moolenaar     /* If nothing changed, then do nothing */
4961788ca347SMarcel Moolenaar     if (oldp->xoc_effects == newp->xoc_effects
4962788ca347SMarcel Moolenaar 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4963788ca347SMarcel Moolenaar 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4964788ca347SMarcel Moolenaar 	return;
4965788ca347SMarcel Moolenaar 
4966788ca347SMarcel Moolenaar     unsigned i, bit;
4967788ca347SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_color_buf;
4968788ca347SMarcel Moolenaar 
4969788ca347SMarcel Moolenaar     xo_buf_reset(xbp);		/* We rebuild content after each change */
4970788ca347SMarcel Moolenaar 
4971788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4972788ca347SMarcel Moolenaar 	if (!(newp->xoc_effects & bit))
4973788ca347SMarcel Moolenaar 	    continue;
4974788ca347SMarcel Moolenaar 
4975788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " effect-");
4976788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, xo_effect_names[i]);
4977788ca347SMarcel Moolenaar     }
4978788ca347SMarcel Moolenaar 
4979788ca347SMarcel Moolenaar     const char *fg = NULL;
4980788ca347SMarcel Moolenaar     const char *bg = NULL;
4981788ca347SMarcel Moolenaar 
4982788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4983788ca347SMarcel Moolenaar 	fg = xo_color_names[newp->xoc_col_fg];
4984788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4985788ca347SMarcel Moolenaar 	bg = xo_color_names[newp->xoc_col_bg];
4986788ca347SMarcel Moolenaar 
4987788ca347SMarcel Moolenaar     if (newp->xoc_effects & XO_EFF_INVERSE) {
4988788ca347SMarcel Moolenaar 	const char *tmp = fg;
4989788ca347SMarcel Moolenaar 	fg = bg;
4990788ca347SMarcel Moolenaar 	bg = tmp;
4991788ca347SMarcel Moolenaar 	if (fg == NULL)
4992788ca347SMarcel Moolenaar 	    fg = "inverse";
4993788ca347SMarcel Moolenaar 	if (bg == NULL)
4994788ca347SMarcel Moolenaar 	    bg = "inverse";
4995788ca347SMarcel Moolenaar 
4996788ca347SMarcel Moolenaar     }
4997788ca347SMarcel Moolenaar 
4998788ca347SMarcel Moolenaar     if (fg) {
4999788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-fg-");
5000788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, fg);
5001788ca347SMarcel Moolenaar     }
5002788ca347SMarcel Moolenaar 
5003788ca347SMarcel Moolenaar     if (bg) {
5004788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-bg-");
5005788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, bg);
5006788ca347SMarcel Moolenaar     }
5007788ca347SMarcel Moolenaar }
5008788ca347SMarcel Moolenaar 
5009788ca347SMarcel Moolenaar static void
xo_format_colors(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)501042ff34c3SPhil Shafer xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
5011264104f2SPhil Shafer 		  const char *value, ssize_t vlen)
5012788ca347SMarcel Moolenaar {
5013d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
50148a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
5015d1a0d267SMarcel Moolenaar 
5016788ca347SMarcel Moolenaar     xo_buffer_t xb;
5017788ca347SMarcel Moolenaar 
5018788ca347SMarcel Moolenaar     /* If the string is static and we've in an encoding style, bail */
5019264104f2SPhil Shafer     if (vlen != 0 && xo_style_is_encoding(xop))
5020788ca347SMarcel Moolenaar 	return;
5021788ca347SMarcel Moolenaar 
5022788ca347SMarcel Moolenaar     xo_buf_init(&xb);
5023788ca347SMarcel Moolenaar 
5024264104f2SPhil Shafer     if (vlen)
5025264104f2SPhil Shafer 	xo_buf_append(&xb, value, vlen);
5026788ca347SMarcel Moolenaar     else if (flen)
5027d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, &xb, fmt, flen, 0);
5028788ca347SMarcel Moolenaar     else
5029788ca347SMarcel Moolenaar 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
5030788ca347SMarcel Moolenaar 
5031788ca347SMarcel Moolenaar     if (xo_colors_enabled(xop)) {
5032788ca347SMarcel Moolenaar 	switch (xo_style(xop)) {
5033788ca347SMarcel Moolenaar 	case XO_STYLE_TEXT:
5034788ca347SMarcel Moolenaar 	case XO_STYLE_HTML:
5035788ca347SMarcel Moolenaar 	    xo_buf_append(&xb, "", 1);
5036788ca347SMarcel Moolenaar 
5037788ca347SMarcel Moolenaar 	    xo_colors_t xoc = xop->xo_colors;
5038788ca347SMarcel Moolenaar 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
5039f2b7bf8aSPhil Shafer 	    xo_colors_update(xop, &xoc);
5040788ca347SMarcel Moolenaar 
5041788ca347SMarcel Moolenaar 	    if (xo_style(xop) == XO_STYLE_TEXT) {
5042788ca347SMarcel Moolenaar 		/*
5043788ca347SMarcel Moolenaar 		 * Text mode means emitting the colors as ANSI character
5044788ca347SMarcel Moolenaar 		 * codes.  This will allow people who like colors to have
5045788ca347SMarcel Moolenaar 		 * colors.  The issue is, of course conflicting with the
5046788ca347SMarcel Moolenaar 		 * user's perfectly reasonable color scheme.  Which leads
5047788ca347SMarcel Moolenaar 		 * to the hell of LSCOLORS, where even app need to have
5048788ca347SMarcel Moolenaar 		 * customization hooks for adjusting colors.  Instead we
5049788ca347SMarcel Moolenaar 		 * provide a simpler-but-still-annoying answer where one
5050788ca347SMarcel Moolenaar 		 * can map colors to other colors.
5051788ca347SMarcel Moolenaar 		 */
5052788ca347SMarcel Moolenaar 		xo_colors_handle_text(xop, &xoc);
5053788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
5054788ca347SMarcel Moolenaar 
5055788ca347SMarcel Moolenaar 	    } else {
5056788ca347SMarcel Moolenaar 		/*
5057788ca347SMarcel Moolenaar 		 * HTML output is wrapped in divs, so the color information
5058788ca347SMarcel Moolenaar 		 * must appear in every div until cleared.  Most pathetic.
5059788ca347SMarcel Moolenaar 		 * Most unavoidable.
5060788ca347SMarcel Moolenaar 		 */
5061788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
5062788ca347SMarcel Moolenaar 		xo_colors_handle_html(xop, &xoc);
5063788ca347SMarcel Moolenaar 	    }
5064788ca347SMarcel Moolenaar 
5065788ca347SMarcel Moolenaar 	    xop->xo_colors = xoc;
5066788ca347SMarcel Moolenaar 	    break;
5067788ca347SMarcel Moolenaar 
5068788ca347SMarcel Moolenaar 	case XO_STYLE_XML:
5069788ca347SMarcel Moolenaar 	case XO_STYLE_JSON:
5070d1a0d267SMarcel Moolenaar 	case XO_STYLE_SDPARAMS:
5071d1a0d267SMarcel Moolenaar 	case XO_STYLE_ENCODER:
5072788ca347SMarcel Moolenaar 	    /*
5073788ca347SMarcel Moolenaar 	     * Nothing to do; we did all that work just to clear the stack of
5074788ca347SMarcel Moolenaar 	     * formatting arguments.
5075788ca347SMarcel Moolenaar 	     */
5076788ca347SMarcel Moolenaar 	    break;
5077788ca347SMarcel Moolenaar 	}
5078788ca347SMarcel Moolenaar     }
5079788ca347SMarcel Moolenaar 
5080788ca347SMarcel Moolenaar     xo_buf_cleanup(&xb);
5081788ca347SMarcel Moolenaar }
5082788ca347SMarcel Moolenaar 
508331337658SMarcel Moolenaar static void
xo_format_units(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)508442ff34c3SPhil Shafer xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
5085264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
508631337658SMarcel Moolenaar {
5087d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
50888a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
5089d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
5090d1a0d267SMarcel Moolenaar 
509131337658SMarcel Moolenaar     static char units_start_xml[] = " units=\"";
509231337658SMarcel Moolenaar     static char units_start_html[] = " data-units=\"";
509331337658SMarcel Moolenaar 
5094d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
5095264104f2SPhil Shafer 	xo_format_content(xop, "units", NULL, value, vlen, fmt, flen, flags);
509631337658SMarcel Moolenaar 	return;
509731337658SMarcel Moolenaar     }
509831337658SMarcel Moolenaar 
509931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
51008a6eceffSPhil Shafer     ssize_t start = xop->xo_units_offset;
51018a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
510231337658SMarcel Moolenaar 
5103788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML)
510431337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
5105788ca347SMarcel Moolenaar     else if (xo_style(xop) == XO_STYLE_HTML)
510631337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
510731337658SMarcel Moolenaar     else
510831337658SMarcel Moolenaar 	return;
510931337658SMarcel Moolenaar 
5110264104f2SPhil Shafer     if (vlen)
5111264104f2SPhil Shafer 	xo_data_escape(xop, value, vlen);
511231337658SMarcel Moolenaar     else
5113d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
511431337658SMarcel Moolenaar 
511531337658SMarcel Moolenaar     xo_buf_append(xbp, "\"", 1);
511631337658SMarcel Moolenaar 
51178a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
51188a6eceffSPhil Shafer     ssize_t delta = now - stop;
5119d1a0d267SMarcel Moolenaar     if (delta <= 0) {		/* Strange; no output to move */
512031337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
512131337658SMarcel Moolenaar 	return;
512231337658SMarcel Moolenaar     }
512331337658SMarcel Moolenaar 
512431337658SMarcel Moolenaar     /*
512531337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the unit value
512631337658SMarcel Moolenaar      * we just created into the right spot.  We make a local copy,
512731337658SMarcel Moolenaar      * move it and then insert our copy.  We know there's room in the
512831337658SMarcel Moolenaar      * buffer, since we're just moving this around.
512931337658SMarcel Moolenaar      */
513031337658SMarcel Moolenaar     char *buf = alloca(delta);
513131337658SMarcel Moolenaar 
513231337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
513331337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
513431337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
513531337658SMarcel Moolenaar }
513631337658SMarcel Moolenaar 
51378a6eceffSPhil Shafer static ssize_t
xo_find_width(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)513842ff34c3SPhil Shafer xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
5139264104f2SPhil Shafer 	       const char *value, ssize_t vlen)
514031337658SMarcel Moolenaar {
5141d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
51428a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
5143d1a0d267SMarcel Moolenaar 
514431337658SMarcel Moolenaar     long width = 0;
514531337658SMarcel Moolenaar     char *bp;
514631337658SMarcel Moolenaar     char *cp;
514731337658SMarcel Moolenaar 
5148264104f2SPhil Shafer     if (vlen) {
5149264104f2SPhil Shafer 	bp = alloca(vlen + 1);	/* Make local NUL-terminated copy of value */
5150264104f2SPhil Shafer 	memcpy(bp, value, vlen);
5151264104f2SPhil Shafer 	bp[vlen] = '\0';
515231337658SMarcel Moolenaar 
515331337658SMarcel Moolenaar 	width = strtol(bp, &cp, 0);
5154fd5e3f3eSPhil Shafer 	if (width == LONG_MIN || width == LONG_MAX || bp == cp || *cp != '\0') {
5155fd5e3f3eSPhil Shafer 	    width = 0;
5156fd5e3f3eSPhil Shafer 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
5157fd5e3f3eSPhil Shafer 	}
5158fd5e3f3eSPhil Shafer     } else if (flen) {
5159fd5e3f3eSPhil Shafer 	/*
5160fd5e3f3eSPhil Shafer 	 * We really expect the format for width to be "{:/%d}" or
5161fd5e3f3eSPhil Shafer 	 * "{:/%u}", so if that's the case, we just grab our width off
5162fd5e3f3eSPhil Shafer 	 * the argument list.  But we need to avoid optimized logic if
5163fd5e3f3eSPhil Shafer 	 * there's a custom formatter.
5164fd5e3f3eSPhil Shafer 	 */
5165fd5e3f3eSPhil Shafer 	if (xop->xo_formatter == NULL && flen == 2
5166fd5e3f3eSPhil Shafer 	        && strncmp("%d", fmt, flen) == 0) {
5167fd5e3f3eSPhil Shafer 	    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5168fd5e3f3eSPhil Shafer 		width = va_arg(xop->xo_vap, int);
5169fd5e3f3eSPhil Shafer 	} else if (xop->xo_formatter == NULL && flen == 2
5170fd5e3f3eSPhil Shafer 		   && strncmp("%u", fmt, flen) == 0) {
5171fd5e3f3eSPhil Shafer 	    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5172fd5e3f3eSPhil Shafer 		width = va_arg(xop->xo_vap, unsigned);
5173fd5e3f3eSPhil Shafer 	} else {
5174fd5e3f3eSPhil Shafer 	    /*
5175fd5e3f3eSPhil Shafer 	     * So we have a format and it's not a simple one like
5176fd5e3f3eSPhil Shafer 	     * "{:/%d}".  That means we need to format the field,
5177fd5e3f3eSPhil Shafer 	     * extract the value from the formatted output, and then
5178fd5e3f3eSPhil Shafer 	     * discard that output.
5179fd5e3f3eSPhil Shafer 	     */
5180fd5e3f3eSPhil Shafer 	    int anchor_was_set = FALSE;
5181fd5e3f3eSPhil Shafer 	    xo_buffer_t *xbp = &xop->xo_data;
5182fd5e3f3eSPhil Shafer 	    ssize_t start_offset = xo_buf_offset(xbp);
5183fd5e3f3eSPhil Shafer 	    bp = xo_buf_cur(xbp);	/* Save start of the string */
5184fd5e3f3eSPhil Shafer 	    cp = NULL;
5185fd5e3f3eSPhil Shafer 
5186fd5e3f3eSPhil Shafer 	    if (XOIF_ISSET(xop, XOIF_ANCHOR)) {
5187fd5e3f3eSPhil Shafer 		XOIF_CLEAR(xop, XOIF_ANCHOR);
5188fd5e3f3eSPhil Shafer 		anchor_was_set = TRUE;
5189fd5e3f3eSPhil Shafer 	    }
5190fd5e3f3eSPhil Shafer 
5191fd5e3f3eSPhil Shafer 	    ssize_t rc = xo_do_format_field(xop, xbp, fmt, flen, 0);
5192fd5e3f3eSPhil Shafer 	    if (rc >= 0) {
5193fd5e3f3eSPhil Shafer 		xo_buf_append(xbp, "", 1); /* Append a NUL */
5194fd5e3f3eSPhil Shafer 
5195fd5e3f3eSPhil Shafer 		width = strtol(bp, &cp, 0);
519631337658SMarcel Moolenaar 		if (width == LONG_MIN || width == LONG_MAX
519731337658SMarcel Moolenaar 		        || bp == cp || *cp != '\0') {
519831337658SMarcel Moolenaar 		    width = 0;
519931337658SMarcel Moolenaar 		    xo_failure(xop, "invalid width for anchor: '%s'", bp);
520031337658SMarcel Moolenaar 		}
5201fd5e3f3eSPhil Shafer 	    }
5202fd5e3f3eSPhil Shafer 
5203fd5e3f3eSPhil Shafer 	    /* Reset the cur pointer to where we found it */
5204fd5e3f3eSPhil Shafer 	    xbp->xb_curp = xbp->xb_bufp + start_offset;
5205fd5e3f3eSPhil Shafer 	    if (anchor_was_set)
5206fd5e3f3eSPhil Shafer 		XOIF_SET(xop, XOIF_ANCHOR);
5207fd5e3f3eSPhil Shafer 	}
520831337658SMarcel Moolenaar     }
520931337658SMarcel Moolenaar 
521031337658SMarcel Moolenaar     return width;
521131337658SMarcel Moolenaar }
521231337658SMarcel Moolenaar 
521331337658SMarcel Moolenaar static void
xo_anchor_clear(xo_handle_t * xop)521431337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop)
521531337658SMarcel Moolenaar {
5216d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_ANCHOR);
521731337658SMarcel Moolenaar     xop->xo_anchor_offset = 0;
521831337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
521931337658SMarcel Moolenaar     xop->xo_anchor_min_width = 0;
522031337658SMarcel Moolenaar }
522131337658SMarcel Moolenaar 
522231337658SMarcel Moolenaar /*
522331337658SMarcel Moolenaar  * An anchor is a marker used to delay field width implications.
522431337658SMarcel Moolenaar  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
522531337658SMarcel Moolenaar  * We are looking for output like "     1/4/5"
522631337658SMarcel Moolenaar  *
522731337658SMarcel Moolenaar  * To make this work, we record the anchor and then return to
522831337658SMarcel Moolenaar  * format it when the end anchor tag is seen.
522931337658SMarcel Moolenaar  */
523031337658SMarcel Moolenaar static void
xo_anchor_start(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)523142ff34c3SPhil Shafer xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
5232264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
523331337658SMarcel Moolenaar {
5234d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
523531337658SMarcel Moolenaar 	xo_failure(xop, "the anchor already recording is discarded");
523631337658SMarcel Moolenaar 
5237d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_ANCHOR);
523831337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
523931337658SMarcel Moolenaar     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
524031337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
524131337658SMarcel Moolenaar 
524231337658SMarcel Moolenaar     /*
524331337658SMarcel Moolenaar      * Now we find the width, if possible.  If it's not there,
524431337658SMarcel Moolenaar      * we'll get it on the end anchor.
524531337658SMarcel Moolenaar      */
5246264104f2SPhil Shafer     xop->xo_anchor_min_width = xo_find_width(xop, xfip, value, vlen);
524731337658SMarcel Moolenaar }
524831337658SMarcel Moolenaar 
524931337658SMarcel Moolenaar static void
xo_anchor_stop(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)525042ff34c3SPhil Shafer xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
5251264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
525231337658SMarcel Moolenaar {
5253d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
525431337658SMarcel Moolenaar 	xo_failure(xop, "no start anchor");
525531337658SMarcel Moolenaar 	return;
525631337658SMarcel Moolenaar     }
525731337658SMarcel Moolenaar 
5258d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
525931337658SMarcel Moolenaar 
5260264104f2SPhil Shafer     ssize_t width = xo_find_width(xop, xfip, value, vlen);
526131337658SMarcel Moolenaar     if (width == 0)
526231337658SMarcel Moolenaar 	width = xop->xo_anchor_min_width;
526331337658SMarcel Moolenaar 
526431337658SMarcel Moolenaar     if (width == 0)		/* No width given; nothing to do */
526531337658SMarcel Moolenaar 	goto done;
526631337658SMarcel Moolenaar 
526731337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
52688a6eceffSPhil Shafer     ssize_t start = xop->xo_anchor_offset;
52698a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
52708a6eceffSPhil Shafer     ssize_t abswidth = (width > 0) ? width : -width;
52718a6eceffSPhil Shafer     ssize_t blen = abswidth - xop->xo_anchor_columns;
527231337658SMarcel Moolenaar 
527331337658SMarcel Moolenaar     if (blen <= 0)		/* Already over width */
527431337658SMarcel Moolenaar 	goto done;
527531337658SMarcel Moolenaar 
527631337658SMarcel Moolenaar     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
527731337658SMarcel Moolenaar 	xo_failure(xop, "width over %u are not supported",
527831337658SMarcel Moolenaar 		   XO_MAX_ANCHOR_WIDTH);
527931337658SMarcel Moolenaar 	goto done;
528031337658SMarcel Moolenaar     }
528131337658SMarcel Moolenaar 
528231337658SMarcel Moolenaar     /* Make a suitable padding field and emit it */
528331337658SMarcel Moolenaar     char *buf = alloca(blen);
528431337658SMarcel Moolenaar     memset(buf, ' ', blen);
5285d1a0d267SMarcel Moolenaar     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
528631337658SMarcel Moolenaar 
528731337658SMarcel Moolenaar     if (width < 0)		/* Already left justified */
528831337658SMarcel Moolenaar 	goto done;
528931337658SMarcel Moolenaar 
52908a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
52918a6eceffSPhil Shafer     ssize_t delta = now - stop;
5292d1a0d267SMarcel Moolenaar     if (delta <= 0)		/* Strange; no output to move */
529331337658SMarcel Moolenaar 	goto done;
529431337658SMarcel Moolenaar 
529531337658SMarcel Moolenaar     /*
529631337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the padding data
529731337658SMarcel Moolenaar      * we just created (which might be an HTML <div> or text) before
529831337658SMarcel Moolenaar      * the formatted data.  We make a local copy, move it and then
529931337658SMarcel Moolenaar      * insert our copy.  We know there's room in the buffer, since
530031337658SMarcel Moolenaar      * we're just moving this around.
530131337658SMarcel Moolenaar      */
530231337658SMarcel Moolenaar     if (delta > blen)
530331337658SMarcel Moolenaar 	buf = alloca(delta);	/* Expand buffer if needed */
530431337658SMarcel Moolenaar 
530531337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
530631337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
530731337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
530831337658SMarcel Moolenaar 
530931337658SMarcel Moolenaar  done:
531031337658SMarcel Moolenaar     xo_anchor_clear(xop);
531131337658SMarcel Moolenaar }
531231337658SMarcel Moolenaar 
5313d1a0d267SMarcel Moolenaar static const char *
xo_class_name(int ftype)5314d1a0d267SMarcel Moolenaar xo_class_name (int ftype)
531531337658SMarcel Moolenaar {
5316d1a0d267SMarcel Moolenaar     switch (ftype) {
5317d1a0d267SMarcel Moolenaar     case 'D': return "decoration";
5318d1a0d267SMarcel Moolenaar     case 'E': return "error";
5319d1a0d267SMarcel Moolenaar     case 'L': return "label";
5320d1a0d267SMarcel Moolenaar     case 'N': return "note";
5321d1a0d267SMarcel Moolenaar     case 'P': return "padding";
5322d1a0d267SMarcel Moolenaar     case 'W': return "warning";
532331337658SMarcel Moolenaar     }
532431337658SMarcel Moolenaar 
5325d1a0d267SMarcel Moolenaar     return NULL;
532631337658SMarcel Moolenaar }
532731337658SMarcel Moolenaar 
5328d1a0d267SMarcel Moolenaar static const char *
xo_tag_name(int ftype)5329d1a0d267SMarcel Moolenaar xo_tag_name (int ftype)
5330d1a0d267SMarcel Moolenaar {
5331d1a0d267SMarcel Moolenaar     switch (ftype) {
5332d1a0d267SMarcel Moolenaar     case 'E': return "__error";
5333d1a0d267SMarcel Moolenaar     case 'W': return "__warning";
5334d1a0d267SMarcel Moolenaar     }
5335d1a0d267SMarcel Moolenaar 
5336d1a0d267SMarcel Moolenaar     return NULL;
5337d1a0d267SMarcel Moolenaar }
5338d1a0d267SMarcel Moolenaar 
5339d1a0d267SMarcel Moolenaar static int
xo_role_wants_default_format(int ftype)5340d1a0d267SMarcel Moolenaar xo_role_wants_default_format (int ftype)
5341d1a0d267SMarcel Moolenaar {
5342d1a0d267SMarcel Moolenaar     switch (ftype) {
5343d1a0d267SMarcel Moolenaar 	/* These roles can be completely empty and/or without formatting */
5344d1a0d267SMarcel Moolenaar     case 'C':
5345d1a0d267SMarcel Moolenaar     case 'G':
5346d1a0d267SMarcel Moolenaar     case '[':
5347d1a0d267SMarcel Moolenaar     case ']':
5348d1a0d267SMarcel Moolenaar 	return 0;
5349d1a0d267SMarcel Moolenaar     }
5350d1a0d267SMarcel Moolenaar 
5351d1a0d267SMarcel Moolenaar     return 1;
5352d1a0d267SMarcel Moolenaar }
5353d1a0d267SMarcel Moolenaar 
5354d1a0d267SMarcel Moolenaar static xo_mapping_t xo_role_names[] = {
5355d1a0d267SMarcel Moolenaar     { 'C', "color" },
5356d1a0d267SMarcel Moolenaar     { 'D', "decoration" },
5357d1a0d267SMarcel Moolenaar     { 'E', "error" },
5358d1a0d267SMarcel Moolenaar     { 'L', "label" },
5359d1a0d267SMarcel Moolenaar     { 'N', "note" },
5360d1a0d267SMarcel Moolenaar     { 'P', "padding" },
5361d1a0d267SMarcel Moolenaar     { 'T', "title" },
5362d1a0d267SMarcel Moolenaar     { 'U', "units" },
5363d1a0d267SMarcel Moolenaar     { 'V', "value" },
5364d1a0d267SMarcel Moolenaar     { 'W', "warning" },
5365d1a0d267SMarcel Moolenaar     { '[', "start-anchor" },
5366d1a0d267SMarcel Moolenaar     { ']', "stop-anchor" },
5367d1a0d267SMarcel Moolenaar     { 0, NULL }
5368d1a0d267SMarcel Moolenaar };
5369d1a0d267SMarcel Moolenaar 
5370d1a0d267SMarcel Moolenaar #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
5371d1a0d267SMarcel Moolenaar #define XO_ROLE_TEXT	'+'
5372d1a0d267SMarcel Moolenaar #define XO_ROLE_NEWLINE	'\n'
5373d1a0d267SMarcel Moolenaar 
5374d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_names[] = {
537542ff34c3SPhil Shafer     { XFF_ARGUMENT, "argument" },
5376d1a0d267SMarcel Moolenaar     { XFF_COLON, "colon" },
5377d1a0d267SMarcel Moolenaar     { XFF_COMMA, "comma" },
5378d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "display" },
5379d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "encoding" },
5380d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "gettext" },
5381d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "humanize" },
5382d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "hn" },
5383d1a0d267SMarcel Moolenaar     { XFF_HN_SPACE, "hn-space" },
5384d1a0d267SMarcel Moolenaar     { XFF_HN_DECIMAL, "hn-decimal" },
5385d1a0d267SMarcel Moolenaar     { XFF_HN_1000, "hn-1000" },
5386d1a0d267SMarcel Moolenaar     { XFF_KEY, "key" },
5387d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "leaf-list" },
5388d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "list" },
5389d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quotes" },
5390d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quote" },
5391d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "plural" },
5392d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quotes" },
5393d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quote" },
5394d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "trim" },
5395d1a0d267SMarcel Moolenaar     { XFF_WS, "white" },
5396d1a0d267SMarcel Moolenaar     { 0, NULL }
5397d1a0d267SMarcel Moolenaar };
5398d1a0d267SMarcel Moolenaar 
5399d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
5400d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_short_names[] = {
5401d1a0d267SMarcel Moolenaar     { XFF_COLON, "c" },
5402d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "d" },
5403d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "e" },
5404d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "g" },
5405d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "h" },
5406d1a0d267SMarcel Moolenaar     { XFF_KEY, "k" },
5407d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "l" },
5408d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "n" },
5409d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "p" },
5410d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "q" },
5411d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "t" },
5412d1a0d267SMarcel Moolenaar     { XFF_WS, "w" },
5413d1a0d267SMarcel Moolenaar     { 0, NULL }
5414d1a0d267SMarcel Moolenaar };
5415d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
5416d1a0d267SMarcel Moolenaar 
5417d1a0d267SMarcel Moolenaar static int
xo_count_fields(xo_handle_t * xop UNUSED,const char * fmt)5418d1a0d267SMarcel Moolenaar xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5419d1a0d267SMarcel Moolenaar {
5420d1a0d267SMarcel Moolenaar     int rc = 1;
5421d1a0d267SMarcel Moolenaar     const char *cp;
5422d1a0d267SMarcel Moolenaar 
5423d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp; cp++)
5424d1a0d267SMarcel Moolenaar 	if (*cp == '{' || *cp == '\n')
5425d1a0d267SMarcel Moolenaar 	    rc += 1;
5426d1a0d267SMarcel Moolenaar 
5427d1a0d267SMarcel Moolenaar     return rc * 2 + 1;
5428d1a0d267SMarcel Moolenaar }
542931337658SMarcel Moolenaar 
543031337658SMarcel Moolenaar /*
5431d1a0d267SMarcel Moolenaar  * The field format is:
543231337658SMarcel Moolenaar  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5433d1a0d267SMarcel Moolenaar  * Roles are optional and include the following field types:
543431337658SMarcel Moolenaar  *   'D': decoration; something non-text and non-data (colons, commmas)
543531337658SMarcel Moolenaar  *   'E': error message
5436d1a0d267SMarcel Moolenaar  *   'G': gettext() the entire string; optional domainname as content
543731337658SMarcel Moolenaar  *   'L': label; text preceding data
543831337658SMarcel Moolenaar  *   'N': note; text following data
543931337658SMarcel Moolenaar  *   'P': padding; whitespace
544031337658SMarcel Moolenaar  *   'T': Title, where 'content' is a column title
544131337658SMarcel Moolenaar  *   'U': Units, where 'content' is the unit label
544231337658SMarcel Moolenaar  *   'V': value, where 'content' is the name of the field (the default)
544331337658SMarcel Moolenaar  *   'W': warning message
544431337658SMarcel Moolenaar  *   '[': start a section of anchored text
544531337658SMarcel Moolenaar  *   ']': end a section of anchored text
5446d1a0d267SMarcel Moolenaar  * The following modifiers are also supported:
544742ff34c3SPhil Shafer  *   'a': content is provided via argument (const char *), not descriptor
544831337658SMarcel Moolenaar  *   'c': flag: emit a colon after the label
5449d1a0d267SMarcel Moolenaar  *   'd': field is only emitted for display styles (text and html)
5450d1a0d267SMarcel Moolenaar  *   'e': field is only emitted for encoding styles (xml and json)
5451d1a0d267SMarcel Moolenaar  *   'g': gettext() the field
5452d1a0d267SMarcel Moolenaar  *   'h': humanize a numeric value (only for display styles)
545331337658SMarcel Moolenaar  *   'k': this field is a key, suitable for XPath predicates
545431337658SMarcel Moolenaar  *   'l': a leaf-list, a simple list of values
545531337658SMarcel Moolenaar  *   'n': no quotes around this field
5456d1a0d267SMarcel Moolenaar  *   'p': the field has plural gettext semantics (ngettext)
545731337658SMarcel Moolenaar  *   'q': add quotes around this field
545831337658SMarcel Moolenaar  *   't': trim whitespace around the value
545931337658SMarcel Moolenaar  *   'w': emit a blank after the label
546031337658SMarcel Moolenaar  * The print-fmt and encode-fmt strings is the printf-style formating
546131337658SMarcel Moolenaar  * for this data.  JSON and XML will use the encoding-fmt, if present.
546231337658SMarcel Moolenaar  * If the encode-fmt is not provided, it defaults to the print-fmt.
546331337658SMarcel Moolenaar  * If the print-fmt is not provided, it defaults to 's'.
546431337658SMarcel Moolenaar  */
5465d1a0d267SMarcel Moolenaar static const char *
xo_parse_roles(xo_handle_t * xop,const char * fmt,const char * basep,xo_field_info_t * xfip)5466d1a0d267SMarcel Moolenaar xo_parse_roles (xo_handle_t *xop, const char *fmt,
5467d1a0d267SMarcel Moolenaar 		const char *basep, xo_field_info_t *xfip)
5468d1a0d267SMarcel Moolenaar {
5469d1a0d267SMarcel Moolenaar     const char *sp;
5470d1a0d267SMarcel Moolenaar     unsigned ftype = 0;
5471d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = 0;
5472d1a0d267SMarcel Moolenaar     uint8_t fnum = 0;
547331337658SMarcel Moolenaar 
547442ff34c3SPhil Shafer     for (sp = basep; sp && *sp; sp++) {
547531337658SMarcel Moolenaar 	if (*sp == ':' || *sp == '/' || *sp == '}')
547631337658SMarcel Moolenaar 	    break;
547731337658SMarcel Moolenaar 
547831337658SMarcel Moolenaar 	if (*sp == '\\') {
547931337658SMarcel Moolenaar 	    if (sp[1] == '\0') {
548031337658SMarcel Moolenaar 		xo_failure(xop, "backslash at the end of string");
5481d1a0d267SMarcel Moolenaar 		return NULL;
548231337658SMarcel Moolenaar 	    }
5483d1a0d267SMarcel Moolenaar 
5484d1a0d267SMarcel Moolenaar 	    /* Anything backslashed is ignored */
548531337658SMarcel Moolenaar 	    sp += 1;
548631337658SMarcel Moolenaar 	    continue;
548731337658SMarcel Moolenaar 	}
548831337658SMarcel Moolenaar 
5489d1a0d267SMarcel Moolenaar 	if (*sp == ',') {
5490d1a0d267SMarcel Moolenaar 	    const char *np;
5491d1a0d267SMarcel Moolenaar 	    for (np = ++sp; *np; np++)
5492d1a0d267SMarcel Moolenaar 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5493d1a0d267SMarcel Moolenaar 		    break;
5494d1a0d267SMarcel Moolenaar 
54958a6eceffSPhil Shafer 	    ssize_t slen = np - sp;
5496d1a0d267SMarcel Moolenaar 	    if (slen > 0) {
5497d1a0d267SMarcel Moolenaar 		xo_xff_flags_t value;
5498d1a0d267SMarcel Moolenaar 
5499d1a0d267SMarcel Moolenaar 		value = xo_name_lookup(xo_role_names, sp, slen);
5500d1a0d267SMarcel Moolenaar 		if (value)
5501d1a0d267SMarcel Moolenaar 		    ftype = value;
5502d1a0d267SMarcel Moolenaar 		else {
5503d1a0d267SMarcel Moolenaar 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
5504d1a0d267SMarcel Moolenaar 		    if (value)
5505d1a0d267SMarcel Moolenaar 			flags |= value;
5506d1a0d267SMarcel Moolenaar 		    else
5507d1a0d267SMarcel Moolenaar 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
5508d1a0d267SMarcel Moolenaar 				   slen, sp);
5509d1a0d267SMarcel Moolenaar 		}
5510d1a0d267SMarcel Moolenaar 	    }
5511d1a0d267SMarcel Moolenaar 
5512d1a0d267SMarcel Moolenaar 	    sp = np - 1;
5513d1a0d267SMarcel Moolenaar 	    continue;
5514d1a0d267SMarcel Moolenaar 	}
5515d1a0d267SMarcel Moolenaar 
551631337658SMarcel Moolenaar 	switch (*sp) {
5517788ca347SMarcel Moolenaar 	case 'C':
551831337658SMarcel Moolenaar 	case 'D':
551931337658SMarcel Moolenaar 	case 'E':
5520d1a0d267SMarcel Moolenaar 	case 'G':
552131337658SMarcel Moolenaar 	case 'L':
552231337658SMarcel Moolenaar 	case 'N':
552331337658SMarcel Moolenaar 	case 'P':
552431337658SMarcel Moolenaar 	case 'T':
552531337658SMarcel Moolenaar 	case 'U':
552631337658SMarcel Moolenaar 	case 'V':
552731337658SMarcel Moolenaar 	case 'W':
552831337658SMarcel Moolenaar 	case '[':
552931337658SMarcel Moolenaar 	case ']':
553031337658SMarcel Moolenaar 	    if (ftype != 0) {
5531d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
5532d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5533d1a0d267SMarcel Moolenaar 		return NULL;
553431337658SMarcel Moolenaar 	    }
553531337658SMarcel Moolenaar 	    ftype = *sp;
553631337658SMarcel Moolenaar 	    break;
553731337658SMarcel Moolenaar 
5538d1a0d267SMarcel Moolenaar 	case '0':
5539d1a0d267SMarcel Moolenaar 	case '1':
5540d1a0d267SMarcel Moolenaar 	case '2':
5541d1a0d267SMarcel Moolenaar 	case '3':
5542d1a0d267SMarcel Moolenaar 	case '4':
5543d1a0d267SMarcel Moolenaar 	case '5':
5544d1a0d267SMarcel Moolenaar 	case '6':
5545d1a0d267SMarcel Moolenaar 	case '7':
5546d1a0d267SMarcel Moolenaar 	case '8':
5547d1a0d267SMarcel Moolenaar 	case '9':
5548d1a0d267SMarcel Moolenaar 	    fnum = (fnum * 10) + (*sp - '0');
5549d1a0d267SMarcel Moolenaar 	    break;
5550d1a0d267SMarcel Moolenaar 
555142ff34c3SPhil Shafer 	case 'a':
555242ff34c3SPhil Shafer 	    flags |= XFF_ARGUMENT;
555342ff34c3SPhil Shafer 	    break;
555442ff34c3SPhil Shafer 
555531337658SMarcel Moolenaar 	case 'c':
555631337658SMarcel Moolenaar 	    flags |= XFF_COLON;
555731337658SMarcel Moolenaar 	    break;
555831337658SMarcel Moolenaar 
555931337658SMarcel Moolenaar 	case 'd':
556031337658SMarcel Moolenaar 	    flags |= XFF_DISPLAY_ONLY;
556131337658SMarcel Moolenaar 	    break;
556231337658SMarcel Moolenaar 
556331337658SMarcel Moolenaar 	case 'e':
556431337658SMarcel Moolenaar 	    flags |= XFF_ENCODE_ONLY;
556531337658SMarcel Moolenaar 	    break;
556631337658SMarcel Moolenaar 
5567d1a0d267SMarcel Moolenaar 	case 'g':
5568d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_FIELD;
5569d1a0d267SMarcel Moolenaar 	    break;
5570d1a0d267SMarcel Moolenaar 
5571d1a0d267SMarcel Moolenaar 	case 'h':
5572d1a0d267SMarcel Moolenaar 	    flags |= XFF_HUMANIZE;
5573d1a0d267SMarcel Moolenaar 	    break;
5574d1a0d267SMarcel Moolenaar 
557531337658SMarcel Moolenaar 	case 'k':
557631337658SMarcel Moolenaar 	    flags |= XFF_KEY;
557731337658SMarcel Moolenaar 	    break;
557831337658SMarcel Moolenaar 
557931337658SMarcel Moolenaar 	case 'l':
558031337658SMarcel Moolenaar 	    flags |= XFF_LEAF_LIST;
558131337658SMarcel Moolenaar 	    break;
558231337658SMarcel Moolenaar 
558331337658SMarcel Moolenaar 	case 'n':
558431337658SMarcel Moolenaar 	    flags |= XFF_NOQUOTE;
558531337658SMarcel Moolenaar 	    break;
558631337658SMarcel Moolenaar 
5587d1a0d267SMarcel Moolenaar 	case 'p':
5588d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_PLURAL;
5589d1a0d267SMarcel Moolenaar 	    break;
5590d1a0d267SMarcel Moolenaar 
559131337658SMarcel Moolenaar 	case 'q':
559231337658SMarcel Moolenaar 	    flags |= XFF_QUOTE;
559331337658SMarcel Moolenaar 	    break;
559431337658SMarcel Moolenaar 
559531337658SMarcel Moolenaar 	case 't':
559631337658SMarcel Moolenaar 	    flags |= XFF_TRIM_WS;
559731337658SMarcel Moolenaar 	    break;
559831337658SMarcel Moolenaar 
559931337658SMarcel Moolenaar 	case 'w':
560031337658SMarcel Moolenaar 	    flags |= XFF_WS;
560131337658SMarcel Moolenaar 	    break;
560231337658SMarcel Moolenaar 
560331337658SMarcel Moolenaar 	default:
5604d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5605d1a0d267SMarcel Moolenaar 		       xo_printable(fmt));
560631337658SMarcel Moolenaar 	    /*
560731337658SMarcel Moolenaar 	     * No good answer here; a bad format will likely
560831337658SMarcel Moolenaar 	     * mean a core file.  We just return and hope
560931337658SMarcel Moolenaar 	     * the caller notices there's no output, and while
5610d1a0d267SMarcel Moolenaar 	     * that seems, well, bad, there's nothing better.
561131337658SMarcel Moolenaar 	     */
5612d1a0d267SMarcel Moolenaar 	    return NULL;
5613d1a0d267SMarcel Moolenaar 	}
5614d1a0d267SMarcel Moolenaar 
5615d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
5616d1a0d267SMarcel Moolenaar 	    if (flags & XFF_COLON) {
5617d1a0d267SMarcel Moolenaar 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5618d1a0d267SMarcel Moolenaar 			   "'%s'", xo_printable(fmt));
5619d1a0d267SMarcel Moolenaar 		flags &= ~XFF_COLON;
5620d1a0d267SMarcel Moolenaar 	    }
562131337658SMarcel Moolenaar 	}
562231337658SMarcel Moolenaar     }
562331337658SMarcel Moolenaar 
5624d1a0d267SMarcel Moolenaar     xfip->xfi_flags = flags;
5625d1a0d267SMarcel Moolenaar     xfip->xfi_ftype = ftype ?: 'V';
5626d1a0d267SMarcel Moolenaar     xfip->xfi_fnum = fnum;
5627d1a0d267SMarcel Moolenaar 
5628d1a0d267SMarcel Moolenaar     return sp;
5629d1a0d267SMarcel Moolenaar }
5630d1a0d267SMarcel Moolenaar 
5631d1a0d267SMarcel Moolenaar /*
5632d1a0d267SMarcel Moolenaar  * Number any remaining fields that need numbers.  Note that some
5633d1a0d267SMarcel Moolenaar  * field types (text, newline, escaped braces) never get numbers.
5634d1a0d267SMarcel Moolenaar  */
5635d1a0d267SMarcel Moolenaar static void
xo_gettext_finish_numbering_fields(xo_handle_t * xop UNUSED,const char * fmt UNUSED,xo_field_info_t * fields)5636d1a0d267SMarcel Moolenaar xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5637d1a0d267SMarcel Moolenaar 				    const char *fmt UNUSED,
5638d1a0d267SMarcel Moolenaar 				    xo_field_info_t *fields)
5639d1a0d267SMarcel Moolenaar {
5640d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5641d1a0d267SMarcel Moolenaar     unsigned fnum, max_fields;
5642d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
564376afb20cSPhil Shafer     const uint64_t one = 1;	/* Avoid "1ULL" */
5644d1a0d267SMarcel Moolenaar 
5645d1a0d267SMarcel Moolenaar     /* First make a list of add the explicitly used bits */
5646d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5647d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5648d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5649d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5650d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5651d1a0d267SMarcel Moolenaar 	case 'G':
5652d1a0d267SMarcel Moolenaar 	    continue;
5653d1a0d267SMarcel Moolenaar 	}
5654d1a0d267SMarcel Moolenaar 
5655d1a0d267SMarcel Moolenaar 	fnum += 1;
5656d1a0d267SMarcel Moolenaar 	if (fnum >= 63)
5657d1a0d267SMarcel Moolenaar 	    break;
5658d1a0d267SMarcel Moolenaar 
5659d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
566076afb20cSPhil Shafer 	    bits |= one << xfip->xfi_fnum;
5661d1a0d267SMarcel Moolenaar     }
5662d1a0d267SMarcel Moolenaar 
5663d1a0d267SMarcel Moolenaar     max_fields = fnum;
5664d1a0d267SMarcel Moolenaar 
5665d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5666d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5667d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5668d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5669d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5670d1a0d267SMarcel Moolenaar 	case 'G':
5671d1a0d267SMarcel Moolenaar 	    continue;
5672d1a0d267SMarcel Moolenaar 	}
5673d1a0d267SMarcel Moolenaar 
5674d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum != 0)
5675d1a0d267SMarcel Moolenaar 	    continue;
5676d1a0d267SMarcel Moolenaar 
5677d1a0d267SMarcel Moolenaar 	/* Find the next unassigned field */
567876afb20cSPhil Shafer 	for (fnum++; bits & (one << fnum); fnum++)
5679d1a0d267SMarcel Moolenaar 	    continue;
5680d1a0d267SMarcel Moolenaar 
5681d1a0d267SMarcel Moolenaar 	if (fnum > max_fields)
5682d1a0d267SMarcel Moolenaar 	    break;
5683d1a0d267SMarcel Moolenaar 
5684d1a0d267SMarcel Moolenaar 	xfip->xfi_fnum = fnum;	/* Mark the field number */
568576afb20cSPhil Shafer 	bits |= one << fnum;	/* Mark it used */
5686d1a0d267SMarcel Moolenaar     }
5687d1a0d267SMarcel Moolenaar }
5688d1a0d267SMarcel Moolenaar 
5689d1a0d267SMarcel Moolenaar /*
5690ee5cf116SPhil Shafer  * The format string uses field numbers, so we need to whiffle through it
5691d1a0d267SMarcel Moolenaar  * and make sure everything's sane and lovely.
5692d1a0d267SMarcel Moolenaar  */
5693d1a0d267SMarcel Moolenaar static int
xo_parse_field_numbers(xo_handle_t * xop,const char * fmt,xo_field_info_t * fields,unsigned num_fields)5694d1a0d267SMarcel Moolenaar xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5695d1a0d267SMarcel Moolenaar 			xo_field_info_t *fields, unsigned num_fields)
5696d1a0d267SMarcel Moolenaar {
5697d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5698d1a0d267SMarcel Moolenaar     unsigned field, fnum;
5699d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
570076afb20cSPhil Shafer     const uint64_t one = 1;	/* Avoid 1ULL */
5701d1a0d267SMarcel Moolenaar 
5702d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5703d1a0d267SMarcel Moolenaar 	/* Fields default to 1:1 with natural position */
5704d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == 0)
5705d1a0d267SMarcel Moolenaar 	    xfip->xfi_fnum = field + 1;
5706d1a0d267SMarcel Moolenaar 	else if (xfip->xfi_fnum > num_fields) {
5707d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5708d1a0d267SMarcel Moolenaar 	    return -1;
5709d1a0d267SMarcel Moolenaar 	}
5710d1a0d267SMarcel Moolenaar 
5711d1a0d267SMarcel Moolenaar 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5712d1a0d267SMarcel Moolenaar 	if (fnum < 64) {	/* Only test what fits */
571376afb20cSPhil Shafer 	    if (bits & (one << fnum)) {
5714d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field number %u reused: '%s'",
5715d1a0d267SMarcel Moolenaar 			   xfip->xfi_fnum, fmt);
5716d1a0d267SMarcel Moolenaar 		return -1;
5717d1a0d267SMarcel Moolenaar 	    }
571876afb20cSPhil Shafer 	    bits |= one << fnum;
5719d1a0d267SMarcel Moolenaar 	}
5720d1a0d267SMarcel Moolenaar     }
5721d1a0d267SMarcel Moolenaar 
5722d1a0d267SMarcel Moolenaar     return 0;
5723d1a0d267SMarcel Moolenaar }
5724d1a0d267SMarcel Moolenaar 
5725d1a0d267SMarcel Moolenaar static int
xo_parse_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned num_fields,const char * fmt)5726d1a0d267SMarcel Moolenaar xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5727d1a0d267SMarcel Moolenaar 		 unsigned num_fields, const char *fmt)
5728d1a0d267SMarcel Moolenaar {
5729d1a0d267SMarcel Moolenaar     const char *cp, *sp, *ep, *basep;
5730d1a0d267SMarcel Moolenaar     unsigned field = 0;
5731d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip = fields;
5732d1a0d267SMarcel Moolenaar     unsigned seen_fnum = 0;
5733d1a0d267SMarcel Moolenaar 
5734d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5735d1a0d267SMarcel Moolenaar 	xfip->xfi_start = cp;
5736d1a0d267SMarcel Moolenaar 
5737d1a0d267SMarcel Moolenaar 	if (*cp == '\n') {
5738d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5739d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = 1;
5740d1a0d267SMarcel Moolenaar 	    cp += 1;
5741d1a0d267SMarcel Moolenaar 	    continue;
5742d1a0d267SMarcel Moolenaar 	}
5743d1a0d267SMarcel Moolenaar 
5744d1a0d267SMarcel Moolenaar 	if (*cp != '{') {
5745d1a0d267SMarcel Moolenaar 	    /* Normal text */
5746d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5747d1a0d267SMarcel Moolenaar 		if (*sp == '{' || *sp == '\n')
5748d1a0d267SMarcel Moolenaar 		    break;
5749d1a0d267SMarcel Moolenaar 	    }
5750d1a0d267SMarcel Moolenaar 
5751d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5752d1a0d267SMarcel Moolenaar 	    xfip->xfi_content = cp;
5753d1a0d267SMarcel Moolenaar 	    xfip->xfi_clen = sp - cp;
5754d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = sp;
5755d1a0d267SMarcel Moolenaar 
5756d1a0d267SMarcel Moolenaar 	    cp = sp;
5757d1a0d267SMarcel Moolenaar 	    continue;
5758d1a0d267SMarcel Moolenaar 	}
5759d1a0d267SMarcel Moolenaar 
5760d1a0d267SMarcel Moolenaar 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5761d1a0d267SMarcel Moolenaar 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5762d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5763d1a0d267SMarcel Moolenaar 
5764d1a0d267SMarcel Moolenaar 	    cp += 2;	/* Skip over _both_ characters */
5765d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5766d1a0d267SMarcel Moolenaar 		if (*sp == '}' && sp[1] == '}')
5767d1a0d267SMarcel Moolenaar 		    break;
5768d1a0d267SMarcel Moolenaar 	    }
5769d1a0d267SMarcel Moolenaar 	    if (*sp == '\0') {
5770d1a0d267SMarcel Moolenaar 		xo_failure(xop, "missing closing '}}': '%s'",
5771d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5772d1a0d267SMarcel Moolenaar 		return -1;
5773d1a0d267SMarcel Moolenaar 	    }
5774d1a0d267SMarcel Moolenaar 
5775d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5776d1a0d267SMarcel Moolenaar 
5777d1a0d267SMarcel Moolenaar 	    /* Move along the string, but don't run off the end */
577876afb20cSPhil Shafer 	    if (*sp == '}' && sp[1] == '}') /* Paranoid; must be true */
5779d1a0d267SMarcel Moolenaar 		sp += 2;
578076afb20cSPhil Shafer 
578176afb20cSPhil Shafer 	    cp = sp;
5782d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = cp;
5783d1a0d267SMarcel Moolenaar 	    continue;
5784d1a0d267SMarcel Moolenaar 	}
5785d1a0d267SMarcel Moolenaar 
5786d1a0d267SMarcel Moolenaar 	/* We are looking at the start of a field definition */
5787d1a0d267SMarcel Moolenaar 	xfip->xfi_start = basep = cp + 1;
5788d1a0d267SMarcel Moolenaar 
5789d1a0d267SMarcel Moolenaar 	const char *format = NULL;
57908a6eceffSPhil Shafer 	ssize_t flen = 0;
5791d1a0d267SMarcel Moolenaar 
5792d1a0d267SMarcel Moolenaar 	/* Looking at roles and modifiers */
5793d1a0d267SMarcel Moolenaar 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5794d1a0d267SMarcel Moolenaar 	if (sp == NULL) {
5795d1a0d267SMarcel Moolenaar 	    /* xo_failure has already been called */
5796d1a0d267SMarcel Moolenaar 	    return -1;
5797d1a0d267SMarcel Moolenaar 	}
5798d1a0d267SMarcel Moolenaar 
5799d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5800d1a0d267SMarcel Moolenaar 	    seen_fnum = 1;
5801d1a0d267SMarcel Moolenaar 
5802d1a0d267SMarcel Moolenaar 	/* Looking at content */
580331337658SMarcel Moolenaar 	if (*sp == ':') {
580431337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
580531337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
580631337658SMarcel Moolenaar 		    break;
580731337658SMarcel Moolenaar 		if (*sp == '\\') {
580831337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
580931337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
581031337658SMarcel Moolenaar 			return -1;
581131337658SMarcel Moolenaar 		    }
581231337658SMarcel Moolenaar 		    sp += 1;
581331337658SMarcel Moolenaar 		    continue;
581431337658SMarcel Moolenaar 		}
581531337658SMarcel Moolenaar 	    }
581631337658SMarcel Moolenaar 	    if (ep != sp) {
5817d1a0d267SMarcel Moolenaar 		xfip->xfi_clen = sp - ep;
5818d1a0d267SMarcel Moolenaar 		xfip->xfi_content = ep;
581931337658SMarcel Moolenaar 	    }
582031337658SMarcel Moolenaar 	} else {
5821d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
582231337658SMarcel Moolenaar 	    return -1;
582331337658SMarcel Moolenaar 	}
582431337658SMarcel Moolenaar 
5825d1a0d267SMarcel Moolenaar 	/* Looking at main (display) format */
582631337658SMarcel Moolenaar 	if (*sp == '/') {
582731337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
582831337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
582931337658SMarcel Moolenaar 		    break;
583031337658SMarcel Moolenaar 		if (*sp == '\\') {
583131337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
583231337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
583331337658SMarcel Moolenaar 			return -1;
583431337658SMarcel Moolenaar 		    }
583531337658SMarcel Moolenaar 		    sp += 1;
583631337658SMarcel Moolenaar 		    continue;
583731337658SMarcel Moolenaar 		}
583831337658SMarcel Moolenaar 	    }
583931337658SMarcel Moolenaar 	    flen = sp - ep;
584031337658SMarcel Moolenaar 	    format = ep;
584131337658SMarcel Moolenaar 	}
584231337658SMarcel Moolenaar 
5843d1a0d267SMarcel Moolenaar 	/* Looking at encoding format */
584431337658SMarcel Moolenaar 	if (*sp == '/') {
584531337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
584631337658SMarcel Moolenaar 		if (*sp == '}')
584731337658SMarcel Moolenaar 		    break;
584831337658SMarcel Moolenaar 	    }
5849d1a0d267SMarcel Moolenaar 
5850d1a0d267SMarcel Moolenaar 	    xfip->xfi_encoding = ep;
5851d1a0d267SMarcel Moolenaar 	    xfip->xfi_elen = sp - ep;
585231337658SMarcel Moolenaar 	}
585331337658SMarcel Moolenaar 
5854d1a0d267SMarcel Moolenaar 	if (*sp != '}') {
5855d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
585631337658SMarcel Moolenaar 	    return -1;
585731337658SMarcel Moolenaar 	}
585831337658SMarcel Moolenaar 
5859d1a0d267SMarcel Moolenaar 	xfip->xfi_len = sp - xfip->xfi_start;
5860d1a0d267SMarcel Moolenaar 	xfip->xfi_next = ++sp;
5861d1a0d267SMarcel Moolenaar 
5862d1a0d267SMarcel Moolenaar 	/* If we have content, then we have a default format */
586342ff34c3SPhil Shafer 	if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5864d1a0d267SMarcel Moolenaar 	    if (format) {
5865d1a0d267SMarcel Moolenaar 		xfip->xfi_format = format;
5866d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = flen;
5867d1a0d267SMarcel Moolenaar 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
586842ff34c3SPhil Shafer 		xfip->xfi_format = xo_default_format;
5869d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = 2;
5870d1a0d267SMarcel Moolenaar 	    }
587131337658SMarcel Moolenaar 	}
587231337658SMarcel Moolenaar 
5873d1a0d267SMarcel Moolenaar 	cp = sp;
5874d1a0d267SMarcel Moolenaar     }
5875545ddfbeSMarcel Moolenaar 
5876d1a0d267SMarcel Moolenaar     int rc = 0;
5877d1a0d267SMarcel Moolenaar 
5878d1a0d267SMarcel Moolenaar     /*
5879d1a0d267SMarcel Moolenaar      * If we saw a field number on at least one field, then we need
5880d1a0d267SMarcel Moolenaar      * to enforce some rules and/or guidelines.
5881d1a0d267SMarcel Moolenaar      */
5882d1a0d267SMarcel Moolenaar     if (seen_fnum)
5883d1a0d267SMarcel Moolenaar 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5884d1a0d267SMarcel Moolenaar 
5885d1a0d267SMarcel Moolenaar     return rc;
5886d1a0d267SMarcel Moolenaar }
5887d1a0d267SMarcel Moolenaar 
5888d1a0d267SMarcel Moolenaar /*
5889d1a0d267SMarcel Moolenaar  * We are passed a pointer to a format string just past the "{G:}"
5890d1a0d267SMarcel Moolenaar  * field.  We build a simplified version of the format string.
5891d1a0d267SMarcel Moolenaar  */
5892d1a0d267SMarcel Moolenaar static int
xo_gettext_simplify_format(xo_handle_t * xop UNUSED,xo_buffer_t * xbp,xo_field_info_t * fields,int this_field,const char * fmt UNUSED,xo_simplify_field_func_t field_cb)5893d1a0d267SMarcel Moolenaar xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5894d1a0d267SMarcel Moolenaar 		       xo_buffer_t *xbp,
5895d1a0d267SMarcel Moolenaar 		       xo_field_info_t *fields,
5896d1a0d267SMarcel Moolenaar 		       int this_field,
5897d1a0d267SMarcel Moolenaar 		       const char *fmt UNUSED,
5898d1a0d267SMarcel Moolenaar 		       xo_simplify_field_func_t field_cb)
5899d1a0d267SMarcel Moolenaar {
5900d1a0d267SMarcel Moolenaar     unsigned ftype;
5901d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags;
5902d1a0d267SMarcel Moolenaar     int field = this_field + 1;
5903d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5904d1a0d267SMarcel Moolenaar     char ch;
5905d1a0d267SMarcel Moolenaar 
5906d1a0d267SMarcel Moolenaar     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5907d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5908d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5909d1a0d267SMarcel Moolenaar 
5910d1a0d267SMarcel Moolenaar 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5911d1a0d267SMarcel Moolenaar 	    if (field_cb)
5912d1a0d267SMarcel Moolenaar 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5913d1a0d267SMarcel Moolenaar 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5914d1a0d267SMarcel Moolenaar 	}
5915d1a0d267SMarcel Moolenaar 
5916d1a0d267SMarcel Moolenaar 	switch (ftype) {
5917d1a0d267SMarcel Moolenaar 	case 'G':
5918d1a0d267SMarcel Moolenaar 	    /* Ignore gettext roles */
5919d1a0d267SMarcel Moolenaar 	    break;
5920d1a0d267SMarcel Moolenaar 
5921d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5922d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1);
5923d1a0d267SMarcel Moolenaar 	    break;
5924d1a0d267SMarcel Moolenaar 
5925d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5926d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5927d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5928d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5929d1a0d267SMarcel Moolenaar 	    break;
5930d1a0d267SMarcel Moolenaar 
5931d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5932d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5933d1a0d267SMarcel Moolenaar 	    break;
5934d1a0d267SMarcel Moolenaar 
5935d1a0d267SMarcel Moolenaar 	default:
5936d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5937d1a0d267SMarcel Moolenaar 	    if (ftype != 'V') {
5938d1a0d267SMarcel Moolenaar 		ch = ftype;
5939d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, &ch, 1);
5940d1a0d267SMarcel Moolenaar 	    }
5941d1a0d267SMarcel Moolenaar 
5942d1a0d267SMarcel Moolenaar 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5943d1a0d267SMarcel Moolenaar 	    if (fnum) {
5944d1a0d267SMarcel Moolenaar 		char num[12];
5945d1a0d267SMarcel Moolenaar 		/* Field numbers are origin 1, not 0, following printf(3) */
5946d1a0d267SMarcel Moolenaar 		snprintf(num, sizeof(num), "%u", fnum);
5947d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, num, strlen(num));
5948d1a0d267SMarcel Moolenaar 	    }
5949d1a0d267SMarcel Moolenaar 
5950d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, ":", 1);
5951d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5952d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5953d1a0d267SMarcel Moolenaar 	}
5954d1a0d267SMarcel Moolenaar     }
5955d1a0d267SMarcel Moolenaar 
5956d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1);
5957d1a0d267SMarcel Moolenaar     return 0;
5958d1a0d267SMarcel Moolenaar }
5959d1a0d267SMarcel Moolenaar 
5960d1a0d267SMarcel Moolenaar void
5961d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5962d1a0d267SMarcel Moolenaar void
xo_dump_fields(xo_field_info_t * fields)5963d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *fields)
5964d1a0d267SMarcel Moolenaar {
5965d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5966d1a0d267SMarcel Moolenaar 
5967d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5968d1a0d267SMarcel Moolenaar 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5969d1a0d267SMarcel Moolenaar 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5970d1a0d267SMarcel Moolenaar 	       (unsigned long) xfip->xfi_flags,
5971d1a0d267SMarcel Moolenaar 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5972d1a0d267SMarcel Moolenaar 	       xfip->xfi_ftype,
59738a6eceffSPhil Shafer 	       (int) xfip->xfi_clen, xfip->xfi_content ?: "",
59748a6eceffSPhil Shafer 	       (int) xfip->xfi_flen, xfip->xfi_format ?: "",
59758a6eceffSPhil Shafer 	       (int) xfip->xfi_elen, xfip->xfi_encoding ?: "");
5976d1a0d267SMarcel Moolenaar     }
5977d1a0d267SMarcel Moolenaar }
5978d1a0d267SMarcel Moolenaar 
5979d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
5980d1a0d267SMarcel Moolenaar /*
5981d1a0d267SMarcel Moolenaar  * Find the field that matches the given field number
5982d1a0d267SMarcel Moolenaar  */
5983d1a0d267SMarcel Moolenaar static xo_field_info_t *
xo_gettext_find_field(xo_field_info_t * fields,unsigned fnum)5984d1a0d267SMarcel Moolenaar xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5985d1a0d267SMarcel Moolenaar {
5986d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5987d1a0d267SMarcel Moolenaar 
5988d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++)
5989d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == fnum)
5990d1a0d267SMarcel Moolenaar 	    return xfip;
5991d1a0d267SMarcel Moolenaar 
5992d1a0d267SMarcel Moolenaar     return NULL;
5993d1a0d267SMarcel Moolenaar }
5994d1a0d267SMarcel Moolenaar 
5995d1a0d267SMarcel Moolenaar /*
5996d1a0d267SMarcel Moolenaar  * At this point, we need to consider if the fields have been reordered,
5997d1a0d267SMarcel Moolenaar  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5998d1a0d267SMarcel Moolenaar  *
5999d1a0d267SMarcel Moolenaar  * We need to rewrite the new_fields using the old fields order,
6000d1a0d267SMarcel Moolenaar  * so that we can render the message using the arguments as they
6001d1a0d267SMarcel Moolenaar  * appear on the stack.  It's a lot of work, but we don't really
6002d1a0d267SMarcel Moolenaar  * want to (eventually) fall into the standard printf code which
6003d1a0d267SMarcel Moolenaar  * means using the arguments straight (and in order) from the
6004d1a0d267SMarcel Moolenaar  * varargs we were originally passed.
6005d1a0d267SMarcel Moolenaar  */
6006d1a0d267SMarcel Moolenaar static void
xo_gettext_rewrite_fields(xo_handle_t * xop UNUSED,xo_field_info_t * fields,unsigned max_fields)6007d1a0d267SMarcel Moolenaar xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
6008d1a0d267SMarcel Moolenaar 			   xo_field_info_t *fields, unsigned max_fields)
6009d1a0d267SMarcel Moolenaar {
6010d1a0d267SMarcel Moolenaar     xo_field_info_t tmp[max_fields];
6011d1a0d267SMarcel Moolenaar     bzero(tmp, max_fields * sizeof(tmp[0]));
6012d1a0d267SMarcel Moolenaar 
6013d1a0d267SMarcel Moolenaar     unsigned fnum = 0;
6014d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *outp, *zp;
6015d1a0d267SMarcel Moolenaar     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
6016d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
6017d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
6018d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
6019d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
6020d1a0d267SMarcel Moolenaar 	case 'G':
6021d1a0d267SMarcel Moolenaar 	    *outp = *newp;
6022d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
6023d1a0d267SMarcel Moolenaar 	    continue;
6024d1a0d267SMarcel Moolenaar 	}
6025d1a0d267SMarcel Moolenaar 
6026d1a0d267SMarcel Moolenaar 	zp = xo_gettext_find_field(fields, ++fnum);
6027d1a0d267SMarcel Moolenaar 	if (zp == NULL) { 	/* Should not occur */
6028d1a0d267SMarcel Moolenaar 	    *outp = *newp;
6029d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
6030d1a0d267SMarcel Moolenaar 	    continue;
6031d1a0d267SMarcel Moolenaar 	}
6032d1a0d267SMarcel Moolenaar 
6033d1a0d267SMarcel Moolenaar 	*outp = *zp;
6034d1a0d267SMarcel Moolenaar 	outp->xfi_renum = newp->xfi_fnum;
6035d1a0d267SMarcel Moolenaar     }
6036d1a0d267SMarcel Moolenaar 
6037d1a0d267SMarcel Moolenaar     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
6038d1a0d267SMarcel Moolenaar }
6039d1a0d267SMarcel Moolenaar 
6040d1a0d267SMarcel Moolenaar /*
6041d1a0d267SMarcel Moolenaar  * We've got two lists of fields, the old list from the original
6042d1a0d267SMarcel Moolenaar  * format string and the new one from the parsed gettext reply.  The
6043d1a0d267SMarcel Moolenaar  * new list has the localized words, where the old list has the
6044d1a0d267SMarcel Moolenaar  * formatting information.  We need to combine them into a single list
6045d1a0d267SMarcel Moolenaar  * (the new list).
6046d1a0d267SMarcel Moolenaar  *
6047d1a0d267SMarcel Moolenaar  * If the list needs to be reordered, then we've got more serious work
6048d1a0d267SMarcel Moolenaar  * to do.
6049d1a0d267SMarcel Moolenaar  */
6050d1a0d267SMarcel Moolenaar static int
xo_gettext_combine_formats(xo_handle_t * xop,const char * fmt UNUSED,const char * gtfmt,xo_field_info_t * old_fields,xo_field_info_t * new_fields,unsigned new_max_fields,int * reorderedp)6051d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
6052d1a0d267SMarcel Moolenaar 		    const char *gtfmt, xo_field_info_t *old_fields,
6053d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields, unsigned new_max_fields,
6054d1a0d267SMarcel Moolenaar 		    int *reorderedp)
6055d1a0d267SMarcel Moolenaar {
6056d1a0d267SMarcel Moolenaar     int reordered = 0;
6057d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *oldp, *startp = old_fields;
6058d1a0d267SMarcel Moolenaar 
6059d1a0d267SMarcel Moolenaar     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
6060d1a0d267SMarcel Moolenaar 
6061d1a0d267SMarcel Moolenaar     for (newp = new_fields; newp->xfi_ftype; newp++) {
6062d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
6063d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
6064d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
6065d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
6066d1a0d267SMarcel Moolenaar 	    continue;
6067d1a0d267SMarcel Moolenaar 
6068d1a0d267SMarcel Moolenaar 	case 'V':
6069d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
6070d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype != 'V')
6071d1a0d267SMarcel Moolenaar 		    continue;
6072d1a0d267SMarcel Moolenaar 		if (newp->xfi_clen != oldp->xfi_clen
6073d1a0d267SMarcel Moolenaar 		    || strncmp(newp->xfi_content, oldp->xfi_content,
6074d1a0d267SMarcel Moolenaar 			       oldp->xfi_clen) != 0) {
6075d1a0d267SMarcel Moolenaar 		    reordered = 1;
6076d1a0d267SMarcel Moolenaar 		    continue;
6077d1a0d267SMarcel Moolenaar 		}
6078d1a0d267SMarcel Moolenaar 		startp = oldp + 1;
6079d1a0d267SMarcel Moolenaar 		break;
6080d1a0d267SMarcel Moolenaar 	    }
6081d1a0d267SMarcel Moolenaar 
6082d1a0d267SMarcel Moolenaar 	    /* Didn't find it on the first pass (starting from start) */
6083d1a0d267SMarcel Moolenaar 	    if (oldp->xfi_ftype == 0) {
6084d1a0d267SMarcel Moolenaar 		for (oldp = old_fields; oldp < startp; oldp++) {
6085d1a0d267SMarcel Moolenaar 		    if (oldp->xfi_ftype != 'V')
6086d1a0d267SMarcel Moolenaar 			continue;
6087d1a0d267SMarcel Moolenaar 		    if (newp->xfi_clen != oldp->xfi_clen)
6088d1a0d267SMarcel Moolenaar 			continue;
6089d1a0d267SMarcel Moolenaar 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
6090d1a0d267SMarcel Moolenaar 				oldp->xfi_clen) != 0)
6091d1a0d267SMarcel Moolenaar 			continue;
6092d1a0d267SMarcel Moolenaar 		    reordered = 1;
6093d1a0d267SMarcel Moolenaar 		    break;
6094d1a0d267SMarcel Moolenaar 		}
6095d1a0d267SMarcel Moolenaar 		if (oldp == startp) {
6096d1a0d267SMarcel Moolenaar 		    /* Field not found */
6097d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "post-gettext format can't find field "
6098d1a0d267SMarcel Moolenaar 			       "'%.*s' in format '%s'",
6099d1a0d267SMarcel Moolenaar 			       newp->xfi_clen, newp->xfi_content,
6100d1a0d267SMarcel Moolenaar 			       xo_printable(gtfmt));
6101d1a0d267SMarcel Moolenaar 		    return -1;
6102d1a0d267SMarcel Moolenaar 		}
6103d1a0d267SMarcel Moolenaar 	    }
6104d1a0d267SMarcel Moolenaar 	    break;
6105d1a0d267SMarcel Moolenaar 
6106d1a0d267SMarcel Moolenaar 	default:
6107d1a0d267SMarcel Moolenaar 	    /*
6108d1a0d267SMarcel Moolenaar 	     * Other fields don't have names for us to use, so if
6109d1a0d267SMarcel Moolenaar 	     * the types aren't the same, then we'll have to assume
6110d1a0d267SMarcel Moolenaar 	     * the original field is a match.
6111d1a0d267SMarcel Moolenaar 	     */
6112d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
6113d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
6114d1a0d267SMarcel Moolenaar 		    break;
6115d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == newp->xfi_ftype)
6116d1a0d267SMarcel Moolenaar 		    goto copy_it; /* Assumably we have a match */
6117d1a0d267SMarcel Moolenaar 	    }
6118d1a0d267SMarcel Moolenaar 	    continue;
6119d1a0d267SMarcel Moolenaar 	}
6120d1a0d267SMarcel Moolenaar 
6121d1a0d267SMarcel Moolenaar 	/*
6122d1a0d267SMarcel Moolenaar 	 * Found a match; copy over appropriate fields
6123d1a0d267SMarcel Moolenaar 	 */
6124d1a0d267SMarcel Moolenaar     copy_it:
6125d1a0d267SMarcel Moolenaar 	newp->xfi_flags = oldp->xfi_flags;
6126d1a0d267SMarcel Moolenaar 	newp->xfi_fnum = oldp->xfi_fnum;
6127d1a0d267SMarcel Moolenaar 	newp->xfi_format = oldp->xfi_format;
6128d1a0d267SMarcel Moolenaar 	newp->xfi_flen = oldp->xfi_flen;
6129d1a0d267SMarcel Moolenaar 	newp->xfi_encoding = oldp->xfi_encoding;
6130d1a0d267SMarcel Moolenaar 	newp->xfi_elen = oldp->xfi_elen;
6131d1a0d267SMarcel Moolenaar     }
6132d1a0d267SMarcel Moolenaar 
6133d1a0d267SMarcel Moolenaar     *reorderedp = reordered;
6134d1a0d267SMarcel Moolenaar     if (reordered) {
6135d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
6136d1a0d267SMarcel Moolenaar 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
6137d1a0d267SMarcel Moolenaar     }
6138d1a0d267SMarcel Moolenaar 
6139d1a0d267SMarcel Moolenaar     return 0;
6140d1a0d267SMarcel Moolenaar }
6141d1a0d267SMarcel Moolenaar 
6142d1a0d267SMarcel Moolenaar /*
6143d1a0d267SMarcel Moolenaar  * We don't want to make gettext() calls here with a complete format
6144d1a0d267SMarcel Moolenaar  * string, since that means changing a flag would mean a
6145d1a0d267SMarcel Moolenaar  * labor-intensive re-translation expense.  Instead we build a
6146d1a0d267SMarcel Moolenaar  * simplified form with a reduced level of detail, perform a lookup on
6147d1a0d267SMarcel Moolenaar  * that string and then re-insert the formating info.
6148d1a0d267SMarcel Moolenaar  *
6149d1a0d267SMarcel Moolenaar  * So something like:
6150d1a0d267SMarcel Moolenaar  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
6151d1a0d267SMarcel Moolenaar  * would have a lookup string of:
6152d1a0d267SMarcel Moolenaar  *   "close {:fd} returned {:error} {:test}\n"
6153d1a0d267SMarcel Moolenaar  *
6154d1a0d267SMarcel Moolenaar  * We also need to handling reordering of fields, where the gettext()
6155d1a0d267SMarcel Moolenaar  * reply string uses fields in a different order than the original
6156d1a0d267SMarcel Moolenaar  * format string:
6157d1a0d267SMarcel Moolenaar  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
6158d1a0d267SMarcel Moolenaar  * If we have to reorder fields within the message, then things get
6159d1a0d267SMarcel Moolenaar  * complicated.  See xo_gettext_rewrite_fields.
6160d1a0d267SMarcel Moolenaar  *
6161d1a0d267SMarcel Moolenaar  * Summary: i18n aighn't cheap.
6162d1a0d267SMarcel Moolenaar  */
6163d1a0d267SMarcel Moolenaar static const char *
xo_gettext_build_format(xo_handle_t * xop,xo_field_info_t * fields,int this_field,const char * fmt,char ** new_fmtp)616442ff34c3SPhil Shafer xo_gettext_build_format (xo_handle_t *xop,
616542ff34c3SPhil Shafer 			 xo_field_info_t *fields, int this_field,
6166d1a0d267SMarcel Moolenaar 			 const char *fmt, char **new_fmtp)
6167d1a0d267SMarcel Moolenaar {
6168d1a0d267SMarcel Moolenaar     if (xo_style_is_encoding(xop))
6169d1a0d267SMarcel Moolenaar 	goto bail;
6170d1a0d267SMarcel Moolenaar 
6171d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
6172d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
6173d1a0d267SMarcel Moolenaar 
6174d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields,
6175d1a0d267SMarcel Moolenaar 				   this_field, fmt, NULL))
6176d1a0d267SMarcel Moolenaar 	goto bail2;
6177d1a0d267SMarcel Moolenaar 
6178d1a0d267SMarcel Moolenaar     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
617976afb20cSPhil Shafer     if (gtfmt == NULL || gtfmt == fmt || xo_streq(gtfmt, fmt))
6180d1a0d267SMarcel Moolenaar 	goto bail2;
6181d1a0d267SMarcel Moolenaar 
6182d1a0d267SMarcel Moolenaar     char *new_fmt = xo_strndup(gtfmt, -1);
6183d1a0d267SMarcel Moolenaar     if (new_fmt == NULL)
6184d1a0d267SMarcel Moolenaar 	goto bail2;
6185d1a0d267SMarcel Moolenaar 
6186f2b7bf8aSPhil Shafer     xo_buf_cleanup(&xb);
6187f2b7bf8aSPhil Shafer 
6188d1a0d267SMarcel Moolenaar     *new_fmtp = new_fmt;
6189d1a0d267SMarcel Moolenaar     return new_fmt;
6190d1a0d267SMarcel Moolenaar 
6191d1a0d267SMarcel Moolenaar  bail2:
6192d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(&xb);
6193d1a0d267SMarcel Moolenaar  bail:
6194d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
6195d1a0d267SMarcel Moolenaar     return fmt;
6196d1a0d267SMarcel Moolenaar }
6197d1a0d267SMarcel Moolenaar 
6198d1a0d267SMarcel Moolenaar static void
xo_gettext_rebuild_content(xo_handle_t * xop,xo_field_info_t * fields,ssize_t * fstart,unsigned min_fstart,ssize_t * fend,unsigned max_fend)6199d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
62008a6eceffSPhil Shafer 			    ssize_t *fstart, unsigned min_fstart,
62018a6eceffSPhil Shafer 			    ssize_t *fend, unsigned max_fend)
6202d1a0d267SMarcel Moolenaar {
6203d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
6204d1a0d267SMarcel Moolenaar     char *buf;
62058a6eceffSPhil Shafer     ssize_t base = fstart[min_fstart];
62068a6eceffSPhil Shafer     ssize_t blen = fend[max_fend] - base;
6207d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
6208d1a0d267SMarcel Moolenaar 
6209d1a0d267SMarcel Moolenaar     if (blen == 0)
6210d1a0d267SMarcel Moolenaar 	return;
6211d1a0d267SMarcel Moolenaar 
6212d1a0d267SMarcel Moolenaar     buf = xo_realloc(NULL, blen);
6213d1a0d267SMarcel Moolenaar     if (buf == NULL)
6214d1a0d267SMarcel Moolenaar 	return;
6215d1a0d267SMarcel Moolenaar 
6216d1a0d267SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
6217d1a0d267SMarcel Moolenaar 
62188a6eceffSPhil Shafer     unsigned field = min_fstart, len, fnum;
62198a6eceffSPhil Shafer     ssize_t soff, doff = base;
6220d1a0d267SMarcel Moolenaar     xo_field_info_t *zp;
6221d1a0d267SMarcel Moolenaar 
6222d1a0d267SMarcel Moolenaar     /*
6223d1a0d267SMarcel Moolenaar      * Be aware there are two competing views of "field number": we
6224d1a0d267SMarcel Moolenaar      * want the user to thing in terms of "The {1:size}" where {G:},
6225d1a0d267SMarcel Moolenaar      * newlines, escaped braces, and text don't have numbers.  But is
6226d1a0d267SMarcel Moolenaar      * also the internal view, where we have an array of
6227d1a0d267SMarcel Moolenaar      * xo_field_info_t and every field have an index.  fnum, fstart[]
6228d1a0d267SMarcel Moolenaar      * and fend[] are the latter, but xfi_renum is the former.
6229d1a0d267SMarcel Moolenaar      */
6230d1a0d267SMarcel Moolenaar     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
6231d1a0d267SMarcel Moolenaar 	fnum = field;
6232d1a0d267SMarcel Moolenaar 	if (xfip->xfi_renum) {
6233d1a0d267SMarcel Moolenaar 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
6234d1a0d267SMarcel Moolenaar 	    fnum = zp ? zp - fields : field;
6235d1a0d267SMarcel Moolenaar 	}
6236d1a0d267SMarcel Moolenaar 
6237d1a0d267SMarcel Moolenaar 	soff = fstart[fnum];
6238d1a0d267SMarcel Moolenaar 	len = fend[fnum] - soff;
6239d1a0d267SMarcel Moolenaar 
6240d1a0d267SMarcel Moolenaar 	if (len > 0) {
6241d1a0d267SMarcel Moolenaar 	    soff -= base;
6242d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
6243d1a0d267SMarcel Moolenaar 	    doff += len;
6244d1a0d267SMarcel Moolenaar 	}
6245d1a0d267SMarcel Moolenaar     }
6246d1a0d267SMarcel Moolenaar 
6247d1a0d267SMarcel Moolenaar     xo_free(buf);
6248d1a0d267SMarcel Moolenaar }
6249d1a0d267SMarcel Moolenaar #else  /* HAVE_GETTEXT */
6250d1a0d267SMarcel Moolenaar static const char *
xo_gettext_build_format(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,int this_field UNUSED,const char * fmt UNUSED,char ** new_fmtp)6251d1a0d267SMarcel Moolenaar xo_gettext_build_format (xo_handle_t *xop UNUSED,
6252d1a0d267SMarcel Moolenaar 			 xo_field_info_t *fields UNUSED,
6253d1a0d267SMarcel Moolenaar 			 int this_field UNUSED,
6254d1a0d267SMarcel Moolenaar 			 const char *fmt UNUSED, char **new_fmtp)
6255d1a0d267SMarcel Moolenaar {
6256d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
6257d1a0d267SMarcel Moolenaar     return fmt;
6258d1a0d267SMarcel Moolenaar }
6259d1a0d267SMarcel Moolenaar 
6260d1a0d267SMarcel Moolenaar static int
xo_gettext_combine_formats(xo_handle_t * xop UNUSED,const char * fmt UNUSED,const char * gtfmt UNUSED,xo_field_info_t * old_fields UNUSED,xo_field_info_t * new_fields UNUSED,unsigned new_max_fields UNUSED,int * reorderedp UNUSED)6261d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
6262d1a0d267SMarcel Moolenaar 		    const char *gtfmt UNUSED,
6263d1a0d267SMarcel Moolenaar 		    xo_field_info_t *old_fields UNUSED,
6264d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields UNUSED,
6265d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields UNUSED,
6266d1a0d267SMarcel Moolenaar 		    int *reorderedp UNUSED)
6267d1a0d267SMarcel Moolenaar {
6268d1a0d267SMarcel Moolenaar     return -1;
6269d1a0d267SMarcel Moolenaar }
6270d1a0d267SMarcel Moolenaar 
6271d1a0d267SMarcel Moolenaar static void
xo_gettext_rebuild_content(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,ssize_t * fstart UNUSED,unsigned min_fstart UNUSED,ssize_t * fend UNUSED,unsigned max_fend UNUSED)6272d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
6273d1a0d267SMarcel Moolenaar 		    xo_field_info_t *fields UNUSED,
62748a6eceffSPhil Shafer 		    ssize_t *fstart UNUSED, unsigned min_fstart UNUSED,
62758a6eceffSPhil Shafer 		    ssize_t *fend UNUSED, unsigned max_fend UNUSED)
6276d1a0d267SMarcel Moolenaar {
6277d1a0d267SMarcel Moolenaar     return;
6278d1a0d267SMarcel Moolenaar }
6279d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
6280d1a0d267SMarcel Moolenaar 
6281d1a0d267SMarcel Moolenaar /*
628242ff34c3SPhil Shafer  * Emit a set of fields.  This is really the core of libxo.
6283d1a0d267SMarcel Moolenaar  */
62848a6eceffSPhil Shafer static ssize_t
xo_do_emit_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned max_fields,const char * fmt)628542ff34c3SPhil Shafer xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
628642ff34c3SPhil Shafer 		   unsigned max_fields, const char *fmt)
6287d1a0d267SMarcel Moolenaar {
6288d1a0d267SMarcel Moolenaar     int gettext_inuse = 0;
6289d1a0d267SMarcel Moolenaar     int gettext_changed = 0;
6290d1a0d267SMarcel Moolenaar     int gettext_reordered = 0;
629142ff34c3SPhil Shafer     unsigned ftype;
629242ff34c3SPhil Shafer     xo_xff_flags_t flags;
6293d1a0d267SMarcel Moolenaar     xo_field_info_t *new_fields = NULL;
629442ff34c3SPhil Shafer     xo_field_info_t *xfip;
629542ff34c3SPhil Shafer     unsigned field;
62968a6eceffSPhil Shafer     ssize_t rc = 0;
629742ff34c3SPhil Shafer 
6298d1a0d267SMarcel Moolenaar     int flush = XOF_ISSET(xop, XOF_FLUSH);
6299d1a0d267SMarcel Moolenaar     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
6300d1a0d267SMarcel Moolenaar     char *new_fmt = NULL;
6301d1a0d267SMarcel Moolenaar 
6302d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
6303d1a0d267SMarcel Moolenaar 	flush_line = 0;
6304d1a0d267SMarcel Moolenaar 
6305d1a0d267SMarcel Moolenaar     /*
6306d1a0d267SMarcel Moolenaar      * Some overhead for gettext; if the fields in the msgstr returned
6307d1a0d267SMarcel Moolenaar      * by gettext are reordered, then we need to record start and end
6308d1a0d267SMarcel Moolenaar      * for each field.  We'll go ahead and render the fields in the
6309d1a0d267SMarcel Moolenaar      * normal order, but later we can then reconstruct the reordered
6310d1a0d267SMarcel Moolenaar      * fields using these fstart/fend values.
6311d1a0d267SMarcel Moolenaar      */
6312d1a0d267SMarcel Moolenaar     unsigned flimit = max_fields * 2; /* Pessimistic limit */
6313d1a0d267SMarcel Moolenaar     unsigned min_fstart = flimit - 1;
6314d1a0d267SMarcel Moolenaar     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
63158a6eceffSPhil Shafer     ssize_t fstart[flimit];
6316d1a0d267SMarcel Moolenaar     bzero(fstart, flimit * sizeof(fstart[0]));
63178a6eceffSPhil Shafer     ssize_t fend[flimit];
6318d1a0d267SMarcel Moolenaar     bzero(fend, flimit * sizeof(fend[0]));
6319d1a0d267SMarcel Moolenaar 
6320f2b7bf8aSPhil Shafer     for (xfip = fields, field = 0; field < max_fields && xfip->xfi_ftype;
6321d1a0d267SMarcel Moolenaar 	 xfip++, field++) {
6322d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
6323d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
6324d1a0d267SMarcel Moolenaar 
6325d1a0d267SMarcel Moolenaar 	/* Record field start offset */
6326d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
6327d1a0d267SMarcel Moolenaar 	    fstart[field] = xo_buf_offset(&xop->xo_data);
6328d1a0d267SMarcel Moolenaar 	    if (min_fstart > field)
6329d1a0d267SMarcel Moolenaar 		min_fstart = field;
6330d1a0d267SMarcel Moolenaar 	}
6331d1a0d267SMarcel Moolenaar 
633242ff34c3SPhil Shafer 	const char *content = xfip->xfi_content;
63338a6eceffSPhil Shafer 	ssize_t clen = xfip->xfi_clen;
633442ff34c3SPhil Shafer 
633542ff34c3SPhil Shafer 	if (flags & XFF_ARGUMENT) {
633642ff34c3SPhil Shafer 	    /*
633742ff34c3SPhil Shafer 	     * Argument flag means the content isn't given in the descriptor,
633842ff34c3SPhil Shafer 	     * but as a UTF-8 string ('const char *') argument in xo_vap.
633942ff34c3SPhil Shafer 	     */
634042ff34c3SPhil Shafer 	    content = va_arg(xop->xo_vap, char *);
634142ff34c3SPhil Shafer 	    clen = content ? strlen(content) : 0;
634242ff34c3SPhil Shafer 	}
634342ff34c3SPhil Shafer 
6344d1a0d267SMarcel Moolenaar 	if (ftype == XO_ROLE_NEWLINE) {
6345d1a0d267SMarcel Moolenaar 	    xo_line_close(xop);
6346d1a0d267SMarcel Moolenaar 	    if (flush_line && xo_flush_h(xop) < 0)
6347d1a0d267SMarcel Moolenaar 		return -1;
6348d1a0d267SMarcel Moolenaar 	    goto bottom;
6349d1a0d267SMarcel Moolenaar 
6350d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_EBRACE) {
6351d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6352d1a0d267SMarcel Moolenaar 	    goto bottom;
6353d1a0d267SMarcel Moolenaar 
6354d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_TEXT) {
6355d1a0d267SMarcel Moolenaar 	    /* Normal text */
6356d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6357d1a0d267SMarcel Moolenaar 	    goto bottom;
6358d1a0d267SMarcel Moolenaar 	}
6359d1a0d267SMarcel Moolenaar 
6360d1a0d267SMarcel Moolenaar 	/*
6361d1a0d267SMarcel Moolenaar 	 * Notes and units need the 'w' flag handled before the content.
6362d1a0d267SMarcel Moolenaar 	 */
6363d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
6364d1a0d267SMarcel Moolenaar 	    if (flags & XFF_WS) {
6365d1a0d267SMarcel Moolenaar 		xo_format_content(xop, "padding", NULL, " ", 1,
6366d1a0d267SMarcel Moolenaar 				  NULL, 0, flags);
6367264104f2SPhil Shafer 		flags &= ~XFF_WS; /* Prevent later handling of this flag */
6368d1a0d267SMarcel Moolenaar 	    }
6369d1a0d267SMarcel Moolenaar 	}
6370d1a0d267SMarcel Moolenaar 
6371d1a0d267SMarcel Moolenaar 	if (ftype == 'V')
6372264104f2SPhil Shafer 	    xo_format_value(xop, content, clen, NULL, 0,
6373d1a0d267SMarcel Moolenaar 			    xfip->xfi_format, xfip->xfi_flen,
6374d1a0d267SMarcel Moolenaar 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
6375d1a0d267SMarcel Moolenaar 	else if (ftype == '[')
637642ff34c3SPhil Shafer 	    xo_anchor_start(xop, xfip, content, clen);
6377545ddfbeSMarcel Moolenaar 	else if (ftype == ']')
637842ff34c3SPhil Shafer 	    xo_anchor_stop(xop, xfip, content, clen);
6379788ca347SMarcel Moolenaar 	else if (ftype == 'C')
638042ff34c3SPhil Shafer 	    xo_format_colors(xop, xfip, content, clen);
6381545ddfbeSMarcel Moolenaar 
6382d1a0d267SMarcel Moolenaar 	else if (ftype == 'G') {
6383d1a0d267SMarcel Moolenaar 	    /*
6384d1a0d267SMarcel Moolenaar 	     * A {G:domain} field; disect the domain name and translate
6385d1a0d267SMarcel Moolenaar 	     * the remaining portion of the input string.  If the user
6386d1a0d267SMarcel Moolenaar 	     * didn't put the {G:} at the start of the format string, then
6387d1a0d267SMarcel Moolenaar 	     * assumably they just want us to translate the rest of it.
6388d1a0d267SMarcel Moolenaar 	     * Since gettext returns strings in a static buffer, we make
6389d1a0d267SMarcel Moolenaar 	     * a copy in new_fmt.
6390d1a0d267SMarcel Moolenaar 	     */
639142ff34c3SPhil Shafer 	    xo_set_gettext_domain(xop, xfip, content, clen);
6392d1a0d267SMarcel Moolenaar 
6393d1a0d267SMarcel Moolenaar 	    if (!gettext_inuse) { /* Only translate once */
6394d1a0d267SMarcel Moolenaar 		gettext_inuse = 1;
6395d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6396d1a0d267SMarcel Moolenaar 		    xo_free(new_fmt);
6397d1a0d267SMarcel Moolenaar 		    new_fmt = NULL;
6398545ddfbeSMarcel Moolenaar 		}
6399545ddfbeSMarcel Moolenaar 
6400d1a0d267SMarcel Moolenaar 		xo_gettext_build_format(xop, fields, field,
6401d1a0d267SMarcel Moolenaar 					xfip->xfi_next, &new_fmt);
6402d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6403d1a0d267SMarcel Moolenaar 		    gettext_changed = 1;
6404d1a0d267SMarcel Moolenaar 
6405d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6406d1a0d267SMarcel Moolenaar 
6407d1a0d267SMarcel Moolenaar 		    if (++new_max_fields < max_fields)
6408d1a0d267SMarcel Moolenaar 			new_max_fields = max_fields;
6409d1a0d267SMarcel Moolenaar 
6410d1a0d267SMarcel Moolenaar 		    /* Leave a blank slot at the beginning */
64118a6eceffSPhil Shafer 		    ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6412d1a0d267SMarcel Moolenaar 		    new_fields = alloca(sz);
6413d1a0d267SMarcel Moolenaar 		    bzero(new_fields, sz);
6414d1a0d267SMarcel Moolenaar 
6415d1a0d267SMarcel Moolenaar 		    if (!xo_parse_fields(xop, new_fields + 1,
6416d1a0d267SMarcel Moolenaar 					 new_max_fields, new_fmt)) {
6417d1a0d267SMarcel Moolenaar 			gettext_reordered = 0;
6418d1a0d267SMarcel Moolenaar 
6419d1a0d267SMarcel Moolenaar 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6420d1a0d267SMarcel Moolenaar 					fields, new_fields + 1,
6421d1a0d267SMarcel Moolenaar 					new_max_fields, &gettext_reordered)) {
6422d1a0d267SMarcel Moolenaar 
6423d1a0d267SMarcel Moolenaar 			    if (gettext_reordered) {
6424d1a0d267SMarcel Moolenaar 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6425d1a0d267SMarcel Moolenaar 				    xo_failure(xop, "gettext finds reordered "
6426d1a0d267SMarcel Moolenaar 					       "fields in '%s' and '%s'",
6427d1a0d267SMarcel Moolenaar 					       xo_printable(fmt),
6428d1a0d267SMarcel Moolenaar 					       xo_printable(new_fmt));
6429d1a0d267SMarcel Moolenaar 				flush_line = 0; /* Must keep at content */
6430d1a0d267SMarcel Moolenaar 				XOIF_SET(xop, XOIF_REORDER);
6431d1a0d267SMarcel Moolenaar 			    }
6432d1a0d267SMarcel Moolenaar 
6433d1a0d267SMarcel Moolenaar 			    field = -1; /* Will be incremented at top of loop */
6434d1a0d267SMarcel Moolenaar 			    xfip = new_fields;
6435d1a0d267SMarcel Moolenaar 			    max_fields = new_max_fields;
6436d1a0d267SMarcel Moolenaar 			}
6437d1a0d267SMarcel Moolenaar 		    }
6438d1a0d267SMarcel Moolenaar 		}
6439d1a0d267SMarcel Moolenaar 	    }
6440d1a0d267SMarcel Moolenaar 	    continue;
6441d1a0d267SMarcel Moolenaar 
644242ff34c3SPhil Shafer 	} else  if (clen || xfip->xfi_format) {
6443d1a0d267SMarcel Moolenaar 
6444d1a0d267SMarcel Moolenaar 	    const char *class_name = xo_class_name(ftype);
6445d1a0d267SMarcel Moolenaar 	    if (class_name)
6446d1a0d267SMarcel Moolenaar 		xo_format_content(xop, class_name, xo_tag_name(ftype),
644742ff34c3SPhil Shafer 				  content, clen,
6448d1a0d267SMarcel Moolenaar 				  xfip->xfi_format, xfip->xfi_flen, flags);
644931337658SMarcel Moolenaar 	    else if (ftype == 'T')
645042ff34c3SPhil Shafer 		xo_format_title(xop, xfip, content, clen);
6451d1a0d267SMarcel Moolenaar 	    else if (ftype == 'U')
645242ff34c3SPhil Shafer 		xo_format_units(xop, xfip, content, clen);
6453d1a0d267SMarcel Moolenaar 	    else
6454d1a0d267SMarcel Moolenaar 		xo_failure(xop, "unknown field type: '%c'", ftype);
6455545ddfbeSMarcel Moolenaar 	}
645631337658SMarcel Moolenaar 
645731337658SMarcel Moolenaar 	if (flags & XFF_COLON)
6458d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
645931337658SMarcel Moolenaar 
6460d1a0d267SMarcel Moolenaar 	if (flags & XFF_WS)
6461d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6462d1a0d267SMarcel Moolenaar 
6463d1a0d267SMarcel Moolenaar     bottom:
6464d1a0d267SMarcel Moolenaar 	/* Record the end-of-field offset */
6465d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
6466d1a0d267SMarcel Moolenaar 	    fend[field] = xo_buf_offset(&xop->xo_data);
6467d1a0d267SMarcel Moolenaar 	    max_fend = field;
646831337658SMarcel Moolenaar 	}
646931337658SMarcel Moolenaar     }
647031337658SMarcel Moolenaar 
6471d1a0d267SMarcel Moolenaar     if (gettext_changed && gettext_reordered) {
6472d1a0d267SMarcel Moolenaar 	/* Final step: rebuild the content using the rendered fields */
6473d1a0d267SMarcel Moolenaar 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6474d1a0d267SMarcel Moolenaar 				   fend, max_fend);
6475d1a0d267SMarcel Moolenaar     }
6476d1a0d267SMarcel Moolenaar 
6477d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_REORDER);
6478d1a0d267SMarcel Moolenaar 
6479ee5cf116SPhil Shafer     /*
6480ee5cf116SPhil Shafer      * If we've got enough data, flush it.
6481ee5cf116SPhil Shafer      */
6482ee5cf116SPhil Shafer     if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER)
6483ee5cf116SPhil Shafer 	flush = 1;
6484ee5cf116SPhil Shafer 
648531337658SMarcel Moolenaar     /* If we don't have an anchor, write the text out */
6486d1a0d267SMarcel Moolenaar     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6487406a584dSPhil Shafer 	if (xo_flush_h(xop) < 0)
6488545ddfbeSMarcel Moolenaar 	    rc = -1;
6489545ddfbeSMarcel Moolenaar     }
649031337658SMarcel Moolenaar 
6491d1a0d267SMarcel Moolenaar     if (new_fmt)
6492d1a0d267SMarcel Moolenaar 	xo_free(new_fmt);
6493d1a0d267SMarcel Moolenaar 
6494d1a0d267SMarcel Moolenaar     /*
6495d1a0d267SMarcel Moolenaar      * We've carried the gettext domainname inside our handle just for
6496d1a0d267SMarcel Moolenaar      * convenience, but we need to ensure it doesn't survive across
6497d1a0d267SMarcel Moolenaar      * xo_emit calls.
6498d1a0d267SMarcel Moolenaar      */
6499d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
6500d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
6501d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
6502d1a0d267SMarcel Moolenaar     }
6503d1a0d267SMarcel Moolenaar 
65048a6eceffSPhil Shafer     return (rc < 0) ? rc : xop->xo_columns;
650531337658SMarcel Moolenaar }
650631337658SMarcel Moolenaar 
6507d1a0d267SMarcel Moolenaar /*
650842ff34c3SPhil Shafer  * Parse and emit a set of fields
650942ff34c3SPhil Shafer  */
651042ff34c3SPhil Shafer static int
xo_do_emit(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt)651142ff34c3SPhil Shafer xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
651242ff34c3SPhil Shafer {
651342ff34c3SPhil Shafer     xop->xo_columns = 0;	/* Always reset it */
651442ff34c3SPhil Shafer     xop->xo_errno = errno;	/* Save for "%m" */
651542ff34c3SPhil Shafer 
651642ff34c3SPhil Shafer     if (fmt == NULL)
651742ff34c3SPhil Shafer 	return 0;
651842ff34c3SPhil Shafer 
651942ff34c3SPhil Shafer     unsigned max_fields;
652042ff34c3SPhil Shafer     xo_field_info_t *fields = NULL;
652142ff34c3SPhil Shafer 
652242ff34c3SPhil Shafer     /* Adjust XOEF_RETAIN based on global flags */
652342ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_ALL))
652442ff34c3SPhil Shafer 	flags |= XOEF_RETAIN;
652542ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_NONE))
652642ff34c3SPhil Shafer 	flags &= ~XOEF_RETAIN;
652742ff34c3SPhil Shafer 
652842ff34c3SPhil Shafer     /*
652942ff34c3SPhil Shafer      * Check for 'retain' flag, telling us to retain the field
653042ff34c3SPhil Shafer      * information.  If we've already saved it, then we can avoid
653142ff34c3SPhil Shafer      * re-parsing the format string.
653242ff34c3SPhil Shafer      */
653342ff34c3SPhil Shafer     if (!(flags & XOEF_RETAIN)
653442ff34c3SPhil Shafer 	|| xo_retain_find(fmt, &fields, &max_fields) != 0
653542ff34c3SPhil Shafer 	|| fields == NULL) {
653642ff34c3SPhil Shafer 
653742ff34c3SPhil Shafer 	/* Nothing retained; parse the format string */
653842ff34c3SPhil Shafer 	max_fields = xo_count_fields(xop, fmt);
653942ff34c3SPhil Shafer 	fields = alloca(max_fields * sizeof(fields[0]));
654042ff34c3SPhil Shafer 	bzero(fields, max_fields * sizeof(fields[0]));
654142ff34c3SPhil Shafer 
654242ff34c3SPhil Shafer 	if (xo_parse_fields(xop, fields, max_fields, fmt))
654342ff34c3SPhil Shafer 	    return -1;		/* Warning already displayed */
654442ff34c3SPhil Shafer 
654542ff34c3SPhil Shafer 	if (flags & XOEF_RETAIN) {
654642ff34c3SPhil Shafer 	    /* Retain the info */
654742ff34c3SPhil Shafer 	    xo_retain_add(fmt, fields, max_fields);
654842ff34c3SPhil Shafer 	}
654942ff34c3SPhil Shafer     }
655042ff34c3SPhil Shafer 
655142ff34c3SPhil Shafer     return xo_do_emit_fields(xop, fields, max_fields, fmt);
655242ff34c3SPhil Shafer }
655342ff34c3SPhil Shafer 
655442ff34c3SPhil Shafer /*
6555d1a0d267SMarcel Moolenaar  * Rebuild a format string in a gettext-friendly format.  This function
6556d1a0d267SMarcel Moolenaar  * is exposed to tools can perform this function.  See xo(1).
6557d1a0d267SMarcel Moolenaar  */
6558d1a0d267SMarcel Moolenaar char *
xo_simplify_format(xo_handle_t * xop,const char * fmt,int with_numbers,xo_simplify_field_func_t field_cb)6559d1a0d267SMarcel Moolenaar xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6560d1a0d267SMarcel Moolenaar 		    xo_simplify_field_func_t field_cb)
6561d1a0d267SMarcel Moolenaar {
6562d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
6563d1a0d267SMarcel Moolenaar 
6564d1a0d267SMarcel Moolenaar     xop->xo_columns = 0;	/* Always reset it */
6565d1a0d267SMarcel Moolenaar     xop->xo_errno = errno;	/* Save for "%m" */
6566d1a0d267SMarcel Moolenaar 
6567d1a0d267SMarcel Moolenaar     unsigned max_fields = xo_count_fields(xop, fmt);
6568d1a0d267SMarcel Moolenaar     xo_field_info_t fields[max_fields];
6569d1a0d267SMarcel Moolenaar 
6570d1a0d267SMarcel Moolenaar     bzero(fields, max_fields * sizeof(fields[0]));
6571d1a0d267SMarcel Moolenaar 
6572d1a0d267SMarcel Moolenaar     if (xo_parse_fields(xop, fields, max_fields, fmt))
6573d1a0d267SMarcel Moolenaar 	return NULL;		/* Warning already displayed */
6574d1a0d267SMarcel Moolenaar 
6575d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
6576d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
6577d1a0d267SMarcel Moolenaar 
6578d1a0d267SMarcel Moolenaar     if (with_numbers)
6579d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
6580d1a0d267SMarcel Moolenaar 
6581d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6582d1a0d267SMarcel Moolenaar 	return NULL;
6583d1a0d267SMarcel Moolenaar 
6584d1a0d267SMarcel Moolenaar     return xb.xb_bufp;
6585d1a0d267SMarcel Moolenaar }
6586d1a0d267SMarcel Moolenaar 
65878a6eceffSPhil Shafer xo_ssize_t
xo_emit_hv(xo_handle_t * xop,const char * fmt,va_list vap)658831337658SMarcel Moolenaar xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
658931337658SMarcel Moolenaar {
65908a6eceffSPhil Shafer     ssize_t rc;
659131337658SMarcel Moolenaar 
659231337658SMarcel Moolenaar     xop = xo_default(xop);
659331337658SMarcel Moolenaar     va_copy(xop->xo_vap, vap);
659442ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
659531337658SMarcel Moolenaar     va_end(xop->xo_vap);
659631337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
659731337658SMarcel Moolenaar 
659831337658SMarcel Moolenaar     return rc;
659931337658SMarcel Moolenaar }
660031337658SMarcel Moolenaar 
66018a6eceffSPhil Shafer xo_ssize_t
xo_emit_h(xo_handle_t * xop,const char * fmt,...)660231337658SMarcel Moolenaar xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
660331337658SMarcel Moolenaar {
66048a6eceffSPhil Shafer     ssize_t rc;
660531337658SMarcel Moolenaar 
660631337658SMarcel Moolenaar     xop = xo_default(xop);
660731337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
660842ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
660931337658SMarcel Moolenaar     va_end(xop->xo_vap);
661031337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
661131337658SMarcel Moolenaar 
661231337658SMarcel Moolenaar     return rc;
661331337658SMarcel Moolenaar }
661431337658SMarcel Moolenaar 
66158a6eceffSPhil Shafer xo_ssize_t
xo_emit(const char * fmt,...)661631337658SMarcel Moolenaar xo_emit (const char *fmt, ...)
661731337658SMarcel Moolenaar {
661831337658SMarcel Moolenaar     xo_handle_t *xop = xo_default(NULL);
66198a6eceffSPhil Shafer     ssize_t rc;
662031337658SMarcel Moolenaar 
662131337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
662242ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
662331337658SMarcel Moolenaar     va_end(xop->xo_vap);
662431337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
662531337658SMarcel Moolenaar 
662631337658SMarcel Moolenaar     return rc;
662731337658SMarcel Moolenaar }
662831337658SMarcel Moolenaar 
66298a6eceffSPhil Shafer xo_ssize_t
xo_emit_hvf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,va_list vap)663042ff34c3SPhil Shafer xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
663142ff34c3SPhil Shafer 	     const char *fmt, va_list vap)
663242ff34c3SPhil Shafer {
66338a6eceffSPhil Shafer     ssize_t rc;
663442ff34c3SPhil Shafer 
663542ff34c3SPhil Shafer     xop = xo_default(xop);
663642ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
663742ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
663842ff34c3SPhil Shafer     va_end(xop->xo_vap);
663942ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
664042ff34c3SPhil Shafer 
664142ff34c3SPhil Shafer     return rc;
664242ff34c3SPhil Shafer }
664342ff34c3SPhil Shafer 
66448a6eceffSPhil Shafer xo_ssize_t
xo_emit_hf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,...)664542ff34c3SPhil Shafer xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
664642ff34c3SPhil Shafer {
66478a6eceffSPhil Shafer     ssize_t rc;
664842ff34c3SPhil Shafer 
664942ff34c3SPhil Shafer     xop = xo_default(xop);
665042ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
665142ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
665242ff34c3SPhil Shafer     va_end(xop->xo_vap);
665342ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
665442ff34c3SPhil Shafer 
665542ff34c3SPhil Shafer     return rc;
665642ff34c3SPhil Shafer }
665742ff34c3SPhil Shafer 
66588a6eceffSPhil Shafer xo_ssize_t
xo_emit_f(xo_emit_flags_t flags,const char * fmt,...)665942ff34c3SPhil Shafer xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
666042ff34c3SPhil Shafer {
666142ff34c3SPhil Shafer     xo_handle_t *xop = xo_default(NULL);
66628a6eceffSPhil Shafer     ssize_t rc;
666342ff34c3SPhil Shafer 
666442ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
666542ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
666642ff34c3SPhil Shafer     va_end(xop->xo_vap);
666742ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
666842ff34c3SPhil Shafer 
666942ff34c3SPhil Shafer     return rc;
667042ff34c3SPhil Shafer }
667142ff34c3SPhil Shafer 
667242ff34c3SPhil Shafer /*
667342ff34c3SPhil Shafer  * Emit a single field by providing the info information typically provided
667442ff34c3SPhil Shafer  * inside the field description (role, modifiers, and formats).  This is
667542ff34c3SPhil Shafer  * a convenience function to avoid callers using snprintf to build field
667642ff34c3SPhil Shafer  * descriptions.
667742ff34c3SPhil Shafer  */
66788a6eceffSPhil Shafer xo_ssize_t
xo_emit_field_hv(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,va_list vap)667942ff34c3SPhil Shafer xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
668042ff34c3SPhil Shafer 		  const char *fmt, const char *efmt,
668142ff34c3SPhil Shafer 		  va_list vap)
668242ff34c3SPhil Shafer {
66838a6eceffSPhil Shafer     ssize_t rc;
668442ff34c3SPhil Shafer 
668542ff34c3SPhil Shafer     xop = xo_default(xop);
668642ff34c3SPhil Shafer 
668742ff34c3SPhil Shafer     if (rolmod == NULL)
668842ff34c3SPhil Shafer 	rolmod = "V";
668942ff34c3SPhil Shafer 
669042ff34c3SPhil Shafer     xo_field_info_t xfi;
669142ff34c3SPhil Shafer 
669242ff34c3SPhil Shafer     bzero(&xfi, sizeof(xfi));
669342ff34c3SPhil Shafer 
669442ff34c3SPhil Shafer     const char *cp;
669542ff34c3SPhil Shafer     cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
669642ff34c3SPhil Shafer     if (cp == NULL)
669742ff34c3SPhil Shafer 	return -1;
669842ff34c3SPhil Shafer 
669942ff34c3SPhil Shafer     xfi.xfi_start = fmt;
670042ff34c3SPhil Shafer     xfi.xfi_content = contents;
670142ff34c3SPhil Shafer     xfi.xfi_format = fmt;
670242ff34c3SPhil Shafer     xfi.xfi_encoding = efmt;
670342ff34c3SPhil Shafer     xfi.xfi_clen = contents ? strlen(contents) : 0;
670442ff34c3SPhil Shafer     xfi.xfi_flen = fmt ? strlen(fmt) : 0;
670542ff34c3SPhil Shafer     xfi.xfi_elen = efmt ? strlen(efmt) : 0;
670642ff34c3SPhil Shafer 
670742ff34c3SPhil Shafer     /* If we have content, then we have a default format */
670842ff34c3SPhil Shafer     if (contents && fmt == NULL
670942ff34c3SPhil Shafer 		&& xo_role_wants_default_format(xfi.xfi_ftype)) {
671042ff34c3SPhil Shafer 	xfi.xfi_format = xo_default_format;
671142ff34c3SPhil Shafer 	xfi.xfi_flen = 2;
671242ff34c3SPhil Shafer     }
671342ff34c3SPhil Shafer 
671442ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
671542ff34c3SPhil Shafer 
671642ff34c3SPhil Shafer     rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
671742ff34c3SPhil Shafer 
671842ff34c3SPhil Shafer     va_end(xop->xo_vap);
671942ff34c3SPhil Shafer 
672042ff34c3SPhil Shafer     return rc;
672142ff34c3SPhil Shafer }
672242ff34c3SPhil Shafer 
67238a6eceffSPhil Shafer xo_ssize_t
xo_emit_field_h(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)672442ff34c3SPhil Shafer xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
672542ff34c3SPhil Shafer 		 const char *fmt, const char *efmt, ...)
672642ff34c3SPhil Shafer {
67278a6eceffSPhil Shafer     ssize_t rc;
672842ff34c3SPhil Shafer     va_list vap;
672942ff34c3SPhil Shafer 
673042ff34c3SPhil Shafer     va_start(vap, efmt);
673142ff34c3SPhil Shafer     rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
673242ff34c3SPhil Shafer     va_end(vap);
673342ff34c3SPhil Shafer 
673442ff34c3SPhil Shafer     return rc;
673542ff34c3SPhil Shafer }
673642ff34c3SPhil Shafer 
67378a6eceffSPhil Shafer xo_ssize_t
xo_emit_field(const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)673842ff34c3SPhil Shafer xo_emit_field (const char *rolmod, const char *contents,
673942ff34c3SPhil Shafer 	       const char *fmt, const char *efmt, ...)
674042ff34c3SPhil Shafer {
67418a6eceffSPhil Shafer     ssize_t rc;
674242ff34c3SPhil Shafer     va_list vap;
674342ff34c3SPhil Shafer 
674442ff34c3SPhil Shafer     va_start(vap, efmt);
674542ff34c3SPhil Shafer     rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
674642ff34c3SPhil Shafer     va_end(vap);
674742ff34c3SPhil Shafer 
674842ff34c3SPhil Shafer     return rc;
674942ff34c3SPhil Shafer }
675042ff34c3SPhil Shafer 
67518a6eceffSPhil Shafer xo_ssize_t
xo_attr_hv(xo_handle_t * xop,const char * name,const char * fmt,va_list vap)675231337658SMarcel Moolenaar xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
675331337658SMarcel Moolenaar {
67548a6eceffSPhil Shafer     const ssize_t extra = 5; 	/* space, equals, quote, quote, and nul */
675531337658SMarcel Moolenaar     xop = xo_default(xop);
675631337658SMarcel Moolenaar 
67578a6eceffSPhil Shafer     ssize_t rc = 0;
67588a6eceffSPhil Shafer     ssize_t nlen = strlen(name);
675931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_attrs;
67608a6eceffSPhil Shafer     ssize_t name_offset, value_offset;
676131337658SMarcel Moolenaar 
6762d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6763d1a0d267SMarcel Moolenaar     case XO_STYLE_XML:
676431337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, nlen + extra))
676531337658SMarcel Moolenaar 	    return -1;
676631337658SMarcel Moolenaar 
676731337658SMarcel Moolenaar 	*xbp->xb_curp++ = ' ';
676831337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, name, nlen);
676931337658SMarcel Moolenaar 	xbp->xb_curp += nlen;
677031337658SMarcel Moolenaar 	*xbp->xb_curp++ = '=';
677131337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
677231337658SMarcel Moolenaar 
6773d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
677431337658SMarcel Moolenaar 
6775d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
677631337658SMarcel Moolenaar 	    rc = xo_escape_xml(xbp, rc, 1);
677731337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
677831337658SMarcel Moolenaar 	}
677931337658SMarcel Moolenaar 
678031337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, 2))
678131337658SMarcel Moolenaar 	    return -1;
678231337658SMarcel Moolenaar 
678331337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
678431337658SMarcel Moolenaar 	*xbp->xb_curp = '\0';
678531337658SMarcel Moolenaar 
6786d1a0d267SMarcel Moolenaar 	rc += nlen + extra;
6787d1a0d267SMarcel Moolenaar 	break;
6788d1a0d267SMarcel Moolenaar 
6789d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6790d1a0d267SMarcel Moolenaar 	name_offset = xo_buf_offset(xbp);
6791d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, name, nlen);
6792d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "", 1);
6793d1a0d267SMarcel Moolenaar 
6794d1a0d267SMarcel Moolenaar 	value_offset = xo_buf_offset(xbp);
6795d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6796d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
6797d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += rc;
6798d1a0d267SMarcel Moolenaar 	    *xbp->xb_curp = '\0';
6799d1a0d267SMarcel Moolenaar 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6800d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, name_offset),
6801f2b7bf8aSPhil Shafer 				   xo_buf_data(xbp, value_offset), 0);
6802d1a0d267SMarcel Moolenaar 	}
6803d1a0d267SMarcel Moolenaar     }
6804d1a0d267SMarcel Moolenaar 
6805d1a0d267SMarcel Moolenaar     return rc;
680631337658SMarcel Moolenaar }
680731337658SMarcel Moolenaar 
68088a6eceffSPhil Shafer xo_ssize_t
xo_attr_h(xo_handle_t * xop,const char * name,const char * fmt,...)680931337658SMarcel Moolenaar xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
681031337658SMarcel Moolenaar {
68118a6eceffSPhil Shafer     ssize_t rc;
681231337658SMarcel Moolenaar     va_list vap;
681331337658SMarcel Moolenaar 
681431337658SMarcel Moolenaar     va_start(vap, fmt);
681531337658SMarcel Moolenaar     rc = xo_attr_hv(xop, name, fmt, vap);
681631337658SMarcel Moolenaar     va_end(vap);
681731337658SMarcel Moolenaar 
681831337658SMarcel Moolenaar     return rc;
681931337658SMarcel Moolenaar }
682031337658SMarcel Moolenaar 
68218a6eceffSPhil Shafer xo_ssize_t
xo_attr(const char * name,const char * fmt,...)682231337658SMarcel Moolenaar xo_attr (const char *name, const char *fmt, ...)
682331337658SMarcel Moolenaar {
68248a6eceffSPhil Shafer     ssize_t rc;
682531337658SMarcel Moolenaar     va_list vap;
682631337658SMarcel Moolenaar 
682731337658SMarcel Moolenaar     va_start(vap, fmt);
682831337658SMarcel Moolenaar     rc = xo_attr_hv(NULL, name, fmt, vap);
682931337658SMarcel Moolenaar     va_end(vap);
683031337658SMarcel Moolenaar 
683131337658SMarcel Moolenaar     return rc;
683231337658SMarcel Moolenaar }
683331337658SMarcel Moolenaar 
683431337658SMarcel Moolenaar static void
xo_depth_change(xo_handle_t * xop,const char * name,int delta,int indent,xo_state_t state,xo_xsf_flags_t flags)683531337658SMarcel Moolenaar xo_depth_change (xo_handle_t *xop, const char *name,
6836545ddfbeSMarcel Moolenaar 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
683731337658SMarcel Moolenaar {
6838788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6839545ddfbeSMarcel Moolenaar 	indent = 0;
6840545ddfbeSMarcel Moolenaar 
6841d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_DTRT))
684231337658SMarcel Moolenaar 	flags |= XSF_DTRT;
684331337658SMarcel Moolenaar 
684431337658SMarcel Moolenaar     if (delta >= 0) {			/* Push operation */
684531337658SMarcel Moolenaar 	if (xo_depth_check(xop, xop->xo_depth + delta))
684631337658SMarcel Moolenaar 	    return;
684731337658SMarcel Moolenaar 
684831337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
684931337658SMarcel Moolenaar 	xsp->xs_flags = flags;
6850545ddfbeSMarcel Moolenaar 	xsp->xs_state = state;
685131337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
685231337658SMarcel Moolenaar 
6853545ddfbeSMarcel Moolenaar 	if (name == NULL)
6854545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
685531337658SMarcel Moolenaar 
6856d1a0d267SMarcel Moolenaar 	xsp->xs_name = xo_strndup(name, -1);
685731337658SMarcel Moolenaar 
685831337658SMarcel Moolenaar     } else {			/* Pop operation */
685931337658SMarcel Moolenaar 	if (xop->xo_depth == 0) {
6860d1a0d267SMarcel Moolenaar 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
686131337658SMarcel Moolenaar 		xo_failure(xop, "close with empty stack: '%s'", name);
686231337658SMarcel Moolenaar 	    return;
686331337658SMarcel Moolenaar 	}
686431337658SMarcel Moolenaar 
686531337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6866d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN)) {
686731337658SMarcel Moolenaar 	    const char *top = xsp->xs_name;
686876afb20cSPhil Shafer 	    if (top != NULL && name != NULL && !xo_streq(name, top)) {
686931337658SMarcel Moolenaar 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
687031337658SMarcel Moolenaar 			      name, top);
687131337658SMarcel Moolenaar 		return;
687231337658SMarcel Moolenaar 	    }
687331337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
687431337658SMarcel Moolenaar 		xo_failure(xop, "list close on list confict: '%s'",
687531337658SMarcel Moolenaar 			      name);
687631337658SMarcel Moolenaar 		return;
687731337658SMarcel Moolenaar 	    }
687831337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
687931337658SMarcel Moolenaar 		xo_failure(xop, "list close on instance confict: '%s'",
688031337658SMarcel Moolenaar 			      name);
688131337658SMarcel Moolenaar 		return;
688231337658SMarcel Moolenaar 	    }
688331337658SMarcel Moolenaar 	}
688431337658SMarcel Moolenaar 
688531337658SMarcel Moolenaar 	if (xsp->xs_name) {
688631337658SMarcel Moolenaar 	    xo_free(xsp->xs_name);
688731337658SMarcel Moolenaar 	    xsp->xs_name = NULL;
688831337658SMarcel Moolenaar 	}
688931337658SMarcel Moolenaar 	if (xsp->xs_keys) {
689031337658SMarcel Moolenaar 	    xo_free(xsp->xs_keys);
689131337658SMarcel Moolenaar 	    xsp->xs_keys = NULL;
689231337658SMarcel Moolenaar 	}
689331337658SMarcel Moolenaar     }
689431337658SMarcel Moolenaar 
689531337658SMarcel Moolenaar     xop->xo_depth += delta;	/* Record new depth */
689631337658SMarcel Moolenaar     xop->xo_indent += indent;
689731337658SMarcel Moolenaar }
689831337658SMarcel Moolenaar 
689931337658SMarcel Moolenaar void
xo_set_depth(xo_handle_t * xop,int depth)690031337658SMarcel Moolenaar xo_set_depth (xo_handle_t *xop, int depth)
690131337658SMarcel Moolenaar {
690231337658SMarcel Moolenaar     xop = xo_default(xop);
690331337658SMarcel Moolenaar 
690431337658SMarcel Moolenaar     if (xo_depth_check(xop, depth))
690531337658SMarcel Moolenaar 	return;
690631337658SMarcel Moolenaar 
690731337658SMarcel Moolenaar     xop->xo_depth += depth;
690831337658SMarcel Moolenaar     xop->xo_indent += depth;
6909406a584dSPhil Shafer 
6910406a584dSPhil Shafer     /*
6911406a584dSPhil Shafer      * Handling the "top wrapper" for JSON is a bit of a pain.  Here
6912406a584dSPhil Shafer      * we need to detect that the depth has been changed to set the
6913406a584dSPhil Shafer      * "XOIF_TOP_EMITTED" flag correctly.
6914406a584dSPhil Shafer      */
6915406a584dSPhil Shafer     if (xop->xo_style == XO_STYLE_JSON
6916406a584dSPhil Shafer 	&& !XOF_ISSET(xop, XOF_NO_TOP) && xop->xo_depth > 0)
6917406a584dSPhil Shafer 	XOIF_SET(xop, XOIF_TOP_EMITTED);
691831337658SMarcel Moolenaar }
691931337658SMarcel Moolenaar 
692031337658SMarcel Moolenaar static xo_xsf_flags_t
xo_stack_flags(xo_xof_flags_t xflags)69218a6eceffSPhil Shafer xo_stack_flags (xo_xof_flags_t xflags)
692231337658SMarcel Moolenaar {
692331337658SMarcel Moolenaar     if (xflags & XOF_DTRT)
692431337658SMarcel Moolenaar 	return XSF_DTRT;
692531337658SMarcel Moolenaar     return 0;
692631337658SMarcel Moolenaar }
692731337658SMarcel Moolenaar 
6928788ca347SMarcel Moolenaar static void
xo_emit_top(xo_handle_t * xop,const char * ppn)6929788ca347SMarcel Moolenaar xo_emit_top (xo_handle_t *xop, const char *ppn)
6930788ca347SMarcel Moolenaar {
6931788ca347SMarcel Moolenaar     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6932d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_TOP_EMITTED);
6933788ca347SMarcel Moolenaar 
6934788ca347SMarcel Moolenaar     if (xop->xo_version) {
6935788ca347SMarcel Moolenaar 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6936788ca347SMarcel Moolenaar 		  xo_indent(xop), "", xop->xo_version, ppn);
6937788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
6938788ca347SMarcel Moolenaar 	xop->xo_version = NULL;
6939788ca347SMarcel Moolenaar     }
6940788ca347SMarcel Moolenaar }
6941788ca347SMarcel Moolenaar 
69428a6eceffSPhil Shafer static ssize_t
xo_do_open_container(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)6943545ddfbeSMarcel Moolenaar xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
694431337658SMarcel Moolenaar {
69458a6eceffSPhil Shafer     ssize_t rc = 0;
6946d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
694731337658SMarcel Moolenaar     const char *pre_nl = "";
694831337658SMarcel Moolenaar 
694931337658SMarcel Moolenaar     if (name == NULL) {
695031337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for container name");
695131337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
695231337658SMarcel Moolenaar     }
695331337658SMarcel Moolenaar 
6954406a584dSPhil Shafer     const char *leader = xo_xml_leader(xop, name);
695531337658SMarcel Moolenaar     flags |= xop->xo_flags;	/* Pick up handle flags */
695631337658SMarcel Moolenaar 
6957788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
695831337658SMarcel Moolenaar     case XO_STYLE_XML:
6959406a584dSPhil Shafer 	rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
6960545ddfbeSMarcel Moolenaar 
6961545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6962545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6963545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6964545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6965545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6966545ddfbeSMarcel Moolenaar 	}
6967545ddfbeSMarcel Moolenaar 
6968545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
696931337658SMarcel Moolenaar 	break;
697031337658SMarcel Moolenaar 
697131337658SMarcel Moolenaar     case XO_STYLE_JSON:
697231337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
697331337658SMarcel Moolenaar 
6974d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6975d1a0d267SMarcel Moolenaar 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6976788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
697731337658SMarcel Moolenaar 
697831337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6979d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
698031337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
698131337658SMarcel Moolenaar 
698234b867caSPhil Shafer 	/* If we need underscores, make a local copy and doctor it */
698334b867caSPhil Shafer 	const char *new_name = name;
698434b867caSPhil Shafer 	if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
698534b867caSPhil Shafer 	    size_t len = strlen(name);
698634b867caSPhil Shafer 	    const char *old_name = name;
698734b867caSPhil Shafer 	    char *buf, *cp, *ep;
698834b867caSPhil Shafer 
698934b867caSPhil Shafer 	    buf = alloca(len + 1);
699034b867caSPhil Shafer 	    for (cp = buf, ep = buf + len + 1; cp < ep; cp++, old_name++)
699134b867caSPhil Shafer 		*cp = (*old_name == '-') ? '_' : *old_name;
699234b867caSPhil Shafer 	    new_name = buf;
699334b867caSPhil Shafer 	}
699434b867caSPhil Shafer 
699531337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
699634b867caSPhil Shafer 		       pre_nl, xo_indent(xop), "", new_name, ppn);
699731337658SMarcel Moolenaar 	break;
6998d1a0d267SMarcel Moolenaar 
6999d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7000d1a0d267SMarcel Moolenaar 	break;
7001d1a0d267SMarcel Moolenaar 
7002d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7003f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL, flags);
7004d1a0d267SMarcel Moolenaar 	break;
700531337658SMarcel Moolenaar     }
700631337658SMarcel Moolenaar 
7007545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
7008545ddfbeSMarcel Moolenaar 		    xo_stack_flags(flags));
7009545ddfbeSMarcel Moolenaar 
701031337658SMarcel Moolenaar     return rc;
701131337658SMarcel Moolenaar }
701231337658SMarcel Moolenaar 
7013406a584dSPhil Shafer xo_ssize_t
xo_open_container_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7014545ddfbeSMarcel Moolenaar xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7015545ddfbeSMarcel Moolenaar {
7016545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
7017545ddfbeSMarcel Moolenaar }
7018545ddfbeSMarcel Moolenaar 
70198a6eceffSPhil Shafer xo_ssize_t
xo_open_container_h(xo_handle_t * xop,const char * name)702031337658SMarcel Moolenaar xo_open_container_h (xo_handle_t *xop, const char *name)
702131337658SMarcel Moolenaar {
702231337658SMarcel Moolenaar     return xo_open_container_hf(xop, 0, name);
702331337658SMarcel Moolenaar }
702431337658SMarcel Moolenaar 
70258a6eceffSPhil Shafer xo_ssize_t
xo_open_container(const char * name)702631337658SMarcel Moolenaar xo_open_container (const char *name)
702731337658SMarcel Moolenaar {
702831337658SMarcel Moolenaar     return xo_open_container_hf(NULL, 0, name);
702931337658SMarcel Moolenaar }
703031337658SMarcel Moolenaar 
70318a6eceffSPhil Shafer xo_ssize_t
xo_open_container_hd(xo_handle_t * xop,const char * name)703231337658SMarcel Moolenaar xo_open_container_hd (xo_handle_t *xop, const char *name)
703331337658SMarcel Moolenaar {
703431337658SMarcel Moolenaar     return xo_open_container_hf(xop, XOF_DTRT, name);
703531337658SMarcel Moolenaar }
703631337658SMarcel Moolenaar 
70378a6eceffSPhil Shafer xo_ssize_t
xo_open_container_d(const char * name)703831337658SMarcel Moolenaar xo_open_container_d (const char *name)
703931337658SMarcel Moolenaar {
704031337658SMarcel Moolenaar     return xo_open_container_hf(NULL, XOF_DTRT, name);
704131337658SMarcel Moolenaar }
704231337658SMarcel Moolenaar 
7043545ddfbeSMarcel Moolenaar static int
xo_do_close_container(xo_handle_t * xop,const char * name)7044545ddfbeSMarcel Moolenaar xo_do_close_container (xo_handle_t *xop, const char *name)
704531337658SMarcel Moolenaar {
704631337658SMarcel Moolenaar     xop = xo_default(xop);
704731337658SMarcel Moolenaar 
70488a6eceffSPhil Shafer     ssize_t rc = 0;
7049d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
705031337658SMarcel Moolenaar     const char *pre_nl = "";
705131337658SMarcel Moolenaar 
705231337658SMarcel Moolenaar     if (name == NULL) {
705331337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
705431337658SMarcel Moolenaar 
705531337658SMarcel Moolenaar 	name = xsp->xs_name;
705631337658SMarcel Moolenaar 	if (name) {
70578a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
705831337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
705931337658SMarcel Moolenaar 	    char *cp = alloca(len);
706031337658SMarcel Moolenaar 	    memcpy(cp, name, len);
706131337658SMarcel Moolenaar 	    name = cp;
7062545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7063545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
706431337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
706531337658SMarcel Moolenaar 	}
7066545ddfbeSMarcel Moolenaar     }
706731337658SMarcel Moolenaar 
7068406a584dSPhil Shafer     const char *leader = xo_xml_leader(xop, name);
7069406a584dSPhil Shafer 
7070788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
707131337658SMarcel Moolenaar     case XO_STYLE_XML:
7072545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
7073406a584dSPhil Shafer 	rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
707431337658SMarcel Moolenaar 	break;
707531337658SMarcel Moolenaar 
707631337658SMarcel Moolenaar     case XO_STYLE_JSON:
7077406a584dSPhil Shafer 	xo_stack_set_flags(xop);
7078406a584dSPhil Shafer 
7079d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7080406a584dSPhil Shafer 	ppn = "";
708131337658SMarcel Moolenaar 
7082545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
708331337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
708431337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
708531337658SMarcel Moolenaar 	break;
708631337658SMarcel Moolenaar 
708731337658SMarcel Moolenaar     case XO_STYLE_HTML:
708831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
7089545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
709031337658SMarcel Moolenaar 	break;
7091d1a0d267SMarcel Moolenaar 
7092d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7093d1a0d267SMarcel Moolenaar 	break;
7094d1a0d267SMarcel Moolenaar 
7095d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7096d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
7097f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL, 0);
7098d1a0d267SMarcel Moolenaar 	break;
709931337658SMarcel Moolenaar     }
710031337658SMarcel Moolenaar 
710131337658SMarcel Moolenaar     return rc;
710231337658SMarcel Moolenaar }
710331337658SMarcel Moolenaar 
71048a6eceffSPhil Shafer xo_ssize_t
xo_close_container_h(xo_handle_t * xop,const char * name)7105545ddfbeSMarcel Moolenaar xo_close_container_h (xo_handle_t *xop, const char *name)
7106545ddfbeSMarcel Moolenaar {
7107545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
7108545ddfbeSMarcel Moolenaar }
7109545ddfbeSMarcel Moolenaar 
71108a6eceffSPhil Shafer xo_ssize_t
xo_close_container(const char * name)711131337658SMarcel Moolenaar xo_close_container (const char *name)
711231337658SMarcel Moolenaar {
711331337658SMarcel Moolenaar     return xo_close_container_h(NULL, name);
711431337658SMarcel Moolenaar }
711531337658SMarcel Moolenaar 
71168a6eceffSPhil Shafer xo_ssize_t
xo_close_container_hd(xo_handle_t * xop)711731337658SMarcel Moolenaar xo_close_container_hd (xo_handle_t *xop)
711831337658SMarcel Moolenaar {
711931337658SMarcel Moolenaar     return xo_close_container_h(xop, NULL);
712031337658SMarcel Moolenaar }
712131337658SMarcel Moolenaar 
71228a6eceffSPhil Shafer xo_ssize_t
xo_close_container_d(void)712331337658SMarcel Moolenaar xo_close_container_d (void)
712431337658SMarcel Moolenaar {
712531337658SMarcel Moolenaar     return xo_close_container_h(NULL, NULL);
712631337658SMarcel Moolenaar }
712731337658SMarcel Moolenaar 
712831337658SMarcel Moolenaar static int
xo_do_open_list(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7129406a584dSPhil Shafer xo_do_open_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
713031337658SMarcel Moolenaar {
71318a6eceffSPhil Shafer     ssize_t rc = 0;
7132545ddfbeSMarcel Moolenaar     int indent = 0;
7133545ddfbeSMarcel Moolenaar 
713431337658SMarcel Moolenaar     xop = xo_default(xop);
713531337658SMarcel Moolenaar 
7136d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
713731337658SMarcel Moolenaar     const char *pre_nl = "";
713831337658SMarcel Moolenaar 
7139d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7140d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7141d1a0d267SMarcel Moolenaar 
7142545ddfbeSMarcel Moolenaar 	indent = 1;
7143d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
7144d1a0d267SMarcel Moolenaar 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7145788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
714631337658SMarcel Moolenaar 
714731337658SMarcel Moolenaar 	if (name == NULL) {
714831337658SMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
714931337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
715031337658SMarcel Moolenaar 	}
715131337658SMarcel Moolenaar 
715231337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
715331337658SMarcel Moolenaar 
715431337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7155d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
715631337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
715731337658SMarcel Moolenaar 
715834b867caSPhil Shafer 	/* If we need underscores, make a local copy and doctor it */
715934b867caSPhil Shafer 	const char *new_name = name;
716034b867caSPhil Shafer 	if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
716134b867caSPhil Shafer 	    size_t len = strlen(name);
716234b867caSPhil Shafer 	    const char *old_name = name;
716334b867caSPhil Shafer 	    char *buf, *cp, *ep;
716434b867caSPhil Shafer 
716534b867caSPhil Shafer 	    buf = alloca(len + 1);
716634b867caSPhil Shafer 	    for (cp = buf, ep = buf + len + 1; cp < ep; cp++, old_name++)
716734b867caSPhil Shafer 		*cp = (*old_name == '-') ? '_' : *old_name;
716834b867caSPhil Shafer 	    new_name = buf;
716934b867caSPhil Shafer 	}
717034b867caSPhil Shafer 
717131337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
717234b867caSPhil Shafer 		       pre_nl, xo_indent(xop), "", new_name, ppn);
7173d1a0d267SMarcel Moolenaar 	break;
7174d1a0d267SMarcel Moolenaar 
7175d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7176f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL, flags);
7177d1a0d267SMarcel Moolenaar 	break;
7178545ddfbeSMarcel Moolenaar     }
7179545ddfbeSMarcel Moolenaar 
7180545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
7181545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
718231337658SMarcel Moolenaar 
718331337658SMarcel Moolenaar     return rc;
718431337658SMarcel Moolenaar }
718531337658SMarcel Moolenaar 
7186406a584dSPhil Shafer xo_ssize_t
xo_open_list_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7187406a584dSPhil Shafer xo_open_list_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7188545ddfbeSMarcel Moolenaar {
7189545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
7190545ddfbeSMarcel Moolenaar }
7191545ddfbeSMarcel Moolenaar 
71928a6eceffSPhil Shafer xo_ssize_t
xo_open_list_h(xo_handle_t * xop,const char * name)719342ff34c3SPhil Shafer xo_open_list_h (xo_handle_t *xop, const char *name)
719431337658SMarcel Moolenaar {
719531337658SMarcel Moolenaar     return xo_open_list_hf(xop, 0, name);
719631337658SMarcel Moolenaar }
719731337658SMarcel Moolenaar 
71988a6eceffSPhil Shafer xo_ssize_t
xo_open_list(const char * name)719931337658SMarcel Moolenaar xo_open_list (const char *name)
720031337658SMarcel Moolenaar {
720131337658SMarcel Moolenaar     return xo_open_list_hf(NULL, 0, name);
720231337658SMarcel Moolenaar }
720331337658SMarcel Moolenaar 
72048a6eceffSPhil Shafer xo_ssize_t
xo_open_list_hd(xo_handle_t * xop,const char * name)720542ff34c3SPhil Shafer xo_open_list_hd (xo_handle_t *xop, const char *name)
720631337658SMarcel Moolenaar {
720731337658SMarcel Moolenaar     return xo_open_list_hf(xop, XOF_DTRT, name);
720831337658SMarcel Moolenaar }
720931337658SMarcel Moolenaar 
72108a6eceffSPhil Shafer xo_ssize_t
xo_open_list_d(const char * name)721131337658SMarcel Moolenaar xo_open_list_d (const char *name)
721231337658SMarcel Moolenaar {
721331337658SMarcel Moolenaar     return xo_open_list_hf(NULL, XOF_DTRT, name);
721431337658SMarcel Moolenaar }
721531337658SMarcel Moolenaar 
7216545ddfbeSMarcel Moolenaar static int
xo_do_close_list(xo_handle_t * xop,const char * name)7217545ddfbeSMarcel Moolenaar xo_do_close_list (xo_handle_t *xop, const char *name)
721831337658SMarcel Moolenaar {
72198a6eceffSPhil Shafer     ssize_t rc = 0;
722031337658SMarcel Moolenaar     const char *pre_nl = "";
722131337658SMarcel Moolenaar 
722231337658SMarcel Moolenaar     if (name == NULL) {
722331337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
722431337658SMarcel Moolenaar 
722531337658SMarcel Moolenaar 	name = xsp->xs_name;
722631337658SMarcel Moolenaar 	if (name) {
72278a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
722831337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
722931337658SMarcel Moolenaar 	    char *cp = alloca(len);
723031337658SMarcel Moolenaar 	    memcpy(cp, name, len);
723131337658SMarcel Moolenaar 	    name = cp;
7232545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7233545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
723431337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
723531337658SMarcel Moolenaar 	}
7236545ddfbeSMarcel Moolenaar     }
723731337658SMarcel Moolenaar 
7238d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7239d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
724031337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7241d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
724231337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
724331337658SMarcel Moolenaar 
7244545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
724531337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
724631337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7247d1a0d267SMarcel Moolenaar 	break;
724831337658SMarcel Moolenaar 
7249d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7250d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7251f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL, 0);
7252d1a0d267SMarcel Moolenaar 	break;
7253d1a0d267SMarcel Moolenaar 
7254d1a0d267SMarcel Moolenaar     default:
7255545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7256545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7257d1a0d267SMarcel Moolenaar 	break;
7258545ddfbeSMarcel Moolenaar     }
7259545ddfbeSMarcel Moolenaar 
7260a0f704ffSMarcel Moolenaar     return rc;
726131337658SMarcel Moolenaar }
726231337658SMarcel Moolenaar 
72638a6eceffSPhil Shafer xo_ssize_t
xo_close_list_h(xo_handle_t * xop,const char * name)7264545ddfbeSMarcel Moolenaar xo_close_list_h (xo_handle_t *xop, const char *name)
7265545ddfbeSMarcel Moolenaar {
7266545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
7267545ddfbeSMarcel Moolenaar }
7268545ddfbeSMarcel Moolenaar 
72698a6eceffSPhil Shafer xo_ssize_t
xo_close_list(const char * name)727031337658SMarcel Moolenaar xo_close_list (const char *name)
727131337658SMarcel Moolenaar {
727231337658SMarcel Moolenaar     return xo_close_list_h(NULL, name);
727331337658SMarcel Moolenaar }
727431337658SMarcel Moolenaar 
72758a6eceffSPhil Shafer xo_ssize_t
xo_close_list_hd(xo_handle_t * xop)727631337658SMarcel Moolenaar xo_close_list_hd (xo_handle_t *xop)
727731337658SMarcel Moolenaar {
727831337658SMarcel Moolenaar     return xo_close_list_h(xop, NULL);
727931337658SMarcel Moolenaar }
728031337658SMarcel Moolenaar 
72818a6eceffSPhil Shafer xo_ssize_t
xo_close_list_d(void)728231337658SMarcel Moolenaar xo_close_list_d (void)
728331337658SMarcel Moolenaar {
728431337658SMarcel Moolenaar     return xo_close_list_h(NULL, NULL);
728531337658SMarcel Moolenaar }
728631337658SMarcel Moolenaar 
728731337658SMarcel Moolenaar static int
xo_do_open_leaf_list(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7288406a584dSPhil Shafer xo_do_open_leaf_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7289545ddfbeSMarcel Moolenaar {
72908a6eceffSPhil Shafer     ssize_t rc = 0;
7291545ddfbeSMarcel Moolenaar     int indent = 0;
7292545ddfbeSMarcel Moolenaar 
7293545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7294545ddfbeSMarcel Moolenaar 
7295d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7296545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
7297545ddfbeSMarcel Moolenaar 
7298d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7299d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7300545ddfbeSMarcel Moolenaar 	indent = 1;
7301545ddfbeSMarcel Moolenaar 
7302d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7303d1a0d267SMarcel Moolenaar 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
7304545ddfbeSMarcel Moolenaar 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
7305d1a0d267SMarcel Moolenaar 		XOIF_SET(xop, XOIF_TOP_EMITTED);
7306545ddfbeSMarcel Moolenaar 	    }
7307545ddfbeSMarcel Moolenaar 	}
7308545ddfbeSMarcel Moolenaar 
7309545ddfbeSMarcel Moolenaar 	if (name == NULL) {
7310545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
7311545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
7312545ddfbeSMarcel Moolenaar 	}
7313545ddfbeSMarcel Moolenaar 
7314545ddfbeSMarcel Moolenaar 	xo_stack_set_flags(xop);
7315545ddfbeSMarcel Moolenaar 
7316545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7317d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7318545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7319545ddfbeSMarcel Moolenaar 
7320545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7321545ddfbeSMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
7322d1a0d267SMarcel Moolenaar 	break;
7323d1a0d267SMarcel Moolenaar 
7324d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7325f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL, flags);
7326d1a0d267SMarcel Moolenaar 	break;
7327545ddfbeSMarcel Moolenaar     }
7328545ddfbeSMarcel Moolenaar 
7329545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
7330545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
7331545ddfbeSMarcel Moolenaar 
7332545ddfbeSMarcel Moolenaar     return rc;
7333545ddfbeSMarcel Moolenaar }
7334545ddfbeSMarcel Moolenaar 
7335545ddfbeSMarcel Moolenaar static int
xo_do_close_leaf_list(xo_handle_t * xop,const char * name)7336545ddfbeSMarcel Moolenaar xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
7337545ddfbeSMarcel Moolenaar {
73388a6eceffSPhil Shafer     ssize_t rc = 0;
7339545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
7340545ddfbeSMarcel Moolenaar 
7341545ddfbeSMarcel Moolenaar     if (name == NULL) {
7342545ddfbeSMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7343545ddfbeSMarcel Moolenaar 
7344545ddfbeSMarcel Moolenaar 	name = xsp->xs_name;
7345545ddfbeSMarcel Moolenaar 	if (name) {
73468a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
7347545ddfbeSMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
7348545ddfbeSMarcel Moolenaar 	    char *cp = alloca(len);
7349545ddfbeSMarcel Moolenaar 	    memcpy(cp, name, len);
7350545ddfbeSMarcel Moolenaar 	    name = cp;
7351545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7352545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
7353545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
7354545ddfbeSMarcel Moolenaar 	}
7355545ddfbeSMarcel Moolenaar     }
7356545ddfbeSMarcel Moolenaar 
7357d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7358d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7359545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7360d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7361545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7362545ddfbeSMarcel Moolenaar 
7363545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7364545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7365545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7366d1a0d267SMarcel Moolenaar 	break;
7367545ddfbeSMarcel Moolenaar 
7368d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7369f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL, 0);
7370ee5cf116SPhil Shafer 	/* FALLTHRU */
7371d1a0d267SMarcel Moolenaar 
7372d1a0d267SMarcel Moolenaar     default:
7373545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7374545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7375d1a0d267SMarcel Moolenaar 	break;
7376545ddfbeSMarcel Moolenaar     }
7377545ddfbeSMarcel Moolenaar 
7378545ddfbeSMarcel Moolenaar     return rc;
7379545ddfbeSMarcel Moolenaar }
7380545ddfbeSMarcel Moolenaar 
7381545ddfbeSMarcel Moolenaar static int
xo_do_open_instance(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7382406a584dSPhil Shafer xo_do_open_instance (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
738331337658SMarcel Moolenaar {
738431337658SMarcel Moolenaar     xop = xo_default(xop);
738531337658SMarcel Moolenaar 
73868a6eceffSPhil Shafer     ssize_t rc = 0;
7387d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
738831337658SMarcel Moolenaar     const char *pre_nl = "";
738931337658SMarcel Moolenaar 
739031337658SMarcel Moolenaar     if (name == NULL) {
739131337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for instance name");
739231337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
739331337658SMarcel Moolenaar     }
739431337658SMarcel Moolenaar 
7395406a584dSPhil Shafer     const char *leader = xo_xml_leader(xop, name);
7396406a584dSPhil Shafer     flags |= xop->xo_flags;
7397406a584dSPhil Shafer 
7398788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
739931337658SMarcel Moolenaar     case XO_STYLE_XML:
7400406a584dSPhil Shafer 	rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
7401545ddfbeSMarcel Moolenaar 
7402545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7403545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7404545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
7405545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7406545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7407545ddfbeSMarcel Moolenaar 	}
7408545ddfbeSMarcel Moolenaar 
7409545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
741031337658SMarcel Moolenaar 	break;
741131337658SMarcel Moolenaar 
741231337658SMarcel Moolenaar     case XO_STYLE_JSON:
741331337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
741431337658SMarcel Moolenaar 
741531337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7416d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
741731337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
741831337658SMarcel Moolenaar 
741931337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s{%s",
742031337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", ppn);
742131337658SMarcel Moolenaar 	break;
7422d1a0d267SMarcel Moolenaar 
7423d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7424d1a0d267SMarcel Moolenaar 	break;
7425d1a0d267SMarcel Moolenaar 
7426d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7427f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL, flags);
7428d1a0d267SMarcel Moolenaar 	break;
742931337658SMarcel Moolenaar     }
743031337658SMarcel Moolenaar 
7431545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7432545ddfbeSMarcel Moolenaar 
743331337658SMarcel Moolenaar     return rc;
743431337658SMarcel Moolenaar }
743531337658SMarcel Moolenaar 
7436406a584dSPhil Shafer xo_ssize_t
xo_open_instance_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7437406a584dSPhil Shafer xo_open_instance_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7438545ddfbeSMarcel Moolenaar {
7439545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7440545ddfbeSMarcel Moolenaar }
7441545ddfbeSMarcel Moolenaar 
74428a6eceffSPhil Shafer xo_ssize_t
xo_open_instance_h(xo_handle_t * xop,const char * name)744331337658SMarcel Moolenaar xo_open_instance_h (xo_handle_t *xop, const char *name)
744431337658SMarcel Moolenaar {
744531337658SMarcel Moolenaar     return xo_open_instance_hf(xop, 0, name);
744631337658SMarcel Moolenaar }
744731337658SMarcel Moolenaar 
74488a6eceffSPhil Shafer xo_ssize_t
xo_open_instance(const char * name)744931337658SMarcel Moolenaar xo_open_instance (const char *name)
745031337658SMarcel Moolenaar {
745131337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, 0, name);
745231337658SMarcel Moolenaar }
745331337658SMarcel Moolenaar 
74548a6eceffSPhil Shafer xo_ssize_t
xo_open_instance_hd(xo_handle_t * xop,const char * name)745531337658SMarcel Moolenaar xo_open_instance_hd (xo_handle_t *xop, const char *name)
745631337658SMarcel Moolenaar {
745731337658SMarcel Moolenaar     return xo_open_instance_hf(xop, XOF_DTRT, name);
745831337658SMarcel Moolenaar }
745931337658SMarcel Moolenaar 
74608a6eceffSPhil Shafer xo_ssize_t
xo_open_instance_d(const char * name)746131337658SMarcel Moolenaar xo_open_instance_d (const char *name)
746231337658SMarcel Moolenaar {
746331337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, XOF_DTRT, name);
746431337658SMarcel Moolenaar }
746531337658SMarcel Moolenaar 
7466545ddfbeSMarcel Moolenaar static int
xo_do_close_instance(xo_handle_t * xop,const char * name)7467545ddfbeSMarcel Moolenaar xo_do_close_instance (xo_handle_t *xop, const char *name)
746831337658SMarcel Moolenaar {
746931337658SMarcel Moolenaar     xop = xo_default(xop);
747031337658SMarcel Moolenaar 
74718a6eceffSPhil Shafer     ssize_t rc = 0;
7472d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
747331337658SMarcel Moolenaar     const char *pre_nl = "";
747431337658SMarcel Moolenaar 
747531337658SMarcel Moolenaar     if (name == NULL) {
747631337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
747731337658SMarcel Moolenaar 
747831337658SMarcel Moolenaar 	name = xsp->xs_name;
747931337658SMarcel Moolenaar 	if (name) {
74808a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
748131337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
748231337658SMarcel Moolenaar 	    char *cp = alloca(len);
748331337658SMarcel Moolenaar 	    memcpy(cp, name, len);
748431337658SMarcel Moolenaar 	    name = cp;
7485545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7486545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
748731337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
748831337658SMarcel Moolenaar 	}
7489545ddfbeSMarcel Moolenaar     }
749031337658SMarcel Moolenaar 
7491406a584dSPhil Shafer     const char *leader = xo_xml_leader(xop, name);
7492406a584dSPhil Shafer 
7493788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
749431337658SMarcel Moolenaar     case XO_STYLE_XML:
7495545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7496406a584dSPhil Shafer 	rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
749731337658SMarcel Moolenaar 	break;
749831337658SMarcel Moolenaar 
749931337658SMarcel Moolenaar     case XO_STYLE_JSON:
7500d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
750131337658SMarcel Moolenaar 
7502545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
750331337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
750431337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
750531337658SMarcel Moolenaar 	break;
750631337658SMarcel Moolenaar 
750731337658SMarcel Moolenaar     case XO_STYLE_HTML:
750831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
7509545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
751031337658SMarcel Moolenaar 	break;
7511d1a0d267SMarcel Moolenaar 
7512d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7513d1a0d267SMarcel Moolenaar 	break;
7514d1a0d267SMarcel Moolenaar 
7515d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7516d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7517f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL, 0);
7518d1a0d267SMarcel Moolenaar 	break;
751931337658SMarcel Moolenaar     }
752031337658SMarcel Moolenaar 
752131337658SMarcel Moolenaar     return rc;
752231337658SMarcel Moolenaar }
752331337658SMarcel Moolenaar 
75248a6eceffSPhil Shafer xo_ssize_t
xo_close_instance_h(xo_handle_t * xop,const char * name)7525545ddfbeSMarcel Moolenaar xo_close_instance_h (xo_handle_t *xop, const char *name)
7526545ddfbeSMarcel Moolenaar {
7527545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7528545ddfbeSMarcel Moolenaar }
7529545ddfbeSMarcel Moolenaar 
75308a6eceffSPhil Shafer xo_ssize_t
xo_close_instance(const char * name)753131337658SMarcel Moolenaar xo_close_instance (const char *name)
753231337658SMarcel Moolenaar {
753331337658SMarcel Moolenaar     return xo_close_instance_h(NULL, name);
753431337658SMarcel Moolenaar }
753531337658SMarcel Moolenaar 
75368a6eceffSPhil Shafer xo_ssize_t
xo_close_instance_hd(xo_handle_t * xop)753731337658SMarcel Moolenaar xo_close_instance_hd (xo_handle_t *xop)
753831337658SMarcel Moolenaar {
753931337658SMarcel Moolenaar     return xo_close_instance_h(xop, NULL);
754031337658SMarcel Moolenaar }
754131337658SMarcel Moolenaar 
75428a6eceffSPhil Shafer xo_ssize_t
xo_close_instance_d(void)754331337658SMarcel Moolenaar xo_close_instance_d (void)
754431337658SMarcel Moolenaar {
754531337658SMarcel Moolenaar     return xo_close_instance_h(NULL, NULL);
754631337658SMarcel Moolenaar }
754731337658SMarcel Moolenaar 
7548545ddfbeSMarcel Moolenaar static int
xo_do_close_all(xo_handle_t * xop,xo_stack_t * limit)7549545ddfbeSMarcel Moolenaar xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7550545ddfbeSMarcel Moolenaar {
7551545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
75528a6eceffSPhil Shafer     ssize_t rc = 0;
7553545ddfbeSMarcel Moolenaar     xo_xsf_flags_t flags;
7554545ddfbeSMarcel Moolenaar 
7555545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7556545ddfbeSMarcel Moolenaar 	switch (xsp->xs_state) {
7557545ddfbeSMarcel Moolenaar 	case XSS_INIT:
7558545ddfbeSMarcel Moolenaar 	    /* Nothing */
7559545ddfbeSMarcel Moolenaar 	    rc = 0;
7560545ddfbeSMarcel Moolenaar 	    break;
7561545ddfbeSMarcel Moolenaar 
7562545ddfbeSMarcel Moolenaar 	case XSS_OPEN_CONTAINER:
7563545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_container(xop, NULL);
7564545ddfbeSMarcel Moolenaar 	    break;
7565545ddfbeSMarcel Moolenaar 
7566545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LIST:
7567545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_list(xop, NULL);
7568545ddfbeSMarcel Moolenaar 	    break;
7569545ddfbeSMarcel Moolenaar 
7570545ddfbeSMarcel Moolenaar 	case XSS_OPEN_INSTANCE:
7571545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7572545ddfbeSMarcel Moolenaar 	    break;
7573545ddfbeSMarcel Moolenaar 
7574545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LEAF_LIST:
7575545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_leaf_list(xop, NULL);
7576545ddfbeSMarcel Moolenaar 	    break;
7577545ddfbeSMarcel Moolenaar 
7578545ddfbeSMarcel Moolenaar 	case XSS_MARKER:
7579545ddfbeSMarcel Moolenaar 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7580545ddfbeSMarcel Moolenaar 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7581545ddfbeSMarcel Moolenaar 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7582545ddfbeSMarcel Moolenaar 	    rc = 0;
7583545ddfbeSMarcel Moolenaar 	    break;
7584545ddfbeSMarcel Moolenaar 	}
7585545ddfbeSMarcel Moolenaar 
7586545ddfbeSMarcel Moolenaar 	if (rc < 0)
7587545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7588545ddfbeSMarcel Moolenaar     }
7589545ddfbeSMarcel Moolenaar 
7590545ddfbeSMarcel Moolenaar     return 0;
7591545ddfbeSMarcel Moolenaar }
7592545ddfbeSMarcel Moolenaar 
7593545ddfbeSMarcel Moolenaar /*
7594545ddfbeSMarcel Moolenaar  * This function is responsible for clearing out whatever is needed
7595545ddfbeSMarcel Moolenaar  * to get to the desired state, if possible.
7596545ddfbeSMarcel Moolenaar  */
7597545ddfbeSMarcel Moolenaar static int
xo_do_close(xo_handle_t * xop,const char * name,xo_state_t new_state)7598545ddfbeSMarcel Moolenaar xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7599545ddfbeSMarcel Moolenaar {
7600545ddfbeSMarcel Moolenaar     xo_stack_t *xsp, *limit = NULL;
76018a6eceffSPhil Shafer     ssize_t rc;
7602545ddfbeSMarcel Moolenaar     xo_state_t need_state = new_state;
7603545ddfbeSMarcel Moolenaar 
7604545ddfbeSMarcel Moolenaar     if (new_state == XSS_CLOSE_CONTAINER)
7605545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_CONTAINER;
7606545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LIST)
7607545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LIST;
7608545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_INSTANCE)
7609545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_INSTANCE;
7610545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LEAF_LIST)
7611545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LEAF_LIST;
7612545ddfbeSMarcel Moolenaar     else if (new_state == XSS_MARKER)
7613545ddfbeSMarcel Moolenaar 	need_state = XSS_MARKER;
7614545ddfbeSMarcel Moolenaar     else
7615545ddfbeSMarcel Moolenaar 	return 0; /* Unknown or useless new states are ignored */
7616545ddfbeSMarcel Moolenaar 
7617545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7618545ddfbeSMarcel Moolenaar 	/*
7619545ddfbeSMarcel Moolenaar 	 * Marker's normally stop us from going any further, unless
7620545ddfbeSMarcel Moolenaar 	 * we are popping a marker (new_state == XSS_MARKER).
7621545ddfbeSMarcel Moolenaar 	 */
7622545ddfbeSMarcel Moolenaar 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7623545ddfbeSMarcel Moolenaar 	    if (name) {
7624545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7625545ddfbeSMarcel Moolenaar 			   "not found '%s'",
7626545ddfbeSMarcel Moolenaar 			   xo_state_name(new_state),
7627545ddfbeSMarcel Moolenaar 			   xsp->xs_name, name);
7628545ddfbeSMarcel Moolenaar 		return 0;
7629545ddfbeSMarcel Moolenaar 
7630545ddfbeSMarcel Moolenaar 	    } else {
7631545ddfbeSMarcel Moolenaar 		limit = xsp;
7632545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7633545ddfbeSMarcel Moolenaar 	    }
7634545ddfbeSMarcel Moolenaar 	    break;
7635545ddfbeSMarcel Moolenaar 	}
7636545ddfbeSMarcel Moolenaar 
7637545ddfbeSMarcel Moolenaar 	if (xsp->xs_state != need_state)
7638545ddfbeSMarcel Moolenaar 	    continue;
7639545ddfbeSMarcel Moolenaar 
764076afb20cSPhil Shafer 	if (name && xsp->xs_name && !xo_streq(name, xsp->xs_name))
7641545ddfbeSMarcel Moolenaar 	    continue;
7642545ddfbeSMarcel Moolenaar 
7643545ddfbeSMarcel Moolenaar 	limit = xsp;
7644545ddfbeSMarcel Moolenaar 	break;
7645545ddfbeSMarcel Moolenaar     }
7646545ddfbeSMarcel Moolenaar 
7647545ddfbeSMarcel Moolenaar     if (limit == NULL) {
7648545ddfbeSMarcel Moolenaar 	xo_failure(xop, "xo_%s can't find match for '%s'",
7649545ddfbeSMarcel Moolenaar 		   xo_state_name(new_state), name);
7650545ddfbeSMarcel Moolenaar 	return 0;
7651545ddfbeSMarcel Moolenaar     }
7652545ddfbeSMarcel Moolenaar 
7653545ddfbeSMarcel Moolenaar     rc = xo_do_close_all(xop, limit);
7654545ddfbeSMarcel Moolenaar 
7655545ddfbeSMarcel Moolenaar     return rc;
7656545ddfbeSMarcel Moolenaar }
7657545ddfbeSMarcel Moolenaar 
7658545ddfbeSMarcel Moolenaar /*
7659545ddfbeSMarcel Moolenaar  * We are in a given state and need to transition to the new state.
7660545ddfbeSMarcel Moolenaar  */
76618a6eceffSPhil Shafer static ssize_t
xo_transition(xo_handle_t * xop,xo_xof_flags_t flags,const char * name,xo_state_t new_state)7662406a584dSPhil Shafer xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
7663545ddfbeSMarcel Moolenaar 	       xo_state_t new_state)
7664545ddfbeSMarcel Moolenaar {
7665545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
76668a6eceffSPhil Shafer     ssize_t rc = 0;
7667545ddfbeSMarcel Moolenaar     int old_state, on_marker;
7668545ddfbeSMarcel Moolenaar 
7669545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7670545ddfbeSMarcel Moolenaar 
7671545ddfbeSMarcel Moolenaar     xsp = &xop->xo_stack[xop->xo_depth];
7672545ddfbeSMarcel Moolenaar     old_state = xsp->xs_state;
7673545ddfbeSMarcel Moolenaar     on_marker = (old_state == XSS_MARKER);
7674545ddfbeSMarcel Moolenaar 
7675545ddfbeSMarcel Moolenaar     /* If there's a marker on top of the stack, we need to find a real state */
7676545ddfbeSMarcel Moolenaar     while (old_state == XSS_MARKER) {
7677545ddfbeSMarcel Moolenaar 	if (xsp == xop->xo_stack)
7678545ddfbeSMarcel Moolenaar 	    break;
7679545ddfbeSMarcel Moolenaar 	xsp -= 1;
7680545ddfbeSMarcel Moolenaar 	old_state = xsp->xs_state;
7681545ddfbeSMarcel Moolenaar     }
7682545ddfbeSMarcel Moolenaar 
7683545ddfbeSMarcel Moolenaar     /*
7684545ddfbeSMarcel Moolenaar      * At this point, the list of possible states are:
7685545ddfbeSMarcel Moolenaar      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7686545ddfbeSMarcel Moolenaar      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7687545ddfbeSMarcel Moolenaar      */
7688545ddfbeSMarcel Moolenaar     switch (XSS_TRANSITION(old_state, new_state)) {
7689545ddfbeSMarcel Moolenaar 
7690545ddfbeSMarcel Moolenaar     open_container:
7691545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7692545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7693545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7694545ddfbeSMarcel Moolenaar        rc = xo_do_open_container(xop, flags, name);
7695545ddfbeSMarcel Moolenaar        break;
7696545ddfbeSMarcel Moolenaar 
7697545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7698545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7699545ddfbeSMarcel Moolenaar 	if (on_marker)
7700545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7701545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7702545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7703545ddfbeSMarcel Moolenaar 	    goto open_container;
7704545ddfbeSMarcel Moolenaar 	break;
7705545ddfbeSMarcel Moolenaar 
7706545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7707545ddfbeSMarcel Moolenaar 	/* This is an exception for "xo --close" */
7708545ddfbeSMarcel Moolenaar 	rc = xo_do_close_container(xop, name);
7709545ddfbeSMarcel Moolenaar 	break;
7710545ddfbeSMarcel Moolenaar 
771176afb20cSPhil Shafer     /*close_container:*/
771276afb20cSPhil Shafer     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7713545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7714545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7715545ddfbeSMarcel Moolenaar 	if (on_marker)
7716545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7717545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7718545ddfbeSMarcel Moolenaar 	break;
7719545ddfbeSMarcel Moolenaar 
7720545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7721545ddfbeSMarcel Moolenaar 	if (on_marker)
7722545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7723545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7724545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7725545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7726545ddfbeSMarcel Moolenaar 	break;
7727545ddfbeSMarcel Moolenaar 
7728545ddfbeSMarcel Moolenaar     open_list:
7729545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7730545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7731545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7732545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7733545ddfbeSMarcel Moolenaar 	break;
7734545ddfbeSMarcel Moolenaar 
7735545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7736545ddfbeSMarcel Moolenaar 	if (on_marker)
7737545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7738545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7739545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7740545ddfbeSMarcel Moolenaar 	    goto open_list;
7741545ddfbeSMarcel Moolenaar 	break;
7742545ddfbeSMarcel Moolenaar 
7743545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7744545ddfbeSMarcel Moolenaar 	if (on_marker)
7745545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7746545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7747545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7748545ddfbeSMarcel Moolenaar 	    goto open_list;
7749545ddfbeSMarcel Moolenaar 	break;
7750545ddfbeSMarcel Moolenaar 
7751545ddfbeSMarcel Moolenaar     /*close_list:*/
7752545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7753545ddfbeSMarcel Moolenaar 	if (on_marker)
7754545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7755545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7756545ddfbeSMarcel Moolenaar 	break;
7757545ddfbeSMarcel Moolenaar 
7758545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7759545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7760545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7761545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7762545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7763545ddfbeSMarcel Moolenaar 	break;
7764545ddfbeSMarcel Moolenaar 
7765545ddfbeSMarcel Moolenaar     open_instance:
7766545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7767545ddfbeSMarcel Moolenaar 	rc = xo_do_open_instance(xop, flags, name);
7768545ddfbeSMarcel Moolenaar 	break;
7769545ddfbeSMarcel Moolenaar 
7770545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7771788ca347SMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7772545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7773545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7774545ddfbeSMarcel Moolenaar 	    goto open_instance;
7775545ddfbeSMarcel Moolenaar 	break;
7776545ddfbeSMarcel Moolenaar 
7777545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7778545ddfbeSMarcel Moolenaar 	if (on_marker) {
7779545ddfbeSMarcel Moolenaar 	    rc = xo_do_open_list(xop, flags, name);
7780545ddfbeSMarcel Moolenaar 	} else {
7781545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7782545ddfbeSMarcel Moolenaar 	}
7783545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7784545ddfbeSMarcel Moolenaar 	    goto open_instance;
7785545ddfbeSMarcel Moolenaar 	break;
7786545ddfbeSMarcel Moolenaar 
7787545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7788545ddfbeSMarcel Moolenaar 	if (on_marker)
7789545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7790545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7791545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7792545ddfbeSMarcel Moolenaar 	    goto open_instance;
7793545ddfbeSMarcel Moolenaar 	break;
7794545ddfbeSMarcel Moolenaar 
7795545ddfbeSMarcel Moolenaar     /*close_instance:*/
7796545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7797545ddfbeSMarcel Moolenaar 	if (on_marker)
7798545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7799545ddfbeSMarcel Moolenaar 	rc = xo_do_close_instance(xop, name);
7800545ddfbeSMarcel Moolenaar 	break;
7801545ddfbeSMarcel Moolenaar 
7802545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7803545ddfbeSMarcel Moolenaar 	/* This one makes no sense; ignore it */
7804788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_instance ignored when called from "
7805788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7806545ddfbeSMarcel Moolenaar 	break;
7807545ddfbeSMarcel Moolenaar 
7808545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7809545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7810545ddfbeSMarcel Moolenaar 	if (on_marker)
7811545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7812545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7813545ddfbeSMarcel Moolenaar 	break;
7814545ddfbeSMarcel Moolenaar 
7815545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7816545ddfbeSMarcel Moolenaar 	if (on_marker)
7817545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7818545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7819545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7820545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7821545ddfbeSMarcel Moolenaar 	break;
7822545ddfbeSMarcel Moolenaar 
7823545ddfbeSMarcel Moolenaar     open_leaf_list:
7824545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7825545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7826545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7827545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7828545ddfbeSMarcel Moolenaar 	break;
7829545ddfbeSMarcel Moolenaar 
7830545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7831545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7832545ddfbeSMarcel Moolenaar 	if (on_marker)
7833545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7834545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7835545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7836545ddfbeSMarcel Moolenaar 	    goto open_leaf_list;
7837545ddfbeSMarcel Moolenaar 	break;
7838545ddfbeSMarcel Moolenaar 
7839545ddfbeSMarcel Moolenaar     /*close_leaf_list:*/
7840545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7841545ddfbeSMarcel Moolenaar 	if (on_marker)
7842545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7843545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, name);
7844545ddfbeSMarcel Moolenaar 	break;
7845545ddfbeSMarcel Moolenaar 
7846545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7847545ddfbeSMarcel Moolenaar 	/* Makes no sense; ignore */
7848788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7849788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7850545ddfbeSMarcel Moolenaar 	break;
7851545ddfbeSMarcel Moolenaar 
7852545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7853545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7854545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7855545ddfbeSMarcel Moolenaar 	if (on_marker)
7856545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7857545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7858545ddfbeSMarcel Moolenaar 	break;
7859545ddfbeSMarcel Moolenaar 
7860545ddfbeSMarcel Moolenaar     /*emit:*/
7861545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7862545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7863545ddfbeSMarcel Moolenaar 	break;
7864545ddfbeSMarcel Moolenaar 
7865545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7866545ddfbeSMarcel Moolenaar 	if (on_marker)
7867545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7868545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7869545ddfbeSMarcel Moolenaar 	break;
7870545ddfbeSMarcel Moolenaar 
7871545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7872545ddfbeSMarcel Moolenaar 	break;
7873545ddfbeSMarcel Moolenaar 
7874545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7875545ddfbeSMarcel Moolenaar 	if (on_marker)
7876545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7877545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7878545ddfbeSMarcel Moolenaar 	break;
7879545ddfbeSMarcel Moolenaar 
7880545ddfbeSMarcel Moolenaar     /*emit_leaf_list:*/
7881545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7882545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7883545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7884545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7885545ddfbeSMarcel Moolenaar 	break;
7886545ddfbeSMarcel Moolenaar 
7887545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7888545ddfbeSMarcel Moolenaar 	break;
7889545ddfbeSMarcel Moolenaar 
7890545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7891545ddfbeSMarcel Moolenaar 	/*
7892545ddfbeSMarcel Moolenaar 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7893545ddfbeSMarcel Moolenaar 	 * API, where both lists and leaf-lists were opened as lists.  So
7894545ddfbeSMarcel Moolenaar 	 * if we find an open list that hasn't had anything written to it,
7895545ddfbeSMarcel Moolenaar 	 * we'll accept it.
7896545ddfbeSMarcel Moolenaar 	 */
7897545ddfbeSMarcel Moolenaar 	break;
7898545ddfbeSMarcel Moolenaar 
7899545ddfbeSMarcel Moolenaar     default:
7900545ddfbeSMarcel Moolenaar 	xo_failure(xop, "unknown transition: (%u -> %u)",
7901545ddfbeSMarcel Moolenaar 		   xsp->xs_state, new_state);
7902545ddfbeSMarcel Moolenaar     }
7903545ddfbeSMarcel Moolenaar 
790442ff34c3SPhil Shafer     /* Handle the flush flag */
790542ff34c3SPhil Shafer     if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
7906406a584dSPhil Shafer 	if (xo_flush_h(xop) < 0)
790742ff34c3SPhil Shafer 	    rc = -1;
790842ff34c3SPhil Shafer 
7909406a584dSPhil Shafer     /* We have now official made output */
7910406a584dSPhil Shafer     XOIF_SET(xop, XOIF_MADE_OUTPUT);
7911406a584dSPhil Shafer 
7912545ddfbeSMarcel Moolenaar     return rc;
7913545ddfbeSMarcel Moolenaar 
7914545ddfbeSMarcel Moolenaar  marker_prevents_close:
7915545ddfbeSMarcel Moolenaar     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7916545ddfbeSMarcel Moolenaar 	       xop->xo_stack[xop->xo_depth].xs_name,
7917545ddfbeSMarcel Moolenaar 	       xo_state_name(old_state), xo_state_name(new_state));
7918545ddfbeSMarcel Moolenaar     return -1;
7919545ddfbeSMarcel Moolenaar }
7920545ddfbeSMarcel Moolenaar 
79218a6eceffSPhil Shafer xo_ssize_t
xo_open_marker_h(xo_handle_t * xop,const char * name)7922545ddfbeSMarcel Moolenaar xo_open_marker_h (xo_handle_t *xop, const char *name)
7923545ddfbeSMarcel Moolenaar {
7924545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7925545ddfbeSMarcel Moolenaar 
7926545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7927545ddfbeSMarcel Moolenaar 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7928545ddfbeSMarcel Moolenaar 
7929545ddfbeSMarcel Moolenaar     return 0;
7930545ddfbeSMarcel Moolenaar }
7931545ddfbeSMarcel Moolenaar 
79328a6eceffSPhil Shafer xo_ssize_t
xo_open_marker(const char * name)7933545ddfbeSMarcel Moolenaar xo_open_marker (const char *name)
7934545ddfbeSMarcel Moolenaar {
7935545ddfbeSMarcel Moolenaar     return xo_open_marker_h(NULL, name);
7936545ddfbeSMarcel Moolenaar }
7937545ddfbeSMarcel Moolenaar 
79388a6eceffSPhil Shafer xo_ssize_t
xo_close_marker_h(xo_handle_t * xop,const char * name)7939545ddfbeSMarcel Moolenaar xo_close_marker_h (xo_handle_t *xop, const char *name)
7940545ddfbeSMarcel Moolenaar {
7941545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7942545ddfbeSMarcel Moolenaar 
7943545ddfbeSMarcel Moolenaar     return xo_do_close(xop, name, XSS_MARKER);
7944545ddfbeSMarcel Moolenaar }
7945545ddfbeSMarcel Moolenaar 
79468a6eceffSPhil Shafer xo_ssize_t
xo_close_marker(const char * name)7947545ddfbeSMarcel Moolenaar xo_close_marker (const char *name)
7948545ddfbeSMarcel Moolenaar {
7949545ddfbeSMarcel Moolenaar     return xo_close_marker_h(NULL, name);
7950545ddfbeSMarcel Moolenaar }
7951545ddfbeSMarcel Moolenaar 
7952d1a0d267SMarcel Moolenaar /*
7953d1a0d267SMarcel Moolenaar  * Record custom output functions into the xo handle, allowing
7954d1a0d267SMarcel Moolenaar  * integration with a variety of output frameworks.
7955d1a0d267SMarcel Moolenaar  */
795631337658SMarcel Moolenaar void
xo_set_writer(xo_handle_t * xop,void * opaque,xo_write_func_t write_func,xo_close_func_t close_func,xo_flush_func_t flush_func)795731337658SMarcel Moolenaar xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7958545ddfbeSMarcel Moolenaar 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
795931337658SMarcel Moolenaar {
796031337658SMarcel Moolenaar     xop = xo_default(xop);
796131337658SMarcel Moolenaar 
796231337658SMarcel Moolenaar     xop->xo_opaque = opaque;
796331337658SMarcel Moolenaar     xop->xo_write = write_func;
796431337658SMarcel Moolenaar     xop->xo_close = close_func;
7965545ddfbeSMarcel Moolenaar     xop->xo_flush = flush_func;
796631337658SMarcel Moolenaar }
796731337658SMarcel Moolenaar 
796831337658SMarcel Moolenaar void
xo_set_allocator(xo_realloc_func_t realloc_func,xo_free_func_t free_func)796931337658SMarcel Moolenaar xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
797031337658SMarcel Moolenaar {
797131337658SMarcel Moolenaar     xo_realloc = realloc_func;
797231337658SMarcel Moolenaar     xo_free = free_func;
797331337658SMarcel Moolenaar }
797431337658SMarcel Moolenaar 
79758a6eceffSPhil Shafer xo_ssize_t
xo_flush_h(xo_handle_t * xop)797631337658SMarcel Moolenaar xo_flush_h (xo_handle_t *xop)
797731337658SMarcel Moolenaar {
79788a6eceffSPhil Shafer     ssize_t rc;
797931337658SMarcel Moolenaar 
798031337658SMarcel Moolenaar     xop = xo_default(xop);
798131337658SMarcel Moolenaar 
7982788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
7983d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7984f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL, 0);
798531337658SMarcel Moolenaar     }
798631337658SMarcel Moolenaar 
7987545ddfbeSMarcel Moolenaar     rc = xo_write(xop);
7988545ddfbeSMarcel Moolenaar     if (rc >= 0 && xop->xo_flush)
7989545ddfbeSMarcel Moolenaar 	if (xop->xo_flush(xop->xo_opaque) < 0)
7990545ddfbeSMarcel Moolenaar 	    return -1;
7991545ddfbeSMarcel Moolenaar 
7992545ddfbeSMarcel Moolenaar     return rc;
799331337658SMarcel Moolenaar }
799431337658SMarcel Moolenaar 
79958a6eceffSPhil Shafer xo_ssize_t
xo_flush(void)799631337658SMarcel Moolenaar xo_flush (void)
799731337658SMarcel Moolenaar {
7998545ddfbeSMarcel Moolenaar     return xo_flush_h(NULL);
799931337658SMarcel Moolenaar }
800031337658SMarcel Moolenaar 
80018a6eceffSPhil Shafer xo_ssize_t
xo_finish_h(xo_handle_t * xop)800231337658SMarcel Moolenaar xo_finish_h (xo_handle_t *xop)
800331337658SMarcel Moolenaar {
8004406a584dSPhil Shafer     const char *open_if_empty = "";
800531337658SMarcel Moolenaar     xop = xo_default(xop);
800631337658SMarcel Moolenaar 
8007d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
8008545ddfbeSMarcel Moolenaar 	xo_do_close_all(xop, xop->xo_stack);
8009545ddfbeSMarcel Moolenaar 
8010788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
801131337658SMarcel Moolenaar     case XO_STYLE_JSON:
8012d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
8013406a584dSPhil Shafer 	    const char *pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
8014406a584dSPhil Shafer 
8015d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
8016d1a0d267SMarcel Moolenaar 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
8017406a584dSPhil Shafer 	    else if (!XOIF_ISSET(xop, XOIF_MADE_OUTPUT)) {
8018406a584dSPhil Shafer 		open_if_empty = "{ ";
8019406a584dSPhil Shafer 		pre_nl = "";
8020406a584dSPhil Shafer 	    }
8021406a584dSPhil Shafer 
8022406a584dSPhil Shafer 	    xo_printf(xop, "%s%*s%s}\n",
8023406a584dSPhil Shafer 		      pre_nl, xo_indent(xop), "", open_if_empty);
802431337658SMarcel Moolenaar 	}
802531337658SMarcel Moolenaar 	break;
8026d1a0d267SMarcel Moolenaar 
8027d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
8028f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL, 0);
8029d1a0d267SMarcel Moolenaar 	break;
803031337658SMarcel Moolenaar     }
803131337658SMarcel Moolenaar 
8032545ddfbeSMarcel Moolenaar     return xo_flush_h(xop);
803331337658SMarcel Moolenaar }
803431337658SMarcel Moolenaar 
80358a6eceffSPhil Shafer xo_ssize_t
xo_finish(void)803631337658SMarcel Moolenaar xo_finish (void)
803731337658SMarcel Moolenaar {
8038545ddfbeSMarcel Moolenaar     return xo_finish_h(NULL);
803931337658SMarcel Moolenaar }
804031337658SMarcel Moolenaar 
804131337658SMarcel Moolenaar /*
8042d1a0d267SMarcel Moolenaar  * xo_finish_atexit is suitable for atexit() calls, to force clear up
8043d1a0d267SMarcel Moolenaar  * and finalizing output.
8044d1a0d267SMarcel Moolenaar  */
8045d1a0d267SMarcel Moolenaar void
xo_finish_atexit(void)8046d1a0d267SMarcel Moolenaar xo_finish_atexit (void)
8047d1a0d267SMarcel Moolenaar {
8048d1a0d267SMarcel Moolenaar     (void) xo_finish_h(NULL);
8049d1a0d267SMarcel Moolenaar }
8050d1a0d267SMarcel Moolenaar 
8051d1a0d267SMarcel Moolenaar /*
805231337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
805331337658SMarcel Moolenaar  */
805431337658SMarcel Moolenaar void
xo_errorn_hv(xo_handle_t * xop,int need_newline,const char * fmt,va_list vap)80555c5819b2SPhil Shafer xo_errorn_hv (xo_handle_t *xop, int need_newline, const char *fmt, va_list vap)
805631337658SMarcel Moolenaar {
805731337658SMarcel Moolenaar     xop = xo_default(xop);
805831337658SMarcel Moolenaar 
805931337658SMarcel Moolenaar     /*
806031337658SMarcel Moolenaar      * If the format string doesn't end with a newline, we pop
806131337658SMarcel Moolenaar      * one on ourselves.
806231337658SMarcel Moolenaar      */
80635c5819b2SPhil Shafer     if (need_newline) {
80648a6eceffSPhil Shafer 	ssize_t len = strlen(fmt);
806531337658SMarcel Moolenaar 	if (len > 0 && fmt[len - 1] != '\n') {
806631337658SMarcel Moolenaar 	    char *newfmt = alloca(len + 2);
806731337658SMarcel Moolenaar 	    memcpy(newfmt, fmt, len);
806831337658SMarcel Moolenaar 	    newfmt[len] = '\n';
806976afb20cSPhil Shafer 	    newfmt[len + 1] = '\0';
807031337658SMarcel Moolenaar 	    fmt = newfmt;
807131337658SMarcel Moolenaar 	}
80725c5819b2SPhil Shafer     }
807331337658SMarcel Moolenaar 
8074788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
807531337658SMarcel Moolenaar     case XO_STYLE_TEXT:
807631337658SMarcel Moolenaar 	vfprintf(stderr, fmt, vap);
807731337658SMarcel Moolenaar 	break;
807831337658SMarcel Moolenaar 
807931337658SMarcel Moolenaar     case XO_STYLE_HTML:
808031337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
808131337658SMarcel Moolenaar 
8082264104f2SPhil Shafer 	xo_buf_append_div(xop, "error", 0, NULL, 0, NULL, 0,
8083264104f2SPhil Shafer 			  fmt, strlen(fmt), NULL, 0);
808431337658SMarcel Moolenaar 
8085d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
808631337658SMarcel Moolenaar 	    xo_line_close(xop);
808731337658SMarcel Moolenaar 
808831337658SMarcel Moolenaar 	xo_write(xop);
808931337658SMarcel Moolenaar 
809031337658SMarcel Moolenaar 	va_end(xop->xo_vap);
809131337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
809231337658SMarcel Moolenaar 	break;
809331337658SMarcel Moolenaar 
809431337658SMarcel Moolenaar     case XO_STYLE_XML:
8095545ddfbeSMarcel Moolenaar     case XO_STYLE_JSON:
809631337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
809731337658SMarcel Moolenaar 
809831337658SMarcel Moolenaar 	xo_open_container_h(xop, "error");
8099264104f2SPhil Shafer 	xo_format_value(xop, "message", 7, NULL, 0,
8100264104f2SPhil Shafer 			fmt, strlen(fmt), NULL, 0, 0);
810131337658SMarcel Moolenaar 	xo_close_container_h(xop, "error");
810231337658SMarcel Moolenaar 
810331337658SMarcel Moolenaar 	va_end(xop->xo_vap);
810431337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
810531337658SMarcel Moolenaar 	break;
8106d1a0d267SMarcel Moolenaar 
8107d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
8108d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
8109d1a0d267SMarcel Moolenaar 	break;
811031337658SMarcel Moolenaar     }
811131337658SMarcel Moolenaar }
811231337658SMarcel Moolenaar 
811331337658SMarcel Moolenaar void
xo_error_h(xo_handle_t * xop,const char * fmt,...)811431337658SMarcel Moolenaar xo_error_h (xo_handle_t *xop, const char *fmt, ...)
811531337658SMarcel Moolenaar {
811631337658SMarcel Moolenaar     va_list vap;
811731337658SMarcel Moolenaar 
811831337658SMarcel Moolenaar     va_start(vap, fmt);
81195c5819b2SPhil Shafer     xo_errorn_hv(xop, 0, fmt, vap);
812031337658SMarcel Moolenaar     va_end(vap);
812131337658SMarcel Moolenaar }
812231337658SMarcel Moolenaar 
812331337658SMarcel Moolenaar /*
812431337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
812531337658SMarcel Moolenaar  */
812631337658SMarcel Moolenaar void
xo_error(const char * fmt,...)812731337658SMarcel Moolenaar xo_error (const char *fmt, ...)
812831337658SMarcel Moolenaar {
812931337658SMarcel Moolenaar     va_list vap;
813031337658SMarcel Moolenaar 
813131337658SMarcel Moolenaar     va_start(vap, fmt);
81325c5819b2SPhil Shafer     xo_errorn_hv(NULL, 0, fmt, vap);
81335c5819b2SPhil Shafer     va_end(vap);
81345c5819b2SPhil Shafer }
81355c5819b2SPhil Shafer 
81365c5819b2SPhil Shafer void
xo_errorn_h(xo_handle_t * xop,const char * fmt,...)81375c5819b2SPhil Shafer xo_errorn_h (xo_handle_t *xop, const char *fmt, ...)
81385c5819b2SPhil Shafer {
81395c5819b2SPhil Shafer     va_list vap;
81405c5819b2SPhil Shafer 
81415c5819b2SPhil Shafer     va_start(vap, fmt);
81425c5819b2SPhil Shafer     xo_errorn_hv(xop, 1, fmt, vap);
81435c5819b2SPhil Shafer     va_end(vap);
81445c5819b2SPhil Shafer }
81455c5819b2SPhil Shafer 
81465c5819b2SPhil Shafer /*
81475c5819b2SPhil Shafer  * Generate an error message, such as would be displayed on stderr
81485c5819b2SPhil Shafer  */
81495c5819b2SPhil Shafer void
xo_errorn(const char * fmt,...)81505c5819b2SPhil Shafer xo_errorn (const char *fmt, ...)
81515c5819b2SPhil Shafer {
81525c5819b2SPhil Shafer     va_list vap;
81535c5819b2SPhil Shafer 
81545c5819b2SPhil Shafer     va_start(vap, fmt);
81555c5819b2SPhil Shafer     xo_errorn_hv(NULL, 1, fmt, vap);
815631337658SMarcel Moolenaar     va_end(vap);
815731337658SMarcel Moolenaar }
815831337658SMarcel Moolenaar 
8159d1a0d267SMarcel Moolenaar /*
8160d1a0d267SMarcel Moolenaar  * Parse any libxo-specific options from the command line, removing them
8161d1a0d267SMarcel Moolenaar  * so the main() argument parsing won't see them.  We return the new value
8162d1a0d267SMarcel Moolenaar  * for argc or -1 for error.  If an error occurred, the program should
8163d1a0d267SMarcel Moolenaar  * exit.  A suitable error message has already been displayed.
8164d1a0d267SMarcel Moolenaar  */
816531337658SMarcel Moolenaar int
xo_parse_args(int argc,char ** argv)816631337658SMarcel Moolenaar xo_parse_args (int argc, char **argv)
816731337658SMarcel Moolenaar {
816831337658SMarcel Moolenaar     static char libxo_opt[] = "--libxo";
816931337658SMarcel Moolenaar     char *cp;
817031337658SMarcel Moolenaar     int i, save;
817131337658SMarcel Moolenaar 
81725c5819b2SPhil Shafer     /*
81735c5819b2SPhil Shafer      * If xo_set_program has always been called, we honor that value
81745c5819b2SPhil Shafer      */
81755c5819b2SPhil Shafer     if (xo_program == NULL) {
817631337658SMarcel Moolenaar 	/* Save our program name for xo_err and friends */
817731337658SMarcel Moolenaar 	xo_program = argv[0];
817831337658SMarcel Moolenaar 	cp = strrchr(xo_program, '/');
817931337658SMarcel Moolenaar 	if (cp)
818076afb20cSPhil Shafer 	    xo_program = ++cp;
818167322d16SPhil Shafer 	else
818267322d16SPhil Shafer 	    cp = argv[0];		/* Reset to front of string */
818376afb20cSPhil Shafer 
81845c5819b2SPhil Shafer 	/*
81855c5819b2SPhil Shafer 	 * GNU libtool add an annoying ".test" as the program
81865c5819b2SPhil Shafer 	 * extension; we remove it.  libtool also adds a "lt-" prefix
81875c5819b2SPhil Shafer 	 * that we cannot remove.
81885c5819b2SPhil Shafer 	 */
818976afb20cSPhil Shafer 	size_t len = strlen(xo_program);
819076afb20cSPhil Shafer 	static const char gnu_ext[] = ".test";
819176afb20cSPhil Shafer 	if (len >= sizeof(gnu_ext)) {
819267322d16SPhil Shafer 	    cp += len + 1 - sizeof(gnu_ext);
819376afb20cSPhil Shafer 	    if (xo_streq(cp, gnu_ext))
819476afb20cSPhil Shafer 		*cp = '\0';
819576afb20cSPhil Shafer 	}
81965c5819b2SPhil Shafer     }
819731337658SMarcel Moolenaar 
8198f2b7bf8aSPhil Shafer     xo_handle_t *xop = xo_default(NULL);
8199f2b7bf8aSPhil Shafer 
820031337658SMarcel Moolenaar     for (save = i = 1; i < argc; i++) {
820131337658SMarcel Moolenaar 	if (argv[i] == NULL
820231337658SMarcel Moolenaar 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
820331337658SMarcel Moolenaar 	    if (save != i)
820431337658SMarcel Moolenaar 		argv[save] = argv[i];
820531337658SMarcel Moolenaar 	    save += 1;
820631337658SMarcel Moolenaar 	    continue;
820731337658SMarcel Moolenaar 	}
820831337658SMarcel Moolenaar 
820931337658SMarcel Moolenaar 	cp = argv[i] + sizeof(libxo_opt) - 1;
8210ee5cf116SPhil Shafer 	if (*cp == '\0') {
821131337658SMarcel Moolenaar 	    cp = argv[++i];
8212ee5cf116SPhil Shafer 	    if (cp == NULL) {
821331337658SMarcel Moolenaar 		xo_warnx("missing libxo option");
821431337658SMarcel Moolenaar 		return -1;
821531337658SMarcel Moolenaar 	    }
821631337658SMarcel Moolenaar 
8217f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, cp) < 0)
821831337658SMarcel Moolenaar 		return -1;
821931337658SMarcel Moolenaar 	} else if (*cp == ':') {
8220f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, cp) < 0)
822131337658SMarcel Moolenaar 		return -1;
822231337658SMarcel Moolenaar 
822331337658SMarcel Moolenaar 	} else if (*cp == '=') {
8224f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, ++cp) < 0)
822531337658SMarcel Moolenaar 		return -1;
822631337658SMarcel Moolenaar 
822731337658SMarcel Moolenaar 	} else if (*cp == '-') {
822831337658SMarcel Moolenaar 	    cp += 1;
822976afb20cSPhil Shafer 	    if (xo_streq(cp, "check")) {
823031337658SMarcel Moolenaar 		exit(XO_HAS_LIBXO);
823131337658SMarcel Moolenaar 
823231337658SMarcel Moolenaar 	    } else {
823331337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
823431337658SMarcel Moolenaar 		return -1;
823531337658SMarcel Moolenaar 	    }
823631337658SMarcel Moolenaar 	} else {
823731337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
823831337658SMarcel Moolenaar 	    return -1;
823931337658SMarcel Moolenaar 	}
824031337658SMarcel Moolenaar     }
824131337658SMarcel Moolenaar 
8242f2b7bf8aSPhil Shafer     /*
8243f2b7bf8aSPhil Shafer      * We only want to do color output on terminals, but we only want
8244f2b7bf8aSPhil Shafer      * to do this if the user has asked for color.
8245f2b7bf8aSPhil Shafer      */
8246f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
8247f2b7bf8aSPhil Shafer 	XOF_SET(xop, XOF_COLOR);
8248f2b7bf8aSPhil Shafer 
824931337658SMarcel Moolenaar     argv[save] = NULL;
825031337658SMarcel Moolenaar     return save;
825131337658SMarcel Moolenaar }
825231337658SMarcel Moolenaar 
8253d1a0d267SMarcel Moolenaar /*
8254d1a0d267SMarcel Moolenaar  * Debugging function that dumps the current stack of open libxo constructs,
8255d1a0d267SMarcel Moolenaar  * suitable for calling from the debugger.
8256d1a0d267SMarcel Moolenaar  */
8257545ddfbeSMarcel Moolenaar void
xo_dump_stack(xo_handle_t * xop)8258545ddfbeSMarcel Moolenaar xo_dump_stack (xo_handle_t *xop)
8259545ddfbeSMarcel Moolenaar {
8260545ddfbeSMarcel Moolenaar     int i;
8261545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
8262545ddfbeSMarcel Moolenaar 
8263545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
8264545ddfbeSMarcel Moolenaar 
8265545ddfbeSMarcel Moolenaar     fprintf(stderr, "Stack dump:\n");
8266545ddfbeSMarcel Moolenaar 
8267545ddfbeSMarcel Moolenaar     xsp = xop->xo_stack;
8268545ddfbeSMarcel Moolenaar     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
8269545ddfbeSMarcel Moolenaar 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
8270545ddfbeSMarcel Moolenaar 		i, xo_state_name(xsp->xs_state),
8271545ddfbeSMarcel Moolenaar 		xsp->xs_name ?: "--", xsp->xs_flags);
8272545ddfbeSMarcel Moolenaar     }
8273545ddfbeSMarcel Moolenaar }
8274545ddfbeSMarcel Moolenaar 
8275d1a0d267SMarcel Moolenaar /*
8276d1a0d267SMarcel Moolenaar  * Record the program name used for error messages
8277d1a0d267SMarcel Moolenaar  */
8278545ddfbeSMarcel Moolenaar void
xo_set_program(const char * name)8279545ddfbeSMarcel Moolenaar xo_set_program (const char *name)
8280545ddfbeSMarcel Moolenaar {
8281545ddfbeSMarcel Moolenaar     xo_program = name;
8282545ddfbeSMarcel Moolenaar }
8283545ddfbeSMarcel Moolenaar 
8284788ca347SMarcel Moolenaar void
xo_set_version_h(xo_handle_t * xop,const char * version)828542ff34c3SPhil Shafer xo_set_version_h (xo_handle_t *xop, const char *version)
8286788ca347SMarcel Moolenaar {
8287788ca347SMarcel Moolenaar     xop = xo_default(xop);
8288788ca347SMarcel Moolenaar 
8289788ca347SMarcel Moolenaar     if (version == NULL || strchr(version, '"') != NULL)
8290788ca347SMarcel Moolenaar 	return;
8291788ca347SMarcel Moolenaar 
8292d1a0d267SMarcel Moolenaar     if (!xo_style_is_encoding(xop))
8293d1a0d267SMarcel Moolenaar 	return;
8294d1a0d267SMarcel Moolenaar 
8295788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
8296788ca347SMarcel Moolenaar     case XO_STYLE_XML:
8297788ca347SMarcel Moolenaar 	/* For XML, we record this as an attribute for the first tag */
82988a6eceffSPhil Shafer 	xo_attr_h(xop, "version", "%s", version);
8299788ca347SMarcel Moolenaar 	break;
8300788ca347SMarcel Moolenaar 
8301788ca347SMarcel Moolenaar     case XO_STYLE_JSON:
8302788ca347SMarcel Moolenaar 	/*
8303d1a0d267SMarcel Moolenaar 	 * For JSON, we record the version string in our handle, and emit
8304788ca347SMarcel Moolenaar 	 * it in xo_emit_top.
8305788ca347SMarcel Moolenaar 	 */
8306d1a0d267SMarcel Moolenaar 	xop->xo_version = xo_strndup(version, -1);
8307d1a0d267SMarcel Moolenaar 	break;
8308d1a0d267SMarcel Moolenaar 
8309d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
8310f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version, 0);
8311788ca347SMarcel Moolenaar 	break;
8312788ca347SMarcel Moolenaar     }
8313788ca347SMarcel Moolenaar }
8314788ca347SMarcel Moolenaar 
8315d1a0d267SMarcel Moolenaar /*
8316ee5cf116SPhil Shafer  * Set the version number for the API content being carried through
8317d1a0d267SMarcel Moolenaar  * the xo handle.
8318d1a0d267SMarcel Moolenaar  */
8319788ca347SMarcel Moolenaar void
xo_set_version(const char * version)8320788ca347SMarcel Moolenaar xo_set_version (const char *version)
8321788ca347SMarcel Moolenaar {
8322788ca347SMarcel Moolenaar     xo_set_version_h(NULL, version);
8323788ca347SMarcel Moolenaar }
8324788ca347SMarcel Moolenaar 
8325d1a0d267SMarcel Moolenaar /*
8326d1a0d267SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
8327d1a0d267SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
8328d1a0d267SMarcel Moolenaar  * XMLified content on standard output.
8329d1a0d267SMarcel Moolenaar  */
8330d1a0d267SMarcel Moolenaar void
xo_emit_warn_hcv(xo_handle_t * xop,int as_warning,int code,const char * fmt,va_list vap)8331d1a0d267SMarcel Moolenaar xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
8332d1a0d267SMarcel Moolenaar 		  const char *fmt, va_list vap)
833331337658SMarcel Moolenaar {
8334d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
833531337658SMarcel Moolenaar 
8336d1a0d267SMarcel Moolenaar     if (fmt == NULL)
8337d1a0d267SMarcel Moolenaar 	return;
833831337658SMarcel Moolenaar 
8339d1a0d267SMarcel Moolenaar     xo_open_marker_h(xop, "xo_emit_warn_hcv");
8340d1a0d267SMarcel Moolenaar     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
834131337658SMarcel Moolenaar 
8342d1a0d267SMarcel Moolenaar     if (xo_program)
8343d1a0d267SMarcel Moolenaar 	xo_emit("{wc:program}", xo_program);
834431337658SMarcel Moolenaar 
8345d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
8346d1a0d267SMarcel Moolenaar 	va_list ap;
8347d1a0d267SMarcel Moolenaar 	xo_handle_t temp;
834831337658SMarcel Moolenaar 
8349d1a0d267SMarcel Moolenaar 	bzero(&temp, sizeof(temp));
8350d1a0d267SMarcel Moolenaar 	temp.xo_style = XO_STYLE_TEXT;
8351d1a0d267SMarcel Moolenaar 	xo_buf_init(&temp.xo_data);
8352d1a0d267SMarcel Moolenaar 	xo_depth_check(&temp, XO_DEPTH);
835331337658SMarcel Moolenaar 
8354d1a0d267SMarcel Moolenaar 	va_copy(ap, vap);
8355d1a0d267SMarcel Moolenaar 	(void) xo_emit_hv(&temp, fmt, ap);
8356d1a0d267SMarcel Moolenaar 	va_end(ap);
835731337658SMarcel Moolenaar 
8358d1a0d267SMarcel Moolenaar 	xo_buffer_t *src = &temp.xo_data;
8359d1a0d267SMarcel Moolenaar 	xo_format_value(xop, "message", 7, src->xb_bufp,
8360264104f2SPhil Shafer 			src->xb_curp - src->xb_bufp, NULL, 0, NULL, 0, 0);
836131337658SMarcel Moolenaar 
8362d1a0d267SMarcel Moolenaar 	xo_free(temp.xo_stack);
8363d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(src);
836431337658SMarcel Moolenaar     }
836531337658SMarcel Moolenaar 
8366d1a0d267SMarcel Moolenaar     (void) xo_emit_hv(xop, fmt, vap);
836731337658SMarcel Moolenaar 
83688a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
8369d1a0d267SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
8370d1a0d267SMarcel Moolenaar 	if (code > 0) {
8371d1a0d267SMarcel Moolenaar 	    const char *msg = strerror(code);
8372d1a0d267SMarcel Moolenaar 	    if (msg)
8373d1a0d267SMarcel Moolenaar 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
8374d1a0d267SMarcel Moolenaar 	}
8375d1a0d267SMarcel Moolenaar 	xo_emit("\n");
837631337658SMarcel Moolenaar     }
837731337658SMarcel Moolenaar 
8378d1a0d267SMarcel Moolenaar     xo_close_marker_h(xop, "xo_emit_warn_hcv");
8379d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
838031337658SMarcel Moolenaar }
838131337658SMarcel Moolenaar 
8382d1a0d267SMarcel Moolenaar void
xo_emit_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)8383d1a0d267SMarcel Moolenaar xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
8384d1a0d267SMarcel Moolenaar {
8385d1a0d267SMarcel Moolenaar     va_list vap;
838631337658SMarcel Moolenaar 
8387d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8388d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
8389d1a0d267SMarcel Moolenaar     va_end(vap);
839031337658SMarcel Moolenaar }
839131337658SMarcel Moolenaar 
8392d1a0d267SMarcel Moolenaar void
xo_emit_warn_c(int code,const char * fmt,...)8393d1a0d267SMarcel Moolenaar xo_emit_warn_c (int code, const char *fmt, ...)
8394d1a0d267SMarcel Moolenaar {
8395d1a0d267SMarcel Moolenaar     va_list vap;
839631337658SMarcel Moolenaar 
8397d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8398d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8399d1a0d267SMarcel Moolenaar     va_end(vap);
8400d1a0d267SMarcel Moolenaar }
840131337658SMarcel Moolenaar 
8402d1a0d267SMarcel Moolenaar void
xo_emit_warn(const char * fmt,...)8403d1a0d267SMarcel Moolenaar xo_emit_warn (const char *fmt, ...)
8404d1a0d267SMarcel Moolenaar {
8405d1a0d267SMarcel Moolenaar     int code = errno;
8406d1a0d267SMarcel Moolenaar     va_list vap;
8407d1a0d267SMarcel Moolenaar 
8408d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8409d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8410d1a0d267SMarcel Moolenaar     va_end(vap);
8411d1a0d267SMarcel Moolenaar }
8412d1a0d267SMarcel Moolenaar 
8413d1a0d267SMarcel Moolenaar void
xo_emit_warnx(const char * fmt,...)8414d1a0d267SMarcel Moolenaar xo_emit_warnx (const char *fmt, ...)
8415d1a0d267SMarcel Moolenaar {
8416d1a0d267SMarcel Moolenaar     va_list vap;
8417d1a0d267SMarcel Moolenaar 
8418d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8419d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
8420d1a0d267SMarcel Moolenaar     va_end(vap);
8421d1a0d267SMarcel Moolenaar }
8422d1a0d267SMarcel Moolenaar 
8423d1a0d267SMarcel Moolenaar void
xo_emit_err_v(int eval,int code,const char * fmt,va_list vap)8424d1a0d267SMarcel Moolenaar xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
8425d1a0d267SMarcel Moolenaar {
8426d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
842731337658SMarcel Moolenaar     xo_finish();
8428d1a0d267SMarcel Moolenaar     exit(eval);
842931337658SMarcel Moolenaar }
8430d1a0d267SMarcel Moolenaar 
8431d1a0d267SMarcel Moolenaar void
xo_emit_err(int eval,const char * fmt,...)8432d1a0d267SMarcel Moolenaar xo_emit_err (int eval, const char *fmt, ...)
8433d1a0d267SMarcel Moolenaar {
8434d1a0d267SMarcel Moolenaar     int code = errno;
8435d1a0d267SMarcel Moolenaar     va_list vap;
8436d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
843776afb20cSPhil Shafer     xo_emit_err_v(eval, code, fmt, vap);
843876afb20cSPhil Shafer     /*NOTREACHED*/
8439d1a0d267SMarcel Moolenaar }
8440d1a0d267SMarcel Moolenaar 
8441d1a0d267SMarcel Moolenaar void
xo_emit_errx(int eval,const char * fmt,...)8442d1a0d267SMarcel Moolenaar xo_emit_errx (int eval, const char *fmt, ...)
8443d1a0d267SMarcel Moolenaar {
8444d1a0d267SMarcel Moolenaar     va_list vap;
8445d1a0d267SMarcel Moolenaar 
8446d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
844776afb20cSPhil Shafer     xo_emit_err_v(eval, -1, fmt, vap); /* This will exit */
844876afb20cSPhil Shafer     /*NOTREACHED*/
8449d1a0d267SMarcel Moolenaar }
8450d1a0d267SMarcel Moolenaar 
8451d1a0d267SMarcel Moolenaar void
xo_emit_errc(int eval,int code,const char * fmt,...)8452d1a0d267SMarcel Moolenaar xo_emit_errc (int eval, int code, const char *fmt, ...)
8453d1a0d267SMarcel Moolenaar {
8454d1a0d267SMarcel Moolenaar     va_list vap;
8455d1a0d267SMarcel Moolenaar 
8456d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
845776afb20cSPhil Shafer     xo_emit_err_v(eval, code, fmt, vap); /* This will exit */
845876afb20cSPhil Shafer     /*NOTREACHED*/
8459d1a0d267SMarcel Moolenaar }
8460d1a0d267SMarcel Moolenaar 
8461d1a0d267SMarcel Moolenaar /*
8462d1a0d267SMarcel Moolenaar  * Get the opaque private pointer for an xo handle
8463d1a0d267SMarcel Moolenaar  */
8464d1a0d267SMarcel Moolenaar void *
xo_get_private(xo_handle_t * xop)8465d1a0d267SMarcel Moolenaar xo_get_private (xo_handle_t *xop)
8466d1a0d267SMarcel Moolenaar {
8467d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8468d1a0d267SMarcel Moolenaar     return xop->xo_private;
8469d1a0d267SMarcel Moolenaar }
8470d1a0d267SMarcel Moolenaar 
8471d1a0d267SMarcel Moolenaar /*
8472d1a0d267SMarcel Moolenaar  * Set the opaque private pointer for an xo handle.
8473d1a0d267SMarcel Moolenaar  */
8474d1a0d267SMarcel Moolenaar void
xo_set_private(xo_handle_t * xop,void * opaque)8475d1a0d267SMarcel Moolenaar xo_set_private (xo_handle_t *xop, void *opaque)
8476d1a0d267SMarcel Moolenaar {
8477d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8478d1a0d267SMarcel Moolenaar     xop->xo_private = opaque;
8479d1a0d267SMarcel Moolenaar }
8480d1a0d267SMarcel Moolenaar 
8481d1a0d267SMarcel Moolenaar /*
8482d1a0d267SMarcel Moolenaar  * Get the encoder function
8483d1a0d267SMarcel Moolenaar  */
8484d1a0d267SMarcel Moolenaar xo_encoder_func_t
xo_get_encoder(xo_handle_t * xop)8485d1a0d267SMarcel Moolenaar xo_get_encoder (xo_handle_t *xop)
8486d1a0d267SMarcel Moolenaar {
8487d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8488d1a0d267SMarcel Moolenaar     return xop->xo_encoder;
8489d1a0d267SMarcel Moolenaar }
8490d1a0d267SMarcel Moolenaar 
8491d1a0d267SMarcel Moolenaar /*
8492d1a0d267SMarcel Moolenaar  * Record an encoder callback function in an xo handle.
8493d1a0d267SMarcel Moolenaar  */
8494d1a0d267SMarcel Moolenaar void
xo_set_encoder(xo_handle_t * xop,xo_encoder_func_t encoder)8495d1a0d267SMarcel Moolenaar xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8496d1a0d267SMarcel Moolenaar {
8497d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8498d1a0d267SMarcel Moolenaar 
8499d1a0d267SMarcel Moolenaar     xop->xo_style = XO_STYLE_ENCODER;
8500d1a0d267SMarcel Moolenaar     xop->xo_encoder = encoder;
8501d1a0d267SMarcel Moolenaar }
8502406a584dSPhil Shafer 
8503406a584dSPhil Shafer /*
8504406a584dSPhil Shafer  * The xo(1) utility needs to be able to open and close lists and
8505406a584dSPhil Shafer  * instances, but since it's called without "state", we cannot
8506406a584dSPhil Shafer  * rely on the state transitions (in xo_transition) to DTRT, so
8507406a584dSPhil Shafer  * we have a mechanism for external parties to "force" transitions
8508406a584dSPhil Shafer  * that would otherwise be impossible.  This is not a general
8509406a584dSPhil Shafer  * mechanism, and is really tailored only for xo(1).
8510406a584dSPhil Shafer  */
8511406a584dSPhil Shafer void
xo_explicit_transition(xo_handle_t * xop,xo_state_t new_state,const char * name,xo_xof_flags_t flags)8512406a584dSPhil Shafer xo_explicit_transition (xo_handle_t *xop, xo_state_t new_state,
8513406a584dSPhil Shafer 			const char *name, xo_xof_flags_t flags)
8514406a584dSPhil Shafer {
8515406a584dSPhil Shafer     xo_xsf_flags_t xsf_flags;
8516406a584dSPhil Shafer 
8517406a584dSPhil Shafer     xop = xo_default(xop);
8518406a584dSPhil Shafer 
8519406a584dSPhil Shafer     switch (new_state) {
8520406a584dSPhil Shafer 
8521406a584dSPhil Shafer     case XSS_OPEN_LIST:
8522406a584dSPhil Shafer 	xo_do_open_list(xop, flags, name);
8523406a584dSPhil Shafer 	break;
8524406a584dSPhil Shafer 
8525406a584dSPhil Shafer     case XSS_OPEN_INSTANCE:
8526406a584dSPhil Shafer 	xo_do_open_instance(xop, flags, name);
8527406a584dSPhil Shafer 	break;
8528406a584dSPhil Shafer 
8529406a584dSPhil Shafer     case XSS_CLOSE_INSTANCE:
8530406a584dSPhil Shafer 	xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE,
8531406a584dSPhil Shafer 			xo_stack_flags(flags));
8532406a584dSPhil Shafer 	xo_stack_set_flags(xop);
8533406a584dSPhil Shafer 	xo_do_close_instance(xop, name);
8534406a584dSPhil Shafer 	break;
8535406a584dSPhil Shafer 
8536406a584dSPhil Shafer     case XSS_CLOSE_LIST:
8537406a584dSPhil Shafer 	xsf_flags = XOF_ISSET(xop, XOF_NOT_FIRST) ? XSF_NOT_FIRST : 0;
8538406a584dSPhil Shafer 
8539406a584dSPhil Shafer 	xo_depth_change(xop, name, 1, 1, XSS_OPEN_LIST,
8540406a584dSPhil Shafer 			XSF_LIST | xsf_flags | xo_stack_flags(flags));
8541406a584dSPhil Shafer 	xo_do_close_list(xop, name);
8542406a584dSPhil Shafer 	break;
8543406a584dSPhil Shafer     }
8544406a584dSPhil Shafer }
8545