xref: /freebsd/contrib/libxo/libxo/libxo.c (revision 264104f2)
131337658SMarcel Moolenaar /*
2545ddfbeSMarcel Moolenaar  * Copyright (c) 2014-2015, 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"
50d1a0d267SMarcel Moolenaar 
51d1a0d267SMarcel Moolenaar /*
52d1a0d267SMarcel Moolenaar  * We ask wcwidth() to do an impossible job, really.  It's supposed to
53d1a0d267SMarcel Moolenaar  * need to tell us the number of columns consumed to display a unicode
54d1a0d267SMarcel Moolenaar  * character.  It returns that number without any sort of context, but
55d1a0d267SMarcel Moolenaar  * we know they are characters whose glyph differs based on placement
56d1a0d267SMarcel Moolenaar  * (end of word, middle of word, etc) and many that affect characters
57d1a0d267SMarcel Moolenaar  * previously emitted.  Without content, it can't hope to tell us.
58d1a0d267SMarcel Moolenaar  * But it's the only standard tool we've got, so we use it.  We would
59ee5cf116SPhil Shafer  * use wcswidth() but it typically just loops through adding the results
60d1a0d267SMarcel Moolenaar  * of wcwidth() calls in an entirely unhelpful way.
61d1a0d267SMarcel Moolenaar  *
62d1a0d267SMarcel Moolenaar  * Even then, there are many poor implementations (macosx), so we have
63d1a0d267SMarcel Moolenaar  * to carry our own.  We could have configure.ac test this (with
64d1a0d267SMarcel Moolenaar  * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
65d1a0d267SMarcel Moolenaar  * to run a binary, which breaks cross-compilation.  Hmm... I could
66d1a0d267SMarcel Moolenaar  * run this test at init time and make a warning for our dear user.
67d1a0d267SMarcel Moolenaar  *
68d1a0d267SMarcel Moolenaar  * Anyhow, it remains a best-effort sort of thing.  And it's all made
69d1a0d267SMarcel Moolenaar  * more hopeless because we assume the display code doing the rendering is
70d1a0d267SMarcel Moolenaar  * playing by the same rules we are.  If it display 0x200d as a square
71d1a0d267SMarcel Moolenaar  * box or a funky question mark, the output will be hosed.
72d1a0d267SMarcel Moolenaar  */
73d1a0d267SMarcel Moolenaar #ifdef LIBXO_WCWIDTH
74d1a0d267SMarcel Moolenaar #include "xo_wcwidth.h"
75d1a0d267SMarcel Moolenaar #else /* LIBXO_WCWIDTH */
76d1a0d267SMarcel Moolenaar #define xo_wcwidth(_x) wcwidth(_x)
77d1a0d267SMarcel Moolenaar #endif /* LIBXO_WCWIDTH */
7831337658SMarcel Moolenaar 
79545ddfbeSMarcel Moolenaar #ifdef HAVE_STDIO_EXT_H
80545ddfbeSMarcel Moolenaar #include <stdio_ext.h>
81545ddfbeSMarcel Moolenaar #endif /* HAVE_STDIO_EXT_H */
82545ddfbeSMarcel Moolenaar 
83d1a0d267SMarcel Moolenaar /*
84d1a0d267SMarcel Moolenaar  * humanize_number is a great function, unless you don't have it.  So
85d1a0d267SMarcel Moolenaar  * we carry one in our pocket.
86d1a0d267SMarcel Moolenaar  */
87d1a0d267SMarcel Moolenaar #ifdef HAVE_HUMANIZE_NUMBER
88d1a0d267SMarcel Moolenaar #include <libutil.h>
89d1a0d267SMarcel Moolenaar #define xo_humanize_number humanize_number
90d1a0d267SMarcel Moolenaar #else /* HAVE_HUMANIZE_NUMBER */
91d1a0d267SMarcel Moolenaar #include "xo_humanize.h"
92d1a0d267SMarcel Moolenaar #endif /* HAVE_HUMANIZE_NUMBER */
93d1a0d267SMarcel Moolenaar 
94d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
95d1a0d267SMarcel Moolenaar #include <libintl.h>
96d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
97d1a0d267SMarcel Moolenaar 
98264104f2SPhil Shafer /* Rather lame that we can't count on these... */
99264104f2SPhil Shafer #ifndef FALSE
100264104f2SPhil Shafer #define FALSE 0
101264104f2SPhil Shafer #endif
102264104f2SPhil Shafer #ifndef TRUE
103264104f2SPhil Shafer #define TRUE 1
104264104f2SPhil Shafer #endif
105264104f2SPhil Shafer 
106d1a0d267SMarcel Moolenaar /*
107d1a0d267SMarcel Moolenaar  * Three styles of specifying thread-local variables are supported.
108ee5cf116SPhil Shafer  * configure.ac has the brains to run each possibility through the
109d1a0d267SMarcel Moolenaar  * compiler and see what works; we are left to define the THREAD_LOCAL
110d1a0d267SMarcel Moolenaar  * macro to the right value.  Most toolchains (clang, gcc) use
111d1a0d267SMarcel Moolenaar  * "before", but some (borland) use "after" and I've heard of some
112d1a0d267SMarcel Moolenaar  * (ms) that use __declspec.  Any others out there?
113d1a0d267SMarcel Moolenaar  */
114d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_before 1
115d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_after 2
116d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_declspec 3
117d1a0d267SMarcel Moolenaar 
118d1a0d267SMarcel Moolenaar #ifndef HAVE_THREAD_LOCAL
119d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x
120d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
121d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __thread _x
122d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
123d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x __thread
124d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
125d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __declspec(_x)
126d1a0d267SMarcel Moolenaar #else
127d1a0d267SMarcel Moolenaar #error unknown thread-local setting
128d1a0d267SMarcel Moolenaar #endif /* HAVE_THREADS_H */
129d1a0d267SMarcel Moolenaar 
13031337658SMarcel Moolenaar const char xo_version[] = LIBXO_VERSION;
13131337658SMarcel Moolenaar const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
13242ff34c3SPhil Shafer static const char xo_default_format[] = "%s";
13331337658SMarcel Moolenaar 
13431337658SMarcel Moolenaar #ifndef UNUSED
13531337658SMarcel Moolenaar #define UNUSED __attribute__ ((__unused__))
13631337658SMarcel Moolenaar #endif /* UNUSED */
13731337658SMarcel Moolenaar 
13831337658SMarcel Moolenaar #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
139d1a0d267SMarcel Moolenaar #define XO_DEPTH	128	 /* Default stack depth */
14031337658SMarcel Moolenaar #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
14131337658SMarcel Moolenaar 
14231337658SMarcel Moolenaar #define XO_FAILURE_NAME	"failure"
14331337658SMarcel Moolenaar 
14431337658SMarcel Moolenaar /* Flags for the stack frame */
14531337658SMarcel Moolenaar typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
14631337658SMarcel Moolenaar #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
14731337658SMarcel Moolenaar #define XSF_LIST	(1<<1)	/* Frame is a list */
14831337658SMarcel Moolenaar #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
14931337658SMarcel Moolenaar #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
15031337658SMarcel Moolenaar 
151545ddfbeSMarcel Moolenaar #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
152545ddfbeSMarcel Moolenaar #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
153545ddfbeSMarcel Moolenaar #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
154545ddfbeSMarcel Moolenaar #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
155545ddfbeSMarcel Moolenaar 
156545ddfbeSMarcel Moolenaar /* These are the flags we propagate between markers and their parents */
157545ddfbeSMarcel Moolenaar #define XSF_MARKER_FLAGS \
158545ddfbeSMarcel Moolenaar  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
159545ddfbeSMarcel Moolenaar 
160545ddfbeSMarcel Moolenaar /*
161d1a0d267SMarcel Moolenaar  * A word about states: We use a finite state machine (FMS) approach
162d1a0d267SMarcel Moolenaar  * to help remove fragility from the caller's code.  Instead of
163d1a0d267SMarcel Moolenaar  * requiring a specific order of calls, we'll allow the caller more
164545ddfbeSMarcel Moolenaar  * flexibility and make the library responsible for recovering from
165d1a0d267SMarcel Moolenaar  * missed steps.  The goal is that the library should not be capable
166d1a0d267SMarcel Moolenaar  * of emitting invalid xml or json, but the developer shouldn't need
167545ddfbeSMarcel Moolenaar  * to know or understand all the details about these encodings.
168545ddfbeSMarcel Moolenaar  *
169d1a0d267SMarcel Moolenaar  * You can think of states as either states or events, since they
170545ddfbeSMarcel Moolenaar  * function rather like both.  None of the XO_CLOSE_* events will
171d1a0d267SMarcel Moolenaar  * persist as states, since the matching stack frame will be popped.
172545ddfbeSMarcel Moolenaar  * Same is true of XSS_EMIT, which is an event that asks us to
173545ddfbeSMarcel Moolenaar  * prep for emitting output fields.
174545ddfbeSMarcel Moolenaar  */
175545ddfbeSMarcel Moolenaar 
176545ddfbeSMarcel Moolenaar /* Stack frame states */
177545ddfbeSMarcel Moolenaar typedef unsigned xo_state_t;
178545ddfbeSMarcel Moolenaar #define XSS_INIT		0      	/* Initial stack state */
179545ddfbeSMarcel Moolenaar #define XSS_OPEN_CONTAINER	1
180545ddfbeSMarcel Moolenaar #define XSS_CLOSE_CONTAINER	2
181545ddfbeSMarcel Moolenaar #define XSS_OPEN_LIST		3
182545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LIST		4
183545ddfbeSMarcel Moolenaar #define XSS_OPEN_INSTANCE	5
184545ddfbeSMarcel Moolenaar #define XSS_CLOSE_INSTANCE	6
185545ddfbeSMarcel Moolenaar #define XSS_OPEN_LEAF_LIST	7
186545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LEAF_LIST	8
187545ddfbeSMarcel Moolenaar #define XSS_DISCARDING		9	/* Discarding data until recovered */
188545ddfbeSMarcel Moolenaar #define XSS_MARKER		10	/* xo_open_marker's marker */
189545ddfbeSMarcel Moolenaar #define XSS_EMIT		11	/* xo_emit has a leaf field */
190545ddfbeSMarcel Moolenaar #define XSS_EMIT_LEAF_LIST	12	/* xo_emit has a leaf-list ({l:}) */
191545ddfbeSMarcel Moolenaar #define XSS_FINISH		13	/* xo_finish was called */
192545ddfbeSMarcel Moolenaar 
193545ddfbeSMarcel Moolenaar #define XSS_MAX			13
194545ddfbeSMarcel Moolenaar 
195545ddfbeSMarcel Moolenaar #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
196545ddfbeSMarcel Moolenaar 
19731337658SMarcel Moolenaar /*
19831337658SMarcel Moolenaar  * xo_stack_t: As we open and close containers and levels, we
19931337658SMarcel Moolenaar  * create a stack of frames to track them.  This is needed for
20031337658SMarcel Moolenaar  * XOF_WARN and XOF_XPATH.
20131337658SMarcel Moolenaar  */
20231337658SMarcel Moolenaar typedef struct xo_stack_s {
20331337658SMarcel Moolenaar     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
204545ddfbeSMarcel Moolenaar     xo_state_t xs_state;	/* State for this stack frame */
20531337658SMarcel Moolenaar     char *xs_name;		/* Name (for XPath value) */
20631337658SMarcel Moolenaar     char *xs_keys;		/* XPath predicate for any key fields */
20731337658SMarcel Moolenaar } xo_stack_t;
20831337658SMarcel Moolenaar 
209d1a0d267SMarcel Moolenaar /*
210d1a0d267SMarcel Moolenaar  * libxo supports colors and effects, for those who like them.
211d1a0d267SMarcel Moolenaar  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
212d1a0d267SMarcel Moolenaar  * ("effects") are bits since we need to maintain state.
213d1a0d267SMarcel Moolenaar  */
214f2b7bf8aSPhil Shafer typedef uint8_t xo_color_t;
215788ca347SMarcel Moolenaar #define XO_COL_DEFAULT		0
216788ca347SMarcel Moolenaar #define XO_COL_BLACK		1
217788ca347SMarcel Moolenaar #define XO_COL_RED		2
218788ca347SMarcel Moolenaar #define XO_COL_GREEN		3
219788ca347SMarcel Moolenaar #define XO_COL_YELLOW		4
220788ca347SMarcel Moolenaar #define XO_COL_BLUE		5
221788ca347SMarcel Moolenaar #define XO_COL_MAGENTA		6
222788ca347SMarcel Moolenaar #define XO_COL_CYAN		7
223788ca347SMarcel Moolenaar #define XO_COL_WHITE		8
224788ca347SMarcel Moolenaar 
225788ca347SMarcel Moolenaar #define XO_NUM_COLORS		9
226788ca347SMarcel Moolenaar 
227788ca347SMarcel Moolenaar /*
228788ca347SMarcel Moolenaar  * Yes, there's no blink.  We're civilized.  We like users.  Blink
229788ca347SMarcel Moolenaar  * isn't something one does to someone you like.  Friends don't let
230788ca347SMarcel Moolenaar  * friends use blink.  On friends.  You know what I mean.  Blink is
231788ca347SMarcel Moolenaar  * like, well, it's like bursting into show tunes at a funeral.  It's
232788ca347SMarcel Moolenaar  * just not done.  Not something anyone wants.  And on those rare
233d1a0d267SMarcel Moolenaar  * instances where it might actually be appropriate, it's still wrong,
234d1a0d267SMarcel Moolenaar  * since it's likely done by the wrong person for the wrong reason.
235d1a0d267SMarcel Moolenaar  * Just like blink.  And if I implemented blink, I'd be like a funeral
236788ca347SMarcel Moolenaar  * director who adds "Would you like us to burst into show tunes?" on
237d1a0d267SMarcel Moolenaar  * the list of questions asked while making funeral arrangements.
238788ca347SMarcel Moolenaar  * It's formalizing wrongness in the wrong way.  And we're just too
239788ca347SMarcel Moolenaar  * civilized to do that.  Hhhmph!
240788ca347SMarcel Moolenaar  */
241788ca347SMarcel Moolenaar #define XO_EFF_RESET		(1<<0)
242788ca347SMarcel Moolenaar #define XO_EFF_NORMAL		(1<<1)
243788ca347SMarcel Moolenaar #define XO_EFF_BOLD		(1<<2)
244788ca347SMarcel Moolenaar #define XO_EFF_UNDERLINE	(1<<3)
245788ca347SMarcel Moolenaar #define XO_EFF_INVERSE		(1<<4)
246788ca347SMarcel Moolenaar 
247d1a0d267SMarcel Moolenaar #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
248788ca347SMarcel Moolenaar 
249788ca347SMarcel Moolenaar typedef uint8_t xo_effect_t;
250788ca347SMarcel Moolenaar typedef struct xo_colors_s {
251788ca347SMarcel Moolenaar     xo_effect_t xoc_effects;	/* Current effect set */
252788ca347SMarcel Moolenaar     xo_color_t xoc_col_fg;	/* Foreground color */
253788ca347SMarcel Moolenaar     xo_color_t xoc_col_bg;	/* Background color */
254788ca347SMarcel Moolenaar } xo_colors_t;
255788ca347SMarcel Moolenaar 
25631337658SMarcel Moolenaar /*
25731337658SMarcel Moolenaar  * xo_handle_t: this is the principle data structure for libxo.
258d1a0d267SMarcel Moolenaar  * It's used as a store for state, options, content, and all manor
259d1a0d267SMarcel Moolenaar  * of other information.
26031337658SMarcel Moolenaar  */
26131337658SMarcel Moolenaar struct xo_handle_s {
262d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
263d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
264d1a0d267SMarcel Moolenaar     xo_style_t xo_style;	/* XO_STYLE_* value */
26531337658SMarcel Moolenaar     unsigned short xo_indent;	/* Indent level (if pretty) */
26631337658SMarcel Moolenaar     unsigned short xo_indent_by; /* Indent amount (tab stop) */
26731337658SMarcel Moolenaar     xo_write_func_t xo_write;	/* Write callback */
268a0f704ffSMarcel Moolenaar     xo_close_func_t xo_close;	/* Close callback */
269545ddfbeSMarcel Moolenaar     xo_flush_func_t xo_flush;	/* Flush callback */
27031337658SMarcel Moolenaar     xo_formatter_t xo_formatter; /* Custom formating function */
27131337658SMarcel Moolenaar     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
27231337658SMarcel Moolenaar     void *xo_opaque;		/* Opaque data for write function */
27331337658SMarcel Moolenaar     xo_buffer_t xo_data;	/* Output data */
27431337658SMarcel Moolenaar     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
27531337658SMarcel Moolenaar     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
27631337658SMarcel Moolenaar     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
27731337658SMarcel Moolenaar     xo_stack_t *xo_stack;	/* Stack pointer */
27831337658SMarcel Moolenaar     int xo_depth;		/* Depth of stack */
27931337658SMarcel Moolenaar     int xo_stack_size;		/* Size of the stack */
28031337658SMarcel Moolenaar     xo_info_t *xo_info;		/* Info fields for all elements */
28131337658SMarcel Moolenaar     int xo_info_count;		/* Number of info entries */
28231337658SMarcel Moolenaar     va_list xo_vap;		/* Variable arguments (stdargs) */
28331337658SMarcel Moolenaar     char *xo_leading_xpath;	/* A leading XPath expression */
28431337658SMarcel Moolenaar     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
2858a6eceffSPhil Shafer     ssize_t xo_anchor_offset;	/* Start of anchored text */
2868a6eceffSPhil Shafer     ssize_t xo_anchor_columns;	/* Number of columns since the start anchor */
2878a6eceffSPhil Shafer     ssize_t xo_anchor_min_width; /* Desired width of anchored text */
2888a6eceffSPhil Shafer     ssize_t xo_units_offset;	/* Start of units insertion point */
2898a6eceffSPhil Shafer     ssize_t xo_columns;	/* Columns emitted during this xo_emit call */
290f2b7bf8aSPhil Shafer #ifndef LIBXO_TEXT_ONLY
291788ca347SMarcel Moolenaar     uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
292788ca347SMarcel Moolenaar     uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
293f2b7bf8aSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
294788ca347SMarcel Moolenaar     xo_colors_t xo_colors;	/* Current color and effect values */
295788ca347SMarcel Moolenaar     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
296788ca347SMarcel Moolenaar     char *xo_version;		/* Version string */
297d1a0d267SMarcel Moolenaar     int xo_errno;		/* Saved errno for "%m" */
298d1a0d267SMarcel Moolenaar     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
299d1a0d267SMarcel Moolenaar     xo_encoder_func_t xo_encoder; /* Encoding function */
300d1a0d267SMarcel Moolenaar     void *xo_private;		/* Private data for external encoders */
30131337658SMarcel Moolenaar };
30231337658SMarcel Moolenaar 
303d1a0d267SMarcel Moolenaar /* Flag operations */
304d1a0d267SMarcel Moolenaar #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
305d1a0d267SMarcel Moolenaar #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
306d1a0d267SMarcel Moolenaar #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
307d1a0d267SMarcel Moolenaar 
308d1a0d267SMarcel Moolenaar #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
309d1a0d267SMarcel Moolenaar #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
310d1a0d267SMarcel Moolenaar #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
311d1a0d267SMarcel Moolenaar 
312d1a0d267SMarcel Moolenaar #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
313d1a0d267SMarcel Moolenaar #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
314d1a0d267SMarcel Moolenaar #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
315d1a0d267SMarcel Moolenaar 
316d1a0d267SMarcel Moolenaar /* Internal flags */
317d1a0d267SMarcel Moolenaar #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
318d1a0d267SMarcel Moolenaar #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
319d1a0d267SMarcel Moolenaar #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
320d1a0d267SMarcel Moolenaar #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
321d1a0d267SMarcel Moolenaar 
322d1a0d267SMarcel Moolenaar #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
323d1a0d267SMarcel Moolenaar #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
324d1a0d267SMarcel Moolenaar 
32531337658SMarcel Moolenaar /* Flags for formatting functions */
32631337658SMarcel Moolenaar typedef unsigned long xo_xff_flags_t;
32731337658SMarcel Moolenaar #define XFF_COLON	(1<<0)	/* Append a ":" */
32831337658SMarcel Moolenaar #define XFF_COMMA	(1<<1)	/* Append a "," iff there's more output */
32931337658SMarcel Moolenaar #define XFF_WS		(1<<2)	/* Append a blank */
330d1a0d267SMarcel Moolenaar #define XFF_ENCODE_ONLY	(1<<3)	/* Only emit for encoding styles (XML, JSON) */
33131337658SMarcel Moolenaar 
33231337658SMarcel Moolenaar #define XFF_QUOTE	(1<<4)	/* Force quotes */
33331337658SMarcel Moolenaar #define XFF_NOQUOTE	(1<<5)	/* Force no quotes */
334d1a0d267SMarcel Moolenaar #define XFF_DISPLAY_ONLY (1<<6)	/* Only emit for display styles (text, html) */
33531337658SMarcel Moolenaar #define XFF_KEY		(1<<7)	/* Field is a key (for XPath) */
33631337658SMarcel Moolenaar 
33731337658SMarcel Moolenaar #define XFF_XML		(1<<8)	/* Force XML encoding style (for XPath) */
33831337658SMarcel Moolenaar #define XFF_ATTR	(1<<9)	/* Escape value using attribute rules (XML) */
33931337658SMarcel Moolenaar #define XFF_BLANK_LINE	(1<<10)	/* Emit a blank line */
34031337658SMarcel Moolenaar #define XFF_NO_OUTPUT	(1<<11)	/* Do not make any output */
34131337658SMarcel Moolenaar 
34231337658SMarcel Moolenaar #define XFF_TRIM_WS	(1<<12)	/* Trim whitespace off encoded values */
34331337658SMarcel Moolenaar #define XFF_LEAF_LIST	(1<<13)	/* A leaf-list (list of values) */
34431337658SMarcel Moolenaar #define XFF_UNESCAPE	(1<<14)	/* Need to printf-style unescape the value */
345d1a0d267SMarcel Moolenaar #define XFF_HUMANIZE	(1<<15)	/* Humanize the value (for display styles) */
346d1a0d267SMarcel Moolenaar 
347d1a0d267SMarcel Moolenaar #define XFF_HN_SPACE	(1<<16)	/* Humanize: put space before suffix */
348d1a0d267SMarcel Moolenaar #define XFF_HN_DECIMAL	(1<<17)	/* Humanize: add one decimal place if <10 */
349d1a0d267SMarcel Moolenaar #define XFF_HN_1000	(1<<18)	/* Humanize: use 1000, not 1024 */
350d1a0d267SMarcel Moolenaar #define XFF_GT_FIELD	(1<<19) /* Call gettext() on a field */
351d1a0d267SMarcel Moolenaar 
352d1a0d267SMarcel Moolenaar #define XFF_GT_PLURAL	(1<<20)	/* Call dngettext to find plural form */
35342ff34c3SPhil Shafer #define XFF_ARGUMENT	(1<<21)	/* Content provided via argument */
354d1a0d267SMarcel Moolenaar 
355d1a0d267SMarcel Moolenaar /* Flags to turn off when we don't want i18n processing */
356d1a0d267SMarcel Moolenaar #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
35731337658SMarcel Moolenaar 
35831337658SMarcel Moolenaar /*
35931337658SMarcel Moolenaar  * Normal printf has width and precision, which for strings operate as
36031337658SMarcel Moolenaar  * min and max number of columns.  But this depends on the idea that
36131337658SMarcel Moolenaar  * one byte means one column, which UTF-8 and multi-byte characters
36231337658SMarcel Moolenaar  * pitches on its ear.  It may take 40 bytes of data to populate 14
36331337658SMarcel Moolenaar  * columns, but we can't go off looking at 40 bytes of data without the
36431337658SMarcel Moolenaar  * caller's permission for fear/knowledge that we'll generate core files.
36531337658SMarcel Moolenaar  *
36631337658SMarcel Moolenaar  * So we make three values, distinguishing between "max column" and
36731337658SMarcel Moolenaar  * "number of bytes that we will inspect inspect safely" We call the
36831337658SMarcel Moolenaar  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
36931337658SMarcel Moolenaar  *
37031337658SMarcel Moolenaar  * Under the "first do no harm" theory, we default "max" to "size".
37131337658SMarcel Moolenaar  * This is a reasonable assumption for folks that don't grok the
37231337658SMarcel Moolenaar  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
37331337658SMarcel Moolenaar  * be evil.
37431337658SMarcel Moolenaar  *
37531337658SMarcel Moolenaar  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
37631337658SMarcel Moolenaar  * columns of output, but will never look at more than 14 bytes of the
37731337658SMarcel Moolenaar  * input buffer.  This is mostly compatible with printf and caller's
37831337658SMarcel Moolenaar  * expectations.
37931337658SMarcel Moolenaar  *
38031337658SMarcel Moolenaar  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
38131337658SMarcel Moolenaar  * many bytes (or until a NUL is seen) are needed to fill 14 columns
38231337658SMarcel Moolenaar  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
38331337658SMarcel Moolenaar  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
38431337658SMarcel Moolenaar  * of output.
38531337658SMarcel Moolenaar  *
38631337658SMarcel Moolenaar  * It's fairly amazing how a good idea (handle all languages of the
38731337658SMarcel Moolenaar  * world) blows such a big hole in the bottom of the fairly weak boat
38831337658SMarcel Moolenaar  * that is C string handling.  The simplicity and completenesss are
38931337658SMarcel Moolenaar  * sunk in ways we haven't even begun to understand.
39031337658SMarcel Moolenaar  */
39131337658SMarcel Moolenaar #define XF_WIDTH_MIN	0	/* Minimal width */
39231337658SMarcel Moolenaar #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
39331337658SMarcel Moolenaar #define XF_WIDTH_MAX	2	/* Maximum width */
39431337658SMarcel Moolenaar #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
39531337658SMarcel Moolenaar 
39631337658SMarcel Moolenaar /* Input and output string encodings */
39731337658SMarcel Moolenaar #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
39831337658SMarcel Moolenaar #define XF_ENC_UTF8	2	/* UTF-8 */
39931337658SMarcel Moolenaar #define XF_ENC_LOCALE	3	/* Current locale */
40031337658SMarcel Moolenaar 
40131337658SMarcel Moolenaar /*
40231337658SMarcel Moolenaar  * A place to parse printf-style format flags for each field
40331337658SMarcel Moolenaar  */
40431337658SMarcel Moolenaar typedef struct xo_format_s {
40531337658SMarcel Moolenaar     unsigned char xf_fc;	/* Format character */
40631337658SMarcel Moolenaar     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
40731337658SMarcel Moolenaar     unsigned char xf_skip;	/* Skip this field */
40831337658SMarcel Moolenaar     unsigned char xf_lflag;	/* 'l' (long) */
40931337658SMarcel Moolenaar     unsigned char xf_hflag;;	/* 'h' (half) */
41031337658SMarcel Moolenaar     unsigned char xf_jflag;	/* 'j' (intmax_t) */
41131337658SMarcel Moolenaar     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
41231337658SMarcel Moolenaar     unsigned char xf_zflag;	/* 'z' (size_t) */
41331337658SMarcel Moolenaar     unsigned char xf_qflag;	/* 'q' (quad_t) */
41431337658SMarcel Moolenaar     unsigned char xf_seen_minus; /* Seen a minus */
41531337658SMarcel Moolenaar     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
41631337658SMarcel Moolenaar     unsigned xf_dots;		/* Seen one or more '.'s */
41731337658SMarcel Moolenaar     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
41831337658SMarcel Moolenaar     unsigned xf_stars;		/* Seen one or more '*'s */
41931337658SMarcel Moolenaar     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
42031337658SMarcel Moolenaar } xo_format_t;
42131337658SMarcel Moolenaar 
42231337658SMarcel Moolenaar /*
423d1a0d267SMarcel Moolenaar  * This structure represents the parsed field information, suitable for
424d1a0d267SMarcel Moolenaar  * processing by xo_do_emit and anything else that needs to parse fields.
425d1a0d267SMarcel Moolenaar  * Note that all pointers point to the main format string.
426d1a0d267SMarcel Moolenaar  *
427d1a0d267SMarcel Moolenaar  * XXX This is a first step toward compilable or cachable format
428d1a0d267SMarcel Moolenaar  * strings.  We can also cache the results of dgettext when no format
429d1a0d267SMarcel Moolenaar  * is used, assuming the 'p' modifier has _not_ been set.
43031337658SMarcel Moolenaar  */
431d1a0d267SMarcel Moolenaar typedef struct xo_field_info_s {
432d1a0d267SMarcel Moolenaar     xo_xff_flags_t xfi_flags;	/* Flags for this field */
433d1a0d267SMarcel Moolenaar     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
434d1a0d267SMarcel Moolenaar     const char *xfi_start;   /* Start of field in the format string */
435d1a0d267SMarcel Moolenaar     const char *xfi_content;	/* Field's content */
436d1a0d267SMarcel Moolenaar     const char *xfi_format;	/* Field's Format */
437d1a0d267SMarcel Moolenaar     const char *xfi_encoding;	/* Field's encoding format */
438d1a0d267SMarcel Moolenaar     const char *xfi_next;	/* Next character in format string */
4398a6eceffSPhil Shafer     ssize_t xfi_len;		/* Length of field */
4408a6eceffSPhil Shafer     ssize_t xfi_clen;		/* Content length */
4418a6eceffSPhil Shafer     ssize_t xfi_flen;		/* Format length */
4428a6eceffSPhil Shafer     ssize_t xfi_elen;		/* Encoding length */
443d1a0d267SMarcel Moolenaar     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
444d1a0d267SMarcel Moolenaar     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
445d1a0d267SMarcel Moolenaar } xo_field_info_t;
446d1a0d267SMarcel Moolenaar 
447d1a0d267SMarcel Moolenaar /*
448d1a0d267SMarcel Moolenaar  * We keep a 'default' handle to allow callers to avoid having to
449d1a0d267SMarcel Moolenaar  * allocate one.  Passing NULL to any of our functions will use
450d1a0d267SMarcel Moolenaar  * this default handle.  Most functions have a variant that doesn't
451d1a0d267SMarcel Moolenaar  * require a handle at all, since most output is to stdout, which
452d1a0d267SMarcel Moolenaar  * the default handle handles handily.
453d1a0d267SMarcel Moolenaar  */
454d1a0d267SMarcel Moolenaar static THREAD_LOCAL(xo_handle_t) xo_default_handle;
455d1a0d267SMarcel Moolenaar static THREAD_LOCAL(int) xo_default_inited;
45631337658SMarcel Moolenaar static int xo_locale_inited;
457545ddfbeSMarcel Moolenaar static const char *xo_program;
45831337658SMarcel Moolenaar 
45931337658SMarcel Moolenaar /*
46031337658SMarcel Moolenaar  * To allow libxo to be used in diverse environment, we allow the
46131337658SMarcel Moolenaar  * caller to give callbacks for memory allocation.
46231337658SMarcel Moolenaar  */
463d1a0d267SMarcel Moolenaar xo_realloc_func_t xo_realloc = realloc;
464d1a0d267SMarcel Moolenaar xo_free_func_t xo_free = free;
46531337658SMarcel Moolenaar 
46631337658SMarcel Moolenaar /* Forward declarations */
46731337658SMarcel Moolenaar static void
46831337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...);
46931337658SMarcel Moolenaar 
4708a6eceffSPhil Shafer static ssize_t
471545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
472545ddfbeSMarcel Moolenaar 	       xo_state_t new_state);
473545ddfbeSMarcel Moolenaar 
474f2b7bf8aSPhil Shafer static int
475f2b7bf8aSPhil Shafer xo_set_options_simple (xo_handle_t *xop, const char *input);
476f2b7bf8aSPhil Shafer 
477f2b7bf8aSPhil Shafer static int
478f2b7bf8aSPhil Shafer xo_color_find (const char *str);
479f2b7bf8aSPhil Shafer 
48031337658SMarcel Moolenaar static void
48131337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
4828a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
4838a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
484264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
4858a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen);
48631337658SMarcel Moolenaar 
48731337658SMarcel Moolenaar static void
48831337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop);
48931337658SMarcel Moolenaar 
49031337658SMarcel Moolenaar /*
491788ca347SMarcel Moolenaar  * xo_style is used to retrieve the current style.  When we're built
492788ca347SMarcel Moolenaar  * for "text only" mode, we use this function to drive the removal
493788ca347SMarcel Moolenaar  * of most of the code in libxo.  We return a constant and the compiler
494788ca347SMarcel Moolenaar  * happily removes the non-text code that is not longer executed.  This
495788ca347SMarcel Moolenaar  * trims our code nicely without needing to trampel perfectly readable
496788ca347SMarcel Moolenaar  * code with ifdefs.
497788ca347SMarcel Moolenaar  */
498d1a0d267SMarcel Moolenaar static inline xo_style_t
499788ca347SMarcel Moolenaar xo_style (xo_handle_t *xop UNUSED)
500788ca347SMarcel Moolenaar {
501788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
502788ca347SMarcel Moolenaar     return XO_STYLE_TEXT;
503788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
504788ca347SMarcel Moolenaar     return xop->xo_style;
505788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
506788ca347SMarcel Moolenaar }
507788ca347SMarcel Moolenaar 
508788ca347SMarcel Moolenaar /*
50931337658SMarcel Moolenaar  * Callback to write data to a FILE pointer
51031337658SMarcel Moolenaar  */
5118a6eceffSPhil Shafer static xo_ssize_t
51231337658SMarcel Moolenaar xo_write_to_file (void *opaque, const char *data)
51331337658SMarcel Moolenaar {
51431337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
515545ddfbeSMarcel Moolenaar 
51631337658SMarcel Moolenaar     return fprintf(fp, "%s", data);
51731337658SMarcel Moolenaar }
51831337658SMarcel Moolenaar 
51931337658SMarcel Moolenaar /*
52031337658SMarcel Moolenaar  * Callback to close a file
52131337658SMarcel Moolenaar  */
52231337658SMarcel Moolenaar static void
52331337658SMarcel Moolenaar xo_close_file (void *opaque)
52431337658SMarcel Moolenaar {
52531337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
526545ddfbeSMarcel Moolenaar 
52731337658SMarcel Moolenaar     fclose(fp);
52831337658SMarcel Moolenaar }
52931337658SMarcel Moolenaar 
53031337658SMarcel Moolenaar /*
531545ddfbeSMarcel Moolenaar  * Callback to flush a FILE pointer
532545ddfbeSMarcel Moolenaar  */
533545ddfbeSMarcel Moolenaar static int
534545ddfbeSMarcel Moolenaar xo_flush_file (void *opaque)
535545ddfbeSMarcel Moolenaar {
536545ddfbeSMarcel Moolenaar     FILE *fp = (FILE *) opaque;
537545ddfbeSMarcel Moolenaar 
538545ddfbeSMarcel Moolenaar     return fflush(fp);
539545ddfbeSMarcel Moolenaar }
540545ddfbeSMarcel Moolenaar 
541545ddfbeSMarcel Moolenaar /*
542d1a0d267SMarcel Moolenaar  * Use a rotating stock of buffers to make a printable string
54331337658SMarcel Moolenaar  */
544d1a0d267SMarcel Moolenaar #define XO_NUMBUFS 8
545d1a0d267SMarcel Moolenaar #define XO_SMBUFSZ 128
546d1a0d267SMarcel Moolenaar 
547d1a0d267SMarcel Moolenaar static const char *
548d1a0d267SMarcel Moolenaar xo_printable (const char *str)
54931337658SMarcel Moolenaar {
550d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
551d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(int) bufnum = 0;
552d1a0d267SMarcel Moolenaar 
553d1a0d267SMarcel Moolenaar     if (str == NULL)
554d1a0d267SMarcel Moolenaar 	return "";
555d1a0d267SMarcel Moolenaar 
556d1a0d267SMarcel Moolenaar     if (++bufnum == XO_NUMBUFS)
557d1a0d267SMarcel Moolenaar 	bufnum = 0;
558d1a0d267SMarcel Moolenaar 
559d1a0d267SMarcel Moolenaar     char *res = bufset[bufnum], *cp, *ep;
560d1a0d267SMarcel Moolenaar 
561d1a0d267SMarcel Moolenaar     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
562d1a0d267SMarcel Moolenaar 	if (*str == '\n') {
563d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
564d1a0d267SMarcel Moolenaar 	    *cp = 'n';
565d1a0d267SMarcel Moolenaar 	} else if (*str == '\r') {
566d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
567d1a0d267SMarcel Moolenaar 	    *cp = 'r';
568d1a0d267SMarcel Moolenaar 	} else if (*str == '\"') {
569d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
570d1a0d267SMarcel Moolenaar 	    *cp = '"';
571d1a0d267SMarcel Moolenaar 	} else
572d1a0d267SMarcel Moolenaar 	    *cp = *str;
57331337658SMarcel Moolenaar     }
57431337658SMarcel Moolenaar 
575d1a0d267SMarcel Moolenaar     *cp = '\0';
576d1a0d267SMarcel Moolenaar     return res;
57731337658SMarcel Moolenaar }
57831337658SMarcel Moolenaar 
57931337658SMarcel Moolenaar static int
58031337658SMarcel Moolenaar xo_depth_check (xo_handle_t *xop, int depth)
58131337658SMarcel Moolenaar {
58231337658SMarcel Moolenaar     xo_stack_t *xsp;
58331337658SMarcel Moolenaar 
58431337658SMarcel Moolenaar     if (depth >= xop->xo_stack_size) {
585d1a0d267SMarcel Moolenaar 	depth += XO_DEPTH;	/* Extra room */
586d1a0d267SMarcel Moolenaar 
58731337658SMarcel Moolenaar 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
58831337658SMarcel Moolenaar 	if (xsp == NULL) {
58931337658SMarcel Moolenaar 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
590d1a0d267SMarcel Moolenaar 	    return -1;
59131337658SMarcel Moolenaar 	}
59231337658SMarcel Moolenaar 
59331337658SMarcel Moolenaar 	int count = depth - xop->xo_stack_size;
59431337658SMarcel Moolenaar 
59531337658SMarcel Moolenaar 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
59631337658SMarcel Moolenaar 	xop->xo_stack_size = depth;
59731337658SMarcel Moolenaar 	xop->xo_stack = xsp;
59831337658SMarcel Moolenaar     }
59931337658SMarcel Moolenaar 
60031337658SMarcel Moolenaar     return 0;
60131337658SMarcel Moolenaar }
60231337658SMarcel Moolenaar 
60331337658SMarcel Moolenaar void
60431337658SMarcel Moolenaar xo_no_setlocale (void)
60531337658SMarcel Moolenaar {
60631337658SMarcel Moolenaar     xo_locale_inited = 1;	/* Skip initialization */
60731337658SMarcel Moolenaar }
60831337658SMarcel Moolenaar 
60931337658SMarcel Moolenaar /*
610545ddfbeSMarcel Moolenaar  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
611545ddfbeSMarcel Moolenaar  * standard way to decide this (e.g. getlinebuf()), we have configure
612788ca347SMarcel Moolenaar  * look to find __flbf, which glibc supported.  If not, we'll rely on
613788ca347SMarcel Moolenaar  * isatty, with the assumption that terminals are the only thing
614545ddfbeSMarcel Moolenaar  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
615545ddfbeSMarcel Moolenaar  * which is all __flbf does, but that's even tackier.  Like a
616545ddfbeSMarcel Moolenaar  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
617545ddfbeSMarcel Moolenaar  * something we're willing to do.
618545ddfbeSMarcel Moolenaar  */
619545ddfbeSMarcel Moolenaar static int
620545ddfbeSMarcel Moolenaar xo_is_line_buffered (FILE *stream)
621545ddfbeSMarcel Moolenaar {
622545ddfbeSMarcel Moolenaar #if HAVE___FLBF
623545ddfbeSMarcel Moolenaar     if (__flbf(stream))
624545ddfbeSMarcel Moolenaar 	return 1;
625545ddfbeSMarcel Moolenaar #else /* HAVE___FLBF */
626545ddfbeSMarcel Moolenaar     if (isatty(fileno(stream)))
627545ddfbeSMarcel Moolenaar 	return 1;
628545ddfbeSMarcel Moolenaar #endif /* HAVE___FLBF */
629545ddfbeSMarcel Moolenaar     return 0;
630545ddfbeSMarcel Moolenaar }
631545ddfbeSMarcel Moolenaar 
632545ddfbeSMarcel Moolenaar /*
63331337658SMarcel Moolenaar  * Initialize an xo_handle_t, using both static defaults and
63431337658SMarcel Moolenaar  * the global settings from the LIBXO_OPTIONS environment
63531337658SMarcel Moolenaar  * variable.
63631337658SMarcel Moolenaar  */
63731337658SMarcel Moolenaar static void
63831337658SMarcel Moolenaar xo_init_handle (xo_handle_t *xop)
63931337658SMarcel Moolenaar {
64031337658SMarcel Moolenaar     xop->xo_opaque = stdout;
64131337658SMarcel Moolenaar     xop->xo_write = xo_write_to_file;
642545ddfbeSMarcel Moolenaar     xop->xo_flush = xo_flush_file;
643545ddfbeSMarcel Moolenaar 
644545ddfbeSMarcel Moolenaar     if (xo_is_line_buffered(stdout))
645d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_FLUSH_LINE);
64631337658SMarcel Moolenaar 
64731337658SMarcel Moolenaar     /*
64831337658SMarcel Moolenaar      * We need to initialize the locale, which isn't really pretty.
64931337658SMarcel Moolenaar      * Libraries should depend on their caller to set up the
65031337658SMarcel Moolenaar      * environment.  But we really can't count on the caller to do
65131337658SMarcel Moolenaar      * this, because well, they won't.  Trust me.
65231337658SMarcel Moolenaar      */
65331337658SMarcel Moolenaar     if (!xo_locale_inited) {
65431337658SMarcel Moolenaar 	xo_locale_inited = 1;	/* Only do this once */
65531337658SMarcel Moolenaar 
65631337658SMarcel Moolenaar 	const char *cp = getenv("LC_CTYPE");
65731337658SMarcel Moolenaar 	if (cp == NULL)
65831337658SMarcel Moolenaar 	    cp = getenv("LANG");
65931337658SMarcel Moolenaar 	if (cp == NULL)
66031337658SMarcel Moolenaar 	    cp = getenv("LC_ALL");
66131337658SMarcel Moolenaar 	if (cp == NULL)
662d1a0d267SMarcel Moolenaar 	    cp = "C";		/* Default for C programs */
663c600d307SMarcel Moolenaar 	(void) setlocale(LC_CTYPE, cp);
66431337658SMarcel Moolenaar     }
66531337658SMarcel Moolenaar 
66631337658SMarcel Moolenaar     /*
66731337658SMarcel Moolenaar      * Initialize only the xo_buffers we know we'll need; the others
66831337658SMarcel Moolenaar      * can be allocated as needed.
66931337658SMarcel Moolenaar      */
67031337658SMarcel Moolenaar     xo_buf_init(&xop->xo_data);
67131337658SMarcel Moolenaar     xo_buf_init(&xop->xo_fmt);
67231337658SMarcel Moolenaar 
673d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
674d1a0d267SMarcel Moolenaar 	return;
675d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
676d1a0d267SMarcel Moolenaar 
67731337658SMarcel Moolenaar     xop->xo_indent_by = XO_INDENT_BY;
67831337658SMarcel Moolenaar     xo_depth_check(xop, XO_DEPTH);
67931337658SMarcel Moolenaar 
680d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
68131337658SMarcel Moolenaar }
68231337658SMarcel Moolenaar 
68331337658SMarcel Moolenaar /*
68431337658SMarcel Moolenaar  * Initialize the default handle.
68531337658SMarcel Moolenaar  */
68631337658SMarcel Moolenaar static void
68731337658SMarcel Moolenaar xo_default_init (void)
68831337658SMarcel Moolenaar {
68931337658SMarcel Moolenaar     xo_handle_t *xop = &xo_default_handle;
69031337658SMarcel Moolenaar 
69131337658SMarcel Moolenaar     xo_init_handle(xop);
69231337658SMarcel Moolenaar 
693f2b7bf8aSPhil Shafer #if !defined(NO_LIBXO_OPTIONS)
694f2b7bf8aSPhil Shafer     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
695f2b7bf8aSPhil Shafer        char *env = getenv("LIBXO_OPTIONS");
6962f784130SPhil Shafer 
697f2b7bf8aSPhil Shafer        if (env)
698f2b7bf8aSPhil Shafer            xo_set_options_simple(xop, env);
699f2b7bf8aSPhil Shafer 
700f2b7bf8aSPhil Shafer     }
701f2b7bf8aSPhil Shafer #endif /* NO_LIBXO_OPTIONS */
702f2b7bf8aSPhil Shafer 
70331337658SMarcel Moolenaar     xo_default_inited = 1;
70431337658SMarcel Moolenaar }
70531337658SMarcel Moolenaar 
70631337658SMarcel Moolenaar /*
70731337658SMarcel Moolenaar  * Cheap convenience function to return either the argument, or
70831337658SMarcel Moolenaar  * the internal handle, after it has been initialized.  The usage
70931337658SMarcel Moolenaar  * is:
71031337658SMarcel Moolenaar  *    xop = xo_default(xop);
71131337658SMarcel Moolenaar  */
71231337658SMarcel Moolenaar static xo_handle_t *
71331337658SMarcel Moolenaar xo_default (xo_handle_t *xop)
71431337658SMarcel Moolenaar {
71531337658SMarcel Moolenaar     if (xop == NULL) {
71631337658SMarcel Moolenaar 	if (xo_default_inited == 0)
71731337658SMarcel Moolenaar 	    xo_default_init();
71831337658SMarcel Moolenaar 	xop = &xo_default_handle;
71931337658SMarcel Moolenaar     }
72031337658SMarcel Moolenaar 
72131337658SMarcel Moolenaar     return xop;
72231337658SMarcel Moolenaar }
72331337658SMarcel Moolenaar 
72431337658SMarcel Moolenaar /*
72531337658SMarcel Moolenaar  * Return the number of spaces we should be indenting.  If
726788ca347SMarcel Moolenaar  * we are pretty-printing, this is indent * indent_by.
72731337658SMarcel Moolenaar  */
72831337658SMarcel Moolenaar static int
72931337658SMarcel Moolenaar xo_indent (xo_handle_t *xop)
73031337658SMarcel Moolenaar {
73131337658SMarcel Moolenaar     int rc = 0;
73231337658SMarcel Moolenaar 
73331337658SMarcel Moolenaar     xop = xo_default(xop);
73431337658SMarcel Moolenaar 
735d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY)) {
73631337658SMarcel Moolenaar 	rc = xop->xo_indent * xop->xo_indent_by;
737d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
73831337658SMarcel Moolenaar 	    rc += xop->xo_indent_by;
73931337658SMarcel Moolenaar     }
74031337658SMarcel Moolenaar 
741545ddfbeSMarcel Moolenaar     return (rc > 0) ? rc : 0;
74231337658SMarcel Moolenaar }
74331337658SMarcel Moolenaar 
74431337658SMarcel Moolenaar static void
74531337658SMarcel Moolenaar xo_buf_indent (xo_handle_t *xop, int indent)
74631337658SMarcel Moolenaar {
74731337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
74831337658SMarcel Moolenaar 
74931337658SMarcel Moolenaar     if (indent <= 0)
75031337658SMarcel Moolenaar 	indent = xo_indent(xop);
75131337658SMarcel Moolenaar 
75231337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, indent))
75331337658SMarcel Moolenaar 	return;
75431337658SMarcel Moolenaar 
75531337658SMarcel Moolenaar     memset(xbp->xb_curp, ' ', indent);
75631337658SMarcel Moolenaar     xbp->xb_curp += indent;
75731337658SMarcel Moolenaar }
75831337658SMarcel Moolenaar 
75931337658SMarcel Moolenaar static char xo_xml_amp[] = "&amp;";
76031337658SMarcel Moolenaar static char xo_xml_lt[] = "&lt;";
76131337658SMarcel Moolenaar static char xo_xml_gt[] = "&gt;";
76231337658SMarcel Moolenaar static char xo_xml_quot[] = "&quot;";
76331337658SMarcel Moolenaar 
7648a6eceffSPhil Shafer static ssize_t
7658a6eceffSPhil Shafer xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags)
76631337658SMarcel Moolenaar {
7678a6eceffSPhil Shafer     ssize_t slen;
7688a6eceffSPhil Shafer     ssize_t delta = 0;
76931337658SMarcel Moolenaar     char *cp, *ep, *ip;
77031337658SMarcel Moolenaar     const char *sp;
7718a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
77231337658SMarcel Moolenaar 
77331337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
77431337658SMarcel Moolenaar 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
77531337658SMarcel Moolenaar 	if (*cp == '<')
77631337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_lt) - 2;
77731337658SMarcel Moolenaar 	else if (*cp == '>')
77831337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_gt) - 2;
77931337658SMarcel Moolenaar 	else if (*cp == '&')
78031337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_amp) - 2;
78131337658SMarcel Moolenaar 	else if (attr && *cp == '"')
78231337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_quot) - 2;
78331337658SMarcel Moolenaar     }
78431337658SMarcel Moolenaar 
78531337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
78631337658SMarcel Moolenaar 	return len;
78731337658SMarcel Moolenaar 
78831337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
78931337658SMarcel Moolenaar 	return 0;
79031337658SMarcel Moolenaar 
79131337658SMarcel Moolenaar     ep = xbp->xb_curp;
79231337658SMarcel Moolenaar     cp = ep + len;
79331337658SMarcel Moolenaar     ip = cp + delta;
79431337658SMarcel Moolenaar     do {
79531337658SMarcel Moolenaar 	cp -= 1;
79631337658SMarcel Moolenaar 	ip -= 1;
79731337658SMarcel Moolenaar 
79831337658SMarcel Moolenaar 	if (*cp == '<')
79931337658SMarcel Moolenaar 	    sp = xo_xml_lt;
80031337658SMarcel Moolenaar 	else if (*cp == '>')
80131337658SMarcel Moolenaar 	    sp = xo_xml_gt;
80231337658SMarcel Moolenaar 	else if (*cp == '&')
80331337658SMarcel Moolenaar 	    sp = xo_xml_amp;
80431337658SMarcel Moolenaar 	else if (attr && *cp == '"')
80531337658SMarcel Moolenaar 	    sp = xo_xml_quot;
80631337658SMarcel Moolenaar 	else {
80731337658SMarcel Moolenaar 	    *ip = *cp;
80831337658SMarcel Moolenaar 	    continue;
80931337658SMarcel Moolenaar 	}
81031337658SMarcel Moolenaar 
81131337658SMarcel Moolenaar 	slen = strlen(sp);
81231337658SMarcel Moolenaar 	ip -= slen - 1;
81331337658SMarcel Moolenaar 	memcpy(ip, sp, slen);
81431337658SMarcel Moolenaar 
81531337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
81631337658SMarcel Moolenaar 
81731337658SMarcel Moolenaar     return len + delta;
81831337658SMarcel Moolenaar }
81931337658SMarcel Moolenaar 
8208a6eceffSPhil Shafer static ssize_t
8218a6eceffSPhil Shafer xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
82231337658SMarcel Moolenaar {
8238a6eceffSPhil Shafer     ssize_t delta = 0;
82431337658SMarcel Moolenaar     char *cp, *ep, *ip;
82531337658SMarcel Moolenaar 
82631337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
827545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"')
82831337658SMarcel Moolenaar 	    delta += 1;
829545ddfbeSMarcel Moolenaar 	else if (*cp == '\n' || *cp == '\r')
83031337658SMarcel Moolenaar 	    delta += 1;
83131337658SMarcel Moolenaar     }
83231337658SMarcel Moolenaar 
83331337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
83431337658SMarcel Moolenaar 	return len;
83531337658SMarcel Moolenaar 
83631337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
83731337658SMarcel Moolenaar 	return 0;
83831337658SMarcel Moolenaar 
83931337658SMarcel Moolenaar     ep = xbp->xb_curp;
84031337658SMarcel Moolenaar     cp = ep + len;
84131337658SMarcel Moolenaar     ip = cp + delta;
84231337658SMarcel Moolenaar     do {
84331337658SMarcel Moolenaar 	cp -= 1;
84431337658SMarcel Moolenaar 	ip -= 1;
84531337658SMarcel Moolenaar 
846545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"') {
84731337658SMarcel Moolenaar 	    *ip-- = *cp;
84831337658SMarcel Moolenaar 	    *ip = '\\';
849545ddfbeSMarcel Moolenaar 	} else if (*cp == '\n') {
850545ddfbeSMarcel Moolenaar 	    *ip-- = 'n';
851545ddfbeSMarcel Moolenaar 	    *ip = '\\';
852545ddfbeSMarcel Moolenaar 	} else if (*cp == '\r') {
853545ddfbeSMarcel Moolenaar 	    *ip-- = 'r';
854545ddfbeSMarcel Moolenaar 	    *ip = '\\';
855545ddfbeSMarcel Moolenaar 	} else {
856545ddfbeSMarcel Moolenaar 	    *ip = *cp;
857545ddfbeSMarcel Moolenaar 	}
85831337658SMarcel Moolenaar 
85931337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
86031337658SMarcel Moolenaar 
86131337658SMarcel Moolenaar     return len + delta;
86231337658SMarcel Moolenaar }
86331337658SMarcel Moolenaar 
86431337658SMarcel Moolenaar /*
865d1a0d267SMarcel Moolenaar  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
866d1a0d267SMarcel Moolenaar  *                                ; ']' MUST be escaped.
86731337658SMarcel Moolenaar  */
8688a6eceffSPhil Shafer static ssize_t
8698a6eceffSPhil Shafer xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
87031337658SMarcel Moolenaar {
8718a6eceffSPhil Shafer     ssize_t delta = 0;
872d1a0d267SMarcel Moolenaar     char *cp, *ep, *ip;
87331337658SMarcel Moolenaar 
874d1a0d267SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
875d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']')
876d1a0d267SMarcel Moolenaar 	    delta += 1;
87731337658SMarcel Moolenaar     }
87831337658SMarcel Moolenaar 
879d1a0d267SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
880d1a0d267SMarcel Moolenaar 	return len;
881788ca347SMarcel Moolenaar 
882d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
883d1a0d267SMarcel Moolenaar 	return 0;
884788ca347SMarcel Moolenaar 
885d1a0d267SMarcel Moolenaar     ep = xbp->xb_curp;
886d1a0d267SMarcel Moolenaar     cp = ep + len;
887d1a0d267SMarcel Moolenaar     ip = cp + delta;
888d1a0d267SMarcel Moolenaar     do {
889d1a0d267SMarcel Moolenaar 	cp -= 1;
890d1a0d267SMarcel Moolenaar 	ip -= 1;
891d1a0d267SMarcel Moolenaar 
892d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
893d1a0d267SMarcel Moolenaar 	    *ip-- = *cp;
894d1a0d267SMarcel Moolenaar 	    *ip = '\\';
895d1a0d267SMarcel Moolenaar 	} else {
896d1a0d267SMarcel Moolenaar 	    *ip = *cp;
897d1a0d267SMarcel Moolenaar 	}
898d1a0d267SMarcel Moolenaar 
899d1a0d267SMarcel Moolenaar     } while (cp > ep && cp != ip);
900d1a0d267SMarcel Moolenaar 
901d1a0d267SMarcel Moolenaar     return len + delta;
902788ca347SMarcel Moolenaar }
903788ca347SMarcel Moolenaar 
90431337658SMarcel Moolenaar static void
90531337658SMarcel Moolenaar xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
9068a6eceffSPhil Shafer 	       const char *str, ssize_t len, xo_xff_flags_t flags)
90731337658SMarcel Moolenaar {
90831337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, len))
90931337658SMarcel Moolenaar 	return;
91031337658SMarcel Moolenaar 
91131337658SMarcel Moolenaar     memcpy(xbp->xb_curp, str, len);
91231337658SMarcel Moolenaar 
913788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
91431337658SMarcel Moolenaar     case XO_STYLE_XML:
91531337658SMarcel Moolenaar     case XO_STYLE_HTML:
916d1a0d267SMarcel Moolenaar 	len = xo_escape_xml(xbp, len, flags);
91731337658SMarcel Moolenaar 	break;
91831337658SMarcel Moolenaar 
91931337658SMarcel Moolenaar     case XO_STYLE_JSON:
920d1a0d267SMarcel Moolenaar 	len = xo_escape_json(xbp, len, flags);
921d1a0d267SMarcel Moolenaar 	break;
922d1a0d267SMarcel Moolenaar 
923d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
924d1a0d267SMarcel Moolenaar 	len = xo_escape_sdparams(xbp, len, flags);
92531337658SMarcel Moolenaar 	break;
92631337658SMarcel Moolenaar     }
92731337658SMarcel Moolenaar 
92831337658SMarcel Moolenaar     xbp->xb_curp += len;
92931337658SMarcel Moolenaar }
93031337658SMarcel Moolenaar 
93131337658SMarcel Moolenaar /*
93231337658SMarcel Moolenaar  * Write the current contents of the data buffer using the handle's
93331337658SMarcel Moolenaar  * xo_write function.
93431337658SMarcel Moolenaar  */
9358a6eceffSPhil Shafer static ssize_t
93631337658SMarcel Moolenaar xo_write (xo_handle_t *xop)
93731337658SMarcel Moolenaar {
9388a6eceffSPhil Shafer     ssize_t rc = 0;
93931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
94031337658SMarcel Moolenaar 
94131337658SMarcel Moolenaar     if (xbp->xb_curp != xbp->xb_bufp) {
94231337658SMarcel Moolenaar 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
94331337658SMarcel Moolenaar 	xo_anchor_clear(xop);
944d1a0d267SMarcel Moolenaar 	if (xop->xo_write)
945545ddfbeSMarcel Moolenaar 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
94631337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp;
94731337658SMarcel Moolenaar     }
94831337658SMarcel Moolenaar 
94931337658SMarcel Moolenaar     /* Turn off the flags that don't survive across writes */
950d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
951545ddfbeSMarcel Moolenaar 
952545ddfbeSMarcel Moolenaar     return rc;
95331337658SMarcel Moolenaar }
95431337658SMarcel Moolenaar 
95531337658SMarcel Moolenaar /*
95631337658SMarcel Moolenaar  * Format arguments into our buffer.  If a custom formatter has been set,
95731337658SMarcel Moolenaar  * we use that to do the work; otherwise we vsnprintf().
95831337658SMarcel Moolenaar  */
9598a6eceffSPhil Shafer static ssize_t
96031337658SMarcel Moolenaar xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
96131337658SMarcel Moolenaar {
96231337658SMarcel Moolenaar     va_list va_local;
9638a6eceffSPhil Shafer     ssize_t rc;
9648a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
96531337658SMarcel Moolenaar 
96631337658SMarcel Moolenaar     va_copy(va_local, vap);
96731337658SMarcel Moolenaar 
96831337658SMarcel Moolenaar     if (xop->xo_formatter)
96931337658SMarcel Moolenaar 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
97031337658SMarcel Moolenaar     else
97131337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
97231337658SMarcel Moolenaar 
973788ca347SMarcel Moolenaar     if (rc >= left) {
974c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
975c600d307SMarcel Moolenaar 	    va_end(va_local);
97631337658SMarcel Moolenaar 	    return -1;
977c600d307SMarcel Moolenaar 	}
97831337658SMarcel Moolenaar 
97931337658SMarcel Moolenaar 	/*
98031337658SMarcel Moolenaar 	 * After we call vsnprintf(), the stage of vap is not defined.
98131337658SMarcel Moolenaar 	 * We need to copy it before we pass.  Then we have to do our
98231337658SMarcel Moolenaar 	 * own logic below to move it along.  This is because the
983788ca347SMarcel Moolenaar 	 * implementation can have va_list be a pointer (bsd) or a
98431337658SMarcel Moolenaar 	 * structure (macosx) or anything in between.
98531337658SMarcel Moolenaar 	 */
98631337658SMarcel Moolenaar 
98731337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
98831337658SMarcel Moolenaar 	va_copy(va_local, vap);
98931337658SMarcel Moolenaar 
99031337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
99131337658SMarcel Moolenaar 	if (xop->xo_formatter)
992788ca347SMarcel Moolenaar 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
99331337658SMarcel Moolenaar 	else
99431337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
99531337658SMarcel Moolenaar     }
99631337658SMarcel Moolenaar     va_end(va_local);
99731337658SMarcel Moolenaar 
99831337658SMarcel Moolenaar     return rc;
99931337658SMarcel Moolenaar }
100031337658SMarcel Moolenaar 
100131337658SMarcel Moolenaar /*
1002ee5cf116SPhil Shafer  * Print some data through the handle.
100331337658SMarcel Moolenaar  */
10048a6eceffSPhil Shafer static ssize_t
100531337658SMarcel Moolenaar xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
100631337658SMarcel Moolenaar {
100731337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
10088a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
10098a6eceffSPhil Shafer     ssize_t rc;
101031337658SMarcel Moolenaar     va_list va_local;
101131337658SMarcel Moolenaar 
101231337658SMarcel Moolenaar     va_copy(va_local, vap);
101331337658SMarcel Moolenaar 
101431337658SMarcel Moolenaar     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
101531337658SMarcel Moolenaar 
1016d1a0d267SMarcel Moolenaar     if (rc >= left) {
1017c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
1018c600d307SMarcel Moolenaar 	    va_end(va_local);
101931337658SMarcel Moolenaar 	    return -1;
1020c600d307SMarcel Moolenaar 	}
102131337658SMarcel Moolenaar 
102231337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
102331337658SMarcel Moolenaar 	va_copy(va_local, vap);
102431337658SMarcel Moolenaar 
102531337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
102631337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
102731337658SMarcel Moolenaar     }
102831337658SMarcel Moolenaar 
102931337658SMarcel Moolenaar     va_end(va_local);
103031337658SMarcel Moolenaar 
103131337658SMarcel Moolenaar     if (rc > 0)
103231337658SMarcel Moolenaar 	xbp->xb_curp += rc;
103331337658SMarcel Moolenaar 
103431337658SMarcel Moolenaar     return rc;
103531337658SMarcel Moolenaar }
103631337658SMarcel Moolenaar 
10378a6eceffSPhil Shafer static ssize_t
103831337658SMarcel Moolenaar xo_printf (xo_handle_t *xop, const char *fmt, ...)
103931337658SMarcel Moolenaar {
10408a6eceffSPhil Shafer     ssize_t rc;
104131337658SMarcel Moolenaar     va_list vap;
104231337658SMarcel Moolenaar 
104331337658SMarcel Moolenaar     va_start(vap, fmt);
104431337658SMarcel Moolenaar 
104531337658SMarcel Moolenaar     rc = xo_printf_v(xop, fmt, vap);
104631337658SMarcel Moolenaar 
104731337658SMarcel Moolenaar     va_end(vap);
104831337658SMarcel Moolenaar     return rc;
104931337658SMarcel Moolenaar }
105031337658SMarcel Moolenaar 
105131337658SMarcel Moolenaar /*
105231337658SMarcel Moolenaar  * These next few function are make The Essential UTF-8 Ginsu Knife.
105331337658SMarcel Moolenaar  * Identify an input and output character, and convert it.
105431337658SMarcel Moolenaar  */
1055f2b7bf8aSPhil Shafer static uint8_t xo_utf8_data_bits[5] = { 0, 0x7f, 0x1f, 0x0f, 0x07 };
1056f2b7bf8aSPhil Shafer static uint8_t xo_utf8_len_bits[5]  = { 0, 0x00, 0xc0, 0xe0, 0xf0 };
105731337658SMarcel Moolenaar 
1058f2b7bf8aSPhil Shafer /*
1059f2b7bf8aSPhil Shafer  * If the byte has a high-bit set, it's UTF-8, not ASCII.
1060f2b7bf8aSPhil Shafer  */
106131337658SMarcel Moolenaar static int
106231337658SMarcel Moolenaar xo_is_utf8 (char ch)
106331337658SMarcel Moolenaar {
106431337658SMarcel Moolenaar     return (ch & 0x80);
106531337658SMarcel Moolenaar }
106631337658SMarcel Moolenaar 
1067f2b7bf8aSPhil Shafer /*
1068f2b7bf8aSPhil Shafer  * Look at the high bits of the first byte to determine the length
1069f2b7bf8aSPhil Shafer  * of the UTF-8 character.
1070f2b7bf8aSPhil Shafer  */
10718a6eceffSPhil Shafer static inline ssize_t
107231337658SMarcel Moolenaar xo_utf8_to_wc_len (const char *buf)
107331337658SMarcel Moolenaar {
1074f2b7bf8aSPhil Shafer     uint8_t bval = (uint8_t) *buf;
10758a6eceffSPhil Shafer     ssize_t len;
107631337658SMarcel Moolenaar 
1077f2b7bf8aSPhil Shafer     if ((bval & 0x80) == 0x0)
107831337658SMarcel Moolenaar 	len = 1;
1079f2b7bf8aSPhil Shafer     else if ((bval & 0xe0) == 0xc0)
108031337658SMarcel Moolenaar 	len = 2;
1081f2b7bf8aSPhil Shafer     else if ((bval & 0xf0) == 0xe0)
108231337658SMarcel Moolenaar 	len = 3;
1083f2b7bf8aSPhil Shafer     else if ((bval & 0xf8) == 0xf0)
108431337658SMarcel Moolenaar 	len = 4;
108531337658SMarcel Moolenaar     else
108631337658SMarcel Moolenaar 	len = -1;
108731337658SMarcel Moolenaar 
108831337658SMarcel Moolenaar     return len;
108931337658SMarcel Moolenaar }
109031337658SMarcel Moolenaar 
10918a6eceffSPhil Shafer static ssize_t
10928a6eceffSPhil Shafer xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz)
109331337658SMarcel Moolenaar {
109431337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
10958a6eceffSPhil Shafer     ssize_t len, i;
109631337658SMarcel Moolenaar 
109731337658SMarcel Moolenaar     len = xo_utf8_to_wc_len(buf);
1098f2b7bf8aSPhil Shafer     if (len < 0) {
109931337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
110031337658SMarcel Moolenaar 	return -1;
110131337658SMarcel Moolenaar     }
110231337658SMarcel Moolenaar 
110331337658SMarcel Moolenaar     if (len > bufsiz) {
110431337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
110531337658SMarcel Moolenaar 		   b, len, bufsiz);
110631337658SMarcel Moolenaar 	return -1;
110731337658SMarcel Moolenaar     }
110831337658SMarcel Moolenaar 
110931337658SMarcel Moolenaar     for (i = 2; i < len; i++) {
111031337658SMarcel Moolenaar 	b = (unsigned char ) buf[i];
111131337658SMarcel Moolenaar 	if ((b & 0xc0) != 0x80) {
111231337658SMarcel Moolenaar 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
111331337658SMarcel Moolenaar 	    return -1;
111431337658SMarcel Moolenaar 	}
111531337658SMarcel Moolenaar     }
111631337658SMarcel Moolenaar 
111731337658SMarcel Moolenaar     return len;
111831337658SMarcel Moolenaar }
111931337658SMarcel Moolenaar 
112031337658SMarcel Moolenaar /*
112131337658SMarcel Moolenaar  * Build a wide character from the input buffer; the number of
112231337658SMarcel Moolenaar  * bits we pull off the first character is dependent on the length,
112331337658SMarcel Moolenaar  * but we put 6 bits off all other bytes.
112431337658SMarcel Moolenaar  */
112542ff34c3SPhil Shafer static inline wchar_t
11268a6eceffSPhil Shafer xo_utf8_char (const char *buf, ssize_t len)
112731337658SMarcel Moolenaar {
112842ff34c3SPhil Shafer     /* Most common case: singleton byte */
112942ff34c3SPhil Shafer     if (len == 1)
113042ff34c3SPhil Shafer 	return (unsigned char) buf[0];
113142ff34c3SPhil Shafer 
11328a6eceffSPhil Shafer     ssize_t i;
113331337658SMarcel Moolenaar     wchar_t wc;
113431337658SMarcel Moolenaar     const unsigned char *cp = (const unsigned char *) buf;
113531337658SMarcel Moolenaar 
1136f2b7bf8aSPhil Shafer     wc = *cp & xo_utf8_data_bits[len];
113731337658SMarcel Moolenaar     for (i = 1; i < len; i++) {
1138f2b7bf8aSPhil Shafer 	wc <<= 6;		/* Low six bits have data */
113931337658SMarcel Moolenaar 	wc |= cp[i] & 0x3f;
114031337658SMarcel Moolenaar 	if ((cp[i] & 0xc0) != 0x80)
114131337658SMarcel Moolenaar 	    return (wchar_t) -1;
114231337658SMarcel Moolenaar     }
114331337658SMarcel Moolenaar 
114431337658SMarcel Moolenaar     return wc;
114531337658SMarcel Moolenaar }
114631337658SMarcel Moolenaar 
114731337658SMarcel Moolenaar /*
114831337658SMarcel Moolenaar  * Determine the number of bytes needed to encode a wide character.
114931337658SMarcel Moolenaar  */
11508a6eceffSPhil Shafer static ssize_t
115131337658SMarcel Moolenaar xo_utf8_emit_len (wchar_t wc)
115231337658SMarcel Moolenaar {
11538a6eceffSPhil Shafer     ssize_t len;
115431337658SMarcel Moolenaar 
115531337658SMarcel Moolenaar     if ((wc & ((1 << 7) - 1)) == wc) /* Simple case */
115631337658SMarcel Moolenaar 	len = 1;
115731337658SMarcel Moolenaar     else if ((wc & ((1 << 11) - 1)) == wc)
115831337658SMarcel Moolenaar 	len = 2;
115931337658SMarcel Moolenaar     else if ((wc & ((1 << 16) - 1)) == wc)
116031337658SMarcel Moolenaar 	len = 3;
116131337658SMarcel Moolenaar     else if ((wc & ((1 << 21) - 1)) == wc)
116231337658SMarcel Moolenaar 	len = 4;
116331337658SMarcel Moolenaar     else
1164f2b7bf8aSPhil Shafer 	len = -1;		/* Invalid */
116531337658SMarcel Moolenaar 
116631337658SMarcel Moolenaar     return len;
116731337658SMarcel Moolenaar }
116831337658SMarcel Moolenaar 
1169f2b7bf8aSPhil Shafer /*
11702f784130SPhil Shafer  * Emit one wide character into the given buffer
1171f2b7bf8aSPhil Shafer  */
117231337658SMarcel Moolenaar static void
11738a6eceffSPhil Shafer xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc)
117431337658SMarcel Moolenaar {
11758a6eceffSPhil Shafer     ssize_t i;
117631337658SMarcel Moolenaar 
117731337658SMarcel Moolenaar     if (len == 1) { /* Simple case */
117831337658SMarcel Moolenaar 	buf[0] = wc & 0x7f;
117931337658SMarcel Moolenaar 	return;
118031337658SMarcel Moolenaar     }
118131337658SMarcel Moolenaar 
11822f784130SPhil Shafer     /* Start with the low bits and insert them, six bits at a time */
118331337658SMarcel Moolenaar     for (i = len - 1; i >= 0; i--) {
118431337658SMarcel Moolenaar 	buf[i] = 0x80 | (wc & 0x3f);
1185f2b7bf8aSPhil Shafer 	wc >>= 6;		/* Drop the low six bits */
118631337658SMarcel Moolenaar     }
118731337658SMarcel Moolenaar 
1188f2b7bf8aSPhil Shafer     /* Finish off the first byte with the length bits */
1189f2b7bf8aSPhil Shafer     buf[0] &= xo_utf8_data_bits[len]; /* Clear out the length bits */
1190f2b7bf8aSPhil Shafer     buf[0] |= xo_utf8_len_bits[len]; /* Drop in new length bits */
119131337658SMarcel Moolenaar }
119231337658SMarcel Moolenaar 
1193f2b7bf8aSPhil Shafer /*
1194f2b7bf8aSPhil Shafer  * Append a single UTF-8 character to a buffer, converting it to locale
1195f2b7bf8aSPhil Shafer  * encoding.  Returns the number of columns consumed by that character,
1196f2b7bf8aSPhil Shafer  * as best we can determine it.
1197f2b7bf8aSPhil Shafer  */
11988a6eceffSPhil Shafer static ssize_t
119931337658SMarcel Moolenaar xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
12008a6eceffSPhil Shafer 				const char *ibuf, ssize_t ilen)
120131337658SMarcel Moolenaar {
120231337658SMarcel Moolenaar     wchar_t wc;
12038a6eceffSPhil Shafer     ssize_t len;
120431337658SMarcel Moolenaar 
120531337658SMarcel Moolenaar     /*
120631337658SMarcel Moolenaar      * Build our wide character from the input buffer; the number of
120731337658SMarcel Moolenaar      * bits we pull off the first character is dependent on the length,
120831337658SMarcel Moolenaar      * but we put 6 bits off all other bytes.
120931337658SMarcel Moolenaar      */
121031337658SMarcel Moolenaar     wc = xo_utf8_char(ibuf, ilen);
121131337658SMarcel Moolenaar     if (wc == (wchar_t) -1) {
1212f2b7bf8aSPhil Shafer 	xo_failure(xop, "invalid UTF-8 byte sequence");
121331337658SMarcel Moolenaar 	return 0;
121431337658SMarcel Moolenaar     }
121531337658SMarcel Moolenaar 
1216d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
121731337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, ilen))
121831337658SMarcel Moolenaar 	    return 0;
121931337658SMarcel Moolenaar 
122031337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, ibuf, ilen);
122131337658SMarcel Moolenaar 	xbp->xb_curp += ilen;
122231337658SMarcel Moolenaar 
122331337658SMarcel Moolenaar     } else {
122431337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
122531337658SMarcel Moolenaar 	    return 0;
122631337658SMarcel Moolenaar 
122731337658SMarcel Moolenaar 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
122831337658SMarcel Moolenaar 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
122931337658SMarcel Moolenaar 
123031337658SMarcel Moolenaar 	if (len <= 0) {
123131337658SMarcel Moolenaar 	    xo_failure(xop, "could not convert wide char: %lx",
123231337658SMarcel Moolenaar 		       (unsigned long) wc);
123331337658SMarcel Moolenaar 	    return 0;
123431337658SMarcel Moolenaar 	}
123531337658SMarcel Moolenaar 	xbp->xb_curp += len;
123631337658SMarcel Moolenaar     }
123731337658SMarcel Moolenaar 
1238d1a0d267SMarcel Moolenaar     return xo_wcwidth(wc);
123931337658SMarcel Moolenaar }
124031337658SMarcel Moolenaar 
1241f2b7bf8aSPhil Shafer /*
1242f2b7bf8aSPhil Shafer  * Append a UTF-8 string to a buffer, converting it into locale encoding
1243f2b7bf8aSPhil Shafer  */
124431337658SMarcel Moolenaar static void
124531337658SMarcel Moolenaar xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
12468a6eceffSPhil Shafer 		      const char *cp, ssize_t len)
124731337658SMarcel Moolenaar {
124831337658SMarcel Moolenaar     const char *sp = cp, *ep = cp + len;
12498a6eceffSPhil Shafer     ssize_t save_off = xbp->xb_bufp - xbp->xb_curp;
12508a6eceffSPhil Shafer     ssize_t slen;
125131337658SMarcel Moolenaar     int cols = 0;
125231337658SMarcel Moolenaar 
125331337658SMarcel Moolenaar     for ( ; cp < ep; cp++) {
125431337658SMarcel Moolenaar 	if (!xo_is_utf8(*cp)) {
125531337658SMarcel Moolenaar 	    cols += 1;
125631337658SMarcel Moolenaar 	    continue;
125731337658SMarcel Moolenaar 	}
125831337658SMarcel Moolenaar 
125931337658SMarcel Moolenaar 	/*
126031337658SMarcel Moolenaar 	 * We're looking at a non-ascii UTF-8 character.
126131337658SMarcel Moolenaar 	 * First we copy the previous data.
126231337658SMarcel Moolenaar 	 * Then we need find the length and validate it.
126331337658SMarcel Moolenaar 	 * Then we turn it into a wide string.
126431337658SMarcel Moolenaar 	 * Then we turn it into a localized string.
126531337658SMarcel Moolenaar 	 * Then we repeat.  Isn't i18n fun?
126631337658SMarcel Moolenaar 	 */
126731337658SMarcel Moolenaar 	if (sp != cp)
126831337658SMarcel Moolenaar 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
126931337658SMarcel Moolenaar 
127031337658SMarcel Moolenaar 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
127131337658SMarcel Moolenaar 	if (slen <= 0) {
127231337658SMarcel Moolenaar 	    /* Bad data; back it all out */
127331337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + save_off;
127431337658SMarcel Moolenaar 	    return;
127531337658SMarcel Moolenaar 	}
127631337658SMarcel Moolenaar 
127731337658SMarcel Moolenaar 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
127831337658SMarcel Moolenaar 
1279ee5cf116SPhil Shafer 	/* Next time through, we'll start at the next character */
128031337658SMarcel Moolenaar 	cp += slen - 1;
128131337658SMarcel Moolenaar 	sp = cp + 1;
128231337658SMarcel Moolenaar     }
128331337658SMarcel Moolenaar 
128431337658SMarcel Moolenaar     /* Update column values */
1285d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
128631337658SMarcel Moolenaar 	xop->xo_columns += cols;
1287d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
128831337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
128931337658SMarcel Moolenaar 
129031337658SMarcel Moolenaar     /* Before we fall into the basic logic below, we need reset len */
129131337658SMarcel Moolenaar     len = ep - sp;
129231337658SMarcel Moolenaar     if (len != 0) /* Append trailing data */
129331337658SMarcel Moolenaar 	xo_buf_append(xbp, sp, len);
129431337658SMarcel Moolenaar }
129531337658SMarcel Moolenaar 
129631337658SMarcel Moolenaar /*
1297d1a0d267SMarcel Moolenaar  * Append the given string to the given buffer, without escaping or
1298d1a0d267SMarcel Moolenaar  * character set conversion.  This is the straight copy to the data
1299d1a0d267SMarcel Moolenaar  * buffer with no fanciness.
130031337658SMarcel Moolenaar  */
130131337658SMarcel Moolenaar static void
13028a6eceffSPhil Shafer xo_data_append (xo_handle_t *xop, const char *str, ssize_t len)
130331337658SMarcel Moolenaar {
130431337658SMarcel Moolenaar     xo_buf_append(&xop->xo_data, str, len);
130531337658SMarcel Moolenaar }
130631337658SMarcel Moolenaar 
130731337658SMarcel Moolenaar /*
130831337658SMarcel Moolenaar  * Append the given string to the given buffer
130931337658SMarcel Moolenaar  */
131031337658SMarcel Moolenaar static void
13118a6eceffSPhil Shafer xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len)
131231337658SMarcel Moolenaar {
131331337658SMarcel Moolenaar     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
131431337658SMarcel Moolenaar }
131531337658SMarcel Moolenaar 
131642ff34c3SPhil Shafer #ifdef LIBXO_NO_RETAIN
131742ff34c3SPhil Shafer /*
131842ff34c3SPhil Shafer  * Empty implementations of the retain logic
131942ff34c3SPhil Shafer  */
132042ff34c3SPhil Shafer 
132142ff34c3SPhil Shafer void
132242ff34c3SPhil Shafer xo_retain_clear_all (void)
132342ff34c3SPhil Shafer {
132442ff34c3SPhil Shafer     return;
132542ff34c3SPhil Shafer }
132642ff34c3SPhil Shafer 
132742ff34c3SPhil Shafer void
132842ff34c3SPhil Shafer xo_retain_clear (const char *fmt UNUSED)
132942ff34c3SPhil Shafer {
133042ff34c3SPhil Shafer     return;
133142ff34c3SPhil Shafer }
133242ff34c3SPhil Shafer static void
133342ff34c3SPhil Shafer xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
133442ff34c3SPhil Shafer 		unsigned num_fields UNUSED)
133542ff34c3SPhil Shafer {
133642ff34c3SPhil Shafer     return;
133742ff34c3SPhil Shafer }
133842ff34c3SPhil Shafer 
133942ff34c3SPhil Shafer static int
134042ff34c3SPhil Shafer xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
134142ff34c3SPhil Shafer 		 unsigned *nump UNUSED)
134242ff34c3SPhil Shafer {
134342ff34c3SPhil Shafer     return -1;
134442ff34c3SPhil Shafer }
134542ff34c3SPhil Shafer 
134642ff34c3SPhil Shafer #else /* !LIBXO_NO_RETAIN */
134742ff34c3SPhil Shafer /*
134842ff34c3SPhil Shafer  * Retain: We retain parsed field definitions to enhance performance,
134942ff34c3SPhil Shafer  * especially inside loops.  We depend on the caller treating the format
135042ff34c3SPhil Shafer  * strings as immutable, so that we can retain pointers into them.  We
135142ff34c3SPhil Shafer  * hold the pointers in a hash table, so allow quick access.  Retained
135242ff34c3SPhil Shafer  * information is retained until xo_retain_clear is called.
135342ff34c3SPhil Shafer  */
135442ff34c3SPhil Shafer 
135542ff34c3SPhil Shafer /*
135642ff34c3SPhil Shafer  * xo_retain_entry_t holds information about one retained set of
135742ff34c3SPhil Shafer  * parsed fields.
135842ff34c3SPhil Shafer  */
135942ff34c3SPhil Shafer typedef struct xo_retain_entry_s {
136042ff34c3SPhil Shafer     struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
136142ff34c3SPhil Shafer     unsigned long xre_hits;		 /* Number of times we've hit */
136242ff34c3SPhil Shafer     const char *xre_format;		 /* Pointer to format string */
136342ff34c3SPhil Shafer     unsigned xre_num_fields;		 /* Number of fields saved */
136442ff34c3SPhil Shafer     xo_field_info_t *xre_fields;	 /* Pointer to fields */
136542ff34c3SPhil Shafer } xo_retain_entry_t;
136642ff34c3SPhil Shafer 
136742ff34c3SPhil Shafer /*
136842ff34c3SPhil Shafer  * xo_retain_t holds a complete set of parsed fields as a hash table.
136942ff34c3SPhil Shafer  */
137042ff34c3SPhil Shafer #ifndef XO_RETAIN_SIZE
137142ff34c3SPhil Shafer #define XO_RETAIN_SIZE 6
137242ff34c3SPhil Shafer #endif /* XO_RETAIN_SIZE */
137342ff34c3SPhil Shafer #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
137442ff34c3SPhil Shafer 
137542ff34c3SPhil Shafer typedef struct xo_retain_s {
137642ff34c3SPhil Shafer     xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
137742ff34c3SPhil Shafer } xo_retain_t;
137842ff34c3SPhil Shafer 
137942ff34c3SPhil Shafer static THREAD_LOCAL(xo_retain_t) xo_retain;
138042ff34c3SPhil Shafer static THREAD_LOCAL(unsigned) xo_retain_count;
138142ff34c3SPhil Shafer 
138242ff34c3SPhil Shafer /*
138342ff34c3SPhil Shafer  * Simple hash function based on Thomas Wang's paper.  The original is
138442ff34c3SPhil Shafer  * gone, but an archive is available on the Way Back Machine:
138542ff34c3SPhil Shafer  *
138642ff34c3SPhil Shafer  * http://web.archive.org/web/20071223173210/\
138742ff34c3SPhil Shafer  *     http://www.concentric.net/~Ttwang/tech/inthash.htm
138842ff34c3SPhil Shafer  *
138942ff34c3SPhil Shafer  * For our purposes, we can assume the low four bits are uninteresting
139042ff34c3SPhil Shafer  * since any string less that 16 bytes wouldn't be worthy of
139142ff34c3SPhil Shafer  * retaining.  We toss the high bits also, since these bits are likely
139242ff34c3SPhil Shafer  * to be common among constant format strings.  We then run Wang's
139342ff34c3SPhil Shafer  * algorithm, and cap the result at RETAIN_HASH_SIZE.
139442ff34c3SPhil Shafer  */
139542ff34c3SPhil Shafer static unsigned
139642ff34c3SPhil Shafer xo_retain_hash (const char *fmt)
139742ff34c3SPhil Shafer {
139842ff34c3SPhil Shafer     volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
139942ff34c3SPhil Shafer 
140042ff34c3SPhil Shafer     /* Discard low four bits and high bits; they aren't interesting */
140142ff34c3SPhil Shafer     uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
140242ff34c3SPhil Shafer 
140342ff34c3SPhil Shafer     val = (val ^ 61) ^ (val >> 16);
140442ff34c3SPhil Shafer     val = val + (val << 3);
140542ff34c3SPhil Shafer     val = val ^ (val >> 4);
140642ff34c3SPhil Shafer     val = val * 0x3a8f05c5;	/* My large prime number */
140742ff34c3SPhil Shafer     val = val ^ (val >> 15);
140842ff34c3SPhil Shafer     val &= RETAIN_HASH_SIZE - 1;
140942ff34c3SPhil Shafer 
141042ff34c3SPhil Shafer     return val;
141142ff34c3SPhil Shafer }
141242ff34c3SPhil Shafer 
141342ff34c3SPhil Shafer /*
141442ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
141542ff34c3SPhil Shafer  */
141642ff34c3SPhil Shafer void
141742ff34c3SPhil Shafer xo_retain_clear_all (void)
141842ff34c3SPhil Shafer {
141942ff34c3SPhil Shafer     int i;
142042ff34c3SPhil Shafer     xo_retain_entry_t *xrep, *next;
142142ff34c3SPhil Shafer 
142242ff34c3SPhil Shafer     for (i = 0; i < RETAIN_HASH_SIZE; i++) {
142342ff34c3SPhil Shafer 	for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
142442ff34c3SPhil Shafer 	    next = xrep->xre_next;
142542ff34c3SPhil Shafer 	    xo_free(xrep);
142642ff34c3SPhil Shafer 	}
142742ff34c3SPhil Shafer 	xo_retain.xr_bucket[i] = NULL;
142842ff34c3SPhil Shafer     }
142942ff34c3SPhil Shafer     xo_retain_count = 0;
143042ff34c3SPhil Shafer }
143142ff34c3SPhil Shafer 
143242ff34c3SPhil Shafer /*
143342ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
143442ff34c3SPhil Shafer  */
143542ff34c3SPhil Shafer void
143642ff34c3SPhil Shafer xo_retain_clear (const char *fmt)
143742ff34c3SPhil Shafer {
143842ff34c3SPhil Shafer     xo_retain_entry_t **xrepp;
143942ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
144042ff34c3SPhil Shafer 
144142ff34c3SPhil Shafer     for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
144242ff34c3SPhil Shafer 	 xrepp = &(*xrepp)->xre_next) {
144342ff34c3SPhil Shafer 	if ((*xrepp)->xre_format == fmt) {
144442ff34c3SPhil Shafer 	    *xrepp = (*xrepp)->xre_next;
144542ff34c3SPhil Shafer 	    xo_retain_count -= 1;
144642ff34c3SPhil Shafer 	    return;
144742ff34c3SPhil Shafer 	}
144842ff34c3SPhil Shafer     }
144942ff34c3SPhil Shafer }
145042ff34c3SPhil Shafer 
145142ff34c3SPhil Shafer /*
145242ff34c3SPhil Shafer  * Search the hash for an entry matching 'fmt'; return it's fields.
145342ff34c3SPhil Shafer  */
145442ff34c3SPhil Shafer static int
145542ff34c3SPhil Shafer xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
145642ff34c3SPhil Shafer {
145742ff34c3SPhil Shafer     if (xo_retain_count == 0)
145842ff34c3SPhil Shafer 	return -1;
145942ff34c3SPhil Shafer 
146042ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
146142ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
146242ff34c3SPhil Shafer 
146342ff34c3SPhil Shafer     for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
146442ff34c3SPhil Shafer 	 xrep = xrep->xre_next) {
146542ff34c3SPhil Shafer 	if (xrep->xre_format == fmt) {
146642ff34c3SPhil Shafer 	    *valp = xrep->xre_fields;
146742ff34c3SPhil Shafer 	    *nump = xrep->xre_num_fields;
146842ff34c3SPhil Shafer 	    xrep->xre_hits += 1;
146942ff34c3SPhil Shafer 	    return 0;
147042ff34c3SPhil Shafer 	}
147142ff34c3SPhil Shafer     }
147242ff34c3SPhil Shafer 
147342ff34c3SPhil Shafer     return -1;
147442ff34c3SPhil Shafer }
147542ff34c3SPhil Shafer 
147642ff34c3SPhil Shafer static void
147742ff34c3SPhil Shafer xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
147842ff34c3SPhil Shafer {
147942ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
148042ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
14818a6eceffSPhil Shafer     ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
148242ff34c3SPhil Shafer     xo_field_info_t *xfip;
148342ff34c3SPhil Shafer 
148442ff34c3SPhil Shafer     xrep = xo_realloc(NULL, sz);
148542ff34c3SPhil Shafer     if (xrep == NULL)
148642ff34c3SPhil Shafer 	return;
148742ff34c3SPhil Shafer 
148842ff34c3SPhil Shafer     xfip = (xo_field_info_t *) &xrep[1];
148942ff34c3SPhil Shafer     memcpy(xfip, fields, num_fields * sizeof(*fields));
149042ff34c3SPhil Shafer 
149142ff34c3SPhil Shafer     bzero(xrep, sizeof(*xrep));
149242ff34c3SPhil Shafer 
149342ff34c3SPhil Shafer     xrep->xre_format = fmt;
149442ff34c3SPhil Shafer     xrep->xre_fields = xfip;
149542ff34c3SPhil Shafer     xrep->xre_num_fields = num_fields;
149642ff34c3SPhil Shafer 
149742ff34c3SPhil Shafer     /* Record the field info in the retain bucket */
149842ff34c3SPhil Shafer     xrep->xre_next = xo_retain.xr_bucket[hash];
149942ff34c3SPhil Shafer     xo_retain.xr_bucket[hash] = xrep;
150042ff34c3SPhil Shafer     xo_retain_count += 1;
150142ff34c3SPhil Shafer }
150242ff34c3SPhil Shafer 
150342ff34c3SPhil Shafer #endif /* !LIBXO_NO_RETAIN */
150442ff34c3SPhil Shafer 
150531337658SMarcel Moolenaar /*
150631337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
150731337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
150831337658SMarcel Moolenaar  * XMLified content on standard output.
150931337658SMarcel Moolenaar  */
151031337658SMarcel Moolenaar static void
151131337658SMarcel Moolenaar xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
151231337658SMarcel Moolenaar 	     const char *fmt, va_list vap)
151331337658SMarcel Moolenaar {
151431337658SMarcel Moolenaar     xop = xo_default(xop);
1515d1a0d267SMarcel Moolenaar     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
151631337658SMarcel Moolenaar 	return;
151731337658SMarcel Moolenaar 
151831337658SMarcel Moolenaar     if (fmt == NULL)
151931337658SMarcel Moolenaar 	return;
152031337658SMarcel Moolenaar 
15218a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
15228a6eceffSPhil Shafer     ssize_t plen = xo_program ? strlen(xo_program) : 0;
1523545ddfbeSMarcel Moolenaar     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
152431337658SMarcel Moolenaar 
152531337658SMarcel Moolenaar     if (plen) {
152631337658SMarcel Moolenaar 	memcpy(newfmt, xo_program, plen);
152731337658SMarcel Moolenaar 	newfmt[plen++] = ':';
152831337658SMarcel Moolenaar 	newfmt[plen++] = ' ';
152931337658SMarcel Moolenaar     }
15302f784130SPhil Shafer 
153131337658SMarcel Moolenaar     memcpy(newfmt + plen, fmt, len);
153231337658SMarcel Moolenaar     newfmt[len + plen] = '\0';
153331337658SMarcel Moolenaar 
1534d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_WARN_XML)) {
153531337658SMarcel Moolenaar 	static char err_open[] = "<error>";
153631337658SMarcel Moolenaar 	static char err_close[] = "</error>";
153731337658SMarcel Moolenaar 	static char msg_open[] = "<message>";
153831337658SMarcel Moolenaar 	static char msg_close[] = "</message>";
153931337658SMarcel Moolenaar 
154031337658SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_data;
154131337658SMarcel Moolenaar 
154231337658SMarcel Moolenaar 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
154331337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
154431337658SMarcel Moolenaar 
154531337658SMarcel Moolenaar 	va_list va_local;
154631337658SMarcel Moolenaar 	va_copy(va_local, vap);
154731337658SMarcel Moolenaar 
15488a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
15498a6eceffSPhil Shafer 	ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
15502f784130SPhil Shafer 
1551d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1552c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1553c600d307SMarcel Moolenaar 		va_end(va_local);
155431337658SMarcel Moolenaar 		return;
1555c600d307SMarcel Moolenaar 	    }
155631337658SMarcel Moolenaar 
155731337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
155831337658SMarcel Moolenaar 	    va_copy(vap, va_local);
155931337658SMarcel Moolenaar 
156031337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
156131337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
156231337658SMarcel Moolenaar 	}
15632f784130SPhil Shafer 
156431337658SMarcel Moolenaar 	va_end(va_local);
156531337658SMarcel Moolenaar 
156631337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 1);
156731337658SMarcel Moolenaar 	xbp->xb_curp += rc;
156831337658SMarcel Moolenaar 
156931337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
157031337658SMarcel Moolenaar 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
157131337658SMarcel Moolenaar 
1572545ddfbeSMarcel Moolenaar 	if (code >= 0) {
157331337658SMarcel Moolenaar 	    const char *msg = strerror(code);
15742f784130SPhil Shafer 
157531337658SMarcel Moolenaar 	    if (msg) {
157631337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
157731337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
157831337658SMarcel Moolenaar 	    }
157931337658SMarcel Moolenaar 	}
158031337658SMarcel Moolenaar 
1581d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1582545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
158331337658SMarcel Moolenaar 
158431337658SMarcel Moolenaar     } else {
158531337658SMarcel Moolenaar 	vfprintf(stderr, newfmt, vap);
1586545ddfbeSMarcel Moolenaar 	if (code >= 0) {
1587545ddfbeSMarcel Moolenaar 	    const char *msg = strerror(code);
15882f784130SPhil Shafer 
1589545ddfbeSMarcel Moolenaar 	    if (msg)
1590545ddfbeSMarcel Moolenaar 		fprintf(stderr, ": %s", msg);
1591545ddfbeSMarcel Moolenaar 	}
1592545ddfbeSMarcel Moolenaar 	fprintf(stderr, "\n");
159331337658SMarcel Moolenaar     }
159431337658SMarcel Moolenaar }
159531337658SMarcel Moolenaar 
159631337658SMarcel Moolenaar void
159731337658SMarcel Moolenaar xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
159831337658SMarcel Moolenaar {
159931337658SMarcel Moolenaar     va_list vap;
160031337658SMarcel Moolenaar 
160131337658SMarcel Moolenaar     va_start(vap, fmt);
160231337658SMarcel Moolenaar     xo_warn_hcv(xop, code, 0, fmt, vap);
160331337658SMarcel Moolenaar     va_end(vap);
160431337658SMarcel Moolenaar }
160531337658SMarcel Moolenaar 
160631337658SMarcel Moolenaar void
160731337658SMarcel Moolenaar xo_warn_c (int code, const char *fmt, ...)
160831337658SMarcel Moolenaar {
160931337658SMarcel Moolenaar     va_list vap;
161031337658SMarcel Moolenaar 
161131337658SMarcel Moolenaar     va_start(vap, fmt);
1612545ddfbeSMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
161331337658SMarcel Moolenaar     va_end(vap);
161431337658SMarcel Moolenaar }
161531337658SMarcel Moolenaar 
161631337658SMarcel Moolenaar void
161731337658SMarcel Moolenaar xo_warn (const char *fmt, ...)
161831337658SMarcel Moolenaar {
161931337658SMarcel Moolenaar     int code = errno;
162031337658SMarcel Moolenaar     va_list vap;
162131337658SMarcel Moolenaar 
162231337658SMarcel Moolenaar     va_start(vap, fmt);
162331337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
162431337658SMarcel Moolenaar     va_end(vap);
162531337658SMarcel Moolenaar }
162631337658SMarcel Moolenaar 
162731337658SMarcel Moolenaar void
162831337658SMarcel Moolenaar xo_warnx (const char *fmt, ...)
162931337658SMarcel Moolenaar {
163031337658SMarcel Moolenaar     va_list vap;
163131337658SMarcel Moolenaar 
163231337658SMarcel Moolenaar     va_start(vap, fmt);
163331337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
163431337658SMarcel Moolenaar     va_end(vap);
163531337658SMarcel Moolenaar }
163631337658SMarcel Moolenaar 
163731337658SMarcel Moolenaar void
163831337658SMarcel Moolenaar xo_err (int eval, const char *fmt, ...)
163931337658SMarcel Moolenaar {
164031337658SMarcel Moolenaar     int code = errno;
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 void
165131337658SMarcel Moolenaar xo_errx (int eval, const char *fmt, ...)
165231337658SMarcel Moolenaar {
165331337658SMarcel Moolenaar     va_list vap;
165431337658SMarcel Moolenaar 
165531337658SMarcel Moolenaar     va_start(vap, fmt);
165631337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
165731337658SMarcel Moolenaar     va_end(vap);
165831337658SMarcel Moolenaar     xo_finish();
165931337658SMarcel Moolenaar     exit(eval);
166031337658SMarcel Moolenaar }
166131337658SMarcel Moolenaar 
166231337658SMarcel Moolenaar void
166331337658SMarcel Moolenaar xo_errc (int eval, int code, const char *fmt, ...)
166431337658SMarcel Moolenaar {
166531337658SMarcel Moolenaar     va_list vap;
166631337658SMarcel Moolenaar 
166731337658SMarcel Moolenaar     va_start(vap, fmt);
166831337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
166931337658SMarcel Moolenaar     va_end(vap);
167031337658SMarcel Moolenaar     xo_finish();
167131337658SMarcel Moolenaar     exit(eval);
167231337658SMarcel Moolenaar }
167331337658SMarcel Moolenaar 
167431337658SMarcel Moolenaar /*
167531337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
167631337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
167731337658SMarcel Moolenaar  * XMLified content on standard output.
167831337658SMarcel Moolenaar  */
167931337658SMarcel Moolenaar void
168031337658SMarcel Moolenaar xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
168131337658SMarcel Moolenaar {
168231337658SMarcel Moolenaar     static char msg_open[] = "<message>";
168331337658SMarcel Moolenaar     static char msg_close[] = "</message>";
168431337658SMarcel Moolenaar     xo_buffer_t *xbp;
16858a6eceffSPhil Shafer     ssize_t rc;
168631337658SMarcel Moolenaar     va_list va_local;
168731337658SMarcel Moolenaar 
168831337658SMarcel Moolenaar     xop = xo_default(xop);
168931337658SMarcel Moolenaar 
169031337658SMarcel Moolenaar     if (fmt == NULL || *fmt == '\0')
169131337658SMarcel Moolenaar 	return;
169231337658SMarcel Moolenaar 
169331337658SMarcel Moolenaar     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
169431337658SMarcel Moolenaar 
1695788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
169631337658SMarcel Moolenaar     case XO_STYLE_XML:
169731337658SMarcel Moolenaar 	xbp = &xop->xo_data;
1698d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
169931337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
170031337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
170131337658SMarcel Moolenaar 
170231337658SMarcel Moolenaar 	va_copy(va_local, vap);
170331337658SMarcel Moolenaar 
17048a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
17052f784130SPhil Shafer 
170631337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1707d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1708c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1709c600d307SMarcel Moolenaar 		va_end(va_local);
171031337658SMarcel Moolenaar 		return;
1711c600d307SMarcel Moolenaar 	    }
171231337658SMarcel Moolenaar 
171331337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
171431337658SMarcel Moolenaar 	    va_copy(vap, va_local);
171531337658SMarcel Moolenaar 
171631337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
171731337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
171831337658SMarcel Moolenaar 	}
17192f784130SPhil Shafer 
172031337658SMarcel Moolenaar 	va_end(va_local);
172131337658SMarcel Moolenaar 
1722d1a0d267SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
172331337658SMarcel Moolenaar 	xbp->xb_curp += rc;
172431337658SMarcel Moolenaar 
172531337658SMarcel Moolenaar 	if (need_nl && code > 0) {
172631337658SMarcel Moolenaar 	    const char *msg = strerror(code);
17272f784130SPhil Shafer 
172831337658SMarcel Moolenaar 	    if (msg) {
172931337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
173031337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
173131337658SMarcel Moolenaar 	    }
173231337658SMarcel Moolenaar 	}
173331337658SMarcel Moolenaar 
173431337658SMarcel Moolenaar 	if (need_nl)
1735d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1736d1a0d267SMarcel Moolenaar 
1737d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1738d1a0d267SMarcel Moolenaar 
1739d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
1740d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1741d1a0d267SMarcel Moolenaar 
1742545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
174331337658SMarcel Moolenaar 	break;
174431337658SMarcel Moolenaar 
174531337658SMarcel Moolenaar     case XO_STYLE_HTML:
174631337658SMarcel Moolenaar 	{
174731337658SMarcel Moolenaar 	    char buf[BUFSIZ], *bp = buf, *cp;
17488a6eceffSPhil Shafer 	    ssize_t bufsiz = sizeof(buf);
17498a6eceffSPhil Shafer 	    ssize_t rc2;
175031337658SMarcel Moolenaar 
175131337658SMarcel Moolenaar 	    va_copy(va_local, vap);
175231337658SMarcel Moolenaar 
1753c600d307SMarcel Moolenaar 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
175431337658SMarcel Moolenaar 	    if (rc > bufsiz) {
175531337658SMarcel Moolenaar 		bufsiz = rc + BUFSIZ;
175631337658SMarcel Moolenaar 		bp = alloca(bufsiz);
175731337658SMarcel Moolenaar 		va_end(va_local);
175831337658SMarcel Moolenaar 		va_copy(va_local, vap);
1759c600d307SMarcel Moolenaar 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
176031337658SMarcel Moolenaar 	    }
17612f784130SPhil Shafer 
1762c600d307SMarcel Moolenaar 	    va_end(va_local);
176331337658SMarcel Moolenaar 	    cp = bp + rc;
176431337658SMarcel Moolenaar 
176531337658SMarcel Moolenaar 	    if (need_nl) {
176631337658SMarcel Moolenaar 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
176731337658SMarcel Moolenaar 			       (code > 0) ? ": " : "",
176831337658SMarcel Moolenaar 			       (code > 0) ? strerror(code) : "");
176931337658SMarcel Moolenaar 		if (rc2 > 0)
177031337658SMarcel Moolenaar 		    rc += rc2;
177131337658SMarcel Moolenaar 	    }
177231337658SMarcel Moolenaar 
1773264104f2SPhil Shafer 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc,
1774264104f2SPhil Shafer 			      NULL, 0, NULL, 0);
177531337658SMarcel Moolenaar 	}
177631337658SMarcel Moolenaar 	break;
177731337658SMarcel Moolenaar 
177831337658SMarcel Moolenaar     case XO_STYLE_JSON:
1779d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
1780d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
1781d1a0d267SMarcel Moolenaar 	/* No means of representing messages */
1782d1a0d267SMarcel Moolenaar 	return;
178331337658SMarcel Moolenaar 
178431337658SMarcel Moolenaar     case XO_STYLE_TEXT:
178531337658SMarcel Moolenaar 	rc = xo_printf_v(xop, fmt, vap);
178631337658SMarcel Moolenaar 	/*
178731337658SMarcel Moolenaar 	 * XXX need to handle UTF-8 widths
178831337658SMarcel Moolenaar 	 */
178931337658SMarcel Moolenaar 	if (rc > 0) {
1790d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
179131337658SMarcel Moolenaar 		xop->xo_columns += rc;
1792d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
179331337658SMarcel Moolenaar 		xop->xo_anchor_columns += rc;
179431337658SMarcel Moolenaar 	}
179531337658SMarcel Moolenaar 
179631337658SMarcel Moolenaar 	if (need_nl && code > 0) {
179731337658SMarcel Moolenaar 	    const char *msg = strerror(code);
17982f784130SPhil Shafer 
179931337658SMarcel Moolenaar 	    if (msg) {
180031337658SMarcel Moolenaar 		xo_printf(xop, ": %s", msg);
180131337658SMarcel Moolenaar 	    }
180231337658SMarcel Moolenaar 	}
180331337658SMarcel Moolenaar 	if (need_nl)
180431337658SMarcel Moolenaar 	    xo_printf(xop, "\n");
180531337658SMarcel Moolenaar 
180631337658SMarcel Moolenaar 	break;
180731337658SMarcel Moolenaar     }
180831337658SMarcel Moolenaar 
180942ff34c3SPhil Shafer     switch (xo_style(xop)) {
181042ff34c3SPhil Shafer     case XO_STYLE_HTML:
181142ff34c3SPhil Shafer 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
181242ff34c3SPhil Shafer 	    static char div_close[] = "</div>";
18132f784130SPhil Shafer 
181442ff34c3SPhil Shafer 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
181542ff34c3SPhil Shafer 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
181642ff34c3SPhil Shafer 
181742ff34c3SPhil Shafer 	    if (XOF_ISSET(xop, XOF_PRETTY))
181842ff34c3SPhil Shafer 		xo_data_append(xop, "\n", 1);
181942ff34c3SPhil Shafer 	}
182042ff34c3SPhil Shafer 	break;
182142ff34c3SPhil Shafer     }
182242ff34c3SPhil Shafer 
1823545ddfbeSMarcel Moolenaar     (void) xo_flush_h(xop);
182431337658SMarcel Moolenaar }
182531337658SMarcel Moolenaar 
182631337658SMarcel Moolenaar void
182731337658SMarcel Moolenaar xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
182831337658SMarcel Moolenaar {
182931337658SMarcel Moolenaar     va_list vap;
183031337658SMarcel Moolenaar 
183131337658SMarcel Moolenaar     va_start(vap, fmt);
183231337658SMarcel Moolenaar     xo_message_hcv(xop, code, fmt, vap);
183331337658SMarcel Moolenaar     va_end(vap);
183431337658SMarcel Moolenaar }
183531337658SMarcel Moolenaar 
183631337658SMarcel Moolenaar void
183731337658SMarcel Moolenaar xo_message_c (int code, const char *fmt, ...)
183831337658SMarcel Moolenaar {
183931337658SMarcel Moolenaar     va_list vap;
184031337658SMarcel Moolenaar 
184131337658SMarcel Moolenaar     va_start(vap, fmt);
184231337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
184331337658SMarcel Moolenaar     va_end(vap);
184431337658SMarcel Moolenaar }
184531337658SMarcel Moolenaar 
184631337658SMarcel Moolenaar void
1847d1a0d267SMarcel Moolenaar xo_message_e (const char *fmt, ...)
184831337658SMarcel Moolenaar {
184931337658SMarcel Moolenaar     int code = errno;
185031337658SMarcel Moolenaar     va_list vap;
185131337658SMarcel Moolenaar 
185231337658SMarcel Moolenaar     va_start(vap, fmt);
185331337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
185431337658SMarcel Moolenaar     va_end(vap);
185531337658SMarcel Moolenaar }
185631337658SMarcel Moolenaar 
1857d1a0d267SMarcel Moolenaar void
1858d1a0d267SMarcel Moolenaar xo_message (const char *fmt, ...)
1859d1a0d267SMarcel Moolenaar {
1860d1a0d267SMarcel Moolenaar     va_list vap;
1861d1a0d267SMarcel Moolenaar 
1862d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
1863d1a0d267SMarcel Moolenaar     xo_message_hcv(NULL, 0, fmt, vap);
1864d1a0d267SMarcel Moolenaar     va_end(vap);
1865d1a0d267SMarcel Moolenaar }
1866d1a0d267SMarcel Moolenaar 
186731337658SMarcel Moolenaar static void
186831337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...)
186931337658SMarcel Moolenaar {
1870d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_WARN))
187131337658SMarcel Moolenaar 	return;
187231337658SMarcel Moolenaar 
187331337658SMarcel Moolenaar     va_list vap;
187431337658SMarcel Moolenaar 
187531337658SMarcel Moolenaar     va_start(vap, fmt);
187631337658SMarcel Moolenaar     xo_warn_hcv(xop, -1, 1, fmt, vap);
187731337658SMarcel Moolenaar     va_end(vap);
187831337658SMarcel Moolenaar }
187931337658SMarcel Moolenaar 
188031337658SMarcel Moolenaar /**
188131337658SMarcel Moolenaar  * Create a handle for use by later libxo functions.
188231337658SMarcel Moolenaar  *
188331337658SMarcel Moolenaar  * Note: normal use of libxo does not require a distinct handle, since
188431337658SMarcel Moolenaar  * the default handle (used when NULL is passed) generates text on stdout.
188531337658SMarcel Moolenaar  *
1886f2b7bf8aSPhil Shafer  * @param style Style of output desired (XO_STYLE_* value)
1887f2b7bf8aSPhil Shafer  * @param flags Set of XOF_* flags in use with this handle
1888f2b7bf8aSPhil Shafer  * @return Newly allocated handle
1889f2b7bf8aSPhil Shafer  * @see xo_destroy
189031337658SMarcel Moolenaar  */
189131337658SMarcel Moolenaar xo_handle_t *
189231337658SMarcel Moolenaar xo_create (xo_style_t style, xo_xof_flags_t flags)
189331337658SMarcel Moolenaar {
189431337658SMarcel Moolenaar     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
189531337658SMarcel Moolenaar 
189631337658SMarcel Moolenaar     if (xop) {
189731337658SMarcel Moolenaar 	bzero(xop, sizeof(*xop));
189831337658SMarcel Moolenaar 
189931337658SMarcel Moolenaar 	xop->xo_style = style;
1900d1a0d267SMarcel Moolenaar 	XOF_SET(xop, flags);
190131337658SMarcel Moolenaar 	xo_init_handle(xop);
1902d1a0d267SMarcel Moolenaar 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
190331337658SMarcel Moolenaar     }
190431337658SMarcel Moolenaar 
190531337658SMarcel Moolenaar     return xop;
190631337658SMarcel Moolenaar }
190731337658SMarcel Moolenaar 
190831337658SMarcel Moolenaar /**
190931337658SMarcel Moolenaar  * Create a handle that will write to the given file.  Use
191031337658SMarcel Moolenaar  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1911f2b7bf8aSPhil Shafer  *
1912f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1913f2b7bf8aSPhil Shafer  * @param style Style of output desired (XO_STYLE_* value)
1914f2b7bf8aSPhil Shafer  * @param flags Set of XOF_* flags to use with this handle
1915f2b7bf8aSPhil Shafer  * @return Newly allocated handle
1916f2b7bf8aSPhil Shafer  * @see xo_destroy
191731337658SMarcel Moolenaar  */
191831337658SMarcel Moolenaar xo_handle_t *
191931337658SMarcel Moolenaar xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
192031337658SMarcel Moolenaar {
192131337658SMarcel Moolenaar     xo_handle_t *xop = xo_create(style, flags);
192231337658SMarcel Moolenaar 
192331337658SMarcel Moolenaar     if (xop) {
192431337658SMarcel Moolenaar 	xop->xo_opaque = fp;
192531337658SMarcel Moolenaar 	xop->xo_write = xo_write_to_file;
192631337658SMarcel Moolenaar 	xop->xo_close = xo_close_file;
1927545ddfbeSMarcel Moolenaar 	xop->xo_flush = xo_flush_file;
192831337658SMarcel Moolenaar     }
192931337658SMarcel Moolenaar 
193031337658SMarcel Moolenaar     return xop;
193131337658SMarcel Moolenaar }
193231337658SMarcel Moolenaar 
193331337658SMarcel Moolenaar /**
193442ff34c3SPhil Shafer  * Set the default handler to output to a file.
1935f2b7bf8aSPhil Shafer  *
1936f2b7bf8aSPhil Shafer  * @param xop libxo handle
1937f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1938f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
193942ff34c3SPhil Shafer  */
194042ff34c3SPhil Shafer int
194142ff34c3SPhil Shafer xo_set_file_h (xo_handle_t *xop, FILE *fp)
194242ff34c3SPhil Shafer {
194342ff34c3SPhil Shafer     xop = xo_default(xop);
194442ff34c3SPhil Shafer 
194542ff34c3SPhil Shafer     if (fp == NULL) {
194642ff34c3SPhil Shafer 	xo_failure(xop, "xo_set_file: NULL fp");
194742ff34c3SPhil Shafer 	return -1;
194842ff34c3SPhil Shafer     }
194942ff34c3SPhil Shafer 
195042ff34c3SPhil Shafer     xop->xo_opaque = fp;
195142ff34c3SPhil Shafer     xop->xo_write = xo_write_to_file;
195242ff34c3SPhil Shafer     xop->xo_close = xo_close_file;
195342ff34c3SPhil Shafer     xop->xo_flush = xo_flush_file;
195442ff34c3SPhil Shafer 
195542ff34c3SPhil Shafer     return 0;
195642ff34c3SPhil Shafer }
195742ff34c3SPhil Shafer 
195842ff34c3SPhil Shafer /**
195942ff34c3SPhil Shafer  * Set the default handler to output to a file.
1960f2b7bf8aSPhil Shafer  *
1961f2b7bf8aSPhil Shafer  * @param fp FILE pointer to use
1962f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
196342ff34c3SPhil Shafer  */
196442ff34c3SPhil Shafer int
196542ff34c3SPhil Shafer xo_set_file (FILE *fp)
196642ff34c3SPhil Shafer {
196742ff34c3SPhil Shafer     return xo_set_file_h(NULL, fp);
196842ff34c3SPhil Shafer }
196942ff34c3SPhil Shafer 
197042ff34c3SPhil Shafer /**
197131337658SMarcel Moolenaar  * Release any resources held by the handle.
1972f2b7bf8aSPhil Shafer  *
1973f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
197431337658SMarcel Moolenaar  */
197531337658SMarcel Moolenaar void
1976c600d307SMarcel Moolenaar xo_destroy (xo_handle_t *xop_arg)
197731337658SMarcel Moolenaar {
1978c600d307SMarcel Moolenaar     xo_handle_t *xop = xo_default(xop_arg);
197931337658SMarcel Moolenaar 
1980d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
1981d1a0d267SMarcel Moolenaar 
1982d1a0d267SMarcel Moolenaar     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
198331337658SMarcel Moolenaar 	xop->xo_close(xop->xo_opaque);
198431337658SMarcel Moolenaar 
198531337658SMarcel Moolenaar     xo_free(xop->xo_stack);
198631337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_data);
198731337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_fmt);
198831337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_predicate);
198931337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_attrs);
1990788ca347SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_color_buf);
1991788ca347SMarcel Moolenaar 
1992788ca347SMarcel Moolenaar     if (xop->xo_version)
1993788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
199431337658SMarcel Moolenaar 
1995c600d307SMarcel Moolenaar     if (xop_arg == NULL) {
1996545ddfbeSMarcel Moolenaar 	bzero(&xo_default_handle, sizeof(xo_default_handle));
199731337658SMarcel Moolenaar 	xo_default_inited = 0;
199831337658SMarcel Moolenaar     } else
199931337658SMarcel Moolenaar 	xo_free(xop);
200031337658SMarcel Moolenaar }
200131337658SMarcel Moolenaar 
200231337658SMarcel Moolenaar /**
200331337658SMarcel Moolenaar  * Record a new output style to use for the given handle (or default if
200431337658SMarcel Moolenaar  * handle is NULL).  This output style will be used for any future output.
200531337658SMarcel Moolenaar  *
2006f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2007f2b7bf8aSPhil Shafer  * @param style new output style (XO_STYLE_*)
200831337658SMarcel Moolenaar  */
200931337658SMarcel Moolenaar void
201031337658SMarcel Moolenaar xo_set_style (xo_handle_t *xop, xo_style_t style)
201131337658SMarcel Moolenaar {
201231337658SMarcel Moolenaar     xop = xo_default(xop);
201331337658SMarcel Moolenaar     xop->xo_style = style;
201431337658SMarcel Moolenaar }
201531337658SMarcel Moolenaar 
2016f2b7bf8aSPhil Shafer /**
2017f2b7bf8aSPhil Shafer  * Return the current style of a handle
2018f2b7bf8aSPhil Shafer  *
2019f2b7bf8aSPhil Shafer  * @param xop XO handle to access
2020f2b7bf8aSPhil Shafer  * @return The handle's current style
2021f2b7bf8aSPhil Shafer  */
202231337658SMarcel Moolenaar xo_style_t
202331337658SMarcel Moolenaar xo_get_style (xo_handle_t *xop)
202431337658SMarcel Moolenaar {
202531337658SMarcel Moolenaar     xop = xo_default(xop);
2026788ca347SMarcel Moolenaar     return xo_style(xop);
202731337658SMarcel Moolenaar }
202831337658SMarcel Moolenaar 
2029f2b7bf8aSPhil Shafer /**
2030f2b7bf8aSPhil Shafer  * Return the XO_STYLE_* value matching a given name
2031f2b7bf8aSPhil Shafer  *
2032f2b7bf8aSPhil Shafer  * @param name String name of a style
2033f2b7bf8aSPhil Shafer  * @return XO_STYLE_* value
2034f2b7bf8aSPhil Shafer  */
203531337658SMarcel Moolenaar static int
203631337658SMarcel Moolenaar xo_name_to_style (const char *name)
203731337658SMarcel Moolenaar {
203831337658SMarcel Moolenaar     if (strcmp(name, "xml") == 0)
203931337658SMarcel Moolenaar 	return XO_STYLE_XML;
204031337658SMarcel Moolenaar     else if (strcmp(name, "json") == 0)
204131337658SMarcel Moolenaar 	return XO_STYLE_JSON;
2042d1a0d267SMarcel Moolenaar     else if (strcmp(name, "encoder") == 0)
2043d1a0d267SMarcel Moolenaar 	return XO_STYLE_ENCODER;
204431337658SMarcel Moolenaar     else if (strcmp(name, "text") == 0)
204531337658SMarcel Moolenaar 	return XO_STYLE_TEXT;
204631337658SMarcel Moolenaar     else if (strcmp(name, "html") == 0)
204731337658SMarcel Moolenaar 	return XO_STYLE_HTML;
2048d1a0d267SMarcel Moolenaar     else if (strcmp(name, "sdparams") == 0)
2049d1a0d267SMarcel Moolenaar 	return XO_STYLE_SDPARAMS;
205031337658SMarcel Moolenaar 
205131337658SMarcel Moolenaar     return -1;
205231337658SMarcel Moolenaar }
205331337658SMarcel Moolenaar 
205431337658SMarcel Moolenaar /*
2055d1a0d267SMarcel Moolenaar  * Indicate if the style is an "encoding" one as opposed to a "display" one.
2056d1a0d267SMarcel Moolenaar  */
2057d1a0d267SMarcel Moolenaar static int
2058d1a0d267SMarcel Moolenaar xo_style_is_encoding (xo_handle_t *xop)
2059d1a0d267SMarcel Moolenaar {
2060d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_JSON
2061d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_XML
2062d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_SDPARAMS
2063d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_ENCODER)
2064d1a0d267SMarcel Moolenaar 	return 1;
2065d1a0d267SMarcel Moolenaar     return 0;
2066d1a0d267SMarcel Moolenaar }
2067d1a0d267SMarcel Moolenaar 
2068d1a0d267SMarcel Moolenaar /* Simple name-value mapping */
2069d1a0d267SMarcel Moolenaar typedef struct xo_mapping_s {
2070f2b7bf8aSPhil Shafer     xo_xff_flags_t xm_value;	/* Flag value */
2071f2b7bf8aSPhil Shafer     const char *xm_name;	/* String name */
2072d1a0d267SMarcel Moolenaar } xo_mapping_t;
2073d1a0d267SMarcel Moolenaar 
2074d1a0d267SMarcel Moolenaar static xo_xff_flags_t
20758a6eceffSPhil Shafer xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len)
2076d1a0d267SMarcel Moolenaar {
2077d1a0d267SMarcel Moolenaar     if (len == 0)
2078d1a0d267SMarcel Moolenaar 	return 0;
2079d1a0d267SMarcel Moolenaar 
2080d1a0d267SMarcel Moolenaar     if (len < 0)
2081d1a0d267SMarcel Moolenaar 	len = strlen(value);
2082d1a0d267SMarcel Moolenaar 
2083d1a0d267SMarcel Moolenaar     while (isspace((int) *value)) {
2084d1a0d267SMarcel Moolenaar 	value += 1;
2085d1a0d267SMarcel Moolenaar 	len -= 1;
2086d1a0d267SMarcel Moolenaar     }
2087d1a0d267SMarcel Moolenaar 
2088d1a0d267SMarcel Moolenaar     while (isspace((int) value[len]))
2089d1a0d267SMarcel Moolenaar 	len -= 1;
2090d1a0d267SMarcel Moolenaar 
2091d1a0d267SMarcel Moolenaar     if (*value == '\0')
2092d1a0d267SMarcel Moolenaar 	return 0;
2093d1a0d267SMarcel Moolenaar 
2094d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2095d1a0d267SMarcel Moolenaar 	if (strncmp(map->xm_name, value, len) == 0)
2096d1a0d267SMarcel Moolenaar 	    return map->xm_value;
2097d1a0d267SMarcel Moolenaar 
2098d1a0d267SMarcel Moolenaar     return 0;
2099d1a0d267SMarcel Moolenaar }
2100d1a0d267SMarcel Moolenaar 
2101d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
2102d1a0d267SMarcel Moolenaar static const char *
2103d1a0d267SMarcel Moolenaar xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2104d1a0d267SMarcel Moolenaar {
2105d1a0d267SMarcel Moolenaar     if (value == 0)
2106d1a0d267SMarcel Moolenaar 	return NULL;
2107d1a0d267SMarcel Moolenaar 
2108d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2109d1a0d267SMarcel Moolenaar 	if (map->xm_value == value)
2110d1a0d267SMarcel Moolenaar 	    return map->xm_name;
2111d1a0d267SMarcel Moolenaar 
2112d1a0d267SMarcel Moolenaar     return NULL;
2113d1a0d267SMarcel Moolenaar }
2114d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
2115d1a0d267SMarcel Moolenaar 
2116d1a0d267SMarcel Moolenaar static xo_mapping_t xo_xof_names[] = {
2117d1a0d267SMarcel Moolenaar     { XOF_COLOR_ALLOWED, "color" },
2118f2b7bf8aSPhil Shafer     { XOF_COLOR, "color-force" },
2119d1a0d267SMarcel Moolenaar     { XOF_COLUMNS, "columns" },
2120d1a0d267SMarcel Moolenaar     { XOF_DTRT, "dtrt" },
2121d1a0d267SMarcel Moolenaar     { XOF_FLUSH, "flush" },
21228a6eceffSPhil Shafer     { XOF_FLUSH_LINE, "flush-line" },
2123d1a0d267SMarcel Moolenaar     { XOF_IGNORE_CLOSE, "ignore-close" },
2124d1a0d267SMarcel Moolenaar     { XOF_INFO, "info" },
2125d1a0d267SMarcel Moolenaar     { XOF_KEYS, "keys" },
2126d1a0d267SMarcel Moolenaar     { XOF_LOG_GETTEXT, "log-gettext" },
2127d1a0d267SMarcel Moolenaar     { XOF_LOG_SYSLOG, "log-syslog" },
2128d1a0d267SMarcel Moolenaar     { XOF_NO_HUMANIZE, "no-humanize" },
2129d1a0d267SMarcel Moolenaar     { XOF_NO_LOCALE, "no-locale" },
213042ff34c3SPhil Shafer     { XOF_RETAIN_NONE, "no-retain" },
2131d1a0d267SMarcel Moolenaar     { XOF_NO_TOP, "no-top" },
2132d1a0d267SMarcel Moolenaar     { XOF_NOT_FIRST, "not-first" },
2133d1a0d267SMarcel Moolenaar     { XOF_PRETTY, "pretty" },
213442ff34c3SPhil Shafer     { XOF_RETAIN_ALL, "retain" },
2135d1a0d267SMarcel Moolenaar     { XOF_UNDERSCORES, "underscores" },
2136d1a0d267SMarcel Moolenaar     { XOF_UNITS, "units" },
2137d1a0d267SMarcel Moolenaar     { XOF_WARN, "warn" },
2138d1a0d267SMarcel Moolenaar     { XOF_WARN_XML, "warn-xml" },
2139d1a0d267SMarcel Moolenaar     { XOF_XPATH, "xpath" },
2140d1a0d267SMarcel Moolenaar     { 0, NULL }
2141d1a0d267SMarcel Moolenaar };
2142d1a0d267SMarcel Moolenaar 
2143f2b7bf8aSPhil Shafer /* Options available via the environment variable ($LIBXO_OPTIONS) */
2144f2b7bf8aSPhil Shafer static xo_mapping_t xo_xof_simple_names[] = {
2145f2b7bf8aSPhil Shafer     { XOF_COLOR_ALLOWED, "color" },
2146f2b7bf8aSPhil Shafer     { XOF_FLUSH, "flush" },
2147f2b7bf8aSPhil Shafer     { XOF_FLUSH_LINE, "flush-line" },
2148f2b7bf8aSPhil Shafer     { XOF_NO_HUMANIZE, "no-humanize" },
2149f2b7bf8aSPhil Shafer     { XOF_NO_LOCALE, "no-locale" },
2150f2b7bf8aSPhil Shafer     { XOF_RETAIN_NONE, "no-retain" },
2151f2b7bf8aSPhil Shafer     { XOF_PRETTY, "pretty" },
2152f2b7bf8aSPhil Shafer     { XOF_RETAIN_ALL, "retain" },
2153f2b7bf8aSPhil Shafer     { XOF_UNDERSCORES, "underscores" },
2154f2b7bf8aSPhil Shafer     { XOF_WARN, "warn" },
2155f2b7bf8aSPhil Shafer     { 0, NULL }
2156f2b7bf8aSPhil Shafer };
2157f2b7bf8aSPhil Shafer 
2158d1a0d267SMarcel Moolenaar /*
215931337658SMarcel Moolenaar  * Convert string name to XOF_* flag value.
216031337658SMarcel Moolenaar  * Not all are useful.  Or safe.  Or sane.
216131337658SMarcel Moolenaar  */
216231337658SMarcel Moolenaar static unsigned
216331337658SMarcel Moolenaar xo_name_to_flag (const char *name)
216431337658SMarcel Moolenaar {
2165d1a0d267SMarcel Moolenaar     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
216631337658SMarcel Moolenaar }
216731337658SMarcel Moolenaar 
2168f2b7bf8aSPhil Shafer /**
2169f2b7bf8aSPhil Shafer  * Set the style of an libxo handle based on a string name
2170f2b7bf8aSPhil Shafer  *
2171f2b7bf8aSPhil Shafer  * @param xop XO handle
2172f2b7bf8aSPhil Shafer  * @param name String value of name
2173f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
2174f2b7bf8aSPhil Shafer  */
217531337658SMarcel Moolenaar int
217631337658SMarcel Moolenaar xo_set_style_name (xo_handle_t *xop, const char *name)
217731337658SMarcel Moolenaar {
217831337658SMarcel Moolenaar     if (name == NULL)
217931337658SMarcel Moolenaar 	return -1;
218031337658SMarcel Moolenaar 
218131337658SMarcel Moolenaar     int style = xo_name_to_style(name);
21822f784130SPhil Shafer 
218331337658SMarcel Moolenaar     if (style < 0)
218431337658SMarcel Moolenaar 	return -1;
218531337658SMarcel Moolenaar 
218631337658SMarcel Moolenaar     xo_set_style(xop, style);
218731337658SMarcel Moolenaar     return 0;
218831337658SMarcel Moolenaar }
218931337658SMarcel Moolenaar 
219031337658SMarcel Moolenaar /*
2191f2b7bf8aSPhil Shafer  * Fill in the color map, based on the input string; currently unimplemented
2192f2b7bf8aSPhil Shafer  * Look for something like "colors=red/blue+green/yellow" as fg/bg pairs.
2193f2b7bf8aSPhil Shafer  */
2194f2b7bf8aSPhil Shafer static void
2195f2b7bf8aSPhil Shafer xo_set_color_map (xo_handle_t *xop, char *value)
2196f2b7bf8aSPhil Shafer {
2197f2b7bf8aSPhil Shafer #ifdef LIBXO_TEXT_ONLY
2198f2b7bf8aSPhil Shafer     return;
2199f2b7bf8aSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
2200f2b7bf8aSPhil Shafer 
2201f2b7bf8aSPhil Shafer     char *cp, *ep, *vp, *np;
2202f2b7bf8aSPhil Shafer     ssize_t len = value ? strlen(value) + 1 : 0;
2203f2b7bf8aSPhil Shafer     int num = 1, fg, bg;
2204f2b7bf8aSPhil Shafer 
2205f2b7bf8aSPhil Shafer     for (cp = value, ep = cp + len - 1; cp && *cp && cp < ep; cp = np) {
2206f2b7bf8aSPhil Shafer 	np = strchr(cp, '+');
2207f2b7bf8aSPhil Shafer 	if (np)
2208f2b7bf8aSPhil Shafer 	    *np++ = '\0';
2209f2b7bf8aSPhil Shafer 
2210f2b7bf8aSPhil Shafer 	vp = strchr(cp, '/');
2211f2b7bf8aSPhil Shafer 	if (vp)
2212f2b7bf8aSPhil Shafer 	    *vp++ = '\0';
2213f2b7bf8aSPhil Shafer 
2214f2b7bf8aSPhil Shafer 	fg = *cp ? xo_color_find(cp) : -1;
2215f2b7bf8aSPhil Shafer 	bg = (vp && *vp) ? xo_color_find(vp) : -1;
2216f2b7bf8aSPhil Shafer 
2217f2b7bf8aSPhil Shafer 	xop->xo_color_map_fg[num] = (fg < 0) ? num : fg;
2218f2b7bf8aSPhil Shafer 	xop->xo_color_map_bg[num] = (bg < 0) ? num : bg;
2219f2b7bf8aSPhil Shafer 	if (++num > XO_NUM_COLORS)
2220f2b7bf8aSPhil Shafer 	    break;
2221f2b7bf8aSPhil Shafer     }
2222f2b7bf8aSPhil Shafer 
2223f2b7bf8aSPhil Shafer     /* If no color initialization happened, then we don't need the map */
2224f2b7bf8aSPhil Shafer     if (num > 0)
2225f2b7bf8aSPhil Shafer 	XOF_SET(xop, XOF_COLOR_MAP);
2226f2b7bf8aSPhil Shafer     else
2227f2b7bf8aSPhil Shafer 	XOF_CLEAR(xop, XOF_COLOR_MAP);
2228f2b7bf8aSPhil Shafer 
2229f2b7bf8aSPhil Shafer     /* Fill in the rest of the colors with the defaults */
2230f2b7bf8aSPhil Shafer     for ( ; num < XO_NUM_COLORS; num++)
2231f2b7bf8aSPhil Shafer 	xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num;
2232f2b7bf8aSPhil Shafer }
2233f2b7bf8aSPhil Shafer 
2234f2b7bf8aSPhil Shafer static int
2235f2b7bf8aSPhil Shafer xo_set_options_simple (xo_handle_t *xop, const char *input)
2236f2b7bf8aSPhil Shafer {
2237f2b7bf8aSPhil Shafer     xo_xof_flags_t new_flag;
2238f2b7bf8aSPhil Shafer     char *cp, *ep, *vp, *np, *bp;
2239f2b7bf8aSPhil Shafer     ssize_t len = strlen(input) + 1;
2240f2b7bf8aSPhil Shafer 
2241f2b7bf8aSPhil Shafer     bp = alloca(len);
2242f2b7bf8aSPhil Shafer     memcpy(bp, input, len);
2243f2b7bf8aSPhil Shafer 
2244f2b7bf8aSPhil Shafer     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2245f2b7bf8aSPhil Shafer 	np = strchr(cp, ',');
2246f2b7bf8aSPhil Shafer 	if (np)
2247f2b7bf8aSPhil Shafer 	    *np++ = '\0';
2248f2b7bf8aSPhil Shafer 
2249f2b7bf8aSPhil Shafer 	vp = strchr(cp, '=');
2250f2b7bf8aSPhil Shafer 	if (vp)
2251f2b7bf8aSPhil Shafer 	    *vp++ = '\0';
2252f2b7bf8aSPhil Shafer 
2253f2b7bf8aSPhil Shafer 	if (strcmp("colors", cp) == 0) {
2254f2b7bf8aSPhil Shafer 	    xo_set_color_map(xop, vp);
2255f2b7bf8aSPhil Shafer 	    continue;
2256f2b7bf8aSPhil Shafer 	}
2257f2b7bf8aSPhil Shafer 
2258f2b7bf8aSPhil Shafer 	new_flag = xo_name_lookup(xo_xof_simple_names, cp, -1);
2259f2b7bf8aSPhil Shafer 	if (new_flag != 0) {
2260f2b7bf8aSPhil Shafer 	    XOF_SET(xop, new_flag);
2261f2b7bf8aSPhil Shafer 	} else if (strcmp(cp, "no-color") == 0) {
2262f2b7bf8aSPhil Shafer 	    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2263f2b7bf8aSPhil Shafer 	} else {
2264f2b7bf8aSPhil Shafer 	    xo_failure(xop, "unknown simple option: %s", cp);
2265f2b7bf8aSPhil Shafer 	    return -1;
2266f2b7bf8aSPhil Shafer 	}
2267f2b7bf8aSPhil Shafer     }
2268f2b7bf8aSPhil Shafer 
2269f2b7bf8aSPhil Shafer     return 0;
2270f2b7bf8aSPhil Shafer }
2271f2b7bf8aSPhil Shafer 
2272f2b7bf8aSPhil Shafer /**
227331337658SMarcel Moolenaar  * Set the options for a handle using a string of options
227431337658SMarcel Moolenaar  * passed in.  The input is a comma-separated set of names
227531337658SMarcel Moolenaar  * and optional values: "xml,pretty,indent=4"
2276f2b7bf8aSPhil Shafer  *
2277f2b7bf8aSPhil Shafer  * @param xop XO handle
2278f2b7bf8aSPhil Shafer  * @param input Comma-separated set of option values
2279f2b7bf8aSPhil Shafer  * @return 0 on success, non-zero on failure
228031337658SMarcel Moolenaar  */
228131337658SMarcel Moolenaar int
228231337658SMarcel Moolenaar xo_set_options (xo_handle_t *xop, const char *input)
228331337658SMarcel Moolenaar {
228431337658SMarcel Moolenaar     char *cp, *ep, *vp, *np, *bp;
22858a6eceffSPhil Shafer     int style = -1, new_style, rc = 0;
22868a6eceffSPhil Shafer     ssize_t len;
228731337658SMarcel Moolenaar     xo_xof_flags_t new_flag;
228831337658SMarcel Moolenaar 
228931337658SMarcel Moolenaar     if (input == NULL)
229031337658SMarcel Moolenaar 	return 0;
229131337658SMarcel Moolenaar 
229231337658SMarcel Moolenaar     xop = xo_default(xop);
229331337658SMarcel Moolenaar 
2294788ca347SMarcel Moolenaar #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2295788ca347SMarcel Moolenaar     /* If the installer used --enable-color-on-by-default, then we allow it */
2296d1a0d267SMarcel Moolenaar     XOF_SET(xop, XOF_COLOR_ALLOWED);
2297788ca347SMarcel Moolenaar #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2298788ca347SMarcel Moolenaar 
229931337658SMarcel Moolenaar     /*
230031337658SMarcel Moolenaar      * We support a simpler, old-school style of giving option
230131337658SMarcel Moolenaar      * also, using a single character for each option.  It's
230231337658SMarcel Moolenaar      * ideal for lazy people, such as myself.
230331337658SMarcel Moolenaar      */
230431337658SMarcel Moolenaar     if (*input == ':') {
23058a6eceffSPhil Shafer 	ssize_t sz;
230631337658SMarcel Moolenaar 
230731337658SMarcel Moolenaar 	for (input++ ; *input; input++) {
230831337658SMarcel Moolenaar 	    switch (*input) {
2309788ca347SMarcel Moolenaar 	    case 'c':
2310d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_COLOR_ALLOWED);
2311788ca347SMarcel Moolenaar 		break;
2312788ca347SMarcel Moolenaar 
231331337658SMarcel Moolenaar 	    case 'f':
2314d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH);
231531337658SMarcel Moolenaar 		break;
231631337658SMarcel Moolenaar 
2317545ddfbeSMarcel Moolenaar 	    case 'F':
2318d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH_LINE);
2319d1a0d267SMarcel Moolenaar 		break;
2320d1a0d267SMarcel Moolenaar 
2321d1a0d267SMarcel Moolenaar 	    case 'g':
2322d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_LOG_GETTEXT);
2323545ddfbeSMarcel Moolenaar 		break;
2324545ddfbeSMarcel Moolenaar 
232531337658SMarcel Moolenaar 	    case 'H':
232631337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_HTML;
232731337658SMarcel Moolenaar 		break;
232831337658SMarcel Moolenaar 
232931337658SMarcel Moolenaar 	    case 'I':
2330d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_INFO);
233131337658SMarcel Moolenaar 		break;
233231337658SMarcel Moolenaar 
233331337658SMarcel Moolenaar 	    case 'i':
233431337658SMarcel Moolenaar 		sz = strspn(input + 1, "0123456789");
233531337658SMarcel Moolenaar 		if (sz > 0) {
233631337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(input + 1);
233731337658SMarcel Moolenaar 		    input += sz - 1;	/* Skip value */
233831337658SMarcel Moolenaar 		}
233931337658SMarcel Moolenaar 		break;
234031337658SMarcel Moolenaar 
234131337658SMarcel Moolenaar 	    case 'J':
234231337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_JSON;
234331337658SMarcel Moolenaar 		break;
234431337658SMarcel Moolenaar 
2345d1a0d267SMarcel Moolenaar 	    case 'k':
2346d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_KEYS);
2347d1a0d267SMarcel Moolenaar 		break;
2348d1a0d267SMarcel Moolenaar 
2349d1a0d267SMarcel Moolenaar 	    case 'n':
2350d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_NO_HUMANIZE);
2351d1a0d267SMarcel Moolenaar 		break;
2352d1a0d267SMarcel Moolenaar 
235331337658SMarcel Moolenaar 	    case 'P':
2354d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_PRETTY);
235531337658SMarcel Moolenaar 		break;
235631337658SMarcel Moolenaar 
235731337658SMarcel Moolenaar 	    case 'T':
235831337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_TEXT;
235931337658SMarcel Moolenaar 		break;
236031337658SMarcel Moolenaar 
236131337658SMarcel Moolenaar 	    case 'U':
2362d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNITS);
236331337658SMarcel Moolenaar 		break;
236431337658SMarcel Moolenaar 
236531337658SMarcel Moolenaar 	    case 'u':
2366d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNDERSCORES);
236731337658SMarcel Moolenaar 		break;
236831337658SMarcel Moolenaar 
236931337658SMarcel Moolenaar 	    case 'W':
2370d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_WARN);
237131337658SMarcel Moolenaar 		break;
237231337658SMarcel Moolenaar 
237331337658SMarcel Moolenaar 	    case 'X':
237431337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_XML;
237531337658SMarcel Moolenaar 		break;
237631337658SMarcel Moolenaar 
237731337658SMarcel Moolenaar 	    case 'x':
2378d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_XPATH);
237931337658SMarcel Moolenaar 		break;
238031337658SMarcel Moolenaar 	    }
238131337658SMarcel Moolenaar 	}
238231337658SMarcel Moolenaar 	return 0;
238331337658SMarcel Moolenaar     }
238431337658SMarcel Moolenaar 
238531337658SMarcel Moolenaar     len = strlen(input) + 1;
238631337658SMarcel Moolenaar     bp = alloca(len);
238731337658SMarcel Moolenaar     memcpy(bp, input, len);
238831337658SMarcel Moolenaar 
238931337658SMarcel Moolenaar     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
239031337658SMarcel Moolenaar 	np = strchr(cp, ',');
239131337658SMarcel Moolenaar 	if (np)
239231337658SMarcel Moolenaar 	    *np++ = '\0';
239331337658SMarcel Moolenaar 
239431337658SMarcel Moolenaar 	vp = strchr(cp, '=');
239531337658SMarcel Moolenaar 	if (vp)
239631337658SMarcel Moolenaar 	    *vp++ = '\0';
239731337658SMarcel Moolenaar 
2398788ca347SMarcel Moolenaar 	if (strcmp("colors", cp) == 0) {
2399f2b7bf8aSPhil Shafer 	    xo_set_color_map(xop, vp);
2400788ca347SMarcel Moolenaar 	    continue;
2401788ca347SMarcel Moolenaar 	}
2402788ca347SMarcel Moolenaar 
2403d1a0d267SMarcel Moolenaar 	/*
2404d1a0d267SMarcel Moolenaar 	 * For options, we don't allow "encoder" since we want to
2405d1a0d267SMarcel Moolenaar 	 * handle it explicitly below as "encoder=xxx".
2406d1a0d267SMarcel Moolenaar 	 */
240731337658SMarcel Moolenaar 	new_style = xo_name_to_style(cp);
2408d1a0d267SMarcel Moolenaar 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
240931337658SMarcel Moolenaar 	    if (style >= 0)
241031337658SMarcel Moolenaar 		xo_warnx("ignoring multiple styles: '%s'", cp);
241131337658SMarcel Moolenaar 	    else
241231337658SMarcel Moolenaar 		style = new_style;
241331337658SMarcel Moolenaar 	} else {
241431337658SMarcel Moolenaar 	    new_flag = xo_name_to_flag(cp);
241531337658SMarcel Moolenaar 	    if (new_flag != 0)
2416d1a0d267SMarcel Moolenaar 		XOF_SET(xop, new_flag);
2417f2b7bf8aSPhil Shafer 	    else if (strcmp(cp, "no-color") == 0)
2418d1a0d267SMarcel Moolenaar 		XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2419f2b7bf8aSPhil Shafer 	    else if (strcmp(cp, "indent") == 0) {
2420d1a0d267SMarcel Moolenaar 		if (vp)
242131337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(vp);
2422d1a0d267SMarcel Moolenaar 		else
2423d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "missing value for indent option");
2424d1a0d267SMarcel Moolenaar 	    } else if (strcmp(cp, "encoder") == 0) {
2425d1a0d267SMarcel Moolenaar 		if (vp == NULL)
2426d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "missing value for encoder option");
2427d1a0d267SMarcel Moolenaar 		else {
2428d1a0d267SMarcel Moolenaar 		    if (xo_encoder_init(xop, vp)) {
2429d1a0d267SMarcel Moolenaar 			xo_failure(xop, "encoder not found: %s", vp);
2430d1a0d267SMarcel Moolenaar 			rc = -1;
2431d1a0d267SMarcel Moolenaar 		    }
2432d1a0d267SMarcel Moolenaar 		}
2433d1a0d267SMarcel Moolenaar 
243431337658SMarcel Moolenaar 	    } else {
2435d1a0d267SMarcel Moolenaar 		xo_warnx("unknown libxo option value: '%s'", cp);
243631337658SMarcel Moolenaar 		rc = -1;
243731337658SMarcel Moolenaar 	    }
243831337658SMarcel Moolenaar 	}
243931337658SMarcel Moolenaar     }
244031337658SMarcel Moolenaar 
244131337658SMarcel Moolenaar     if (style > 0)
244231337658SMarcel Moolenaar 	xop->xo_style= style;
244331337658SMarcel Moolenaar 
244431337658SMarcel Moolenaar     return rc;
244531337658SMarcel Moolenaar }
244631337658SMarcel Moolenaar 
244731337658SMarcel Moolenaar /**
244831337658SMarcel Moolenaar  * Set one or more flags for a given handle (or default if handle is NULL).
244931337658SMarcel Moolenaar  * These flags will affect future output.
245031337658SMarcel Moolenaar  *
2451f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2452f2b7bf8aSPhil Shafer  * @param flags Flags to be set (XOF_*)
245331337658SMarcel Moolenaar  */
245431337658SMarcel Moolenaar void
245531337658SMarcel Moolenaar xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
245631337658SMarcel Moolenaar {
245731337658SMarcel Moolenaar     xop = xo_default(xop);
245831337658SMarcel Moolenaar 
2459d1a0d267SMarcel Moolenaar     XOF_SET(xop, flags);
246031337658SMarcel Moolenaar }
246131337658SMarcel Moolenaar 
2462f2b7bf8aSPhil Shafer /**
2463f2b7bf8aSPhil Shafer  * Accessor to return the current set of flags for a handle
2464f2b7bf8aSPhil Shafer  * @param xop XO handle
2465f2b7bf8aSPhil Shafer  * @return Current set of flags
2466f2b7bf8aSPhil Shafer  */
246731337658SMarcel Moolenaar xo_xof_flags_t
246831337658SMarcel Moolenaar xo_get_flags (xo_handle_t *xop)
246931337658SMarcel Moolenaar {
247031337658SMarcel Moolenaar     xop = xo_default(xop);
247131337658SMarcel Moolenaar 
247231337658SMarcel Moolenaar     return xop->xo_flags;
247331337658SMarcel Moolenaar }
247431337658SMarcel Moolenaar 
2475f2b7bf8aSPhil Shafer /**
2476f2b7bf8aSPhil Shafer  * strndup with a twist: len < 0 means len = strlen(str)
2477d1a0d267SMarcel Moolenaar  */
2478d1a0d267SMarcel Moolenaar static char *
24798a6eceffSPhil Shafer xo_strndup (const char *str, ssize_t len)
2480d1a0d267SMarcel Moolenaar {
2481d1a0d267SMarcel Moolenaar     if (len < 0)
2482d1a0d267SMarcel Moolenaar 	len = strlen(str);
2483d1a0d267SMarcel Moolenaar 
2484d1a0d267SMarcel Moolenaar     char *cp = xo_realloc(NULL, len + 1);
2485d1a0d267SMarcel Moolenaar     if (cp) {
2486d1a0d267SMarcel Moolenaar 	memcpy(cp, str, len);
2487d1a0d267SMarcel Moolenaar 	cp[len] = '\0';
2488d1a0d267SMarcel Moolenaar     }
2489d1a0d267SMarcel Moolenaar 
2490d1a0d267SMarcel Moolenaar     return cp;
2491d1a0d267SMarcel Moolenaar }
2492d1a0d267SMarcel Moolenaar 
249331337658SMarcel Moolenaar /**
249431337658SMarcel Moolenaar  * Record a leading prefix for the XPath we generate.  This allows the
249531337658SMarcel Moolenaar  * generated data to be placed within an XML hierarchy but still have
249631337658SMarcel Moolenaar  * accurate XPath expressions.
249731337658SMarcel Moolenaar  *
2498f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2499f2b7bf8aSPhil Shafer  * @param path The XPath expression
250031337658SMarcel Moolenaar  */
250131337658SMarcel Moolenaar void
250231337658SMarcel Moolenaar xo_set_leading_xpath (xo_handle_t *xop, const char *path)
250331337658SMarcel Moolenaar {
250431337658SMarcel Moolenaar     xop = xo_default(xop);
250531337658SMarcel Moolenaar 
250631337658SMarcel Moolenaar     if (xop->xo_leading_xpath) {
250731337658SMarcel Moolenaar 	xo_free(xop->xo_leading_xpath);
250831337658SMarcel Moolenaar 	xop->xo_leading_xpath = NULL;
250931337658SMarcel Moolenaar     }
251031337658SMarcel Moolenaar 
251131337658SMarcel Moolenaar     if (path == NULL)
251231337658SMarcel Moolenaar 	return;
251331337658SMarcel Moolenaar 
2514d1a0d267SMarcel Moolenaar     xop->xo_leading_xpath = xo_strndup(path, -1);
251531337658SMarcel Moolenaar }
251631337658SMarcel Moolenaar 
251731337658SMarcel Moolenaar /**
251831337658SMarcel Moolenaar  * Record the info data for a set of tags
251931337658SMarcel Moolenaar  *
2520f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2521f2b7bf8aSPhil Shafer  * @param info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
2522f2b7bf8aSPhil Shafer  * @pararm count Number of entries in info (or -1 to count them ourselves)
252331337658SMarcel Moolenaar  */
252431337658SMarcel Moolenaar void
252531337658SMarcel Moolenaar xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
252631337658SMarcel Moolenaar {
252731337658SMarcel Moolenaar     xop = xo_default(xop);
252831337658SMarcel Moolenaar 
252931337658SMarcel Moolenaar     if (count < 0 && infop) {
253031337658SMarcel Moolenaar 	xo_info_t *xip;
253131337658SMarcel Moolenaar 
253231337658SMarcel Moolenaar 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
253331337658SMarcel Moolenaar 	    continue;
253431337658SMarcel Moolenaar     }
253531337658SMarcel Moolenaar 
253631337658SMarcel Moolenaar     xop->xo_info = infop;
253731337658SMarcel Moolenaar     xop->xo_info_count = count;
253831337658SMarcel Moolenaar }
253931337658SMarcel Moolenaar 
254031337658SMarcel Moolenaar /**
254131337658SMarcel Moolenaar  * Set the formatter callback for a handle.  The callback should
254231337658SMarcel Moolenaar  * return a newly formatting contents of a formatting instruction,
254331337658SMarcel Moolenaar  * meaning the bits inside the braces.
254431337658SMarcel Moolenaar  */
254531337658SMarcel Moolenaar void
254631337658SMarcel Moolenaar xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
254731337658SMarcel Moolenaar 		  xo_checkpointer_t cfunc)
254831337658SMarcel Moolenaar {
254931337658SMarcel Moolenaar     xop = xo_default(xop);
255031337658SMarcel Moolenaar 
255131337658SMarcel Moolenaar     xop->xo_formatter = func;
255231337658SMarcel Moolenaar     xop->xo_checkpointer = cfunc;
255331337658SMarcel Moolenaar }
255431337658SMarcel Moolenaar 
255531337658SMarcel Moolenaar /**
255631337658SMarcel Moolenaar  * Clear one or more flags for a given handle (or default if handle is NULL).
255731337658SMarcel Moolenaar  * These flags will affect future output.
255831337658SMarcel Moolenaar  *
2559f2b7bf8aSPhil Shafer  * @param xop XO handle to alter (or NULL for default handle)
2560f2b7bf8aSPhil Shafer  * @param flags Flags to be cleared (XOF_*)
256131337658SMarcel Moolenaar  */
256231337658SMarcel Moolenaar void
256331337658SMarcel Moolenaar xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
256431337658SMarcel Moolenaar {
256531337658SMarcel Moolenaar     xop = xo_default(xop);
256631337658SMarcel Moolenaar 
2567d1a0d267SMarcel Moolenaar     XOF_CLEAR(xop, flags);
256831337658SMarcel Moolenaar }
256931337658SMarcel Moolenaar 
2570545ddfbeSMarcel Moolenaar static const char *
2571545ddfbeSMarcel Moolenaar xo_state_name (xo_state_t state)
2572545ddfbeSMarcel Moolenaar {
2573545ddfbeSMarcel Moolenaar     static const char *names[] = {
2574545ddfbeSMarcel Moolenaar 	"init",
2575545ddfbeSMarcel Moolenaar 	"open_container",
2576545ddfbeSMarcel Moolenaar 	"close_container",
2577545ddfbeSMarcel Moolenaar 	"open_list",
2578545ddfbeSMarcel Moolenaar 	"close_list",
2579545ddfbeSMarcel Moolenaar 	"open_instance",
2580545ddfbeSMarcel Moolenaar 	"close_instance",
2581545ddfbeSMarcel Moolenaar 	"open_leaf_list",
2582545ddfbeSMarcel Moolenaar 	"close_leaf_list",
2583545ddfbeSMarcel Moolenaar 	"discarding",
2584545ddfbeSMarcel Moolenaar 	"marker",
2585545ddfbeSMarcel Moolenaar 	"emit",
2586545ddfbeSMarcel Moolenaar 	"emit_leaf_list",
2587545ddfbeSMarcel Moolenaar 	"finish",
2588545ddfbeSMarcel Moolenaar 	NULL
2589545ddfbeSMarcel Moolenaar     };
2590545ddfbeSMarcel Moolenaar 
2591545ddfbeSMarcel Moolenaar     if (state < (sizeof(names) / sizeof(names[0])))
2592545ddfbeSMarcel Moolenaar 	return names[state];
2593545ddfbeSMarcel Moolenaar 
2594545ddfbeSMarcel Moolenaar     return "unknown";
2595545ddfbeSMarcel Moolenaar }
2596545ddfbeSMarcel Moolenaar 
259731337658SMarcel Moolenaar static void
259831337658SMarcel Moolenaar xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
259931337658SMarcel Moolenaar {
260031337658SMarcel Moolenaar     static char div_open[] = "<div class=\"line\">";
260131337658SMarcel Moolenaar     static char div_open_blank[] = "<div class=\"blank-line\">";
260231337658SMarcel Moolenaar 
2603d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
260431337658SMarcel Moolenaar 	return;
260531337658SMarcel Moolenaar 
2606788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_HTML)
260731337658SMarcel Moolenaar 	return;
260831337658SMarcel Moolenaar 
2609d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_DIV_OPEN);
261031337658SMarcel Moolenaar     if (flags & XFF_BLANK_LINE)
261131337658SMarcel Moolenaar 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
261231337658SMarcel Moolenaar     else
261331337658SMarcel Moolenaar 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
261431337658SMarcel Moolenaar 
2615d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
261631337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
261731337658SMarcel Moolenaar }
261831337658SMarcel Moolenaar 
261931337658SMarcel Moolenaar static void
262031337658SMarcel Moolenaar xo_line_close (xo_handle_t *xop)
262131337658SMarcel Moolenaar {
262231337658SMarcel Moolenaar     static char div_close[] = "</div>";
262331337658SMarcel Moolenaar 
2624788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
262531337658SMarcel Moolenaar     case XO_STYLE_HTML:
2626d1a0d267SMarcel Moolenaar 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
262731337658SMarcel Moolenaar 	    xo_line_ensure_open(xop, 0);
262831337658SMarcel Moolenaar 
2629d1a0d267SMarcel Moolenaar 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
263031337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
263131337658SMarcel Moolenaar 
2632d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
263331337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
263431337658SMarcel Moolenaar 	break;
263531337658SMarcel Moolenaar 
263631337658SMarcel Moolenaar     case XO_STYLE_TEXT:
263731337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
263831337658SMarcel Moolenaar 	break;
263931337658SMarcel Moolenaar     }
264031337658SMarcel Moolenaar }
264131337658SMarcel Moolenaar 
264231337658SMarcel Moolenaar static int
264331337658SMarcel Moolenaar xo_info_compare (const void *key, const void *data)
264431337658SMarcel Moolenaar {
264531337658SMarcel Moolenaar     const char *name = key;
264631337658SMarcel Moolenaar     const xo_info_t *xip = data;
264731337658SMarcel Moolenaar 
264831337658SMarcel Moolenaar     return strcmp(name, xip->xi_name);
264931337658SMarcel Moolenaar }
265031337658SMarcel Moolenaar 
265131337658SMarcel Moolenaar 
265231337658SMarcel Moolenaar static xo_info_t *
26538a6eceffSPhil Shafer xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen)
265431337658SMarcel Moolenaar {
265531337658SMarcel Moolenaar     xo_info_t *xip;
265631337658SMarcel Moolenaar     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
265731337658SMarcel Moolenaar 
265831337658SMarcel Moolenaar     memcpy(cp, name, nlen);
265931337658SMarcel Moolenaar     cp[nlen] = '\0';
266031337658SMarcel Moolenaar 
266131337658SMarcel Moolenaar     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
266231337658SMarcel Moolenaar 		  sizeof(xop->xo_info[0]), xo_info_compare);
266331337658SMarcel Moolenaar     return xip;
266431337658SMarcel Moolenaar }
266531337658SMarcel Moolenaar 
266631337658SMarcel Moolenaar #define CONVERT(_have, _need) (((_have) << 8) | (_need))
266731337658SMarcel Moolenaar 
266831337658SMarcel Moolenaar /*
266931337658SMarcel Moolenaar  * Check to see that the conversion is safe and sane.
267031337658SMarcel Moolenaar  */
267131337658SMarcel Moolenaar static int
267231337658SMarcel Moolenaar xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
267331337658SMarcel Moolenaar {
267431337658SMarcel Moolenaar     switch (CONVERT(have_enc, need_enc)) {
267531337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
267631337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
267731337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
267831337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
267931337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
268031337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
268131337658SMarcel Moolenaar 	return 0;
268231337658SMarcel Moolenaar 
268331337658SMarcel Moolenaar     default:
268431337658SMarcel Moolenaar 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
268531337658SMarcel Moolenaar 	return 1;
268631337658SMarcel Moolenaar     }
268731337658SMarcel Moolenaar }
268831337658SMarcel Moolenaar 
268931337658SMarcel Moolenaar static int
269031337658SMarcel Moolenaar xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
269131337658SMarcel Moolenaar 			 xo_xff_flags_t flags,
26928a6eceffSPhil Shafer 			 const wchar_t *wcp, const char *cp,
26938a6eceffSPhil Shafer 			 ssize_t len, int max,
269431337658SMarcel Moolenaar 			 int need_enc, int have_enc)
269531337658SMarcel Moolenaar {
269631337658SMarcel Moolenaar     int cols = 0;
2697c600d307SMarcel Moolenaar     wchar_t wc = 0;
26988a6eceffSPhil Shafer     ssize_t ilen, olen;
26998a6eceffSPhil Shafer     ssize_t width;
27008a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
270131337658SMarcel Moolenaar     const char *sp;
270231337658SMarcel Moolenaar 
270331337658SMarcel Moolenaar     if (len > 0 && !xo_buf_has_room(xbp, len))
270431337658SMarcel Moolenaar 	return 0;
270531337658SMarcel Moolenaar 
270631337658SMarcel Moolenaar     for (;;) {
270731337658SMarcel Moolenaar 	if (len == 0)
270831337658SMarcel Moolenaar 	    break;
270931337658SMarcel Moolenaar 
271031337658SMarcel Moolenaar 	if (cp) {
271131337658SMarcel Moolenaar 	    if (*cp == '\0')
271231337658SMarcel Moolenaar 		break;
271331337658SMarcel Moolenaar 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
271431337658SMarcel Moolenaar 		cp += 1;
271531337658SMarcel Moolenaar 		len -= 1;
2716264104f2SPhil Shafer 		if (len == 0 || *cp == '\0')
2717264104f2SPhil Shafer 		    break;
271831337658SMarcel Moolenaar 	    }
271931337658SMarcel Moolenaar 	}
272031337658SMarcel Moolenaar 
272131337658SMarcel Moolenaar 	if (wcp && *wcp == L'\0')
272231337658SMarcel Moolenaar 	    break;
272331337658SMarcel Moolenaar 
272431337658SMarcel Moolenaar 	ilen = 0;
272531337658SMarcel Moolenaar 
272631337658SMarcel Moolenaar 	switch (have_enc) {
272731337658SMarcel Moolenaar 	case XF_ENC_WIDE:		/* Wide character */
272831337658SMarcel Moolenaar 	    wc = *wcp++;
272931337658SMarcel Moolenaar 	    ilen = 1;
273031337658SMarcel Moolenaar 	    break;
273131337658SMarcel Moolenaar 
273231337658SMarcel Moolenaar 	case XF_ENC_UTF8:		/* UTF-8 */
273331337658SMarcel Moolenaar 	    ilen = xo_utf8_to_wc_len(cp);
273431337658SMarcel Moolenaar 	    if (ilen < 0) {
273531337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2736d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
273731337658SMarcel Moolenaar 	    }
273831337658SMarcel Moolenaar 
273931337658SMarcel Moolenaar 	    if (len > 0 && len < ilen) {
274031337658SMarcel Moolenaar 		len = 0;	/* Break out of the loop */
274131337658SMarcel Moolenaar 		continue;
274231337658SMarcel Moolenaar 	    }
274331337658SMarcel Moolenaar 
274431337658SMarcel Moolenaar 	    wc = xo_utf8_char(cp, ilen);
274531337658SMarcel Moolenaar 	    if (wc == (wchar_t) -1) {
274631337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
274731337658SMarcel Moolenaar 			   *cp, ilen);
2748d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
274931337658SMarcel Moolenaar 	    }
275031337658SMarcel Moolenaar 	    cp += ilen;
275131337658SMarcel Moolenaar 	    break;
275231337658SMarcel Moolenaar 
275331337658SMarcel Moolenaar 	case XF_ENC_LOCALE:		/* Native locale */
275431337658SMarcel Moolenaar 	    ilen = (len > 0) ? len : MB_LEN_MAX;
275531337658SMarcel Moolenaar 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
275631337658SMarcel Moolenaar 	    if (ilen < 0) {		/* Invalid data; skip */
275731337658SMarcel Moolenaar 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2758dbf26257SAlexander Kabaev 		wc = L'?';
2759dbf26257SAlexander Kabaev 		ilen = 1;
276031337658SMarcel Moolenaar 	    }
2761d1a0d267SMarcel Moolenaar 
276231337658SMarcel Moolenaar 	    if (ilen == 0) {		/* Hit a wide NUL character */
276331337658SMarcel Moolenaar 		len = 0;
276431337658SMarcel Moolenaar 		continue;
276531337658SMarcel Moolenaar 	    }
276631337658SMarcel Moolenaar 
276731337658SMarcel Moolenaar 	    cp += ilen;
276831337658SMarcel Moolenaar 	    break;
276931337658SMarcel Moolenaar 	}
277031337658SMarcel Moolenaar 
277131337658SMarcel Moolenaar 	/* Reduce len, but not below zero */
277231337658SMarcel Moolenaar 	if (len > 0) {
277331337658SMarcel Moolenaar 	    len -= ilen;
277431337658SMarcel Moolenaar 	    if (len < 0)
277531337658SMarcel Moolenaar 		len = 0;
277631337658SMarcel Moolenaar 	}
277731337658SMarcel Moolenaar 
277831337658SMarcel Moolenaar 	/*
277931337658SMarcel Moolenaar 	 * Find the width-in-columns of this character, which must be done
278031337658SMarcel Moolenaar 	 * in wide characters, since we lack a mbswidth() function.  If
278131337658SMarcel Moolenaar 	 * it doesn't fit
278231337658SMarcel Moolenaar 	 */
2783d1a0d267SMarcel Moolenaar 	width = xo_wcwidth(wc);
278431337658SMarcel Moolenaar 	if (width < 0)
278531337658SMarcel Moolenaar 	    width = iswcntrl(wc) ? 0 : 1;
278631337658SMarcel Moolenaar 
2787788ca347SMarcel Moolenaar 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
278831337658SMarcel Moolenaar 	    if (max > 0 && cols + width > max)
278931337658SMarcel Moolenaar 		break;
279031337658SMarcel Moolenaar 	}
279131337658SMarcel Moolenaar 
279231337658SMarcel Moolenaar 	switch (need_enc) {
279331337658SMarcel Moolenaar 	case XF_ENC_UTF8:
279431337658SMarcel Moolenaar 
279531337658SMarcel Moolenaar 	    /* Output in UTF-8 needs to be escaped, based on the style */
2796788ca347SMarcel Moolenaar 	    switch (xo_style(xop)) {
279731337658SMarcel Moolenaar 	    case XO_STYLE_XML:
279831337658SMarcel Moolenaar 	    case XO_STYLE_HTML:
279931337658SMarcel Moolenaar 		if (wc == '<')
280031337658SMarcel Moolenaar 		    sp = xo_xml_lt;
280131337658SMarcel Moolenaar 		else if (wc == '>')
280231337658SMarcel Moolenaar 		    sp = xo_xml_gt;
280331337658SMarcel Moolenaar 		else if (wc == '&')
280431337658SMarcel Moolenaar 		    sp = xo_xml_amp;
280531337658SMarcel Moolenaar 		else if (attr && wc == '"')
280631337658SMarcel Moolenaar 		    sp = xo_xml_quot;
280731337658SMarcel Moolenaar 		else
280831337658SMarcel Moolenaar 		    break;
280931337658SMarcel Moolenaar 
28108a6eceffSPhil Shafer 		ssize_t slen = strlen(sp);
281131337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, slen - 1))
281231337658SMarcel Moolenaar 		    return -1;
281331337658SMarcel Moolenaar 
281431337658SMarcel Moolenaar 		memcpy(xbp->xb_curp, sp, slen);
281531337658SMarcel Moolenaar 		xbp->xb_curp += slen;
281631337658SMarcel Moolenaar 		goto done_with_encoding; /* Need multi-level 'break' */
281731337658SMarcel Moolenaar 
281831337658SMarcel Moolenaar 	    case XO_STYLE_JSON:
2819545ddfbeSMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
282031337658SMarcel Moolenaar 		    break;
282131337658SMarcel Moolenaar 
282231337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
282331337658SMarcel Moolenaar 		    return -1;
282431337658SMarcel Moolenaar 
282531337658SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2826545ddfbeSMarcel Moolenaar 		if (wc == '\n')
2827545ddfbeSMarcel Moolenaar 		    wc = 'n';
2828545ddfbeSMarcel Moolenaar 		else if (wc == '\r')
2829545ddfbeSMarcel Moolenaar 		    wc = 'r';
2830545ddfbeSMarcel Moolenaar 		else wc = wc & 0x7f;
2831545ddfbeSMarcel Moolenaar 
2832545ddfbeSMarcel Moolenaar 		*xbp->xb_curp++ = wc;
283331337658SMarcel Moolenaar 		goto done_with_encoding;
2834d1a0d267SMarcel Moolenaar 
2835d1a0d267SMarcel Moolenaar 	    case XO_STYLE_SDPARAMS:
2836d1a0d267SMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != ']')
2837d1a0d267SMarcel Moolenaar 		    break;
2838d1a0d267SMarcel Moolenaar 
2839d1a0d267SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
2840d1a0d267SMarcel Moolenaar 		    return -1;
2841d1a0d267SMarcel Moolenaar 
2842d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2843d1a0d267SMarcel Moolenaar 		wc = wc & 0x7f;
2844d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = wc;
2845d1a0d267SMarcel Moolenaar 		goto done_with_encoding;
284631337658SMarcel Moolenaar 	    }
284731337658SMarcel Moolenaar 
284831337658SMarcel Moolenaar 	    olen = xo_utf8_emit_len(wc);
284931337658SMarcel Moolenaar 	    if (olen < 0) {
285031337658SMarcel Moolenaar 		xo_failure(xop, "ignoring bad length");
285131337658SMarcel Moolenaar 		continue;
285231337658SMarcel Moolenaar 	    }
285331337658SMarcel Moolenaar 
285431337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, olen))
285531337658SMarcel Moolenaar 		return -1;
285631337658SMarcel Moolenaar 
285731337658SMarcel Moolenaar 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
285831337658SMarcel Moolenaar 	    xbp->xb_curp += olen;
285931337658SMarcel Moolenaar 	    break;
286031337658SMarcel Moolenaar 
286131337658SMarcel Moolenaar 	case XF_ENC_LOCALE:
286231337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
286331337658SMarcel Moolenaar 		return -1;
286431337658SMarcel Moolenaar 
286531337658SMarcel Moolenaar 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
286631337658SMarcel Moolenaar 	    if (olen <= 0) {
286731337658SMarcel Moolenaar 		xo_failure(xop, "could not convert wide char: %lx",
286831337658SMarcel Moolenaar 			   (unsigned long) wc);
286931337658SMarcel Moolenaar 		width = 1;
287031337658SMarcel Moolenaar 		*xbp->xb_curp++ = '?';
287131337658SMarcel Moolenaar 	    } else
287231337658SMarcel Moolenaar 		xbp->xb_curp += olen;
287331337658SMarcel Moolenaar 	    break;
287431337658SMarcel Moolenaar 	}
287531337658SMarcel Moolenaar 
287631337658SMarcel Moolenaar     done_with_encoding:
287731337658SMarcel Moolenaar 	cols += width;
287831337658SMarcel Moolenaar     }
287931337658SMarcel Moolenaar 
288031337658SMarcel Moolenaar     return cols;
288131337658SMarcel Moolenaar }
288231337658SMarcel Moolenaar 
288331337658SMarcel Moolenaar static int
2884d1a0d267SMarcel Moolenaar xo_needed_encoding (xo_handle_t *xop)
2885d1a0d267SMarcel Moolenaar {
2886d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2887d1a0d267SMarcel Moolenaar 	return XF_ENC_UTF8;
2888d1a0d267SMarcel Moolenaar 
2889d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2890d1a0d267SMarcel Moolenaar 	return XF_ENC_LOCALE;
2891d1a0d267SMarcel Moolenaar 
2892d1a0d267SMarcel Moolenaar     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2893d1a0d267SMarcel Moolenaar }
2894d1a0d267SMarcel Moolenaar 
28958a6eceffSPhil Shafer static ssize_t
289631337658SMarcel Moolenaar xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
289731337658SMarcel Moolenaar 		  xo_format_t *xfp)
289831337658SMarcel Moolenaar {
289931337658SMarcel Moolenaar     static char null[] = "(null)";
2900d1a0d267SMarcel Moolenaar     static char null_no_quotes[] = "null";
2901a0f704ffSMarcel Moolenaar 
290231337658SMarcel Moolenaar     char *cp = NULL;
290331337658SMarcel Moolenaar     wchar_t *wcp = NULL;
29048a6eceffSPhil Shafer     ssize_t len;
29058a6eceffSPhil Shafer     ssize_t cols = 0, rc = 0;
29068a6eceffSPhil Shafer     ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2;
2907d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
290831337658SMarcel Moolenaar 
290931337658SMarcel Moolenaar     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
291031337658SMarcel Moolenaar 	return 0;
291131337658SMarcel Moolenaar 
2912a0f704ffSMarcel Moolenaar     len = xfp->xf_width[XF_WIDTH_SIZE];
2913a0f704ffSMarcel Moolenaar 
2914d1a0d267SMarcel Moolenaar     if (xfp->xf_fc == 'm') {
2915d1a0d267SMarcel Moolenaar 	cp = strerror(xop->xo_errno);
2916d1a0d267SMarcel Moolenaar 	if (len < 0)
2917d1a0d267SMarcel Moolenaar 	    len = cp ? strlen(cp) : 0;
2918d1a0d267SMarcel Moolenaar 	goto normal_string;
2919d1a0d267SMarcel Moolenaar 
2920d1a0d267SMarcel Moolenaar     } else if (xfp->xf_enc == XF_ENC_WIDE) {
292131337658SMarcel Moolenaar 	wcp = va_arg(xop->xo_vap, wchar_t *);
292231337658SMarcel Moolenaar 	if (xfp->xf_skip)
292331337658SMarcel Moolenaar 	    return 0;
292431337658SMarcel Moolenaar 
2925a0f704ffSMarcel Moolenaar 	/*
2926a0f704ffSMarcel Moolenaar 	 * Dont' deref NULL; use the traditional "(null)" instead
2927a0f704ffSMarcel Moolenaar 	 * of the more accurate "who's been a naughty boy, then?".
2928a0f704ffSMarcel Moolenaar 	 */
2929a0f704ffSMarcel Moolenaar 	if (wcp == NULL) {
2930a0f704ffSMarcel Moolenaar 	    cp = null;
2931a0f704ffSMarcel Moolenaar 	    len = sizeof(null) - 1;
2932a0f704ffSMarcel Moolenaar 	}
2933a0f704ffSMarcel Moolenaar 
293431337658SMarcel Moolenaar     } else {
293531337658SMarcel Moolenaar 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2936d1a0d267SMarcel Moolenaar 
2937d1a0d267SMarcel Moolenaar     normal_string:
293831337658SMarcel Moolenaar 	if (xfp->xf_skip)
293931337658SMarcel Moolenaar 	    return 0;
294031337658SMarcel Moolenaar 
2941a0f704ffSMarcel Moolenaar 	/* Echo "Dont' deref NULL" logic */
2942a0f704ffSMarcel Moolenaar 	if (cp == NULL) {
2943d1a0d267SMarcel Moolenaar 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2944d1a0d267SMarcel Moolenaar 		cp = null_no_quotes;
2945d1a0d267SMarcel Moolenaar 		len = sizeof(null_no_quotes) - 1;
2946d1a0d267SMarcel Moolenaar 	    } else {
2947a0f704ffSMarcel Moolenaar 		cp = null;
2948a0f704ffSMarcel Moolenaar 		len = sizeof(null) - 1;
2949a0f704ffSMarcel Moolenaar 	    }
2950d1a0d267SMarcel Moolenaar 	}
2951a0f704ffSMarcel Moolenaar 
295231337658SMarcel Moolenaar 	/*
295331337658SMarcel Moolenaar 	 * Optimize the most common case, which is "%s".  We just
295431337658SMarcel Moolenaar 	 * need to copy the complete string to the output buffer.
295531337658SMarcel Moolenaar 	 */
295631337658SMarcel Moolenaar 	if (xfp->xf_enc == need_enc
295731337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
295831337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
295931337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2960d1a0d267SMarcel Moolenaar 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2961d1a0d267SMarcel Moolenaar 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
296231337658SMarcel Moolenaar 	    len = strlen(cp);
296331337658SMarcel Moolenaar 	    xo_buf_escape(xop, xbp, cp, len, flags);
296431337658SMarcel Moolenaar 
296531337658SMarcel Moolenaar 	    /*
296631337658SMarcel Moolenaar 	     * Our caller expects xb_curp left untouched, so we have
296731337658SMarcel Moolenaar 	     * to reset it and return the number of bytes written to
296831337658SMarcel Moolenaar 	     * the buffer.
296931337658SMarcel Moolenaar 	     */
297031337658SMarcel Moolenaar 	    off2 = xbp->xb_curp - xbp->xb_bufp;
297131337658SMarcel Moolenaar 	    rc = off2 - off;
297231337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + off;
297331337658SMarcel Moolenaar 
297431337658SMarcel Moolenaar 	    return rc;
297531337658SMarcel Moolenaar 	}
297631337658SMarcel Moolenaar     }
297731337658SMarcel Moolenaar 
297831337658SMarcel Moolenaar     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
297931337658SMarcel Moolenaar 				   xfp->xf_width[XF_WIDTH_MAX],
298031337658SMarcel Moolenaar 				   need_enc, xfp->xf_enc);
298131337658SMarcel Moolenaar     if (cols < 0)
298231337658SMarcel Moolenaar 	goto bail;
298331337658SMarcel Moolenaar 
298431337658SMarcel Moolenaar     /*
298531337658SMarcel Moolenaar      * xo_buf_append* will move xb_curp, so we save/restore it.
298631337658SMarcel Moolenaar      */
298731337658SMarcel Moolenaar     off2 = xbp->xb_curp - xbp->xb_bufp;
298831337658SMarcel Moolenaar     rc = off2 - off;
298931337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
299031337658SMarcel Moolenaar 
299131337658SMarcel Moolenaar     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
299231337658SMarcel Moolenaar 	/*
299331337658SMarcel Moolenaar 	 * Find the number of columns needed to display the string.
299431337658SMarcel Moolenaar 	 * If we have the original wide string, we just call wcswidth,
299531337658SMarcel Moolenaar 	 * but if we did the work ourselves, then we need to do it.
299631337658SMarcel Moolenaar 	 */
299731337658SMarcel Moolenaar 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2998ee5cf116SPhil Shafer 	if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN]))
299931337658SMarcel Moolenaar 	    goto bail;
300031337658SMarcel Moolenaar 
300131337658SMarcel Moolenaar 	/*
300231337658SMarcel Moolenaar 	 * If seen_minus, then pad on the right; otherwise move it so
300331337658SMarcel Moolenaar 	 * we can pad on the left.
300431337658SMarcel Moolenaar 	 */
300531337658SMarcel Moolenaar 	if (xfp->xf_seen_minus) {
300631337658SMarcel Moolenaar 	    cp = xbp->xb_curp + rc;
300731337658SMarcel Moolenaar 	} else {
300831337658SMarcel Moolenaar 	    cp = xbp->xb_curp;
300931337658SMarcel Moolenaar 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
301031337658SMarcel Moolenaar 	}
301131337658SMarcel Moolenaar 
301231337658SMarcel Moolenaar 	/* Set the padding */
301331337658SMarcel Moolenaar 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
301431337658SMarcel Moolenaar 	rc += delta;
301531337658SMarcel Moolenaar 	cols += delta;
301631337658SMarcel Moolenaar     }
301731337658SMarcel Moolenaar 
3018d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
301931337658SMarcel Moolenaar 	xop->xo_columns += cols;
3020d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
302131337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
302231337658SMarcel Moolenaar 
302331337658SMarcel Moolenaar     return rc;
302431337658SMarcel Moolenaar 
302531337658SMarcel Moolenaar  bail:
302631337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
302731337658SMarcel Moolenaar     return 0;
302831337658SMarcel Moolenaar }
302931337658SMarcel Moolenaar 
3030d1a0d267SMarcel Moolenaar /*
3031d1a0d267SMarcel Moolenaar  * Look backwards in a buffer to find a numeric value
3032d1a0d267SMarcel Moolenaar  */
3033d1a0d267SMarcel Moolenaar static int
30348a6eceffSPhil Shafer xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset)
3035d1a0d267SMarcel Moolenaar {
3036d1a0d267SMarcel Moolenaar     int rc = 0;			/* Fail with zero */
3037d1a0d267SMarcel Moolenaar     int digit = 1;
3038d1a0d267SMarcel Moolenaar     char *sp = xbp->xb_bufp;
3039d1a0d267SMarcel Moolenaar     char *cp = sp + start_offset;
3040d1a0d267SMarcel Moolenaar 
3041d1a0d267SMarcel Moolenaar     while (--cp >= sp)
3042d1a0d267SMarcel Moolenaar 	if (isdigit((int) *cp))
3043d1a0d267SMarcel Moolenaar 	    break;
3044d1a0d267SMarcel Moolenaar 
3045d1a0d267SMarcel Moolenaar     for ( ; cp >= sp; cp--) {
3046d1a0d267SMarcel Moolenaar 	if (!isdigit((int) *cp))
3047d1a0d267SMarcel Moolenaar 	    break;
3048d1a0d267SMarcel Moolenaar 	rc += (*cp - '0') * digit;
3049d1a0d267SMarcel Moolenaar 	digit *= 10;
3050d1a0d267SMarcel Moolenaar     }
3051d1a0d267SMarcel Moolenaar 
3052d1a0d267SMarcel Moolenaar     return rc;
3053d1a0d267SMarcel Moolenaar }
3054d1a0d267SMarcel Moolenaar 
30558a6eceffSPhil Shafer static ssize_t
30568a6eceffSPhil Shafer xo_count_utf8_cols (const char *str, ssize_t len)
3057d1a0d267SMarcel Moolenaar {
30588a6eceffSPhil Shafer     ssize_t tlen;
3059d1a0d267SMarcel Moolenaar     wchar_t wc;
30608a6eceffSPhil Shafer     ssize_t cols = 0;
3061d1a0d267SMarcel Moolenaar     const char *ep = str + len;
3062d1a0d267SMarcel Moolenaar 
3063d1a0d267SMarcel Moolenaar     while (str < ep) {
3064d1a0d267SMarcel Moolenaar 	tlen = xo_utf8_to_wc_len(str);
3065d1a0d267SMarcel Moolenaar 	if (tlen < 0)		/* Broken input is very bad */
3066d1a0d267SMarcel Moolenaar 	    return cols;
3067d1a0d267SMarcel Moolenaar 
3068d1a0d267SMarcel Moolenaar 	wc = xo_utf8_char(str, tlen);
3069d1a0d267SMarcel Moolenaar 	if (wc == (wchar_t) -1)
3070d1a0d267SMarcel Moolenaar 	    return cols;
3071d1a0d267SMarcel Moolenaar 
3072d1a0d267SMarcel Moolenaar 	/* We only print printable characters */
3073d1a0d267SMarcel Moolenaar 	if (iswprint((wint_t) wc)) {
3074d1a0d267SMarcel Moolenaar 	    /*
3075d1a0d267SMarcel Moolenaar 	     * Find the width-in-columns of this character, which must be done
3076d1a0d267SMarcel Moolenaar 	     * in wide characters, since we lack a mbswidth() function.
3077d1a0d267SMarcel Moolenaar 	     */
30788a6eceffSPhil Shafer 	    ssize_t width = xo_wcwidth(wc);
3079d1a0d267SMarcel Moolenaar 	    if (width < 0)
3080d1a0d267SMarcel Moolenaar 		width = iswcntrl(wc) ? 0 : 1;
3081d1a0d267SMarcel Moolenaar 
3082d1a0d267SMarcel Moolenaar 	    cols += width;
3083d1a0d267SMarcel Moolenaar 	}
3084d1a0d267SMarcel Moolenaar 
3085d1a0d267SMarcel Moolenaar 	str += tlen;
3086d1a0d267SMarcel Moolenaar     }
3087d1a0d267SMarcel Moolenaar 
3088d1a0d267SMarcel Moolenaar     return cols;
3089d1a0d267SMarcel Moolenaar }
3090d1a0d267SMarcel Moolenaar 
3091d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
3092d1a0d267SMarcel Moolenaar static inline const char *
3093d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop, const char *str)
3094d1a0d267SMarcel Moolenaar {
3095d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
3096d1a0d267SMarcel Moolenaar     const char *res;
3097d1a0d267SMarcel Moolenaar 
3098d1a0d267SMarcel Moolenaar     res = dgettext(domainname, str);
3099d1a0d267SMarcel Moolenaar 
3100d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3101d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
3102d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "", xo_printable(domainname),
3103d1a0d267SMarcel Moolenaar 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
3104d1a0d267SMarcel Moolenaar 
3105d1a0d267SMarcel Moolenaar     return res;
3106d1a0d267SMarcel Moolenaar }
3107d1a0d267SMarcel Moolenaar 
3108d1a0d267SMarcel Moolenaar static inline const char *
3109d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
3110d1a0d267SMarcel Moolenaar 	      unsigned long int n)
3111d1a0d267SMarcel Moolenaar {
3112d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
3113d1a0d267SMarcel Moolenaar     const char *res;
3114d1a0d267SMarcel Moolenaar 
3115d1a0d267SMarcel Moolenaar     res = dngettext(domainname, sing, plural, n);
3116d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3117d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%s"
3118d1a0d267SMarcel Moolenaar 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
3119d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "",
3120d1a0d267SMarcel Moolenaar 		xo_printable(domainname), domainname ? "\", " : "",
3121d1a0d267SMarcel Moolenaar 		xo_printable(sing),
3122d1a0d267SMarcel Moolenaar 		xo_printable(plural), n, xo_printable(res));
3123d1a0d267SMarcel Moolenaar 
3124d1a0d267SMarcel Moolenaar     return res;
3125d1a0d267SMarcel Moolenaar }
3126d1a0d267SMarcel Moolenaar #else /* HAVE_GETTEXT */
3127d1a0d267SMarcel Moolenaar static inline const char *
3128d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
3129d1a0d267SMarcel Moolenaar {
3130d1a0d267SMarcel Moolenaar     return str;
3131d1a0d267SMarcel Moolenaar }
3132d1a0d267SMarcel Moolenaar 
3133d1a0d267SMarcel Moolenaar static inline const char *
3134d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
3135d1a0d267SMarcel Moolenaar 	      const char *plural, unsigned long int n)
3136d1a0d267SMarcel Moolenaar {
3137d1a0d267SMarcel Moolenaar     return (n == 1) ? singular : plural;
3138d1a0d267SMarcel Moolenaar }
3139d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
3140d1a0d267SMarcel Moolenaar 
3141d1a0d267SMarcel Moolenaar /*
3142d1a0d267SMarcel Moolenaar  * This is really _re_formatting, since the normal format code has
3143d1a0d267SMarcel Moolenaar  * generated a beautiful string into xo_data, starting at
3144d1a0d267SMarcel Moolenaar  * start_offset.  We need to see if it's plural, which means
3145d1a0d267SMarcel Moolenaar  * comma-separated options, or singular.  Then we make the appropriate
3146d1a0d267SMarcel Moolenaar  * call to d[n]gettext() to get the locale-based version.  Note that
3147d1a0d267SMarcel Moolenaar  * both input and output of gettext() this should be UTF-8.
3148d1a0d267SMarcel Moolenaar  */
31498a6eceffSPhil Shafer static ssize_t
3150d1a0d267SMarcel Moolenaar xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
31518a6eceffSPhil Shafer 		   ssize_t start_offset, ssize_t cols, int need_enc)
3152d1a0d267SMarcel Moolenaar {
3153d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
3154d1a0d267SMarcel Moolenaar 
3155d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, 1))
3156d1a0d267SMarcel Moolenaar 	return cols;
3157d1a0d267SMarcel Moolenaar 
3158d1a0d267SMarcel Moolenaar     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
3159d1a0d267SMarcel Moolenaar 
3160d1a0d267SMarcel Moolenaar     char *cp = xbp->xb_bufp + start_offset;
31618a6eceffSPhil Shafer     ssize_t len = xbp->xb_curp - cp;
3162d1a0d267SMarcel Moolenaar     const char *newstr = NULL;
3163d1a0d267SMarcel Moolenaar 
3164d1a0d267SMarcel Moolenaar     /*
3165d1a0d267SMarcel Moolenaar      * The plural flag asks us to look backwards at the last numeric
3166d1a0d267SMarcel Moolenaar      * value rendered and disect the string into two pieces.
3167d1a0d267SMarcel Moolenaar      */
3168d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_PLURAL) {
3169d1a0d267SMarcel Moolenaar 	int n = xo_buf_find_last_number(xbp, start_offset);
3170d1a0d267SMarcel Moolenaar 	char *two = memchr(cp, (int) ',', len);
3171d1a0d267SMarcel Moolenaar 	if (two == NULL) {
3172d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
3173d1a0d267SMarcel Moolenaar 	    return cols;
3174d1a0d267SMarcel Moolenaar 	}
3175d1a0d267SMarcel Moolenaar 
3176d1a0d267SMarcel Moolenaar 	if (two == cp) {
3177d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing before comma in plural gettext "
3178d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3179d1a0d267SMarcel Moolenaar 	    return cols;
3180d1a0d267SMarcel Moolenaar 	}
3181d1a0d267SMarcel Moolenaar 
3182d1a0d267SMarcel Moolenaar 	if (two == xbp->xb_curp) {
3183d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing after comma in plural gettext "
3184d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3185d1a0d267SMarcel Moolenaar 	    return cols;
3186d1a0d267SMarcel Moolenaar 	}
3187d1a0d267SMarcel Moolenaar 
3188d1a0d267SMarcel Moolenaar 	*two++ = '\0';
3189d1a0d267SMarcel Moolenaar 	if (flags & XFF_GT_FIELD) {
3190d1a0d267SMarcel Moolenaar 	    newstr = xo_dngettext(xop, cp, two, n);
3191d1a0d267SMarcel Moolenaar 	} else {
3192d1a0d267SMarcel Moolenaar 	    /* Don't do a gettext() look up, just get the plural form */
3193d1a0d267SMarcel Moolenaar 	    newstr = (n == 1) ? cp : two;
3194d1a0d267SMarcel Moolenaar 	}
3195d1a0d267SMarcel Moolenaar 
3196d1a0d267SMarcel Moolenaar 	/*
3197d1a0d267SMarcel Moolenaar 	 * If we returned the first string, optimize a bit by
3198d1a0d267SMarcel Moolenaar 	 * backing up over comma
3199d1a0d267SMarcel Moolenaar 	 */
3200d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3201d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = two - 1; /* One for comma */
3202d1a0d267SMarcel Moolenaar 	    /*
3203d1a0d267SMarcel Moolenaar 	     * If the caller wanted UTF8, we're done; nothing changed,
3204d1a0d267SMarcel Moolenaar 	     * but we need to count the columns used.
3205d1a0d267SMarcel Moolenaar 	     */
3206d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3207d1a0d267SMarcel Moolenaar 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3208d1a0d267SMarcel Moolenaar 	}
3209d1a0d267SMarcel Moolenaar 
3210d1a0d267SMarcel Moolenaar     } else {
3211d1a0d267SMarcel Moolenaar 	/* The simple case (singular) */
3212d1a0d267SMarcel Moolenaar 	newstr = xo_dgettext(xop, cp);
3213d1a0d267SMarcel Moolenaar 
3214d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3215d1a0d267SMarcel Moolenaar 	    /* If the caller wanted UTF8, we're done; nothing changed */
3216d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3217d1a0d267SMarcel Moolenaar 		return cols;
3218d1a0d267SMarcel Moolenaar 	}
3219d1a0d267SMarcel Moolenaar     }
3220d1a0d267SMarcel Moolenaar 
3221d1a0d267SMarcel Moolenaar     /*
3222d1a0d267SMarcel Moolenaar      * Since the new string string might be in gettext's buffer or
3223d1a0d267SMarcel Moolenaar      * in the buffer (as the plural form), we make a copy.
3224d1a0d267SMarcel Moolenaar      */
32258a6eceffSPhil Shafer     ssize_t nlen = strlen(newstr);
3226d1a0d267SMarcel Moolenaar     char *newcopy = alloca(nlen + 1);
3227d1a0d267SMarcel Moolenaar     memcpy(newcopy, newstr, nlen + 1);
3228d1a0d267SMarcel Moolenaar 
3229d1a0d267SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3230d1a0d267SMarcel Moolenaar     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3231d1a0d267SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3232d1a0d267SMarcel Moolenaar }
3233d1a0d267SMarcel Moolenaar 
323431337658SMarcel Moolenaar static void
32358a6eceffSPhil Shafer xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len,
3236d1a0d267SMarcel Moolenaar 			xo_xff_flags_t flags)
323731337658SMarcel Moolenaar {
323831337658SMarcel Moolenaar     int cols;
3239d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
32408a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(&xop->xo_data);
324131337658SMarcel Moolenaar 
3242d1a0d267SMarcel Moolenaar     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
324331337658SMarcel Moolenaar 				   NULL, str, len, -1,
324431337658SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3245d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS)
3246d1a0d267SMarcel Moolenaar 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
324731337658SMarcel Moolenaar 
3248d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
324931337658SMarcel Moolenaar 	xop->xo_columns += cols;
3250d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
325131337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
325231337658SMarcel Moolenaar }
325331337658SMarcel Moolenaar 
3254f2b7bf8aSPhil Shafer /**
3255f2b7bf8aSPhil Shafer  * Bump one of the 'width' values in a format strings (e.g. "%40.50.60s").
3256f2b7bf8aSPhil Shafer  * @param xfp Formatting instructions
3257f2b7bf8aSPhil Shafer  * @param digit Single digit (0-9) of input
3258f2b7bf8aSPhil Shafer  */
325931337658SMarcel Moolenaar static void
326031337658SMarcel Moolenaar xo_bump_width (xo_format_t *xfp, int digit)
326131337658SMarcel Moolenaar {
326231337658SMarcel Moolenaar     int *ip = &xfp->xf_width[xfp->xf_dots];
326331337658SMarcel Moolenaar 
326431337658SMarcel Moolenaar     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
326531337658SMarcel Moolenaar }
326631337658SMarcel Moolenaar 
32678a6eceffSPhil Shafer static ssize_t
32688a6eceffSPhil Shafer xo_trim_ws (xo_buffer_t *xbp, ssize_t len)
326931337658SMarcel Moolenaar {
327031337658SMarcel Moolenaar     char *cp, *sp, *ep;
32718a6eceffSPhil Shafer     ssize_t delta;
327231337658SMarcel Moolenaar 
327331337658SMarcel Moolenaar     /* First trim leading space */
327431337658SMarcel Moolenaar     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
327531337658SMarcel Moolenaar 	if (*cp != ' ')
327631337658SMarcel Moolenaar 	    break;
327731337658SMarcel Moolenaar     }
327831337658SMarcel Moolenaar 
327931337658SMarcel Moolenaar     delta = cp - sp;
328031337658SMarcel Moolenaar     if (delta) {
328131337658SMarcel Moolenaar 	len -= delta;
328231337658SMarcel Moolenaar 	memmove(sp, cp, len);
328331337658SMarcel Moolenaar     }
328431337658SMarcel Moolenaar 
328531337658SMarcel Moolenaar     /* Then trim off the end */
328631337658SMarcel Moolenaar     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
328731337658SMarcel Moolenaar 	if (ep[-1] != ' ')
328831337658SMarcel Moolenaar 	    break;
328931337658SMarcel Moolenaar     }
329031337658SMarcel Moolenaar 
329131337658SMarcel Moolenaar     delta = sp - ep;
329231337658SMarcel Moolenaar     if (delta) {
329331337658SMarcel Moolenaar 	len -= delta;
329431337658SMarcel Moolenaar 	cp[len] = '\0';
329531337658SMarcel Moolenaar     }
329631337658SMarcel Moolenaar 
329731337658SMarcel Moolenaar     return len;
329831337658SMarcel Moolenaar }
329931337658SMarcel Moolenaar 
3300d1a0d267SMarcel Moolenaar /*
3301d1a0d267SMarcel Moolenaar  * Interface to format a single field.  The arguments are in xo_vap,
3302d1a0d267SMarcel Moolenaar  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
3303d1a0d267SMarcel Moolenaar  * this is the most common case.
3304d1a0d267SMarcel Moolenaar  */
33058a6eceffSPhil Shafer static ssize_t
3306d1a0d267SMarcel Moolenaar xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
33078a6eceffSPhil Shafer 		const char *fmt, ssize_t flen, xo_xff_flags_t flags)
330831337658SMarcel Moolenaar {
330931337658SMarcel Moolenaar     xo_format_t xf;
331031337658SMarcel Moolenaar     const char *cp, *ep, *sp, *xp = NULL;
33118a6eceffSPhil Shafer     ssize_t rc, cols;
3312788ca347SMarcel Moolenaar     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
33138a6eceffSPhil Shafer     unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0;
3314d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
3315d1a0d267SMarcel Moolenaar     int real_need_enc = need_enc;
33168a6eceffSPhil Shafer     ssize_t old_cols = xop->xo_columns;
3317d1a0d267SMarcel Moolenaar 
3318d1a0d267SMarcel Moolenaar     /* The gettext interface is UTF-8, so we'll need that for now */
3319d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FIELD)
3320d1a0d267SMarcel Moolenaar 	need_enc = XF_ENC_UTF8;
332131337658SMarcel Moolenaar 
332231337658SMarcel Moolenaar     if (xbp == NULL)
332331337658SMarcel Moolenaar 	xbp = &xop->xo_data;
332431337658SMarcel Moolenaar 
33258a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(xbp);
3326d1a0d267SMarcel Moolenaar 
332731337658SMarcel Moolenaar     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3328d1a0d267SMarcel Moolenaar 	/*
3329d1a0d267SMarcel Moolenaar 	 * Since we're starting a new field, save the starting offset.
3330d1a0d267SMarcel Moolenaar 	 * We'll need this later for field-related operations.
3331d1a0d267SMarcel Moolenaar 	 */
3332d1a0d267SMarcel Moolenaar 
333331337658SMarcel Moolenaar 	if (*cp != '%') {
333431337658SMarcel Moolenaar 	add_one:
333531337658SMarcel Moolenaar 	    if (xp == NULL)
333631337658SMarcel Moolenaar 		xp = cp;
333731337658SMarcel Moolenaar 
333831337658SMarcel Moolenaar 	    if (*cp == '\\' && cp[1] != '\0')
333931337658SMarcel Moolenaar 		cp += 1;
334031337658SMarcel Moolenaar 	    continue;
334131337658SMarcel Moolenaar 
334231337658SMarcel Moolenaar 	} if (cp + 1 < ep && cp[1] == '%') {
334331337658SMarcel Moolenaar 	    cp += 1;
334431337658SMarcel Moolenaar 	    goto add_one;
334531337658SMarcel Moolenaar 	}
334631337658SMarcel Moolenaar 
334731337658SMarcel Moolenaar 	if (xp) {
334831337658SMarcel Moolenaar 	    if (make_output) {
334931337658SMarcel Moolenaar 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
335031337658SMarcel Moolenaar 					       NULL, xp, cp - xp, -1,
335131337658SMarcel Moolenaar 					       need_enc, XF_ENC_UTF8);
3352d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
335331337658SMarcel Moolenaar 		    xop->xo_columns += cols;
3354d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
335531337658SMarcel Moolenaar 		    xop->xo_anchor_columns += cols;
335631337658SMarcel Moolenaar 	    }
335731337658SMarcel Moolenaar 
335831337658SMarcel Moolenaar 	    xp = NULL;
335931337658SMarcel Moolenaar 	}
336031337658SMarcel Moolenaar 
336131337658SMarcel Moolenaar 	bzero(&xf, sizeof(xf));
336231337658SMarcel Moolenaar 	xf.xf_leading_zero = -1;
336331337658SMarcel Moolenaar 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
336431337658SMarcel Moolenaar 
336531337658SMarcel Moolenaar 	/*
336631337658SMarcel Moolenaar 	 * "%@" starts an XO-specific set of flags:
336731337658SMarcel Moolenaar 	 *   @X@ - XML-only field; ignored if style isn't XML
336831337658SMarcel Moolenaar 	 */
336931337658SMarcel Moolenaar 	if (cp[1] == '@') {
337031337658SMarcel Moolenaar 	    for (cp += 2; cp < ep; cp++) {
337131337658SMarcel Moolenaar 		if (*cp == '@') {
337231337658SMarcel Moolenaar 		    break;
337331337658SMarcel Moolenaar 		}
337431337658SMarcel Moolenaar 		if (*cp == '*') {
337531337658SMarcel Moolenaar 		    /*
337631337658SMarcel Moolenaar 		     * '*' means there's a "%*.*s" value in vap that
337731337658SMarcel Moolenaar 		     * we want to ignore
337831337658SMarcel Moolenaar 		     */
3379d1a0d267SMarcel Moolenaar 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
338031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
338131337658SMarcel Moolenaar 		}
338231337658SMarcel Moolenaar 	    }
338331337658SMarcel Moolenaar 	}
338431337658SMarcel Moolenaar 
338531337658SMarcel Moolenaar 	/* Hidden fields are only visible to JSON and XML */
3386d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
338731337658SMarcel Moolenaar 	    if (style != XO_STYLE_XML
3388d1a0d267SMarcel Moolenaar 		    && !xo_style_is_encoding(xop))
338931337658SMarcel Moolenaar 		xf.xf_skip = 1;
3390d1a0d267SMarcel Moolenaar 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
339131337658SMarcel Moolenaar 	    if (style != XO_STYLE_TEXT
3392788ca347SMarcel Moolenaar 		    && xo_style(xop) != XO_STYLE_HTML)
339331337658SMarcel Moolenaar 		xf.xf_skip = 1;
339431337658SMarcel Moolenaar 	}
339531337658SMarcel Moolenaar 
339631337658SMarcel Moolenaar 	if (!make_output)
339731337658SMarcel Moolenaar 	    xf.xf_skip = 1;
339831337658SMarcel Moolenaar 
339931337658SMarcel Moolenaar 	/*
340031337658SMarcel Moolenaar 	 * Looking at one piece of a format; find the end and
340131337658SMarcel Moolenaar 	 * call snprintf.  Then advance xo_vap on our own.
340231337658SMarcel Moolenaar 	 *
340331337658SMarcel Moolenaar 	 * Note that 'n', 'v', and '$' are not supported.
340431337658SMarcel Moolenaar 	 */
340531337658SMarcel Moolenaar 	sp = cp;		/* Save start pointer */
340631337658SMarcel Moolenaar 	for (cp += 1; cp < ep; cp++) {
340731337658SMarcel Moolenaar 	    if (*cp == 'l')
340831337658SMarcel Moolenaar 		xf.xf_lflag += 1;
340931337658SMarcel Moolenaar 	    else if (*cp == 'h')
341031337658SMarcel Moolenaar 		xf.xf_hflag += 1;
341131337658SMarcel Moolenaar 	    else if (*cp == 'j')
341231337658SMarcel Moolenaar 		xf.xf_jflag += 1;
341331337658SMarcel Moolenaar 	    else if (*cp == 't')
341431337658SMarcel Moolenaar 		xf.xf_tflag += 1;
341531337658SMarcel Moolenaar 	    else if (*cp == 'z')
341631337658SMarcel Moolenaar 		xf.xf_zflag += 1;
341731337658SMarcel Moolenaar 	    else if (*cp == 'q')
341831337658SMarcel Moolenaar 		xf.xf_qflag += 1;
341931337658SMarcel Moolenaar 	    else if (*cp == '.') {
342031337658SMarcel Moolenaar 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
342131337658SMarcel Moolenaar 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
342231337658SMarcel Moolenaar 		    return -1;
342331337658SMarcel Moolenaar 		}
342431337658SMarcel Moolenaar 	    } else if (*cp == '-')
342531337658SMarcel Moolenaar 		xf.xf_seen_minus = 1;
342631337658SMarcel Moolenaar 	    else if (isdigit((int) *cp)) {
342731337658SMarcel Moolenaar 		if (xf.xf_leading_zero < 0)
342831337658SMarcel Moolenaar 		    xf.xf_leading_zero = (*cp == '0');
342931337658SMarcel Moolenaar 		xo_bump_width(&xf, *cp - '0');
343031337658SMarcel Moolenaar 	    } else if (*cp == '*') {
343131337658SMarcel Moolenaar 		xf.xf_stars += 1;
343231337658SMarcel Moolenaar 		xf.xf_star[xf.xf_dots] = 1;
3433d1a0d267SMarcel Moolenaar 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
343431337658SMarcel Moolenaar 		break;
343531337658SMarcel Moolenaar 	    else if (*cp == 'n' || *cp == 'v') {
343631337658SMarcel Moolenaar 		xo_failure(xop, "unsupported format: '%s'", fmt);
343731337658SMarcel Moolenaar 		return -1;
343831337658SMarcel Moolenaar 	    }
343931337658SMarcel Moolenaar 	}
344031337658SMarcel Moolenaar 
344131337658SMarcel Moolenaar 	if (cp == ep)
344231337658SMarcel Moolenaar 	    xo_failure(xop, "field format missing format character: %s",
344331337658SMarcel Moolenaar 			  fmt);
344431337658SMarcel Moolenaar 
344531337658SMarcel Moolenaar 	xf.xf_fc = *cp;
344631337658SMarcel Moolenaar 
3447d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
344831337658SMarcel Moolenaar 	    if (*cp == 's' || *cp == 'S') {
344931337658SMarcel Moolenaar 		/* Handle "%*.*.*s" */
345031337658SMarcel Moolenaar 		int s;
345131337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
345231337658SMarcel Moolenaar 		    if (xf.xf_star[s]) {
345331337658SMarcel Moolenaar 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
345431337658SMarcel Moolenaar 
345531337658SMarcel Moolenaar 			/* Normalize a negative width value */
345631337658SMarcel Moolenaar 			if (xf.xf_width[s] < 0) {
345731337658SMarcel Moolenaar 			    if (s == 0) {
345831337658SMarcel Moolenaar 				xf.xf_width[0] = -xf.xf_width[0];
345931337658SMarcel Moolenaar 				xf.xf_seen_minus = 1;
346031337658SMarcel Moolenaar 			    } else
346131337658SMarcel Moolenaar 				xf.xf_width[s] = -1; /* Ignore negative values */
346231337658SMarcel Moolenaar 			}
346331337658SMarcel Moolenaar 		    }
346431337658SMarcel Moolenaar 		}
346531337658SMarcel Moolenaar 	    }
346631337658SMarcel Moolenaar 	}
346731337658SMarcel Moolenaar 
346831337658SMarcel Moolenaar 	/* If no max is given, it defaults to size */
346931337658SMarcel Moolenaar 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
347031337658SMarcel Moolenaar 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
347131337658SMarcel Moolenaar 
347231337658SMarcel Moolenaar 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
347331337658SMarcel Moolenaar 	    xf.xf_lflag = 1;
347431337658SMarcel Moolenaar 
347531337658SMarcel Moolenaar 	if (!xf.xf_skip) {
347631337658SMarcel Moolenaar 	    xo_buffer_t *fbp = &xop->xo_fmt;
34778a6eceffSPhil Shafer 	    ssize_t len = cp - sp + 1;
347831337658SMarcel Moolenaar 	    if (!xo_buf_has_room(fbp, len + 1))
347931337658SMarcel Moolenaar 		return -1;
348031337658SMarcel Moolenaar 
348131337658SMarcel Moolenaar 	    char *newfmt = fbp->xb_curp;
348231337658SMarcel Moolenaar 	    memcpy(newfmt, sp, len);
348331337658SMarcel Moolenaar 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
348431337658SMarcel Moolenaar 	    newfmt[len] = '\0';
348531337658SMarcel Moolenaar 
348631337658SMarcel Moolenaar 	    /*
348731337658SMarcel Moolenaar 	     * Bad news: our strings are UTF-8, but the stock printf
348831337658SMarcel Moolenaar 	     * functions won't handle field widths for wide characters
348931337658SMarcel Moolenaar 	     * correctly.  So we have to handle this ourselves.
349031337658SMarcel Moolenaar 	     */
349131337658SMarcel Moolenaar 	    if (xop->xo_formatter == NULL
3492d1a0d267SMarcel Moolenaar 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3493d1a0d267SMarcel Moolenaar 			|| xf.xf_fc == 'm')) {
3494d1a0d267SMarcel Moolenaar 
3495d1a0d267SMarcel Moolenaar 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3496d1a0d267SMarcel Moolenaar 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3497d1a0d267SMarcel Moolenaar 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3498d1a0d267SMarcel Moolenaar 
349931337658SMarcel Moolenaar 		rc = xo_format_string(xop, xbp, flags, &xf);
350031337658SMarcel Moolenaar 
3501d1a0d267SMarcel Moolenaar 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
350231337658SMarcel Moolenaar 		    rc = xo_trim_ws(xbp, rc);
350331337658SMarcel Moolenaar 
350431337658SMarcel Moolenaar 	    } else {
3505f2b7bf8aSPhil Shafer 		ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt,
3506f2b7bf8aSPhil Shafer 						    xop->xo_vap);
350731337658SMarcel Moolenaar 
350831337658SMarcel Moolenaar 		/*
350931337658SMarcel Moolenaar 		 * For XML and HTML, we need "&<>" processing; for JSON,
351031337658SMarcel Moolenaar 		 * it's quotes.  Text gets nothing.
351131337658SMarcel Moolenaar 		 */
351231337658SMarcel Moolenaar 		switch (style) {
351331337658SMarcel Moolenaar 		case XO_STYLE_XML:
351431337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
351531337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3516ee5cf116SPhil Shafer 		    /* FALLTHRU */
351731337658SMarcel Moolenaar 		case XO_STYLE_HTML:
351831337658SMarcel Moolenaar 		    rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
351931337658SMarcel Moolenaar 		    break;
352031337658SMarcel Moolenaar 
352131337658SMarcel Moolenaar 		case XO_STYLE_JSON:
352231337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
352331337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3524d1a0d267SMarcel Moolenaar 		    rc = xo_escape_json(xbp, rc, 0);
3525d1a0d267SMarcel Moolenaar 		    break;
3526d1a0d267SMarcel Moolenaar 
3527d1a0d267SMarcel Moolenaar 		case XO_STYLE_SDPARAMS:
3528d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3529d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3530d1a0d267SMarcel Moolenaar 		    rc = xo_escape_sdparams(xbp, rc, 0);
3531d1a0d267SMarcel Moolenaar 		    break;
3532d1a0d267SMarcel Moolenaar 
3533d1a0d267SMarcel Moolenaar 		case XO_STYLE_ENCODER:
3534d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3535d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
353631337658SMarcel Moolenaar 		    break;
353731337658SMarcel Moolenaar 		}
353831337658SMarcel Moolenaar 
353931337658SMarcel Moolenaar 		/*
3540d1a0d267SMarcel Moolenaar 		 * We can assume all the non-%s data we've
3541d1a0d267SMarcel Moolenaar 		 * added is ASCII, so the columns and bytes are the
3542d1a0d267SMarcel Moolenaar 		 * same.  xo_format_string handles all the fancy
3543d1a0d267SMarcel Moolenaar 		 * string conversions and updates xo_anchor_columns
3544d1a0d267SMarcel Moolenaar 		 * accordingly.
354531337658SMarcel Moolenaar 		 */
3546d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
354731337658SMarcel Moolenaar 		    xop->xo_columns += columns;
3548d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
354931337658SMarcel Moolenaar 		    xop->xo_anchor_columns += columns;
355031337658SMarcel Moolenaar 	    }
355131337658SMarcel Moolenaar 
355231337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
355331337658SMarcel Moolenaar 	}
355431337658SMarcel Moolenaar 
355531337658SMarcel Moolenaar 	/*
355631337658SMarcel Moolenaar 	 * Now for the tricky part: we need to move the argument pointer
355731337658SMarcel Moolenaar 	 * along by the amount needed.
355831337658SMarcel Moolenaar 	 */
3559d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
356031337658SMarcel Moolenaar 
356131337658SMarcel Moolenaar 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
356231337658SMarcel Moolenaar 		/*
356331337658SMarcel Moolenaar 		 * The 'S' and 's' formats are normally handled in
356431337658SMarcel Moolenaar 		 * xo_format_string, but if we skipped it, then we
356531337658SMarcel Moolenaar 		 * need to pop it.
356631337658SMarcel Moolenaar 		 */
356731337658SMarcel Moolenaar 		if (xf.xf_skip)
356831337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, char *);
356931337658SMarcel Moolenaar 
3570d1a0d267SMarcel Moolenaar 	    } else if (xf.xf_fc == 'm') {
3571d1a0d267SMarcel Moolenaar 		/* Nothing on the stack for "%m" */
3572d1a0d267SMarcel Moolenaar 
357331337658SMarcel Moolenaar 	    } else {
357431337658SMarcel Moolenaar 		int s;
357531337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
357631337658SMarcel Moolenaar 		    if (xf.xf_star[s])
357731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
357831337658SMarcel Moolenaar 		}
357931337658SMarcel Moolenaar 
358031337658SMarcel Moolenaar 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
358131337658SMarcel Moolenaar 		    if (xf.xf_hflag > 1) {
358231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
358331337658SMarcel Moolenaar 
358431337658SMarcel Moolenaar 		    } else if (xf.xf_hflag > 0) {
358531337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
358631337658SMarcel Moolenaar 
358731337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 1) {
358831337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long long);
358931337658SMarcel Moolenaar 
359031337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 0) {
359131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long);
359231337658SMarcel Moolenaar 
359331337658SMarcel Moolenaar 		    } else if (xf.xf_jflag > 0) {
359431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, intmax_t);
359531337658SMarcel Moolenaar 
359631337658SMarcel Moolenaar 		    } else if (xf.xf_tflag > 0) {
359731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, ptrdiff_t);
359831337658SMarcel Moolenaar 
359931337658SMarcel Moolenaar 		    } else if (xf.xf_zflag > 0) {
360031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, size_t);
360131337658SMarcel Moolenaar 
360231337658SMarcel Moolenaar 		    } else if (xf.xf_qflag > 0) {
360331337658SMarcel Moolenaar 			va_arg(xop->xo_vap, quad_t);
360431337658SMarcel Moolenaar 
360531337658SMarcel Moolenaar 		    } else {
360631337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
360731337658SMarcel Moolenaar 		    }
360831337658SMarcel Moolenaar 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
360931337658SMarcel Moolenaar 		    if (xf.xf_lflag)
361031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, long double);
361131337658SMarcel Moolenaar 		    else
361231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, double);
361331337658SMarcel Moolenaar 
361431337658SMarcel Moolenaar 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
361531337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, wint_t);
361631337658SMarcel Moolenaar 
361731337658SMarcel Moolenaar 		else if (xf.xf_fc == 'c')
361831337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, int);
361931337658SMarcel Moolenaar 
362031337658SMarcel Moolenaar 		else if (xf.xf_fc == 'p')
362131337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, void *);
362231337658SMarcel Moolenaar 	    }
362331337658SMarcel Moolenaar 	}
362431337658SMarcel Moolenaar     }
362531337658SMarcel Moolenaar 
362631337658SMarcel Moolenaar     if (xp) {
362731337658SMarcel Moolenaar 	if (make_output) {
362831337658SMarcel Moolenaar 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
362931337658SMarcel Moolenaar 					   NULL, xp, cp - xp, -1,
363031337658SMarcel Moolenaar 					   need_enc, XF_ENC_UTF8);
3631d1a0d267SMarcel Moolenaar 
3632d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
363331337658SMarcel Moolenaar 		xop->xo_columns += cols;
3634d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
363531337658SMarcel Moolenaar 		xop->xo_anchor_columns += cols;
363631337658SMarcel Moolenaar 	}
363731337658SMarcel Moolenaar 
363831337658SMarcel Moolenaar 	xp = NULL;
363931337658SMarcel Moolenaar     }
364031337658SMarcel Moolenaar 
3641d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS) {
3642d1a0d267SMarcel Moolenaar 	/*
3643d1a0d267SMarcel Moolenaar 	 * Handle gettext()ing the field by looking up the value
3644d1a0d267SMarcel Moolenaar 	 * and then copying it in, while converting to locale, if
3645d1a0d267SMarcel Moolenaar 	 * needed.
3646d1a0d267SMarcel Moolenaar 	 */
36478a6eceffSPhil Shafer 	ssize_t new_cols = xo_format_gettext(xop, flags, start_offset,
3648d1a0d267SMarcel Moolenaar 					 old_cols, real_need_enc);
3649d1a0d267SMarcel Moolenaar 
3650d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_COLUMNS))
3651d1a0d267SMarcel Moolenaar 	    xop->xo_columns += new_cols - old_cols;
3652d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3653d1a0d267SMarcel Moolenaar 	    xop->xo_anchor_columns += new_cols - old_cols;
3654d1a0d267SMarcel Moolenaar     }
3655d1a0d267SMarcel Moolenaar 
365631337658SMarcel Moolenaar     return 0;
365731337658SMarcel Moolenaar }
365831337658SMarcel Moolenaar 
3659264104f2SPhil Shafer /*
3660264104f2SPhil Shafer  * Remove any numeric precision/width format from the format string by
3661264104f2SPhil Shafer  * inserting the "%" after the [0-9]+, returning the substring.
3662264104f2SPhil Shafer  */
366331337658SMarcel Moolenaar static char *
366431337658SMarcel Moolenaar xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
366531337658SMarcel Moolenaar {
366631337658SMarcel Moolenaar     char *cp = encoding;
366731337658SMarcel Moolenaar 
366831337658SMarcel Moolenaar     if (cp[0] != '%' || !isdigit((int) cp[1]))
366931337658SMarcel Moolenaar 	return encoding;
367031337658SMarcel Moolenaar 
367131337658SMarcel Moolenaar     for (cp += 2; *cp; cp++) {
367231337658SMarcel Moolenaar 	if (!isdigit((int) *cp))
367331337658SMarcel Moolenaar 	    break;
367431337658SMarcel Moolenaar     }
367531337658SMarcel Moolenaar 
3676264104f2SPhil Shafer     *--cp = '%';		/* Back off and insert the '%' */
367731337658SMarcel Moolenaar 
367831337658SMarcel Moolenaar     return cp;
367931337658SMarcel Moolenaar }
368031337658SMarcel Moolenaar 
368131337658SMarcel Moolenaar static void
3682788ca347SMarcel Moolenaar xo_color_append_html (xo_handle_t *xop)
3683788ca347SMarcel Moolenaar {
3684788ca347SMarcel Moolenaar     /*
3685788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3686788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3687788ca347SMarcel Moolenaar      */
3688788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3689788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3690788ca347SMarcel Moolenaar 
3691788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3692788ca347SMarcel Moolenaar     }
3693788ca347SMarcel Moolenaar }
3694788ca347SMarcel Moolenaar 
3695d1a0d267SMarcel Moolenaar /*
3696d1a0d267SMarcel Moolenaar  * A wrapper for humanize_number that autoscales, since the
3697d1a0d267SMarcel Moolenaar  * HN_AUTOSCALE flag scales as needed based on the size of
3698d1a0d267SMarcel Moolenaar  * the output buffer, not the size of the value.  I also
3699d1a0d267SMarcel Moolenaar  * wish HN_DECIMAL was more imperative, without the <10
3700d1a0d267SMarcel Moolenaar  * test.  But the boat only goes where we want when we hold
3701d1a0d267SMarcel Moolenaar  * the rudder, so xo_humanize fixes part of the problem.
3702d1a0d267SMarcel Moolenaar  */
37038a6eceffSPhil Shafer static ssize_t
37048a6eceffSPhil Shafer xo_humanize (char *buf, ssize_t len, uint64_t value, int flags)
3705d1a0d267SMarcel Moolenaar {
3706d1a0d267SMarcel Moolenaar     int scale = 0;
3707d1a0d267SMarcel Moolenaar 
3708d1a0d267SMarcel Moolenaar     if (value) {
3709d1a0d267SMarcel Moolenaar 	uint64_t left = value;
3710d1a0d267SMarcel Moolenaar 
3711d1a0d267SMarcel Moolenaar 	if (flags & HN_DIVISOR_1000) {
3712d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3713d1a0d267SMarcel Moolenaar 		left /= 1000;
3714d1a0d267SMarcel Moolenaar 	} else {
3715d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3716d1a0d267SMarcel Moolenaar 		left /= 1024;
3717d1a0d267SMarcel Moolenaar 	}
3718d1a0d267SMarcel Moolenaar 	scale -= 1;
3719d1a0d267SMarcel Moolenaar     }
3720d1a0d267SMarcel Moolenaar 
3721d1a0d267SMarcel Moolenaar     return xo_humanize_number(buf, len, value, "", scale, flags);
3722d1a0d267SMarcel Moolenaar }
3723d1a0d267SMarcel Moolenaar 
3724d1a0d267SMarcel Moolenaar /*
3725d1a0d267SMarcel Moolenaar  * This is an area where we can save information from the handle for
3726d1a0d267SMarcel Moolenaar  * later restoration.  We need to know what data was rendered to know
3727d1a0d267SMarcel Moolenaar  * what needs cleaned up.
3728d1a0d267SMarcel Moolenaar  */
3729d1a0d267SMarcel Moolenaar typedef struct xo_humanize_save_s {
37308a6eceffSPhil Shafer     ssize_t xhs_offset;		/* Saved xo_offset */
37318a6eceffSPhil Shafer     ssize_t xhs_columns;	/* Saved xo_columns */
37328a6eceffSPhil Shafer     ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */
3733d1a0d267SMarcel Moolenaar } xo_humanize_save_t;
3734d1a0d267SMarcel Moolenaar 
3735d1a0d267SMarcel Moolenaar /*
3736d1a0d267SMarcel Moolenaar  * Format a "humanized" value for a numeric, meaning something nice
3737d1a0d267SMarcel Moolenaar  * like "44M" instead of "44470272".  We autoscale, choosing the
3738d1a0d267SMarcel Moolenaar  * most appropriate value for K/M/G/T/P/E based on the value given.
3739d1a0d267SMarcel Moolenaar  */
3740d1a0d267SMarcel Moolenaar static void
3741d1a0d267SMarcel Moolenaar xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3742d1a0d267SMarcel Moolenaar 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3743d1a0d267SMarcel Moolenaar {
3744d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3745d1a0d267SMarcel Moolenaar 	return;
3746d1a0d267SMarcel Moolenaar 
37478a6eceffSPhil Shafer     ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
3748d1a0d267SMarcel Moolenaar     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3749d1a0d267SMarcel Moolenaar 	return;
3750d1a0d267SMarcel Moolenaar 
3751d1a0d267SMarcel Moolenaar     /*
3752d1a0d267SMarcel Moolenaar      * We have a string that's allegedly a number. We want to
3753d1a0d267SMarcel Moolenaar      * humanize it, which means turning it back into a number
3754d1a0d267SMarcel Moolenaar      * and calling xo_humanize_number on it.
3755d1a0d267SMarcel Moolenaar      */
3756d1a0d267SMarcel Moolenaar     uint64_t value;
3757d1a0d267SMarcel Moolenaar     char *ep;
3758d1a0d267SMarcel Moolenaar 
3759d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3760d1a0d267SMarcel Moolenaar 
3761d1a0d267SMarcel Moolenaar     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3762d1a0d267SMarcel Moolenaar     if (!(value == ULLONG_MAX && errno == ERANGE)
3763d1a0d267SMarcel Moolenaar 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3764d1a0d267SMarcel Moolenaar 	/*
3765d1a0d267SMarcel Moolenaar 	 * There are few values where humanize_number needs
3766d1a0d267SMarcel Moolenaar 	 * more bytes than the original value.  I've used
3767d1a0d267SMarcel Moolenaar 	 * 10 as a rectal number to cover those scenarios.
3768d1a0d267SMarcel Moolenaar 	 */
3769d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, 10)) {
3770d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3771d1a0d267SMarcel Moolenaar 
37728a6eceffSPhil Shafer 	    ssize_t rc;
37738a6eceffSPhil Shafer 	    ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3774d1a0d267SMarcel Moolenaar 	    int hn_flags = HN_NOSPACE; /* On by default */
3775d1a0d267SMarcel Moolenaar 
3776d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_SPACE)
3777d1a0d267SMarcel Moolenaar 		hn_flags &= ~HN_NOSPACE;
3778d1a0d267SMarcel Moolenaar 
3779d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_DECIMAL)
3780d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DECIMAL;
3781d1a0d267SMarcel Moolenaar 
3782d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_1000)
3783d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DIVISOR_1000;
3784d1a0d267SMarcel Moolenaar 
37858a6eceffSPhil Shafer 	    rc = xo_humanize(xbp->xb_curp, left, value, hn_flags);
3786d1a0d267SMarcel Moolenaar 	    if (rc > 0) {
3787d1a0d267SMarcel Moolenaar 		xbp->xb_curp += rc;
3788d1a0d267SMarcel Moolenaar 		xop->xo_columns = savep->xhs_columns + rc;
3789d1a0d267SMarcel Moolenaar 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3790d1a0d267SMarcel Moolenaar 	    }
3791d1a0d267SMarcel Moolenaar 	}
3792d1a0d267SMarcel Moolenaar     }
3793d1a0d267SMarcel Moolenaar }
3794d1a0d267SMarcel Moolenaar 
3795264104f2SPhil Shafer /*
3796264104f2SPhil Shafer  * Convenience function that either append a fixed value (if one is
3797264104f2SPhil Shafer  * given) or formats a field using a format string.  If it's
3798264104f2SPhil Shafer  * encode_only, then we can't skip formatting the field, since it may
3799264104f2SPhil Shafer  * be pulling arguments off the stack.
3800264104f2SPhil Shafer  */
3801264104f2SPhil Shafer static inline void
3802264104f2SPhil Shafer xo_simple_field (xo_handle_t *xop, unsigned encode_only,
3803264104f2SPhil Shafer 		      const char *value, ssize_t vlen,
3804264104f2SPhil Shafer 		      const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3805264104f2SPhil Shafer {
3806264104f2SPhil Shafer     if (encode_only)
3807264104f2SPhil Shafer 	flags |= XFF_NO_OUTPUT;
3808264104f2SPhil Shafer 
3809264104f2SPhil Shafer     if (vlen == 0)
3810264104f2SPhil Shafer 	xo_do_format_field(xop, NULL, fmt, flen, flags);
3811264104f2SPhil Shafer     else if (!encode_only)
3812264104f2SPhil Shafer 	xo_data_append_content(xop, value, vlen, flags);
3813264104f2SPhil Shafer }
3814264104f2SPhil Shafer 
3815264104f2SPhil Shafer /*
3816264104f2SPhil Shafer  * Html mode: append a <div> to the output buffer contain a field
3817264104f2SPhil Shafer  * along with all the supporting information indicated by the flags.
3818264104f2SPhil Shafer  */
3819788ca347SMarcel Moolenaar static void
382031337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
38218a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
38228a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
3823264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
38248a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen)
382531337658SMarcel Moolenaar {
382631337658SMarcel Moolenaar     static char div_start[] = "<div class=\"";
382731337658SMarcel Moolenaar     static char div_tag[] = "\" data-tag=\"";
382831337658SMarcel Moolenaar     static char div_xpath[] = "\" data-xpath=\"";
382931337658SMarcel Moolenaar     static char div_key[] = "\" data-key=\"key";
383031337658SMarcel Moolenaar     static char div_end[] = "\">";
383131337658SMarcel Moolenaar     static char div_close[] = "</div>";
383231337658SMarcel Moolenaar 
3833a321cc5dSPhil Shafer     /* The encoding format defaults to the normal format */
3834264104f2SPhil Shafer     if (encoding == NULL && fmt != NULL) {
3835264104f2SPhil Shafer 	char *enc  = alloca(flen + 1);
3836264104f2SPhil Shafer 	memcpy(enc, fmt, flen);
3837264104f2SPhil Shafer 	enc[flen] = '\0';
3838a321cc5dSPhil Shafer 	encoding = xo_fix_encoding(xop, enc);
3839a321cc5dSPhil Shafer 	elen = strlen(encoding);
3840a321cc5dSPhil Shafer     }
3841a321cc5dSPhil Shafer 
384231337658SMarcel Moolenaar     /*
384331337658SMarcel Moolenaar      * To build our XPath predicate, we need to save the va_list before
384431337658SMarcel Moolenaar      * we format our data, and then restore it before we format the
384531337658SMarcel Moolenaar      * xpath expression.
384631337658SMarcel Moolenaar      * Display-only keys implies that we've got an encode-only key
384731337658SMarcel Moolenaar      * elsewhere, so we don't use them from making predicates.
384831337658SMarcel Moolenaar      */
384931337658SMarcel Moolenaar     int need_predidate =
385031337658SMarcel Moolenaar 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
38518a6eceffSPhil Shafer 	 && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0;
385231337658SMarcel Moolenaar 
385331337658SMarcel Moolenaar     if (need_predidate) {
385431337658SMarcel Moolenaar 	va_list va_local;
385531337658SMarcel Moolenaar 
385631337658SMarcel Moolenaar 	va_copy(va_local, xop->xo_vap);
385731337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
385831337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
385931337658SMarcel Moolenaar 
386031337658SMarcel Moolenaar 	/*
386131337658SMarcel Moolenaar 	 * Build an XPath predicate expression to match this key.
386231337658SMarcel Moolenaar 	 * We use the format buffer.
386331337658SMarcel Moolenaar 	 */
386431337658SMarcel Moolenaar 	xo_buffer_t *pbp = &xop->xo_predicate;
386531337658SMarcel Moolenaar 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
386631337658SMarcel Moolenaar 
386731337658SMarcel Moolenaar 	xo_buf_append(pbp, "[", 1);
386831337658SMarcel Moolenaar 	xo_buf_escape(xop, pbp, name, nlen, 0);
3869d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
387031337658SMarcel Moolenaar 	    xo_buf_append(pbp, " = '", 4);
387131337658SMarcel Moolenaar 	else
387231337658SMarcel Moolenaar 	    xo_buf_append(pbp, "='", 2);
387331337658SMarcel Moolenaar 
3874d1a0d267SMarcel Moolenaar 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3875d1a0d267SMarcel Moolenaar 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3876d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
387731337658SMarcel Moolenaar 
387831337658SMarcel Moolenaar 	xo_buf_append(pbp, "']", 2);
387931337658SMarcel Moolenaar 
388031337658SMarcel Moolenaar 	/* Now we record this predicate expression in the stack */
388131337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
38828a6eceffSPhil Shafer 	ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
38838a6eceffSPhil Shafer 	ssize_t dlen = pbp->xb_curp - pbp->xb_bufp;
388431337658SMarcel Moolenaar 
388531337658SMarcel Moolenaar 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
388631337658SMarcel Moolenaar 	if (cp) {
388731337658SMarcel Moolenaar 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
388831337658SMarcel Moolenaar 	    cp[olen + dlen] = '\0';
388931337658SMarcel Moolenaar 	    xsp->xs_keys = cp;
389031337658SMarcel Moolenaar 	}
389131337658SMarcel Moolenaar 
389231337658SMarcel Moolenaar 	/* Now we reset the xo_vap as if we were never here */
389331337658SMarcel Moolenaar 	va_end(xop->xo_vap);
389431337658SMarcel Moolenaar 	va_copy(xop->xo_vap, va_local);
389531337658SMarcel Moolenaar 	va_end(va_local);
389631337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
389731337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
389831337658SMarcel Moolenaar     }
389931337658SMarcel Moolenaar 
390031337658SMarcel Moolenaar     if (flags & XFF_ENCODE_ONLY) {
390131337658SMarcel Moolenaar 	/*
3902ee5cf116SPhil Shafer 	 * Even if this is encode-only, we need to go through the
390331337658SMarcel Moolenaar 	 * work of formatting it to make sure the args are cleared
3904264104f2SPhil Shafer 	 * from xo_vap.  This is not true when vlen is zero, since
3905264104f2SPhil Shafer 	 * that means our "value" isn't on the stack.
390631337658SMarcel Moolenaar 	 */
3907264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, NULL, 0, encoding, elen, flags);
390831337658SMarcel Moolenaar 	return;
390931337658SMarcel Moolenaar     }
391031337658SMarcel Moolenaar 
391131337658SMarcel Moolenaar     xo_line_ensure_open(xop, 0);
391231337658SMarcel Moolenaar 
3913d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
391431337658SMarcel Moolenaar 	xo_buf_indent(xop, xop->xo_indent_by);
391531337658SMarcel Moolenaar 
391631337658SMarcel Moolenaar     xo_data_append(xop, div_start, sizeof(div_start) - 1);
391731337658SMarcel Moolenaar     xo_data_append(xop, class, strlen(class));
391831337658SMarcel Moolenaar 
3919788ca347SMarcel Moolenaar     /*
3920788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3921788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3922788ca347SMarcel Moolenaar      */
3923788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3924788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3925788ca347SMarcel Moolenaar 
3926788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3927788ca347SMarcel Moolenaar     }
3928788ca347SMarcel Moolenaar 
392931337658SMarcel Moolenaar     if (name) {
393031337658SMarcel Moolenaar 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
393131337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
393231337658SMarcel Moolenaar 
393331337658SMarcel Moolenaar 	/*
393431337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
393531337658SMarcel Moolenaar 	 */
3936d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
3937d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
393831337658SMarcel Moolenaar 	    /*
393931337658SMarcel Moolenaar 	     * Note: We need the '+1' here because we know we've not
394031337658SMarcel Moolenaar 	     * added the closing quote.  We add one, knowing the quote
394131337658SMarcel Moolenaar 	     * will be added shortly.
394231337658SMarcel Moolenaar 	     */
394331337658SMarcel Moolenaar 	    xop->xo_units_offset =
394431337658SMarcel Moolenaar 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
394531337658SMarcel Moolenaar 	}
394631337658SMarcel Moolenaar 
3947d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_XPATH)) {
394831337658SMarcel Moolenaar 	    int i;
394931337658SMarcel Moolenaar 	    xo_stack_t *xsp;
395031337658SMarcel Moolenaar 
395131337658SMarcel Moolenaar 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
395231337658SMarcel Moolenaar 	    if (xop->xo_leading_xpath)
395331337658SMarcel Moolenaar 		xo_data_append(xop, xop->xo_leading_xpath,
395431337658SMarcel Moolenaar 			       strlen(xop->xo_leading_xpath));
395531337658SMarcel Moolenaar 
395631337658SMarcel Moolenaar 	    for (i = 0; i <= xop->xo_depth; i++) {
395731337658SMarcel Moolenaar 		xsp = &xop->xo_stack[i];
395831337658SMarcel Moolenaar 		if (xsp->xs_name == NULL)
395931337658SMarcel Moolenaar 		    continue;
396031337658SMarcel Moolenaar 
3961545ddfbeSMarcel Moolenaar 		/*
3962545ddfbeSMarcel Moolenaar 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3963545ddfbeSMarcel Moolenaar 		 * are directly under XSS_OPEN_INSTANCE frames so we
3964545ddfbeSMarcel Moolenaar 		 * don't need to put these in our XPath expressions.
3965545ddfbeSMarcel Moolenaar 		 */
3966545ddfbeSMarcel Moolenaar 		if (xsp->xs_state == XSS_OPEN_LIST
3967545ddfbeSMarcel Moolenaar 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3968545ddfbeSMarcel Moolenaar 		    continue;
3969545ddfbeSMarcel Moolenaar 
397031337658SMarcel Moolenaar 		xo_data_append(xop, "/", 1);
397131337658SMarcel Moolenaar 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
397231337658SMarcel Moolenaar 		if (xsp->xs_keys) {
397331337658SMarcel Moolenaar 		    /* Don't show keys for the key field */
397431337658SMarcel Moolenaar 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
397531337658SMarcel Moolenaar 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
397631337658SMarcel Moolenaar 		}
397731337658SMarcel Moolenaar 	    }
397831337658SMarcel Moolenaar 
397931337658SMarcel Moolenaar 	    xo_data_append(xop, "/", 1);
398031337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
398131337658SMarcel Moolenaar 	}
398231337658SMarcel Moolenaar 
3983d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
398431337658SMarcel Moolenaar 	    static char in_type[] = "\" data-type=\"";
398531337658SMarcel Moolenaar 	    static char in_help[] = "\" data-help=\"";
398631337658SMarcel Moolenaar 
398731337658SMarcel Moolenaar 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
398831337658SMarcel Moolenaar 	    if (xip) {
398931337658SMarcel Moolenaar 		if (xip->xi_type) {
399031337658SMarcel Moolenaar 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
399131337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
399231337658SMarcel Moolenaar 		}
399331337658SMarcel Moolenaar 		if (xip->xi_help) {
399431337658SMarcel Moolenaar 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
399531337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
399631337658SMarcel Moolenaar 		}
399731337658SMarcel Moolenaar 	    }
399831337658SMarcel Moolenaar 	}
399931337658SMarcel Moolenaar 
4000d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
400131337658SMarcel Moolenaar 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
400231337658SMarcel Moolenaar     }
400331337658SMarcel Moolenaar 
4004d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
40058a6eceffSPhil Shafer     ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp;
4006d1a0d267SMarcel Moolenaar 
400731337658SMarcel Moolenaar     xo_data_append(xop, div_end, sizeof(div_end) - 1);
400831337658SMarcel Moolenaar 
4009d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
4010d1a0d267SMarcel Moolenaar 
4011d1a0d267SMarcel Moolenaar     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4012d1a0d267SMarcel Moolenaar     save.xhs_columns = xop->xo_columns;
4013d1a0d267SMarcel Moolenaar     save.xhs_anchor_columns = xop->xo_anchor_columns;
4014d1a0d267SMarcel Moolenaar 
4015264104f2SPhil Shafer     xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4016d1a0d267SMarcel Moolenaar 
4017d1a0d267SMarcel Moolenaar     if (flags & XFF_HUMANIZE) {
4018d1a0d267SMarcel Moolenaar 	/*
4019d1a0d267SMarcel Moolenaar 	 * Unlike text style, we want to retain the original value and
4020d1a0d267SMarcel Moolenaar 	 * stuff it into the "data-number" attribute.
4021d1a0d267SMarcel Moolenaar 	 */
4022d1a0d267SMarcel Moolenaar 	static const char div_number[] = "\" data-number=\"";
40238a6eceffSPhil Shafer 	ssize_t div_len = sizeof(div_number) - 1;
4024d1a0d267SMarcel Moolenaar 
40258a6eceffSPhil Shafer 	ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
40268a6eceffSPhil Shafer 	ssize_t olen = end_offset - save.xhs_offset;
4027d1a0d267SMarcel Moolenaar 
4028d1a0d267SMarcel Moolenaar 	char *cp = alloca(olen + 1);
4029d1a0d267SMarcel Moolenaar 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
4030d1a0d267SMarcel Moolenaar 	cp[olen] = '\0';
4031d1a0d267SMarcel Moolenaar 
4032d1a0d267SMarcel Moolenaar 	xo_format_humanize(xop, xbp, &save, flags);
4033d1a0d267SMarcel Moolenaar 
4034d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, div_len + olen)) {
40358a6eceffSPhil Shafer 	    ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp;
4036d1a0d267SMarcel Moolenaar 
4037d1a0d267SMarcel Moolenaar 
4038d1a0d267SMarcel Moolenaar 	    /* Move the humanized string off to the left */
4039d1a0d267SMarcel Moolenaar 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
4040d1a0d267SMarcel Moolenaar 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
4041d1a0d267SMarcel Moolenaar 
4042d1a0d267SMarcel Moolenaar 	    /* Copy the data_number attribute name */
4043d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
4044d1a0d267SMarcel Moolenaar 
4045d1a0d267SMarcel Moolenaar 	    /* Copy the original long value */
4046d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
4047d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += div_len + olen;
4048d1a0d267SMarcel Moolenaar 	}
4049d1a0d267SMarcel Moolenaar     }
405031337658SMarcel Moolenaar 
405131337658SMarcel Moolenaar     xo_data_append(xop, div_close, sizeof(div_close) - 1);
405231337658SMarcel Moolenaar 
4053d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
405431337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
405531337658SMarcel Moolenaar }
405631337658SMarcel Moolenaar 
405731337658SMarcel Moolenaar static void
40588a6eceffSPhil Shafer xo_format_text (xo_handle_t *xop, const char *str, ssize_t len)
405931337658SMarcel Moolenaar {
4060788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
406131337658SMarcel Moolenaar     case XO_STYLE_TEXT:
406231337658SMarcel Moolenaar 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
406331337658SMarcel Moolenaar 	break;
406431337658SMarcel Moolenaar 
406531337658SMarcel Moolenaar     case XO_STYLE_HTML:
4066264104f2SPhil Shafer 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0, NULL, 0);
406731337658SMarcel Moolenaar 	break;
406831337658SMarcel Moolenaar     }
406931337658SMarcel Moolenaar }
407031337658SMarcel Moolenaar 
407131337658SMarcel Moolenaar static void
407242ff34c3SPhil Shafer xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
4073264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
407431337658SMarcel Moolenaar {
4075d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
40768a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4077d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
4078d1a0d267SMarcel Moolenaar 
4079788ca347SMarcel Moolenaar     static char div_open[] = "<div class=\"title";
4080788ca347SMarcel Moolenaar     static char div_middle[] = "\">";
408131337658SMarcel Moolenaar     static char div_close[] = "</div>";
408231337658SMarcel Moolenaar 
4083545ddfbeSMarcel Moolenaar     if (flen == 0) {
4084545ddfbeSMarcel Moolenaar 	fmt = "%s";
4085545ddfbeSMarcel Moolenaar 	flen = 2;
4086545ddfbeSMarcel Moolenaar     }
4087545ddfbeSMarcel Moolenaar 
4088788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
408931337658SMarcel Moolenaar     case XO_STYLE_XML:
409031337658SMarcel Moolenaar     case XO_STYLE_JSON:
4091d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4092d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
409331337658SMarcel Moolenaar 	/*
409431337658SMarcel Moolenaar 	 * Even though we don't care about text, we need to do
409531337658SMarcel Moolenaar 	 * enough parsing work to skip over the right bits of xo_vap.
409631337658SMarcel Moolenaar 	 */
4097264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
409831337658SMarcel Moolenaar 	return;
409931337658SMarcel Moolenaar     }
410031337658SMarcel Moolenaar 
410131337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
41028a6eceffSPhil Shafer     ssize_t start = xbp->xb_curp - xbp->xb_bufp;
41038a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - start;
41048a6eceffSPhil Shafer     ssize_t rc;
410531337658SMarcel Moolenaar 
4106788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
410731337658SMarcel Moolenaar 	xo_line_ensure_open(xop, 0);
4108d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
410931337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
411031337658SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
4111788ca347SMarcel Moolenaar 	xo_color_append_html(xop);
4112788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
411331337658SMarcel Moolenaar     }
411431337658SMarcel Moolenaar 
411531337658SMarcel Moolenaar     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
4116264104f2SPhil Shafer     if (vlen) {
411731337658SMarcel Moolenaar 	char *newfmt = alloca(flen + 1);
411831337658SMarcel Moolenaar 	memcpy(newfmt, fmt, flen);
411931337658SMarcel Moolenaar 	newfmt[flen] = '\0';
412031337658SMarcel Moolenaar 
412131337658SMarcel Moolenaar 	/* If len is non-zero, the format string apply to the name */
4122264104f2SPhil Shafer 	char *newstr = alloca(vlen + 1);
4123264104f2SPhil Shafer 	memcpy(newstr, value, vlen);
4124264104f2SPhil Shafer 	newstr[vlen] = '\0';
412531337658SMarcel Moolenaar 
4126264104f2SPhil Shafer 	if (newstr[vlen - 1] == 's') {
412731337658SMarcel Moolenaar 	    char *bp;
412831337658SMarcel Moolenaar 
412931337658SMarcel Moolenaar 	    rc = snprintf(NULL, 0, newfmt, newstr);
413031337658SMarcel Moolenaar 	    if (rc > 0) {
413131337658SMarcel Moolenaar 		/*
413231337658SMarcel Moolenaar 		 * We have to do this the hard way, since we might need
413331337658SMarcel Moolenaar 		 * the columns.
413431337658SMarcel Moolenaar 		 */
413531337658SMarcel Moolenaar 		bp = alloca(rc + 1);
413631337658SMarcel Moolenaar 		rc = snprintf(bp, rc + 1, newfmt, newstr);
4137d1a0d267SMarcel Moolenaar 
4138d1a0d267SMarcel Moolenaar 		xo_data_append_content(xop, bp, rc, flags);
413931337658SMarcel Moolenaar 	    }
414031337658SMarcel Moolenaar 	    goto move_along;
414131337658SMarcel Moolenaar 
414231337658SMarcel Moolenaar 	} else {
414331337658SMarcel Moolenaar 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4144d1a0d267SMarcel Moolenaar 	    if (rc >= left) {
414531337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, rc))
414631337658SMarcel Moolenaar 		    return;
414731337658SMarcel Moolenaar 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
414831337658SMarcel Moolenaar 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
414931337658SMarcel Moolenaar 	    }
415031337658SMarcel Moolenaar 
415131337658SMarcel Moolenaar 	    if (rc > 0) {
4152d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
415331337658SMarcel Moolenaar 		    xop->xo_columns += rc;
4154d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
415531337658SMarcel Moolenaar 		    xop->xo_anchor_columns += rc;
415631337658SMarcel Moolenaar 	    }
415731337658SMarcel Moolenaar 	}
415831337658SMarcel Moolenaar 
415931337658SMarcel Moolenaar     } else {
4160d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
416131337658SMarcel Moolenaar 
4162d1a0d267SMarcel Moolenaar 	/* xo_do_format_field moved curp, so we need to reset it */
416331337658SMarcel Moolenaar 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
416431337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + start;
416531337658SMarcel Moolenaar     }
416631337658SMarcel Moolenaar 
416731337658SMarcel Moolenaar     /* If we're styling HTML, then we need to escape it */
4168788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
416931337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
417031337658SMarcel Moolenaar     }
417131337658SMarcel Moolenaar 
417231337658SMarcel Moolenaar     if (rc > 0)
417331337658SMarcel Moolenaar 	xbp->xb_curp += rc;
417431337658SMarcel Moolenaar 
417531337658SMarcel Moolenaar  move_along:
4176788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
417731337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
4178d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
417931337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
418031337658SMarcel Moolenaar     }
418131337658SMarcel Moolenaar }
418231337658SMarcel Moolenaar 
418331337658SMarcel Moolenaar static void
418431337658SMarcel Moolenaar xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
418531337658SMarcel Moolenaar {
418631337658SMarcel Moolenaar     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
418731337658SMarcel Moolenaar 	xo_data_append(xop, ",", 1);
4188d1a0d267SMarcel Moolenaar 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
418931337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
419031337658SMarcel Moolenaar     } else
419131337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
419231337658SMarcel Moolenaar }
419331337658SMarcel Moolenaar 
419431337658SMarcel Moolenaar #if 0
419531337658SMarcel Moolenaar /* Useful debugging function */
419631337658SMarcel Moolenaar void
419731337658SMarcel Moolenaar xo_arg (xo_handle_t *xop);
419831337658SMarcel Moolenaar void
419931337658SMarcel Moolenaar xo_arg (xo_handle_t *xop)
420031337658SMarcel Moolenaar {
420131337658SMarcel Moolenaar     xop = xo_default(xop);
420231337658SMarcel Moolenaar     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
420331337658SMarcel Moolenaar }
420431337658SMarcel Moolenaar #endif /* 0 */
420531337658SMarcel Moolenaar 
420631337658SMarcel Moolenaar static void
42078a6eceffSPhil Shafer xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
4208264104f2SPhil Shafer 		 const char *value, ssize_t vlen,
4209264104f2SPhil Shafer 		 const char *fmt, ssize_t flen,
42108a6eceffSPhil Shafer 		 const char *encoding, ssize_t elen, xo_xff_flags_t flags)
421131337658SMarcel Moolenaar {
4212d1a0d267SMarcel Moolenaar     int pretty = XOF_ISSET(xop, XOF_PRETTY);
421331337658SMarcel Moolenaar     int quote;
421431337658SMarcel Moolenaar 
4215545ddfbeSMarcel Moolenaar     /*
4216545ddfbeSMarcel Moolenaar      * Before we emit a value, we need to know that the frame is ready.
4217545ddfbeSMarcel Moolenaar      */
4218545ddfbeSMarcel Moolenaar     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4219545ddfbeSMarcel Moolenaar 
4220545ddfbeSMarcel Moolenaar     if (flags & XFF_LEAF_LIST) {
4221545ddfbeSMarcel Moolenaar 	/*
4222545ddfbeSMarcel Moolenaar 	 * Check if we've already started to emit normal leafs
4223545ddfbeSMarcel Moolenaar 	 * or if we're not in a leaf list.
4224545ddfbeSMarcel Moolenaar 	 */
4225545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4226545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4227545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4228545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4229545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4230545ddfbeSMarcel Moolenaar 
42318a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4232545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4233545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4234545ddfbeSMarcel Moolenaar 	    else
4235545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4236545ddfbeSMarcel Moolenaar 	}
4237545ddfbeSMarcel Moolenaar 
4238545ddfbeSMarcel Moolenaar 	xsp = &xop->xo_stack[xop->xo_depth];
4239545ddfbeSMarcel Moolenaar 	if (xsp->xs_name) {
4240545ddfbeSMarcel Moolenaar 	    name = xsp->xs_name;
4241545ddfbeSMarcel Moolenaar 	    nlen = strlen(name);
4242545ddfbeSMarcel Moolenaar 	}
4243545ddfbeSMarcel Moolenaar 
4244545ddfbeSMarcel Moolenaar     } else if (flags & XFF_KEY) {
4245545ddfbeSMarcel Moolenaar 	/* Emitting a 'k' (key) field */
4246545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4247545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4248545ddfbeSMarcel Moolenaar 		       nlen, name);
4249545ddfbeSMarcel Moolenaar 
4250545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4251545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4252545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4253545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4254545ddfbeSMarcel Moolenaar 
42558a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4256545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4257545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4258545ddfbeSMarcel Moolenaar 	    else
4259545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4260545ddfbeSMarcel Moolenaar 
4261545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4262545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT_KEY;
4263545ddfbeSMarcel Moolenaar 	}
4264545ddfbeSMarcel Moolenaar 
4265545ddfbeSMarcel Moolenaar     } else {
4266545ddfbeSMarcel Moolenaar 	/* Emitting a normal value field */
4267545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4268545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT)) {
4269545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4270545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4271545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4272545ddfbeSMarcel Moolenaar 
42738a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4274545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4275545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4276545ddfbeSMarcel Moolenaar 	    else
4277545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4278545ddfbeSMarcel Moolenaar 
4279545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4280545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT;
4281545ddfbeSMarcel Moolenaar 	}
4282545ddfbeSMarcel Moolenaar     }
4283545ddfbeSMarcel Moolenaar 
4284d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
4285d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
4286d1a0d267SMarcel Moolenaar 
4287788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
428831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
428931337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
429031337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4291d1a0d267SMarcel Moolenaar 
4292d1a0d267SMarcel Moolenaar 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4293d1a0d267SMarcel Moolenaar 	save.xhs_columns = xop->xo_columns;
4294d1a0d267SMarcel Moolenaar 	save.xhs_anchor_columns = xop->xo_anchor_columns;
4295d1a0d267SMarcel Moolenaar 
4296264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4297d1a0d267SMarcel Moolenaar 
4298d1a0d267SMarcel Moolenaar 	if (flags & XFF_HUMANIZE)
4299d1a0d267SMarcel Moolenaar 	    xo_format_humanize(xop, xbp, &save, flags);
430031337658SMarcel Moolenaar 	break;
430131337658SMarcel Moolenaar 
430231337658SMarcel Moolenaar     case XO_STYLE_HTML:
430331337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
430431337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4305d1a0d267SMarcel Moolenaar 
4306264104f2SPhil Shafer 	xo_buf_append_div(xop, "data", flags, name, nlen, value, vlen,
4307264104f2SPhil Shafer 			  fmt, flen, encoding, elen);
430831337658SMarcel Moolenaar 	break;
430931337658SMarcel Moolenaar 
431031337658SMarcel Moolenaar     case XO_STYLE_XML:
431131337658SMarcel Moolenaar 	/*
431231337658SMarcel Moolenaar 	 * Even though we're not making output, we still need to
431331337658SMarcel Moolenaar 	 * let the formatting code handle the va_arg popping.
431431337658SMarcel Moolenaar 	 */
431531337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4316264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
431731337658SMarcel Moolenaar 	    break;
431831337658SMarcel Moolenaar 	}
431931337658SMarcel Moolenaar 
432031337658SMarcel Moolenaar 	if (encoding) {
4321264104f2SPhil Shafer    	    fmt = encoding;
432231337658SMarcel Moolenaar 	    flen = elen;
432331337658SMarcel Moolenaar 	} else {
432431337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4325264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
432631337658SMarcel Moolenaar 	    enc[flen] = '\0';
4327264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4328264104f2SPhil Shafer 	    flen = strlen(fmt);
432931337658SMarcel Moolenaar 	}
433031337658SMarcel Moolenaar 
433131337658SMarcel Moolenaar 	if (nlen == 0) {
433231337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4333264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
433431337658SMarcel Moolenaar 	    name = missing;
433531337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
433631337658SMarcel Moolenaar 	}
433731337658SMarcel Moolenaar 
433831337658SMarcel Moolenaar 	if (pretty)
433931337658SMarcel Moolenaar 	    xo_buf_indent(xop, -1);
434031337658SMarcel Moolenaar 	xo_data_append(xop, "<", 1);
434131337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
434231337658SMarcel Moolenaar 
434331337658SMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
434431337658SMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
434531337658SMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
434631337658SMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
434731337658SMarcel Moolenaar 	}
434831337658SMarcel Moolenaar 
434931337658SMarcel Moolenaar 	/*
435031337658SMarcel Moolenaar 	 * We indicate 'key' fields using the 'key' attribute.  While
435131337658SMarcel Moolenaar 	 * this is really committing the crime of mixing meta-data with
435231337658SMarcel Moolenaar 	 * data, it's often useful.  Especially when format meta-data is
435331337658SMarcel Moolenaar 	 * difficult to come by.
435431337658SMarcel Moolenaar 	 */
4355d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
435631337658SMarcel Moolenaar 	    static char attr[] = " key=\"key\"";
435731337658SMarcel Moolenaar 	    xo_data_append(xop, attr, sizeof(attr) - 1);
435831337658SMarcel Moolenaar 	}
435931337658SMarcel Moolenaar 
436031337658SMarcel Moolenaar 	/*
436131337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
436231337658SMarcel Moolenaar 	 */
4363d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
4364d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
436531337658SMarcel Moolenaar 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
436631337658SMarcel Moolenaar 	}
436731337658SMarcel Moolenaar 
436831337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
4369264104f2SPhil Shafer 
4370264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4371264104f2SPhil Shafer 
437231337658SMarcel Moolenaar 	xo_data_append(xop, "</", 2);
437331337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
437431337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
437531337658SMarcel Moolenaar 	if (pretty)
437631337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
437731337658SMarcel Moolenaar 	break;
437831337658SMarcel Moolenaar 
437931337658SMarcel Moolenaar     case XO_STYLE_JSON:
438031337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4381264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
438231337658SMarcel Moolenaar 	    break;
438331337658SMarcel Moolenaar 	}
438431337658SMarcel Moolenaar 
438531337658SMarcel Moolenaar 	if (encoding) {
4386264104f2SPhil Shafer 	    fmt = encoding;
438731337658SMarcel Moolenaar 	    flen = elen;
438831337658SMarcel Moolenaar 	} else {
438931337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4390264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
439131337658SMarcel Moolenaar 	    enc[flen] = '\0';
4392264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4393264104f2SPhil Shafer 	    flen = strlen(fmt);
439431337658SMarcel Moolenaar 	}
439531337658SMarcel Moolenaar 
43968a6eceffSPhil Shafer 	int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
43978a6eceffSPhil Shafer 	    ? 0 : 1;
439831337658SMarcel Moolenaar 
439931337658SMarcel Moolenaar 	xo_format_prep(xop, flags);
440031337658SMarcel Moolenaar 
440131337658SMarcel Moolenaar 	if (flags & XFF_QUOTE)
440231337658SMarcel Moolenaar 	    quote = 1;
440331337658SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
440431337658SMarcel Moolenaar 	    quote = 0;
4405264104f2SPhil Shafer 	else if (vlen != 0)
4406264104f2SPhil Shafer 	    quote = 1;
440731337658SMarcel Moolenaar 	else if (flen == 0) {
440831337658SMarcel Moolenaar 	    quote = 0;
4409264104f2SPhil Shafer 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
441031337658SMarcel Moolenaar 	    flen = 4;
4411264104f2SPhil Shafer 	} else if (strchr("diouDOUeEfFgG", fmt[flen - 1]) == NULL)
441231337658SMarcel Moolenaar 	    quote = 1;
441331337658SMarcel Moolenaar 	else
441431337658SMarcel Moolenaar 	    quote = 0;
441531337658SMarcel Moolenaar 
441631337658SMarcel Moolenaar 	if (nlen == 0) {
441731337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4418264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
441931337658SMarcel Moolenaar 	    name = missing;
442031337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
442131337658SMarcel Moolenaar 	}
442231337658SMarcel Moolenaar 
442331337658SMarcel Moolenaar 	if (flags & XFF_LEAF_LIST) {
4424788ca347SMarcel Moolenaar 	    if (!first && pretty)
4425788ca347SMarcel Moolenaar 		xo_data_append(xop, "\n", 1);
4426788ca347SMarcel Moolenaar 	    if (pretty)
442731337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
442831337658SMarcel Moolenaar 	} else {
442931337658SMarcel Moolenaar 	    if (pretty)
443031337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
443131337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
443231337658SMarcel Moolenaar 
443331337658SMarcel Moolenaar 	    xbp = &xop->xo_data;
44348a6eceffSPhil Shafer 	    ssize_t off = xbp->xb_curp - xbp->xb_bufp;
443531337658SMarcel Moolenaar 
443631337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
443731337658SMarcel Moolenaar 
4438d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
44398a6eceffSPhil Shafer 		ssize_t coff = xbp->xb_curp - xbp->xb_bufp;
44408a6eceffSPhil Shafer 		for ( ; off < coff; off++)
444131337658SMarcel Moolenaar 		    if (xbp->xb_bufp[off] == '-')
444231337658SMarcel Moolenaar 			xbp->xb_bufp[off] = '_';
444331337658SMarcel Moolenaar 	    }
444431337658SMarcel Moolenaar 	    xo_data_append(xop, "\":", 2);
444531337658SMarcel Moolenaar 	    if (pretty)
444631337658SMarcel Moolenaar 	        xo_data_append(xop, " ", 1);
4447788ca347SMarcel Moolenaar 	}
4448788ca347SMarcel Moolenaar 
444931337658SMarcel Moolenaar 	if (quote)
445031337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
445131337658SMarcel Moolenaar 
4452264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
445331337658SMarcel Moolenaar 
445431337658SMarcel Moolenaar 	if (quote)
445531337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
445631337658SMarcel Moolenaar 	break;
4457d1a0d267SMarcel Moolenaar 
4458d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4459d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4460264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4461d1a0d267SMarcel Moolenaar 	    break;
4462d1a0d267SMarcel Moolenaar 	}
4463d1a0d267SMarcel Moolenaar 
4464d1a0d267SMarcel Moolenaar 	if (encoding) {
4465264104f2SPhil Shafer 	    fmt = encoding;
4466d1a0d267SMarcel Moolenaar 	    flen = elen;
4467d1a0d267SMarcel Moolenaar 	} else {
4468d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4469264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
4470d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4471264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4472264104f2SPhil Shafer 	    flen = strlen(fmt);
4473d1a0d267SMarcel Moolenaar 	}
4474d1a0d267SMarcel Moolenaar 
4475d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4476d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4477264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
4478d1a0d267SMarcel Moolenaar 	    name = missing;
4479d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4480d1a0d267SMarcel Moolenaar 	}
4481d1a0d267SMarcel Moolenaar 
4482d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
4483d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "=\"", 2);
4484264104f2SPhil Shafer 
4485264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4486264104f2SPhil Shafer 
4487d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "\" ", 2);
4488d1a0d267SMarcel Moolenaar 	break;
4489d1a0d267SMarcel Moolenaar 
4490d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4491d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4492264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4493d1a0d267SMarcel Moolenaar 	    break;
4494d1a0d267SMarcel Moolenaar 	}
4495d1a0d267SMarcel Moolenaar 
4496d1a0d267SMarcel Moolenaar 	if (flags & XFF_QUOTE)
4497d1a0d267SMarcel Moolenaar 	    quote = 1;
4498d1a0d267SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
4499d1a0d267SMarcel Moolenaar 	    quote = 0;
4500d1a0d267SMarcel Moolenaar 	else if (flen == 0) {
4501d1a0d267SMarcel Moolenaar 	    quote = 0;
4502264104f2SPhil Shafer 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
4503d1a0d267SMarcel Moolenaar 	    flen = 4;
4504264104f2SPhil Shafer 	} else if (strchr("diouxXDOUeEfFgGaAcCp", fmt[flen - 1]) == NULL)
4505d1a0d267SMarcel Moolenaar 	    quote = 1;
4506d1a0d267SMarcel Moolenaar 	else
4507d1a0d267SMarcel Moolenaar 	    quote = 0;
4508d1a0d267SMarcel Moolenaar 
4509d1a0d267SMarcel Moolenaar 	if (encoding) {
4510264104f2SPhil Shafer 	    fmt = encoding;
4511d1a0d267SMarcel Moolenaar 	    flen = elen;
4512d1a0d267SMarcel Moolenaar 	} else {
4513d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4514264104f2SPhil Shafer 	    memcpy(enc, fmt, flen);
4515d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4516264104f2SPhil Shafer 	    fmt = xo_fix_encoding(xop, enc);
4517264104f2SPhil Shafer 	    flen = strlen(fmt);
4518d1a0d267SMarcel Moolenaar 	}
4519d1a0d267SMarcel Moolenaar 
4520d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4521d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4522264104f2SPhil Shafer 	    xo_failure(xop, "missing field name: %s", fmt);
4523d1a0d267SMarcel Moolenaar 	    name = missing;
4524d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4525d1a0d267SMarcel Moolenaar 	}
4526d1a0d267SMarcel Moolenaar 
45278a6eceffSPhil Shafer 	ssize_t name_offset = xo_buf_offset(&xop->xo_data);
4528d1a0d267SMarcel Moolenaar 	xo_data_append(xop, name, nlen);
4529d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4530d1a0d267SMarcel Moolenaar 
45318a6eceffSPhil Shafer 	ssize_t value_offset = xo_buf_offset(&xop->xo_data);
4532264104f2SPhil Shafer 
4533264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4534264104f2SPhil Shafer 
4535d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4536d1a0d267SMarcel Moolenaar 
4537d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4538d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, name_offset),
4539f2b7bf8aSPhil Shafer 			  xo_buf_data(&xop->xo_data, value_offset), flags);
4540d1a0d267SMarcel Moolenaar 	xo_buf_reset(&xop->xo_data);
4541d1a0d267SMarcel Moolenaar 	break;
454231337658SMarcel Moolenaar     }
454331337658SMarcel Moolenaar }
454431337658SMarcel Moolenaar 
454531337658SMarcel Moolenaar static void
454642ff34c3SPhil Shafer xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
45478a6eceffSPhil Shafer 		       const char *str, ssize_t len)
4548d1a0d267SMarcel Moolenaar {
4549d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
45508a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4551d1a0d267SMarcel Moolenaar 
4552d1a0d267SMarcel Moolenaar     /* Start by discarding previous domain */
4553d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
4554d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
4555d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
4556d1a0d267SMarcel Moolenaar     }
4557d1a0d267SMarcel Moolenaar 
4558d1a0d267SMarcel Moolenaar     /* An empty {G:} means no domainname */
4559d1a0d267SMarcel Moolenaar     if (len == 0 && flen == 0)
4560d1a0d267SMarcel Moolenaar 	return;
4561d1a0d267SMarcel Moolenaar 
45628a6eceffSPhil Shafer     ssize_t start_offset = -1;
4563d1a0d267SMarcel Moolenaar     if (len == 0 && flen != 0) {
4564d1a0d267SMarcel Moolenaar 	/* Need to do format the data to get the domainname from args */
4565d1a0d267SMarcel Moolenaar 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4566d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4567d1a0d267SMarcel Moolenaar 
45688a6eceffSPhil Shafer 	ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4569d1a0d267SMarcel Moolenaar 	len = end_offset - start_offset;
4570d1a0d267SMarcel Moolenaar 	str = xop->xo_data.xb_bufp + start_offset;
4571d1a0d267SMarcel Moolenaar     }
4572d1a0d267SMarcel Moolenaar 
4573d1a0d267SMarcel Moolenaar     xop->xo_gt_domain = xo_strndup(str, len);
4574d1a0d267SMarcel Moolenaar 
4575d1a0d267SMarcel Moolenaar     /* Reset the current buffer point to avoid emitting the name as output */
4576d1a0d267SMarcel Moolenaar     if (start_offset >= 0)
4577d1a0d267SMarcel Moolenaar 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4578d1a0d267SMarcel Moolenaar }
4579d1a0d267SMarcel Moolenaar 
4580d1a0d267SMarcel Moolenaar static void
458131337658SMarcel Moolenaar xo_format_content (xo_handle_t *xop, const char *class_name,
4582d1a0d267SMarcel Moolenaar 		   const char *tag_name,
4583264104f2SPhil Shafer 		   const char *value, ssize_t vlen,
4584264104f2SPhil Shafer 		   const char *fmt, ssize_t flen,
4585d1a0d267SMarcel Moolenaar 		   xo_xff_flags_t flags)
458631337658SMarcel Moolenaar {
4587788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
458831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
4589264104f2SPhil Shafer 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
459031337658SMarcel Moolenaar 	break;
459131337658SMarcel Moolenaar 
459231337658SMarcel Moolenaar     case XO_STYLE_HTML:
4593264104f2SPhil Shafer 	xo_buf_append_div(xop, class_name, flags, NULL, 0,
4594264104f2SPhil Shafer 			  value, vlen, fmt, flen, NULL, 0);
459531337658SMarcel Moolenaar 	break;
459631337658SMarcel Moolenaar 
459731337658SMarcel Moolenaar     case XO_STYLE_XML:
4598d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
4599d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4600d1a0d267SMarcel Moolenaar 	if (tag_name) {
4601d1a0d267SMarcel Moolenaar 	    xo_open_container_h(xop, tag_name);
4602264104f2SPhil Shafer 	    xo_format_value(xop, "message", 7, value, vlen,
4603264104f2SPhil Shafer 			    fmt, flen, NULL, 0, flags);
4604d1a0d267SMarcel Moolenaar 	    xo_close_container_h(xop, tag_name);
460531337658SMarcel Moolenaar 
460631337658SMarcel Moolenaar 	} else {
460731337658SMarcel Moolenaar 	    /*
460831337658SMarcel Moolenaar 	     * Even though we don't care about labels, we need to do
460931337658SMarcel Moolenaar 	     * enough parsing work to skip over the right bits of xo_vap.
461031337658SMarcel Moolenaar 	     */
4611264104f2SPhil Shafer 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
461231337658SMarcel Moolenaar 	}
461331337658SMarcel Moolenaar 	break;
461431337658SMarcel Moolenaar 
4615d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4616264104f2SPhil Shafer 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
461731337658SMarcel Moolenaar 	break;
461831337658SMarcel Moolenaar     }
461931337658SMarcel Moolenaar }
462031337658SMarcel Moolenaar 
4621788ca347SMarcel Moolenaar static const char *xo_color_names[] = {
4622788ca347SMarcel Moolenaar     "default",	/* XO_COL_DEFAULT */
4623788ca347SMarcel Moolenaar     "black",	/* XO_COL_BLACK */
4624788ca347SMarcel Moolenaar     "red",	/* XO_CLOR_RED */
4625788ca347SMarcel Moolenaar     "green",	/* XO_COL_GREEN */
4626788ca347SMarcel Moolenaar     "yellow",	/* XO_COL_YELLOW */
4627788ca347SMarcel Moolenaar     "blue",	/* XO_COL_BLUE */
4628788ca347SMarcel Moolenaar     "magenta",	/* XO_COL_MAGENTA */
4629788ca347SMarcel Moolenaar     "cyan",	/* XO_COL_CYAN */
4630788ca347SMarcel Moolenaar     "white",	/* XO_COL_WHITE */
4631788ca347SMarcel Moolenaar     NULL
4632788ca347SMarcel Moolenaar };
4633788ca347SMarcel Moolenaar 
4634788ca347SMarcel Moolenaar static int
4635788ca347SMarcel Moolenaar xo_color_find (const char *str)
4636788ca347SMarcel Moolenaar {
4637788ca347SMarcel Moolenaar     int i;
4638788ca347SMarcel Moolenaar 
4639788ca347SMarcel Moolenaar     for (i = 0; xo_color_names[i]; i++) {
4640788ca347SMarcel Moolenaar 	if (strcmp(xo_color_names[i], str) == 0)
4641788ca347SMarcel Moolenaar 	    return i;
4642788ca347SMarcel Moolenaar     }
4643788ca347SMarcel Moolenaar 
4644788ca347SMarcel Moolenaar     return -1;
4645788ca347SMarcel Moolenaar }
4646788ca347SMarcel Moolenaar 
4647788ca347SMarcel Moolenaar static const char *xo_effect_names[] = {
4648788ca347SMarcel Moolenaar     "reset",			/* XO_EFF_RESET */
4649788ca347SMarcel Moolenaar     "normal",			/* XO_EFF_NORMAL */
4650788ca347SMarcel Moolenaar     "bold",			/* XO_EFF_BOLD */
4651788ca347SMarcel Moolenaar     "underline",		/* XO_EFF_UNDERLINE */
4652788ca347SMarcel Moolenaar     "inverse",			/* XO_EFF_INVERSE */
4653788ca347SMarcel Moolenaar     NULL
4654788ca347SMarcel Moolenaar };
4655788ca347SMarcel Moolenaar 
4656788ca347SMarcel Moolenaar static const char *xo_effect_on_codes[] = {
4657788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4658788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4659788ca347SMarcel Moolenaar     "1",			/* XO_EFF_BOLD */
4660788ca347SMarcel Moolenaar     "4",			/* XO_EFF_UNDERLINE */
4661788ca347SMarcel Moolenaar     "7",			/* XO_EFF_INVERSE */
4662788ca347SMarcel Moolenaar     NULL
4663788ca347SMarcel Moolenaar };
4664788ca347SMarcel Moolenaar 
4665788ca347SMarcel Moolenaar #if 0
4666788ca347SMarcel Moolenaar /*
4667788ca347SMarcel Moolenaar  * See comment below re: joy of terminal standards.  These can
4668788ca347SMarcel Moolenaar  * be use by just adding:
4669d1a0d267SMarcel Moolenaar  * +	if (newp->xoc_effects & bit)
4670788ca347SMarcel Moolenaar  *	    code = xo_effect_on_codes[i];
4671788ca347SMarcel Moolenaar  * +	else
4672788ca347SMarcel Moolenaar  * +	    code = xo_effect_off_codes[i];
4673788ca347SMarcel Moolenaar  * in xo_color_handle_text.
4674788ca347SMarcel Moolenaar  */
4675788ca347SMarcel Moolenaar static const char *xo_effect_off_codes[] = {
4676788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4677788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4678788ca347SMarcel Moolenaar     "21",			/* XO_EFF_BOLD */
4679788ca347SMarcel Moolenaar     "24",			/* XO_EFF_UNDERLINE */
4680788ca347SMarcel Moolenaar     "27",			/* XO_EFF_INVERSE */
4681788ca347SMarcel Moolenaar     NULL
4682788ca347SMarcel Moolenaar };
4683788ca347SMarcel Moolenaar #endif /* 0 */
4684788ca347SMarcel Moolenaar 
4685788ca347SMarcel Moolenaar static int
4686788ca347SMarcel Moolenaar xo_effect_find (const char *str)
4687788ca347SMarcel Moolenaar {
4688788ca347SMarcel Moolenaar     int i;
4689788ca347SMarcel Moolenaar 
4690788ca347SMarcel Moolenaar     for (i = 0; xo_effect_names[i]; i++) {
4691788ca347SMarcel Moolenaar 	if (strcmp(xo_effect_names[i], str) == 0)
4692788ca347SMarcel Moolenaar 	    return i;
4693788ca347SMarcel Moolenaar     }
4694788ca347SMarcel Moolenaar 
4695788ca347SMarcel Moolenaar     return -1;
4696788ca347SMarcel Moolenaar }
4697788ca347SMarcel Moolenaar 
4698788ca347SMarcel Moolenaar static void
4699788ca347SMarcel Moolenaar xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4700788ca347SMarcel Moolenaar {
4701788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4702788ca347SMarcel Moolenaar     return;
4703788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4704788ca347SMarcel Moolenaar 
4705788ca347SMarcel Moolenaar     char *cp, *ep, *np, *xp;
47068a6eceffSPhil Shafer     ssize_t len = strlen(str);
4707788ca347SMarcel Moolenaar     int rc;
4708788ca347SMarcel Moolenaar 
4709788ca347SMarcel Moolenaar     /*
4710788ca347SMarcel Moolenaar      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4711788ca347SMarcel Moolenaar      */
4712788ca347SMarcel Moolenaar     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4713788ca347SMarcel Moolenaar 	/* Trim leading whitespace */
4714788ca347SMarcel Moolenaar 	while (isspace((int) *cp))
4715788ca347SMarcel Moolenaar 	    cp += 1;
4716788ca347SMarcel Moolenaar 
4717788ca347SMarcel Moolenaar 	np = strchr(cp, ',');
4718788ca347SMarcel Moolenaar 	if (np)
4719788ca347SMarcel Moolenaar 	    *np++ = '\0';
4720788ca347SMarcel Moolenaar 
4721788ca347SMarcel Moolenaar 	/* Trim trailing whitespace */
4722788ca347SMarcel Moolenaar 	xp = cp + strlen(cp) - 1;
4723788ca347SMarcel Moolenaar 	while (isspace(*xp) && xp > cp)
4724788ca347SMarcel Moolenaar 	    *xp-- = '\0';
4725788ca347SMarcel Moolenaar 
4726788ca347SMarcel Moolenaar 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4727788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4728788ca347SMarcel Moolenaar 	    if (rc < 0)
4729788ca347SMarcel Moolenaar 		goto unknown;
4730788ca347SMarcel Moolenaar 
4731788ca347SMarcel Moolenaar 	    xocp->xoc_col_fg = rc;
4732788ca347SMarcel Moolenaar 
4733788ca347SMarcel Moolenaar 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4734788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4735788ca347SMarcel Moolenaar 	    if (rc < 0)
4736788ca347SMarcel Moolenaar 		goto unknown;
4737788ca347SMarcel Moolenaar 	    xocp->xoc_col_bg = rc;
4738788ca347SMarcel Moolenaar 
4739788ca347SMarcel Moolenaar 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4740788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp + 3);
4741788ca347SMarcel Moolenaar 	    if (rc < 0)
4742788ca347SMarcel Moolenaar 		goto unknown;
4743788ca347SMarcel Moolenaar 	    xocp->xoc_effects &= ~(1 << rc);
4744788ca347SMarcel Moolenaar 
4745788ca347SMarcel Moolenaar 	} else {
4746788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp);
4747788ca347SMarcel Moolenaar 	    if (rc < 0)
4748788ca347SMarcel Moolenaar 		goto unknown;
4749788ca347SMarcel Moolenaar 	    xocp->xoc_effects |= 1 << rc;
4750788ca347SMarcel Moolenaar 
4751788ca347SMarcel Moolenaar 	    switch (1 << rc) {
4752788ca347SMarcel Moolenaar 	    case XO_EFF_RESET:
4753788ca347SMarcel Moolenaar 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4754788ca347SMarcel Moolenaar 		/* Note: not "|=" since we want to wipe out the old value */
4755788ca347SMarcel Moolenaar 		xocp->xoc_effects = XO_EFF_RESET;
4756788ca347SMarcel Moolenaar 		break;
4757788ca347SMarcel Moolenaar 
4758788ca347SMarcel Moolenaar 	    case XO_EFF_NORMAL:
4759788ca347SMarcel Moolenaar 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4760788ca347SMarcel Moolenaar 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4761788ca347SMarcel Moolenaar 		break;
4762788ca347SMarcel Moolenaar 	    }
4763788ca347SMarcel Moolenaar 	}
4764788ca347SMarcel Moolenaar 	continue;
4765788ca347SMarcel Moolenaar 
4766788ca347SMarcel Moolenaar     unknown:
4767d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN))
4768788ca347SMarcel Moolenaar 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4769788ca347SMarcel Moolenaar     }
4770788ca347SMarcel Moolenaar }
4771788ca347SMarcel Moolenaar 
4772788ca347SMarcel Moolenaar static inline int
4773788ca347SMarcel Moolenaar xo_colors_enabled (xo_handle_t *xop UNUSED)
4774788ca347SMarcel Moolenaar {
4775788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4776788ca347SMarcel Moolenaar     return 0;
4777788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
4778d1a0d267SMarcel Moolenaar     return XOF_ISSET(xop, XOF_COLOR);
4779788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4780788ca347SMarcel Moolenaar }
4781788ca347SMarcel Moolenaar 
4782f2b7bf8aSPhil Shafer /*
4783f2b7bf8aSPhil Shafer  * If the color map is in use (--libxo colors=xxxx), then update
4784f2b7bf8aSPhil Shafer  * the incoming foreground and background colors from the map.
4785f2b7bf8aSPhil Shafer  */
4786f2b7bf8aSPhil Shafer static void
4787f2b7bf8aSPhil Shafer xo_colors_update (xo_handle_t *xop, xo_colors_t *newp)
4788f2b7bf8aSPhil Shafer {
4789f2b7bf8aSPhil Shafer #ifdef LIBXO_TEXT_ONLY
4790f2b7bf8aSPhil Shafer     return;
4791f2b7bf8aSPhil Shafer #endif /* LIBXO_TEXT_ONLY */
4792f2b7bf8aSPhil Shafer 
4793f2b7bf8aSPhil Shafer     xo_color_t fg = newp->xoc_col_fg;
4794f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS)
4795f2b7bf8aSPhil Shafer 	fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */
4796f2b7bf8aSPhil Shafer     newp->xoc_col_fg = fg;
4797f2b7bf8aSPhil Shafer 
4798f2b7bf8aSPhil Shafer     xo_color_t bg = newp->xoc_col_bg;
4799f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS)
4800f2b7bf8aSPhil Shafer 	bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */
4801f2b7bf8aSPhil Shafer     newp->xoc_col_bg = bg;
4802f2b7bf8aSPhil Shafer }
4803f2b7bf8aSPhil Shafer 
4804788ca347SMarcel Moolenaar static void
480542ff34c3SPhil Shafer xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4806788ca347SMarcel Moolenaar {
4807788ca347SMarcel Moolenaar     char buf[BUFSIZ];
4808788ca347SMarcel Moolenaar     char *cp = buf, *ep = buf + sizeof(buf);
4809788ca347SMarcel Moolenaar     unsigned i, bit;
4810788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
481142ff34c3SPhil Shafer     const char *code = NULL;
4812788ca347SMarcel Moolenaar 
4813788ca347SMarcel Moolenaar     /*
4814788ca347SMarcel Moolenaar      * Start the buffer with an escape.  We don't want to add the '['
4815788ca347SMarcel Moolenaar      * now, since we let xo_effect_text_add unconditionally add the ';'.
4816788ca347SMarcel Moolenaar      * We'll replace the first ';' with a '[' when we're done.
4817788ca347SMarcel Moolenaar      */
4818788ca347SMarcel Moolenaar     *cp++ = 0x1b;		/* Escape */
4819788ca347SMarcel Moolenaar 
4820788ca347SMarcel Moolenaar     /*
4821788ca347SMarcel Moolenaar      * Terminals were designed back in the age before "certainty" was
4822788ca347SMarcel Moolenaar      * invented, when standards were more what you'd call "guidelines"
4823788ca347SMarcel Moolenaar      * than actual rules.  Anyway we can't depend on them to operate
4824788ca347SMarcel Moolenaar      * correctly.  So when display attributes are changed, we punt,
4825788ca347SMarcel Moolenaar      * reseting them all and turning back on the ones we want to keep.
4826788ca347SMarcel Moolenaar      * Longer, but should be completely reliable.  Savvy?
4827788ca347SMarcel Moolenaar      */
4828788ca347SMarcel Moolenaar     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4829788ca347SMarcel Moolenaar 	newp->xoc_effects |= XO_EFF_RESET;
4830788ca347SMarcel Moolenaar 	oldp->xoc_effects = 0;
4831788ca347SMarcel Moolenaar     }
4832788ca347SMarcel Moolenaar 
4833788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4834788ca347SMarcel Moolenaar 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4835788ca347SMarcel Moolenaar 	    continue;
4836788ca347SMarcel Moolenaar 
4837788ca347SMarcel Moolenaar 	code = xo_effect_on_codes[i];
4838788ca347SMarcel Moolenaar 
4839788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";%s", code);
4840788ca347SMarcel Moolenaar 	if (cp >= ep)
4841788ca347SMarcel Moolenaar 	    return;		/* Should not occur */
4842788ca347SMarcel Moolenaar 
4843788ca347SMarcel Moolenaar 	if (bit == XO_EFF_RESET) {
4844788ca347SMarcel Moolenaar 	    /* Mark up the old value so we can detect current values as new */
4845788ca347SMarcel Moolenaar 	    oldp->xoc_effects = 0;
4846788ca347SMarcel Moolenaar 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4847788ca347SMarcel Moolenaar 	}
4848788ca347SMarcel Moolenaar     }
4849788ca347SMarcel Moolenaar 
4850f2b7bf8aSPhil Shafer     xo_color_t fg = newp->xoc_col_fg;
4851f2b7bf8aSPhil Shafer     if (fg != oldp->xoc_col_fg) {
4852788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";3%u",
4853f2b7bf8aSPhil Shafer 		       (fg != XO_COL_DEFAULT) ? fg - 1 : 9);
4854788ca347SMarcel Moolenaar     }
4855788ca347SMarcel Moolenaar 
4856f2b7bf8aSPhil Shafer     xo_color_t bg = newp->xoc_col_bg;
4857f2b7bf8aSPhil Shafer     if (bg != oldp->xoc_col_bg) {
4858788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";4%u",
4859f2b7bf8aSPhil Shafer 		       (bg != XO_COL_DEFAULT) ? bg - 1 : 9);
4860788ca347SMarcel Moolenaar     }
4861788ca347SMarcel Moolenaar 
4862788ca347SMarcel Moolenaar     if (cp - buf != 1 && cp < ep - 3) {
4863788ca347SMarcel Moolenaar 	buf[1] = '[';		/* Overwrite leading ';' */
4864788ca347SMarcel Moolenaar 	*cp++ = 'm';
4865788ca347SMarcel Moolenaar 	*cp = '\0';
4866788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4867788ca347SMarcel Moolenaar     }
4868788ca347SMarcel Moolenaar }
4869788ca347SMarcel Moolenaar 
4870788ca347SMarcel Moolenaar static void
4871788ca347SMarcel Moolenaar xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4872788ca347SMarcel Moolenaar {
4873788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
4874788ca347SMarcel Moolenaar 
4875788ca347SMarcel Moolenaar     /*
4876788ca347SMarcel Moolenaar      * HTML colors are mostly trivial: fill in xo_color_buf with
4877788ca347SMarcel Moolenaar      * a set of class tags representing the colors and effects.
4878788ca347SMarcel Moolenaar      */
4879788ca347SMarcel Moolenaar 
4880788ca347SMarcel Moolenaar     /* If nothing changed, then do nothing */
4881788ca347SMarcel Moolenaar     if (oldp->xoc_effects == newp->xoc_effects
4882788ca347SMarcel Moolenaar 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4883788ca347SMarcel Moolenaar 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4884788ca347SMarcel Moolenaar 	return;
4885788ca347SMarcel Moolenaar 
4886788ca347SMarcel Moolenaar     unsigned i, bit;
4887788ca347SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_color_buf;
4888788ca347SMarcel Moolenaar 
4889788ca347SMarcel Moolenaar     xo_buf_reset(xbp);		/* We rebuild content after each change */
4890788ca347SMarcel Moolenaar 
4891788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4892788ca347SMarcel Moolenaar 	if (!(newp->xoc_effects & bit))
4893788ca347SMarcel Moolenaar 	    continue;
4894788ca347SMarcel Moolenaar 
4895788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " effect-");
4896788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, xo_effect_names[i]);
4897788ca347SMarcel Moolenaar     }
4898788ca347SMarcel Moolenaar 
4899788ca347SMarcel Moolenaar     const char *fg = NULL;
4900788ca347SMarcel Moolenaar     const char *bg = NULL;
4901788ca347SMarcel Moolenaar 
4902788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4903788ca347SMarcel Moolenaar 	fg = xo_color_names[newp->xoc_col_fg];
4904788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4905788ca347SMarcel Moolenaar 	bg = xo_color_names[newp->xoc_col_bg];
4906788ca347SMarcel Moolenaar 
4907788ca347SMarcel Moolenaar     if (newp->xoc_effects & XO_EFF_INVERSE) {
4908788ca347SMarcel Moolenaar 	const char *tmp = fg;
4909788ca347SMarcel Moolenaar 	fg = bg;
4910788ca347SMarcel Moolenaar 	bg = tmp;
4911788ca347SMarcel Moolenaar 	if (fg == NULL)
4912788ca347SMarcel Moolenaar 	    fg = "inverse";
4913788ca347SMarcel Moolenaar 	if (bg == NULL)
4914788ca347SMarcel Moolenaar 	    bg = "inverse";
4915788ca347SMarcel Moolenaar 
4916788ca347SMarcel Moolenaar     }
4917788ca347SMarcel Moolenaar 
4918788ca347SMarcel Moolenaar     if (fg) {
4919788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-fg-");
4920788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, fg);
4921788ca347SMarcel Moolenaar     }
4922788ca347SMarcel Moolenaar 
4923788ca347SMarcel Moolenaar     if (bg) {
4924788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-bg-");
4925788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, bg);
4926788ca347SMarcel Moolenaar     }
4927788ca347SMarcel Moolenaar }
4928788ca347SMarcel Moolenaar 
4929788ca347SMarcel Moolenaar static void
493042ff34c3SPhil Shafer xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
4931264104f2SPhil Shafer 		  const char *value, ssize_t vlen)
4932788ca347SMarcel Moolenaar {
4933d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
49348a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4935d1a0d267SMarcel Moolenaar 
4936788ca347SMarcel Moolenaar     xo_buffer_t xb;
4937788ca347SMarcel Moolenaar 
4938788ca347SMarcel Moolenaar     /* If the string is static and we've in an encoding style, bail */
4939264104f2SPhil Shafer     if (vlen != 0 && xo_style_is_encoding(xop))
4940788ca347SMarcel Moolenaar 	return;
4941788ca347SMarcel Moolenaar 
4942788ca347SMarcel Moolenaar     xo_buf_init(&xb);
4943788ca347SMarcel Moolenaar 
4944264104f2SPhil Shafer     if (vlen)
4945264104f2SPhil Shafer 	xo_buf_append(&xb, value, vlen);
4946788ca347SMarcel Moolenaar     else if (flen)
4947d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, &xb, fmt, flen, 0);
4948788ca347SMarcel Moolenaar     else
4949788ca347SMarcel Moolenaar 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
4950788ca347SMarcel Moolenaar 
4951788ca347SMarcel Moolenaar     if (xo_colors_enabled(xop)) {
4952788ca347SMarcel Moolenaar 	switch (xo_style(xop)) {
4953788ca347SMarcel Moolenaar 	case XO_STYLE_TEXT:
4954788ca347SMarcel Moolenaar 	case XO_STYLE_HTML:
4955788ca347SMarcel Moolenaar 	    xo_buf_append(&xb, "", 1);
4956788ca347SMarcel Moolenaar 
4957788ca347SMarcel Moolenaar 	    xo_colors_t xoc = xop->xo_colors;
4958788ca347SMarcel Moolenaar 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
4959f2b7bf8aSPhil Shafer 	    xo_colors_update(xop, &xoc);
4960788ca347SMarcel Moolenaar 
4961788ca347SMarcel Moolenaar 	    if (xo_style(xop) == XO_STYLE_TEXT) {
4962788ca347SMarcel Moolenaar 		/*
4963788ca347SMarcel Moolenaar 		 * Text mode means emitting the colors as ANSI character
4964788ca347SMarcel Moolenaar 		 * codes.  This will allow people who like colors to have
4965788ca347SMarcel Moolenaar 		 * colors.  The issue is, of course conflicting with the
4966788ca347SMarcel Moolenaar 		 * user's perfectly reasonable color scheme.  Which leads
4967788ca347SMarcel Moolenaar 		 * to the hell of LSCOLORS, where even app need to have
4968788ca347SMarcel Moolenaar 		 * customization hooks for adjusting colors.  Instead we
4969788ca347SMarcel Moolenaar 		 * provide a simpler-but-still-annoying answer where one
4970788ca347SMarcel Moolenaar 		 * can map colors to other colors.
4971788ca347SMarcel Moolenaar 		 */
4972788ca347SMarcel Moolenaar 		xo_colors_handle_text(xop, &xoc);
4973788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
4974788ca347SMarcel Moolenaar 
4975788ca347SMarcel Moolenaar 	    } else {
4976788ca347SMarcel Moolenaar 		/*
4977788ca347SMarcel Moolenaar 		 * HTML output is wrapped in divs, so the color information
4978788ca347SMarcel Moolenaar 		 * must appear in every div until cleared.  Most pathetic.
4979788ca347SMarcel Moolenaar 		 * Most unavoidable.
4980788ca347SMarcel Moolenaar 		 */
4981788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
4982788ca347SMarcel Moolenaar 		xo_colors_handle_html(xop, &xoc);
4983788ca347SMarcel Moolenaar 	    }
4984788ca347SMarcel Moolenaar 
4985788ca347SMarcel Moolenaar 	    xop->xo_colors = xoc;
4986788ca347SMarcel Moolenaar 	    break;
4987788ca347SMarcel Moolenaar 
4988788ca347SMarcel Moolenaar 	case XO_STYLE_XML:
4989788ca347SMarcel Moolenaar 	case XO_STYLE_JSON:
4990d1a0d267SMarcel Moolenaar 	case XO_STYLE_SDPARAMS:
4991d1a0d267SMarcel Moolenaar 	case XO_STYLE_ENCODER:
4992788ca347SMarcel Moolenaar 	    /*
4993788ca347SMarcel Moolenaar 	     * Nothing to do; we did all that work just to clear the stack of
4994788ca347SMarcel Moolenaar 	     * formatting arguments.
4995788ca347SMarcel Moolenaar 	     */
4996788ca347SMarcel Moolenaar 	    break;
4997788ca347SMarcel Moolenaar 	}
4998788ca347SMarcel Moolenaar     }
4999788ca347SMarcel Moolenaar 
5000788ca347SMarcel Moolenaar     xo_buf_cleanup(&xb);
5001788ca347SMarcel Moolenaar }
5002788ca347SMarcel Moolenaar 
500331337658SMarcel Moolenaar static void
500442ff34c3SPhil Shafer xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
5005264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
500631337658SMarcel Moolenaar {
5007d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
50088a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
5009d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
5010d1a0d267SMarcel Moolenaar 
501131337658SMarcel Moolenaar     static char units_start_xml[] = " units=\"";
501231337658SMarcel Moolenaar     static char units_start_html[] = " data-units=\"";
501331337658SMarcel Moolenaar 
5014d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
5015264104f2SPhil Shafer 	xo_format_content(xop, "units", NULL, value, vlen, fmt, flen, flags);
501631337658SMarcel Moolenaar 	return;
501731337658SMarcel Moolenaar     }
501831337658SMarcel Moolenaar 
501931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
50208a6eceffSPhil Shafer     ssize_t start = xop->xo_units_offset;
50218a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
502231337658SMarcel Moolenaar 
5023788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML)
502431337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
5025788ca347SMarcel Moolenaar     else if (xo_style(xop) == XO_STYLE_HTML)
502631337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
502731337658SMarcel Moolenaar     else
502831337658SMarcel Moolenaar 	return;
502931337658SMarcel Moolenaar 
5030264104f2SPhil Shafer     if (vlen)
5031264104f2SPhil Shafer 	xo_data_escape(xop, value, vlen);
503231337658SMarcel Moolenaar     else
5033d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
503431337658SMarcel Moolenaar 
503531337658SMarcel Moolenaar     xo_buf_append(xbp, "\"", 1);
503631337658SMarcel Moolenaar 
50378a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
50388a6eceffSPhil Shafer     ssize_t delta = now - stop;
5039d1a0d267SMarcel Moolenaar     if (delta <= 0) {		/* Strange; no output to move */
504031337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
504131337658SMarcel Moolenaar 	return;
504231337658SMarcel Moolenaar     }
504331337658SMarcel Moolenaar 
504431337658SMarcel Moolenaar     /*
504531337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the unit value
504631337658SMarcel Moolenaar      * we just created into the right spot.  We make a local copy,
504731337658SMarcel Moolenaar      * move it and then insert our copy.  We know there's room in the
504831337658SMarcel Moolenaar      * buffer, since we're just moving this around.
504931337658SMarcel Moolenaar      */
505031337658SMarcel Moolenaar     char *buf = alloca(delta);
505131337658SMarcel Moolenaar 
505231337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
505331337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
505431337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
505531337658SMarcel Moolenaar }
505631337658SMarcel Moolenaar 
50578a6eceffSPhil Shafer static ssize_t
505842ff34c3SPhil Shafer xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
5059264104f2SPhil Shafer 	       const char *value, ssize_t vlen)
506031337658SMarcel Moolenaar {
5061d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
50628a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
5063d1a0d267SMarcel Moolenaar 
506431337658SMarcel Moolenaar     long width = 0;
506531337658SMarcel Moolenaar     char *bp;
506631337658SMarcel Moolenaar     char *cp;
506731337658SMarcel Moolenaar 
5068264104f2SPhil Shafer     if (vlen) {
5069264104f2SPhil Shafer 	bp = alloca(vlen + 1);	/* Make local NUL-terminated copy of value */
5070264104f2SPhil Shafer 	memcpy(bp, value, vlen);
5071264104f2SPhil Shafer 	bp[vlen] = '\0';
507231337658SMarcel Moolenaar 
507331337658SMarcel Moolenaar 	width = strtol(bp, &cp, 0);
507431337658SMarcel Moolenaar 	if (width == LONG_MIN || width == LONG_MAX
507531337658SMarcel Moolenaar 	    || bp == cp || *cp != '\0' ) {
507631337658SMarcel Moolenaar 	    width = 0;
507731337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
507831337658SMarcel Moolenaar 	}
507931337658SMarcel Moolenaar     } else if (flen) {
508031337658SMarcel Moolenaar 	if (flen != 2 || strncmp("%d", fmt, flen) != 0)
508131337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
5082d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
508331337658SMarcel Moolenaar 	    width = va_arg(xop->xo_vap, int);
508431337658SMarcel Moolenaar     }
508531337658SMarcel Moolenaar 
508631337658SMarcel Moolenaar     return width;
508731337658SMarcel Moolenaar }
508831337658SMarcel Moolenaar 
508931337658SMarcel Moolenaar static void
509031337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop)
509131337658SMarcel Moolenaar {
5092d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_ANCHOR);
509331337658SMarcel Moolenaar     xop->xo_anchor_offset = 0;
509431337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
509531337658SMarcel Moolenaar     xop->xo_anchor_min_width = 0;
509631337658SMarcel Moolenaar }
509731337658SMarcel Moolenaar 
509831337658SMarcel Moolenaar /*
509931337658SMarcel Moolenaar  * An anchor is a marker used to delay field width implications.
510031337658SMarcel Moolenaar  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
510131337658SMarcel Moolenaar  * We are looking for output like "     1/4/5"
510231337658SMarcel Moolenaar  *
510331337658SMarcel Moolenaar  * To make this work, we record the anchor and then return to
510431337658SMarcel Moolenaar  * format it when the end anchor tag is seen.
510531337658SMarcel Moolenaar  */
510631337658SMarcel Moolenaar static void
510742ff34c3SPhil Shafer xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
5108264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
510931337658SMarcel Moolenaar {
5110788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
511131337658SMarcel Moolenaar 	return;
511231337658SMarcel Moolenaar 
5113d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
511431337658SMarcel Moolenaar 	xo_failure(xop, "the anchor already recording is discarded");
511531337658SMarcel Moolenaar 
5116d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_ANCHOR);
511731337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
511831337658SMarcel Moolenaar     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
511931337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
512031337658SMarcel Moolenaar 
512131337658SMarcel Moolenaar     /*
512231337658SMarcel Moolenaar      * Now we find the width, if possible.  If it's not there,
512331337658SMarcel Moolenaar      * we'll get it on the end anchor.
512431337658SMarcel Moolenaar      */
5125264104f2SPhil Shafer     xop->xo_anchor_min_width = xo_find_width(xop, xfip, value, vlen);
512631337658SMarcel Moolenaar }
512731337658SMarcel Moolenaar 
512831337658SMarcel Moolenaar static void
512942ff34c3SPhil Shafer xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
5130264104f2SPhil Shafer 		 const char *value, ssize_t vlen)
513131337658SMarcel Moolenaar {
5132788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
513331337658SMarcel Moolenaar 	return;
513431337658SMarcel Moolenaar 
5135d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
513631337658SMarcel Moolenaar 	xo_failure(xop, "no start anchor");
513731337658SMarcel Moolenaar 	return;
513831337658SMarcel Moolenaar     }
513931337658SMarcel Moolenaar 
5140d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
514131337658SMarcel Moolenaar 
5142264104f2SPhil Shafer     ssize_t width = xo_find_width(xop, xfip, value, vlen);
514331337658SMarcel Moolenaar     if (width == 0)
514431337658SMarcel Moolenaar 	width = xop->xo_anchor_min_width;
514531337658SMarcel Moolenaar 
514631337658SMarcel Moolenaar     if (width == 0)		/* No width given; nothing to do */
514731337658SMarcel Moolenaar 	goto done;
514831337658SMarcel Moolenaar 
514931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
51508a6eceffSPhil Shafer     ssize_t start = xop->xo_anchor_offset;
51518a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
51528a6eceffSPhil Shafer     ssize_t abswidth = (width > 0) ? width : -width;
51538a6eceffSPhil Shafer     ssize_t blen = abswidth - xop->xo_anchor_columns;
515431337658SMarcel Moolenaar 
515531337658SMarcel Moolenaar     if (blen <= 0)		/* Already over width */
515631337658SMarcel Moolenaar 	goto done;
515731337658SMarcel Moolenaar 
515831337658SMarcel Moolenaar     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
515931337658SMarcel Moolenaar 	xo_failure(xop, "width over %u are not supported",
516031337658SMarcel Moolenaar 		   XO_MAX_ANCHOR_WIDTH);
516131337658SMarcel Moolenaar 	goto done;
516231337658SMarcel Moolenaar     }
516331337658SMarcel Moolenaar 
516431337658SMarcel Moolenaar     /* Make a suitable padding field and emit it */
516531337658SMarcel Moolenaar     char *buf = alloca(blen);
516631337658SMarcel Moolenaar     memset(buf, ' ', blen);
5167d1a0d267SMarcel Moolenaar     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
516831337658SMarcel Moolenaar 
516931337658SMarcel Moolenaar     if (width < 0)		/* Already left justified */
517031337658SMarcel Moolenaar 	goto done;
517131337658SMarcel Moolenaar 
51728a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
51738a6eceffSPhil Shafer     ssize_t delta = now - stop;
5174d1a0d267SMarcel Moolenaar     if (delta <= 0)		/* Strange; no output to move */
517531337658SMarcel Moolenaar 	goto done;
517631337658SMarcel Moolenaar 
517731337658SMarcel Moolenaar     /*
517831337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the padding data
517931337658SMarcel Moolenaar      * we just created (which might be an HTML <div> or text) before
518031337658SMarcel Moolenaar      * the formatted data.  We make a local copy, move it and then
518131337658SMarcel Moolenaar      * insert our copy.  We know there's room in the buffer, since
518231337658SMarcel Moolenaar      * we're just moving this around.
518331337658SMarcel Moolenaar      */
518431337658SMarcel Moolenaar     if (delta > blen)
518531337658SMarcel Moolenaar 	buf = alloca(delta);	/* Expand buffer if needed */
518631337658SMarcel Moolenaar 
518731337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
518831337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
518931337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
519031337658SMarcel Moolenaar 
519131337658SMarcel Moolenaar  done:
519231337658SMarcel Moolenaar     xo_anchor_clear(xop);
519331337658SMarcel Moolenaar }
519431337658SMarcel Moolenaar 
5195d1a0d267SMarcel Moolenaar static const char *
5196d1a0d267SMarcel Moolenaar xo_class_name (int ftype)
519731337658SMarcel Moolenaar {
5198d1a0d267SMarcel Moolenaar     switch (ftype) {
5199d1a0d267SMarcel Moolenaar     case 'D': return "decoration";
5200d1a0d267SMarcel Moolenaar     case 'E': return "error";
5201d1a0d267SMarcel Moolenaar     case 'L': return "label";
5202d1a0d267SMarcel Moolenaar     case 'N': return "note";
5203d1a0d267SMarcel Moolenaar     case 'P': return "padding";
5204d1a0d267SMarcel Moolenaar     case 'W': return "warning";
520531337658SMarcel Moolenaar     }
520631337658SMarcel Moolenaar 
5207d1a0d267SMarcel Moolenaar     return NULL;
520831337658SMarcel Moolenaar }
520931337658SMarcel Moolenaar 
5210d1a0d267SMarcel Moolenaar static const char *
5211d1a0d267SMarcel Moolenaar xo_tag_name (int ftype)
5212d1a0d267SMarcel Moolenaar {
5213d1a0d267SMarcel Moolenaar     switch (ftype) {
5214d1a0d267SMarcel Moolenaar     case 'E': return "__error";
5215d1a0d267SMarcel Moolenaar     case 'W': return "__warning";
5216d1a0d267SMarcel Moolenaar     }
5217d1a0d267SMarcel Moolenaar 
5218d1a0d267SMarcel Moolenaar     return NULL;
5219d1a0d267SMarcel Moolenaar }
5220d1a0d267SMarcel Moolenaar 
5221d1a0d267SMarcel Moolenaar static int
5222d1a0d267SMarcel Moolenaar xo_role_wants_default_format (int ftype)
5223d1a0d267SMarcel Moolenaar {
5224d1a0d267SMarcel Moolenaar     switch (ftype) {
5225d1a0d267SMarcel Moolenaar 	/* These roles can be completely empty and/or without formatting */
5226d1a0d267SMarcel Moolenaar     case 'C':
5227d1a0d267SMarcel Moolenaar     case 'G':
5228d1a0d267SMarcel Moolenaar     case '[':
5229d1a0d267SMarcel Moolenaar     case ']':
5230d1a0d267SMarcel Moolenaar 	return 0;
5231d1a0d267SMarcel Moolenaar     }
5232d1a0d267SMarcel Moolenaar 
5233d1a0d267SMarcel Moolenaar     return 1;
5234d1a0d267SMarcel Moolenaar }
5235d1a0d267SMarcel Moolenaar 
5236d1a0d267SMarcel Moolenaar static xo_mapping_t xo_role_names[] = {
5237d1a0d267SMarcel Moolenaar     { 'C', "color" },
5238d1a0d267SMarcel Moolenaar     { 'D', "decoration" },
5239d1a0d267SMarcel Moolenaar     { 'E', "error" },
5240d1a0d267SMarcel Moolenaar     { 'L', "label" },
5241d1a0d267SMarcel Moolenaar     { 'N', "note" },
5242d1a0d267SMarcel Moolenaar     { 'P', "padding" },
5243d1a0d267SMarcel Moolenaar     { 'T', "title" },
5244d1a0d267SMarcel Moolenaar     { 'U', "units" },
5245d1a0d267SMarcel Moolenaar     { 'V', "value" },
5246d1a0d267SMarcel Moolenaar     { 'W', "warning" },
5247d1a0d267SMarcel Moolenaar     { '[', "start-anchor" },
5248d1a0d267SMarcel Moolenaar     { ']', "stop-anchor" },
5249d1a0d267SMarcel Moolenaar     { 0, NULL }
5250d1a0d267SMarcel Moolenaar };
5251d1a0d267SMarcel Moolenaar 
5252d1a0d267SMarcel Moolenaar #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
5253d1a0d267SMarcel Moolenaar #define XO_ROLE_TEXT	'+'
5254d1a0d267SMarcel Moolenaar #define XO_ROLE_NEWLINE	'\n'
5255d1a0d267SMarcel Moolenaar 
5256d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_names[] = {
525742ff34c3SPhil Shafer     { XFF_ARGUMENT, "argument" },
5258d1a0d267SMarcel Moolenaar     { XFF_COLON, "colon" },
5259d1a0d267SMarcel Moolenaar     { XFF_COMMA, "comma" },
5260d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "display" },
5261d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "encoding" },
5262d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "gettext" },
5263d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "humanize" },
5264d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "hn" },
5265d1a0d267SMarcel Moolenaar     { XFF_HN_SPACE, "hn-space" },
5266d1a0d267SMarcel Moolenaar     { XFF_HN_DECIMAL, "hn-decimal" },
5267d1a0d267SMarcel Moolenaar     { XFF_HN_1000, "hn-1000" },
5268d1a0d267SMarcel Moolenaar     { XFF_KEY, "key" },
5269d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "leaf-list" },
5270d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "list" },
5271d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quotes" },
5272d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quote" },
5273d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "plural" },
5274d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quotes" },
5275d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quote" },
5276d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "trim" },
5277d1a0d267SMarcel Moolenaar     { XFF_WS, "white" },
5278d1a0d267SMarcel Moolenaar     { 0, NULL }
5279d1a0d267SMarcel Moolenaar };
5280d1a0d267SMarcel Moolenaar 
5281d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
5282d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_short_names[] = {
5283d1a0d267SMarcel Moolenaar     { XFF_COLON, "c" },
5284d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "d" },
5285d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "e" },
5286d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "g" },
5287d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "h" },
5288d1a0d267SMarcel Moolenaar     { XFF_KEY, "k" },
5289d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "l" },
5290d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "n" },
5291d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "p" },
5292d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "q" },
5293d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "t" },
5294d1a0d267SMarcel Moolenaar     { XFF_WS, "w" },
5295d1a0d267SMarcel Moolenaar     { 0, NULL }
5296d1a0d267SMarcel Moolenaar };
5297d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
5298d1a0d267SMarcel Moolenaar 
5299d1a0d267SMarcel Moolenaar static int
5300d1a0d267SMarcel Moolenaar xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5301d1a0d267SMarcel Moolenaar {
5302d1a0d267SMarcel Moolenaar     int rc = 1;
5303d1a0d267SMarcel Moolenaar     const char *cp;
5304d1a0d267SMarcel Moolenaar 
5305d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp; cp++)
5306d1a0d267SMarcel Moolenaar 	if (*cp == '{' || *cp == '\n')
5307d1a0d267SMarcel Moolenaar 	    rc += 1;
5308d1a0d267SMarcel Moolenaar 
5309d1a0d267SMarcel Moolenaar     return rc * 2 + 1;
5310d1a0d267SMarcel Moolenaar }
531131337658SMarcel Moolenaar 
531231337658SMarcel Moolenaar /*
5313d1a0d267SMarcel Moolenaar  * The field format is:
531431337658SMarcel Moolenaar  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5315d1a0d267SMarcel Moolenaar  * Roles are optional and include the following field types:
531631337658SMarcel Moolenaar  *   'D': decoration; something non-text and non-data (colons, commmas)
531731337658SMarcel Moolenaar  *   'E': error message
5318d1a0d267SMarcel Moolenaar  *   'G': gettext() the entire string; optional domainname as content
531931337658SMarcel Moolenaar  *   'L': label; text preceding data
532031337658SMarcel Moolenaar  *   'N': note; text following data
532131337658SMarcel Moolenaar  *   'P': padding; whitespace
532231337658SMarcel Moolenaar  *   'T': Title, where 'content' is a column title
532331337658SMarcel Moolenaar  *   'U': Units, where 'content' is the unit label
532431337658SMarcel Moolenaar  *   'V': value, where 'content' is the name of the field (the default)
532531337658SMarcel Moolenaar  *   'W': warning message
532631337658SMarcel Moolenaar  *   '[': start a section of anchored text
532731337658SMarcel Moolenaar  *   ']': end a section of anchored text
5328d1a0d267SMarcel Moolenaar  * The following modifiers are also supported:
532942ff34c3SPhil Shafer  *   'a': content is provided via argument (const char *), not descriptor
533031337658SMarcel Moolenaar  *   'c': flag: emit a colon after the label
5331d1a0d267SMarcel Moolenaar  *   'd': field is only emitted for display styles (text and html)
5332d1a0d267SMarcel Moolenaar  *   'e': field is only emitted for encoding styles (xml and json)
5333d1a0d267SMarcel Moolenaar  *   'g': gettext() the field
5334d1a0d267SMarcel Moolenaar  *   'h': humanize a numeric value (only for display styles)
533531337658SMarcel Moolenaar  *   'k': this field is a key, suitable for XPath predicates
533631337658SMarcel Moolenaar  *   'l': a leaf-list, a simple list of values
533731337658SMarcel Moolenaar  *   'n': no quotes around this field
5338d1a0d267SMarcel Moolenaar  *   'p': the field has plural gettext semantics (ngettext)
533931337658SMarcel Moolenaar  *   'q': add quotes around this field
534031337658SMarcel Moolenaar  *   't': trim whitespace around the value
534131337658SMarcel Moolenaar  *   'w': emit a blank after the label
534231337658SMarcel Moolenaar  * The print-fmt and encode-fmt strings is the printf-style formating
534331337658SMarcel Moolenaar  * for this data.  JSON and XML will use the encoding-fmt, if present.
534431337658SMarcel Moolenaar  * If the encode-fmt is not provided, it defaults to the print-fmt.
534531337658SMarcel Moolenaar  * If the print-fmt is not provided, it defaults to 's'.
534631337658SMarcel Moolenaar  */
5347d1a0d267SMarcel Moolenaar static const char *
5348d1a0d267SMarcel Moolenaar xo_parse_roles (xo_handle_t *xop, const char *fmt,
5349d1a0d267SMarcel Moolenaar 		const char *basep, xo_field_info_t *xfip)
5350d1a0d267SMarcel Moolenaar {
5351d1a0d267SMarcel Moolenaar     const char *sp;
5352d1a0d267SMarcel Moolenaar     unsigned ftype = 0;
5353d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = 0;
5354d1a0d267SMarcel Moolenaar     uint8_t fnum = 0;
535531337658SMarcel Moolenaar 
535642ff34c3SPhil Shafer     for (sp = basep; sp && *sp; sp++) {
535731337658SMarcel Moolenaar 	if (*sp == ':' || *sp == '/' || *sp == '}')
535831337658SMarcel Moolenaar 	    break;
535931337658SMarcel Moolenaar 
536031337658SMarcel Moolenaar 	if (*sp == '\\') {
536131337658SMarcel Moolenaar 	    if (sp[1] == '\0') {
536231337658SMarcel Moolenaar 		xo_failure(xop, "backslash at the end of string");
5363d1a0d267SMarcel Moolenaar 		return NULL;
536431337658SMarcel Moolenaar 	    }
5365d1a0d267SMarcel Moolenaar 
5366d1a0d267SMarcel Moolenaar 	    /* Anything backslashed is ignored */
536731337658SMarcel Moolenaar 	    sp += 1;
536831337658SMarcel Moolenaar 	    continue;
536931337658SMarcel Moolenaar 	}
537031337658SMarcel Moolenaar 
5371d1a0d267SMarcel Moolenaar 	if (*sp == ',') {
5372d1a0d267SMarcel Moolenaar 	    const char *np;
5373d1a0d267SMarcel Moolenaar 	    for (np = ++sp; *np; np++)
5374d1a0d267SMarcel Moolenaar 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5375d1a0d267SMarcel Moolenaar 		    break;
5376d1a0d267SMarcel Moolenaar 
53778a6eceffSPhil Shafer 	    ssize_t slen = np - sp;
5378d1a0d267SMarcel Moolenaar 	    if (slen > 0) {
5379d1a0d267SMarcel Moolenaar 		xo_xff_flags_t value;
5380d1a0d267SMarcel Moolenaar 
5381d1a0d267SMarcel Moolenaar 		value = xo_name_lookup(xo_role_names, sp, slen);
5382d1a0d267SMarcel Moolenaar 		if (value)
5383d1a0d267SMarcel Moolenaar 		    ftype = value;
5384d1a0d267SMarcel Moolenaar 		else {
5385d1a0d267SMarcel Moolenaar 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
5386d1a0d267SMarcel Moolenaar 		    if (value)
5387d1a0d267SMarcel Moolenaar 			flags |= value;
5388d1a0d267SMarcel Moolenaar 		    else
5389d1a0d267SMarcel Moolenaar 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
5390d1a0d267SMarcel Moolenaar 				   slen, sp);
5391d1a0d267SMarcel Moolenaar 		}
5392d1a0d267SMarcel Moolenaar 	    }
5393d1a0d267SMarcel Moolenaar 
5394d1a0d267SMarcel Moolenaar 	    sp = np - 1;
5395d1a0d267SMarcel Moolenaar 	    continue;
5396d1a0d267SMarcel Moolenaar 	}
5397d1a0d267SMarcel Moolenaar 
539831337658SMarcel Moolenaar 	switch (*sp) {
5399788ca347SMarcel Moolenaar 	case 'C':
540031337658SMarcel Moolenaar 	case 'D':
540131337658SMarcel Moolenaar 	case 'E':
5402d1a0d267SMarcel Moolenaar 	case 'G':
540331337658SMarcel Moolenaar 	case 'L':
540431337658SMarcel Moolenaar 	case 'N':
540531337658SMarcel Moolenaar 	case 'P':
540631337658SMarcel Moolenaar 	case 'T':
540731337658SMarcel Moolenaar 	case 'U':
540831337658SMarcel Moolenaar 	case 'V':
540931337658SMarcel Moolenaar 	case 'W':
541031337658SMarcel Moolenaar 	case '[':
541131337658SMarcel Moolenaar 	case ']':
541231337658SMarcel Moolenaar 	    if (ftype != 0) {
5413d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
5414d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5415d1a0d267SMarcel Moolenaar 		return NULL;
541631337658SMarcel Moolenaar 	    }
541731337658SMarcel Moolenaar 	    ftype = *sp;
541831337658SMarcel Moolenaar 	    break;
541931337658SMarcel Moolenaar 
5420d1a0d267SMarcel Moolenaar 	case '0':
5421d1a0d267SMarcel Moolenaar 	case '1':
5422d1a0d267SMarcel Moolenaar 	case '2':
5423d1a0d267SMarcel Moolenaar 	case '3':
5424d1a0d267SMarcel Moolenaar 	case '4':
5425d1a0d267SMarcel Moolenaar 	case '5':
5426d1a0d267SMarcel Moolenaar 	case '6':
5427d1a0d267SMarcel Moolenaar 	case '7':
5428d1a0d267SMarcel Moolenaar 	case '8':
5429d1a0d267SMarcel Moolenaar 	case '9':
5430d1a0d267SMarcel Moolenaar 	    fnum = (fnum * 10) + (*sp - '0');
5431d1a0d267SMarcel Moolenaar 	    break;
5432d1a0d267SMarcel Moolenaar 
543342ff34c3SPhil Shafer 	case 'a':
543442ff34c3SPhil Shafer 	    flags |= XFF_ARGUMENT;
543542ff34c3SPhil Shafer 	    break;
543642ff34c3SPhil Shafer 
543731337658SMarcel Moolenaar 	case 'c':
543831337658SMarcel Moolenaar 	    flags |= XFF_COLON;
543931337658SMarcel Moolenaar 	    break;
544031337658SMarcel Moolenaar 
544131337658SMarcel Moolenaar 	case 'd':
544231337658SMarcel Moolenaar 	    flags |= XFF_DISPLAY_ONLY;
544331337658SMarcel Moolenaar 	    break;
544431337658SMarcel Moolenaar 
544531337658SMarcel Moolenaar 	case 'e':
544631337658SMarcel Moolenaar 	    flags |= XFF_ENCODE_ONLY;
544731337658SMarcel Moolenaar 	    break;
544831337658SMarcel Moolenaar 
5449d1a0d267SMarcel Moolenaar 	case 'g':
5450d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_FIELD;
5451d1a0d267SMarcel Moolenaar 	    break;
5452d1a0d267SMarcel Moolenaar 
5453d1a0d267SMarcel Moolenaar 	case 'h':
5454d1a0d267SMarcel Moolenaar 	    flags |= XFF_HUMANIZE;
5455d1a0d267SMarcel Moolenaar 	    break;
5456d1a0d267SMarcel Moolenaar 
545731337658SMarcel Moolenaar 	case 'k':
545831337658SMarcel Moolenaar 	    flags |= XFF_KEY;
545931337658SMarcel Moolenaar 	    break;
546031337658SMarcel Moolenaar 
546131337658SMarcel Moolenaar 	case 'l':
546231337658SMarcel Moolenaar 	    flags |= XFF_LEAF_LIST;
546331337658SMarcel Moolenaar 	    break;
546431337658SMarcel Moolenaar 
546531337658SMarcel Moolenaar 	case 'n':
546631337658SMarcel Moolenaar 	    flags |= XFF_NOQUOTE;
546731337658SMarcel Moolenaar 	    break;
546831337658SMarcel Moolenaar 
5469d1a0d267SMarcel Moolenaar 	case 'p':
5470d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_PLURAL;
5471d1a0d267SMarcel Moolenaar 	    break;
5472d1a0d267SMarcel Moolenaar 
547331337658SMarcel Moolenaar 	case 'q':
547431337658SMarcel Moolenaar 	    flags |= XFF_QUOTE;
547531337658SMarcel Moolenaar 	    break;
547631337658SMarcel Moolenaar 
547731337658SMarcel Moolenaar 	case 't':
547831337658SMarcel Moolenaar 	    flags |= XFF_TRIM_WS;
547931337658SMarcel Moolenaar 	    break;
548031337658SMarcel Moolenaar 
548131337658SMarcel Moolenaar 	case 'w':
548231337658SMarcel Moolenaar 	    flags |= XFF_WS;
548331337658SMarcel Moolenaar 	    break;
548431337658SMarcel Moolenaar 
548531337658SMarcel Moolenaar 	default:
5486d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5487d1a0d267SMarcel Moolenaar 		       xo_printable(fmt));
548831337658SMarcel Moolenaar 	    /*
548931337658SMarcel Moolenaar 	     * No good answer here; a bad format will likely
549031337658SMarcel Moolenaar 	     * mean a core file.  We just return and hope
549131337658SMarcel Moolenaar 	     * the caller notices there's no output, and while
5492d1a0d267SMarcel Moolenaar 	     * that seems, well, bad, there's nothing better.
549331337658SMarcel Moolenaar 	     */
5494d1a0d267SMarcel Moolenaar 	    return NULL;
5495d1a0d267SMarcel Moolenaar 	}
5496d1a0d267SMarcel Moolenaar 
5497d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
5498d1a0d267SMarcel Moolenaar 	    if (flags & XFF_COLON) {
5499d1a0d267SMarcel Moolenaar 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5500d1a0d267SMarcel Moolenaar 			   "'%s'", xo_printable(fmt));
5501d1a0d267SMarcel Moolenaar 		flags &= ~XFF_COLON;
5502d1a0d267SMarcel Moolenaar 	    }
550331337658SMarcel Moolenaar 	}
550431337658SMarcel Moolenaar     }
550531337658SMarcel Moolenaar 
5506d1a0d267SMarcel Moolenaar     xfip->xfi_flags = flags;
5507d1a0d267SMarcel Moolenaar     xfip->xfi_ftype = ftype ?: 'V';
5508d1a0d267SMarcel Moolenaar     xfip->xfi_fnum = fnum;
5509d1a0d267SMarcel Moolenaar 
5510d1a0d267SMarcel Moolenaar     return sp;
5511d1a0d267SMarcel Moolenaar }
5512d1a0d267SMarcel Moolenaar 
5513d1a0d267SMarcel Moolenaar /*
5514d1a0d267SMarcel Moolenaar  * Number any remaining fields that need numbers.  Note that some
5515d1a0d267SMarcel Moolenaar  * field types (text, newline, escaped braces) never get numbers.
5516d1a0d267SMarcel Moolenaar  */
5517d1a0d267SMarcel Moolenaar static void
5518d1a0d267SMarcel Moolenaar xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5519d1a0d267SMarcel Moolenaar 				    const char *fmt UNUSED,
5520d1a0d267SMarcel Moolenaar 				    xo_field_info_t *fields)
5521d1a0d267SMarcel Moolenaar {
5522d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5523d1a0d267SMarcel Moolenaar     unsigned fnum, max_fields;
5524d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5525d1a0d267SMarcel Moolenaar 
5526d1a0d267SMarcel Moolenaar     /* First make a list of add the explicitly used bits */
5527d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5528d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5529d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5530d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5531d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5532d1a0d267SMarcel Moolenaar 	case 'G':
5533d1a0d267SMarcel Moolenaar 	    continue;
5534d1a0d267SMarcel Moolenaar 	}
5535d1a0d267SMarcel Moolenaar 
5536d1a0d267SMarcel Moolenaar 	fnum += 1;
5537d1a0d267SMarcel Moolenaar 	if (fnum >= 63)
5538d1a0d267SMarcel Moolenaar 	    break;
5539d1a0d267SMarcel Moolenaar 
5540d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5541d1a0d267SMarcel Moolenaar 	    bits |= 1 << xfip->xfi_fnum;
5542d1a0d267SMarcel Moolenaar     }
5543d1a0d267SMarcel Moolenaar 
5544d1a0d267SMarcel Moolenaar     max_fields = fnum;
5545d1a0d267SMarcel Moolenaar 
5546d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5547d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5548d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5549d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5550d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5551d1a0d267SMarcel Moolenaar 	case 'G':
5552d1a0d267SMarcel Moolenaar 	    continue;
5553d1a0d267SMarcel Moolenaar 	}
5554d1a0d267SMarcel Moolenaar 
5555d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum != 0)
5556d1a0d267SMarcel Moolenaar 	    continue;
5557d1a0d267SMarcel Moolenaar 
5558d1a0d267SMarcel Moolenaar 	/* Find the next unassigned field */
5559d1a0d267SMarcel Moolenaar 	for (fnum++; bits & (1 << fnum); fnum++)
5560d1a0d267SMarcel Moolenaar 	    continue;
5561d1a0d267SMarcel Moolenaar 
5562d1a0d267SMarcel Moolenaar 	if (fnum > max_fields)
5563d1a0d267SMarcel Moolenaar 	    break;
5564d1a0d267SMarcel Moolenaar 
5565d1a0d267SMarcel Moolenaar 	xfip->xfi_fnum = fnum;	/* Mark the field number */
5566d1a0d267SMarcel Moolenaar 	bits |= 1 << fnum;	/* Mark it used */
5567d1a0d267SMarcel Moolenaar     }
5568d1a0d267SMarcel Moolenaar }
5569d1a0d267SMarcel Moolenaar 
5570d1a0d267SMarcel Moolenaar /*
5571ee5cf116SPhil Shafer  * The format string uses field numbers, so we need to whiffle through it
5572d1a0d267SMarcel Moolenaar  * and make sure everything's sane and lovely.
5573d1a0d267SMarcel Moolenaar  */
5574d1a0d267SMarcel Moolenaar static int
5575d1a0d267SMarcel Moolenaar xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5576d1a0d267SMarcel Moolenaar 			xo_field_info_t *fields, unsigned num_fields)
5577d1a0d267SMarcel Moolenaar {
5578d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5579d1a0d267SMarcel Moolenaar     unsigned field, fnum;
5580d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5581d1a0d267SMarcel Moolenaar 
5582d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5583d1a0d267SMarcel Moolenaar 	/* Fields default to 1:1 with natural position */
5584d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == 0)
5585d1a0d267SMarcel Moolenaar 	    xfip->xfi_fnum = field + 1;
5586d1a0d267SMarcel Moolenaar 	else if (xfip->xfi_fnum > num_fields) {
5587d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5588d1a0d267SMarcel Moolenaar 	    return -1;
5589d1a0d267SMarcel Moolenaar 	}
5590d1a0d267SMarcel Moolenaar 
5591d1a0d267SMarcel Moolenaar 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5592d1a0d267SMarcel Moolenaar 	if (fnum < 64) {	/* Only test what fits */
5593d1a0d267SMarcel Moolenaar 	    if (bits & (1 << fnum)) {
5594d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field number %u reused: '%s'",
5595d1a0d267SMarcel Moolenaar 			   xfip->xfi_fnum, fmt);
5596d1a0d267SMarcel Moolenaar 		return -1;
5597d1a0d267SMarcel Moolenaar 	    }
5598d1a0d267SMarcel Moolenaar 	    bits |= 1 << fnum;
5599d1a0d267SMarcel Moolenaar 	}
5600d1a0d267SMarcel Moolenaar     }
5601d1a0d267SMarcel Moolenaar 
5602d1a0d267SMarcel Moolenaar     return 0;
5603d1a0d267SMarcel Moolenaar }
5604d1a0d267SMarcel Moolenaar 
5605d1a0d267SMarcel Moolenaar static int
5606d1a0d267SMarcel Moolenaar xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5607d1a0d267SMarcel Moolenaar 		 unsigned num_fields, const char *fmt)
5608d1a0d267SMarcel Moolenaar {
5609d1a0d267SMarcel Moolenaar     const char *cp, *sp, *ep, *basep;
5610d1a0d267SMarcel Moolenaar     unsigned field = 0;
5611d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip = fields;
5612d1a0d267SMarcel Moolenaar     unsigned seen_fnum = 0;
5613d1a0d267SMarcel Moolenaar 
5614d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5615d1a0d267SMarcel Moolenaar 	xfip->xfi_start = cp;
5616d1a0d267SMarcel Moolenaar 
5617d1a0d267SMarcel Moolenaar 	if (*cp == '\n') {
5618d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5619d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = 1;
5620d1a0d267SMarcel Moolenaar 	    cp += 1;
5621d1a0d267SMarcel Moolenaar 	    continue;
5622d1a0d267SMarcel Moolenaar 	}
5623d1a0d267SMarcel Moolenaar 
5624d1a0d267SMarcel Moolenaar 	if (*cp != '{') {
5625d1a0d267SMarcel Moolenaar 	    /* Normal text */
5626d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5627d1a0d267SMarcel Moolenaar 		if (*sp == '{' || *sp == '\n')
5628d1a0d267SMarcel Moolenaar 		    break;
5629d1a0d267SMarcel Moolenaar 	    }
5630d1a0d267SMarcel Moolenaar 
5631d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5632d1a0d267SMarcel Moolenaar 	    xfip->xfi_content = cp;
5633d1a0d267SMarcel Moolenaar 	    xfip->xfi_clen = sp - cp;
5634d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = sp;
5635d1a0d267SMarcel Moolenaar 
5636d1a0d267SMarcel Moolenaar 	    cp = sp;
5637d1a0d267SMarcel Moolenaar 	    continue;
5638d1a0d267SMarcel Moolenaar 	}
5639d1a0d267SMarcel Moolenaar 
5640d1a0d267SMarcel Moolenaar 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5641d1a0d267SMarcel Moolenaar 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5642d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5643d1a0d267SMarcel Moolenaar 
5644d1a0d267SMarcel Moolenaar 	    cp += 2;	/* Skip over _both_ characters */
5645d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5646d1a0d267SMarcel Moolenaar 		if (*sp == '}' && sp[1] == '}')
5647d1a0d267SMarcel Moolenaar 		    break;
5648d1a0d267SMarcel Moolenaar 	    }
5649d1a0d267SMarcel Moolenaar 	    if (*sp == '\0') {
5650d1a0d267SMarcel Moolenaar 		xo_failure(xop, "missing closing '}}': '%s'",
5651d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5652d1a0d267SMarcel Moolenaar 		return -1;
5653d1a0d267SMarcel Moolenaar 	    }
5654d1a0d267SMarcel Moolenaar 
5655d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5656d1a0d267SMarcel Moolenaar 
5657d1a0d267SMarcel Moolenaar 	    /* Move along the string, but don't run off the end */
5658d1a0d267SMarcel Moolenaar 	    if (*sp == '}' && sp[1] == '}')
5659d1a0d267SMarcel Moolenaar 		sp += 2;
5660d1a0d267SMarcel Moolenaar 	    cp = *sp ? sp : sp;
5661d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = cp;
5662d1a0d267SMarcel Moolenaar 	    continue;
5663d1a0d267SMarcel Moolenaar 	}
5664d1a0d267SMarcel Moolenaar 
5665d1a0d267SMarcel Moolenaar 	/* We are looking at the start of a field definition */
5666d1a0d267SMarcel Moolenaar 	xfip->xfi_start = basep = cp + 1;
5667d1a0d267SMarcel Moolenaar 
5668d1a0d267SMarcel Moolenaar 	const char *format = NULL;
56698a6eceffSPhil Shafer 	ssize_t flen = 0;
5670d1a0d267SMarcel Moolenaar 
5671d1a0d267SMarcel Moolenaar 	/* Looking at roles and modifiers */
5672d1a0d267SMarcel Moolenaar 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5673d1a0d267SMarcel Moolenaar 	if (sp == NULL) {
5674d1a0d267SMarcel Moolenaar 	    /* xo_failure has already been called */
5675d1a0d267SMarcel Moolenaar 	    return -1;
5676d1a0d267SMarcel Moolenaar 	}
5677d1a0d267SMarcel Moolenaar 
5678d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5679d1a0d267SMarcel Moolenaar 	    seen_fnum = 1;
5680d1a0d267SMarcel Moolenaar 
5681d1a0d267SMarcel Moolenaar 	/* Looking at content */
568231337658SMarcel Moolenaar 	if (*sp == ':') {
568331337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
568431337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
568531337658SMarcel Moolenaar 		    break;
568631337658SMarcel Moolenaar 		if (*sp == '\\') {
568731337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
568831337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
568931337658SMarcel Moolenaar 			return -1;
569031337658SMarcel Moolenaar 		    }
569131337658SMarcel Moolenaar 		    sp += 1;
569231337658SMarcel Moolenaar 		    continue;
569331337658SMarcel Moolenaar 		}
569431337658SMarcel Moolenaar 	    }
569531337658SMarcel Moolenaar 	    if (ep != sp) {
5696d1a0d267SMarcel Moolenaar 		xfip->xfi_clen = sp - ep;
5697d1a0d267SMarcel Moolenaar 		xfip->xfi_content = ep;
569831337658SMarcel Moolenaar 	    }
569931337658SMarcel Moolenaar 	} else {
5700d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
570131337658SMarcel Moolenaar 	    return -1;
570231337658SMarcel Moolenaar 	}
570331337658SMarcel Moolenaar 
5704d1a0d267SMarcel Moolenaar 	/* Looking at main (display) format */
570531337658SMarcel Moolenaar 	if (*sp == '/') {
570631337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
570731337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
570831337658SMarcel Moolenaar 		    break;
570931337658SMarcel Moolenaar 		if (*sp == '\\') {
571031337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
571131337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
571231337658SMarcel Moolenaar 			return -1;
571331337658SMarcel Moolenaar 		    }
571431337658SMarcel Moolenaar 		    sp += 1;
571531337658SMarcel Moolenaar 		    continue;
571631337658SMarcel Moolenaar 		}
571731337658SMarcel Moolenaar 	    }
571831337658SMarcel Moolenaar 	    flen = sp - ep;
571931337658SMarcel Moolenaar 	    format = ep;
572031337658SMarcel Moolenaar 	}
572131337658SMarcel Moolenaar 
5722d1a0d267SMarcel Moolenaar 	/* Looking at encoding format */
572331337658SMarcel Moolenaar 	if (*sp == '/') {
572431337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
572531337658SMarcel Moolenaar 		if (*sp == '}')
572631337658SMarcel Moolenaar 		    break;
572731337658SMarcel Moolenaar 	    }
5728d1a0d267SMarcel Moolenaar 
5729d1a0d267SMarcel Moolenaar 	    xfip->xfi_encoding = ep;
5730d1a0d267SMarcel Moolenaar 	    xfip->xfi_elen = sp - ep;
573131337658SMarcel Moolenaar 	}
573231337658SMarcel Moolenaar 
5733d1a0d267SMarcel Moolenaar 	if (*sp != '}') {
5734d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
573531337658SMarcel Moolenaar 	    return -1;
573631337658SMarcel Moolenaar 	}
573731337658SMarcel Moolenaar 
5738d1a0d267SMarcel Moolenaar 	xfip->xfi_len = sp - xfip->xfi_start;
5739d1a0d267SMarcel Moolenaar 	xfip->xfi_next = ++sp;
5740d1a0d267SMarcel Moolenaar 
5741d1a0d267SMarcel Moolenaar 	/* If we have content, then we have a default format */
574242ff34c3SPhil Shafer 	if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5743d1a0d267SMarcel Moolenaar 	    if (format) {
5744d1a0d267SMarcel Moolenaar 		xfip->xfi_format = format;
5745d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = flen;
5746d1a0d267SMarcel Moolenaar 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
574742ff34c3SPhil Shafer 		xfip->xfi_format = xo_default_format;
5748d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = 2;
5749d1a0d267SMarcel Moolenaar 	    }
575031337658SMarcel Moolenaar 	}
575131337658SMarcel Moolenaar 
5752d1a0d267SMarcel Moolenaar 	cp = sp;
5753d1a0d267SMarcel Moolenaar     }
5754545ddfbeSMarcel Moolenaar 
5755d1a0d267SMarcel Moolenaar     int rc = 0;
5756d1a0d267SMarcel Moolenaar 
5757d1a0d267SMarcel Moolenaar     /*
5758d1a0d267SMarcel Moolenaar      * If we saw a field number on at least one field, then we need
5759d1a0d267SMarcel Moolenaar      * to enforce some rules and/or guidelines.
5760d1a0d267SMarcel Moolenaar      */
5761d1a0d267SMarcel Moolenaar     if (seen_fnum)
5762d1a0d267SMarcel Moolenaar 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5763d1a0d267SMarcel Moolenaar 
5764d1a0d267SMarcel Moolenaar     return rc;
5765d1a0d267SMarcel Moolenaar }
5766d1a0d267SMarcel Moolenaar 
5767d1a0d267SMarcel Moolenaar /*
5768d1a0d267SMarcel Moolenaar  * We are passed a pointer to a format string just past the "{G:}"
5769d1a0d267SMarcel Moolenaar  * field.  We build a simplified version of the format string.
5770d1a0d267SMarcel Moolenaar  */
5771d1a0d267SMarcel Moolenaar static int
5772d1a0d267SMarcel Moolenaar xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5773d1a0d267SMarcel Moolenaar 		       xo_buffer_t *xbp,
5774d1a0d267SMarcel Moolenaar 		       xo_field_info_t *fields,
5775d1a0d267SMarcel Moolenaar 		       int this_field,
5776d1a0d267SMarcel Moolenaar 		       const char *fmt UNUSED,
5777d1a0d267SMarcel Moolenaar 		       xo_simplify_field_func_t field_cb)
5778d1a0d267SMarcel Moolenaar {
5779d1a0d267SMarcel Moolenaar     unsigned ftype;
5780d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags;
5781d1a0d267SMarcel Moolenaar     int field = this_field + 1;
5782d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5783d1a0d267SMarcel Moolenaar     char ch;
5784d1a0d267SMarcel Moolenaar 
5785d1a0d267SMarcel Moolenaar     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5786d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5787d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5788d1a0d267SMarcel Moolenaar 
5789d1a0d267SMarcel Moolenaar 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5790d1a0d267SMarcel Moolenaar 	    if (field_cb)
5791d1a0d267SMarcel Moolenaar 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5792d1a0d267SMarcel Moolenaar 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5793d1a0d267SMarcel Moolenaar 	}
5794d1a0d267SMarcel Moolenaar 
5795d1a0d267SMarcel Moolenaar 	switch (ftype) {
5796d1a0d267SMarcel Moolenaar 	case 'G':
5797d1a0d267SMarcel Moolenaar 	    /* Ignore gettext roles */
5798d1a0d267SMarcel Moolenaar 	    break;
5799d1a0d267SMarcel Moolenaar 
5800d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5801d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1);
5802d1a0d267SMarcel Moolenaar 	    break;
5803d1a0d267SMarcel Moolenaar 
5804d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5805d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5806d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5807d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5808d1a0d267SMarcel Moolenaar 	    break;
5809d1a0d267SMarcel Moolenaar 
5810d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5811d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5812d1a0d267SMarcel Moolenaar 	    break;
5813d1a0d267SMarcel Moolenaar 
5814d1a0d267SMarcel Moolenaar 	default:
5815d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5816d1a0d267SMarcel Moolenaar 	    if (ftype != 'V') {
5817d1a0d267SMarcel Moolenaar 		ch = ftype;
5818d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, &ch, 1);
5819d1a0d267SMarcel Moolenaar 	    }
5820d1a0d267SMarcel Moolenaar 
5821d1a0d267SMarcel Moolenaar 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5822d1a0d267SMarcel Moolenaar 	    if (fnum) {
5823d1a0d267SMarcel Moolenaar 		char num[12];
5824d1a0d267SMarcel Moolenaar 		/* Field numbers are origin 1, not 0, following printf(3) */
5825d1a0d267SMarcel Moolenaar 		snprintf(num, sizeof(num), "%u", fnum);
5826d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, num, strlen(num));
5827d1a0d267SMarcel Moolenaar 	    }
5828d1a0d267SMarcel Moolenaar 
5829d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, ":", 1);
5830d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5831d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5832d1a0d267SMarcel Moolenaar 	}
5833d1a0d267SMarcel Moolenaar     }
5834d1a0d267SMarcel Moolenaar 
5835d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1);
5836d1a0d267SMarcel Moolenaar     return 0;
5837d1a0d267SMarcel Moolenaar }
5838d1a0d267SMarcel Moolenaar 
5839d1a0d267SMarcel Moolenaar void
5840d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5841d1a0d267SMarcel Moolenaar void
5842d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *fields)
5843d1a0d267SMarcel Moolenaar {
5844d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5845d1a0d267SMarcel Moolenaar 
5846d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5847d1a0d267SMarcel Moolenaar 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5848d1a0d267SMarcel Moolenaar 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5849d1a0d267SMarcel Moolenaar 	       (unsigned long) xfip->xfi_flags,
5850d1a0d267SMarcel Moolenaar 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5851d1a0d267SMarcel Moolenaar 	       xfip->xfi_ftype,
58528a6eceffSPhil Shafer 	       (int) xfip->xfi_clen, xfip->xfi_content ?: "",
58538a6eceffSPhil Shafer 	       (int) xfip->xfi_flen, xfip->xfi_format ?: "",
58548a6eceffSPhil Shafer 	       (int) xfip->xfi_elen, xfip->xfi_encoding ?: "");
5855d1a0d267SMarcel Moolenaar     }
5856d1a0d267SMarcel Moolenaar }
5857d1a0d267SMarcel Moolenaar 
5858d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
5859d1a0d267SMarcel Moolenaar /*
5860d1a0d267SMarcel Moolenaar  * Find the field that matches the given field number
5861d1a0d267SMarcel Moolenaar  */
5862d1a0d267SMarcel Moolenaar static xo_field_info_t *
5863d1a0d267SMarcel Moolenaar xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5864d1a0d267SMarcel Moolenaar {
5865d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5866d1a0d267SMarcel Moolenaar 
5867d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++)
5868d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == fnum)
5869d1a0d267SMarcel Moolenaar 	    return xfip;
5870d1a0d267SMarcel Moolenaar 
5871d1a0d267SMarcel Moolenaar     return NULL;
5872d1a0d267SMarcel Moolenaar }
5873d1a0d267SMarcel Moolenaar 
5874d1a0d267SMarcel Moolenaar /*
5875d1a0d267SMarcel Moolenaar  * At this point, we need to consider if the fields have been reordered,
5876d1a0d267SMarcel Moolenaar  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5877d1a0d267SMarcel Moolenaar  *
5878d1a0d267SMarcel Moolenaar  * We need to rewrite the new_fields using the old fields order,
5879d1a0d267SMarcel Moolenaar  * so that we can render the message using the arguments as they
5880d1a0d267SMarcel Moolenaar  * appear on the stack.  It's a lot of work, but we don't really
5881d1a0d267SMarcel Moolenaar  * want to (eventually) fall into the standard printf code which
5882d1a0d267SMarcel Moolenaar  * means using the arguments straight (and in order) from the
5883d1a0d267SMarcel Moolenaar  * varargs we were originally passed.
5884d1a0d267SMarcel Moolenaar  */
5885d1a0d267SMarcel Moolenaar static void
5886d1a0d267SMarcel Moolenaar xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
5887d1a0d267SMarcel Moolenaar 			   xo_field_info_t *fields, unsigned max_fields)
5888d1a0d267SMarcel Moolenaar {
5889d1a0d267SMarcel Moolenaar     xo_field_info_t tmp[max_fields];
5890d1a0d267SMarcel Moolenaar     bzero(tmp, max_fields * sizeof(tmp[0]));
5891d1a0d267SMarcel Moolenaar 
5892d1a0d267SMarcel Moolenaar     unsigned fnum = 0;
5893d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *outp, *zp;
5894d1a0d267SMarcel Moolenaar     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
5895d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5896d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5897d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5898d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5899d1a0d267SMarcel Moolenaar 	case 'G':
5900d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5901d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5902d1a0d267SMarcel Moolenaar 	    continue;
5903d1a0d267SMarcel Moolenaar 	}
5904d1a0d267SMarcel Moolenaar 
5905d1a0d267SMarcel Moolenaar 	zp = xo_gettext_find_field(fields, ++fnum);
5906d1a0d267SMarcel Moolenaar 	if (zp == NULL) { 	/* Should not occur */
5907d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5908d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5909d1a0d267SMarcel Moolenaar 	    continue;
5910d1a0d267SMarcel Moolenaar 	}
5911d1a0d267SMarcel Moolenaar 
5912d1a0d267SMarcel Moolenaar 	*outp = *zp;
5913d1a0d267SMarcel Moolenaar 	outp->xfi_renum = newp->xfi_fnum;
5914d1a0d267SMarcel Moolenaar     }
5915d1a0d267SMarcel Moolenaar 
5916d1a0d267SMarcel Moolenaar     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
5917d1a0d267SMarcel Moolenaar }
5918d1a0d267SMarcel Moolenaar 
5919d1a0d267SMarcel Moolenaar /*
5920d1a0d267SMarcel Moolenaar  * We've got two lists of fields, the old list from the original
5921d1a0d267SMarcel Moolenaar  * format string and the new one from the parsed gettext reply.  The
5922d1a0d267SMarcel Moolenaar  * new list has the localized words, where the old list has the
5923d1a0d267SMarcel Moolenaar  * formatting information.  We need to combine them into a single list
5924d1a0d267SMarcel Moolenaar  * (the new list).
5925d1a0d267SMarcel Moolenaar  *
5926d1a0d267SMarcel Moolenaar  * If the list needs to be reordered, then we've got more serious work
5927d1a0d267SMarcel Moolenaar  * to do.
5928d1a0d267SMarcel Moolenaar  */
5929d1a0d267SMarcel Moolenaar static int
5930d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
5931d1a0d267SMarcel Moolenaar 		    const char *gtfmt, xo_field_info_t *old_fields,
5932d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields, unsigned new_max_fields,
5933d1a0d267SMarcel Moolenaar 		    int *reorderedp)
5934d1a0d267SMarcel Moolenaar {
5935d1a0d267SMarcel Moolenaar     int reordered = 0;
5936d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *oldp, *startp = old_fields;
5937d1a0d267SMarcel Moolenaar 
5938d1a0d267SMarcel Moolenaar     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
5939d1a0d267SMarcel Moolenaar 
5940d1a0d267SMarcel Moolenaar     for (newp = new_fields; newp->xfi_ftype; newp++) {
5941d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5942d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5943d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5944d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5945d1a0d267SMarcel Moolenaar 	    continue;
5946d1a0d267SMarcel Moolenaar 
5947d1a0d267SMarcel Moolenaar 	case 'V':
5948d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5949d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype != 'V')
5950d1a0d267SMarcel Moolenaar 		    continue;
5951d1a0d267SMarcel Moolenaar 		if (newp->xfi_clen != oldp->xfi_clen
5952d1a0d267SMarcel Moolenaar 		    || strncmp(newp->xfi_content, oldp->xfi_content,
5953d1a0d267SMarcel Moolenaar 			       oldp->xfi_clen) != 0) {
5954d1a0d267SMarcel Moolenaar 		    reordered = 1;
5955d1a0d267SMarcel Moolenaar 		    continue;
5956d1a0d267SMarcel Moolenaar 		}
5957d1a0d267SMarcel Moolenaar 		startp = oldp + 1;
5958d1a0d267SMarcel Moolenaar 		break;
5959d1a0d267SMarcel Moolenaar 	    }
5960d1a0d267SMarcel Moolenaar 
5961d1a0d267SMarcel Moolenaar 	    /* Didn't find it on the first pass (starting from start) */
5962d1a0d267SMarcel Moolenaar 	    if (oldp->xfi_ftype == 0) {
5963d1a0d267SMarcel Moolenaar 		for (oldp = old_fields; oldp < startp; oldp++) {
5964d1a0d267SMarcel Moolenaar 		    if (oldp->xfi_ftype != 'V')
5965d1a0d267SMarcel Moolenaar 			continue;
5966d1a0d267SMarcel Moolenaar 		    if (newp->xfi_clen != oldp->xfi_clen)
5967d1a0d267SMarcel Moolenaar 			continue;
5968d1a0d267SMarcel Moolenaar 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
5969d1a0d267SMarcel Moolenaar 				oldp->xfi_clen) != 0)
5970d1a0d267SMarcel Moolenaar 			continue;
5971d1a0d267SMarcel Moolenaar 		    reordered = 1;
5972d1a0d267SMarcel Moolenaar 		    break;
5973d1a0d267SMarcel Moolenaar 		}
5974d1a0d267SMarcel Moolenaar 		if (oldp == startp) {
5975d1a0d267SMarcel Moolenaar 		    /* Field not found */
5976d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "post-gettext format can't find field "
5977d1a0d267SMarcel Moolenaar 			       "'%.*s' in format '%s'",
5978d1a0d267SMarcel Moolenaar 			       newp->xfi_clen, newp->xfi_content,
5979d1a0d267SMarcel Moolenaar 			       xo_printable(gtfmt));
5980d1a0d267SMarcel Moolenaar 		    return -1;
5981d1a0d267SMarcel Moolenaar 		}
5982d1a0d267SMarcel Moolenaar 	    }
5983d1a0d267SMarcel Moolenaar 	    break;
5984d1a0d267SMarcel Moolenaar 
5985d1a0d267SMarcel Moolenaar 	default:
5986d1a0d267SMarcel Moolenaar 	    /*
5987d1a0d267SMarcel Moolenaar 	     * Other fields don't have names for us to use, so if
5988d1a0d267SMarcel Moolenaar 	     * the types aren't the same, then we'll have to assume
5989d1a0d267SMarcel Moolenaar 	     * the original field is a match.
5990d1a0d267SMarcel Moolenaar 	     */
5991d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5992d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
5993d1a0d267SMarcel Moolenaar 		    break;
5994d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == newp->xfi_ftype)
5995d1a0d267SMarcel Moolenaar 		    goto copy_it; /* Assumably we have a match */
5996d1a0d267SMarcel Moolenaar 	    }
5997d1a0d267SMarcel Moolenaar 	    continue;
5998d1a0d267SMarcel Moolenaar 	}
5999d1a0d267SMarcel Moolenaar 
6000d1a0d267SMarcel Moolenaar 	/*
6001d1a0d267SMarcel Moolenaar 	 * Found a match; copy over appropriate fields
6002d1a0d267SMarcel Moolenaar 	 */
6003d1a0d267SMarcel Moolenaar     copy_it:
6004d1a0d267SMarcel Moolenaar 	newp->xfi_flags = oldp->xfi_flags;
6005d1a0d267SMarcel Moolenaar 	newp->xfi_fnum = oldp->xfi_fnum;
6006d1a0d267SMarcel Moolenaar 	newp->xfi_format = oldp->xfi_format;
6007d1a0d267SMarcel Moolenaar 	newp->xfi_flen = oldp->xfi_flen;
6008d1a0d267SMarcel Moolenaar 	newp->xfi_encoding = oldp->xfi_encoding;
6009d1a0d267SMarcel Moolenaar 	newp->xfi_elen = oldp->xfi_elen;
6010d1a0d267SMarcel Moolenaar     }
6011d1a0d267SMarcel Moolenaar 
6012d1a0d267SMarcel Moolenaar     *reorderedp = reordered;
6013d1a0d267SMarcel Moolenaar     if (reordered) {
6014d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
6015d1a0d267SMarcel Moolenaar 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
6016d1a0d267SMarcel Moolenaar     }
6017d1a0d267SMarcel Moolenaar 
6018d1a0d267SMarcel Moolenaar     return 0;
6019d1a0d267SMarcel Moolenaar }
6020d1a0d267SMarcel Moolenaar 
6021d1a0d267SMarcel Moolenaar /*
6022d1a0d267SMarcel Moolenaar  * We don't want to make gettext() calls here with a complete format
6023d1a0d267SMarcel Moolenaar  * string, since that means changing a flag would mean a
6024d1a0d267SMarcel Moolenaar  * labor-intensive re-translation expense.  Instead we build a
6025d1a0d267SMarcel Moolenaar  * simplified form with a reduced level of detail, perform a lookup on
6026d1a0d267SMarcel Moolenaar  * that string and then re-insert the formating info.
6027d1a0d267SMarcel Moolenaar  *
6028d1a0d267SMarcel Moolenaar  * So something like:
6029d1a0d267SMarcel Moolenaar  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
6030d1a0d267SMarcel Moolenaar  * would have a lookup string of:
6031d1a0d267SMarcel Moolenaar  *   "close {:fd} returned {:error} {:test}\n"
6032d1a0d267SMarcel Moolenaar  *
6033d1a0d267SMarcel Moolenaar  * We also need to handling reordering of fields, where the gettext()
6034d1a0d267SMarcel Moolenaar  * reply string uses fields in a different order than the original
6035d1a0d267SMarcel Moolenaar  * format string:
6036d1a0d267SMarcel Moolenaar  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
6037d1a0d267SMarcel Moolenaar  * If we have to reorder fields within the message, then things get
6038d1a0d267SMarcel Moolenaar  * complicated.  See xo_gettext_rewrite_fields.
6039d1a0d267SMarcel Moolenaar  *
6040d1a0d267SMarcel Moolenaar  * Summary: i18n aighn't cheap.
6041d1a0d267SMarcel Moolenaar  */
6042d1a0d267SMarcel Moolenaar static const char *
604342ff34c3SPhil Shafer xo_gettext_build_format (xo_handle_t *xop,
604442ff34c3SPhil Shafer 			 xo_field_info_t *fields, int this_field,
6045d1a0d267SMarcel Moolenaar 			 const char *fmt, char **new_fmtp)
6046d1a0d267SMarcel Moolenaar {
6047d1a0d267SMarcel Moolenaar     if (xo_style_is_encoding(xop))
6048d1a0d267SMarcel Moolenaar 	goto bail;
6049d1a0d267SMarcel Moolenaar 
6050d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
6051d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
6052d1a0d267SMarcel Moolenaar 
6053d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields,
6054d1a0d267SMarcel Moolenaar 				   this_field, fmt, NULL))
6055d1a0d267SMarcel Moolenaar 	goto bail2;
6056d1a0d267SMarcel Moolenaar 
6057d1a0d267SMarcel Moolenaar     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
6058d1a0d267SMarcel Moolenaar     if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
6059d1a0d267SMarcel Moolenaar 	goto bail2;
6060d1a0d267SMarcel Moolenaar 
6061d1a0d267SMarcel Moolenaar     char *new_fmt = xo_strndup(gtfmt, -1);
6062d1a0d267SMarcel Moolenaar     if (new_fmt == NULL)
6063d1a0d267SMarcel Moolenaar 	goto bail2;
6064d1a0d267SMarcel Moolenaar 
6065f2b7bf8aSPhil Shafer     xo_buf_cleanup(&xb);
6066f2b7bf8aSPhil Shafer 
6067d1a0d267SMarcel Moolenaar     *new_fmtp = new_fmt;
6068d1a0d267SMarcel Moolenaar     return new_fmt;
6069d1a0d267SMarcel Moolenaar 
6070d1a0d267SMarcel Moolenaar  bail2:
6071d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(&xb);
6072d1a0d267SMarcel Moolenaar  bail:
6073d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
6074d1a0d267SMarcel Moolenaar     return fmt;
6075d1a0d267SMarcel Moolenaar }
6076d1a0d267SMarcel Moolenaar 
6077d1a0d267SMarcel Moolenaar static void
6078d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
60798a6eceffSPhil Shafer 			    ssize_t *fstart, unsigned min_fstart,
60808a6eceffSPhil Shafer 			    ssize_t *fend, unsigned max_fend)
6081d1a0d267SMarcel Moolenaar {
6082d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
6083d1a0d267SMarcel Moolenaar     char *buf;
60848a6eceffSPhil Shafer     ssize_t base = fstart[min_fstart];
60858a6eceffSPhil Shafer     ssize_t blen = fend[max_fend] - base;
6086d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
6087d1a0d267SMarcel Moolenaar 
6088d1a0d267SMarcel Moolenaar     if (blen == 0)
6089d1a0d267SMarcel Moolenaar 	return;
6090d1a0d267SMarcel Moolenaar 
6091d1a0d267SMarcel Moolenaar     buf = xo_realloc(NULL, blen);
6092d1a0d267SMarcel Moolenaar     if (buf == NULL)
6093d1a0d267SMarcel Moolenaar 	return;
6094d1a0d267SMarcel Moolenaar 
6095d1a0d267SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
6096d1a0d267SMarcel Moolenaar 
60978a6eceffSPhil Shafer     unsigned field = min_fstart, len, fnum;
60988a6eceffSPhil Shafer     ssize_t soff, doff = base;
6099d1a0d267SMarcel Moolenaar     xo_field_info_t *zp;
6100d1a0d267SMarcel Moolenaar 
6101d1a0d267SMarcel Moolenaar     /*
6102d1a0d267SMarcel Moolenaar      * Be aware there are two competing views of "field number": we
6103d1a0d267SMarcel Moolenaar      * want the user to thing in terms of "The {1:size}" where {G:},
6104d1a0d267SMarcel Moolenaar      * newlines, escaped braces, and text don't have numbers.  But is
6105d1a0d267SMarcel Moolenaar      * also the internal view, where we have an array of
6106d1a0d267SMarcel Moolenaar      * xo_field_info_t and every field have an index.  fnum, fstart[]
6107d1a0d267SMarcel Moolenaar      * and fend[] are the latter, but xfi_renum is the former.
6108d1a0d267SMarcel Moolenaar      */
6109d1a0d267SMarcel Moolenaar     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
6110d1a0d267SMarcel Moolenaar 	fnum = field;
6111d1a0d267SMarcel Moolenaar 	if (xfip->xfi_renum) {
6112d1a0d267SMarcel Moolenaar 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
6113d1a0d267SMarcel Moolenaar 	    fnum = zp ? zp - fields : field;
6114d1a0d267SMarcel Moolenaar 	}
6115d1a0d267SMarcel Moolenaar 
6116d1a0d267SMarcel Moolenaar 	soff = fstart[fnum];
6117d1a0d267SMarcel Moolenaar 	len = fend[fnum] - soff;
6118d1a0d267SMarcel Moolenaar 
6119d1a0d267SMarcel Moolenaar 	if (len > 0) {
6120d1a0d267SMarcel Moolenaar 	    soff -= base;
6121d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
6122d1a0d267SMarcel Moolenaar 	    doff += len;
6123d1a0d267SMarcel Moolenaar 	}
6124d1a0d267SMarcel Moolenaar     }
6125d1a0d267SMarcel Moolenaar 
6126d1a0d267SMarcel Moolenaar     xo_free(buf);
6127d1a0d267SMarcel Moolenaar }
6128d1a0d267SMarcel Moolenaar #else  /* HAVE_GETTEXT */
6129d1a0d267SMarcel Moolenaar static const char *
6130d1a0d267SMarcel Moolenaar xo_gettext_build_format (xo_handle_t *xop UNUSED,
6131d1a0d267SMarcel Moolenaar 			 xo_field_info_t *fields UNUSED,
6132d1a0d267SMarcel Moolenaar 			 int this_field UNUSED,
6133d1a0d267SMarcel Moolenaar 			 const char *fmt UNUSED, char **new_fmtp)
6134d1a0d267SMarcel Moolenaar {
6135d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
6136d1a0d267SMarcel Moolenaar     return fmt;
6137d1a0d267SMarcel Moolenaar }
6138d1a0d267SMarcel Moolenaar 
6139d1a0d267SMarcel Moolenaar static int
6140d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
6141d1a0d267SMarcel Moolenaar 		    const char *gtfmt UNUSED,
6142d1a0d267SMarcel Moolenaar 		    xo_field_info_t *old_fields UNUSED,
6143d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields UNUSED,
6144d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields UNUSED,
6145d1a0d267SMarcel Moolenaar 		    int *reorderedp UNUSED)
6146d1a0d267SMarcel Moolenaar {
6147d1a0d267SMarcel Moolenaar     return -1;
6148d1a0d267SMarcel Moolenaar }
6149d1a0d267SMarcel Moolenaar 
6150d1a0d267SMarcel Moolenaar static void
6151d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
6152d1a0d267SMarcel Moolenaar 		    xo_field_info_t *fields UNUSED,
61538a6eceffSPhil Shafer 		    ssize_t *fstart UNUSED, unsigned min_fstart UNUSED,
61548a6eceffSPhil Shafer 		    ssize_t *fend UNUSED, unsigned max_fend UNUSED)
6155d1a0d267SMarcel Moolenaar {
6156d1a0d267SMarcel Moolenaar     return;
6157d1a0d267SMarcel Moolenaar }
6158d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
6159d1a0d267SMarcel Moolenaar 
6160d1a0d267SMarcel Moolenaar /*
616142ff34c3SPhil Shafer  * Emit a set of fields.  This is really the core of libxo.
6162d1a0d267SMarcel Moolenaar  */
61638a6eceffSPhil Shafer static ssize_t
616442ff34c3SPhil Shafer xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
616542ff34c3SPhil Shafer 		   unsigned max_fields, const char *fmt)
6166d1a0d267SMarcel Moolenaar {
6167d1a0d267SMarcel Moolenaar     int gettext_inuse = 0;
6168d1a0d267SMarcel Moolenaar     int gettext_changed = 0;
6169d1a0d267SMarcel Moolenaar     int gettext_reordered = 0;
617042ff34c3SPhil Shafer     unsigned ftype;
617142ff34c3SPhil Shafer     xo_xff_flags_t flags;
6172d1a0d267SMarcel Moolenaar     xo_field_info_t *new_fields = NULL;
617342ff34c3SPhil Shafer     xo_field_info_t *xfip;
617442ff34c3SPhil Shafer     unsigned field;
61758a6eceffSPhil Shafer     ssize_t rc = 0;
617642ff34c3SPhil Shafer 
6177d1a0d267SMarcel Moolenaar     int flush = XOF_ISSET(xop, XOF_FLUSH);
6178d1a0d267SMarcel Moolenaar     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
6179d1a0d267SMarcel Moolenaar     char *new_fmt = NULL;
6180d1a0d267SMarcel Moolenaar 
6181d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
6182d1a0d267SMarcel Moolenaar 	flush_line = 0;
6183d1a0d267SMarcel Moolenaar 
6184d1a0d267SMarcel Moolenaar     /*
6185d1a0d267SMarcel Moolenaar      * Some overhead for gettext; if the fields in the msgstr returned
6186d1a0d267SMarcel Moolenaar      * by gettext are reordered, then we need to record start and end
6187d1a0d267SMarcel Moolenaar      * for each field.  We'll go ahead and render the fields in the
6188d1a0d267SMarcel Moolenaar      * normal order, but later we can then reconstruct the reordered
6189d1a0d267SMarcel Moolenaar      * fields using these fstart/fend values.
6190d1a0d267SMarcel Moolenaar      */
6191d1a0d267SMarcel Moolenaar     unsigned flimit = max_fields * 2; /* Pessimistic limit */
6192d1a0d267SMarcel Moolenaar     unsigned min_fstart = flimit - 1;
6193d1a0d267SMarcel Moolenaar     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
61948a6eceffSPhil Shafer     ssize_t fstart[flimit];
6195d1a0d267SMarcel Moolenaar     bzero(fstart, flimit * sizeof(fstart[0]));
61968a6eceffSPhil Shafer     ssize_t fend[flimit];
6197d1a0d267SMarcel Moolenaar     bzero(fend, flimit * sizeof(fend[0]));
6198d1a0d267SMarcel Moolenaar 
6199f2b7bf8aSPhil Shafer     for (xfip = fields, field = 0; field < max_fields && xfip->xfi_ftype;
6200d1a0d267SMarcel Moolenaar 	 xfip++, field++) {
6201d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
6202d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
6203d1a0d267SMarcel Moolenaar 
6204d1a0d267SMarcel Moolenaar 	/* Record field start offset */
6205d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
6206d1a0d267SMarcel Moolenaar 	    fstart[field] = xo_buf_offset(&xop->xo_data);
6207d1a0d267SMarcel Moolenaar 	    if (min_fstart > field)
6208d1a0d267SMarcel Moolenaar 		min_fstart = field;
6209d1a0d267SMarcel Moolenaar 	}
6210d1a0d267SMarcel Moolenaar 
621142ff34c3SPhil Shafer 	const char *content = xfip->xfi_content;
62128a6eceffSPhil Shafer 	ssize_t clen = xfip->xfi_clen;
621342ff34c3SPhil Shafer 
621442ff34c3SPhil Shafer 	if (flags & XFF_ARGUMENT) {
621542ff34c3SPhil Shafer 	    /*
621642ff34c3SPhil Shafer 	     * Argument flag means the content isn't given in the descriptor,
621742ff34c3SPhil Shafer 	     * but as a UTF-8 string ('const char *') argument in xo_vap.
621842ff34c3SPhil Shafer 	     */
621942ff34c3SPhil Shafer 	    content = va_arg(xop->xo_vap, char *);
622042ff34c3SPhil Shafer 	    clen = content ? strlen(content) : 0;
622142ff34c3SPhil Shafer 	}
622242ff34c3SPhil Shafer 
6223d1a0d267SMarcel Moolenaar 	if (ftype == XO_ROLE_NEWLINE) {
6224d1a0d267SMarcel Moolenaar 	    xo_line_close(xop);
6225d1a0d267SMarcel Moolenaar 	    if (flush_line && xo_flush_h(xop) < 0)
6226d1a0d267SMarcel Moolenaar 		return -1;
6227d1a0d267SMarcel Moolenaar 	    goto bottom;
6228d1a0d267SMarcel Moolenaar 
6229d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_EBRACE) {
6230d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6231d1a0d267SMarcel Moolenaar 	    goto bottom;
6232d1a0d267SMarcel Moolenaar 
6233d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_TEXT) {
6234d1a0d267SMarcel Moolenaar 	    /* Normal text */
6235d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6236d1a0d267SMarcel Moolenaar 	    goto bottom;
6237d1a0d267SMarcel Moolenaar 	}
6238d1a0d267SMarcel Moolenaar 
6239d1a0d267SMarcel Moolenaar 	/*
6240d1a0d267SMarcel Moolenaar 	 * Notes and units need the 'w' flag handled before the content.
6241d1a0d267SMarcel Moolenaar 	 */
6242d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
6243d1a0d267SMarcel Moolenaar 	    if (flags & XFF_WS) {
6244d1a0d267SMarcel Moolenaar 		xo_format_content(xop, "padding", NULL, " ", 1,
6245d1a0d267SMarcel Moolenaar 				  NULL, 0, flags);
6246264104f2SPhil Shafer 		flags &= ~XFF_WS; /* Prevent later handling of this flag */
6247d1a0d267SMarcel Moolenaar 	    }
6248d1a0d267SMarcel Moolenaar 	}
6249d1a0d267SMarcel Moolenaar 
6250d1a0d267SMarcel Moolenaar 	if (ftype == 'V')
6251264104f2SPhil Shafer 	    xo_format_value(xop, content, clen, NULL, 0,
6252d1a0d267SMarcel Moolenaar 			    xfip->xfi_format, xfip->xfi_flen,
6253d1a0d267SMarcel Moolenaar 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
6254d1a0d267SMarcel Moolenaar 	else if (ftype == '[')
625542ff34c3SPhil Shafer 	    xo_anchor_start(xop, xfip, content, clen);
6256545ddfbeSMarcel Moolenaar 	else if (ftype == ']')
625742ff34c3SPhil Shafer 	    xo_anchor_stop(xop, xfip, content, clen);
6258788ca347SMarcel Moolenaar 	else if (ftype == 'C')
625942ff34c3SPhil Shafer 	    xo_format_colors(xop, xfip, content, clen);
6260545ddfbeSMarcel Moolenaar 
6261d1a0d267SMarcel Moolenaar 	else if (ftype == 'G') {
6262d1a0d267SMarcel Moolenaar 	    /*
6263d1a0d267SMarcel Moolenaar 	     * A {G:domain} field; disect the domain name and translate
6264d1a0d267SMarcel Moolenaar 	     * the remaining portion of the input string.  If the user
6265d1a0d267SMarcel Moolenaar 	     * didn't put the {G:} at the start of the format string, then
6266d1a0d267SMarcel Moolenaar 	     * assumably they just want us to translate the rest of it.
6267d1a0d267SMarcel Moolenaar 	     * Since gettext returns strings in a static buffer, we make
6268d1a0d267SMarcel Moolenaar 	     * a copy in new_fmt.
6269d1a0d267SMarcel Moolenaar 	     */
627042ff34c3SPhil Shafer 	    xo_set_gettext_domain(xop, xfip, content, clen);
6271d1a0d267SMarcel Moolenaar 
6272d1a0d267SMarcel Moolenaar 	    if (!gettext_inuse) { /* Only translate once */
6273d1a0d267SMarcel Moolenaar 		gettext_inuse = 1;
6274d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6275d1a0d267SMarcel Moolenaar 		    xo_free(new_fmt);
6276d1a0d267SMarcel Moolenaar 		    new_fmt = NULL;
6277545ddfbeSMarcel Moolenaar 		}
6278545ddfbeSMarcel Moolenaar 
6279d1a0d267SMarcel Moolenaar 		xo_gettext_build_format(xop, fields, field,
6280d1a0d267SMarcel Moolenaar 					xfip->xfi_next, &new_fmt);
6281d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6282d1a0d267SMarcel Moolenaar 		    gettext_changed = 1;
6283d1a0d267SMarcel Moolenaar 
6284d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6285d1a0d267SMarcel Moolenaar 
6286d1a0d267SMarcel Moolenaar 		    if (++new_max_fields < max_fields)
6287d1a0d267SMarcel Moolenaar 			new_max_fields = max_fields;
6288d1a0d267SMarcel Moolenaar 
6289d1a0d267SMarcel Moolenaar 		    /* Leave a blank slot at the beginning */
62908a6eceffSPhil Shafer 		    ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6291d1a0d267SMarcel Moolenaar 		    new_fields = alloca(sz);
6292d1a0d267SMarcel Moolenaar 		    bzero(new_fields, sz);
6293d1a0d267SMarcel Moolenaar 
6294d1a0d267SMarcel Moolenaar 		    if (!xo_parse_fields(xop, new_fields + 1,
6295d1a0d267SMarcel Moolenaar 					 new_max_fields, new_fmt)) {
6296d1a0d267SMarcel Moolenaar 			gettext_reordered = 0;
6297d1a0d267SMarcel Moolenaar 
6298d1a0d267SMarcel Moolenaar 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6299d1a0d267SMarcel Moolenaar 					fields, new_fields + 1,
6300d1a0d267SMarcel Moolenaar 					new_max_fields, &gettext_reordered)) {
6301d1a0d267SMarcel Moolenaar 
6302d1a0d267SMarcel Moolenaar 			    if (gettext_reordered) {
6303d1a0d267SMarcel Moolenaar 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6304d1a0d267SMarcel Moolenaar 				    xo_failure(xop, "gettext finds reordered "
6305d1a0d267SMarcel Moolenaar 					       "fields in '%s' and '%s'",
6306d1a0d267SMarcel Moolenaar 					       xo_printable(fmt),
6307d1a0d267SMarcel Moolenaar 					       xo_printable(new_fmt));
6308d1a0d267SMarcel Moolenaar 				flush_line = 0; /* Must keep at content */
6309d1a0d267SMarcel Moolenaar 				XOIF_SET(xop, XOIF_REORDER);
6310d1a0d267SMarcel Moolenaar 			    }
6311d1a0d267SMarcel Moolenaar 
6312d1a0d267SMarcel Moolenaar 			    field = -1; /* Will be incremented at top of loop */
6313d1a0d267SMarcel Moolenaar 			    xfip = new_fields;
6314d1a0d267SMarcel Moolenaar 			    max_fields = new_max_fields;
6315d1a0d267SMarcel Moolenaar 			}
6316d1a0d267SMarcel Moolenaar 		    }
6317d1a0d267SMarcel Moolenaar 		}
6318d1a0d267SMarcel Moolenaar 	    }
6319d1a0d267SMarcel Moolenaar 	    continue;
6320d1a0d267SMarcel Moolenaar 
632142ff34c3SPhil Shafer 	} else  if (clen || xfip->xfi_format) {
6322d1a0d267SMarcel Moolenaar 
6323d1a0d267SMarcel Moolenaar 	    const char *class_name = xo_class_name(ftype);
6324d1a0d267SMarcel Moolenaar 	    if (class_name)
6325d1a0d267SMarcel Moolenaar 		xo_format_content(xop, class_name, xo_tag_name(ftype),
632642ff34c3SPhil Shafer 				  content, clen,
6327d1a0d267SMarcel Moolenaar 				  xfip->xfi_format, xfip->xfi_flen, flags);
632831337658SMarcel Moolenaar 	    else if (ftype == 'T')
632942ff34c3SPhil Shafer 		xo_format_title(xop, xfip, content, clen);
6330d1a0d267SMarcel Moolenaar 	    else if (ftype == 'U')
633142ff34c3SPhil Shafer 		xo_format_units(xop, xfip, content, clen);
6332d1a0d267SMarcel Moolenaar 	    else
6333d1a0d267SMarcel Moolenaar 		xo_failure(xop, "unknown field type: '%c'", ftype);
6334545ddfbeSMarcel Moolenaar 	}
633531337658SMarcel Moolenaar 
633631337658SMarcel Moolenaar 	if (flags & XFF_COLON)
6337d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
633831337658SMarcel Moolenaar 
6339d1a0d267SMarcel Moolenaar 	if (flags & XFF_WS)
6340d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6341d1a0d267SMarcel Moolenaar 
6342d1a0d267SMarcel Moolenaar     bottom:
6343d1a0d267SMarcel Moolenaar 	/* Record the end-of-field offset */
6344d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
6345d1a0d267SMarcel Moolenaar 	    fend[field] = xo_buf_offset(&xop->xo_data);
6346d1a0d267SMarcel Moolenaar 	    max_fend = field;
634731337658SMarcel Moolenaar 	}
634831337658SMarcel Moolenaar     }
634931337658SMarcel Moolenaar 
6350d1a0d267SMarcel Moolenaar     if (gettext_changed && gettext_reordered) {
6351d1a0d267SMarcel Moolenaar 	/* Final step: rebuild the content using the rendered fields */
6352d1a0d267SMarcel Moolenaar 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6353d1a0d267SMarcel Moolenaar 				   fend, max_fend);
6354d1a0d267SMarcel Moolenaar     }
6355d1a0d267SMarcel Moolenaar 
6356d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_REORDER);
6357d1a0d267SMarcel Moolenaar 
6358ee5cf116SPhil Shafer     /*
6359ee5cf116SPhil Shafer      * If we've got enough data, flush it.
6360ee5cf116SPhil Shafer      */
6361ee5cf116SPhil Shafer     if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER)
6362ee5cf116SPhil Shafer 	flush = 1;
6363ee5cf116SPhil Shafer 
636431337658SMarcel Moolenaar     /* If we don't have an anchor, write the text out */
6365d1a0d267SMarcel Moolenaar     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6366545ddfbeSMarcel Moolenaar 	if (xo_write(xop) < 0)
6367545ddfbeSMarcel Moolenaar 	    rc = -1;		/* Report failure */
636842ff34c3SPhil Shafer 	else if (xo_flush_h(xop) < 0)
6369545ddfbeSMarcel Moolenaar 	    rc = -1;
6370545ddfbeSMarcel Moolenaar     }
637131337658SMarcel Moolenaar 
6372d1a0d267SMarcel Moolenaar     if (new_fmt)
6373d1a0d267SMarcel Moolenaar 	xo_free(new_fmt);
6374d1a0d267SMarcel Moolenaar 
6375d1a0d267SMarcel Moolenaar     /*
6376d1a0d267SMarcel Moolenaar      * We've carried the gettext domainname inside our handle just for
6377d1a0d267SMarcel Moolenaar      * convenience, but we need to ensure it doesn't survive across
6378d1a0d267SMarcel Moolenaar      * xo_emit calls.
6379d1a0d267SMarcel Moolenaar      */
6380d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
6381d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
6382d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
6383d1a0d267SMarcel Moolenaar     }
6384d1a0d267SMarcel Moolenaar 
63858a6eceffSPhil Shafer     return (rc < 0) ? rc : xop->xo_columns;
638631337658SMarcel Moolenaar }
638731337658SMarcel Moolenaar 
6388d1a0d267SMarcel Moolenaar /*
638942ff34c3SPhil Shafer  * Parse and emit a set of fields
639042ff34c3SPhil Shafer  */
639142ff34c3SPhil Shafer static int
639242ff34c3SPhil Shafer xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
639342ff34c3SPhil Shafer {
639442ff34c3SPhil Shafer     xop->xo_columns = 0;	/* Always reset it */
639542ff34c3SPhil Shafer     xop->xo_errno = errno;	/* Save for "%m" */
639642ff34c3SPhil Shafer 
639742ff34c3SPhil Shafer     if (fmt == NULL)
639842ff34c3SPhil Shafer 	return 0;
639942ff34c3SPhil Shafer 
640042ff34c3SPhil Shafer     unsigned max_fields;
640142ff34c3SPhil Shafer     xo_field_info_t *fields = NULL;
640242ff34c3SPhil Shafer 
640342ff34c3SPhil Shafer     /* Adjust XOEF_RETAIN based on global flags */
640442ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_ALL))
640542ff34c3SPhil Shafer 	flags |= XOEF_RETAIN;
640642ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_NONE))
640742ff34c3SPhil Shafer 	flags &= ~XOEF_RETAIN;
640842ff34c3SPhil Shafer 
640942ff34c3SPhil Shafer     /*
641042ff34c3SPhil Shafer      * Check for 'retain' flag, telling us to retain the field
641142ff34c3SPhil Shafer      * information.  If we've already saved it, then we can avoid
641242ff34c3SPhil Shafer      * re-parsing the format string.
641342ff34c3SPhil Shafer      */
641442ff34c3SPhil Shafer     if (!(flags & XOEF_RETAIN)
641542ff34c3SPhil Shafer 	|| xo_retain_find(fmt, &fields, &max_fields) != 0
641642ff34c3SPhil Shafer 	|| fields == NULL) {
641742ff34c3SPhil Shafer 
641842ff34c3SPhil Shafer 	/* Nothing retained; parse the format string */
641942ff34c3SPhil Shafer 	max_fields = xo_count_fields(xop, fmt);
642042ff34c3SPhil Shafer 	fields = alloca(max_fields * sizeof(fields[0]));
642142ff34c3SPhil Shafer 	bzero(fields, max_fields * sizeof(fields[0]));
642242ff34c3SPhil Shafer 
642342ff34c3SPhil Shafer 	if (xo_parse_fields(xop, fields, max_fields, fmt))
642442ff34c3SPhil Shafer 	    return -1;		/* Warning already displayed */
642542ff34c3SPhil Shafer 
642642ff34c3SPhil Shafer 	if (flags & XOEF_RETAIN) {
642742ff34c3SPhil Shafer 	    /* Retain the info */
642842ff34c3SPhil Shafer 	    xo_retain_add(fmt, fields, max_fields);
642942ff34c3SPhil Shafer 	}
643042ff34c3SPhil Shafer     }
643142ff34c3SPhil Shafer 
643242ff34c3SPhil Shafer     return xo_do_emit_fields(xop, fields, max_fields, fmt);
643342ff34c3SPhil Shafer }
643442ff34c3SPhil Shafer 
643542ff34c3SPhil Shafer /*
6436d1a0d267SMarcel Moolenaar  * Rebuild a format string in a gettext-friendly format.  This function
6437d1a0d267SMarcel Moolenaar  * is exposed to tools can perform this function.  See xo(1).
6438d1a0d267SMarcel Moolenaar  */
6439d1a0d267SMarcel Moolenaar char *
6440d1a0d267SMarcel Moolenaar xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6441d1a0d267SMarcel Moolenaar 		    xo_simplify_field_func_t field_cb)
6442d1a0d267SMarcel Moolenaar {
6443d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
6444d1a0d267SMarcel Moolenaar 
6445d1a0d267SMarcel Moolenaar     xop->xo_columns = 0;	/* Always reset it */
6446d1a0d267SMarcel Moolenaar     xop->xo_errno = errno;	/* Save for "%m" */
6447d1a0d267SMarcel Moolenaar 
6448d1a0d267SMarcel Moolenaar     unsigned max_fields = xo_count_fields(xop, fmt);
6449d1a0d267SMarcel Moolenaar     xo_field_info_t fields[max_fields];
6450d1a0d267SMarcel Moolenaar 
6451d1a0d267SMarcel Moolenaar     bzero(fields, max_fields * sizeof(fields[0]));
6452d1a0d267SMarcel Moolenaar 
6453d1a0d267SMarcel Moolenaar     if (xo_parse_fields(xop, fields, max_fields, fmt))
6454d1a0d267SMarcel Moolenaar 	return NULL;		/* Warning already displayed */
6455d1a0d267SMarcel Moolenaar 
6456d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
6457d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
6458d1a0d267SMarcel Moolenaar 
6459d1a0d267SMarcel Moolenaar     if (with_numbers)
6460d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
6461d1a0d267SMarcel Moolenaar 
6462d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6463d1a0d267SMarcel Moolenaar 	return NULL;
6464d1a0d267SMarcel Moolenaar 
6465d1a0d267SMarcel Moolenaar     return xb.xb_bufp;
6466d1a0d267SMarcel Moolenaar }
6467d1a0d267SMarcel Moolenaar 
64688a6eceffSPhil Shafer xo_ssize_t
646931337658SMarcel Moolenaar xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
647031337658SMarcel Moolenaar {
64718a6eceffSPhil Shafer     ssize_t rc;
647231337658SMarcel Moolenaar 
647331337658SMarcel Moolenaar     xop = xo_default(xop);
647431337658SMarcel Moolenaar     va_copy(xop->xo_vap, vap);
647542ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
647631337658SMarcel Moolenaar     va_end(xop->xo_vap);
647731337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
647831337658SMarcel Moolenaar 
647931337658SMarcel Moolenaar     return rc;
648031337658SMarcel Moolenaar }
648131337658SMarcel Moolenaar 
64828a6eceffSPhil Shafer xo_ssize_t
648331337658SMarcel Moolenaar xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
648431337658SMarcel Moolenaar {
64858a6eceffSPhil Shafer     ssize_t rc;
648631337658SMarcel Moolenaar 
648731337658SMarcel Moolenaar     xop = xo_default(xop);
648831337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
648942ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
649031337658SMarcel Moolenaar     va_end(xop->xo_vap);
649131337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
649231337658SMarcel Moolenaar 
649331337658SMarcel Moolenaar     return rc;
649431337658SMarcel Moolenaar }
649531337658SMarcel Moolenaar 
64968a6eceffSPhil Shafer xo_ssize_t
649731337658SMarcel Moolenaar xo_emit (const char *fmt, ...)
649831337658SMarcel Moolenaar {
649931337658SMarcel Moolenaar     xo_handle_t *xop = xo_default(NULL);
65008a6eceffSPhil Shafer     ssize_t rc;
650131337658SMarcel Moolenaar 
650231337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
650342ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
650431337658SMarcel Moolenaar     va_end(xop->xo_vap);
650531337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
650631337658SMarcel Moolenaar 
650731337658SMarcel Moolenaar     return rc;
650831337658SMarcel Moolenaar }
650931337658SMarcel Moolenaar 
65108a6eceffSPhil Shafer xo_ssize_t
651142ff34c3SPhil Shafer xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
651242ff34c3SPhil Shafer 	     const char *fmt, va_list vap)
651342ff34c3SPhil Shafer {
65148a6eceffSPhil Shafer     ssize_t rc;
651542ff34c3SPhil Shafer 
651642ff34c3SPhil Shafer     xop = xo_default(xop);
651742ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
651842ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
651942ff34c3SPhil Shafer     va_end(xop->xo_vap);
652042ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
652142ff34c3SPhil Shafer 
652242ff34c3SPhil Shafer     return rc;
652342ff34c3SPhil Shafer }
652442ff34c3SPhil Shafer 
65258a6eceffSPhil Shafer xo_ssize_t
652642ff34c3SPhil Shafer xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
652742ff34c3SPhil Shafer {
65288a6eceffSPhil Shafer     ssize_t rc;
652942ff34c3SPhil Shafer 
653042ff34c3SPhil Shafer     xop = xo_default(xop);
653142ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
653242ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
653342ff34c3SPhil Shafer     va_end(xop->xo_vap);
653442ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
653542ff34c3SPhil Shafer 
653642ff34c3SPhil Shafer     return rc;
653742ff34c3SPhil Shafer }
653842ff34c3SPhil Shafer 
65398a6eceffSPhil Shafer xo_ssize_t
654042ff34c3SPhil Shafer xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
654142ff34c3SPhil Shafer {
654242ff34c3SPhil Shafer     xo_handle_t *xop = xo_default(NULL);
65438a6eceffSPhil Shafer     ssize_t rc;
654442ff34c3SPhil Shafer 
654542ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
654642ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
654742ff34c3SPhil Shafer     va_end(xop->xo_vap);
654842ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
654942ff34c3SPhil Shafer 
655042ff34c3SPhil Shafer     return rc;
655142ff34c3SPhil Shafer }
655242ff34c3SPhil Shafer 
655342ff34c3SPhil Shafer /*
655442ff34c3SPhil Shafer  * Emit a single field by providing the info information typically provided
655542ff34c3SPhil Shafer  * inside the field description (role, modifiers, and formats).  This is
655642ff34c3SPhil Shafer  * a convenience function to avoid callers using snprintf to build field
655742ff34c3SPhil Shafer  * descriptions.
655842ff34c3SPhil Shafer  */
65598a6eceffSPhil Shafer xo_ssize_t
656042ff34c3SPhil Shafer xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
656142ff34c3SPhil Shafer 		  const char *fmt, const char *efmt,
656242ff34c3SPhil Shafer 		  va_list vap)
656342ff34c3SPhil Shafer {
65648a6eceffSPhil Shafer     ssize_t rc;
656542ff34c3SPhil Shafer 
656642ff34c3SPhil Shafer     xop = xo_default(xop);
656742ff34c3SPhil Shafer 
656842ff34c3SPhil Shafer     if (rolmod == NULL)
656942ff34c3SPhil Shafer 	rolmod = "V";
657042ff34c3SPhil Shafer 
657142ff34c3SPhil Shafer     xo_field_info_t xfi;
657242ff34c3SPhil Shafer 
657342ff34c3SPhil Shafer     bzero(&xfi, sizeof(xfi));
657442ff34c3SPhil Shafer 
657542ff34c3SPhil Shafer     const char *cp;
657642ff34c3SPhil Shafer     cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
657742ff34c3SPhil Shafer     if (cp == NULL)
657842ff34c3SPhil Shafer 	return -1;
657942ff34c3SPhil Shafer 
658042ff34c3SPhil Shafer     xfi.xfi_start = fmt;
658142ff34c3SPhil Shafer     xfi.xfi_content = contents;
658242ff34c3SPhil Shafer     xfi.xfi_format = fmt;
658342ff34c3SPhil Shafer     xfi.xfi_encoding = efmt;
658442ff34c3SPhil Shafer     xfi.xfi_clen = contents ? strlen(contents) : 0;
658542ff34c3SPhil Shafer     xfi.xfi_flen = fmt ? strlen(fmt) : 0;
658642ff34c3SPhil Shafer     xfi.xfi_elen = efmt ? strlen(efmt) : 0;
658742ff34c3SPhil Shafer 
658842ff34c3SPhil Shafer     /* If we have content, then we have a default format */
658942ff34c3SPhil Shafer     if (contents && fmt == NULL
659042ff34c3SPhil Shafer 		&& xo_role_wants_default_format(xfi.xfi_ftype)) {
659142ff34c3SPhil Shafer 	xfi.xfi_format = xo_default_format;
659242ff34c3SPhil Shafer 	xfi.xfi_flen = 2;
659342ff34c3SPhil Shafer     }
659442ff34c3SPhil Shafer 
659542ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
659642ff34c3SPhil Shafer 
659742ff34c3SPhil Shafer     rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
659842ff34c3SPhil Shafer 
659942ff34c3SPhil Shafer     va_end(xop->xo_vap);
660042ff34c3SPhil Shafer 
660142ff34c3SPhil Shafer     return rc;
660242ff34c3SPhil Shafer }
660342ff34c3SPhil Shafer 
66048a6eceffSPhil Shafer xo_ssize_t
660542ff34c3SPhil Shafer xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
660642ff34c3SPhil Shafer 		 const char *fmt, const char *efmt, ...)
660742ff34c3SPhil Shafer {
66088a6eceffSPhil Shafer     ssize_t rc;
660942ff34c3SPhil Shafer     va_list vap;
661042ff34c3SPhil Shafer 
661142ff34c3SPhil Shafer     va_start(vap, efmt);
661242ff34c3SPhil Shafer     rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
661342ff34c3SPhil Shafer     va_end(vap);
661442ff34c3SPhil Shafer 
661542ff34c3SPhil Shafer     return rc;
661642ff34c3SPhil Shafer }
661742ff34c3SPhil Shafer 
66188a6eceffSPhil Shafer xo_ssize_t
661942ff34c3SPhil Shafer xo_emit_field (const char *rolmod, const char *contents,
662042ff34c3SPhil Shafer 	       const char *fmt, const char *efmt, ...)
662142ff34c3SPhil Shafer {
66228a6eceffSPhil Shafer     ssize_t rc;
662342ff34c3SPhil Shafer     va_list vap;
662442ff34c3SPhil Shafer 
662542ff34c3SPhil Shafer     va_start(vap, efmt);
662642ff34c3SPhil Shafer     rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
662742ff34c3SPhil Shafer     va_end(vap);
662842ff34c3SPhil Shafer 
662942ff34c3SPhil Shafer     return rc;
663042ff34c3SPhil Shafer }
663142ff34c3SPhil Shafer 
66328a6eceffSPhil Shafer xo_ssize_t
663331337658SMarcel Moolenaar xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
663431337658SMarcel Moolenaar {
66358a6eceffSPhil Shafer     const ssize_t extra = 5; 	/* space, equals, quote, quote, and nul */
663631337658SMarcel Moolenaar     xop = xo_default(xop);
663731337658SMarcel Moolenaar 
66388a6eceffSPhil Shafer     ssize_t rc = 0;
66398a6eceffSPhil Shafer     ssize_t nlen = strlen(name);
664031337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_attrs;
66418a6eceffSPhil Shafer     ssize_t name_offset, value_offset;
664231337658SMarcel Moolenaar 
6643d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6644d1a0d267SMarcel Moolenaar     case XO_STYLE_XML:
664531337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, nlen + extra))
664631337658SMarcel Moolenaar 	    return -1;
664731337658SMarcel Moolenaar 
664831337658SMarcel Moolenaar 	*xbp->xb_curp++ = ' ';
664931337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, name, nlen);
665031337658SMarcel Moolenaar 	xbp->xb_curp += nlen;
665131337658SMarcel Moolenaar 	*xbp->xb_curp++ = '=';
665231337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
665331337658SMarcel Moolenaar 
6654d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
665531337658SMarcel Moolenaar 
6656d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
665731337658SMarcel Moolenaar 	    rc = xo_escape_xml(xbp, rc, 1);
665831337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
665931337658SMarcel Moolenaar 	}
666031337658SMarcel Moolenaar 
666131337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, 2))
666231337658SMarcel Moolenaar 	    return -1;
666331337658SMarcel Moolenaar 
666431337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
666531337658SMarcel Moolenaar 	*xbp->xb_curp = '\0';
666631337658SMarcel Moolenaar 
6667d1a0d267SMarcel Moolenaar 	rc += nlen + extra;
6668d1a0d267SMarcel Moolenaar 	break;
6669d1a0d267SMarcel Moolenaar 
6670d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6671d1a0d267SMarcel Moolenaar 	name_offset = xo_buf_offset(xbp);
6672d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, name, nlen);
6673d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "", 1);
6674d1a0d267SMarcel Moolenaar 
6675d1a0d267SMarcel Moolenaar 	value_offset = xo_buf_offset(xbp);
6676d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6677d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
6678d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += rc;
6679d1a0d267SMarcel Moolenaar 	    *xbp->xb_curp = '\0';
6680d1a0d267SMarcel Moolenaar 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6681d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, name_offset),
6682f2b7bf8aSPhil Shafer 				   xo_buf_data(xbp, value_offset), 0);
6683d1a0d267SMarcel Moolenaar 	}
6684d1a0d267SMarcel Moolenaar     }
6685d1a0d267SMarcel Moolenaar 
6686d1a0d267SMarcel Moolenaar     return rc;
668731337658SMarcel Moolenaar }
668831337658SMarcel Moolenaar 
66898a6eceffSPhil Shafer xo_ssize_t
669031337658SMarcel Moolenaar xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
669131337658SMarcel Moolenaar {
66928a6eceffSPhil Shafer     ssize_t rc;
669331337658SMarcel Moolenaar     va_list vap;
669431337658SMarcel Moolenaar 
669531337658SMarcel Moolenaar     va_start(vap, fmt);
669631337658SMarcel Moolenaar     rc = xo_attr_hv(xop, name, fmt, vap);
669731337658SMarcel Moolenaar     va_end(vap);
669831337658SMarcel Moolenaar 
669931337658SMarcel Moolenaar     return rc;
670031337658SMarcel Moolenaar }
670131337658SMarcel Moolenaar 
67028a6eceffSPhil Shafer xo_ssize_t
670331337658SMarcel Moolenaar xo_attr (const char *name, const char *fmt, ...)
670431337658SMarcel Moolenaar {
67058a6eceffSPhil Shafer     ssize_t rc;
670631337658SMarcel Moolenaar     va_list vap;
670731337658SMarcel Moolenaar 
670831337658SMarcel Moolenaar     va_start(vap, fmt);
670931337658SMarcel Moolenaar     rc = xo_attr_hv(NULL, name, fmt, vap);
671031337658SMarcel Moolenaar     va_end(vap);
671131337658SMarcel Moolenaar 
671231337658SMarcel Moolenaar     return rc;
671331337658SMarcel Moolenaar }
671431337658SMarcel Moolenaar 
671531337658SMarcel Moolenaar static void
671631337658SMarcel Moolenaar xo_stack_set_flags (xo_handle_t *xop)
671731337658SMarcel Moolenaar {
6718d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
671931337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
672031337658SMarcel Moolenaar 
672131337658SMarcel Moolenaar 	xsp->xs_flags |= XSF_NOT_FIRST;
6722d1a0d267SMarcel Moolenaar 	XOF_CLEAR(xop, XOF_NOT_FIRST);
672331337658SMarcel Moolenaar     }
672431337658SMarcel Moolenaar }
672531337658SMarcel Moolenaar 
672631337658SMarcel Moolenaar static void
672731337658SMarcel Moolenaar xo_depth_change (xo_handle_t *xop, const char *name,
6728545ddfbeSMarcel Moolenaar 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
672931337658SMarcel Moolenaar {
6730788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6731545ddfbeSMarcel Moolenaar 	indent = 0;
6732545ddfbeSMarcel Moolenaar 
6733d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_DTRT))
673431337658SMarcel Moolenaar 	flags |= XSF_DTRT;
673531337658SMarcel Moolenaar 
673631337658SMarcel Moolenaar     if (delta >= 0) {			/* Push operation */
673731337658SMarcel Moolenaar 	if (xo_depth_check(xop, xop->xo_depth + delta))
673831337658SMarcel Moolenaar 	    return;
673931337658SMarcel Moolenaar 
674031337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
674131337658SMarcel Moolenaar 	xsp->xs_flags = flags;
6742545ddfbeSMarcel Moolenaar 	xsp->xs_state = state;
674331337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
674431337658SMarcel Moolenaar 
6745545ddfbeSMarcel Moolenaar 	if (name == NULL)
6746545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
674731337658SMarcel Moolenaar 
6748d1a0d267SMarcel Moolenaar 	xsp->xs_name = xo_strndup(name, -1);
674931337658SMarcel Moolenaar 
675031337658SMarcel Moolenaar     } else {			/* Pop operation */
675131337658SMarcel Moolenaar 	if (xop->xo_depth == 0) {
6752d1a0d267SMarcel Moolenaar 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
675331337658SMarcel Moolenaar 		xo_failure(xop, "close with empty stack: '%s'", name);
675431337658SMarcel Moolenaar 	    return;
675531337658SMarcel Moolenaar 	}
675631337658SMarcel Moolenaar 
675731337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6758d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN)) {
675931337658SMarcel Moolenaar 	    const char *top = xsp->xs_name;
6760f2b7bf8aSPhil Shafer 	    if (top != NULL && name != NULL && strcmp(name, top) != 0) {
676131337658SMarcel Moolenaar 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
676231337658SMarcel Moolenaar 			      name, top);
676331337658SMarcel Moolenaar 		return;
676431337658SMarcel Moolenaar 	    }
676531337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
676631337658SMarcel Moolenaar 		xo_failure(xop, "list close on list confict: '%s'",
676731337658SMarcel Moolenaar 			      name);
676831337658SMarcel Moolenaar 		return;
676931337658SMarcel Moolenaar 	    }
677031337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
677131337658SMarcel Moolenaar 		xo_failure(xop, "list close on instance confict: '%s'",
677231337658SMarcel Moolenaar 			      name);
677331337658SMarcel Moolenaar 		return;
677431337658SMarcel Moolenaar 	    }
677531337658SMarcel Moolenaar 	}
677631337658SMarcel Moolenaar 
677731337658SMarcel Moolenaar 	if (xsp->xs_name) {
677831337658SMarcel Moolenaar 	    xo_free(xsp->xs_name);
677931337658SMarcel Moolenaar 	    xsp->xs_name = NULL;
678031337658SMarcel Moolenaar 	}
678131337658SMarcel Moolenaar 	if (xsp->xs_keys) {
678231337658SMarcel Moolenaar 	    xo_free(xsp->xs_keys);
678331337658SMarcel Moolenaar 	    xsp->xs_keys = NULL;
678431337658SMarcel Moolenaar 	}
678531337658SMarcel Moolenaar     }
678631337658SMarcel Moolenaar 
678731337658SMarcel Moolenaar     xop->xo_depth += delta;	/* Record new depth */
678831337658SMarcel Moolenaar     xop->xo_indent += indent;
678931337658SMarcel Moolenaar }
679031337658SMarcel Moolenaar 
679131337658SMarcel Moolenaar void
679231337658SMarcel Moolenaar xo_set_depth (xo_handle_t *xop, int depth)
679331337658SMarcel Moolenaar {
679431337658SMarcel Moolenaar     xop = xo_default(xop);
679531337658SMarcel Moolenaar 
679631337658SMarcel Moolenaar     if (xo_depth_check(xop, depth))
679731337658SMarcel Moolenaar 	return;
679831337658SMarcel Moolenaar 
679931337658SMarcel Moolenaar     xop->xo_depth += depth;
680031337658SMarcel Moolenaar     xop->xo_indent += depth;
680131337658SMarcel Moolenaar }
680231337658SMarcel Moolenaar 
680331337658SMarcel Moolenaar static xo_xsf_flags_t
68048a6eceffSPhil Shafer xo_stack_flags (xo_xof_flags_t xflags)
680531337658SMarcel Moolenaar {
680631337658SMarcel Moolenaar     if (xflags & XOF_DTRT)
680731337658SMarcel Moolenaar 	return XSF_DTRT;
680831337658SMarcel Moolenaar     return 0;
680931337658SMarcel Moolenaar }
681031337658SMarcel Moolenaar 
6811788ca347SMarcel Moolenaar static void
6812788ca347SMarcel Moolenaar xo_emit_top (xo_handle_t *xop, const char *ppn)
6813788ca347SMarcel Moolenaar {
6814788ca347SMarcel Moolenaar     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6815d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_TOP_EMITTED);
6816788ca347SMarcel Moolenaar 
6817788ca347SMarcel Moolenaar     if (xop->xo_version) {
6818788ca347SMarcel Moolenaar 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6819788ca347SMarcel Moolenaar 		  xo_indent(xop), "", xop->xo_version, ppn);
6820788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
6821788ca347SMarcel Moolenaar 	xop->xo_version = NULL;
6822788ca347SMarcel Moolenaar     }
6823788ca347SMarcel Moolenaar }
6824788ca347SMarcel Moolenaar 
68258a6eceffSPhil Shafer static ssize_t
6826545ddfbeSMarcel Moolenaar xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
682731337658SMarcel Moolenaar {
68288a6eceffSPhil Shafer     ssize_t rc = 0;
6829d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
683031337658SMarcel Moolenaar     const char *pre_nl = "";
683131337658SMarcel Moolenaar 
683231337658SMarcel Moolenaar     if (name == NULL) {
683331337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for container name");
683431337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
683531337658SMarcel Moolenaar     }
683631337658SMarcel Moolenaar 
683731337658SMarcel Moolenaar     flags |= xop->xo_flags;	/* Pick up handle flags */
683831337658SMarcel Moolenaar 
6839788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
684031337658SMarcel Moolenaar     case XO_STYLE_XML:
6841545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
6842545ddfbeSMarcel Moolenaar 
6843545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6844545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6845545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6846545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6847545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6848545ddfbeSMarcel Moolenaar 	}
6849545ddfbeSMarcel Moolenaar 
6850545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
685131337658SMarcel Moolenaar 	break;
685231337658SMarcel Moolenaar 
685331337658SMarcel Moolenaar     case XO_STYLE_JSON:
685431337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
685531337658SMarcel Moolenaar 
6856d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6857d1a0d267SMarcel Moolenaar 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6858788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
685931337658SMarcel Moolenaar 
686031337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6861d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
686231337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
686331337658SMarcel Moolenaar 
686431337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
686531337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
686631337658SMarcel Moolenaar 	break;
6867d1a0d267SMarcel Moolenaar 
6868d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6869d1a0d267SMarcel Moolenaar 	break;
6870d1a0d267SMarcel Moolenaar 
6871d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6872f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL, flags);
6873d1a0d267SMarcel Moolenaar 	break;
687431337658SMarcel Moolenaar     }
687531337658SMarcel Moolenaar 
6876545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
6877545ddfbeSMarcel Moolenaar 		    xo_stack_flags(flags));
6878545ddfbeSMarcel Moolenaar 
687931337658SMarcel Moolenaar     return rc;
688031337658SMarcel Moolenaar }
688131337658SMarcel Moolenaar 
6882545ddfbeSMarcel Moolenaar static int
6883545ddfbeSMarcel Moolenaar xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6884545ddfbeSMarcel Moolenaar {
6885545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
6886545ddfbeSMarcel Moolenaar }
6887545ddfbeSMarcel Moolenaar 
68888a6eceffSPhil Shafer xo_ssize_t
688931337658SMarcel Moolenaar xo_open_container_h (xo_handle_t *xop, const char *name)
689031337658SMarcel Moolenaar {
689131337658SMarcel Moolenaar     return xo_open_container_hf(xop, 0, name);
689231337658SMarcel Moolenaar }
689331337658SMarcel Moolenaar 
68948a6eceffSPhil Shafer xo_ssize_t
689531337658SMarcel Moolenaar xo_open_container (const char *name)
689631337658SMarcel Moolenaar {
689731337658SMarcel Moolenaar     return xo_open_container_hf(NULL, 0, name);
689831337658SMarcel Moolenaar }
689931337658SMarcel Moolenaar 
69008a6eceffSPhil Shafer xo_ssize_t
690131337658SMarcel Moolenaar xo_open_container_hd (xo_handle_t *xop, const char *name)
690231337658SMarcel Moolenaar {
690331337658SMarcel Moolenaar     return xo_open_container_hf(xop, XOF_DTRT, name);
690431337658SMarcel Moolenaar }
690531337658SMarcel Moolenaar 
69068a6eceffSPhil Shafer xo_ssize_t
690731337658SMarcel Moolenaar xo_open_container_d (const char *name)
690831337658SMarcel Moolenaar {
690931337658SMarcel Moolenaar     return xo_open_container_hf(NULL, XOF_DTRT, name);
691031337658SMarcel Moolenaar }
691131337658SMarcel Moolenaar 
6912545ddfbeSMarcel Moolenaar static int
6913545ddfbeSMarcel Moolenaar xo_do_close_container (xo_handle_t *xop, const char *name)
691431337658SMarcel Moolenaar {
691531337658SMarcel Moolenaar     xop = xo_default(xop);
691631337658SMarcel Moolenaar 
69178a6eceffSPhil Shafer     ssize_t rc = 0;
6918d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
691931337658SMarcel Moolenaar     const char *pre_nl = "";
692031337658SMarcel Moolenaar 
692131337658SMarcel Moolenaar     if (name == NULL) {
692231337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
692331337658SMarcel Moolenaar 
692431337658SMarcel Moolenaar 	name = xsp->xs_name;
692531337658SMarcel Moolenaar 	if (name) {
69268a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
692731337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
692831337658SMarcel Moolenaar 	    char *cp = alloca(len);
692931337658SMarcel Moolenaar 	    memcpy(cp, name, len);
693031337658SMarcel Moolenaar 	    name = cp;
6931545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6932545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
693331337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
693431337658SMarcel Moolenaar 	}
6935545ddfbeSMarcel Moolenaar     }
693631337658SMarcel Moolenaar 
6937788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
693831337658SMarcel Moolenaar     case XO_STYLE_XML:
6939545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
694031337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
694131337658SMarcel Moolenaar 	break;
694231337658SMarcel Moolenaar 
694331337658SMarcel Moolenaar     case XO_STYLE_JSON:
6944d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
694531337658SMarcel Moolenaar 	ppn = (xop->xo_depth <= 1) ? "\n" : "";
694631337658SMarcel Moolenaar 
6947545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
694831337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
694931337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
695031337658SMarcel Moolenaar 	break;
695131337658SMarcel Moolenaar 
695231337658SMarcel Moolenaar     case XO_STYLE_HTML:
695331337658SMarcel Moolenaar     case XO_STYLE_TEXT:
6954545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
695531337658SMarcel Moolenaar 	break;
6956d1a0d267SMarcel Moolenaar 
6957d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6958d1a0d267SMarcel Moolenaar 	break;
6959d1a0d267SMarcel Moolenaar 
6960d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6961d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
6962f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL, 0);
6963d1a0d267SMarcel Moolenaar 	break;
696431337658SMarcel Moolenaar     }
696531337658SMarcel Moolenaar 
696631337658SMarcel Moolenaar     return rc;
696731337658SMarcel Moolenaar }
696831337658SMarcel Moolenaar 
69698a6eceffSPhil Shafer xo_ssize_t
6970545ddfbeSMarcel Moolenaar xo_close_container_h (xo_handle_t *xop, const char *name)
6971545ddfbeSMarcel Moolenaar {
6972545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
6973545ddfbeSMarcel Moolenaar }
6974545ddfbeSMarcel Moolenaar 
69758a6eceffSPhil Shafer xo_ssize_t
697631337658SMarcel Moolenaar xo_close_container (const char *name)
697731337658SMarcel Moolenaar {
697831337658SMarcel Moolenaar     return xo_close_container_h(NULL, name);
697931337658SMarcel Moolenaar }
698031337658SMarcel Moolenaar 
69818a6eceffSPhil Shafer xo_ssize_t
698231337658SMarcel Moolenaar xo_close_container_hd (xo_handle_t *xop)
698331337658SMarcel Moolenaar {
698431337658SMarcel Moolenaar     return xo_close_container_h(xop, NULL);
698531337658SMarcel Moolenaar }
698631337658SMarcel Moolenaar 
69878a6eceffSPhil Shafer xo_ssize_t
698831337658SMarcel Moolenaar xo_close_container_d (void)
698931337658SMarcel Moolenaar {
699031337658SMarcel Moolenaar     return xo_close_container_h(NULL, NULL);
699131337658SMarcel Moolenaar }
699231337658SMarcel Moolenaar 
699331337658SMarcel Moolenaar static int
6994545ddfbeSMarcel Moolenaar xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
699531337658SMarcel Moolenaar {
69968a6eceffSPhil Shafer     ssize_t rc = 0;
6997545ddfbeSMarcel Moolenaar     int indent = 0;
6998545ddfbeSMarcel Moolenaar 
699931337658SMarcel Moolenaar     xop = xo_default(xop);
700031337658SMarcel Moolenaar 
7001d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
700231337658SMarcel Moolenaar     const char *pre_nl = "";
700331337658SMarcel Moolenaar 
7004d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7005d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7006d1a0d267SMarcel Moolenaar 
7007545ddfbeSMarcel Moolenaar 	indent = 1;
7008d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
7009d1a0d267SMarcel Moolenaar 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7010788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
701131337658SMarcel Moolenaar 
701231337658SMarcel Moolenaar 	if (name == NULL) {
701331337658SMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
701431337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
701531337658SMarcel Moolenaar 	}
701631337658SMarcel Moolenaar 
701731337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
701831337658SMarcel Moolenaar 
701931337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7020d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
702131337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
702231337658SMarcel Moolenaar 
702331337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
702431337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
7025d1a0d267SMarcel Moolenaar 	break;
7026d1a0d267SMarcel Moolenaar 
7027d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7028f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL, flags);
7029d1a0d267SMarcel Moolenaar 	break;
7030545ddfbeSMarcel Moolenaar     }
7031545ddfbeSMarcel Moolenaar 
7032545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
7033545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
703431337658SMarcel Moolenaar 
703531337658SMarcel Moolenaar     return rc;
703631337658SMarcel Moolenaar }
703731337658SMarcel Moolenaar 
7038545ddfbeSMarcel Moolenaar static int
7039545ddfbeSMarcel Moolenaar xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7040545ddfbeSMarcel Moolenaar {
7041545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
7042545ddfbeSMarcel Moolenaar }
7043545ddfbeSMarcel Moolenaar 
70448a6eceffSPhil Shafer xo_ssize_t
704542ff34c3SPhil Shafer xo_open_list_h (xo_handle_t *xop, const char *name)
704631337658SMarcel Moolenaar {
704731337658SMarcel Moolenaar     return xo_open_list_hf(xop, 0, name);
704831337658SMarcel Moolenaar }
704931337658SMarcel Moolenaar 
70508a6eceffSPhil Shafer xo_ssize_t
705131337658SMarcel Moolenaar xo_open_list (const char *name)
705231337658SMarcel Moolenaar {
705331337658SMarcel Moolenaar     return xo_open_list_hf(NULL, 0, name);
705431337658SMarcel Moolenaar }
705531337658SMarcel Moolenaar 
70568a6eceffSPhil Shafer xo_ssize_t
705742ff34c3SPhil Shafer xo_open_list_hd (xo_handle_t *xop, const char *name)
705831337658SMarcel Moolenaar {
705931337658SMarcel Moolenaar     return xo_open_list_hf(xop, XOF_DTRT, name);
706031337658SMarcel Moolenaar }
706131337658SMarcel Moolenaar 
70628a6eceffSPhil Shafer xo_ssize_t
706331337658SMarcel Moolenaar xo_open_list_d (const char *name)
706431337658SMarcel Moolenaar {
706531337658SMarcel Moolenaar     return xo_open_list_hf(NULL, XOF_DTRT, name);
706631337658SMarcel Moolenaar }
706731337658SMarcel Moolenaar 
7068545ddfbeSMarcel Moolenaar static int
7069545ddfbeSMarcel Moolenaar xo_do_close_list (xo_handle_t *xop, const char *name)
707031337658SMarcel Moolenaar {
70718a6eceffSPhil Shafer     ssize_t rc = 0;
707231337658SMarcel Moolenaar     const char *pre_nl = "";
707331337658SMarcel Moolenaar 
707431337658SMarcel Moolenaar     if (name == NULL) {
707531337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
707631337658SMarcel Moolenaar 
707731337658SMarcel Moolenaar 	name = xsp->xs_name;
707831337658SMarcel Moolenaar 	if (name) {
70798a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
708031337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
708131337658SMarcel Moolenaar 	    char *cp = alloca(len);
708231337658SMarcel Moolenaar 	    memcpy(cp, name, len);
708331337658SMarcel Moolenaar 	    name = cp;
7084545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7085545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
708631337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
708731337658SMarcel Moolenaar 	}
7088545ddfbeSMarcel Moolenaar     }
708931337658SMarcel Moolenaar 
7090d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7091d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
709231337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7093d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
709431337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
709531337658SMarcel Moolenaar 
7096545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
709731337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
709831337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7099d1a0d267SMarcel Moolenaar 	break;
710031337658SMarcel Moolenaar 
7101d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7102d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7103f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL, 0);
7104d1a0d267SMarcel Moolenaar 	break;
7105d1a0d267SMarcel Moolenaar 
7106d1a0d267SMarcel Moolenaar     default:
7107545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7108545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7109d1a0d267SMarcel Moolenaar 	break;
7110545ddfbeSMarcel Moolenaar     }
7111545ddfbeSMarcel Moolenaar 
7112a0f704ffSMarcel Moolenaar     return rc;
711331337658SMarcel Moolenaar }
711431337658SMarcel Moolenaar 
71158a6eceffSPhil Shafer xo_ssize_t
7116545ddfbeSMarcel Moolenaar xo_close_list_h (xo_handle_t *xop, const char *name)
7117545ddfbeSMarcel Moolenaar {
7118545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
7119545ddfbeSMarcel Moolenaar }
7120545ddfbeSMarcel Moolenaar 
71218a6eceffSPhil Shafer xo_ssize_t
712231337658SMarcel Moolenaar xo_close_list (const char *name)
712331337658SMarcel Moolenaar {
712431337658SMarcel Moolenaar     return xo_close_list_h(NULL, name);
712531337658SMarcel Moolenaar }
712631337658SMarcel Moolenaar 
71278a6eceffSPhil Shafer xo_ssize_t
712831337658SMarcel Moolenaar xo_close_list_hd (xo_handle_t *xop)
712931337658SMarcel Moolenaar {
713031337658SMarcel Moolenaar     return xo_close_list_h(xop, NULL);
713131337658SMarcel Moolenaar }
713231337658SMarcel Moolenaar 
71338a6eceffSPhil Shafer xo_ssize_t
713431337658SMarcel Moolenaar xo_close_list_d (void)
713531337658SMarcel Moolenaar {
713631337658SMarcel Moolenaar     return xo_close_list_h(NULL, NULL);
713731337658SMarcel Moolenaar }
713831337658SMarcel Moolenaar 
713931337658SMarcel Moolenaar static int
7140545ddfbeSMarcel Moolenaar xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7141545ddfbeSMarcel Moolenaar {
71428a6eceffSPhil Shafer     ssize_t rc = 0;
7143545ddfbeSMarcel Moolenaar     int indent = 0;
7144545ddfbeSMarcel Moolenaar 
7145545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7146545ddfbeSMarcel Moolenaar 
7147d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7148545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
7149545ddfbeSMarcel Moolenaar 
7150d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7151d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7152545ddfbeSMarcel Moolenaar 	indent = 1;
7153545ddfbeSMarcel Moolenaar 
7154d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7155d1a0d267SMarcel Moolenaar 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
7156545ddfbeSMarcel Moolenaar 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
7157d1a0d267SMarcel Moolenaar 		XOIF_SET(xop, XOIF_TOP_EMITTED);
7158545ddfbeSMarcel Moolenaar 	    }
7159545ddfbeSMarcel Moolenaar 	}
7160545ddfbeSMarcel Moolenaar 
7161545ddfbeSMarcel Moolenaar 	if (name == NULL) {
7162545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
7163545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
7164545ddfbeSMarcel Moolenaar 	}
7165545ddfbeSMarcel Moolenaar 
7166545ddfbeSMarcel Moolenaar 	xo_stack_set_flags(xop);
7167545ddfbeSMarcel Moolenaar 
7168545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7169d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7170545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7171545ddfbeSMarcel Moolenaar 
7172545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7173545ddfbeSMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
7174d1a0d267SMarcel Moolenaar 	break;
7175d1a0d267SMarcel Moolenaar 
7176d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7177f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL, flags);
7178d1a0d267SMarcel Moolenaar 	break;
7179545ddfbeSMarcel Moolenaar     }
7180545ddfbeSMarcel Moolenaar 
7181545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
7182545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
7183545ddfbeSMarcel Moolenaar 
7184545ddfbeSMarcel Moolenaar     return rc;
7185545ddfbeSMarcel Moolenaar }
7186545ddfbeSMarcel Moolenaar 
7187545ddfbeSMarcel Moolenaar static int
7188545ddfbeSMarcel Moolenaar xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
7189545ddfbeSMarcel Moolenaar {
71908a6eceffSPhil Shafer     ssize_t rc = 0;
7191545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
7192545ddfbeSMarcel Moolenaar 
7193545ddfbeSMarcel Moolenaar     if (name == NULL) {
7194545ddfbeSMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7195545ddfbeSMarcel Moolenaar 
7196545ddfbeSMarcel Moolenaar 	name = xsp->xs_name;
7197545ddfbeSMarcel Moolenaar 	if (name) {
71988a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
7199545ddfbeSMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
7200545ddfbeSMarcel Moolenaar 	    char *cp = alloca(len);
7201545ddfbeSMarcel Moolenaar 	    memcpy(cp, name, len);
7202545ddfbeSMarcel Moolenaar 	    name = cp;
7203545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7204545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
7205545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
7206545ddfbeSMarcel Moolenaar 	}
7207545ddfbeSMarcel Moolenaar     }
7208545ddfbeSMarcel Moolenaar 
7209d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
7210d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
7211545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7212d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7213545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7214545ddfbeSMarcel Moolenaar 
7215545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7216545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7217545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7218d1a0d267SMarcel Moolenaar 	break;
7219545ddfbeSMarcel Moolenaar 
7220d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7221f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL, 0);
7222ee5cf116SPhil Shafer 	/* FALLTHRU */
7223d1a0d267SMarcel Moolenaar 
7224d1a0d267SMarcel Moolenaar     default:
7225545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7226545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7227d1a0d267SMarcel Moolenaar 	break;
7228545ddfbeSMarcel Moolenaar     }
7229545ddfbeSMarcel Moolenaar 
7230545ddfbeSMarcel Moolenaar     return rc;
7231545ddfbeSMarcel Moolenaar }
7232545ddfbeSMarcel Moolenaar 
7233545ddfbeSMarcel Moolenaar static int
7234545ddfbeSMarcel Moolenaar xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
723531337658SMarcel Moolenaar {
723631337658SMarcel Moolenaar     xop = xo_default(xop);
723731337658SMarcel Moolenaar 
72388a6eceffSPhil Shafer     ssize_t rc = 0;
7239d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
724031337658SMarcel Moolenaar     const char *pre_nl = "";
724131337658SMarcel Moolenaar 
724231337658SMarcel Moolenaar     flags |= xop->xo_flags;
724331337658SMarcel Moolenaar 
724431337658SMarcel Moolenaar     if (name == NULL) {
724531337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for instance name");
724631337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
724731337658SMarcel Moolenaar     }
724831337658SMarcel Moolenaar 
7249788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
725031337658SMarcel Moolenaar     case XO_STYLE_XML:
7251545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
7252545ddfbeSMarcel Moolenaar 
7253545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7254545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7255545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
7256545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7257545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7258545ddfbeSMarcel Moolenaar 	}
7259545ddfbeSMarcel Moolenaar 
7260545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
726131337658SMarcel Moolenaar 	break;
726231337658SMarcel Moolenaar 
726331337658SMarcel Moolenaar     case XO_STYLE_JSON:
726431337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
726531337658SMarcel Moolenaar 
726631337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7267d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
726831337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
726931337658SMarcel Moolenaar 
727031337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s{%s",
727131337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", ppn);
727231337658SMarcel Moolenaar 	break;
7273d1a0d267SMarcel Moolenaar 
7274d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7275d1a0d267SMarcel Moolenaar 	break;
7276d1a0d267SMarcel Moolenaar 
7277d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7278f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL, flags);
7279d1a0d267SMarcel Moolenaar 	break;
728031337658SMarcel Moolenaar     }
728131337658SMarcel Moolenaar 
7282545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7283545ddfbeSMarcel Moolenaar 
728431337658SMarcel Moolenaar     return rc;
728531337658SMarcel Moolenaar }
728631337658SMarcel Moolenaar 
7287545ddfbeSMarcel Moolenaar static int
7288545ddfbeSMarcel Moolenaar xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7289545ddfbeSMarcel Moolenaar {
7290545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7291545ddfbeSMarcel Moolenaar }
7292545ddfbeSMarcel Moolenaar 
72938a6eceffSPhil Shafer xo_ssize_t
729431337658SMarcel Moolenaar xo_open_instance_h (xo_handle_t *xop, const char *name)
729531337658SMarcel Moolenaar {
729631337658SMarcel Moolenaar     return xo_open_instance_hf(xop, 0, name);
729731337658SMarcel Moolenaar }
729831337658SMarcel Moolenaar 
72998a6eceffSPhil Shafer xo_ssize_t
730031337658SMarcel Moolenaar xo_open_instance (const char *name)
730131337658SMarcel Moolenaar {
730231337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, 0, name);
730331337658SMarcel Moolenaar }
730431337658SMarcel Moolenaar 
73058a6eceffSPhil Shafer xo_ssize_t
730631337658SMarcel Moolenaar xo_open_instance_hd (xo_handle_t *xop, const char *name)
730731337658SMarcel Moolenaar {
730831337658SMarcel Moolenaar     return xo_open_instance_hf(xop, XOF_DTRT, name);
730931337658SMarcel Moolenaar }
731031337658SMarcel Moolenaar 
73118a6eceffSPhil Shafer xo_ssize_t
731231337658SMarcel Moolenaar xo_open_instance_d (const char *name)
731331337658SMarcel Moolenaar {
731431337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, XOF_DTRT, name);
731531337658SMarcel Moolenaar }
731631337658SMarcel Moolenaar 
7317545ddfbeSMarcel Moolenaar static int
7318545ddfbeSMarcel Moolenaar xo_do_close_instance (xo_handle_t *xop, const char *name)
731931337658SMarcel Moolenaar {
732031337658SMarcel Moolenaar     xop = xo_default(xop);
732131337658SMarcel Moolenaar 
73228a6eceffSPhil Shafer     ssize_t rc = 0;
7323d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
732431337658SMarcel Moolenaar     const char *pre_nl = "";
732531337658SMarcel Moolenaar 
732631337658SMarcel Moolenaar     if (name == NULL) {
732731337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
732831337658SMarcel Moolenaar 
732931337658SMarcel Moolenaar 	name = xsp->xs_name;
733031337658SMarcel Moolenaar 	if (name) {
73318a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
733231337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
733331337658SMarcel Moolenaar 	    char *cp = alloca(len);
733431337658SMarcel Moolenaar 	    memcpy(cp, name, len);
733531337658SMarcel Moolenaar 	    name = cp;
7336545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7337545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
733831337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
733931337658SMarcel Moolenaar 	}
7340545ddfbeSMarcel Moolenaar     }
734131337658SMarcel Moolenaar 
7342788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
734331337658SMarcel Moolenaar     case XO_STYLE_XML:
7344545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
734531337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
734631337658SMarcel Moolenaar 	break;
734731337658SMarcel Moolenaar 
734831337658SMarcel Moolenaar     case XO_STYLE_JSON:
7349d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
735031337658SMarcel Moolenaar 
7351545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
735231337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
735331337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
735431337658SMarcel Moolenaar 	break;
735531337658SMarcel Moolenaar 
735631337658SMarcel Moolenaar     case XO_STYLE_HTML:
735731337658SMarcel Moolenaar     case XO_STYLE_TEXT:
7358545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
735931337658SMarcel Moolenaar 	break;
7360d1a0d267SMarcel Moolenaar 
7361d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7362d1a0d267SMarcel Moolenaar 	break;
7363d1a0d267SMarcel Moolenaar 
7364d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7365d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7366f2b7bf8aSPhil Shafer 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL, 0);
7367d1a0d267SMarcel Moolenaar 	break;
736831337658SMarcel Moolenaar     }
736931337658SMarcel Moolenaar 
737031337658SMarcel Moolenaar     return rc;
737131337658SMarcel Moolenaar }
737231337658SMarcel Moolenaar 
73738a6eceffSPhil Shafer xo_ssize_t
7374545ddfbeSMarcel Moolenaar xo_close_instance_h (xo_handle_t *xop, const char *name)
7375545ddfbeSMarcel Moolenaar {
7376545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7377545ddfbeSMarcel Moolenaar }
7378545ddfbeSMarcel Moolenaar 
73798a6eceffSPhil Shafer xo_ssize_t
738031337658SMarcel Moolenaar xo_close_instance (const char *name)
738131337658SMarcel Moolenaar {
738231337658SMarcel Moolenaar     return xo_close_instance_h(NULL, name);
738331337658SMarcel Moolenaar }
738431337658SMarcel Moolenaar 
73858a6eceffSPhil Shafer xo_ssize_t
738631337658SMarcel Moolenaar xo_close_instance_hd (xo_handle_t *xop)
738731337658SMarcel Moolenaar {
738831337658SMarcel Moolenaar     return xo_close_instance_h(xop, NULL);
738931337658SMarcel Moolenaar }
739031337658SMarcel Moolenaar 
73918a6eceffSPhil Shafer xo_ssize_t
739231337658SMarcel Moolenaar xo_close_instance_d (void)
739331337658SMarcel Moolenaar {
739431337658SMarcel Moolenaar     return xo_close_instance_h(NULL, NULL);
739531337658SMarcel Moolenaar }
739631337658SMarcel Moolenaar 
7397545ddfbeSMarcel Moolenaar static int
7398545ddfbeSMarcel Moolenaar xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7399545ddfbeSMarcel Moolenaar {
7400545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
74018a6eceffSPhil Shafer     ssize_t rc = 0;
7402545ddfbeSMarcel Moolenaar     xo_xsf_flags_t flags;
7403545ddfbeSMarcel Moolenaar 
7404545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7405545ddfbeSMarcel Moolenaar 	switch (xsp->xs_state) {
7406545ddfbeSMarcel Moolenaar 	case XSS_INIT:
7407545ddfbeSMarcel Moolenaar 	    /* Nothing */
7408545ddfbeSMarcel Moolenaar 	    rc = 0;
7409545ddfbeSMarcel Moolenaar 	    break;
7410545ddfbeSMarcel Moolenaar 
7411545ddfbeSMarcel Moolenaar 	case XSS_OPEN_CONTAINER:
7412545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_container(xop, NULL);
7413545ddfbeSMarcel Moolenaar 	    break;
7414545ddfbeSMarcel Moolenaar 
7415545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LIST:
7416545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_list(xop, NULL);
7417545ddfbeSMarcel Moolenaar 	    break;
7418545ddfbeSMarcel Moolenaar 
7419545ddfbeSMarcel Moolenaar 	case XSS_OPEN_INSTANCE:
7420545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7421545ddfbeSMarcel Moolenaar 	    break;
7422545ddfbeSMarcel Moolenaar 
7423545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LEAF_LIST:
7424545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_leaf_list(xop, NULL);
7425545ddfbeSMarcel Moolenaar 	    break;
7426545ddfbeSMarcel Moolenaar 
7427545ddfbeSMarcel Moolenaar 	case XSS_MARKER:
7428545ddfbeSMarcel Moolenaar 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7429545ddfbeSMarcel Moolenaar 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7430545ddfbeSMarcel Moolenaar 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7431545ddfbeSMarcel Moolenaar 	    rc = 0;
7432545ddfbeSMarcel Moolenaar 	    break;
7433545ddfbeSMarcel Moolenaar 	}
7434545ddfbeSMarcel Moolenaar 
7435545ddfbeSMarcel Moolenaar 	if (rc < 0)
7436545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7437545ddfbeSMarcel Moolenaar     }
7438545ddfbeSMarcel Moolenaar 
7439545ddfbeSMarcel Moolenaar     return 0;
7440545ddfbeSMarcel Moolenaar }
7441545ddfbeSMarcel Moolenaar 
7442545ddfbeSMarcel Moolenaar /*
7443545ddfbeSMarcel Moolenaar  * This function is responsible for clearing out whatever is needed
7444545ddfbeSMarcel Moolenaar  * to get to the desired state, if possible.
7445545ddfbeSMarcel Moolenaar  */
7446545ddfbeSMarcel Moolenaar static int
7447545ddfbeSMarcel Moolenaar xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7448545ddfbeSMarcel Moolenaar {
7449545ddfbeSMarcel Moolenaar     xo_stack_t *xsp, *limit = NULL;
74508a6eceffSPhil Shafer     ssize_t rc;
7451545ddfbeSMarcel Moolenaar     xo_state_t need_state = new_state;
7452545ddfbeSMarcel Moolenaar 
7453545ddfbeSMarcel Moolenaar     if (new_state == XSS_CLOSE_CONTAINER)
7454545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_CONTAINER;
7455545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LIST)
7456545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LIST;
7457545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_INSTANCE)
7458545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_INSTANCE;
7459545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LEAF_LIST)
7460545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LEAF_LIST;
7461545ddfbeSMarcel Moolenaar     else if (new_state == XSS_MARKER)
7462545ddfbeSMarcel Moolenaar 	need_state = XSS_MARKER;
7463545ddfbeSMarcel Moolenaar     else
7464545ddfbeSMarcel Moolenaar 	return 0; /* Unknown or useless new states are ignored */
7465545ddfbeSMarcel Moolenaar 
7466545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7467545ddfbeSMarcel Moolenaar 	/*
7468545ddfbeSMarcel Moolenaar 	 * Marker's normally stop us from going any further, unless
7469545ddfbeSMarcel Moolenaar 	 * we are popping a marker (new_state == XSS_MARKER).
7470545ddfbeSMarcel Moolenaar 	 */
7471545ddfbeSMarcel Moolenaar 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7472545ddfbeSMarcel Moolenaar 	    if (name) {
7473545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7474545ddfbeSMarcel Moolenaar 			   "not found '%s'",
7475545ddfbeSMarcel Moolenaar 			   xo_state_name(new_state),
7476545ddfbeSMarcel Moolenaar 			   xsp->xs_name, name);
7477545ddfbeSMarcel Moolenaar 		return 0;
7478545ddfbeSMarcel Moolenaar 
7479545ddfbeSMarcel Moolenaar 	    } else {
7480545ddfbeSMarcel Moolenaar 		limit = xsp;
7481545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7482545ddfbeSMarcel Moolenaar 	    }
7483545ddfbeSMarcel Moolenaar 	    break;
7484545ddfbeSMarcel Moolenaar 	}
7485545ddfbeSMarcel Moolenaar 
7486545ddfbeSMarcel Moolenaar 	if (xsp->xs_state != need_state)
7487545ddfbeSMarcel Moolenaar 	    continue;
7488545ddfbeSMarcel Moolenaar 
7489545ddfbeSMarcel Moolenaar 	if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
7490545ddfbeSMarcel Moolenaar 	    continue;
7491545ddfbeSMarcel Moolenaar 
7492545ddfbeSMarcel Moolenaar 	limit = xsp;
7493545ddfbeSMarcel Moolenaar 	break;
7494545ddfbeSMarcel Moolenaar     }
7495545ddfbeSMarcel Moolenaar 
7496545ddfbeSMarcel Moolenaar     if (limit == NULL) {
7497545ddfbeSMarcel Moolenaar 	xo_failure(xop, "xo_%s can't find match for '%s'",
7498545ddfbeSMarcel Moolenaar 		   xo_state_name(new_state), name);
7499545ddfbeSMarcel Moolenaar 	return 0;
7500545ddfbeSMarcel Moolenaar     }
7501545ddfbeSMarcel Moolenaar 
7502545ddfbeSMarcel Moolenaar     rc = xo_do_close_all(xop, limit);
7503545ddfbeSMarcel Moolenaar 
7504545ddfbeSMarcel Moolenaar     return rc;
7505545ddfbeSMarcel Moolenaar }
7506545ddfbeSMarcel Moolenaar 
7507545ddfbeSMarcel Moolenaar /*
7508545ddfbeSMarcel Moolenaar  * We are in a given state and need to transition to the new state.
7509545ddfbeSMarcel Moolenaar  */
75108a6eceffSPhil Shafer static ssize_t
7511545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
7512545ddfbeSMarcel Moolenaar 	       xo_state_t new_state)
7513545ddfbeSMarcel Moolenaar {
7514545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
75158a6eceffSPhil Shafer     ssize_t rc = 0;
7516545ddfbeSMarcel Moolenaar     int old_state, on_marker;
7517545ddfbeSMarcel Moolenaar 
7518545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7519545ddfbeSMarcel Moolenaar 
7520545ddfbeSMarcel Moolenaar     xsp = &xop->xo_stack[xop->xo_depth];
7521545ddfbeSMarcel Moolenaar     old_state = xsp->xs_state;
7522545ddfbeSMarcel Moolenaar     on_marker = (old_state == XSS_MARKER);
7523545ddfbeSMarcel Moolenaar 
7524545ddfbeSMarcel Moolenaar     /* If there's a marker on top of the stack, we need to find a real state */
7525545ddfbeSMarcel Moolenaar     while (old_state == XSS_MARKER) {
7526545ddfbeSMarcel Moolenaar 	if (xsp == xop->xo_stack)
7527545ddfbeSMarcel Moolenaar 	    break;
7528545ddfbeSMarcel Moolenaar 	xsp -= 1;
7529545ddfbeSMarcel Moolenaar 	old_state = xsp->xs_state;
7530545ddfbeSMarcel Moolenaar     }
7531545ddfbeSMarcel Moolenaar 
7532545ddfbeSMarcel Moolenaar     /*
7533545ddfbeSMarcel Moolenaar      * At this point, the list of possible states are:
7534545ddfbeSMarcel Moolenaar      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7535545ddfbeSMarcel Moolenaar      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7536545ddfbeSMarcel Moolenaar      */
7537545ddfbeSMarcel Moolenaar     switch (XSS_TRANSITION(old_state, new_state)) {
7538545ddfbeSMarcel Moolenaar 
7539545ddfbeSMarcel Moolenaar     open_container:
7540545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7541545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7542545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7543545ddfbeSMarcel Moolenaar        rc = xo_do_open_container(xop, flags, name);
7544545ddfbeSMarcel Moolenaar        break;
7545545ddfbeSMarcel Moolenaar 
7546545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7547545ddfbeSMarcel Moolenaar 	if (on_marker)
7548545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7549545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7550545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7551545ddfbeSMarcel Moolenaar 	    goto open_container;
7552545ddfbeSMarcel Moolenaar 	break;
7553545ddfbeSMarcel Moolenaar 
7554545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7555545ddfbeSMarcel Moolenaar 	if (on_marker)
7556545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7557545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7558545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7559545ddfbeSMarcel Moolenaar 	    goto open_container;
7560545ddfbeSMarcel Moolenaar 	break;
7561545ddfbeSMarcel Moolenaar 
7562545ddfbeSMarcel Moolenaar     /*close_container:*/
7563545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7564545ddfbeSMarcel Moolenaar 	if (on_marker)
7565545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7566545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7567545ddfbeSMarcel Moolenaar 	break;
7568545ddfbeSMarcel Moolenaar 
7569545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7570545ddfbeSMarcel Moolenaar 	/* This is an exception for "xo --close" */
7571545ddfbeSMarcel Moolenaar 	rc = xo_do_close_container(xop, name);
7572545ddfbeSMarcel Moolenaar 	break;
7573545ddfbeSMarcel Moolenaar 
7574545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7575545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7576545ddfbeSMarcel Moolenaar 	if (on_marker)
7577545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7578545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7579545ddfbeSMarcel Moolenaar 	break;
7580545ddfbeSMarcel Moolenaar 
7581545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7582545ddfbeSMarcel Moolenaar 	if (on_marker)
7583545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7584545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7585545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7586545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7587545ddfbeSMarcel Moolenaar 	break;
7588545ddfbeSMarcel Moolenaar 
7589545ddfbeSMarcel Moolenaar     open_list:
7590545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7591545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7592545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7593545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7594545ddfbeSMarcel Moolenaar 	break;
7595545ddfbeSMarcel Moolenaar 
7596545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7597545ddfbeSMarcel Moolenaar 	if (on_marker)
7598545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7599545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7600545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7601545ddfbeSMarcel Moolenaar 	    goto open_list;
7602545ddfbeSMarcel Moolenaar 	break;
7603545ddfbeSMarcel Moolenaar 
7604545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7605545ddfbeSMarcel Moolenaar 	if (on_marker)
7606545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7607545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7608545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7609545ddfbeSMarcel Moolenaar 	    goto open_list;
7610545ddfbeSMarcel Moolenaar 	break;
7611545ddfbeSMarcel Moolenaar 
7612545ddfbeSMarcel Moolenaar     /*close_list:*/
7613545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7614545ddfbeSMarcel Moolenaar 	if (on_marker)
7615545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7616545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7617545ddfbeSMarcel Moolenaar 	break;
7618545ddfbeSMarcel Moolenaar 
7619545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7620545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7621545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7622545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7623545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7624545ddfbeSMarcel Moolenaar 	break;
7625545ddfbeSMarcel Moolenaar 
7626545ddfbeSMarcel Moolenaar     open_instance:
7627545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7628545ddfbeSMarcel Moolenaar 	rc = xo_do_open_instance(xop, flags, name);
7629545ddfbeSMarcel Moolenaar 	break;
7630545ddfbeSMarcel Moolenaar 
7631545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7632788ca347SMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7633545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7634545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7635545ddfbeSMarcel Moolenaar 	    goto open_instance;
7636545ddfbeSMarcel Moolenaar 	break;
7637545ddfbeSMarcel Moolenaar 
7638545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7639545ddfbeSMarcel Moolenaar 	if (on_marker) {
7640545ddfbeSMarcel Moolenaar 	    rc = xo_do_open_list(xop, flags, name);
7641545ddfbeSMarcel Moolenaar 	} else {
7642545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7643545ddfbeSMarcel Moolenaar 	}
7644545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7645545ddfbeSMarcel Moolenaar 	    goto open_instance;
7646545ddfbeSMarcel Moolenaar 	break;
7647545ddfbeSMarcel Moolenaar 
7648545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7649545ddfbeSMarcel Moolenaar 	if (on_marker)
7650545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7651545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7652545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7653545ddfbeSMarcel Moolenaar 	    goto open_instance;
7654545ddfbeSMarcel Moolenaar 	break;
7655545ddfbeSMarcel Moolenaar 
7656545ddfbeSMarcel Moolenaar     /*close_instance:*/
7657545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7658545ddfbeSMarcel Moolenaar 	if (on_marker)
7659545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7660545ddfbeSMarcel Moolenaar 	rc = xo_do_close_instance(xop, name);
7661545ddfbeSMarcel Moolenaar 	break;
7662545ddfbeSMarcel Moolenaar 
7663545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7664545ddfbeSMarcel Moolenaar 	/* This one makes no sense; ignore it */
7665788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_instance ignored when called from "
7666788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7667545ddfbeSMarcel Moolenaar 	break;
7668545ddfbeSMarcel Moolenaar 
7669545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7670545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7671545ddfbeSMarcel Moolenaar 	if (on_marker)
7672545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7673545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7674545ddfbeSMarcel Moolenaar 	break;
7675545ddfbeSMarcel Moolenaar 
7676545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7677545ddfbeSMarcel Moolenaar 	if (on_marker)
7678545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7679545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7680545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7681545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7682545ddfbeSMarcel Moolenaar 	break;
7683545ddfbeSMarcel Moolenaar 
7684545ddfbeSMarcel Moolenaar     open_leaf_list:
7685545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7686545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7687545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7688545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7689545ddfbeSMarcel Moolenaar 	break;
7690545ddfbeSMarcel Moolenaar 
7691545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7692545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7693545ddfbeSMarcel Moolenaar 	if (on_marker)
7694545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7695545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7696545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7697545ddfbeSMarcel Moolenaar 	    goto open_leaf_list;
7698545ddfbeSMarcel Moolenaar 	break;
7699545ddfbeSMarcel Moolenaar 
7700545ddfbeSMarcel Moolenaar     /*close_leaf_list:*/
7701545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7702545ddfbeSMarcel Moolenaar 	if (on_marker)
7703545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7704545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, name);
7705545ddfbeSMarcel Moolenaar 	break;
7706545ddfbeSMarcel Moolenaar 
7707545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7708545ddfbeSMarcel Moolenaar 	/* Makes no sense; ignore */
7709788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7710788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7711545ddfbeSMarcel Moolenaar 	break;
7712545ddfbeSMarcel Moolenaar 
7713545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7714545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7715545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7716545ddfbeSMarcel Moolenaar 	if (on_marker)
7717545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7718545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7719545ddfbeSMarcel Moolenaar 	break;
7720545ddfbeSMarcel Moolenaar 
7721545ddfbeSMarcel Moolenaar     /*emit:*/
7722545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7723545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7724545ddfbeSMarcel Moolenaar 	break;
7725545ddfbeSMarcel Moolenaar 
7726545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7727545ddfbeSMarcel Moolenaar 	if (on_marker)
7728545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7729545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7730545ddfbeSMarcel Moolenaar 	break;
7731545ddfbeSMarcel Moolenaar 
7732545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7733545ddfbeSMarcel Moolenaar 	break;
7734545ddfbeSMarcel Moolenaar 
7735545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7736545ddfbeSMarcel Moolenaar 	if (on_marker)
7737545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7738545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7739545ddfbeSMarcel Moolenaar 	break;
7740545ddfbeSMarcel Moolenaar 
7741545ddfbeSMarcel Moolenaar     /*emit_leaf_list:*/
7742545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7743545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7744545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7745545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7746545ddfbeSMarcel Moolenaar 	break;
7747545ddfbeSMarcel Moolenaar 
7748545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7749545ddfbeSMarcel Moolenaar 	break;
7750545ddfbeSMarcel Moolenaar 
7751545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7752545ddfbeSMarcel Moolenaar 	/*
7753545ddfbeSMarcel Moolenaar 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7754545ddfbeSMarcel Moolenaar 	 * API, where both lists and leaf-lists were opened as lists.  So
7755545ddfbeSMarcel Moolenaar 	 * if we find an open list that hasn't had anything written to it,
7756545ddfbeSMarcel Moolenaar 	 * we'll accept it.
7757545ddfbeSMarcel Moolenaar 	 */
7758545ddfbeSMarcel Moolenaar 	break;
7759545ddfbeSMarcel Moolenaar 
7760545ddfbeSMarcel Moolenaar     default:
7761545ddfbeSMarcel Moolenaar 	xo_failure(xop, "unknown transition: (%u -> %u)",
7762545ddfbeSMarcel Moolenaar 		   xsp->xs_state, new_state);
7763545ddfbeSMarcel Moolenaar     }
7764545ddfbeSMarcel Moolenaar 
776542ff34c3SPhil Shafer     /* Handle the flush flag */
776642ff34c3SPhil Shafer     if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
776742ff34c3SPhil Shafer 	if (xo_flush_h(xop))
776842ff34c3SPhil Shafer 	    rc = -1;
776942ff34c3SPhil Shafer 
7770545ddfbeSMarcel Moolenaar     return rc;
7771545ddfbeSMarcel Moolenaar 
7772545ddfbeSMarcel Moolenaar  marker_prevents_close:
7773545ddfbeSMarcel Moolenaar     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7774545ddfbeSMarcel Moolenaar 	       xop->xo_stack[xop->xo_depth].xs_name,
7775545ddfbeSMarcel Moolenaar 	       xo_state_name(old_state), xo_state_name(new_state));
7776545ddfbeSMarcel Moolenaar     return -1;
7777545ddfbeSMarcel Moolenaar }
7778545ddfbeSMarcel Moolenaar 
77798a6eceffSPhil Shafer xo_ssize_t
7780545ddfbeSMarcel Moolenaar xo_open_marker_h (xo_handle_t *xop, const char *name)
7781545ddfbeSMarcel Moolenaar {
7782545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7783545ddfbeSMarcel Moolenaar 
7784545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7785545ddfbeSMarcel Moolenaar 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7786545ddfbeSMarcel Moolenaar 
7787545ddfbeSMarcel Moolenaar     return 0;
7788545ddfbeSMarcel Moolenaar }
7789545ddfbeSMarcel Moolenaar 
77908a6eceffSPhil Shafer xo_ssize_t
7791545ddfbeSMarcel Moolenaar xo_open_marker (const char *name)
7792545ddfbeSMarcel Moolenaar {
7793545ddfbeSMarcel Moolenaar     return xo_open_marker_h(NULL, name);
7794545ddfbeSMarcel Moolenaar }
7795545ddfbeSMarcel Moolenaar 
77968a6eceffSPhil Shafer xo_ssize_t
7797545ddfbeSMarcel Moolenaar xo_close_marker_h (xo_handle_t *xop, const char *name)
7798545ddfbeSMarcel Moolenaar {
7799545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7800545ddfbeSMarcel Moolenaar 
7801545ddfbeSMarcel Moolenaar     return xo_do_close(xop, name, XSS_MARKER);
7802545ddfbeSMarcel Moolenaar }
7803545ddfbeSMarcel Moolenaar 
78048a6eceffSPhil Shafer xo_ssize_t
7805545ddfbeSMarcel Moolenaar xo_close_marker (const char *name)
7806545ddfbeSMarcel Moolenaar {
7807545ddfbeSMarcel Moolenaar     return xo_close_marker_h(NULL, name);
7808545ddfbeSMarcel Moolenaar }
7809545ddfbeSMarcel Moolenaar 
7810d1a0d267SMarcel Moolenaar /*
7811d1a0d267SMarcel Moolenaar  * Record custom output functions into the xo handle, allowing
7812d1a0d267SMarcel Moolenaar  * integration with a variety of output frameworks.
7813d1a0d267SMarcel Moolenaar  */
781431337658SMarcel Moolenaar void
781531337658SMarcel Moolenaar xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7816545ddfbeSMarcel Moolenaar 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
781731337658SMarcel Moolenaar {
781831337658SMarcel Moolenaar     xop = xo_default(xop);
781931337658SMarcel Moolenaar 
782031337658SMarcel Moolenaar     xop->xo_opaque = opaque;
782131337658SMarcel Moolenaar     xop->xo_write = write_func;
782231337658SMarcel Moolenaar     xop->xo_close = close_func;
7823545ddfbeSMarcel Moolenaar     xop->xo_flush = flush_func;
782431337658SMarcel Moolenaar }
782531337658SMarcel Moolenaar 
782631337658SMarcel Moolenaar void
782731337658SMarcel Moolenaar xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
782831337658SMarcel Moolenaar {
782931337658SMarcel Moolenaar     xo_realloc = realloc_func;
783031337658SMarcel Moolenaar     xo_free = free_func;
783131337658SMarcel Moolenaar }
783231337658SMarcel Moolenaar 
78338a6eceffSPhil Shafer xo_ssize_t
783431337658SMarcel Moolenaar xo_flush_h (xo_handle_t *xop)
783531337658SMarcel Moolenaar {
78368a6eceffSPhil Shafer     ssize_t rc;
783731337658SMarcel Moolenaar 
783831337658SMarcel Moolenaar     xop = xo_default(xop);
783931337658SMarcel Moolenaar 
7840788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
7841d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7842f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL, 0);
784331337658SMarcel Moolenaar     }
784431337658SMarcel Moolenaar 
7845545ddfbeSMarcel Moolenaar     rc = xo_write(xop);
7846545ddfbeSMarcel Moolenaar     if (rc >= 0 && xop->xo_flush)
7847545ddfbeSMarcel Moolenaar 	if (xop->xo_flush(xop->xo_opaque) < 0)
7848545ddfbeSMarcel Moolenaar 	    return -1;
7849545ddfbeSMarcel Moolenaar 
7850545ddfbeSMarcel Moolenaar     return rc;
785131337658SMarcel Moolenaar }
785231337658SMarcel Moolenaar 
78538a6eceffSPhil Shafer xo_ssize_t
785431337658SMarcel Moolenaar xo_flush (void)
785531337658SMarcel Moolenaar {
7856545ddfbeSMarcel Moolenaar     return xo_flush_h(NULL);
785731337658SMarcel Moolenaar }
785831337658SMarcel Moolenaar 
78598a6eceffSPhil Shafer xo_ssize_t
786031337658SMarcel Moolenaar xo_finish_h (xo_handle_t *xop)
786131337658SMarcel Moolenaar {
786231337658SMarcel Moolenaar     const char *cp = "";
786331337658SMarcel Moolenaar     xop = xo_default(xop);
786431337658SMarcel Moolenaar 
7865d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
7866545ddfbeSMarcel Moolenaar 	xo_do_close_all(xop, xop->xo_stack);
7867545ddfbeSMarcel Moolenaar 
7868788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
786931337658SMarcel Moolenaar     case XO_STYLE_JSON:
7870d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7871d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7872d1a0d267SMarcel Moolenaar 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
787331337658SMarcel Moolenaar 	    else
787431337658SMarcel Moolenaar 		cp = "{ ";
787531337658SMarcel Moolenaar 	    xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
787631337658SMarcel Moolenaar 	}
787731337658SMarcel Moolenaar 	break;
7878d1a0d267SMarcel Moolenaar 
7879d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7880f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL, 0);
7881d1a0d267SMarcel Moolenaar 	break;
788231337658SMarcel Moolenaar     }
788331337658SMarcel Moolenaar 
7884545ddfbeSMarcel Moolenaar     return xo_flush_h(xop);
788531337658SMarcel Moolenaar }
788631337658SMarcel Moolenaar 
78878a6eceffSPhil Shafer xo_ssize_t
788831337658SMarcel Moolenaar xo_finish (void)
788931337658SMarcel Moolenaar {
7890545ddfbeSMarcel Moolenaar     return xo_finish_h(NULL);
789131337658SMarcel Moolenaar }
789231337658SMarcel Moolenaar 
789331337658SMarcel Moolenaar /*
7894d1a0d267SMarcel Moolenaar  * xo_finish_atexit is suitable for atexit() calls, to force clear up
7895d1a0d267SMarcel Moolenaar  * and finalizing output.
7896d1a0d267SMarcel Moolenaar  */
7897d1a0d267SMarcel Moolenaar void
7898d1a0d267SMarcel Moolenaar xo_finish_atexit (void)
7899d1a0d267SMarcel Moolenaar {
7900d1a0d267SMarcel Moolenaar     (void) xo_finish_h(NULL);
7901d1a0d267SMarcel Moolenaar }
7902d1a0d267SMarcel Moolenaar 
7903d1a0d267SMarcel Moolenaar /*
790431337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
790531337658SMarcel Moolenaar  */
790631337658SMarcel Moolenaar void
790731337658SMarcel Moolenaar xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
790831337658SMarcel Moolenaar {
790931337658SMarcel Moolenaar     xop = xo_default(xop);
791031337658SMarcel Moolenaar 
791131337658SMarcel Moolenaar     /*
791231337658SMarcel Moolenaar      * If the format string doesn't end with a newline, we pop
791331337658SMarcel Moolenaar      * one on ourselves.
791431337658SMarcel Moolenaar      */
79158a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
791631337658SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
791731337658SMarcel Moolenaar 	char *newfmt = alloca(len + 2);
791831337658SMarcel Moolenaar 	memcpy(newfmt, fmt, len);
791931337658SMarcel Moolenaar 	newfmt[len] = '\n';
792031337658SMarcel Moolenaar 	newfmt[len] = '\0';
792131337658SMarcel Moolenaar 	fmt = newfmt;
792231337658SMarcel Moolenaar     }
792331337658SMarcel Moolenaar 
7924788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
792531337658SMarcel Moolenaar     case XO_STYLE_TEXT:
792631337658SMarcel Moolenaar 	vfprintf(stderr, fmt, vap);
792731337658SMarcel Moolenaar 	break;
792831337658SMarcel Moolenaar 
792931337658SMarcel Moolenaar     case XO_STYLE_HTML:
793031337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
793131337658SMarcel Moolenaar 
7932264104f2SPhil Shafer 	xo_buf_append_div(xop, "error", 0, NULL, 0, NULL, 0,
7933264104f2SPhil Shafer 			  fmt, strlen(fmt), NULL, 0);
793431337658SMarcel Moolenaar 
7935d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
793631337658SMarcel Moolenaar 	    xo_line_close(xop);
793731337658SMarcel Moolenaar 
793831337658SMarcel Moolenaar 	xo_write(xop);
793931337658SMarcel Moolenaar 
794031337658SMarcel Moolenaar 	va_end(xop->xo_vap);
794131337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
794231337658SMarcel Moolenaar 	break;
794331337658SMarcel Moolenaar 
794431337658SMarcel Moolenaar     case XO_STYLE_XML:
7945545ddfbeSMarcel Moolenaar     case XO_STYLE_JSON:
794631337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
794731337658SMarcel Moolenaar 
794831337658SMarcel Moolenaar 	xo_open_container_h(xop, "error");
7949264104f2SPhil Shafer 	xo_format_value(xop, "message", 7, NULL, 0,
7950264104f2SPhil Shafer 			fmt, strlen(fmt), NULL, 0, 0);
795131337658SMarcel Moolenaar 	xo_close_container_h(xop, "error");
795231337658SMarcel Moolenaar 
795331337658SMarcel Moolenaar 	va_end(xop->xo_vap);
795431337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
795531337658SMarcel Moolenaar 	break;
7956d1a0d267SMarcel Moolenaar 
7957d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7958d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7959d1a0d267SMarcel Moolenaar 	break;
796031337658SMarcel Moolenaar     }
796131337658SMarcel Moolenaar }
796231337658SMarcel Moolenaar 
796331337658SMarcel Moolenaar void
796431337658SMarcel Moolenaar xo_error_h (xo_handle_t *xop, const char *fmt, ...)
796531337658SMarcel Moolenaar {
796631337658SMarcel Moolenaar     va_list vap;
796731337658SMarcel Moolenaar 
796831337658SMarcel Moolenaar     va_start(vap, fmt);
796931337658SMarcel Moolenaar     xo_error_hv(xop, fmt, vap);
797031337658SMarcel Moolenaar     va_end(vap);
797131337658SMarcel Moolenaar }
797231337658SMarcel Moolenaar 
797331337658SMarcel Moolenaar /*
797431337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
797531337658SMarcel Moolenaar  */
797631337658SMarcel Moolenaar void
797731337658SMarcel Moolenaar xo_error (const char *fmt, ...)
797831337658SMarcel Moolenaar {
797931337658SMarcel Moolenaar     va_list vap;
798031337658SMarcel Moolenaar 
798131337658SMarcel Moolenaar     va_start(vap, fmt);
798231337658SMarcel Moolenaar     xo_error_hv(NULL, fmt, vap);
798331337658SMarcel Moolenaar     va_end(vap);
798431337658SMarcel Moolenaar }
798531337658SMarcel Moolenaar 
7986d1a0d267SMarcel Moolenaar /*
7987d1a0d267SMarcel Moolenaar  * Parse any libxo-specific options from the command line, removing them
7988d1a0d267SMarcel Moolenaar  * so the main() argument parsing won't see them.  We return the new value
7989d1a0d267SMarcel Moolenaar  * for argc or -1 for error.  If an error occurred, the program should
7990d1a0d267SMarcel Moolenaar  * exit.  A suitable error message has already been displayed.
7991d1a0d267SMarcel Moolenaar  */
799231337658SMarcel Moolenaar int
799331337658SMarcel Moolenaar xo_parse_args (int argc, char **argv)
799431337658SMarcel Moolenaar {
799531337658SMarcel Moolenaar     static char libxo_opt[] = "--libxo";
799631337658SMarcel Moolenaar     char *cp;
799731337658SMarcel Moolenaar     int i, save;
799831337658SMarcel Moolenaar 
799931337658SMarcel Moolenaar     /* Save our program name for xo_err and friends */
800031337658SMarcel Moolenaar     xo_program = argv[0];
800131337658SMarcel Moolenaar     cp = strrchr(xo_program, '/');
800231337658SMarcel Moolenaar     if (cp)
800331337658SMarcel Moolenaar 	xo_program = cp + 1;
800431337658SMarcel Moolenaar 
8005f2b7bf8aSPhil Shafer     xo_handle_t *xop = xo_default(NULL);
8006f2b7bf8aSPhil Shafer 
800731337658SMarcel Moolenaar     for (save = i = 1; i < argc; i++) {
800831337658SMarcel Moolenaar 	if (argv[i] == NULL
800931337658SMarcel Moolenaar 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
801031337658SMarcel Moolenaar 	    if (save != i)
801131337658SMarcel Moolenaar 		argv[save] = argv[i];
801231337658SMarcel Moolenaar 	    save += 1;
801331337658SMarcel Moolenaar 	    continue;
801431337658SMarcel Moolenaar 	}
801531337658SMarcel Moolenaar 
801631337658SMarcel Moolenaar 	cp = argv[i] + sizeof(libxo_opt) - 1;
8017ee5cf116SPhil Shafer 	if (*cp == '\0') {
801831337658SMarcel Moolenaar 	    cp = argv[++i];
8019ee5cf116SPhil Shafer 	    if (cp == NULL) {
802031337658SMarcel Moolenaar 		xo_warnx("missing libxo option");
802131337658SMarcel Moolenaar 		return -1;
802231337658SMarcel Moolenaar 	    }
802331337658SMarcel Moolenaar 
8024f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, cp) < 0)
802531337658SMarcel Moolenaar 		return -1;
802631337658SMarcel Moolenaar 	} else if (*cp == ':') {
8027f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, cp) < 0)
802831337658SMarcel Moolenaar 		return -1;
802931337658SMarcel Moolenaar 
803031337658SMarcel Moolenaar 	} else if (*cp == '=') {
8031f2b7bf8aSPhil Shafer 	    if (xo_set_options(xop, ++cp) < 0)
803231337658SMarcel Moolenaar 		return -1;
803331337658SMarcel Moolenaar 
803431337658SMarcel Moolenaar 	} else if (*cp == '-') {
803531337658SMarcel Moolenaar 	    cp += 1;
803631337658SMarcel Moolenaar 	    if (strcmp(cp, "check") == 0) {
803731337658SMarcel Moolenaar 		exit(XO_HAS_LIBXO);
803831337658SMarcel Moolenaar 
803931337658SMarcel Moolenaar 	    } else {
804031337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
804131337658SMarcel Moolenaar 		return -1;
804231337658SMarcel Moolenaar 	    }
804331337658SMarcel Moolenaar 	} else {
804431337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
804531337658SMarcel Moolenaar 	    return -1;
804631337658SMarcel Moolenaar 	}
804731337658SMarcel Moolenaar     }
804831337658SMarcel Moolenaar 
8049f2b7bf8aSPhil Shafer     /*
8050f2b7bf8aSPhil Shafer      * We only want to do color output on terminals, but we only want
8051f2b7bf8aSPhil Shafer      * to do this if the user has asked for color.
8052f2b7bf8aSPhil Shafer      */
8053f2b7bf8aSPhil Shafer     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
8054f2b7bf8aSPhil Shafer 	XOF_SET(xop, XOF_COLOR);
8055f2b7bf8aSPhil Shafer 
805631337658SMarcel Moolenaar     argv[save] = NULL;
805731337658SMarcel Moolenaar     return save;
805831337658SMarcel Moolenaar }
805931337658SMarcel Moolenaar 
8060d1a0d267SMarcel Moolenaar /*
8061d1a0d267SMarcel Moolenaar  * Debugging function that dumps the current stack of open libxo constructs,
8062d1a0d267SMarcel Moolenaar  * suitable for calling from the debugger.
8063d1a0d267SMarcel Moolenaar  */
8064545ddfbeSMarcel Moolenaar void
8065545ddfbeSMarcel Moolenaar xo_dump_stack (xo_handle_t *xop)
8066545ddfbeSMarcel Moolenaar {
8067545ddfbeSMarcel Moolenaar     int i;
8068545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
8069545ddfbeSMarcel Moolenaar 
8070545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
8071545ddfbeSMarcel Moolenaar 
8072545ddfbeSMarcel Moolenaar     fprintf(stderr, "Stack dump:\n");
8073545ddfbeSMarcel Moolenaar 
8074545ddfbeSMarcel Moolenaar     xsp = xop->xo_stack;
8075545ddfbeSMarcel Moolenaar     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
8076545ddfbeSMarcel Moolenaar 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
8077545ddfbeSMarcel Moolenaar 		i, xo_state_name(xsp->xs_state),
8078545ddfbeSMarcel Moolenaar 		xsp->xs_name ?: "--", xsp->xs_flags);
8079545ddfbeSMarcel Moolenaar     }
8080545ddfbeSMarcel Moolenaar }
8081545ddfbeSMarcel Moolenaar 
8082d1a0d267SMarcel Moolenaar /*
8083d1a0d267SMarcel Moolenaar  * Record the program name used for error messages
8084d1a0d267SMarcel Moolenaar  */
8085545ddfbeSMarcel Moolenaar void
8086545ddfbeSMarcel Moolenaar xo_set_program (const char *name)
8087545ddfbeSMarcel Moolenaar {
8088545ddfbeSMarcel Moolenaar     xo_program = name;
8089545ddfbeSMarcel Moolenaar }
8090545ddfbeSMarcel Moolenaar 
8091788ca347SMarcel Moolenaar void
809242ff34c3SPhil Shafer xo_set_version_h (xo_handle_t *xop, const char *version)
8093788ca347SMarcel Moolenaar {
8094788ca347SMarcel Moolenaar     xop = xo_default(xop);
8095788ca347SMarcel Moolenaar 
8096788ca347SMarcel Moolenaar     if (version == NULL || strchr(version, '"') != NULL)
8097788ca347SMarcel Moolenaar 	return;
8098788ca347SMarcel Moolenaar 
8099d1a0d267SMarcel Moolenaar     if (!xo_style_is_encoding(xop))
8100d1a0d267SMarcel Moolenaar 	return;
8101d1a0d267SMarcel Moolenaar 
8102788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
8103788ca347SMarcel Moolenaar     case XO_STYLE_XML:
8104788ca347SMarcel Moolenaar 	/* For XML, we record this as an attribute for the first tag */
81058a6eceffSPhil Shafer 	xo_attr_h(xop, "version", "%s", version);
8106788ca347SMarcel Moolenaar 	break;
8107788ca347SMarcel Moolenaar 
8108788ca347SMarcel Moolenaar     case XO_STYLE_JSON:
8109788ca347SMarcel Moolenaar 	/*
8110d1a0d267SMarcel Moolenaar 	 * For JSON, we record the version string in our handle, and emit
8111788ca347SMarcel Moolenaar 	 * it in xo_emit_top.
8112788ca347SMarcel Moolenaar 	 */
8113d1a0d267SMarcel Moolenaar 	xop->xo_version = xo_strndup(version, -1);
8114d1a0d267SMarcel Moolenaar 	break;
8115d1a0d267SMarcel Moolenaar 
8116d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
8117f2b7bf8aSPhil Shafer 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version, 0);
8118788ca347SMarcel Moolenaar 	break;
8119788ca347SMarcel Moolenaar     }
8120788ca347SMarcel Moolenaar }
8121788ca347SMarcel Moolenaar 
8122d1a0d267SMarcel Moolenaar /*
8123ee5cf116SPhil Shafer  * Set the version number for the API content being carried through
8124d1a0d267SMarcel Moolenaar  * the xo handle.
8125d1a0d267SMarcel Moolenaar  */
8126788ca347SMarcel Moolenaar void
8127788ca347SMarcel Moolenaar xo_set_version (const char *version)
8128788ca347SMarcel Moolenaar {
8129788ca347SMarcel Moolenaar     xo_set_version_h(NULL, version);
8130788ca347SMarcel Moolenaar }
8131788ca347SMarcel Moolenaar 
8132d1a0d267SMarcel Moolenaar /*
8133d1a0d267SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
8134d1a0d267SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
8135d1a0d267SMarcel Moolenaar  * XMLified content on standard output.
8136d1a0d267SMarcel Moolenaar  */
8137d1a0d267SMarcel Moolenaar void
8138d1a0d267SMarcel Moolenaar xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
8139d1a0d267SMarcel Moolenaar 		  const char *fmt, va_list vap)
814031337658SMarcel Moolenaar {
8141d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
814231337658SMarcel Moolenaar 
8143d1a0d267SMarcel Moolenaar     if (fmt == NULL)
8144d1a0d267SMarcel Moolenaar 	return;
814531337658SMarcel Moolenaar 
8146d1a0d267SMarcel Moolenaar     xo_open_marker_h(xop, "xo_emit_warn_hcv");
8147d1a0d267SMarcel Moolenaar     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
814831337658SMarcel Moolenaar 
8149d1a0d267SMarcel Moolenaar     if (xo_program)
8150d1a0d267SMarcel Moolenaar 	xo_emit("{wc:program}", xo_program);
815131337658SMarcel Moolenaar 
8152d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
8153d1a0d267SMarcel Moolenaar 	va_list ap;
8154d1a0d267SMarcel Moolenaar 	xo_handle_t temp;
815531337658SMarcel Moolenaar 
8156d1a0d267SMarcel Moolenaar 	bzero(&temp, sizeof(temp));
8157d1a0d267SMarcel Moolenaar 	temp.xo_style = XO_STYLE_TEXT;
8158d1a0d267SMarcel Moolenaar 	xo_buf_init(&temp.xo_data);
8159d1a0d267SMarcel Moolenaar 	xo_depth_check(&temp, XO_DEPTH);
816031337658SMarcel Moolenaar 
8161d1a0d267SMarcel Moolenaar 	va_copy(ap, vap);
8162d1a0d267SMarcel Moolenaar 	(void) xo_emit_hv(&temp, fmt, ap);
8163d1a0d267SMarcel Moolenaar 	va_end(ap);
816431337658SMarcel Moolenaar 
8165d1a0d267SMarcel Moolenaar 	xo_buffer_t *src = &temp.xo_data;
8166d1a0d267SMarcel Moolenaar 	xo_format_value(xop, "message", 7, src->xb_bufp,
8167264104f2SPhil Shafer 			src->xb_curp - src->xb_bufp, NULL, 0, NULL, 0, 0);
816831337658SMarcel Moolenaar 
8169d1a0d267SMarcel Moolenaar 	xo_free(temp.xo_stack);
8170d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(src);
817131337658SMarcel Moolenaar     }
817231337658SMarcel Moolenaar 
8173d1a0d267SMarcel Moolenaar     (void) xo_emit_hv(xop, fmt, vap);
817431337658SMarcel Moolenaar 
81758a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
8176d1a0d267SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
8177d1a0d267SMarcel Moolenaar 	if (code > 0) {
8178d1a0d267SMarcel Moolenaar 	    const char *msg = strerror(code);
8179d1a0d267SMarcel Moolenaar 	    if (msg)
8180d1a0d267SMarcel Moolenaar 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
8181d1a0d267SMarcel Moolenaar 	}
8182d1a0d267SMarcel Moolenaar 	xo_emit("\n");
818331337658SMarcel Moolenaar     }
818431337658SMarcel Moolenaar 
8185d1a0d267SMarcel Moolenaar     xo_close_marker_h(xop, "xo_emit_warn_hcv");
8186d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
818731337658SMarcel Moolenaar }
818831337658SMarcel Moolenaar 
8189d1a0d267SMarcel Moolenaar void
8190d1a0d267SMarcel Moolenaar xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
8191d1a0d267SMarcel Moolenaar {
8192d1a0d267SMarcel Moolenaar     va_list vap;
819331337658SMarcel Moolenaar 
8194d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8195d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
8196d1a0d267SMarcel Moolenaar     va_end(vap);
819731337658SMarcel Moolenaar }
819831337658SMarcel Moolenaar 
8199d1a0d267SMarcel Moolenaar void
8200d1a0d267SMarcel Moolenaar xo_emit_warn_c (int code, const char *fmt, ...)
8201d1a0d267SMarcel Moolenaar {
8202d1a0d267SMarcel Moolenaar     va_list vap;
820331337658SMarcel Moolenaar 
8204d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8205d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8206d1a0d267SMarcel Moolenaar     va_end(vap);
8207d1a0d267SMarcel Moolenaar }
820831337658SMarcel Moolenaar 
8209d1a0d267SMarcel Moolenaar void
8210d1a0d267SMarcel Moolenaar xo_emit_warn (const char *fmt, ...)
8211d1a0d267SMarcel Moolenaar {
8212d1a0d267SMarcel Moolenaar     int code = errno;
8213d1a0d267SMarcel Moolenaar     va_list vap;
8214d1a0d267SMarcel Moolenaar 
8215d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8216d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8217d1a0d267SMarcel Moolenaar     va_end(vap);
8218d1a0d267SMarcel Moolenaar }
8219d1a0d267SMarcel Moolenaar 
8220d1a0d267SMarcel Moolenaar void
8221d1a0d267SMarcel Moolenaar xo_emit_warnx (const char *fmt, ...)
8222d1a0d267SMarcel Moolenaar {
8223d1a0d267SMarcel Moolenaar     va_list vap;
8224d1a0d267SMarcel Moolenaar 
8225d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8226d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
8227d1a0d267SMarcel Moolenaar     va_end(vap);
8228d1a0d267SMarcel Moolenaar }
8229d1a0d267SMarcel Moolenaar 
8230d1a0d267SMarcel Moolenaar void
8231d1a0d267SMarcel Moolenaar xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
8232d1a0d267SMarcel Moolenaar {
8233d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
823431337658SMarcel Moolenaar     xo_finish();
8235d1a0d267SMarcel Moolenaar     exit(eval);
823631337658SMarcel Moolenaar }
8237d1a0d267SMarcel Moolenaar 
8238d1a0d267SMarcel Moolenaar void
8239d1a0d267SMarcel Moolenaar xo_emit_err (int eval, const char *fmt, ...)
8240d1a0d267SMarcel Moolenaar {
8241d1a0d267SMarcel Moolenaar     int code = errno;
8242d1a0d267SMarcel Moolenaar     va_list vap;
8243d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8244d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, code, fmt, vap);
8245d1a0d267SMarcel Moolenaar     va_end(vap);
8246d1a0d267SMarcel Moolenaar     exit(eval);
8247d1a0d267SMarcel Moolenaar }
8248d1a0d267SMarcel Moolenaar 
8249d1a0d267SMarcel Moolenaar void
8250d1a0d267SMarcel Moolenaar xo_emit_errx (int eval, const char *fmt, ...)
8251d1a0d267SMarcel Moolenaar {
8252d1a0d267SMarcel Moolenaar     va_list vap;
8253d1a0d267SMarcel Moolenaar 
8254d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8255d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, -1, fmt, vap);
8256d1a0d267SMarcel Moolenaar     va_end(vap);
8257d1a0d267SMarcel Moolenaar     xo_finish();
8258d1a0d267SMarcel Moolenaar     exit(eval);
8259d1a0d267SMarcel Moolenaar }
8260d1a0d267SMarcel Moolenaar 
8261d1a0d267SMarcel Moolenaar void
8262d1a0d267SMarcel Moolenaar xo_emit_errc (int eval, int code, const char *fmt, ...)
8263d1a0d267SMarcel Moolenaar {
8264d1a0d267SMarcel Moolenaar     va_list vap;
8265d1a0d267SMarcel Moolenaar 
8266d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8267d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8268d1a0d267SMarcel Moolenaar     va_end(vap);
8269d1a0d267SMarcel Moolenaar     xo_finish();
8270d1a0d267SMarcel Moolenaar     exit(eval);
8271d1a0d267SMarcel Moolenaar }
8272d1a0d267SMarcel Moolenaar 
8273d1a0d267SMarcel Moolenaar /*
8274d1a0d267SMarcel Moolenaar  * Get the opaque private pointer for an xo handle
8275d1a0d267SMarcel Moolenaar  */
8276d1a0d267SMarcel Moolenaar void *
8277d1a0d267SMarcel Moolenaar xo_get_private (xo_handle_t *xop)
8278d1a0d267SMarcel Moolenaar {
8279d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8280d1a0d267SMarcel Moolenaar     return xop->xo_private;
8281d1a0d267SMarcel Moolenaar }
8282d1a0d267SMarcel Moolenaar 
8283d1a0d267SMarcel Moolenaar /*
8284d1a0d267SMarcel Moolenaar  * Set the opaque private pointer for an xo handle.
8285d1a0d267SMarcel Moolenaar  */
8286d1a0d267SMarcel Moolenaar void
8287d1a0d267SMarcel Moolenaar xo_set_private (xo_handle_t *xop, void *opaque)
8288d1a0d267SMarcel Moolenaar {
8289d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8290d1a0d267SMarcel Moolenaar     xop->xo_private = opaque;
8291d1a0d267SMarcel Moolenaar }
8292d1a0d267SMarcel Moolenaar 
8293d1a0d267SMarcel Moolenaar /*
8294d1a0d267SMarcel Moolenaar  * Get the encoder function
8295d1a0d267SMarcel Moolenaar  */
8296d1a0d267SMarcel Moolenaar xo_encoder_func_t
8297d1a0d267SMarcel Moolenaar xo_get_encoder (xo_handle_t *xop)
8298d1a0d267SMarcel Moolenaar {
8299d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8300d1a0d267SMarcel Moolenaar     return xop->xo_encoder;
8301d1a0d267SMarcel Moolenaar }
8302d1a0d267SMarcel Moolenaar 
8303d1a0d267SMarcel Moolenaar /*
8304d1a0d267SMarcel Moolenaar  * Record an encoder callback function in an xo handle.
8305d1a0d267SMarcel Moolenaar  */
8306d1a0d267SMarcel Moolenaar void
8307d1a0d267SMarcel Moolenaar xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8308d1a0d267SMarcel Moolenaar {
8309d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8310d1a0d267SMarcel Moolenaar 
8311d1a0d267SMarcel Moolenaar     xop->xo_style = XO_STYLE_ENCODER;
8312d1a0d267SMarcel Moolenaar     xop->xo_encoder = encoder;
8313d1a0d267SMarcel Moolenaar }
8314