xref: /freebsd/contrib/libxo/libxo/libxo.c (revision 8a6eceff)
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 
98d1a0d267SMarcel Moolenaar /*
99d1a0d267SMarcel Moolenaar  * Three styles of specifying thread-local variables are supported.
100ee5cf116SPhil Shafer  * configure.ac has the brains to run each possibility through the
101d1a0d267SMarcel Moolenaar  * compiler and see what works; we are left to define the THREAD_LOCAL
102d1a0d267SMarcel Moolenaar  * macro to the right value.  Most toolchains (clang, gcc) use
103d1a0d267SMarcel Moolenaar  * "before", but some (borland) use "after" and I've heard of some
104d1a0d267SMarcel Moolenaar  * (ms) that use __declspec.  Any others out there?
105d1a0d267SMarcel Moolenaar  */
106d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_before 1
107d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_after 2
108d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_declspec 3
109d1a0d267SMarcel Moolenaar 
110d1a0d267SMarcel Moolenaar #ifndef HAVE_THREAD_LOCAL
111d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x
112d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
113d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __thread _x
114d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
115d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x __thread
116d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
117d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __declspec(_x)
118d1a0d267SMarcel Moolenaar #else
119d1a0d267SMarcel Moolenaar #error unknown thread-local setting
120d1a0d267SMarcel Moolenaar #endif /* HAVE_THREADS_H */
121d1a0d267SMarcel Moolenaar 
12231337658SMarcel Moolenaar const char xo_version[] = LIBXO_VERSION;
12331337658SMarcel Moolenaar const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
12442ff34c3SPhil Shafer static const char xo_default_format[] = "%s";
12531337658SMarcel Moolenaar 
12631337658SMarcel Moolenaar #ifndef UNUSED
12731337658SMarcel Moolenaar #define UNUSED __attribute__ ((__unused__))
12831337658SMarcel Moolenaar #endif /* UNUSED */
12931337658SMarcel Moolenaar 
13031337658SMarcel Moolenaar #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
131d1a0d267SMarcel Moolenaar #define XO_DEPTH	128	 /* Default stack depth */
13231337658SMarcel Moolenaar #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
13331337658SMarcel Moolenaar 
13431337658SMarcel Moolenaar #define XO_FAILURE_NAME	"failure"
13531337658SMarcel Moolenaar 
13631337658SMarcel Moolenaar /* Flags for the stack frame */
13731337658SMarcel Moolenaar typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
13831337658SMarcel Moolenaar #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
13931337658SMarcel Moolenaar #define XSF_LIST	(1<<1)	/* Frame is a list */
14031337658SMarcel Moolenaar #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
14131337658SMarcel Moolenaar #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
14231337658SMarcel Moolenaar 
143545ddfbeSMarcel Moolenaar #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
144545ddfbeSMarcel Moolenaar #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
145545ddfbeSMarcel Moolenaar #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
146545ddfbeSMarcel Moolenaar #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
147545ddfbeSMarcel Moolenaar 
148545ddfbeSMarcel Moolenaar /* These are the flags we propagate between markers and their parents */
149545ddfbeSMarcel Moolenaar #define XSF_MARKER_FLAGS \
150545ddfbeSMarcel Moolenaar  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
151545ddfbeSMarcel Moolenaar 
152545ddfbeSMarcel Moolenaar /*
153d1a0d267SMarcel Moolenaar  * A word about states: We use a finite state machine (FMS) approach
154d1a0d267SMarcel Moolenaar  * to help remove fragility from the caller's code.  Instead of
155d1a0d267SMarcel Moolenaar  * requiring a specific order of calls, we'll allow the caller more
156545ddfbeSMarcel Moolenaar  * flexibility and make the library responsible for recovering from
157d1a0d267SMarcel Moolenaar  * missed steps.  The goal is that the library should not be capable
158d1a0d267SMarcel Moolenaar  * of emitting invalid xml or json, but the developer shouldn't need
159545ddfbeSMarcel Moolenaar  * to know or understand all the details about these encodings.
160545ddfbeSMarcel Moolenaar  *
161d1a0d267SMarcel Moolenaar  * You can think of states as either states or events, since they
162545ddfbeSMarcel Moolenaar  * function rather like both.  None of the XO_CLOSE_* events will
163d1a0d267SMarcel Moolenaar  * persist as states, since the matching stack frame will be popped.
164545ddfbeSMarcel Moolenaar  * Same is true of XSS_EMIT, which is an event that asks us to
165545ddfbeSMarcel Moolenaar  * prep for emitting output fields.
166545ddfbeSMarcel Moolenaar  */
167545ddfbeSMarcel Moolenaar 
168545ddfbeSMarcel Moolenaar /* Stack frame states */
169545ddfbeSMarcel Moolenaar typedef unsigned xo_state_t;
170545ddfbeSMarcel Moolenaar #define XSS_INIT		0      	/* Initial stack state */
171545ddfbeSMarcel Moolenaar #define XSS_OPEN_CONTAINER	1
172545ddfbeSMarcel Moolenaar #define XSS_CLOSE_CONTAINER	2
173545ddfbeSMarcel Moolenaar #define XSS_OPEN_LIST		3
174545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LIST		4
175545ddfbeSMarcel Moolenaar #define XSS_OPEN_INSTANCE	5
176545ddfbeSMarcel Moolenaar #define XSS_CLOSE_INSTANCE	6
177545ddfbeSMarcel Moolenaar #define XSS_OPEN_LEAF_LIST	7
178545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LEAF_LIST	8
179545ddfbeSMarcel Moolenaar #define XSS_DISCARDING		9	/* Discarding data until recovered */
180545ddfbeSMarcel Moolenaar #define XSS_MARKER		10	/* xo_open_marker's marker */
181545ddfbeSMarcel Moolenaar #define XSS_EMIT		11	/* xo_emit has a leaf field */
182545ddfbeSMarcel Moolenaar #define XSS_EMIT_LEAF_LIST	12	/* xo_emit has a leaf-list ({l:}) */
183545ddfbeSMarcel Moolenaar #define XSS_FINISH		13	/* xo_finish was called */
184545ddfbeSMarcel Moolenaar 
185545ddfbeSMarcel Moolenaar #define XSS_MAX			13
186545ddfbeSMarcel Moolenaar 
187545ddfbeSMarcel Moolenaar #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
188545ddfbeSMarcel Moolenaar 
18931337658SMarcel Moolenaar /*
19031337658SMarcel Moolenaar  * xo_stack_t: As we open and close containers and levels, we
19131337658SMarcel Moolenaar  * create a stack of frames to track them.  This is needed for
19231337658SMarcel Moolenaar  * XOF_WARN and XOF_XPATH.
19331337658SMarcel Moolenaar  */
19431337658SMarcel Moolenaar typedef struct xo_stack_s {
19531337658SMarcel Moolenaar     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
196545ddfbeSMarcel Moolenaar     xo_state_t xs_state;	/* State for this stack frame */
19731337658SMarcel Moolenaar     char *xs_name;		/* Name (for XPath value) */
19831337658SMarcel Moolenaar     char *xs_keys;		/* XPath predicate for any key fields */
19931337658SMarcel Moolenaar } xo_stack_t;
20031337658SMarcel Moolenaar 
201d1a0d267SMarcel Moolenaar /*
202d1a0d267SMarcel Moolenaar  * libxo supports colors and effects, for those who like them.
203d1a0d267SMarcel Moolenaar  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
204d1a0d267SMarcel Moolenaar  * ("effects") are bits since we need to maintain state.
205d1a0d267SMarcel Moolenaar  */
206788ca347SMarcel Moolenaar #define XO_COL_DEFAULT		0
207788ca347SMarcel Moolenaar #define XO_COL_BLACK		1
208788ca347SMarcel Moolenaar #define XO_COL_RED		2
209788ca347SMarcel Moolenaar #define XO_COL_GREEN		3
210788ca347SMarcel Moolenaar #define XO_COL_YELLOW		4
211788ca347SMarcel Moolenaar #define XO_COL_BLUE		5
212788ca347SMarcel Moolenaar #define XO_COL_MAGENTA		6
213788ca347SMarcel Moolenaar #define XO_COL_CYAN		7
214788ca347SMarcel Moolenaar #define XO_COL_WHITE		8
215788ca347SMarcel Moolenaar 
216788ca347SMarcel Moolenaar #define XO_NUM_COLORS		9
217788ca347SMarcel Moolenaar 
218788ca347SMarcel Moolenaar /*
219788ca347SMarcel Moolenaar  * Yes, there's no blink.  We're civilized.  We like users.  Blink
220788ca347SMarcel Moolenaar  * isn't something one does to someone you like.  Friends don't let
221788ca347SMarcel Moolenaar  * friends use blink.  On friends.  You know what I mean.  Blink is
222788ca347SMarcel Moolenaar  * like, well, it's like bursting into show tunes at a funeral.  It's
223788ca347SMarcel Moolenaar  * just not done.  Not something anyone wants.  And on those rare
224d1a0d267SMarcel Moolenaar  * instances where it might actually be appropriate, it's still wrong,
225d1a0d267SMarcel Moolenaar  * since it's likely done by the wrong person for the wrong reason.
226d1a0d267SMarcel Moolenaar  * Just like blink.  And if I implemented blink, I'd be like a funeral
227788ca347SMarcel Moolenaar  * director who adds "Would you like us to burst into show tunes?" on
228d1a0d267SMarcel Moolenaar  * the list of questions asked while making funeral arrangements.
229788ca347SMarcel Moolenaar  * It's formalizing wrongness in the wrong way.  And we're just too
230788ca347SMarcel Moolenaar  * civilized to do that.  Hhhmph!
231788ca347SMarcel Moolenaar  */
232788ca347SMarcel Moolenaar #define XO_EFF_RESET		(1<<0)
233788ca347SMarcel Moolenaar #define XO_EFF_NORMAL		(1<<1)
234788ca347SMarcel Moolenaar #define XO_EFF_BOLD		(1<<2)
235788ca347SMarcel Moolenaar #define XO_EFF_UNDERLINE	(1<<3)
236788ca347SMarcel Moolenaar #define XO_EFF_INVERSE		(1<<4)
237788ca347SMarcel Moolenaar 
238d1a0d267SMarcel Moolenaar #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
239788ca347SMarcel Moolenaar 
240788ca347SMarcel Moolenaar typedef uint8_t xo_effect_t;
241788ca347SMarcel Moolenaar typedef uint8_t xo_color_t;
242788ca347SMarcel Moolenaar typedef struct xo_colors_s {
243788ca347SMarcel Moolenaar     xo_effect_t xoc_effects;	/* Current effect set */
244788ca347SMarcel Moolenaar     xo_color_t xoc_col_fg;	/* Foreground color */
245788ca347SMarcel Moolenaar     xo_color_t xoc_col_bg;	/* Background color */
246788ca347SMarcel Moolenaar } xo_colors_t;
247788ca347SMarcel Moolenaar 
24831337658SMarcel Moolenaar /*
24931337658SMarcel Moolenaar  * xo_handle_t: this is the principle data structure for libxo.
250d1a0d267SMarcel Moolenaar  * It's used as a store for state, options, content, and all manor
251d1a0d267SMarcel Moolenaar  * of other information.
25231337658SMarcel Moolenaar  */
25331337658SMarcel Moolenaar struct xo_handle_s {
254d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
255d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
256d1a0d267SMarcel Moolenaar     xo_style_t xo_style;	/* XO_STYLE_* value */
25731337658SMarcel Moolenaar     unsigned short xo_indent;	/* Indent level (if pretty) */
25831337658SMarcel Moolenaar     unsigned short xo_indent_by; /* Indent amount (tab stop) */
25931337658SMarcel Moolenaar     xo_write_func_t xo_write;	/* Write callback */
260a0f704ffSMarcel Moolenaar     xo_close_func_t xo_close;	/* Close callback */
261545ddfbeSMarcel Moolenaar     xo_flush_func_t xo_flush;	/* Flush callback */
26231337658SMarcel Moolenaar     xo_formatter_t xo_formatter; /* Custom formating function */
26331337658SMarcel Moolenaar     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
26431337658SMarcel Moolenaar     void *xo_opaque;		/* Opaque data for write function */
26531337658SMarcel Moolenaar     xo_buffer_t xo_data;	/* Output data */
26631337658SMarcel Moolenaar     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
26731337658SMarcel Moolenaar     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
26831337658SMarcel Moolenaar     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
26931337658SMarcel Moolenaar     xo_stack_t *xo_stack;	/* Stack pointer */
27031337658SMarcel Moolenaar     int xo_depth;		/* Depth of stack */
27131337658SMarcel Moolenaar     int xo_stack_size;		/* Size of the stack */
27231337658SMarcel Moolenaar     xo_info_t *xo_info;		/* Info fields for all elements */
27331337658SMarcel Moolenaar     int xo_info_count;		/* Number of info entries */
27431337658SMarcel Moolenaar     va_list xo_vap;		/* Variable arguments (stdargs) */
27531337658SMarcel Moolenaar     char *xo_leading_xpath;	/* A leading XPath expression */
27631337658SMarcel Moolenaar     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
2778a6eceffSPhil Shafer     ssize_t xo_anchor_offset;	/* Start of anchored text */
2788a6eceffSPhil Shafer     ssize_t xo_anchor_columns;	/* Number of columns since the start anchor */
2798a6eceffSPhil Shafer     ssize_t xo_anchor_min_width; /* Desired width of anchored text */
2808a6eceffSPhil Shafer     ssize_t xo_units_offset;	/* Start of units insertion point */
2818a6eceffSPhil Shafer     ssize_t xo_columns;	/* Columns emitted during this xo_emit call */
282788ca347SMarcel Moolenaar     uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
283788ca347SMarcel Moolenaar     uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
284788ca347SMarcel Moolenaar     xo_colors_t xo_colors;	/* Current color and effect values */
285788ca347SMarcel Moolenaar     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
286788ca347SMarcel Moolenaar     char *xo_version;		/* Version string */
287d1a0d267SMarcel Moolenaar     int xo_errno;		/* Saved errno for "%m" */
288d1a0d267SMarcel Moolenaar     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
289d1a0d267SMarcel Moolenaar     xo_encoder_func_t xo_encoder; /* Encoding function */
290d1a0d267SMarcel Moolenaar     void *xo_private;		/* Private data for external encoders */
29131337658SMarcel Moolenaar };
29231337658SMarcel Moolenaar 
293d1a0d267SMarcel Moolenaar /* Flag operations */
294d1a0d267SMarcel Moolenaar #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
295d1a0d267SMarcel Moolenaar #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
296d1a0d267SMarcel Moolenaar #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
297d1a0d267SMarcel Moolenaar 
298d1a0d267SMarcel Moolenaar #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
299d1a0d267SMarcel Moolenaar #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
300d1a0d267SMarcel Moolenaar #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
301d1a0d267SMarcel Moolenaar 
302d1a0d267SMarcel Moolenaar #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
303d1a0d267SMarcel Moolenaar #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
304d1a0d267SMarcel Moolenaar #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
305d1a0d267SMarcel Moolenaar 
306d1a0d267SMarcel Moolenaar /* Internal flags */
307d1a0d267SMarcel Moolenaar #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
308d1a0d267SMarcel Moolenaar #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
309d1a0d267SMarcel Moolenaar #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
310d1a0d267SMarcel Moolenaar #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
311d1a0d267SMarcel Moolenaar 
312d1a0d267SMarcel Moolenaar #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
313d1a0d267SMarcel Moolenaar #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
314d1a0d267SMarcel Moolenaar 
31531337658SMarcel Moolenaar /* Flags for formatting functions */
31631337658SMarcel Moolenaar typedef unsigned long xo_xff_flags_t;
31731337658SMarcel Moolenaar #define XFF_COLON	(1<<0)	/* Append a ":" */
31831337658SMarcel Moolenaar #define XFF_COMMA	(1<<1)	/* Append a "," iff there's more output */
31931337658SMarcel Moolenaar #define XFF_WS		(1<<2)	/* Append a blank */
320d1a0d267SMarcel Moolenaar #define XFF_ENCODE_ONLY	(1<<3)	/* Only emit for encoding styles (XML, JSON) */
32131337658SMarcel Moolenaar 
32231337658SMarcel Moolenaar #define XFF_QUOTE	(1<<4)	/* Force quotes */
32331337658SMarcel Moolenaar #define XFF_NOQUOTE	(1<<5)	/* Force no quotes */
324d1a0d267SMarcel Moolenaar #define XFF_DISPLAY_ONLY (1<<6)	/* Only emit for display styles (text, html) */
32531337658SMarcel Moolenaar #define XFF_KEY		(1<<7)	/* Field is a key (for XPath) */
32631337658SMarcel Moolenaar 
32731337658SMarcel Moolenaar #define XFF_XML		(1<<8)	/* Force XML encoding style (for XPath) */
32831337658SMarcel Moolenaar #define XFF_ATTR	(1<<9)	/* Escape value using attribute rules (XML) */
32931337658SMarcel Moolenaar #define XFF_BLANK_LINE	(1<<10)	/* Emit a blank line */
33031337658SMarcel Moolenaar #define XFF_NO_OUTPUT	(1<<11)	/* Do not make any output */
33131337658SMarcel Moolenaar 
33231337658SMarcel Moolenaar #define XFF_TRIM_WS	(1<<12)	/* Trim whitespace off encoded values */
33331337658SMarcel Moolenaar #define XFF_LEAF_LIST	(1<<13)	/* A leaf-list (list of values) */
33431337658SMarcel Moolenaar #define XFF_UNESCAPE	(1<<14)	/* Need to printf-style unescape the value */
335d1a0d267SMarcel Moolenaar #define XFF_HUMANIZE	(1<<15)	/* Humanize the value (for display styles) */
336d1a0d267SMarcel Moolenaar 
337d1a0d267SMarcel Moolenaar #define XFF_HN_SPACE	(1<<16)	/* Humanize: put space before suffix */
338d1a0d267SMarcel Moolenaar #define XFF_HN_DECIMAL	(1<<17)	/* Humanize: add one decimal place if <10 */
339d1a0d267SMarcel Moolenaar #define XFF_HN_1000	(1<<18)	/* Humanize: use 1000, not 1024 */
340d1a0d267SMarcel Moolenaar #define XFF_GT_FIELD	(1<<19) /* Call gettext() on a field */
341d1a0d267SMarcel Moolenaar 
342d1a0d267SMarcel Moolenaar #define XFF_GT_PLURAL	(1<<20)	/* Call dngettext to find plural form */
34342ff34c3SPhil Shafer #define XFF_ARGUMENT	(1<<21)	/* Content provided via argument */
344d1a0d267SMarcel Moolenaar 
345d1a0d267SMarcel Moolenaar /* Flags to turn off when we don't want i18n processing */
346d1a0d267SMarcel Moolenaar #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
34731337658SMarcel Moolenaar 
34831337658SMarcel Moolenaar /*
34931337658SMarcel Moolenaar  * Normal printf has width and precision, which for strings operate as
35031337658SMarcel Moolenaar  * min and max number of columns.  But this depends on the idea that
35131337658SMarcel Moolenaar  * one byte means one column, which UTF-8 and multi-byte characters
35231337658SMarcel Moolenaar  * pitches on its ear.  It may take 40 bytes of data to populate 14
35331337658SMarcel Moolenaar  * columns, but we can't go off looking at 40 bytes of data without the
35431337658SMarcel Moolenaar  * caller's permission for fear/knowledge that we'll generate core files.
35531337658SMarcel Moolenaar  *
35631337658SMarcel Moolenaar  * So we make three values, distinguishing between "max column" and
35731337658SMarcel Moolenaar  * "number of bytes that we will inspect inspect safely" We call the
35831337658SMarcel Moolenaar  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
35931337658SMarcel Moolenaar  *
36031337658SMarcel Moolenaar  * Under the "first do no harm" theory, we default "max" to "size".
36131337658SMarcel Moolenaar  * This is a reasonable assumption for folks that don't grok the
36231337658SMarcel Moolenaar  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
36331337658SMarcel Moolenaar  * be evil.
36431337658SMarcel Moolenaar  *
36531337658SMarcel Moolenaar  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
36631337658SMarcel Moolenaar  * columns of output, but will never look at more than 14 bytes of the
36731337658SMarcel Moolenaar  * input buffer.  This is mostly compatible with printf and caller's
36831337658SMarcel Moolenaar  * expectations.
36931337658SMarcel Moolenaar  *
37031337658SMarcel Moolenaar  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
37131337658SMarcel Moolenaar  * many bytes (or until a NUL is seen) are needed to fill 14 columns
37231337658SMarcel Moolenaar  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
37331337658SMarcel Moolenaar  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
37431337658SMarcel Moolenaar  * of output.
37531337658SMarcel Moolenaar  *
37631337658SMarcel Moolenaar  * It's fairly amazing how a good idea (handle all languages of the
37731337658SMarcel Moolenaar  * world) blows such a big hole in the bottom of the fairly weak boat
37831337658SMarcel Moolenaar  * that is C string handling.  The simplicity and completenesss are
37931337658SMarcel Moolenaar  * sunk in ways we haven't even begun to understand.
38031337658SMarcel Moolenaar  */
38131337658SMarcel Moolenaar #define XF_WIDTH_MIN	0	/* Minimal width */
38231337658SMarcel Moolenaar #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
38331337658SMarcel Moolenaar #define XF_WIDTH_MAX	2	/* Maximum width */
38431337658SMarcel Moolenaar #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
38531337658SMarcel Moolenaar 
38631337658SMarcel Moolenaar /* Input and output string encodings */
38731337658SMarcel Moolenaar #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
38831337658SMarcel Moolenaar #define XF_ENC_UTF8	2	/* UTF-8 */
38931337658SMarcel Moolenaar #define XF_ENC_LOCALE	3	/* Current locale */
39031337658SMarcel Moolenaar 
39131337658SMarcel Moolenaar /*
39231337658SMarcel Moolenaar  * A place to parse printf-style format flags for each field
39331337658SMarcel Moolenaar  */
39431337658SMarcel Moolenaar typedef struct xo_format_s {
39531337658SMarcel Moolenaar     unsigned char xf_fc;	/* Format character */
39631337658SMarcel Moolenaar     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
39731337658SMarcel Moolenaar     unsigned char xf_skip;	/* Skip this field */
39831337658SMarcel Moolenaar     unsigned char xf_lflag;	/* 'l' (long) */
39931337658SMarcel Moolenaar     unsigned char xf_hflag;;	/* 'h' (half) */
40031337658SMarcel Moolenaar     unsigned char xf_jflag;	/* 'j' (intmax_t) */
40131337658SMarcel Moolenaar     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
40231337658SMarcel Moolenaar     unsigned char xf_zflag;	/* 'z' (size_t) */
40331337658SMarcel Moolenaar     unsigned char xf_qflag;	/* 'q' (quad_t) */
40431337658SMarcel Moolenaar     unsigned char xf_seen_minus; /* Seen a minus */
40531337658SMarcel Moolenaar     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
40631337658SMarcel Moolenaar     unsigned xf_dots;		/* Seen one or more '.'s */
40731337658SMarcel Moolenaar     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
40831337658SMarcel Moolenaar     unsigned xf_stars;		/* Seen one or more '*'s */
40931337658SMarcel Moolenaar     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
41031337658SMarcel Moolenaar } xo_format_t;
41131337658SMarcel Moolenaar 
41231337658SMarcel Moolenaar /*
413d1a0d267SMarcel Moolenaar  * This structure represents the parsed field information, suitable for
414d1a0d267SMarcel Moolenaar  * processing by xo_do_emit and anything else that needs to parse fields.
415d1a0d267SMarcel Moolenaar  * Note that all pointers point to the main format string.
416d1a0d267SMarcel Moolenaar  *
417d1a0d267SMarcel Moolenaar  * XXX This is a first step toward compilable or cachable format
418d1a0d267SMarcel Moolenaar  * strings.  We can also cache the results of dgettext when no format
419d1a0d267SMarcel Moolenaar  * is used, assuming the 'p' modifier has _not_ been set.
42031337658SMarcel Moolenaar  */
421d1a0d267SMarcel Moolenaar typedef struct xo_field_info_s {
422d1a0d267SMarcel Moolenaar     xo_xff_flags_t xfi_flags;	/* Flags for this field */
423d1a0d267SMarcel Moolenaar     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
424d1a0d267SMarcel Moolenaar     const char *xfi_start;   /* Start of field in the format string */
425d1a0d267SMarcel Moolenaar     const char *xfi_content;	/* Field's content */
426d1a0d267SMarcel Moolenaar     const char *xfi_format;	/* Field's Format */
427d1a0d267SMarcel Moolenaar     const char *xfi_encoding;	/* Field's encoding format */
428d1a0d267SMarcel Moolenaar     const char *xfi_next;	/* Next character in format string */
4298a6eceffSPhil Shafer     ssize_t xfi_len;		/* Length of field */
4308a6eceffSPhil Shafer     ssize_t xfi_clen;		/* Content length */
4318a6eceffSPhil Shafer     ssize_t xfi_flen;		/* Format length */
4328a6eceffSPhil Shafer     ssize_t xfi_elen;		/* Encoding length */
433d1a0d267SMarcel Moolenaar     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
434d1a0d267SMarcel Moolenaar     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
435d1a0d267SMarcel Moolenaar } xo_field_info_t;
436d1a0d267SMarcel Moolenaar 
437d1a0d267SMarcel Moolenaar /*
438d1a0d267SMarcel Moolenaar  * We keep a 'default' handle to allow callers to avoid having to
439d1a0d267SMarcel Moolenaar  * allocate one.  Passing NULL to any of our functions will use
440d1a0d267SMarcel Moolenaar  * this default handle.  Most functions have a variant that doesn't
441d1a0d267SMarcel Moolenaar  * require a handle at all, since most output is to stdout, which
442d1a0d267SMarcel Moolenaar  * the default handle handles handily.
443d1a0d267SMarcel Moolenaar  */
444d1a0d267SMarcel Moolenaar static THREAD_LOCAL(xo_handle_t) xo_default_handle;
445d1a0d267SMarcel Moolenaar static THREAD_LOCAL(int) xo_default_inited;
44631337658SMarcel Moolenaar static int xo_locale_inited;
447545ddfbeSMarcel Moolenaar static const char *xo_program;
44831337658SMarcel Moolenaar 
44931337658SMarcel Moolenaar /*
45031337658SMarcel Moolenaar  * To allow libxo to be used in diverse environment, we allow the
45131337658SMarcel Moolenaar  * caller to give callbacks for memory allocation.
45231337658SMarcel Moolenaar  */
453d1a0d267SMarcel Moolenaar xo_realloc_func_t xo_realloc = realloc;
454d1a0d267SMarcel Moolenaar xo_free_func_t xo_free = free;
45531337658SMarcel Moolenaar 
45631337658SMarcel Moolenaar /* Forward declarations */
45731337658SMarcel Moolenaar static void
45831337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...);
45931337658SMarcel Moolenaar 
4608a6eceffSPhil Shafer static ssize_t
461545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
462545ddfbeSMarcel Moolenaar 	       xo_state_t new_state);
463545ddfbeSMarcel Moolenaar 
46431337658SMarcel Moolenaar static void
46531337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
4668a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
4678a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
4688a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen);
46931337658SMarcel Moolenaar 
47031337658SMarcel Moolenaar static void
47131337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop);
47231337658SMarcel Moolenaar 
47331337658SMarcel Moolenaar /*
474788ca347SMarcel Moolenaar  * xo_style is used to retrieve the current style.  When we're built
475788ca347SMarcel Moolenaar  * for "text only" mode, we use this function to drive the removal
476788ca347SMarcel Moolenaar  * of most of the code in libxo.  We return a constant and the compiler
477788ca347SMarcel Moolenaar  * happily removes the non-text code that is not longer executed.  This
478788ca347SMarcel Moolenaar  * trims our code nicely without needing to trampel perfectly readable
479788ca347SMarcel Moolenaar  * code with ifdefs.
480788ca347SMarcel Moolenaar  */
481d1a0d267SMarcel Moolenaar static inline xo_style_t
482788ca347SMarcel Moolenaar xo_style (xo_handle_t *xop UNUSED)
483788ca347SMarcel Moolenaar {
484788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
485788ca347SMarcel Moolenaar     return XO_STYLE_TEXT;
486788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
487788ca347SMarcel Moolenaar     return xop->xo_style;
488788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
489788ca347SMarcel Moolenaar }
490788ca347SMarcel Moolenaar 
491788ca347SMarcel Moolenaar /*
49231337658SMarcel Moolenaar  * Callback to write data to a FILE pointer
49331337658SMarcel Moolenaar  */
4948a6eceffSPhil Shafer static xo_ssize_t
49531337658SMarcel Moolenaar xo_write_to_file (void *opaque, const char *data)
49631337658SMarcel Moolenaar {
49731337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
498545ddfbeSMarcel Moolenaar 
49931337658SMarcel Moolenaar     return fprintf(fp, "%s", data);
50031337658SMarcel Moolenaar }
50131337658SMarcel Moolenaar 
50231337658SMarcel Moolenaar /*
50331337658SMarcel Moolenaar  * Callback to close a file
50431337658SMarcel Moolenaar  */
50531337658SMarcel Moolenaar static void
50631337658SMarcel Moolenaar xo_close_file (void *opaque)
50731337658SMarcel Moolenaar {
50831337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
509545ddfbeSMarcel Moolenaar 
51031337658SMarcel Moolenaar     fclose(fp);
51131337658SMarcel Moolenaar }
51231337658SMarcel Moolenaar 
51331337658SMarcel Moolenaar /*
514545ddfbeSMarcel Moolenaar  * Callback to flush a FILE pointer
515545ddfbeSMarcel Moolenaar  */
516545ddfbeSMarcel Moolenaar static int
517545ddfbeSMarcel Moolenaar xo_flush_file (void *opaque)
518545ddfbeSMarcel Moolenaar {
519545ddfbeSMarcel Moolenaar     FILE *fp = (FILE *) opaque;
520545ddfbeSMarcel Moolenaar 
521545ddfbeSMarcel Moolenaar     return fflush(fp);
522545ddfbeSMarcel Moolenaar }
523545ddfbeSMarcel Moolenaar 
524545ddfbeSMarcel Moolenaar /*
525d1a0d267SMarcel Moolenaar  * Use a rotating stock of buffers to make a printable string
52631337658SMarcel Moolenaar  */
527d1a0d267SMarcel Moolenaar #define XO_NUMBUFS 8
528d1a0d267SMarcel Moolenaar #define XO_SMBUFSZ 128
529d1a0d267SMarcel Moolenaar 
530d1a0d267SMarcel Moolenaar static const char *
531d1a0d267SMarcel Moolenaar xo_printable (const char *str)
53231337658SMarcel Moolenaar {
533d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
534d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(int) bufnum = 0;
535d1a0d267SMarcel Moolenaar 
536d1a0d267SMarcel Moolenaar     if (str == NULL)
537d1a0d267SMarcel Moolenaar 	return "";
538d1a0d267SMarcel Moolenaar 
539d1a0d267SMarcel Moolenaar     if (++bufnum == XO_NUMBUFS)
540d1a0d267SMarcel Moolenaar 	bufnum = 0;
541d1a0d267SMarcel Moolenaar 
542d1a0d267SMarcel Moolenaar     char *res = bufset[bufnum], *cp, *ep;
543d1a0d267SMarcel Moolenaar 
544d1a0d267SMarcel Moolenaar     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
545d1a0d267SMarcel Moolenaar 	if (*str == '\n') {
546d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
547d1a0d267SMarcel Moolenaar 	    *cp = 'n';
548d1a0d267SMarcel Moolenaar 	} else if (*str == '\r') {
549d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
550d1a0d267SMarcel Moolenaar 	    *cp = 'r';
551d1a0d267SMarcel Moolenaar 	} else if (*str == '\"') {
552d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
553d1a0d267SMarcel Moolenaar 	    *cp = '"';
554d1a0d267SMarcel Moolenaar 	} else
555d1a0d267SMarcel Moolenaar 	    *cp = *str;
55631337658SMarcel Moolenaar     }
55731337658SMarcel Moolenaar 
558d1a0d267SMarcel Moolenaar     *cp = '\0';
559d1a0d267SMarcel Moolenaar     return res;
56031337658SMarcel Moolenaar }
56131337658SMarcel Moolenaar 
56231337658SMarcel Moolenaar static int
56331337658SMarcel Moolenaar xo_depth_check (xo_handle_t *xop, int depth)
56431337658SMarcel Moolenaar {
56531337658SMarcel Moolenaar     xo_stack_t *xsp;
56631337658SMarcel Moolenaar 
56731337658SMarcel Moolenaar     if (depth >= xop->xo_stack_size) {
568d1a0d267SMarcel Moolenaar 	depth += XO_DEPTH;	/* Extra room */
569d1a0d267SMarcel Moolenaar 
57031337658SMarcel Moolenaar 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
57131337658SMarcel Moolenaar 	if (xsp == NULL) {
57231337658SMarcel Moolenaar 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
573d1a0d267SMarcel Moolenaar 	    return -1;
57431337658SMarcel Moolenaar 	}
57531337658SMarcel Moolenaar 
57631337658SMarcel Moolenaar 	int count = depth - xop->xo_stack_size;
57731337658SMarcel Moolenaar 
57831337658SMarcel Moolenaar 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
57931337658SMarcel Moolenaar 	xop->xo_stack_size = depth;
58031337658SMarcel Moolenaar 	xop->xo_stack = xsp;
58131337658SMarcel Moolenaar     }
58231337658SMarcel Moolenaar 
58331337658SMarcel Moolenaar     return 0;
58431337658SMarcel Moolenaar }
58531337658SMarcel Moolenaar 
58631337658SMarcel Moolenaar void
58731337658SMarcel Moolenaar xo_no_setlocale (void)
58831337658SMarcel Moolenaar {
58931337658SMarcel Moolenaar     xo_locale_inited = 1;	/* Skip initialization */
59031337658SMarcel Moolenaar }
59131337658SMarcel Moolenaar 
59231337658SMarcel Moolenaar /*
593545ddfbeSMarcel Moolenaar  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
594545ddfbeSMarcel Moolenaar  * standard way to decide this (e.g. getlinebuf()), we have configure
595788ca347SMarcel Moolenaar  * look to find __flbf, which glibc supported.  If not, we'll rely on
596788ca347SMarcel Moolenaar  * isatty, with the assumption that terminals are the only thing
597545ddfbeSMarcel Moolenaar  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
598545ddfbeSMarcel Moolenaar  * which is all __flbf does, but that's even tackier.  Like a
599545ddfbeSMarcel Moolenaar  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
600545ddfbeSMarcel Moolenaar  * something we're willing to do.
601545ddfbeSMarcel Moolenaar  */
602545ddfbeSMarcel Moolenaar static int
603545ddfbeSMarcel Moolenaar xo_is_line_buffered (FILE *stream)
604545ddfbeSMarcel Moolenaar {
605545ddfbeSMarcel Moolenaar #if HAVE___FLBF
606545ddfbeSMarcel Moolenaar     if (__flbf(stream))
607545ddfbeSMarcel Moolenaar 	return 1;
608545ddfbeSMarcel Moolenaar #else /* HAVE___FLBF */
609545ddfbeSMarcel Moolenaar     if (isatty(fileno(stream)))
610545ddfbeSMarcel Moolenaar 	return 1;
611545ddfbeSMarcel Moolenaar #endif /* HAVE___FLBF */
612545ddfbeSMarcel Moolenaar     return 0;
613545ddfbeSMarcel Moolenaar }
614545ddfbeSMarcel Moolenaar 
615545ddfbeSMarcel Moolenaar /*
61631337658SMarcel Moolenaar  * Initialize an xo_handle_t, using both static defaults and
61731337658SMarcel Moolenaar  * the global settings from the LIBXO_OPTIONS environment
61831337658SMarcel Moolenaar  * variable.
61931337658SMarcel Moolenaar  */
62031337658SMarcel Moolenaar static void
62131337658SMarcel Moolenaar xo_init_handle (xo_handle_t *xop)
62231337658SMarcel Moolenaar {
62331337658SMarcel Moolenaar     xop->xo_opaque = stdout;
62431337658SMarcel Moolenaar     xop->xo_write = xo_write_to_file;
625545ddfbeSMarcel Moolenaar     xop->xo_flush = xo_flush_file;
626545ddfbeSMarcel Moolenaar 
627545ddfbeSMarcel Moolenaar     if (xo_is_line_buffered(stdout))
628d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_FLUSH_LINE);
62931337658SMarcel Moolenaar 
63031337658SMarcel Moolenaar     /*
631788ca347SMarcel Moolenaar      * We only want to do color output on terminals, but we only want
632788ca347SMarcel Moolenaar      * to do this if the user has asked for color.
633788ca347SMarcel Moolenaar      */
634d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
635d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_COLOR);
636788ca347SMarcel Moolenaar 
637788ca347SMarcel Moolenaar     /*
63831337658SMarcel Moolenaar      * We need to initialize the locale, which isn't really pretty.
63931337658SMarcel Moolenaar      * Libraries should depend on their caller to set up the
64031337658SMarcel Moolenaar      * environment.  But we really can't count on the caller to do
64131337658SMarcel Moolenaar      * this, because well, they won't.  Trust me.
64231337658SMarcel Moolenaar      */
64331337658SMarcel Moolenaar     if (!xo_locale_inited) {
64431337658SMarcel Moolenaar 	xo_locale_inited = 1;	/* Only do this once */
64531337658SMarcel Moolenaar 
64631337658SMarcel Moolenaar 	const char *cp = getenv("LC_CTYPE");
64731337658SMarcel Moolenaar 	if (cp == NULL)
64831337658SMarcel Moolenaar 	    cp = getenv("LANG");
64931337658SMarcel Moolenaar 	if (cp == NULL)
65031337658SMarcel Moolenaar 	    cp = getenv("LC_ALL");
65131337658SMarcel Moolenaar 	if (cp == NULL)
652d1a0d267SMarcel Moolenaar 	    cp = "C";		/* Default for C programs */
653c600d307SMarcel Moolenaar 	(void) setlocale(LC_CTYPE, cp);
65431337658SMarcel Moolenaar     }
65531337658SMarcel Moolenaar 
65631337658SMarcel Moolenaar     /*
65731337658SMarcel Moolenaar      * Initialize only the xo_buffers we know we'll need; the others
65831337658SMarcel Moolenaar      * can be allocated as needed.
65931337658SMarcel Moolenaar      */
66031337658SMarcel Moolenaar     xo_buf_init(&xop->xo_data);
66131337658SMarcel Moolenaar     xo_buf_init(&xop->xo_fmt);
66231337658SMarcel Moolenaar 
663d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
664d1a0d267SMarcel Moolenaar 	return;
665d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
666d1a0d267SMarcel Moolenaar 
66731337658SMarcel Moolenaar     xop->xo_indent_by = XO_INDENT_BY;
66831337658SMarcel Moolenaar     xo_depth_check(xop, XO_DEPTH);
66931337658SMarcel Moolenaar 
67031337658SMarcel Moolenaar #if !defined(NO_LIBXO_OPTIONS)
671d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
67231337658SMarcel Moolenaar 	char *env = getenv("LIBXO_OPTIONS");
67331337658SMarcel Moolenaar 	if (env)
67431337658SMarcel Moolenaar 	    xo_set_options(xop, env);
675d1a0d267SMarcel Moolenaar 
67631337658SMarcel Moolenaar     }
67731337658SMarcel Moolenaar #endif /* NO_GETENV */
678d1a0d267SMarcel Moolenaar 
679d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
68031337658SMarcel Moolenaar }
68131337658SMarcel Moolenaar 
68231337658SMarcel Moolenaar /*
68331337658SMarcel Moolenaar  * Initialize the default handle.
68431337658SMarcel Moolenaar  */
68531337658SMarcel Moolenaar static void
68631337658SMarcel Moolenaar xo_default_init (void)
68731337658SMarcel Moolenaar {
68831337658SMarcel Moolenaar     xo_handle_t *xop = &xo_default_handle;
68931337658SMarcel Moolenaar 
69031337658SMarcel Moolenaar     xo_init_handle(xop);
69131337658SMarcel Moolenaar 
69231337658SMarcel Moolenaar     xo_default_inited = 1;
69331337658SMarcel Moolenaar }
69431337658SMarcel Moolenaar 
69531337658SMarcel Moolenaar /*
69631337658SMarcel Moolenaar  * Cheap convenience function to return either the argument, or
69731337658SMarcel Moolenaar  * the internal handle, after it has been initialized.  The usage
69831337658SMarcel Moolenaar  * is:
69931337658SMarcel Moolenaar  *    xop = xo_default(xop);
70031337658SMarcel Moolenaar  */
70131337658SMarcel Moolenaar static xo_handle_t *
70231337658SMarcel Moolenaar xo_default (xo_handle_t *xop)
70331337658SMarcel Moolenaar {
70431337658SMarcel Moolenaar     if (xop == NULL) {
70531337658SMarcel Moolenaar 	if (xo_default_inited == 0)
70631337658SMarcel Moolenaar 	    xo_default_init();
70731337658SMarcel Moolenaar 	xop = &xo_default_handle;
70831337658SMarcel Moolenaar     }
70931337658SMarcel Moolenaar 
71031337658SMarcel Moolenaar     return xop;
71131337658SMarcel Moolenaar }
71231337658SMarcel Moolenaar 
71331337658SMarcel Moolenaar /*
71431337658SMarcel Moolenaar  * Return the number of spaces we should be indenting.  If
715788ca347SMarcel Moolenaar  * we are pretty-printing, this is indent * indent_by.
71631337658SMarcel Moolenaar  */
71731337658SMarcel Moolenaar static int
71831337658SMarcel Moolenaar xo_indent (xo_handle_t *xop)
71931337658SMarcel Moolenaar {
72031337658SMarcel Moolenaar     int rc = 0;
72131337658SMarcel Moolenaar 
72231337658SMarcel Moolenaar     xop = xo_default(xop);
72331337658SMarcel Moolenaar 
724d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY)) {
72531337658SMarcel Moolenaar 	rc = xop->xo_indent * xop->xo_indent_by;
726d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
72731337658SMarcel Moolenaar 	    rc += xop->xo_indent_by;
72831337658SMarcel Moolenaar     }
72931337658SMarcel Moolenaar 
730545ddfbeSMarcel Moolenaar     return (rc > 0) ? rc : 0;
73131337658SMarcel Moolenaar }
73231337658SMarcel Moolenaar 
73331337658SMarcel Moolenaar static void
73431337658SMarcel Moolenaar xo_buf_indent (xo_handle_t *xop, int indent)
73531337658SMarcel Moolenaar {
73631337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
73731337658SMarcel Moolenaar 
73831337658SMarcel Moolenaar     if (indent <= 0)
73931337658SMarcel Moolenaar 	indent = xo_indent(xop);
74031337658SMarcel Moolenaar 
74131337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, indent))
74231337658SMarcel Moolenaar 	return;
74331337658SMarcel Moolenaar 
74431337658SMarcel Moolenaar     memset(xbp->xb_curp, ' ', indent);
74531337658SMarcel Moolenaar     xbp->xb_curp += indent;
74631337658SMarcel Moolenaar }
74731337658SMarcel Moolenaar 
74831337658SMarcel Moolenaar static char xo_xml_amp[] = "&amp;";
74931337658SMarcel Moolenaar static char xo_xml_lt[] = "&lt;";
75031337658SMarcel Moolenaar static char xo_xml_gt[] = "&gt;";
75131337658SMarcel Moolenaar static char xo_xml_quot[] = "&quot;";
75231337658SMarcel Moolenaar 
7538a6eceffSPhil Shafer static ssize_t
7548a6eceffSPhil Shafer xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags)
75531337658SMarcel Moolenaar {
7568a6eceffSPhil Shafer     ssize_t slen;
7578a6eceffSPhil Shafer     ssize_t delta = 0;
75831337658SMarcel Moolenaar     char *cp, *ep, *ip;
75931337658SMarcel Moolenaar     const char *sp;
7608a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
76131337658SMarcel Moolenaar 
76231337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
76331337658SMarcel Moolenaar 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
76431337658SMarcel Moolenaar 	if (*cp == '<')
76531337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_lt) - 2;
76631337658SMarcel Moolenaar 	else if (*cp == '>')
76731337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_gt) - 2;
76831337658SMarcel Moolenaar 	else if (*cp == '&')
76931337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_amp) - 2;
77031337658SMarcel Moolenaar 	else if (attr && *cp == '"')
77131337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_quot) - 2;
77231337658SMarcel Moolenaar     }
77331337658SMarcel Moolenaar 
77431337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
77531337658SMarcel Moolenaar 	return len;
77631337658SMarcel Moolenaar 
77731337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
77831337658SMarcel Moolenaar 	return 0;
77931337658SMarcel Moolenaar 
78031337658SMarcel Moolenaar     ep = xbp->xb_curp;
78131337658SMarcel Moolenaar     cp = ep + len;
78231337658SMarcel Moolenaar     ip = cp + delta;
78331337658SMarcel Moolenaar     do {
78431337658SMarcel Moolenaar 	cp -= 1;
78531337658SMarcel Moolenaar 	ip -= 1;
78631337658SMarcel Moolenaar 
78731337658SMarcel Moolenaar 	if (*cp == '<')
78831337658SMarcel Moolenaar 	    sp = xo_xml_lt;
78931337658SMarcel Moolenaar 	else if (*cp == '>')
79031337658SMarcel Moolenaar 	    sp = xo_xml_gt;
79131337658SMarcel Moolenaar 	else if (*cp == '&')
79231337658SMarcel Moolenaar 	    sp = xo_xml_amp;
79331337658SMarcel Moolenaar 	else if (attr && *cp == '"')
79431337658SMarcel Moolenaar 	    sp = xo_xml_quot;
79531337658SMarcel Moolenaar 	else {
79631337658SMarcel Moolenaar 	    *ip = *cp;
79731337658SMarcel Moolenaar 	    continue;
79831337658SMarcel Moolenaar 	}
79931337658SMarcel Moolenaar 
80031337658SMarcel Moolenaar 	slen = strlen(sp);
80131337658SMarcel Moolenaar 	ip -= slen - 1;
80231337658SMarcel Moolenaar 	memcpy(ip, sp, slen);
80331337658SMarcel Moolenaar 
80431337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
80531337658SMarcel Moolenaar 
80631337658SMarcel Moolenaar     return len + delta;
80731337658SMarcel Moolenaar }
80831337658SMarcel Moolenaar 
8098a6eceffSPhil Shafer static ssize_t
8108a6eceffSPhil Shafer xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
81131337658SMarcel Moolenaar {
8128a6eceffSPhil Shafer     ssize_t delta = 0;
81331337658SMarcel Moolenaar     char *cp, *ep, *ip;
81431337658SMarcel Moolenaar 
81531337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
816545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"')
81731337658SMarcel Moolenaar 	    delta += 1;
818545ddfbeSMarcel Moolenaar 	else if (*cp == '\n' || *cp == '\r')
81931337658SMarcel Moolenaar 	    delta += 1;
82031337658SMarcel Moolenaar     }
82131337658SMarcel Moolenaar 
82231337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
82331337658SMarcel Moolenaar 	return len;
82431337658SMarcel Moolenaar 
82531337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
82631337658SMarcel Moolenaar 	return 0;
82731337658SMarcel Moolenaar 
82831337658SMarcel Moolenaar     ep = xbp->xb_curp;
82931337658SMarcel Moolenaar     cp = ep + len;
83031337658SMarcel Moolenaar     ip = cp + delta;
83131337658SMarcel Moolenaar     do {
83231337658SMarcel Moolenaar 	cp -= 1;
83331337658SMarcel Moolenaar 	ip -= 1;
83431337658SMarcel Moolenaar 
835545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"') {
83631337658SMarcel Moolenaar 	    *ip-- = *cp;
83731337658SMarcel Moolenaar 	    *ip = '\\';
838545ddfbeSMarcel Moolenaar 	} else if (*cp == '\n') {
839545ddfbeSMarcel Moolenaar 	    *ip-- = 'n';
840545ddfbeSMarcel Moolenaar 	    *ip = '\\';
841545ddfbeSMarcel Moolenaar 	} else if (*cp == '\r') {
842545ddfbeSMarcel Moolenaar 	    *ip-- = 'r';
843545ddfbeSMarcel Moolenaar 	    *ip = '\\';
844545ddfbeSMarcel Moolenaar 	} else {
845545ddfbeSMarcel Moolenaar 	    *ip = *cp;
846545ddfbeSMarcel Moolenaar 	}
84731337658SMarcel Moolenaar 
84831337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
84931337658SMarcel Moolenaar 
85031337658SMarcel Moolenaar     return len + delta;
85131337658SMarcel Moolenaar }
85231337658SMarcel Moolenaar 
85331337658SMarcel Moolenaar /*
854d1a0d267SMarcel Moolenaar  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
855d1a0d267SMarcel Moolenaar  *                                ; ']' MUST be escaped.
85631337658SMarcel Moolenaar  */
8578a6eceffSPhil Shafer static ssize_t
8588a6eceffSPhil Shafer xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
85931337658SMarcel Moolenaar {
8608a6eceffSPhil Shafer     ssize_t delta = 0;
861d1a0d267SMarcel Moolenaar     char *cp, *ep, *ip;
86231337658SMarcel Moolenaar 
863d1a0d267SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
864d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']')
865d1a0d267SMarcel Moolenaar 	    delta += 1;
86631337658SMarcel Moolenaar     }
86731337658SMarcel Moolenaar 
868d1a0d267SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
869d1a0d267SMarcel Moolenaar 	return len;
870788ca347SMarcel Moolenaar 
871d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
872d1a0d267SMarcel Moolenaar 	return 0;
873788ca347SMarcel Moolenaar 
874d1a0d267SMarcel Moolenaar     ep = xbp->xb_curp;
875d1a0d267SMarcel Moolenaar     cp = ep + len;
876d1a0d267SMarcel Moolenaar     ip = cp + delta;
877d1a0d267SMarcel Moolenaar     do {
878d1a0d267SMarcel Moolenaar 	cp -= 1;
879d1a0d267SMarcel Moolenaar 	ip -= 1;
880d1a0d267SMarcel Moolenaar 
881d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
882d1a0d267SMarcel Moolenaar 	    *ip-- = *cp;
883d1a0d267SMarcel Moolenaar 	    *ip = '\\';
884d1a0d267SMarcel Moolenaar 	} else {
885d1a0d267SMarcel Moolenaar 	    *ip = *cp;
886d1a0d267SMarcel Moolenaar 	}
887d1a0d267SMarcel Moolenaar 
888d1a0d267SMarcel Moolenaar     } while (cp > ep && cp != ip);
889d1a0d267SMarcel Moolenaar 
890d1a0d267SMarcel Moolenaar     return len + delta;
891788ca347SMarcel Moolenaar }
892788ca347SMarcel Moolenaar 
89331337658SMarcel Moolenaar static void
89431337658SMarcel Moolenaar xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
8958a6eceffSPhil Shafer 	       const char *str, ssize_t len, xo_xff_flags_t flags)
89631337658SMarcel Moolenaar {
89731337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, len))
89831337658SMarcel Moolenaar 	return;
89931337658SMarcel Moolenaar 
90031337658SMarcel Moolenaar     memcpy(xbp->xb_curp, str, len);
90131337658SMarcel Moolenaar 
902788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
90331337658SMarcel Moolenaar     case XO_STYLE_XML:
90431337658SMarcel Moolenaar     case XO_STYLE_HTML:
905d1a0d267SMarcel Moolenaar 	len = xo_escape_xml(xbp, len, flags);
90631337658SMarcel Moolenaar 	break;
90731337658SMarcel Moolenaar 
90831337658SMarcel Moolenaar     case XO_STYLE_JSON:
909d1a0d267SMarcel Moolenaar 	len = xo_escape_json(xbp, len, flags);
910d1a0d267SMarcel Moolenaar 	break;
911d1a0d267SMarcel Moolenaar 
912d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
913d1a0d267SMarcel Moolenaar 	len = xo_escape_sdparams(xbp, len, flags);
91431337658SMarcel Moolenaar 	break;
91531337658SMarcel Moolenaar     }
91631337658SMarcel Moolenaar 
91731337658SMarcel Moolenaar     xbp->xb_curp += len;
91831337658SMarcel Moolenaar }
91931337658SMarcel Moolenaar 
92031337658SMarcel Moolenaar /*
92131337658SMarcel Moolenaar  * Write the current contents of the data buffer using the handle's
92231337658SMarcel Moolenaar  * xo_write function.
92331337658SMarcel Moolenaar  */
9248a6eceffSPhil Shafer static ssize_t
92531337658SMarcel Moolenaar xo_write (xo_handle_t *xop)
92631337658SMarcel Moolenaar {
9278a6eceffSPhil Shafer     ssize_t rc = 0;
92831337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
92931337658SMarcel Moolenaar 
93031337658SMarcel Moolenaar     if (xbp->xb_curp != xbp->xb_bufp) {
93131337658SMarcel Moolenaar 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
93231337658SMarcel Moolenaar 	xo_anchor_clear(xop);
933d1a0d267SMarcel Moolenaar 	if (xop->xo_write)
934545ddfbeSMarcel Moolenaar 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
93531337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp;
93631337658SMarcel Moolenaar     }
93731337658SMarcel Moolenaar 
93831337658SMarcel Moolenaar     /* Turn off the flags that don't survive across writes */
939d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
940545ddfbeSMarcel Moolenaar 
941545ddfbeSMarcel Moolenaar     return rc;
94231337658SMarcel Moolenaar }
94331337658SMarcel Moolenaar 
94431337658SMarcel Moolenaar /*
94531337658SMarcel Moolenaar  * Format arguments into our buffer.  If a custom formatter has been set,
94631337658SMarcel Moolenaar  * we use that to do the work; otherwise we vsnprintf().
94731337658SMarcel Moolenaar  */
9488a6eceffSPhil Shafer static ssize_t
94931337658SMarcel Moolenaar xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
95031337658SMarcel Moolenaar {
95131337658SMarcel Moolenaar     va_list va_local;
9528a6eceffSPhil Shafer     ssize_t rc;
9538a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
95431337658SMarcel Moolenaar 
95531337658SMarcel Moolenaar     va_copy(va_local, vap);
95631337658SMarcel Moolenaar 
95731337658SMarcel Moolenaar     if (xop->xo_formatter)
95831337658SMarcel Moolenaar 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
95931337658SMarcel Moolenaar     else
96031337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
96131337658SMarcel Moolenaar 
962788ca347SMarcel Moolenaar     if (rc >= left) {
963c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
964c600d307SMarcel Moolenaar 	    va_end(va_local);
96531337658SMarcel Moolenaar 	    return -1;
966c600d307SMarcel Moolenaar 	}
96731337658SMarcel Moolenaar 
96831337658SMarcel Moolenaar 	/*
96931337658SMarcel Moolenaar 	 * After we call vsnprintf(), the stage of vap is not defined.
97031337658SMarcel Moolenaar 	 * We need to copy it before we pass.  Then we have to do our
97131337658SMarcel Moolenaar 	 * own logic below to move it along.  This is because the
972788ca347SMarcel Moolenaar 	 * implementation can have va_list be a pointer (bsd) or a
97331337658SMarcel Moolenaar 	 * structure (macosx) or anything in between.
97431337658SMarcel Moolenaar 	 */
97531337658SMarcel Moolenaar 
97631337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
97731337658SMarcel Moolenaar 	va_copy(va_local, vap);
97831337658SMarcel Moolenaar 
97931337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
98031337658SMarcel Moolenaar 	if (xop->xo_formatter)
981788ca347SMarcel Moolenaar 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
98231337658SMarcel Moolenaar 	else
98331337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
98431337658SMarcel Moolenaar     }
98531337658SMarcel Moolenaar     va_end(va_local);
98631337658SMarcel Moolenaar 
98731337658SMarcel Moolenaar     return rc;
98831337658SMarcel Moolenaar }
98931337658SMarcel Moolenaar 
99031337658SMarcel Moolenaar /*
991ee5cf116SPhil Shafer  * Print some data through the handle.
99231337658SMarcel Moolenaar  */
9938a6eceffSPhil Shafer static ssize_t
99431337658SMarcel Moolenaar xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
99531337658SMarcel Moolenaar {
99631337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
9978a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
9988a6eceffSPhil Shafer     ssize_t rc;
99931337658SMarcel Moolenaar     va_list va_local;
100031337658SMarcel Moolenaar 
100131337658SMarcel Moolenaar     va_copy(va_local, vap);
100231337658SMarcel Moolenaar 
100331337658SMarcel Moolenaar     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
100431337658SMarcel Moolenaar 
1005d1a0d267SMarcel Moolenaar     if (rc >= left) {
1006c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
1007c600d307SMarcel Moolenaar 	    va_end(va_local);
100831337658SMarcel Moolenaar 	    return -1;
1009c600d307SMarcel Moolenaar 	}
101031337658SMarcel Moolenaar 
101131337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
101231337658SMarcel Moolenaar 	va_copy(va_local, vap);
101331337658SMarcel Moolenaar 
101431337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
101531337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
101631337658SMarcel Moolenaar     }
101731337658SMarcel Moolenaar 
101831337658SMarcel Moolenaar     va_end(va_local);
101931337658SMarcel Moolenaar 
102031337658SMarcel Moolenaar     if (rc > 0)
102131337658SMarcel Moolenaar 	xbp->xb_curp += rc;
102231337658SMarcel Moolenaar 
102331337658SMarcel Moolenaar     return rc;
102431337658SMarcel Moolenaar }
102531337658SMarcel Moolenaar 
10268a6eceffSPhil Shafer static ssize_t
102731337658SMarcel Moolenaar xo_printf (xo_handle_t *xop, const char *fmt, ...)
102831337658SMarcel Moolenaar {
10298a6eceffSPhil Shafer     ssize_t rc;
103031337658SMarcel Moolenaar     va_list vap;
103131337658SMarcel Moolenaar 
103231337658SMarcel Moolenaar     va_start(vap, fmt);
103331337658SMarcel Moolenaar 
103431337658SMarcel Moolenaar     rc = xo_printf_v(xop, fmt, vap);
103531337658SMarcel Moolenaar 
103631337658SMarcel Moolenaar     va_end(vap);
103731337658SMarcel Moolenaar     return rc;
103831337658SMarcel Moolenaar }
103931337658SMarcel Moolenaar 
104031337658SMarcel Moolenaar /*
104131337658SMarcel Moolenaar  * These next few function are make The Essential UTF-8 Ginsu Knife.
104231337658SMarcel Moolenaar  * Identify an input and output character, and convert it.
104331337658SMarcel Moolenaar  */
10448a6eceffSPhil Shafer static uint8_t xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
104531337658SMarcel Moolenaar 
104631337658SMarcel Moolenaar static int
104731337658SMarcel Moolenaar xo_is_utf8 (char ch)
104831337658SMarcel Moolenaar {
104931337658SMarcel Moolenaar     return (ch & 0x80);
105031337658SMarcel Moolenaar }
105131337658SMarcel Moolenaar 
10528a6eceffSPhil Shafer static inline ssize_t
105331337658SMarcel Moolenaar xo_utf8_to_wc_len (const char *buf)
105431337658SMarcel Moolenaar {
105531337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
10568a6eceffSPhil Shafer     ssize_t len;
105731337658SMarcel Moolenaar 
105831337658SMarcel Moolenaar     if ((b & 0x80) == 0x0)
105931337658SMarcel Moolenaar 	len = 1;
106031337658SMarcel Moolenaar     else if ((b & 0xe0) == 0xc0)
106131337658SMarcel Moolenaar 	len = 2;
106231337658SMarcel Moolenaar     else if ((b & 0xf0) == 0xe0)
106331337658SMarcel Moolenaar 	len = 3;
106431337658SMarcel Moolenaar     else if ((b & 0xf8) == 0xf0)
106531337658SMarcel Moolenaar 	len = 4;
106631337658SMarcel Moolenaar     else if ((b & 0xfc) == 0xf8)
106731337658SMarcel Moolenaar 	len = 5;
106831337658SMarcel Moolenaar     else if ((b & 0xfe) == 0xfc)
106931337658SMarcel Moolenaar 	len = 6;
107031337658SMarcel Moolenaar     else
107131337658SMarcel Moolenaar 	len = -1;
107231337658SMarcel Moolenaar 
107331337658SMarcel Moolenaar     return len;
107431337658SMarcel Moolenaar }
107531337658SMarcel Moolenaar 
10768a6eceffSPhil Shafer static ssize_t
10778a6eceffSPhil Shafer xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz)
107831337658SMarcel Moolenaar {
107931337658SMarcel Moolenaar 
108031337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
10818a6eceffSPhil Shafer     ssize_t len, i;
108231337658SMarcel Moolenaar 
108331337658SMarcel Moolenaar     len = xo_utf8_to_wc_len(buf);
108431337658SMarcel Moolenaar     if (len == -1) {
108531337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
108631337658SMarcel Moolenaar 	return -1;
108731337658SMarcel Moolenaar     }
108831337658SMarcel Moolenaar 
108931337658SMarcel Moolenaar     if (len > bufsiz) {
109031337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
109131337658SMarcel Moolenaar 		   b, len, bufsiz);
109231337658SMarcel Moolenaar 	return -1;
109331337658SMarcel Moolenaar     }
109431337658SMarcel Moolenaar 
109531337658SMarcel Moolenaar     for (i = 2; i < len; i++) {
109631337658SMarcel Moolenaar 	b = (unsigned char ) buf[i];
109731337658SMarcel Moolenaar 	if ((b & 0xc0) != 0x80) {
109831337658SMarcel Moolenaar 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
109931337658SMarcel Moolenaar 	    return -1;
110031337658SMarcel Moolenaar 	}
110131337658SMarcel Moolenaar     }
110231337658SMarcel Moolenaar 
110331337658SMarcel Moolenaar     return len;
110431337658SMarcel Moolenaar }
110531337658SMarcel Moolenaar 
110631337658SMarcel Moolenaar /*
110731337658SMarcel Moolenaar  * Build a wide character from the input buffer; the number of
110831337658SMarcel Moolenaar  * bits we pull off the first character is dependent on the length,
110931337658SMarcel Moolenaar  * but we put 6 bits off all other bytes.
111031337658SMarcel Moolenaar  */
111142ff34c3SPhil Shafer static inline wchar_t
11128a6eceffSPhil Shafer xo_utf8_char (const char *buf, ssize_t len)
111331337658SMarcel Moolenaar {
111442ff34c3SPhil Shafer     /* Most common case: singleton byte */
111542ff34c3SPhil Shafer     if (len == 1)
111642ff34c3SPhil Shafer 	return (unsigned char) buf[0];
111742ff34c3SPhil Shafer 
11188a6eceffSPhil Shafer     ssize_t i;
111931337658SMarcel Moolenaar     wchar_t wc;
112031337658SMarcel Moolenaar     const unsigned char *cp = (const unsigned char *) buf;
112131337658SMarcel Moolenaar 
112231337658SMarcel Moolenaar     wc = *cp & xo_utf8_bits[len];
112331337658SMarcel Moolenaar     for (i = 1; i < len; i++) {
112431337658SMarcel Moolenaar 	wc <<= 6;
112531337658SMarcel Moolenaar 	wc |= cp[i] & 0x3f;
112631337658SMarcel Moolenaar 	if ((cp[i] & 0xc0) != 0x80)
112731337658SMarcel Moolenaar 	    return (wchar_t) -1;
112831337658SMarcel Moolenaar     }
112931337658SMarcel Moolenaar 
113031337658SMarcel Moolenaar     return wc;
113131337658SMarcel Moolenaar }
113231337658SMarcel Moolenaar 
113331337658SMarcel Moolenaar /*
113431337658SMarcel Moolenaar  * Determine the number of bytes needed to encode a wide character.
113531337658SMarcel Moolenaar  */
11368a6eceffSPhil Shafer static ssize_t
113731337658SMarcel Moolenaar xo_utf8_emit_len (wchar_t wc)
113831337658SMarcel Moolenaar {
11398a6eceffSPhil Shafer     ssize_t len;
114031337658SMarcel Moolenaar 
114131337658SMarcel Moolenaar     if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
114231337658SMarcel Moolenaar 	len = 1;
114331337658SMarcel Moolenaar     else if ((wc & ((1<<11) - 1)) == wc)
114431337658SMarcel Moolenaar 	len = 2;
114531337658SMarcel Moolenaar     else if ((wc & ((1<<16) - 1)) == wc)
114631337658SMarcel Moolenaar 	len = 3;
114731337658SMarcel Moolenaar     else if ((wc & ((1<<21) - 1)) == wc)
114831337658SMarcel Moolenaar 	len = 4;
114931337658SMarcel Moolenaar     else if ((wc & ((1<<26) - 1)) == wc)
115031337658SMarcel Moolenaar 	len = 5;
115131337658SMarcel Moolenaar     else
115231337658SMarcel Moolenaar 	len = 6;
115331337658SMarcel Moolenaar 
115431337658SMarcel Moolenaar     return len;
115531337658SMarcel Moolenaar }
115631337658SMarcel Moolenaar 
115731337658SMarcel Moolenaar static void
11588a6eceffSPhil Shafer xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc)
115931337658SMarcel Moolenaar {
11608a6eceffSPhil Shafer     ssize_t i;
116131337658SMarcel Moolenaar 
116231337658SMarcel Moolenaar     if (len == 1) { /* Simple case */
116331337658SMarcel Moolenaar 	buf[0] = wc & 0x7f;
116431337658SMarcel Moolenaar 	return;
116531337658SMarcel Moolenaar     }
116631337658SMarcel Moolenaar 
116731337658SMarcel Moolenaar     for (i = len - 1; i >= 0; i--) {
116831337658SMarcel Moolenaar 	buf[i] = 0x80 | (wc & 0x3f);
116931337658SMarcel Moolenaar 	wc >>= 6;
117031337658SMarcel Moolenaar     }
117131337658SMarcel Moolenaar 
117231337658SMarcel Moolenaar     buf[0] &= xo_utf8_bits[len];
117331337658SMarcel Moolenaar     buf[0] |= ~xo_utf8_bits[len] << 1;
117431337658SMarcel Moolenaar }
117531337658SMarcel Moolenaar 
11768a6eceffSPhil Shafer static ssize_t
117731337658SMarcel Moolenaar xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
11788a6eceffSPhil Shafer 				const char *ibuf, ssize_t ilen)
117931337658SMarcel Moolenaar {
118031337658SMarcel Moolenaar     wchar_t wc;
11818a6eceffSPhil Shafer     ssize_t len;
118231337658SMarcel Moolenaar 
118331337658SMarcel Moolenaar     /*
118431337658SMarcel Moolenaar      * Build our wide character from the input buffer; the number of
118531337658SMarcel Moolenaar      * bits we pull off the first character is dependent on the length,
118631337658SMarcel Moolenaar      * but we put 6 bits off all other bytes.
118731337658SMarcel Moolenaar      */
118831337658SMarcel Moolenaar     wc = xo_utf8_char(ibuf, ilen);
118931337658SMarcel Moolenaar     if (wc == (wchar_t) -1) {
119031337658SMarcel Moolenaar 	xo_failure(xop, "invalid utf-8 byte sequence");
119131337658SMarcel Moolenaar 	return 0;
119231337658SMarcel Moolenaar     }
119331337658SMarcel Moolenaar 
1194d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
119531337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, ilen))
119631337658SMarcel Moolenaar 	    return 0;
119731337658SMarcel Moolenaar 
119831337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, ibuf, ilen);
119931337658SMarcel Moolenaar 	xbp->xb_curp += ilen;
120031337658SMarcel Moolenaar 
120131337658SMarcel Moolenaar     } else {
120231337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
120331337658SMarcel Moolenaar 	    return 0;
120431337658SMarcel Moolenaar 
120531337658SMarcel Moolenaar 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
120631337658SMarcel Moolenaar 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
120731337658SMarcel Moolenaar 
120831337658SMarcel Moolenaar 	if (len <= 0) {
120931337658SMarcel Moolenaar 	    xo_failure(xop, "could not convert wide char: %lx",
121031337658SMarcel Moolenaar 		       (unsigned long) wc);
121131337658SMarcel Moolenaar 	    return 0;
121231337658SMarcel Moolenaar 	}
121331337658SMarcel Moolenaar 	xbp->xb_curp += len;
121431337658SMarcel Moolenaar     }
121531337658SMarcel Moolenaar 
1216d1a0d267SMarcel Moolenaar     return xo_wcwidth(wc);
121731337658SMarcel Moolenaar }
121831337658SMarcel Moolenaar 
121931337658SMarcel Moolenaar static void
122031337658SMarcel Moolenaar xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
12218a6eceffSPhil Shafer 		      const char *cp, ssize_t len)
122231337658SMarcel Moolenaar {
122331337658SMarcel Moolenaar     const char *sp = cp, *ep = cp + len;
12248a6eceffSPhil Shafer     ssize_t save_off = xbp->xb_bufp - xbp->xb_curp;
12258a6eceffSPhil Shafer     ssize_t slen;
122631337658SMarcel Moolenaar     int cols = 0;
122731337658SMarcel Moolenaar 
122831337658SMarcel Moolenaar     for ( ; cp < ep; cp++) {
122931337658SMarcel Moolenaar 	if (!xo_is_utf8(*cp)) {
123031337658SMarcel Moolenaar 	    cols += 1;
123131337658SMarcel Moolenaar 	    continue;
123231337658SMarcel Moolenaar 	}
123331337658SMarcel Moolenaar 
123431337658SMarcel Moolenaar 	/*
123531337658SMarcel Moolenaar 	 * We're looking at a non-ascii UTF-8 character.
123631337658SMarcel Moolenaar 	 * First we copy the previous data.
123731337658SMarcel Moolenaar 	 * Then we need find the length and validate it.
123831337658SMarcel Moolenaar 	 * Then we turn it into a wide string.
123931337658SMarcel Moolenaar 	 * Then we turn it into a localized string.
124031337658SMarcel Moolenaar 	 * Then we repeat.  Isn't i18n fun?
124131337658SMarcel Moolenaar 	 */
124231337658SMarcel Moolenaar 	if (sp != cp)
124331337658SMarcel Moolenaar 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
124431337658SMarcel Moolenaar 
124531337658SMarcel Moolenaar 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
124631337658SMarcel Moolenaar 	if (slen <= 0) {
124731337658SMarcel Moolenaar 	    /* Bad data; back it all out */
124831337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + save_off;
124931337658SMarcel Moolenaar 	    return;
125031337658SMarcel Moolenaar 	}
125131337658SMarcel Moolenaar 
125231337658SMarcel Moolenaar 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
125331337658SMarcel Moolenaar 
1254ee5cf116SPhil Shafer 	/* Next time through, we'll start at the next character */
125531337658SMarcel Moolenaar 	cp += slen - 1;
125631337658SMarcel Moolenaar 	sp = cp + 1;
125731337658SMarcel Moolenaar     }
125831337658SMarcel Moolenaar 
125931337658SMarcel Moolenaar     /* Update column values */
1260d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
126131337658SMarcel Moolenaar 	xop->xo_columns += cols;
1262d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
126331337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
126431337658SMarcel Moolenaar 
126531337658SMarcel Moolenaar     /* Before we fall into the basic logic below, we need reset len */
126631337658SMarcel Moolenaar     len = ep - sp;
126731337658SMarcel Moolenaar     if (len != 0) /* Append trailing data */
126831337658SMarcel Moolenaar 	xo_buf_append(xbp, sp, len);
126931337658SMarcel Moolenaar }
127031337658SMarcel Moolenaar 
127131337658SMarcel Moolenaar /*
1272d1a0d267SMarcel Moolenaar  * Append the given string to the given buffer, without escaping or
1273d1a0d267SMarcel Moolenaar  * character set conversion.  This is the straight copy to the data
1274d1a0d267SMarcel Moolenaar  * buffer with no fanciness.
127531337658SMarcel Moolenaar  */
127631337658SMarcel Moolenaar static void
12778a6eceffSPhil Shafer xo_data_append (xo_handle_t *xop, const char *str, ssize_t len)
127831337658SMarcel Moolenaar {
127931337658SMarcel Moolenaar     xo_buf_append(&xop->xo_data, str, len);
128031337658SMarcel Moolenaar }
128131337658SMarcel Moolenaar 
128231337658SMarcel Moolenaar /*
128331337658SMarcel Moolenaar  * Append the given string to the given buffer
128431337658SMarcel Moolenaar  */
128531337658SMarcel Moolenaar static void
12868a6eceffSPhil Shafer xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len)
128731337658SMarcel Moolenaar {
128831337658SMarcel Moolenaar     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
128931337658SMarcel Moolenaar }
129031337658SMarcel Moolenaar 
129142ff34c3SPhil Shafer #ifdef LIBXO_NO_RETAIN
129242ff34c3SPhil Shafer /*
129342ff34c3SPhil Shafer  * Empty implementations of the retain logic
129442ff34c3SPhil Shafer  */
129542ff34c3SPhil Shafer 
129642ff34c3SPhil Shafer void
129742ff34c3SPhil Shafer xo_retain_clear_all (void)
129842ff34c3SPhil Shafer {
129942ff34c3SPhil Shafer     return;
130042ff34c3SPhil Shafer }
130142ff34c3SPhil Shafer 
130242ff34c3SPhil Shafer void
130342ff34c3SPhil Shafer xo_retain_clear (const char *fmt UNUSED)
130442ff34c3SPhil Shafer {
130542ff34c3SPhil Shafer     return;
130642ff34c3SPhil Shafer }
130742ff34c3SPhil Shafer static void
130842ff34c3SPhil Shafer xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
130942ff34c3SPhil Shafer 		unsigned num_fields UNUSED)
131042ff34c3SPhil Shafer {
131142ff34c3SPhil Shafer     return;
131242ff34c3SPhil Shafer }
131342ff34c3SPhil Shafer 
131442ff34c3SPhil Shafer static int
131542ff34c3SPhil Shafer xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
131642ff34c3SPhil Shafer 		 unsigned *nump UNUSED)
131742ff34c3SPhil Shafer {
131842ff34c3SPhil Shafer     return -1;
131942ff34c3SPhil Shafer }
132042ff34c3SPhil Shafer 
132142ff34c3SPhil Shafer #else /* !LIBXO_NO_RETAIN */
132242ff34c3SPhil Shafer /*
132342ff34c3SPhil Shafer  * Retain: We retain parsed field definitions to enhance performance,
132442ff34c3SPhil Shafer  * especially inside loops.  We depend on the caller treating the format
132542ff34c3SPhil Shafer  * strings as immutable, so that we can retain pointers into them.  We
132642ff34c3SPhil Shafer  * hold the pointers in a hash table, so allow quick access.  Retained
132742ff34c3SPhil Shafer  * information is retained until xo_retain_clear is called.
132842ff34c3SPhil Shafer  */
132942ff34c3SPhil Shafer 
133042ff34c3SPhil Shafer /*
133142ff34c3SPhil Shafer  * xo_retain_entry_t holds information about one retained set of
133242ff34c3SPhil Shafer  * parsed fields.
133342ff34c3SPhil Shafer  */
133442ff34c3SPhil Shafer typedef struct xo_retain_entry_s {
133542ff34c3SPhil Shafer     struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
133642ff34c3SPhil Shafer     unsigned long xre_hits;		 /* Number of times we've hit */
133742ff34c3SPhil Shafer     const char *xre_format;		 /* Pointer to format string */
133842ff34c3SPhil Shafer     unsigned xre_num_fields;		 /* Number of fields saved */
133942ff34c3SPhil Shafer     xo_field_info_t *xre_fields;	 /* Pointer to fields */
134042ff34c3SPhil Shafer } xo_retain_entry_t;
134142ff34c3SPhil Shafer 
134242ff34c3SPhil Shafer /*
134342ff34c3SPhil Shafer  * xo_retain_t holds a complete set of parsed fields as a hash table.
134442ff34c3SPhil Shafer  */
134542ff34c3SPhil Shafer #ifndef XO_RETAIN_SIZE
134642ff34c3SPhil Shafer #define XO_RETAIN_SIZE 6
134742ff34c3SPhil Shafer #endif /* XO_RETAIN_SIZE */
134842ff34c3SPhil Shafer #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
134942ff34c3SPhil Shafer 
135042ff34c3SPhil Shafer typedef struct xo_retain_s {
135142ff34c3SPhil Shafer     xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
135242ff34c3SPhil Shafer } xo_retain_t;
135342ff34c3SPhil Shafer 
135442ff34c3SPhil Shafer static THREAD_LOCAL(xo_retain_t) xo_retain;
135542ff34c3SPhil Shafer static THREAD_LOCAL(unsigned) xo_retain_count;
135642ff34c3SPhil Shafer 
135742ff34c3SPhil Shafer /*
135842ff34c3SPhil Shafer  * Simple hash function based on Thomas Wang's paper.  The original is
135942ff34c3SPhil Shafer  * gone, but an archive is available on the Way Back Machine:
136042ff34c3SPhil Shafer  *
136142ff34c3SPhil Shafer  * http://web.archive.org/web/20071223173210/\
136242ff34c3SPhil Shafer  *     http://www.concentric.net/~Ttwang/tech/inthash.htm
136342ff34c3SPhil Shafer  *
136442ff34c3SPhil Shafer  * For our purposes, we can assume the low four bits are uninteresting
136542ff34c3SPhil Shafer  * since any string less that 16 bytes wouldn't be worthy of
136642ff34c3SPhil Shafer  * retaining.  We toss the high bits also, since these bits are likely
136742ff34c3SPhil Shafer  * to be common among constant format strings.  We then run Wang's
136842ff34c3SPhil Shafer  * algorithm, and cap the result at RETAIN_HASH_SIZE.
136942ff34c3SPhil Shafer  */
137042ff34c3SPhil Shafer static unsigned
137142ff34c3SPhil Shafer xo_retain_hash (const char *fmt)
137242ff34c3SPhil Shafer {
137342ff34c3SPhil Shafer     volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
137442ff34c3SPhil Shafer 
137542ff34c3SPhil Shafer     /* Discard low four bits and high bits; they aren't interesting */
137642ff34c3SPhil Shafer     uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
137742ff34c3SPhil Shafer 
137842ff34c3SPhil Shafer     val = (val ^ 61) ^ (val >> 16);
137942ff34c3SPhil Shafer     val = val + (val << 3);
138042ff34c3SPhil Shafer     val = val ^ (val >> 4);
138142ff34c3SPhil Shafer     val = val * 0x3a8f05c5;	/* My large prime number */
138242ff34c3SPhil Shafer     val = val ^ (val >> 15);
138342ff34c3SPhil Shafer     val &= RETAIN_HASH_SIZE - 1;
138442ff34c3SPhil Shafer 
138542ff34c3SPhil Shafer     return val;
138642ff34c3SPhil Shafer }
138742ff34c3SPhil Shafer 
138842ff34c3SPhil Shafer /*
138942ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
139042ff34c3SPhil Shafer  */
139142ff34c3SPhil Shafer void
139242ff34c3SPhil Shafer xo_retain_clear_all (void)
139342ff34c3SPhil Shafer {
139442ff34c3SPhil Shafer     int i;
139542ff34c3SPhil Shafer     xo_retain_entry_t *xrep, *next;
139642ff34c3SPhil Shafer 
139742ff34c3SPhil Shafer     for (i = 0; i < RETAIN_HASH_SIZE; i++) {
139842ff34c3SPhil Shafer 	for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
139942ff34c3SPhil Shafer 	    next = xrep->xre_next;
140042ff34c3SPhil Shafer 	    xo_free(xrep);
140142ff34c3SPhil Shafer 	}
140242ff34c3SPhil Shafer 	xo_retain.xr_bucket[i] = NULL;
140342ff34c3SPhil Shafer     }
140442ff34c3SPhil Shafer     xo_retain_count = 0;
140542ff34c3SPhil Shafer }
140642ff34c3SPhil Shafer 
140742ff34c3SPhil Shafer /*
140842ff34c3SPhil Shafer  * Walk all buckets, clearing all retained entries
140942ff34c3SPhil Shafer  */
141042ff34c3SPhil Shafer void
141142ff34c3SPhil Shafer xo_retain_clear (const char *fmt)
141242ff34c3SPhil Shafer {
141342ff34c3SPhil Shafer     xo_retain_entry_t **xrepp;
141442ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
141542ff34c3SPhil Shafer 
141642ff34c3SPhil Shafer     for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
141742ff34c3SPhil Shafer 	 xrepp = &(*xrepp)->xre_next) {
141842ff34c3SPhil Shafer 	if ((*xrepp)->xre_format == fmt) {
141942ff34c3SPhil Shafer 	    *xrepp = (*xrepp)->xre_next;
142042ff34c3SPhil Shafer 	    xo_retain_count -= 1;
142142ff34c3SPhil Shafer 	    return;
142242ff34c3SPhil Shafer 	}
142342ff34c3SPhil Shafer     }
142442ff34c3SPhil Shafer }
142542ff34c3SPhil Shafer 
142642ff34c3SPhil Shafer /*
142742ff34c3SPhil Shafer  * Search the hash for an entry matching 'fmt'; return it's fields.
142842ff34c3SPhil Shafer  */
142942ff34c3SPhil Shafer static int
143042ff34c3SPhil Shafer xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
143142ff34c3SPhil Shafer {
143242ff34c3SPhil Shafer     if (xo_retain_count == 0)
143342ff34c3SPhil Shafer 	return -1;
143442ff34c3SPhil Shafer 
143542ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
143642ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
143742ff34c3SPhil Shafer 
143842ff34c3SPhil Shafer     for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
143942ff34c3SPhil Shafer 	 xrep = xrep->xre_next) {
144042ff34c3SPhil Shafer 	if (xrep->xre_format == fmt) {
144142ff34c3SPhil Shafer 	    *valp = xrep->xre_fields;
144242ff34c3SPhil Shafer 	    *nump = xrep->xre_num_fields;
144342ff34c3SPhil Shafer 	    xrep->xre_hits += 1;
144442ff34c3SPhil Shafer 	    return 0;
144542ff34c3SPhil Shafer 	}
144642ff34c3SPhil Shafer     }
144742ff34c3SPhil Shafer 
144842ff34c3SPhil Shafer     return -1;
144942ff34c3SPhil Shafer }
145042ff34c3SPhil Shafer 
145142ff34c3SPhil Shafer static void
145242ff34c3SPhil Shafer xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
145342ff34c3SPhil Shafer {
145442ff34c3SPhil Shafer     unsigned hash = xo_retain_hash(fmt);
145542ff34c3SPhil Shafer     xo_retain_entry_t *xrep;
14568a6eceffSPhil Shafer     ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
145742ff34c3SPhil Shafer     xo_field_info_t *xfip;
145842ff34c3SPhil Shafer 
145942ff34c3SPhil Shafer     xrep = xo_realloc(NULL, sz);
146042ff34c3SPhil Shafer     if (xrep == NULL)
146142ff34c3SPhil Shafer 	return;
146242ff34c3SPhil Shafer 
146342ff34c3SPhil Shafer     xfip = (xo_field_info_t *) &xrep[1];
146442ff34c3SPhil Shafer     memcpy(xfip, fields, num_fields * sizeof(*fields));
146542ff34c3SPhil Shafer 
146642ff34c3SPhil Shafer     bzero(xrep, sizeof(*xrep));
146742ff34c3SPhil Shafer 
146842ff34c3SPhil Shafer     xrep->xre_format = fmt;
146942ff34c3SPhil Shafer     xrep->xre_fields = xfip;
147042ff34c3SPhil Shafer     xrep->xre_num_fields = num_fields;
147142ff34c3SPhil Shafer 
147242ff34c3SPhil Shafer     /* Record the field info in the retain bucket */
147342ff34c3SPhil Shafer     xrep->xre_next = xo_retain.xr_bucket[hash];
147442ff34c3SPhil Shafer     xo_retain.xr_bucket[hash] = xrep;
147542ff34c3SPhil Shafer     xo_retain_count += 1;
147642ff34c3SPhil Shafer }
147742ff34c3SPhil Shafer 
147842ff34c3SPhil Shafer #endif /* !LIBXO_NO_RETAIN */
147942ff34c3SPhil Shafer 
148031337658SMarcel Moolenaar /*
148131337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
148231337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
148331337658SMarcel Moolenaar  * XMLified content on standard output.
148431337658SMarcel Moolenaar  */
148531337658SMarcel Moolenaar static void
148631337658SMarcel Moolenaar xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
148731337658SMarcel Moolenaar 	     const char *fmt, va_list vap)
148831337658SMarcel Moolenaar {
148931337658SMarcel Moolenaar     xop = xo_default(xop);
1490d1a0d267SMarcel Moolenaar     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
149131337658SMarcel Moolenaar 	return;
149231337658SMarcel Moolenaar 
149331337658SMarcel Moolenaar     if (fmt == NULL)
149431337658SMarcel Moolenaar 	return;
149531337658SMarcel Moolenaar 
14968a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
14978a6eceffSPhil Shafer     ssize_t plen = xo_program ? strlen(xo_program) : 0;
1498545ddfbeSMarcel Moolenaar     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
149931337658SMarcel Moolenaar 
150031337658SMarcel Moolenaar     if (plen) {
150131337658SMarcel Moolenaar 	memcpy(newfmt, xo_program, plen);
150231337658SMarcel Moolenaar 	newfmt[plen++] = ':';
150331337658SMarcel Moolenaar 	newfmt[plen++] = ' ';
150431337658SMarcel Moolenaar     }
150531337658SMarcel Moolenaar     memcpy(newfmt + plen, fmt, len);
150631337658SMarcel Moolenaar     newfmt[len + plen] = '\0';
150731337658SMarcel Moolenaar 
1508d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_WARN_XML)) {
150931337658SMarcel Moolenaar 	static char err_open[] = "<error>";
151031337658SMarcel Moolenaar 	static char err_close[] = "</error>";
151131337658SMarcel Moolenaar 	static char msg_open[] = "<message>";
151231337658SMarcel Moolenaar 	static char msg_close[] = "</message>";
151331337658SMarcel Moolenaar 
151431337658SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_data;
151531337658SMarcel Moolenaar 
151631337658SMarcel Moolenaar 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
151731337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
151831337658SMarcel Moolenaar 
151931337658SMarcel Moolenaar 	va_list va_local;
152031337658SMarcel Moolenaar 	va_copy(va_local, vap);
152131337658SMarcel Moolenaar 
15228a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
15238a6eceffSPhil Shafer 	ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1524d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1525c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1526c600d307SMarcel Moolenaar 		va_end(va_local);
152731337658SMarcel Moolenaar 		return;
1528c600d307SMarcel Moolenaar 	    }
152931337658SMarcel Moolenaar 
153031337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
153131337658SMarcel Moolenaar 	    va_copy(vap, va_local);
153231337658SMarcel Moolenaar 
153331337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
153431337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
153531337658SMarcel Moolenaar 	}
153631337658SMarcel Moolenaar 	va_end(va_local);
153731337658SMarcel Moolenaar 
153831337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 1);
153931337658SMarcel Moolenaar 	xbp->xb_curp += rc;
154031337658SMarcel Moolenaar 
154131337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
154231337658SMarcel Moolenaar 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
154331337658SMarcel Moolenaar 
1544545ddfbeSMarcel Moolenaar 	if (code >= 0) {
154531337658SMarcel Moolenaar 	    const char *msg = strerror(code);
154631337658SMarcel Moolenaar 	    if (msg) {
154731337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
154831337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
154931337658SMarcel Moolenaar 	    }
155031337658SMarcel Moolenaar 	}
155131337658SMarcel Moolenaar 
1552d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1553545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
155431337658SMarcel Moolenaar 
155531337658SMarcel Moolenaar     } else {
155631337658SMarcel Moolenaar 	vfprintf(stderr, newfmt, vap);
1557545ddfbeSMarcel Moolenaar 	if (code >= 0) {
1558545ddfbeSMarcel Moolenaar 	    const char *msg = strerror(code);
1559545ddfbeSMarcel Moolenaar 	    if (msg)
1560545ddfbeSMarcel Moolenaar 		fprintf(stderr, ": %s", msg);
1561545ddfbeSMarcel Moolenaar 	}
1562545ddfbeSMarcel Moolenaar 	fprintf(stderr, "\n");
156331337658SMarcel Moolenaar     }
156431337658SMarcel Moolenaar }
156531337658SMarcel Moolenaar 
156631337658SMarcel Moolenaar void
156731337658SMarcel Moolenaar xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
156831337658SMarcel Moolenaar {
156931337658SMarcel Moolenaar     va_list vap;
157031337658SMarcel Moolenaar 
157131337658SMarcel Moolenaar     va_start(vap, fmt);
157231337658SMarcel Moolenaar     xo_warn_hcv(xop, code, 0, fmt, vap);
157331337658SMarcel Moolenaar     va_end(vap);
157431337658SMarcel Moolenaar }
157531337658SMarcel Moolenaar 
157631337658SMarcel Moolenaar void
157731337658SMarcel Moolenaar xo_warn_c (int code, const char *fmt, ...)
157831337658SMarcel Moolenaar {
157931337658SMarcel Moolenaar     va_list vap;
158031337658SMarcel Moolenaar 
158131337658SMarcel Moolenaar     va_start(vap, fmt);
1582545ddfbeSMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
158331337658SMarcel Moolenaar     va_end(vap);
158431337658SMarcel Moolenaar }
158531337658SMarcel Moolenaar 
158631337658SMarcel Moolenaar void
158731337658SMarcel Moolenaar xo_warn (const char *fmt, ...)
158831337658SMarcel Moolenaar {
158931337658SMarcel Moolenaar     int code = errno;
159031337658SMarcel Moolenaar     va_list vap;
159131337658SMarcel Moolenaar 
159231337658SMarcel Moolenaar     va_start(vap, fmt);
159331337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
159431337658SMarcel Moolenaar     va_end(vap);
159531337658SMarcel Moolenaar }
159631337658SMarcel Moolenaar 
159731337658SMarcel Moolenaar void
159831337658SMarcel Moolenaar xo_warnx (const char *fmt, ...)
159931337658SMarcel Moolenaar {
160031337658SMarcel Moolenaar     va_list vap;
160131337658SMarcel Moolenaar 
160231337658SMarcel Moolenaar     va_start(vap, fmt);
160331337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
160431337658SMarcel Moolenaar     va_end(vap);
160531337658SMarcel Moolenaar }
160631337658SMarcel Moolenaar 
160731337658SMarcel Moolenaar void
160831337658SMarcel Moolenaar xo_err (int eval, const char *fmt, ...)
160931337658SMarcel Moolenaar {
161031337658SMarcel Moolenaar     int code = errno;
161131337658SMarcel Moolenaar     va_list vap;
161231337658SMarcel Moolenaar 
161331337658SMarcel Moolenaar     va_start(vap, fmt);
161431337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
161531337658SMarcel Moolenaar     va_end(vap);
161631337658SMarcel Moolenaar     xo_finish();
161731337658SMarcel Moolenaar     exit(eval);
161831337658SMarcel Moolenaar }
161931337658SMarcel Moolenaar 
162031337658SMarcel Moolenaar void
162131337658SMarcel Moolenaar xo_errx (int eval, const char *fmt, ...)
162231337658SMarcel Moolenaar {
162331337658SMarcel Moolenaar     va_list vap;
162431337658SMarcel Moolenaar 
162531337658SMarcel Moolenaar     va_start(vap, fmt);
162631337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
162731337658SMarcel Moolenaar     va_end(vap);
162831337658SMarcel Moolenaar     xo_finish();
162931337658SMarcel Moolenaar     exit(eval);
163031337658SMarcel Moolenaar }
163131337658SMarcel Moolenaar 
163231337658SMarcel Moolenaar void
163331337658SMarcel Moolenaar xo_errc (int eval, int code, const char *fmt, ...)
163431337658SMarcel Moolenaar {
163531337658SMarcel Moolenaar     va_list vap;
163631337658SMarcel Moolenaar 
163731337658SMarcel Moolenaar     va_start(vap, fmt);
163831337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
163931337658SMarcel Moolenaar     va_end(vap);
164031337658SMarcel Moolenaar     xo_finish();
164131337658SMarcel Moolenaar     exit(eval);
164231337658SMarcel Moolenaar }
164331337658SMarcel Moolenaar 
164431337658SMarcel Moolenaar /*
164531337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
164631337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
164731337658SMarcel Moolenaar  * XMLified content on standard output.
164831337658SMarcel Moolenaar  */
164931337658SMarcel Moolenaar void
165031337658SMarcel Moolenaar xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
165131337658SMarcel Moolenaar {
165231337658SMarcel Moolenaar     static char msg_open[] = "<message>";
165331337658SMarcel Moolenaar     static char msg_close[] = "</message>";
165431337658SMarcel Moolenaar     xo_buffer_t *xbp;
16558a6eceffSPhil Shafer     ssize_t rc;
165631337658SMarcel Moolenaar     va_list va_local;
165731337658SMarcel Moolenaar 
165831337658SMarcel Moolenaar     xop = xo_default(xop);
165931337658SMarcel Moolenaar 
166031337658SMarcel Moolenaar     if (fmt == NULL || *fmt == '\0')
166131337658SMarcel Moolenaar 	return;
166231337658SMarcel Moolenaar 
166331337658SMarcel Moolenaar     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
166431337658SMarcel Moolenaar 
1665788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
166631337658SMarcel Moolenaar     case XO_STYLE_XML:
166731337658SMarcel Moolenaar 	xbp = &xop->xo_data;
1668d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
166931337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
167031337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
167131337658SMarcel Moolenaar 
167231337658SMarcel Moolenaar 	va_copy(va_local, vap);
167331337658SMarcel Moolenaar 
16748a6eceffSPhil Shafer 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
167531337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1676d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1677c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1678c600d307SMarcel Moolenaar 		va_end(va_local);
167931337658SMarcel Moolenaar 		return;
1680c600d307SMarcel Moolenaar 	    }
168131337658SMarcel Moolenaar 
168231337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
168331337658SMarcel Moolenaar 	    va_copy(vap, va_local);
168431337658SMarcel Moolenaar 
168531337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
168631337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
168731337658SMarcel Moolenaar 	}
168831337658SMarcel Moolenaar 	va_end(va_local);
168931337658SMarcel Moolenaar 
1690d1a0d267SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
169131337658SMarcel Moolenaar 	xbp->xb_curp += rc;
169231337658SMarcel Moolenaar 
169331337658SMarcel Moolenaar 	if (need_nl && code > 0) {
169431337658SMarcel Moolenaar 	    const char *msg = strerror(code);
169531337658SMarcel Moolenaar 	    if (msg) {
169631337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
169731337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
169831337658SMarcel Moolenaar 	    }
169931337658SMarcel Moolenaar 	}
170031337658SMarcel Moolenaar 
170131337658SMarcel Moolenaar 	if (need_nl)
1702d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1703d1a0d267SMarcel Moolenaar 
1704d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1705d1a0d267SMarcel Moolenaar 
1706d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
1707d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1708d1a0d267SMarcel Moolenaar 
1709545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
171031337658SMarcel Moolenaar 	break;
171131337658SMarcel Moolenaar 
171231337658SMarcel Moolenaar     case XO_STYLE_HTML:
171331337658SMarcel Moolenaar 	{
171431337658SMarcel Moolenaar 	    char buf[BUFSIZ], *bp = buf, *cp;
17158a6eceffSPhil Shafer 	    ssize_t bufsiz = sizeof(buf);
17168a6eceffSPhil Shafer 	    ssize_t rc2;
171731337658SMarcel Moolenaar 
171831337658SMarcel Moolenaar 	    va_copy(va_local, vap);
171931337658SMarcel Moolenaar 
1720c600d307SMarcel Moolenaar 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
172131337658SMarcel Moolenaar 	    if (rc > bufsiz) {
172231337658SMarcel Moolenaar 		bufsiz = rc + BUFSIZ;
172331337658SMarcel Moolenaar 		bp = alloca(bufsiz);
172431337658SMarcel Moolenaar 		va_end(va_local);
172531337658SMarcel Moolenaar 		va_copy(va_local, vap);
1726c600d307SMarcel Moolenaar 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
172731337658SMarcel Moolenaar 	    }
1728c600d307SMarcel Moolenaar 	    va_end(va_local);
172931337658SMarcel Moolenaar 	    cp = bp + rc;
173031337658SMarcel Moolenaar 
173131337658SMarcel Moolenaar 	    if (need_nl) {
173231337658SMarcel Moolenaar 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
173331337658SMarcel Moolenaar 			       (code > 0) ? ": " : "",
173431337658SMarcel Moolenaar 			       (code > 0) ? strerror(code) : "");
173531337658SMarcel Moolenaar 		if (rc2 > 0)
173631337658SMarcel Moolenaar 		    rc += rc2;
173731337658SMarcel Moolenaar 	    }
173831337658SMarcel Moolenaar 
173931337658SMarcel Moolenaar 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
174031337658SMarcel Moolenaar 	}
174131337658SMarcel Moolenaar 	break;
174231337658SMarcel Moolenaar 
174331337658SMarcel Moolenaar     case XO_STYLE_JSON:
1744d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
1745d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
1746d1a0d267SMarcel Moolenaar 	/* No means of representing messages */
1747d1a0d267SMarcel Moolenaar 	return;
174831337658SMarcel Moolenaar 
174931337658SMarcel Moolenaar     case XO_STYLE_TEXT:
175031337658SMarcel Moolenaar 	rc = xo_printf_v(xop, fmt, vap);
175131337658SMarcel Moolenaar 	/*
175231337658SMarcel Moolenaar 	 * XXX need to handle UTF-8 widths
175331337658SMarcel Moolenaar 	 */
175431337658SMarcel Moolenaar 	if (rc > 0) {
1755d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
175631337658SMarcel Moolenaar 		xop->xo_columns += rc;
1757d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
175831337658SMarcel Moolenaar 		xop->xo_anchor_columns += rc;
175931337658SMarcel Moolenaar 	}
176031337658SMarcel Moolenaar 
176131337658SMarcel Moolenaar 	if (need_nl && code > 0) {
176231337658SMarcel Moolenaar 	    const char *msg = strerror(code);
176331337658SMarcel Moolenaar 	    if (msg) {
176431337658SMarcel Moolenaar 		xo_printf(xop, ": %s", msg);
176531337658SMarcel Moolenaar 	    }
176631337658SMarcel Moolenaar 	}
176731337658SMarcel Moolenaar 	if (need_nl)
176831337658SMarcel Moolenaar 	    xo_printf(xop, "\n");
176931337658SMarcel Moolenaar 
177031337658SMarcel Moolenaar 	break;
177131337658SMarcel Moolenaar     }
177231337658SMarcel Moolenaar 
177342ff34c3SPhil Shafer     switch (xo_style(xop)) {
177442ff34c3SPhil Shafer     case XO_STYLE_HTML:
177542ff34c3SPhil Shafer 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
177642ff34c3SPhil Shafer 	    static char div_close[] = "</div>";
177742ff34c3SPhil Shafer 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
177842ff34c3SPhil Shafer 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
177942ff34c3SPhil Shafer 
178042ff34c3SPhil Shafer 	    if (XOF_ISSET(xop, XOF_PRETTY))
178142ff34c3SPhil Shafer 		xo_data_append(xop, "\n", 1);
178242ff34c3SPhil Shafer 	}
178342ff34c3SPhil Shafer 	break;
178442ff34c3SPhil Shafer     }
178542ff34c3SPhil Shafer 
1786545ddfbeSMarcel Moolenaar     (void) xo_flush_h(xop);
178731337658SMarcel Moolenaar }
178831337658SMarcel Moolenaar 
178931337658SMarcel Moolenaar void
179031337658SMarcel Moolenaar xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
179131337658SMarcel Moolenaar {
179231337658SMarcel Moolenaar     va_list vap;
179331337658SMarcel Moolenaar 
179431337658SMarcel Moolenaar     va_start(vap, fmt);
179531337658SMarcel Moolenaar     xo_message_hcv(xop, code, fmt, vap);
179631337658SMarcel Moolenaar     va_end(vap);
179731337658SMarcel Moolenaar }
179831337658SMarcel Moolenaar 
179931337658SMarcel Moolenaar void
180031337658SMarcel Moolenaar xo_message_c (int code, const char *fmt, ...)
180131337658SMarcel Moolenaar {
180231337658SMarcel Moolenaar     va_list vap;
180331337658SMarcel Moolenaar 
180431337658SMarcel Moolenaar     va_start(vap, fmt);
180531337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
180631337658SMarcel Moolenaar     va_end(vap);
180731337658SMarcel Moolenaar }
180831337658SMarcel Moolenaar 
180931337658SMarcel Moolenaar void
1810d1a0d267SMarcel Moolenaar xo_message_e (const char *fmt, ...)
181131337658SMarcel Moolenaar {
181231337658SMarcel Moolenaar     int code = errno;
181331337658SMarcel Moolenaar     va_list vap;
181431337658SMarcel Moolenaar 
181531337658SMarcel Moolenaar     va_start(vap, fmt);
181631337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
181731337658SMarcel Moolenaar     va_end(vap);
181831337658SMarcel Moolenaar }
181931337658SMarcel Moolenaar 
1820d1a0d267SMarcel Moolenaar void
1821d1a0d267SMarcel Moolenaar xo_message (const char *fmt, ...)
1822d1a0d267SMarcel Moolenaar {
1823d1a0d267SMarcel Moolenaar     va_list vap;
1824d1a0d267SMarcel Moolenaar 
1825d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
1826d1a0d267SMarcel Moolenaar     xo_message_hcv(NULL, 0, fmt, vap);
1827d1a0d267SMarcel Moolenaar     va_end(vap);
1828d1a0d267SMarcel Moolenaar }
1829d1a0d267SMarcel Moolenaar 
183031337658SMarcel Moolenaar static void
183131337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...)
183231337658SMarcel Moolenaar {
1833d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_WARN))
183431337658SMarcel Moolenaar 	return;
183531337658SMarcel Moolenaar 
183631337658SMarcel Moolenaar     va_list vap;
183731337658SMarcel Moolenaar 
183831337658SMarcel Moolenaar     va_start(vap, fmt);
183931337658SMarcel Moolenaar     xo_warn_hcv(xop, -1, 1, fmt, vap);
184031337658SMarcel Moolenaar     va_end(vap);
184131337658SMarcel Moolenaar }
184231337658SMarcel Moolenaar 
184331337658SMarcel Moolenaar /**
184431337658SMarcel Moolenaar  * Create a handle for use by later libxo functions.
184531337658SMarcel Moolenaar  *
184631337658SMarcel Moolenaar  * Note: normal use of libxo does not require a distinct handle, since
184731337658SMarcel Moolenaar  * the default handle (used when NULL is passed) generates text on stdout.
184831337658SMarcel Moolenaar  *
184931337658SMarcel Moolenaar  * @style Style of output desired (XO_STYLE_* value)
185031337658SMarcel Moolenaar  * @flags Set of XOF_* flags in use with this handle
185131337658SMarcel Moolenaar  */
185231337658SMarcel Moolenaar xo_handle_t *
185331337658SMarcel Moolenaar xo_create (xo_style_t style, xo_xof_flags_t flags)
185431337658SMarcel Moolenaar {
185531337658SMarcel Moolenaar     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
185631337658SMarcel Moolenaar 
185731337658SMarcel Moolenaar     if (xop) {
185831337658SMarcel Moolenaar 	bzero(xop, sizeof(*xop));
185931337658SMarcel Moolenaar 
186031337658SMarcel Moolenaar 	xop->xo_style = style;
1861d1a0d267SMarcel Moolenaar 	XOF_SET(xop, flags);
186231337658SMarcel Moolenaar 	xo_init_handle(xop);
1863d1a0d267SMarcel Moolenaar 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
186431337658SMarcel Moolenaar     }
186531337658SMarcel Moolenaar 
186631337658SMarcel Moolenaar     return xop;
186731337658SMarcel Moolenaar }
186831337658SMarcel Moolenaar 
186931337658SMarcel Moolenaar /**
187031337658SMarcel Moolenaar  * Create a handle that will write to the given file.  Use
187131337658SMarcel Moolenaar  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
187231337658SMarcel Moolenaar  * @fp FILE pointer to use
187331337658SMarcel Moolenaar  * @style Style of output desired (XO_STYLE_* value)
187431337658SMarcel Moolenaar  * @flags Set of XOF_* flags to use with this handle
187531337658SMarcel Moolenaar  */
187631337658SMarcel Moolenaar xo_handle_t *
187731337658SMarcel Moolenaar xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
187831337658SMarcel Moolenaar {
187931337658SMarcel Moolenaar     xo_handle_t *xop = xo_create(style, flags);
188031337658SMarcel Moolenaar 
188131337658SMarcel Moolenaar     if (xop) {
188231337658SMarcel Moolenaar 	xop->xo_opaque = fp;
188331337658SMarcel Moolenaar 	xop->xo_write = xo_write_to_file;
188431337658SMarcel Moolenaar 	xop->xo_close = xo_close_file;
1885545ddfbeSMarcel Moolenaar 	xop->xo_flush = xo_flush_file;
188631337658SMarcel Moolenaar     }
188731337658SMarcel Moolenaar 
188831337658SMarcel Moolenaar     return xop;
188931337658SMarcel Moolenaar }
189031337658SMarcel Moolenaar 
189131337658SMarcel Moolenaar /**
189242ff34c3SPhil Shafer  * Set the default handler to output to a file.
189342ff34c3SPhil Shafer  * @xop libxo handle
189442ff34c3SPhil Shafer  * @fp FILE pointer to use
189542ff34c3SPhil Shafer  */
189642ff34c3SPhil Shafer int
189742ff34c3SPhil Shafer xo_set_file_h (xo_handle_t *xop, FILE *fp)
189842ff34c3SPhil Shafer {
189942ff34c3SPhil Shafer     xop = xo_default(xop);
190042ff34c3SPhil Shafer 
190142ff34c3SPhil Shafer     if (fp == NULL) {
190242ff34c3SPhil Shafer 	xo_failure(xop, "xo_set_file: NULL fp");
190342ff34c3SPhil Shafer 	return -1;
190442ff34c3SPhil Shafer     }
190542ff34c3SPhil Shafer 
190642ff34c3SPhil Shafer     xop->xo_opaque = fp;
190742ff34c3SPhil Shafer     xop->xo_write = xo_write_to_file;
190842ff34c3SPhil Shafer     xop->xo_close = xo_close_file;
190942ff34c3SPhil Shafer     xop->xo_flush = xo_flush_file;
191042ff34c3SPhil Shafer 
191142ff34c3SPhil Shafer     return 0;
191242ff34c3SPhil Shafer }
191342ff34c3SPhil Shafer 
191442ff34c3SPhil Shafer /**
191542ff34c3SPhil Shafer  * Set the default handler to output to a file.
191642ff34c3SPhil Shafer  * @fp FILE pointer to use
191742ff34c3SPhil Shafer  */
191842ff34c3SPhil Shafer int
191942ff34c3SPhil Shafer xo_set_file (FILE *fp)
192042ff34c3SPhil Shafer {
192142ff34c3SPhil Shafer     return xo_set_file_h(NULL, fp);
192242ff34c3SPhil Shafer }
192342ff34c3SPhil Shafer 
192442ff34c3SPhil Shafer /**
192531337658SMarcel Moolenaar  * Release any resources held by the handle.
192631337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
192731337658SMarcel Moolenaar  */
192831337658SMarcel Moolenaar void
1929c600d307SMarcel Moolenaar xo_destroy (xo_handle_t *xop_arg)
193031337658SMarcel Moolenaar {
1931c600d307SMarcel Moolenaar     xo_handle_t *xop = xo_default(xop_arg);
193231337658SMarcel Moolenaar 
1933d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
1934d1a0d267SMarcel Moolenaar 
1935d1a0d267SMarcel Moolenaar     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
193631337658SMarcel Moolenaar 	xop->xo_close(xop->xo_opaque);
193731337658SMarcel Moolenaar 
193831337658SMarcel Moolenaar     xo_free(xop->xo_stack);
193931337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_data);
194031337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_fmt);
194131337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_predicate);
194231337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_attrs);
1943788ca347SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_color_buf);
1944788ca347SMarcel Moolenaar 
1945788ca347SMarcel Moolenaar     if (xop->xo_version)
1946788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
194731337658SMarcel Moolenaar 
1948c600d307SMarcel Moolenaar     if (xop_arg == NULL) {
1949545ddfbeSMarcel Moolenaar 	bzero(&xo_default_handle, sizeof(xo_default_handle));
195031337658SMarcel Moolenaar 	xo_default_inited = 0;
195131337658SMarcel Moolenaar     } else
195231337658SMarcel Moolenaar 	xo_free(xop);
195331337658SMarcel Moolenaar }
195431337658SMarcel Moolenaar 
195531337658SMarcel Moolenaar /**
195631337658SMarcel Moolenaar  * Record a new output style to use for the given handle (or default if
195731337658SMarcel Moolenaar  * handle is NULL).  This output style will be used for any future output.
195831337658SMarcel Moolenaar  *
195931337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
196031337658SMarcel Moolenaar  * @style new output style (XO_STYLE_*)
196131337658SMarcel Moolenaar  */
196231337658SMarcel Moolenaar void
196331337658SMarcel Moolenaar xo_set_style (xo_handle_t *xop, xo_style_t style)
196431337658SMarcel Moolenaar {
196531337658SMarcel Moolenaar     xop = xo_default(xop);
196631337658SMarcel Moolenaar     xop->xo_style = style;
196731337658SMarcel Moolenaar }
196831337658SMarcel Moolenaar 
196931337658SMarcel Moolenaar xo_style_t
197031337658SMarcel Moolenaar xo_get_style (xo_handle_t *xop)
197131337658SMarcel Moolenaar {
197231337658SMarcel Moolenaar     xop = xo_default(xop);
1973788ca347SMarcel Moolenaar     return xo_style(xop);
197431337658SMarcel Moolenaar }
197531337658SMarcel Moolenaar 
197631337658SMarcel Moolenaar static int
197731337658SMarcel Moolenaar xo_name_to_style (const char *name)
197831337658SMarcel Moolenaar {
197931337658SMarcel Moolenaar     if (strcmp(name, "xml") == 0)
198031337658SMarcel Moolenaar 	return XO_STYLE_XML;
198131337658SMarcel Moolenaar     else if (strcmp(name, "json") == 0)
198231337658SMarcel Moolenaar 	return XO_STYLE_JSON;
1983d1a0d267SMarcel Moolenaar     else if (strcmp(name, "encoder") == 0)
1984d1a0d267SMarcel Moolenaar 	return XO_STYLE_ENCODER;
198531337658SMarcel Moolenaar     else if (strcmp(name, "text") == 0)
198631337658SMarcel Moolenaar 	return XO_STYLE_TEXT;
198731337658SMarcel Moolenaar     else if (strcmp(name, "html") == 0)
198831337658SMarcel Moolenaar 	return XO_STYLE_HTML;
1989d1a0d267SMarcel Moolenaar     else if (strcmp(name, "sdparams") == 0)
1990d1a0d267SMarcel Moolenaar 	return XO_STYLE_SDPARAMS;
199131337658SMarcel Moolenaar 
199231337658SMarcel Moolenaar     return -1;
199331337658SMarcel Moolenaar }
199431337658SMarcel Moolenaar 
199531337658SMarcel Moolenaar /*
1996d1a0d267SMarcel Moolenaar  * Indicate if the style is an "encoding" one as opposed to a "display" one.
1997d1a0d267SMarcel Moolenaar  */
1998d1a0d267SMarcel Moolenaar static int
1999d1a0d267SMarcel Moolenaar xo_style_is_encoding (xo_handle_t *xop)
2000d1a0d267SMarcel Moolenaar {
2001d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_JSON
2002d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_XML
2003d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_SDPARAMS
2004d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_ENCODER)
2005d1a0d267SMarcel Moolenaar 	return 1;
2006d1a0d267SMarcel Moolenaar     return 0;
2007d1a0d267SMarcel Moolenaar }
2008d1a0d267SMarcel Moolenaar 
2009d1a0d267SMarcel Moolenaar /* Simple name-value mapping */
2010d1a0d267SMarcel Moolenaar typedef struct xo_mapping_s {
2011d1a0d267SMarcel Moolenaar     xo_xff_flags_t xm_value;
2012d1a0d267SMarcel Moolenaar     const char *xm_name;
2013d1a0d267SMarcel Moolenaar } xo_mapping_t;
2014d1a0d267SMarcel Moolenaar 
2015d1a0d267SMarcel Moolenaar static xo_xff_flags_t
20168a6eceffSPhil Shafer xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len)
2017d1a0d267SMarcel Moolenaar {
2018d1a0d267SMarcel Moolenaar     if (len == 0)
2019d1a0d267SMarcel Moolenaar 	return 0;
2020d1a0d267SMarcel Moolenaar 
2021d1a0d267SMarcel Moolenaar     if (len < 0)
2022d1a0d267SMarcel Moolenaar 	len = strlen(value);
2023d1a0d267SMarcel Moolenaar 
2024d1a0d267SMarcel Moolenaar     while (isspace((int) *value)) {
2025d1a0d267SMarcel Moolenaar 	value += 1;
2026d1a0d267SMarcel Moolenaar 	len -= 1;
2027d1a0d267SMarcel Moolenaar     }
2028d1a0d267SMarcel Moolenaar 
2029d1a0d267SMarcel Moolenaar     while (isspace((int) value[len]))
2030d1a0d267SMarcel Moolenaar 	len -= 1;
2031d1a0d267SMarcel Moolenaar 
2032d1a0d267SMarcel Moolenaar     if (*value == '\0')
2033d1a0d267SMarcel Moolenaar 	return 0;
2034d1a0d267SMarcel Moolenaar 
2035d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2036d1a0d267SMarcel Moolenaar 	if (strncmp(map->xm_name, value, len) == 0)
2037d1a0d267SMarcel Moolenaar 	    return map->xm_value;
2038d1a0d267SMarcel Moolenaar 
2039d1a0d267SMarcel Moolenaar     return 0;
2040d1a0d267SMarcel Moolenaar }
2041d1a0d267SMarcel Moolenaar 
2042d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
2043d1a0d267SMarcel Moolenaar static const char *
2044d1a0d267SMarcel Moolenaar xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2045d1a0d267SMarcel Moolenaar {
2046d1a0d267SMarcel Moolenaar     if (value == 0)
2047d1a0d267SMarcel Moolenaar 	return NULL;
2048d1a0d267SMarcel Moolenaar 
2049d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
2050d1a0d267SMarcel Moolenaar 	if (map->xm_value == value)
2051d1a0d267SMarcel Moolenaar 	    return map->xm_name;
2052d1a0d267SMarcel Moolenaar 
2053d1a0d267SMarcel Moolenaar     return NULL;
2054d1a0d267SMarcel Moolenaar }
2055d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
2056d1a0d267SMarcel Moolenaar 
2057d1a0d267SMarcel Moolenaar static xo_mapping_t xo_xof_names[] = {
2058d1a0d267SMarcel Moolenaar     { XOF_COLOR_ALLOWED, "color" },
2059d1a0d267SMarcel Moolenaar     { XOF_COLUMNS, "columns" },
2060d1a0d267SMarcel Moolenaar     { XOF_DTRT, "dtrt" },
2061d1a0d267SMarcel Moolenaar     { XOF_FLUSH, "flush" },
20628a6eceffSPhil Shafer     { XOF_FLUSH_LINE, "flush-line" },
2063d1a0d267SMarcel Moolenaar     { XOF_IGNORE_CLOSE, "ignore-close" },
2064d1a0d267SMarcel Moolenaar     { XOF_INFO, "info" },
2065d1a0d267SMarcel Moolenaar     { XOF_KEYS, "keys" },
2066d1a0d267SMarcel Moolenaar     { XOF_LOG_GETTEXT, "log-gettext" },
2067d1a0d267SMarcel Moolenaar     { XOF_LOG_SYSLOG, "log-syslog" },
2068d1a0d267SMarcel Moolenaar     { XOF_NO_HUMANIZE, "no-humanize" },
2069d1a0d267SMarcel Moolenaar     { XOF_NO_LOCALE, "no-locale" },
207042ff34c3SPhil Shafer     { XOF_RETAIN_NONE, "no-retain" },
2071d1a0d267SMarcel Moolenaar     { XOF_NO_TOP, "no-top" },
2072d1a0d267SMarcel Moolenaar     { XOF_NOT_FIRST, "not-first" },
2073d1a0d267SMarcel Moolenaar     { XOF_PRETTY, "pretty" },
207442ff34c3SPhil Shafer     { XOF_RETAIN_ALL, "retain" },
2075d1a0d267SMarcel Moolenaar     { XOF_UNDERSCORES, "underscores" },
2076d1a0d267SMarcel Moolenaar     { XOF_UNITS, "units" },
2077d1a0d267SMarcel Moolenaar     { XOF_WARN, "warn" },
2078d1a0d267SMarcel Moolenaar     { XOF_WARN_XML, "warn-xml" },
2079d1a0d267SMarcel Moolenaar     { XOF_XPATH, "xpath" },
2080d1a0d267SMarcel Moolenaar     { 0, NULL }
2081d1a0d267SMarcel Moolenaar };
2082d1a0d267SMarcel Moolenaar 
2083d1a0d267SMarcel Moolenaar /*
208431337658SMarcel Moolenaar  * Convert string name to XOF_* flag value.
208531337658SMarcel Moolenaar  * Not all are useful.  Or safe.  Or sane.
208631337658SMarcel Moolenaar  */
208731337658SMarcel Moolenaar static unsigned
208831337658SMarcel Moolenaar xo_name_to_flag (const char *name)
208931337658SMarcel Moolenaar {
2090d1a0d267SMarcel Moolenaar     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
209131337658SMarcel Moolenaar }
209231337658SMarcel Moolenaar 
209331337658SMarcel Moolenaar int
209431337658SMarcel Moolenaar xo_set_style_name (xo_handle_t *xop, const char *name)
209531337658SMarcel Moolenaar {
209631337658SMarcel Moolenaar     if (name == NULL)
209731337658SMarcel Moolenaar 	return -1;
209831337658SMarcel Moolenaar 
209931337658SMarcel Moolenaar     int style = xo_name_to_style(name);
210031337658SMarcel Moolenaar     if (style < 0)
210131337658SMarcel Moolenaar 	return -1;
210231337658SMarcel Moolenaar 
210331337658SMarcel Moolenaar     xo_set_style(xop, style);
210431337658SMarcel Moolenaar     return 0;
210531337658SMarcel Moolenaar }
210631337658SMarcel Moolenaar 
210731337658SMarcel Moolenaar /*
210831337658SMarcel Moolenaar  * Set the options for a handle using a string of options
210931337658SMarcel Moolenaar  * passed in.  The input is a comma-separated set of names
211031337658SMarcel Moolenaar  * and optional values: "xml,pretty,indent=4"
211131337658SMarcel Moolenaar  */
211231337658SMarcel Moolenaar int
211331337658SMarcel Moolenaar xo_set_options (xo_handle_t *xop, const char *input)
211431337658SMarcel Moolenaar {
211531337658SMarcel Moolenaar     char *cp, *ep, *vp, *np, *bp;
21168a6eceffSPhil Shafer     int style = -1, new_style, rc = 0;
21178a6eceffSPhil Shafer     ssize_t len;
211831337658SMarcel Moolenaar     xo_xof_flags_t new_flag;
211931337658SMarcel Moolenaar 
212031337658SMarcel Moolenaar     if (input == NULL)
212131337658SMarcel Moolenaar 	return 0;
212231337658SMarcel Moolenaar 
212331337658SMarcel Moolenaar     xop = xo_default(xop);
212431337658SMarcel Moolenaar 
2125788ca347SMarcel Moolenaar #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2126788ca347SMarcel Moolenaar     /* If the installer used --enable-color-on-by-default, then we allow it */
2127d1a0d267SMarcel Moolenaar     XOF_SET(xop, XOF_COLOR_ALLOWED);
2128788ca347SMarcel Moolenaar #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2129788ca347SMarcel Moolenaar 
213031337658SMarcel Moolenaar     /*
213131337658SMarcel Moolenaar      * We support a simpler, old-school style of giving option
213231337658SMarcel Moolenaar      * also, using a single character for each option.  It's
213331337658SMarcel Moolenaar      * ideal for lazy people, such as myself.
213431337658SMarcel Moolenaar      */
213531337658SMarcel Moolenaar     if (*input == ':') {
21368a6eceffSPhil Shafer 	ssize_t sz;
213731337658SMarcel Moolenaar 
213831337658SMarcel Moolenaar 	for (input++ ; *input; input++) {
213931337658SMarcel Moolenaar 	    switch (*input) {
2140788ca347SMarcel Moolenaar 	    case 'c':
2141d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_COLOR_ALLOWED);
2142788ca347SMarcel Moolenaar 		break;
2143788ca347SMarcel Moolenaar 
214431337658SMarcel Moolenaar 	    case 'f':
2145d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH);
214631337658SMarcel Moolenaar 		break;
214731337658SMarcel Moolenaar 
2148545ddfbeSMarcel Moolenaar 	    case 'F':
2149d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH_LINE);
2150d1a0d267SMarcel Moolenaar 		break;
2151d1a0d267SMarcel Moolenaar 
2152d1a0d267SMarcel Moolenaar 	    case 'g':
2153d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_LOG_GETTEXT);
2154545ddfbeSMarcel Moolenaar 		break;
2155545ddfbeSMarcel Moolenaar 
215631337658SMarcel Moolenaar 	    case 'H':
215731337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_HTML;
215831337658SMarcel Moolenaar 		break;
215931337658SMarcel Moolenaar 
216031337658SMarcel Moolenaar 	    case 'I':
2161d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_INFO);
216231337658SMarcel Moolenaar 		break;
216331337658SMarcel Moolenaar 
216431337658SMarcel Moolenaar 	    case 'i':
216531337658SMarcel Moolenaar 		sz = strspn(input + 1, "0123456789");
216631337658SMarcel Moolenaar 		if (sz > 0) {
216731337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(input + 1);
216831337658SMarcel Moolenaar 		    input += sz - 1;	/* Skip value */
216931337658SMarcel Moolenaar 		}
217031337658SMarcel Moolenaar 		break;
217131337658SMarcel Moolenaar 
217231337658SMarcel Moolenaar 	    case 'J':
217331337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_JSON;
217431337658SMarcel Moolenaar 		break;
217531337658SMarcel Moolenaar 
2176d1a0d267SMarcel Moolenaar 	    case 'k':
2177d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_KEYS);
2178d1a0d267SMarcel Moolenaar 		break;
2179d1a0d267SMarcel Moolenaar 
2180d1a0d267SMarcel Moolenaar 	    case 'n':
2181d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_NO_HUMANIZE);
2182d1a0d267SMarcel Moolenaar 		break;
2183d1a0d267SMarcel Moolenaar 
218431337658SMarcel Moolenaar 	    case 'P':
2185d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_PRETTY);
218631337658SMarcel Moolenaar 		break;
218731337658SMarcel Moolenaar 
218831337658SMarcel Moolenaar 	    case 'T':
218931337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_TEXT;
219031337658SMarcel Moolenaar 		break;
219131337658SMarcel Moolenaar 
219231337658SMarcel Moolenaar 	    case 'U':
2193d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNITS);
219431337658SMarcel Moolenaar 		break;
219531337658SMarcel Moolenaar 
219631337658SMarcel Moolenaar 	    case 'u':
2197d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNDERSCORES);
219831337658SMarcel Moolenaar 		break;
219931337658SMarcel Moolenaar 
220031337658SMarcel Moolenaar 	    case 'W':
2201d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_WARN);
220231337658SMarcel Moolenaar 		break;
220331337658SMarcel Moolenaar 
220431337658SMarcel Moolenaar 	    case 'X':
220531337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_XML;
220631337658SMarcel Moolenaar 		break;
220731337658SMarcel Moolenaar 
220831337658SMarcel Moolenaar 	    case 'x':
2209d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_XPATH);
221031337658SMarcel Moolenaar 		break;
221131337658SMarcel Moolenaar 	    }
221231337658SMarcel Moolenaar 	}
221331337658SMarcel Moolenaar 	return 0;
221431337658SMarcel Moolenaar     }
221531337658SMarcel Moolenaar 
221631337658SMarcel Moolenaar     len = strlen(input) + 1;
221731337658SMarcel Moolenaar     bp = alloca(len);
221831337658SMarcel Moolenaar     memcpy(bp, input, len);
221931337658SMarcel Moolenaar 
222031337658SMarcel Moolenaar     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
222131337658SMarcel Moolenaar 	np = strchr(cp, ',');
222231337658SMarcel Moolenaar 	if (np)
222331337658SMarcel Moolenaar 	    *np++ = '\0';
222431337658SMarcel Moolenaar 
222531337658SMarcel Moolenaar 	vp = strchr(cp, '=');
222631337658SMarcel Moolenaar 	if (vp)
222731337658SMarcel Moolenaar 	    *vp++ = '\0';
222831337658SMarcel Moolenaar 
2229788ca347SMarcel Moolenaar 	if (strcmp("colors", cp) == 0) {
2230788ca347SMarcel Moolenaar 	    /* XXX Look for colors=red-blue+green-yellow */
2231788ca347SMarcel Moolenaar 	    continue;
2232788ca347SMarcel Moolenaar 	}
2233788ca347SMarcel Moolenaar 
2234d1a0d267SMarcel Moolenaar 	/*
2235d1a0d267SMarcel Moolenaar 	 * For options, we don't allow "encoder" since we want to
2236d1a0d267SMarcel Moolenaar 	 * handle it explicitly below as "encoder=xxx".
2237d1a0d267SMarcel Moolenaar 	 */
223831337658SMarcel Moolenaar 	new_style = xo_name_to_style(cp);
2239d1a0d267SMarcel Moolenaar 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
224031337658SMarcel Moolenaar 	    if (style >= 0)
224131337658SMarcel Moolenaar 		xo_warnx("ignoring multiple styles: '%s'", cp);
224231337658SMarcel Moolenaar 	    else
224331337658SMarcel Moolenaar 		style = new_style;
224431337658SMarcel Moolenaar 	} else {
224531337658SMarcel Moolenaar 	    new_flag = xo_name_to_flag(cp);
224631337658SMarcel Moolenaar 	    if (new_flag != 0)
2247d1a0d267SMarcel Moolenaar 		XOF_SET(xop, new_flag);
224831337658SMarcel Moolenaar 	    else {
2249788ca347SMarcel Moolenaar 		if (strcmp(cp, "no-color") == 0) {
2250d1a0d267SMarcel Moolenaar 		    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2251788ca347SMarcel Moolenaar 		} else if (strcmp(cp, "indent") == 0) {
2252d1a0d267SMarcel Moolenaar 		    if (vp)
225331337658SMarcel Moolenaar 			xop->xo_indent_by = atoi(vp);
2254d1a0d267SMarcel Moolenaar 		    else
2255d1a0d267SMarcel Moolenaar 			xo_failure(xop, "missing value for indent option");
2256d1a0d267SMarcel Moolenaar 		} else if (strcmp(cp, "encoder") == 0) {
2257d1a0d267SMarcel Moolenaar 		    if (vp == NULL)
2258d1a0d267SMarcel Moolenaar 			xo_failure(xop, "missing value for encoder option");
2259d1a0d267SMarcel Moolenaar 		    else {
2260d1a0d267SMarcel Moolenaar 			if (xo_encoder_init(xop, vp)) {
2261d1a0d267SMarcel Moolenaar 			    xo_failure(xop, "encoder not found: %s", vp);
2262d1a0d267SMarcel Moolenaar 			    rc = -1;
2263d1a0d267SMarcel Moolenaar 			}
2264d1a0d267SMarcel Moolenaar 		    }
2265d1a0d267SMarcel Moolenaar 
226631337658SMarcel Moolenaar 		} else {
2267d1a0d267SMarcel Moolenaar 		    xo_warnx("unknown libxo option value: '%s'", cp);
226831337658SMarcel Moolenaar 		    rc = -1;
226931337658SMarcel Moolenaar 		}
227031337658SMarcel Moolenaar 	    }
227131337658SMarcel Moolenaar 	}
227231337658SMarcel Moolenaar     }
227331337658SMarcel Moolenaar 
227431337658SMarcel Moolenaar     if (style > 0)
227531337658SMarcel Moolenaar 	xop->xo_style= style;
227631337658SMarcel Moolenaar 
227731337658SMarcel Moolenaar     return rc;
227831337658SMarcel Moolenaar }
227931337658SMarcel Moolenaar 
228031337658SMarcel Moolenaar /**
228131337658SMarcel Moolenaar  * Set one or more flags for a given handle (or default if handle is NULL).
228231337658SMarcel Moolenaar  * These flags will affect future output.
228331337658SMarcel Moolenaar  *
228431337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
228531337658SMarcel Moolenaar  * @flags Flags to be set (XOF_*)
228631337658SMarcel Moolenaar  */
228731337658SMarcel Moolenaar void
228831337658SMarcel Moolenaar xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
228931337658SMarcel Moolenaar {
229031337658SMarcel Moolenaar     xop = xo_default(xop);
229131337658SMarcel Moolenaar 
2292d1a0d267SMarcel Moolenaar     XOF_SET(xop, flags);
229331337658SMarcel Moolenaar }
229431337658SMarcel Moolenaar 
229531337658SMarcel Moolenaar xo_xof_flags_t
229631337658SMarcel Moolenaar xo_get_flags (xo_handle_t *xop)
229731337658SMarcel Moolenaar {
229831337658SMarcel Moolenaar     xop = xo_default(xop);
229931337658SMarcel Moolenaar 
230031337658SMarcel Moolenaar     return xop->xo_flags;
230131337658SMarcel Moolenaar }
230231337658SMarcel Moolenaar 
2303d1a0d267SMarcel Moolenaar /*
2304d1a0d267SMarcel Moolenaar  * strndup with a twist: len < 0 means strlen
2305d1a0d267SMarcel Moolenaar  */
2306d1a0d267SMarcel Moolenaar static char *
23078a6eceffSPhil Shafer xo_strndup (const char *str, ssize_t len)
2308d1a0d267SMarcel Moolenaar {
2309d1a0d267SMarcel Moolenaar     if (len < 0)
2310d1a0d267SMarcel Moolenaar 	len = strlen(str);
2311d1a0d267SMarcel Moolenaar 
2312d1a0d267SMarcel Moolenaar     char *cp = xo_realloc(NULL, len + 1);
2313d1a0d267SMarcel Moolenaar     if (cp) {
2314d1a0d267SMarcel Moolenaar 	memcpy(cp, str, len);
2315d1a0d267SMarcel Moolenaar 	cp[len] = '\0';
2316d1a0d267SMarcel Moolenaar     }
2317d1a0d267SMarcel Moolenaar 
2318d1a0d267SMarcel Moolenaar     return cp;
2319d1a0d267SMarcel Moolenaar }
2320d1a0d267SMarcel Moolenaar 
232131337658SMarcel Moolenaar /**
232231337658SMarcel Moolenaar  * Record a leading prefix for the XPath we generate.  This allows the
232331337658SMarcel Moolenaar  * generated data to be placed within an XML hierarchy but still have
232431337658SMarcel Moolenaar  * accurate XPath expressions.
232531337658SMarcel Moolenaar  *
232631337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
232731337658SMarcel Moolenaar  * @path The XPath expression
232831337658SMarcel Moolenaar  */
232931337658SMarcel Moolenaar void
233031337658SMarcel Moolenaar xo_set_leading_xpath (xo_handle_t *xop, const char *path)
233131337658SMarcel Moolenaar {
233231337658SMarcel Moolenaar     xop = xo_default(xop);
233331337658SMarcel Moolenaar 
233431337658SMarcel Moolenaar     if (xop->xo_leading_xpath) {
233531337658SMarcel Moolenaar 	xo_free(xop->xo_leading_xpath);
233631337658SMarcel Moolenaar 	xop->xo_leading_xpath = NULL;
233731337658SMarcel Moolenaar     }
233831337658SMarcel Moolenaar 
233931337658SMarcel Moolenaar     if (path == NULL)
234031337658SMarcel Moolenaar 	return;
234131337658SMarcel Moolenaar 
2342d1a0d267SMarcel Moolenaar     xop->xo_leading_xpath = xo_strndup(path, -1);
234331337658SMarcel Moolenaar }
234431337658SMarcel Moolenaar 
234531337658SMarcel Moolenaar /**
234631337658SMarcel Moolenaar  * Record the info data for a set of tags
234731337658SMarcel Moolenaar  *
234831337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
234931337658SMarcel Moolenaar  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
235031337658SMarcel Moolenaar  * @count Number of entries in info (or -1 to count them ourselves)
235131337658SMarcel Moolenaar  */
235231337658SMarcel Moolenaar void
235331337658SMarcel Moolenaar xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
235431337658SMarcel Moolenaar {
235531337658SMarcel Moolenaar     xop = xo_default(xop);
235631337658SMarcel Moolenaar 
235731337658SMarcel Moolenaar     if (count < 0 && infop) {
235831337658SMarcel Moolenaar 	xo_info_t *xip;
235931337658SMarcel Moolenaar 
236031337658SMarcel Moolenaar 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
236131337658SMarcel Moolenaar 	    continue;
236231337658SMarcel Moolenaar     }
236331337658SMarcel Moolenaar 
236431337658SMarcel Moolenaar     xop->xo_info = infop;
236531337658SMarcel Moolenaar     xop->xo_info_count = count;
236631337658SMarcel Moolenaar }
236731337658SMarcel Moolenaar 
236831337658SMarcel Moolenaar /**
236931337658SMarcel Moolenaar  * Set the formatter callback for a handle.  The callback should
237031337658SMarcel Moolenaar  * return a newly formatting contents of a formatting instruction,
237131337658SMarcel Moolenaar  * meaning the bits inside the braces.
237231337658SMarcel Moolenaar  */
237331337658SMarcel Moolenaar void
237431337658SMarcel Moolenaar xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
237531337658SMarcel Moolenaar 		  xo_checkpointer_t cfunc)
237631337658SMarcel Moolenaar {
237731337658SMarcel Moolenaar     xop = xo_default(xop);
237831337658SMarcel Moolenaar 
237931337658SMarcel Moolenaar     xop->xo_formatter = func;
238031337658SMarcel Moolenaar     xop->xo_checkpointer = cfunc;
238131337658SMarcel Moolenaar }
238231337658SMarcel Moolenaar 
238331337658SMarcel Moolenaar /**
238431337658SMarcel Moolenaar  * Clear one or more flags for a given handle (or default if handle is NULL).
238531337658SMarcel Moolenaar  * These flags will affect future output.
238631337658SMarcel Moolenaar  *
238731337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
238831337658SMarcel Moolenaar  * @flags Flags to be cleared (XOF_*)
238931337658SMarcel Moolenaar  */
239031337658SMarcel Moolenaar void
239131337658SMarcel Moolenaar xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
239231337658SMarcel Moolenaar {
239331337658SMarcel Moolenaar     xop = xo_default(xop);
239431337658SMarcel Moolenaar 
2395d1a0d267SMarcel Moolenaar     XOF_CLEAR(xop, flags);
239631337658SMarcel Moolenaar }
239731337658SMarcel Moolenaar 
2398545ddfbeSMarcel Moolenaar static const char *
2399545ddfbeSMarcel Moolenaar xo_state_name (xo_state_t state)
2400545ddfbeSMarcel Moolenaar {
2401545ddfbeSMarcel Moolenaar     static const char *names[] = {
2402545ddfbeSMarcel Moolenaar 	"init",
2403545ddfbeSMarcel Moolenaar 	"open_container",
2404545ddfbeSMarcel Moolenaar 	"close_container",
2405545ddfbeSMarcel Moolenaar 	"open_list",
2406545ddfbeSMarcel Moolenaar 	"close_list",
2407545ddfbeSMarcel Moolenaar 	"open_instance",
2408545ddfbeSMarcel Moolenaar 	"close_instance",
2409545ddfbeSMarcel Moolenaar 	"open_leaf_list",
2410545ddfbeSMarcel Moolenaar 	"close_leaf_list",
2411545ddfbeSMarcel Moolenaar 	"discarding",
2412545ddfbeSMarcel Moolenaar 	"marker",
2413545ddfbeSMarcel Moolenaar 	"emit",
2414545ddfbeSMarcel Moolenaar 	"emit_leaf_list",
2415545ddfbeSMarcel Moolenaar 	"finish",
2416545ddfbeSMarcel Moolenaar 	NULL
2417545ddfbeSMarcel Moolenaar     };
2418545ddfbeSMarcel Moolenaar 
2419545ddfbeSMarcel Moolenaar     if (state < (sizeof(names) / sizeof(names[0])))
2420545ddfbeSMarcel Moolenaar 	return names[state];
2421545ddfbeSMarcel Moolenaar 
2422545ddfbeSMarcel Moolenaar     return "unknown";
2423545ddfbeSMarcel Moolenaar }
2424545ddfbeSMarcel Moolenaar 
242531337658SMarcel Moolenaar static void
242631337658SMarcel Moolenaar xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
242731337658SMarcel Moolenaar {
242831337658SMarcel Moolenaar     static char div_open[] = "<div class=\"line\">";
242931337658SMarcel Moolenaar     static char div_open_blank[] = "<div class=\"blank-line\">";
243031337658SMarcel Moolenaar 
2431d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
243231337658SMarcel Moolenaar 	return;
243331337658SMarcel Moolenaar 
2434788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_HTML)
243531337658SMarcel Moolenaar 	return;
243631337658SMarcel Moolenaar 
2437d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_DIV_OPEN);
243831337658SMarcel Moolenaar     if (flags & XFF_BLANK_LINE)
243931337658SMarcel Moolenaar 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
244031337658SMarcel Moolenaar     else
244131337658SMarcel Moolenaar 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
244231337658SMarcel Moolenaar 
2443d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
244431337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
244531337658SMarcel Moolenaar }
244631337658SMarcel Moolenaar 
244731337658SMarcel Moolenaar static void
244831337658SMarcel Moolenaar xo_line_close (xo_handle_t *xop)
244931337658SMarcel Moolenaar {
245031337658SMarcel Moolenaar     static char div_close[] = "</div>";
245131337658SMarcel Moolenaar 
2452788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
245331337658SMarcel Moolenaar     case XO_STYLE_HTML:
2454d1a0d267SMarcel Moolenaar 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
245531337658SMarcel Moolenaar 	    xo_line_ensure_open(xop, 0);
245631337658SMarcel Moolenaar 
2457d1a0d267SMarcel Moolenaar 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
245831337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
245931337658SMarcel Moolenaar 
2460d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
246131337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
246231337658SMarcel Moolenaar 	break;
246331337658SMarcel Moolenaar 
246431337658SMarcel Moolenaar     case XO_STYLE_TEXT:
246531337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
246631337658SMarcel Moolenaar 	break;
246731337658SMarcel Moolenaar     }
246831337658SMarcel Moolenaar }
246931337658SMarcel Moolenaar 
247031337658SMarcel Moolenaar static int
247131337658SMarcel Moolenaar xo_info_compare (const void *key, const void *data)
247231337658SMarcel Moolenaar {
247331337658SMarcel Moolenaar     const char *name = key;
247431337658SMarcel Moolenaar     const xo_info_t *xip = data;
247531337658SMarcel Moolenaar 
247631337658SMarcel Moolenaar     return strcmp(name, xip->xi_name);
247731337658SMarcel Moolenaar }
247831337658SMarcel Moolenaar 
247931337658SMarcel Moolenaar 
248031337658SMarcel Moolenaar static xo_info_t *
24818a6eceffSPhil Shafer xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen)
248231337658SMarcel Moolenaar {
248331337658SMarcel Moolenaar     xo_info_t *xip;
248431337658SMarcel Moolenaar     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
248531337658SMarcel Moolenaar 
248631337658SMarcel Moolenaar     memcpy(cp, name, nlen);
248731337658SMarcel Moolenaar     cp[nlen] = '\0';
248831337658SMarcel Moolenaar 
248931337658SMarcel Moolenaar     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
249031337658SMarcel Moolenaar 		  sizeof(xop->xo_info[0]), xo_info_compare);
249131337658SMarcel Moolenaar     return xip;
249231337658SMarcel Moolenaar }
249331337658SMarcel Moolenaar 
249431337658SMarcel Moolenaar #define CONVERT(_have, _need) (((_have) << 8) | (_need))
249531337658SMarcel Moolenaar 
249631337658SMarcel Moolenaar /*
249731337658SMarcel Moolenaar  * Check to see that the conversion is safe and sane.
249831337658SMarcel Moolenaar  */
249931337658SMarcel Moolenaar static int
250031337658SMarcel Moolenaar xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
250131337658SMarcel Moolenaar {
250231337658SMarcel Moolenaar     switch (CONVERT(have_enc, need_enc)) {
250331337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
250431337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
250531337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
250631337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
250731337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
250831337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
250931337658SMarcel Moolenaar 	return 0;
251031337658SMarcel Moolenaar 
251131337658SMarcel Moolenaar     default:
251231337658SMarcel Moolenaar 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
251331337658SMarcel Moolenaar 	return 1;
251431337658SMarcel Moolenaar     }
251531337658SMarcel Moolenaar }
251631337658SMarcel Moolenaar 
251731337658SMarcel Moolenaar static int
251831337658SMarcel Moolenaar xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
251931337658SMarcel Moolenaar 			 xo_xff_flags_t flags,
25208a6eceffSPhil Shafer 			 const wchar_t *wcp, const char *cp,
25218a6eceffSPhil Shafer 			 ssize_t len, int max,
252231337658SMarcel Moolenaar 			 int need_enc, int have_enc)
252331337658SMarcel Moolenaar {
252431337658SMarcel Moolenaar     int cols = 0;
2525c600d307SMarcel Moolenaar     wchar_t wc = 0;
25268a6eceffSPhil Shafer     ssize_t ilen, olen;
25278a6eceffSPhil Shafer     ssize_t width;
25288a6eceffSPhil Shafer     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
252931337658SMarcel Moolenaar     const char *sp;
253031337658SMarcel Moolenaar 
253131337658SMarcel Moolenaar     if (len > 0 && !xo_buf_has_room(xbp, len))
253231337658SMarcel Moolenaar 	return 0;
253331337658SMarcel Moolenaar 
253431337658SMarcel Moolenaar     for (;;) {
253531337658SMarcel Moolenaar 	if (len == 0)
253631337658SMarcel Moolenaar 	    break;
253731337658SMarcel Moolenaar 
253831337658SMarcel Moolenaar 	if (cp) {
253931337658SMarcel Moolenaar 	    if (*cp == '\0')
254031337658SMarcel Moolenaar 		break;
254131337658SMarcel Moolenaar 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
254231337658SMarcel Moolenaar 		cp += 1;
254331337658SMarcel Moolenaar 		len -= 1;
254431337658SMarcel Moolenaar 	    }
254531337658SMarcel Moolenaar 	}
254631337658SMarcel Moolenaar 
254731337658SMarcel Moolenaar 	if (wcp && *wcp == L'\0')
254831337658SMarcel Moolenaar 	    break;
254931337658SMarcel Moolenaar 
255031337658SMarcel Moolenaar 	ilen = 0;
255131337658SMarcel Moolenaar 
255231337658SMarcel Moolenaar 	switch (have_enc) {
255331337658SMarcel Moolenaar 	case XF_ENC_WIDE:		/* Wide character */
255431337658SMarcel Moolenaar 	    wc = *wcp++;
255531337658SMarcel Moolenaar 	    ilen = 1;
255631337658SMarcel Moolenaar 	    break;
255731337658SMarcel Moolenaar 
255831337658SMarcel Moolenaar 	case XF_ENC_UTF8:		/* UTF-8 */
255931337658SMarcel Moolenaar 	    ilen = xo_utf8_to_wc_len(cp);
256031337658SMarcel Moolenaar 	    if (ilen < 0) {
256131337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2562d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
256331337658SMarcel Moolenaar 	    }
256431337658SMarcel Moolenaar 
256531337658SMarcel Moolenaar 	    if (len > 0 && len < ilen) {
256631337658SMarcel Moolenaar 		len = 0;	/* Break out of the loop */
256731337658SMarcel Moolenaar 		continue;
256831337658SMarcel Moolenaar 	    }
256931337658SMarcel Moolenaar 
257031337658SMarcel Moolenaar 	    wc = xo_utf8_char(cp, ilen);
257131337658SMarcel Moolenaar 	    if (wc == (wchar_t) -1) {
257231337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
257331337658SMarcel Moolenaar 			   *cp, ilen);
2574d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
257531337658SMarcel Moolenaar 	    }
257631337658SMarcel Moolenaar 	    cp += ilen;
257731337658SMarcel Moolenaar 	    break;
257831337658SMarcel Moolenaar 
257931337658SMarcel Moolenaar 	case XF_ENC_LOCALE:		/* Native locale */
258031337658SMarcel Moolenaar 	    ilen = (len > 0) ? len : MB_LEN_MAX;
258131337658SMarcel Moolenaar 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
258231337658SMarcel Moolenaar 	    if (ilen < 0) {		/* Invalid data; skip */
258331337658SMarcel Moolenaar 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2584dbf26257SAlexander Kabaev 		wc = L'?';
2585dbf26257SAlexander Kabaev 		ilen = 1;
258631337658SMarcel Moolenaar 	    }
2587d1a0d267SMarcel Moolenaar 
258831337658SMarcel Moolenaar 	    if (ilen == 0) {		/* Hit a wide NUL character */
258931337658SMarcel Moolenaar 		len = 0;
259031337658SMarcel Moolenaar 		continue;
259131337658SMarcel Moolenaar 	    }
259231337658SMarcel Moolenaar 
259331337658SMarcel Moolenaar 	    cp += ilen;
259431337658SMarcel Moolenaar 	    break;
259531337658SMarcel Moolenaar 	}
259631337658SMarcel Moolenaar 
259731337658SMarcel Moolenaar 	/* Reduce len, but not below zero */
259831337658SMarcel Moolenaar 	if (len > 0) {
259931337658SMarcel Moolenaar 	    len -= ilen;
260031337658SMarcel Moolenaar 	    if (len < 0)
260131337658SMarcel Moolenaar 		len = 0;
260231337658SMarcel Moolenaar 	}
260331337658SMarcel Moolenaar 
260431337658SMarcel Moolenaar 	/*
260531337658SMarcel Moolenaar 	 * Find the width-in-columns of this character, which must be done
260631337658SMarcel Moolenaar 	 * in wide characters, since we lack a mbswidth() function.  If
260731337658SMarcel Moolenaar 	 * it doesn't fit
260831337658SMarcel Moolenaar 	 */
2609d1a0d267SMarcel Moolenaar 	width = xo_wcwidth(wc);
261031337658SMarcel Moolenaar 	if (width < 0)
261131337658SMarcel Moolenaar 	    width = iswcntrl(wc) ? 0 : 1;
261231337658SMarcel Moolenaar 
2613788ca347SMarcel Moolenaar 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
261431337658SMarcel Moolenaar 	    if (max > 0 && cols + width > max)
261531337658SMarcel Moolenaar 		break;
261631337658SMarcel Moolenaar 	}
261731337658SMarcel Moolenaar 
261831337658SMarcel Moolenaar 	switch (need_enc) {
261931337658SMarcel Moolenaar 	case XF_ENC_UTF8:
262031337658SMarcel Moolenaar 
262131337658SMarcel Moolenaar 	    /* Output in UTF-8 needs to be escaped, based on the style */
2622788ca347SMarcel Moolenaar 	    switch (xo_style(xop)) {
262331337658SMarcel Moolenaar 	    case XO_STYLE_XML:
262431337658SMarcel Moolenaar 	    case XO_STYLE_HTML:
262531337658SMarcel Moolenaar 		if (wc == '<')
262631337658SMarcel Moolenaar 		    sp = xo_xml_lt;
262731337658SMarcel Moolenaar 		else if (wc == '>')
262831337658SMarcel Moolenaar 		    sp = xo_xml_gt;
262931337658SMarcel Moolenaar 		else if (wc == '&')
263031337658SMarcel Moolenaar 		    sp = xo_xml_amp;
263131337658SMarcel Moolenaar 		else if (attr && wc == '"')
263231337658SMarcel Moolenaar 		    sp = xo_xml_quot;
263331337658SMarcel Moolenaar 		else
263431337658SMarcel Moolenaar 		    break;
263531337658SMarcel Moolenaar 
26368a6eceffSPhil Shafer 		ssize_t slen = strlen(sp);
263731337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, slen - 1))
263831337658SMarcel Moolenaar 		    return -1;
263931337658SMarcel Moolenaar 
264031337658SMarcel Moolenaar 		memcpy(xbp->xb_curp, sp, slen);
264131337658SMarcel Moolenaar 		xbp->xb_curp += slen;
264231337658SMarcel Moolenaar 		goto done_with_encoding; /* Need multi-level 'break' */
264331337658SMarcel Moolenaar 
264431337658SMarcel Moolenaar 	    case XO_STYLE_JSON:
2645545ddfbeSMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
264631337658SMarcel Moolenaar 		    break;
264731337658SMarcel Moolenaar 
264831337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
264931337658SMarcel Moolenaar 		    return -1;
265031337658SMarcel Moolenaar 
265131337658SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2652545ddfbeSMarcel Moolenaar 		if (wc == '\n')
2653545ddfbeSMarcel Moolenaar 		    wc = 'n';
2654545ddfbeSMarcel Moolenaar 		else if (wc == '\r')
2655545ddfbeSMarcel Moolenaar 		    wc = 'r';
2656545ddfbeSMarcel Moolenaar 		else wc = wc & 0x7f;
2657545ddfbeSMarcel Moolenaar 
2658545ddfbeSMarcel Moolenaar 		*xbp->xb_curp++ = wc;
265931337658SMarcel Moolenaar 		goto done_with_encoding;
2660d1a0d267SMarcel Moolenaar 
2661d1a0d267SMarcel Moolenaar 	    case XO_STYLE_SDPARAMS:
2662d1a0d267SMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != ']')
2663d1a0d267SMarcel Moolenaar 		    break;
2664d1a0d267SMarcel Moolenaar 
2665d1a0d267SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
2666d1a0d267SMarcel Moolenaar 		    return -1;
2667d1a0d267SMarcel Moolenaar 
2668d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2669d1a0d267SMarcel Moolenaar 		wc = wc & 0x7f;
2670d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = wc;
2671d1a0d267SMarcel Moolenaar 		goto done_with_encoding;
267231337658SMarcel Moolenaar 	    }
267331337658SMarcel Moolenaar 
267431337658SMarcel Moolenaar 	    olen = xo_utf8_emit_len(wc);
267531337658SMarcel Moolenaar 	    if (olen < 0) {
267631337658SMarcel Moolenaar 		xo_failure(xop, "ignoring bad length");
267731337658SMarcel Moolenaar 		continue;
267831337658SMarcel Moolenaar 	    }
267931337658SMarcel Moolenaar 
268031337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, olen))
268131337658SMarcel Moolenaar 		return -1;
268231337658SMarcel Moolenaar 
268331337658SMarcel Moolenaar 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
268431337658SMarcel Moolenaar 	    xbp->xb_curp += olen;
268531337658SMarcel Moolenaar 	    break;
268631337658SMarcel Moolenaar 
268731337658SMarcel Moolenaar 	case XF_ENC_LOCALE:
268831337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
268931337658SMarcel Moolenaar 		return -1;
269031337658SMarcel Moolenaar 
269131337658SMarcel Moolenaar 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
269231337658SMarcel Moolenaar 	    if (olen <= 0) {
269331337658SMarcel Moolenaar 		xo_failure(xop, "could not convert wide char: %lx",
269431337658SMarcel Moolenaar 			   (unsigned long) wc);
269531337658SMarcel Moolenaar 		width = 1;
269631337658SMarcel Moolenaar 		*xbp->xb_curp++ = '?';
269731337658SMarcel Moolenaar 	    } else
269831337658SMarcel Moolenaar 		xbp->xb_curp += olen;
269931337658SMarcel Moolenaar 	    break;
270031337658SMarcel Moolenaar 	}
270131337658SMarcel Moolenaar 
270231337658SMarcel Moolenaar     done_with_encoding:
270331337658SMarcel Moolenaar 	cols += width;
270431337658SMarcel Moolenaar     }
270531337658SMarcel Moolenaar 
270631337658SMarcel Moolenaar     return cols;
270731337658SMarcel Moolenaar }
270831337658SMarcel Moolenaar 
270931337658SMarcel Moolenaar static int
2710d1a0d267SMarcel Moolenaar xo_needed_encoding (xo_handle_t *xop)
2711d1a0d267SMarcel Moolenaar {
2712d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2713d1a0d267SMarcel Moolenaar 	return XF_ENC_UTF8;
2714d1a0d267SMarcel Moolenaar 
2715d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2716d1a0d267SMarcel Moolenaar 	return XF_ENC_LOCALE;
2717d1a0d267SMarcel Moolenaar 
2718d1a0d267SMarcel Moolenaar     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2719d1a0d267SMarcel Moolenaar }
2720d1a0d267SMarcel Moolenaar 
27218a6eceffSPhil Shafer static ssize_t
272231337658SMarcel Moolenaar xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
272331337658SMarcel Moolenaar 		  xo_format_t *xfp)
272431337658SMarcel Moolenaar {
272531337658SMarcel Moolenaar     static char null[] = "(null)";
2726d1a0d267SMarcel Moolenaar     static char null_no_quotes[] = "null";
2727a0f704ffSMarcel Moolenaar 
272831337658SMarcel Moolenaar     char *cp = NULL;
272931337658SMarcel Moolenaar     wchar_t *wcp = NULL;
27308a6eceffSPhil Shafer     ssize_t len;
27318a6eceffSPhil Shafer     ssize_t cols = 0, rc = 0;
27328a6eceffSPhil Shafer     ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2;
2733d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
273431337658SMarcel Moolenaar 
273531337658SMarcel Moolenaar     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
273631337658SMarcel Moolenaar 	return 0;
273731337658SMarcel Moolenaar 
2738a0f704ffSMarcel Moolenaar     len = xfp->xf_width[XF_WIDTH_SIZE];
2739a0f704ffSMarcel Moolenaar 
2740d1a0d267SMarcel Moolenaar     if (xfp->xf_fc == 'm') {
2741d1a0d267SMarcel Moolenaar 	cp = strerror(xop->xo_errno);
2742d1a0d267SMarcel Moolenaar 	if (len < 0)
2743d1a0d267SMarcel Moolenaar 	    len = cp ? strlen(cp) : 0;
2744d1a0d267SMarcel Moolenaar 	goto normal_string;
2745d1a0d267SMarcel Moolenaar 
2746d1a0d267SMarcel Moolenaar     } else if (xfp->xf_enc == XF_ENC_WIDE) {
274731337658SMarcel Moolenaar 	wcp = va_arg(xop->xo_vap, wchar_t *);
274831337658SMarcel Moolenaar 	if (xfp->xf_skip)
274931337658SMarcel Moolenaar 	    return 0;
275031337658SMarcel Moolenaar 
2751a0f704ffSMarcel Moolenaar 	/*
2752a0f704ffSMarcel Moolenaar 	 * Dont' deref NULL; use the traditional "(null)" instead
2753a0f704ffSMarcel Moolenaar 	 * of the more accurate "who's been a naughty boy, then?".
2754a0f704ffSMarcel Moolenaar 	 */
2755a0f704ffSMarcel Moolenaar 	if (wcp == NULL) {
2756a0f704ffSMarcel Moolenaar 	    cp = null;
2757a0f704ffSMarcel Moolenaar 	    len = sizeof(null) - 1;
2758a0f704ffSMarcel Moolenaar 	}
2759a0f704ffSMarcel Moolenaar 
276031337658SMarcel Moolenaar     } else {
276131337658SMarcel Moolenaar 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2762d1a0d267SMarcel Moolenaar 
2763d1a0d267SMarcel Moolenaar     normal_string:
276431337658SMarcel Moolenaar 	if (xfp->xf_skip)
276531337658SMarcel Moolenaar 	    return 0;
276631337658SMarcel Moolenaar 
2767a0f704ffSMarcel Moolenaar 	/* Echo "Dont' deref NULL" logic */
2768a0f704ffSMarcel Moolenaar 	if (cp == NULL) {
2769d1a0d267SMarcel Moolenaar 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2770d1a0d267SMarcel Moolenaar 		cp = null_no_quotes;
2771d1a0d267SMarcel Moolenaar 		len = sizeof(null_no_quotes) - 1;
2772d1a0d267SMarcel Moolenaar 	    } else {
2773a0f704ffSMarcel Moolenaar 		cp = null;
2774a0f704ffSMarcel Moolenaar 		len = sizeof(null) - 1;
2775a0f704ffSMarcel Moolenaar 	    }
2776d1a0d267SMarcel Moolenaar 	}
2777a0f704ffSMarcel Moolenaar 
277831337658SMarcel Moolenaar 	/*
277931337658SMarcel Moolenaar 	 * Optimize the most common case, which is "%s".  We just
278031337658SMarcel Moolenaar 	 * need to copy the complete string to the output buffer.
278131337658SMarcel Moolenaar 	 */
278231337658SMarcel Moolenaar 	if (xfp->xf_enc == need_enc
278331337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
278431337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
278531337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2786d1a0d267SMarcel Moolenaar 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2787d1a0d267SMarcel Moolenaar 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
278831337658SMarcel Moolenaar 	    len = strlen(cp);
278931337658SMarcel Moolenaar 	    xo_buf_escape(xop, xbp, cp, len, flags);
279031337658SMarcel Moolenaar 
279131337658SMarcel Moolenaar 	    /*
279231337658SMarcel Moolenaar 	     * Our caller expects xb_curp left untouched, so we have
279331337658SMarcel Moolenaar 	     * to reset it and return the number of bytes written to
279431337658SMarcel Moolenaar 	     * the buffer.
279531337658SMarcel Moolenaar 	     */
279631337658SMarcel Moolenaar 	    off2 = xbp->xb_curp - xbp->xb_bufp;
279731337658SMarcel Moolenaar 	    rc = off2 - off;
279831337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + off;
279931337658SMarcel Moolenaar 
280031337658SMarcel Moolenaar 	    return rc;
280131337658SMarcel Moolenaar 	}
280231337658SMarcel Moolenaar     }
280331337658SMarcel Moolenaar 
280431337658SMarcel Moolenaar     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
280531337658SMarcel Moolenaar 				   xfp->xf_width[XF_WIDTH_MAX],
280631337658SMarcel Moolenaar 				   need_enc, xfp->xf_enc);
280731337658SMarcel Moolenaar     if (cols < 0)
280831337658SMarcel Moolenaar 	goto bail;
280931337658SMarcel Moolenaar 
281031337658SMarcel Moolenaar     /*
281131337658SMarcel Moolenaar      * xo_buf_append* will move xb_curp, so we save/restore it.
281231337658SMarcel Moolenaar      */
281331337658SMarcel Moolenaar     off2 = xbp->xb_curp - xbp->xb_bufp;
281431337658SMarcel Moolenaar     rc = off2 - off;
281531337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
281631337658SMarcel Moolenaar 
281731337658SMarcel Moolenaar     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
281831337658SMarcel Moolenaar 	/*
281931337658SMarcel Moolenaar 	 * Find the number of columns needed to display the string.
282031337658SMarcel Moolenaar 	 * If we have the original wide string, we just call wcswidth,
282131337658SMarcel Moolenaar 	 * but if we did the work ourselves, then we need to do it.
282231337658SMarcel Moolenaar 	 */
282331337658SMarcel Moolenaar 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2824ee5cf116SPhil Shafer 	if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN]))
282531337658SMarcel Moolenaar 	    goto bail;
282631337658SMarcel Moolenaar 
282731337658SMarcel Moolenaar 	/*
282831337658SMarcel Moolenaar 	 * If seen_minus, then pad on the right; otherwise move it so
282931337658SMarcel Moolenaar 	 * we can pad on the left.
283031337658SMarcel Moolenaar 	 */
283131337658SMarcel Moolenaar 	if (xfp->xf_seen_minus) {
283231337658SMarcel Moolenaar 	    cp = xbp->xb_curp + rc;
283331337658SMarcel Moolenaar 	} else {
283431337658SMarcel Moolenaar 	    cp = xbp->xb_curp;
283531337658SMarcel Moolenaar 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
283631337658SMarcel Moolenaar 	}
283731337658SMarcel Moolenaar 
283831337658SMarcel Moolenaar 	/* Set the padding */
283931337658SMarcel Moolenaar 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
284031337658SMarcel Moolenaar 	rc += delta;
284131337658SMarcel Moolenaar 	cols += delta;
284231337658SMarcel Moolenaar     }
284331337658SMarcel Moolenaar 
2844d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
284531337658SMarcel Moolenaar 	xop->xo_columns += cols;
2846d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
284731337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
284831337658SMarcel Moolenaar 
284931337658SMarcel Moolenaar     return rc;
285031337658SMarcel Moolenaar 
285131337658SMarcel Moolenaar  bail:
285231337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
285331337658SMarcel Moolenaar     return 0;
285431337658SMarcel Moolenaar }
285531337658SMarcel Moolenaar 
2856d1a0d267SMarcel Moolenaar /*
2857d1a0d267SMarcel Moolenaar  * Look backwards in a buffer to find a numeric value
2858d1a0d267SMarcel Moolenaar  */
2859d1a0d267SMarcel Moolenaar static int
28608a6eceffSPhil Shafer xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset)
2861d1a0d267SMarcel Moolenaar {
2862d1a0d267SMarcel Moolenaar     int rc = 0;			/* Fail with zero */
2863d1a0d267SMarcel Moolenaar     int digit = 1;
2864d1a0d267SMarcel Moolenaar     char *sp = xbp->xb_bufp;
2865d1a0d267SMarcel Moolenaar     char *cp = sp + start_offset;
2866d1a0d267SMarcel Moolenaar 
2867d1a0d267SMarcel Moolenaar     while (--cp >= sp)
2868d1a0d267SMarcel Moolenaar 	if (isdigit((int) *cp))
2869d1a0d267SMarcel Moolenaar 	    break;
2870d1a0d267SMarcel Moolenaar 
2871d1a0d267SMarcel Moolenaar     for ( ; cp >= sp; cp--) {
2872d1a0d267SMarcel Moolenaar 	if (!isdigit((int) *cp))
2873d1a0d267SMarcel Moolenaar 	    break;
2874d1a0d267SMarcel Moolenaar 	rc += (*cp - '0') * digit;
2875d1a0d267SMarcel Moolenaar 	digit *= 10;
2876d1a0d267SMarcel Moolenaar     }
2877d1a0d267SMarcel Moolenaar 
2878d1a0d267SMarcel Moolenaar     return rc;
2879d1a0d267SMarcel Moolenaar }
2880d1a0d267SMarcel Moolenaar 
28818a6eceffSPhil Shafer static ssize_t
28828a6eceffSPhil Shafer xo_count_utf8_cols (const char *str, ssize_t len)
2883d1a0d267SMarcel Moolenaar {
28848a6eceffSPhil Shafer     ssize_t tlen;
2885d1a0d267SMarcel Moolenaar     wchar_t wc;
28868a6eceffSPhil Shafer     ssize_t cols = 0;
2887d1a0d267SMarcel Moolenaar     const char *ep = str + len;
2888d1a0d267SMarcel Moolenaar 
2889d1a0d267SMarcel Moolenaar     while (str < ep) {
2890d1a0d267SMarcel Moolenaar 	tlen = xo_utf8_to_wc_len(str);
2891d1a0d267SMarcel Moolenaar 	if (tlen < 0)		/* Broken input is very bad */
2892d1a0d267SMarcel Moolenaar 	    return cols;
2893d1a0d267SMarcel Moolenaar 
2894d1a0d267SMarcel Moolenaar 	wc = xo_utf8_char(str, tlen);
2895d1a0d267SMarcel Moolenaar 	if (wc == (wchar_t) -1)
2896d1a0d267SMarcel Moolenaar 	    return cols;
2897d1a0d267SMarcel Moolenaar 
2898d1a0d267SMarcel Moolenaar 	/* We only print printable characters */
2899d1a0d267SMarcel Moolenaar 	if (iswprint((wint_t) wc)) {
2900d1a0d267SMarcel Moolenaar 	    /*
2901d1a0d267SMarcel Moolenaar 	     * Find the width-in-columns of this character, which must be done
2902d1a0d267SMarcel Moolenaar 	     * in wide characters, since we lack a mbswidth() function.
2903d1a0d267SMarcel Moolenaar 	     */
29048a6eceffSPhil Shafer 	    ssize_t width = xo_wcwidth(wc);
2905d1a0d267SMarcel Moolenaar 	    if (width < 0)
2906d1a0d267SMarcel Moolenaar 		width = iswcntrl(wc) ? 0 : 1;
2907d1a0d267SMarcel Moolenaar 
2908d1a0d267SMarcel Moolenaar 	    cols += width;
2909d1a0d267SMarcel Moolenaar 	}
2910d1a0d267SMarcel Moolenaar 
2911d1a0d267SMarcel Moolenaar 	str += tlen;
2912d1a0d267SMarcel Moolenaar     }
2913d1a0d267SMarcel Moolenaar 
2914d1a0d267SMarcel Moolenaar     return cols;
2915d1a0d267SMarcel Moolenaar }
2916d1a0d267SMarcel Moolenaar 
2917d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
2918d1a0d267SMarcel Moolenaar static inline const char *
2919d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop, const char *str)
2920d1a0d267SMarcel Moolenaar {
2921d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
2922d1a0d267SMarcel Moolenaar     const char *res;
2923d1a0d267SMarcel Moolenaar 
2924d1a0d267SMarcel Moolenaar     res = dgettext(domainname, str);
2925d1a0d267SMarcel Moolenaar 
2926d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2927d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
2928d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "", xo_printable(domainname),
2929d1a0d267SMarcel Moolenaar 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
2930d1a0d267SMarcel Moolenaar 
2931d1a0d267SMarcel Moolenaar     return res;
2932d1a0d267SMarcel Moolenaar }
2933d1a0d267SMarcel Moolenaar 
2934d1a0d267SMarcel Moolenaar static inline const char *
2935d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
2936d1a0d267SMarcel Moolenaar 	      unsigned long int n)
2937d1a0d267SMarcel Moolenaar {
2938d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
2939d1a0d267SMarcel Moolenaar     const char *res;
2940d1a0d267SMarcel Moolenaar 
2941d1a0d267SMarcel Moolenaar     res = dngettext(domainname, sing, plural, n);
2942d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2943d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%s"
2944d1a0d267SMarcel Moolenaar 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
2945d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "",
2946d1a0d267SMarcel Moolenaar 		xo_printable(domainname), domainname ? "\", " : "",
2947d1a0d267SMarcel Moolenaar 		xo_printable(sing),
2948d1a0d267SMarcel Moolenaar 		xo_printable(plural), n, xo_printable(res));
2949d1a0d267SMarcel Moolenaar 
2950d1a0d267SMarcel Moolenaar     return res;
2951d1a0d267SMarcel Moolenaar }
2952d1a0d267SMarcel Moolenaar #else /* HAVE_GETTEXT */
2953d1a0d267SMarcel Moolenaar static inline const char *
2954d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
2955d1a0d267SMarcel Moolenaar {
2956d1a0d267SMarcel Moolenaar     return str;
2957d1a0d267SMarcel Moolenaar }
2958d1a0d267SMarcel Moolenaar 
2959d1a0d267SMarcel Moolenaar static inline const char *
2960d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
2961d1a0d267SMarcel Moolenaar 	      const char *plural, unsigned long int n)
2962d1a0d267SMarcel Moolenaar {
2963d1a0d267SMarcel Moolenaar     return (n == 1) ? singular : plural;
2964d1a0d267SMarcel Moolenaar }
2965d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
2966d1a0d267SMarcel Moolenaar 
2967d1a0d267SMarcel Moolenaar /*
2968d1a0d267SMarcel Moolenaar  * This is really _re_formatting, since the normal format code has
2969d1a0d267SMarcel Moolenaar  * generated a beautiful string into xo_data, starting at
2970d1a0d267SMarcel Moolenaar  * start_offset.  We need to see if it's plural, which means
2971d1a0d267SMarcel Moolenaar  * comma-separated options, or singular.  Then we make the appropriate
2972d1a0d267SMarcel Moolenaar  * call to d[n]gettext() to get the locale-based version.  Note that
2973d1a0d267SMarcel Moolenaar  * both input and output of gettext() this should be UTF-8.
2974d1a0d267SMarcel Moolenaar  */
29758a6eceffSPhil Shafer static ssize_t
2976d1a0d267SMarcel Moolenaar xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
29778a6eceffSPhil Shafer 		   ssize_t start_offset, ssize_t cols, int need_enc)
2978d1a0d267SMarcel Moolenaar {
2979d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
2980d1a0d267SMarcel Moolenaar 
2981d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, 1))
2982d1a0d267SMarcel Moolenaar 	return cols;
2983d1a0d267SMarcel Moolenaar 
2984d1a0d267SMarcel Moolenaar     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
2985d1a0d267SMarcel Moolenaar 
2986d1a0d267SMarcel Moolenaar     char *cp = xbp->xb_bufp + start_offset;
29878a6eceffSPhil Shafer     ssize_t len = xbp->xb_curp - cp;
2988d1a0d267SMarcel Moolenaar     const char *newstr = NULL;
2989d1a0d267SMarcel Moolenaar 
2990d1a0d267SMarcel Moolenaar     /*
2991d1a0d267SMarcel Moolenaar      * The plural flag asks us to look backwards at the last numeric
2992d1a0d267SMarcel Moolenaar      * value rendered and disect the string into two pieces.
2993d1a0d267SMarcel Moolenaar      */
2994d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_PLURAL) {
2995d1a0d267SMarcel Moolenaar 	int n = xo_buf_find_last_number(xbp, start_offset);
2996d1a0d267SMarcel Moolenaar 	char *two = memchr(cp, (int) ',', len);
2997d1a0d267SMarcel Moolenaar 	if (two == NULL) {
2998d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
2999d1a0d267SMarcel Moolenaar 	    return cols;
3000d1a0d267SMarcel Moolenaar 	}
3001d1a0d267SMarcel Moolenaar 
3002d1a0d267SMarcel Moolenaar 	if (two == cp) {
3003d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing before comma in plural gettext "
3004d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3005d1a0d267SMarcel Moolenaar 	    return cols;
3006d1a0d267SMarcel Moolenaar 	}
3007d1a0d267SMarcel Moolenaar 
3008d1a0d267SMarcel Moolenaar 	if (two == xbp->xb_curp) {
3009d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing after comma in plural gettext "
3010d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
3011d1a0d267SMarcel Moolenaar 	    return cols;
3012d1a0d267SMarcel Moolenaar 	}
3013d1a0d267SMarcel Moolenaar 
3014d1a0d267SMarcel Moolenaar 	*two++ = '\0';
3015d1a0d267SMarcel Moolenaar 	if (flags & XFF_GT_FIELD) {
3016d1a0d267SMarcel Moolenaar 	    newstr = xo_dngettext(xop, cp, two, n);
3017d1a0d267SMarcel Moolenaar 	} else {
3018d1a0d267SMarcel Moolenaar 	    /* Don't do a gettext() look up, just get the plural form */
3019d1a0d267SMarcel Moolenaar 	    newstr = (n == 1) ? cp : two;
3020d1a0d267SMarcel Moolenaar 	}
3021d1a0d267SMarcel Moolenaar 
3022d1a0d267SMarcel Moolenaar 	/*
3023d1a0d267SMarcel Moolenaar 	 * If we returned the first string, optimize a bit by
3024d1a0d267SMarcel Moolenaar 	 * backing up over comma
3025d1a0d267SMarcel Moolenaar 	 */
3026d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3027d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = two - 1; /* One for comma */
3028d1a0d267SMarcel Moolenaar 	    /*
3029d1a0d267SMarcel Moolenaar 	     * If the caller wanted UTF8, we're done; nothing changed,
3030d1a0d267SMarcel Moolenaar 	     * but we need to count the columns used.
3031d1a0d267SMarcel Moolenaar 	     */
3032d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3033d1a0d267SMarcel Moolenaar 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3034d1a0d267SMarcel Moolenaar 	}
3035d1a0d267SMarcel Moolenaar 
3036d1a0d267SMarcel Moolenaar     } else {
3037d1a0d267SMarcel Moolenaar 	/* The simple case (singular) */
3038d1a0d267SMarcel Moolenaar 	newstr = xo_dgettext(xop, cp);
3039d1a0d267SMarcel Moolenaar 
3040d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
3041d1a0d267SMarcel Moolenaar 	    /* If the caller wanted UTF8, we're done; nothing changed */
3042d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
3043d1a0d267SMarcel Moolenaar 		return cols;
3044d1a0d267SMarcel Moolenaar 	}
3045d1a0d267SMarcel Moolenaar     }
3046d1a0d267SMarcel Moolenaar 
3047d1a0d267SMarcel Moolenaar     /*
3048d1a0d267SMarcel Moolenaar      * Since the new string string might be in gettext's buffer or
3049d1a0d267SMarcel Moolenaar      * in the buffer (as the plural form), we make a copy.
3050d1a0d267SMarcel Moolenaar      */
30518a6eceffSPhil Shafer     ssize_t nlen = strlen(newstr);
3052d1a0d267SMarcel Moolenaar     char *newcopy = alloca(nlen + 1);
3053d1a0d267SMarcel Moolenaar     memcpy(newcopy, newstr, nlen + 1);
3054d1a0d267SMarcel Moolenaar 
3055d1a0d267SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3056d1a0d267SMarcel Moolenaar     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3057d1a0d267SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3058d1a0d267SMarcel Moolenaar }
3059d1a0d267SMarcel Moolenaar 
306031337658SMarcel Moolenaar static void
30618a6eceffSPhil Shafer xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len,
3062d1a0d267SMarcel Moolenaar 			xo_xff_flags_t flags)
306331337658SMarcel Moolenaar {
306431337658SMarcel Moolenaar     int cols;
3065d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
30668a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(&xop->xo_data);
306731337658SMarcel Moolenaar 
3068d1a0d267SMarcel Moolenaar     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
306931337658SMarcel Moolenaar 				   NULL, str, len, -1,
307031337658SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
3071d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS)
3072d1a0d267SMarcel Moolenaar 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
307331337658SMarcel Moolenaar 
3074d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
307531337658SMarcel Moolenaar 	xop->xo_columns += cols;
3076d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
307731337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
307831337658SMarcel Moolenaar }
307931337658SMarcel Moolenaar 
308031337658SMarcel Moolenaar static void
308131337658SMarcel Moolenaar xo_bump_width (xo_format_t *xfp, int digit)
308231337658SMarcel Moolenaar {
308331337658SMarcel Moolenaar     int *ip = &xfp->xf_width[xfp->xf_dots];
308431337658SMarcel Moolenaar 
308531337658SMarcel Moolenaar     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
308631337658SMarcel Moolenaar }
308731337658SMarcel Moolenaar 
30888a6eceffSPhil Shafer static ssize_t
30898a6eceffSPhil Shafer xo_trim_ws (xo_buffer_t *xbp, ssize_t len)
309031337658SMarcel Moolenaar {
309131337658SMarcel Moolenaar     char *cp, *sp, *ep;
30928a6eceffSPhil Shafer     ssize_t delta;
309331337658SMarcel Moolenaar 
309431337658SMarcel Moolenaar     /* First trim leading space */
309531337658SMarcel Moolenaar     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
309631337658SMarcel Moolenaar 	if (*cp != ' ')
309731337658SMarcel Moolenaar 	    break;
309831337658SMarcel Moolenaar     }
309931337658SMarcel Moolenaar 
310031337658SMarcel Moolenaar     delta = cp - sp;
310131337658SMarcel Moolenaar     if (delta) {
310231337658SMarcel Moolenaar 	len -= delta;
310331337658SMarcel Moolenaar 	memmove(sp, cp, len);
310431337658SMarcel Moolenaar     }
310531337658SMarcel Moolenaar 
310631337658SMarcel Moolenaar     /* Then trim off the end */
310731337658SMarcel Moolenaar     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
310831337658SMarcel Moolenaar 	if (ep[-1] != ' ')
310931337658SMarcel Moolenaar 	    break;
311031337658SMarcel Moolenaar     }
311131337658SMarcel Moolenaar 
311231337658SMarcel Moolenaar     delta = sp - ep;
311331337658SMarcel Moolenaar     if (delta) {
311431337658SMarcel Moolenaar 	len -= delta;
311531337658SMarcel Moolenaar 	cp[len] = '\0';
311631337658SMarcel Moolenaar     }
311731337658SMarcel Moolenaar 
311831337658SMarcel Moolenaar     return len;
311931337658SMarcel Moolenaar }
312031337658SMarcel Moolenaar 
3121d1a0d267SMarcel Moolenaar /*
3122d1a0d267SMarcel Moolenaar  * Interface to format a single field.  The arguments are in xo_vap,
3123d1a0d267SMarcel Moolenaar  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
3124d1a0d267SMarcel Moolenaar  * this is the most common case.
3125d1a0d267SMarcel Moolenaar  */
31268a6eceffSPhil Shafer static ssize_t
3127d1a0d267SMarcel Moolenaar xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
31288a6eceffSPhil Shafer 		const char *fmt, ssize_t flen, xo_xff_flags_t flags)
312931337658SMarcel Moolenaar {
313031337658SMarcel Moolenaar     xo_format_t xf;
313131337658SMarcel Moolenaar     const char *cp, *ep, *sp, *xp = NULL;
31328a6eceffSPhil Shafer     ssize_t rc, cols;
3133788ca347SMarcel Moolenaar     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
31348a6eceffSPhil Shafer     unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0;
3135d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
3136d1a0d267SMarcel Moolenaar     int real_need_enc = need_enc;
31378a6eceffSPhil Shafer     ssize_t old_cols = xop->xo_columns;
3138d1a0d267SMarcel Moolenaar 
3139d1a0d267SMarcel Moolenaar     /* The gettext interface is UTF-8, so we'll need that for now */
3140d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FIELD)
3141d1a0d267SMarcel Moolenaar 	need_enc = XF_ENC_UTF8;
314231337658SMarcel Moolenaar 
314331337658SMarcel Moolenaar     if (xbp == NULL)
314431337658SMarcel Moolenaar 	xbp = &xop->xo_data;
314531337658SMarcel Moolenaar 
31468a6eceffSPhil Shafer     ssize_t start_offset = xo_buf_offset(xbp);
3147d1a0d267SMarcel Moolenaar 
314831337658SMarcel Moolenaar     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3149d1a0d267SMarcel Moolenaar 	/*
3150d1a0d267SMarcel Moolenaar 	 * Since we're starting a new field, save the starting offset.
3151d1a0d267SMarcel Moolenaar 	 * We'll need this later for field-related operations.
3152d1a0d267SMarcel Moolenaar 	 */
3153d1a0d267SMarcel Moolenaar 
315431337658SMarcel Moolenaar 	if (*cp != '%') {
315531337658SMarcel Moolenaar 	add_one:
315631337658SMarcel Moolenaar 	    if (xp == NULL)
315731337658SMarcel Moolenaar 		xp = cp;
315831337658SMarcel Moolenaar 
315931337658SMarcel Moolenaar 	    if (*cp == '\\' && cp[1] != '\0')
316031337658SMarcel Moolenaar 		cp += 1;
316131337658SMarcel Moolenaar 	    continue;
316231337658SMarcel Moolenaar 
316331337658SMarcel Moolenaar 	} if (cp + 1 < ep && cp[1] == '%') {
316431337658SMarcel Moolenaar 	    cp += 1;
316531337658SMarcel Moolenaar 	    goto add_one;
316631337658SMarcel Moolenaar 	}
316731337658SMarcel Moolenaar 
316831337658SMarcel Moolenaar 	if (xp) {
316931337658SMarcel Moolenaar 	    if (make_output) {
317031337658SMarcel Moolenaar 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
317131337658SMarcel Moolenaar 					       NULL, xp, cp - xp, -1,
317231337658SMarcel Moolenaar 					       need_enc, XF_ENC_UTF8);
3173d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
317431337658SMarcel Moolenaar 		    xop->xo_columns += cols;
3175d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
317631337658SMarcel Moolenaar 		    xop->xo_anchor_columns += cols;
317731337658SMarcel Moolenaar 	    }
317831337658SMarcel Moolenaar 
317931337658SMarcel Moolenaar 	    xp = NULL;
318031337658SMarcel Moolenaar 	}
318131337658SMarcel Moolenaar 
318231337658SMarcel Moolenaar 	bzero(&xf, sizeof(xf));
318331337658SMarcel Moolenaar 	xf.xf_leading_zero = -1;
318431337658SMarcel Moolenaar 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
318531337658SMarcel Moolenaar 
318631337658SMarcel Moolenaar 	/*
318731337658SMarcel Moolenaar 	 * "%@" starts an XO-specific set of flags:
318831337658SMarcel Moolenaar 	 *   @X@ - XML-only field; ignored if style isn't XML
318931337658SMarcel Moolenaar 	 */
319031337658SMarcel Moolenaar 	if (cp[1] == '@') {
319131337658SMarcel Moolenaar 	    for (cp += 2; cp < ep; cp++) {
319231337658SMarcel Moolenaar 		if (*cp == '@') {
319331337658SMarcel Moolenaar 		    break;
319431337658SMarcel Moolenaar 		}
319531337658SMarcel Moolenaar 		if (*cp == '*') {
319631337658SMarcel Moolenaar 		    /*
319731337658SMarcel Moolenaar 		     * '*' means there's a "%*.*s" value in vap that
319831337658SMarcel Moolenaar 		     * we want to ignore
319931337658SMarcel Moolenaar 		     */
3200d1a0d267SMarcel Moolenaar 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
320131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
320231337658SMarcel Moolenaar 		}
320331337658SMarcel Moolenaar 	    }
320431337658SMarcel Moolenaar 	}
320531337658SMarcel Moolenaar 
320631337658SMarcel Moolenaar 	/* Hidden fields are only visible to JSON and XML */
3207d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
320831337658SMarcel Moolenaar 	    if (style != XO_STYLE_XML
3209d1a0d267SMarcel Moolenaar 		    && !xo_style_is_encoding(xop))
321031337658SMarcel Moolenaar 		xf.xf_skip = 1;
3211d1a0d267SMarcel Moolenaar 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
321231337658SMarcel Moolenaar 	    if (style != XO_STYLE_TEXT
3213788ca347SMarcel Moolenaar 		    && xo_style(xop) != XO_STYLE_HTML)
321431337658SMarcel Moolenaar 		xf.xf_skip = 1;
321531337658SMarcel Moolenaar 	}
321631337658SMarcel Moolenaar 
321731337658SMarcel Moolenaar 	if (!make_output)
321831337658SMarcel Moolenaar 	    xf.xf_skip = 1;
321931337658SMarcel Moolenaar 
322031337658SMarcel Moolenaar 	/*
322131337658SMarcel Moolenaar 	 * Looking at one piece of a format; find the end and
322231337658SMarcel Moolenaar 	 * call snprintf.  Then advance xo_vap on our own.
322331337658SMarcel Moolenaar 	 *
322431337658SMarcel Moolenaar 	 * Note that 'n', 'v', and '$' are not supported.
322531337658SMarcel Moolenaar 	 */
322631337658SMarcel Moolenaar 	sp = cp;		/* Save start pointer */
322731337658SMarcel Moolenaar 	for (cp += 1; cp < ep; cp++) {
322831337658SMarcel Moolenaar 	    if (*cp == 'l')
322931337658SMarcel Moolenaar 		xf.xf_lflag += 1;
323031337658SMarcel Moolenaar 	    else if (*cp == 'h')
323131337658SMarcel Moolenaar 		xf.xf_hflag += 1;
323231337658SMarcel Moolenaar 	    else if (*cp == 'j')
323331337658SMarcel Moolenaar 		xf.xf_jflag += 1;
323431337658SMarcel Moolenaar 	    else if (*cp == 't')
323531337658SMarcel Moolenaar 		xf.xf_tflag += 1;
323631337658SMarcel Moolenaar 	    else if (*cp == 'z')
323731337658SMarcel Moolenaar 		xf.xf_zflag += 1;
323831337658SMarcel Moolenaar 	    else if (*cp == 'q')
323931337658SMarcel Moolenaar 		xf.xf_qflag += 1;
324031337658SMarcel Moolenaar 	    else if (*cp == '.') {
324131337658SMarcel Moolenaar 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
324231337658SMarcel Moolenaar 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
324331337658SMarcel Moolenaar 		    return -1;
324431337658SMarcel Moolenaar 		}
324531337658SMarcel Moolenaar 	    } else if (*cp == '-')
324631337658SMarcel Moolenaar 		xf.xf_seen_minus = 1;
324731337658SMarcel Moolenaar 	    else if (isdigit((int) *cp)) {
324831337658SMarcel Moolenaar 		if (xf.xf_leading_zero < 0)
324931337658SMarcel Moolenaar 		    xf.xf_leading_zero = (*cp == '0');
325031337658SMarcel Moolenaar 		xo_bump_width(&xf, *cp - '0');
325131337658SMarcel Moolenaar 	    } else if (*cp == '*') {
325231337658SMarcel Moolenaar 		xf.xf_stars += 1;
325331337658SMarcel Moolenaar 		xf.xf_star[xf.xf_dots] = 1;
3254d1a0d267SMarcel Moolenaar 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
325531337658SMarcel Moolenaar 		break;
325631337658SMarcel Moolenaar 	    else if (*cp == 'n' || *cp == 'v') {
325731337658SMarcel Moolenaar 		xo_failure(xop, "unsupported format: '%s'", fmt);
325831337658SMarcel Moolenaar 		return -1;
325931337658SMarcel Moolenaar 	    }
326031337658SMarcel Moolenaar 	}
326131337658SMarcel Moolenaar 
326231337658SMarcel Moolenaar 	if (cp == ep)
326331337658SMarcel Moolenaar 	    xo_failure(xop, "field format missing format character: %s",
326431337658SMarcel Moolenaar 			  fmt);
326531337658SMarcel Moolenaar 
326631337658SMarcel Moolenaar 	xf.xf_fc = *cp;
326731337658SMarcel Moolenaar 
3268d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
326931337658SMarcel Moolenaar 	    if (*cp == 's' || *cp == 'S') {
327031337658SMarcel Moolenaar 		/* Handle "%*.*.*s" */
327131337658SMarcel Moolenaar 		int s;
327231337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
327331337658SMarcel Moolenaar 		    if (xf.xf_star[s]) {
327431337658SMarcel Moolenaar 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
327531337658SMarcel Moolenaar 
327631337658SMarcel Moolenaar 			/* Normalize a negative width value */
327731337658SMarcel Moolenaar 			if (xf.xf_width[s] < 0) {
327831337658SMarcel Moolenaar 			    if (s == 0) {
327931337658SMarcel Moolenaar 				xf.xf_width[0] = -xf.xf_width[0];
328031337658SMarcel Moolenaar 				xf.xf_seen_minus = 1;
328131337658SMarcel Moolenaar 			    } else
328231337658SMarcel Moolenaar 				xf.xf_width[s] = -1; /* Ignore negative values */
328331337658SMarcel Moolenaar 			}
328431337658SMarcel Moolenaar 		    }
328531337658SMarcel Moolenaar 		}
328631337658SMarcel Moolenaar 	    }
328731337658SMarcel Moolenaar 	}
328831337658SMarcel Moolenaar 
328931337658SMarcel Moolenaar 	/* If no max is given, it defaults to size */
329031337658SMarcel Moolenaar 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
329131337658SMarcel Moolenaar 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
329231337658SMarcel Moolenaar 
329331337658SMarcel Moolenaar 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
329431337658SMarcel Moolenaar 	    xf.xf_lflag = 1;
329531337658SMarcel Moolenaar 
329631337658SMarcel Moolenaar 	if (!xf.xf_skip) {
329731337658SMarcel Moolenaar 	    xo_buffer_t *fbp = &xop->xo_fmt;
32988a6eceffSPhil Shafer 	    ssize_t len = cp - sp + 1;
329931337658SMarcel Moolenaar 	    if (!xo_buf_has_room(fbp, len + 1))
330031337658SMarcel Moolenaar 		return -1;
330131337658SMarcel Moolenaar 
330231337658SMarcel Moolenaar 	    char *newfmt = fbp->xb_curp;
330331337658SMarcel Moolenaar 	    memcpy(newfmt, sp, len);
330431337658SMarcel Moolenaar 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
330531337658SMarcel Moolenaar 	    newfmt[len] = '\0';
330631337658SMarcel Moolenaar 
330731337658SMarcel Moolenaar 	    /*
330831337658SMarcel Moolenaar 	     * Bad news: our strings are UTF-8, but the stock printf
330931337658SMarcel Moolenaar 	     * functions won't handle field widths for wide characters
331031337658SMarcel Moolenaar 	     * correctly.  So we have to handle this ourselves.
331131337658SMarcel Moolenaar 	     */
331231337658SMarcel Moolenaar 	    if (xop->xo_formatter == NULL
3313d1a0d267SMarcel Moolenaar 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3314d1a0d267SMarcel Moolenaar 			|| xf.xf_fc == 'm')) {
3315d1a0d267SMarcel Moolenaar 
3316d1a0d267SMarcel Moolenaar 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3317d1a0d267SMarcel Moolenaar 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3318d1a0d267SMarcel Moolenaar 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3319d1a0d267SMarcel Moolenaar 
332031337658SMarcel Moolenaar 		rc = xo_format_string(xop, xbp, flags, &xf);
332131337658SMarcel Moolenaar 
3322d1a0d267SMarcel Moolenaar 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
332331337658SMarcel Moolenaar 		    rc = xo_trim_ws(xbp, rc);
332431337658SMarcel Moolenaar 
332531337658SMarcel Moolenaar 	    } else {
33268a6eceffSPhil Shafer 		ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
332731337658SMarcel Moolenaar 
332831337658SMarcel Moolenaar 		/*
332931337658SMarcel Moolenaar 		 * For XML and HTML, we need "&<>" processing; for JSON,
333031337658SMarcel Moolenaar 		 * it's quotes.  Text gets nothing.
333131337658SMarcel Moolenaar 		 */
333231337658SMarcel Moolenaar 		switch (style) {
333331337658SMarcel Moolenaar 		case XO_STYLE_XML:
333431337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
333531337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3336ee5cf116SPhil Shafer 		    /* FALLTHRU */
333731337658SMarcel Moolenaar 		case XO_STYLE_HTML:
333831337658SMarcel Moolenaar 		    rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
333931337658SMarcel Moolenaar 		    break;
334031337658SMarcel Moolenaar 
334131337658SMarcel Moolenaar 		case XO_STYLE_JSON:
334231337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
334331337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3344d1a0d267SMarcel Moolenaar 		    rc = xo_escape_json(xbp, rc, 0);
3345d1a0d267SMarcel Moolenaar 		    break;
3346d1a0d267SMarcel Moolenaar 
3347d1a0d267SMarcel Moolenaar 		case XO_STYLE_SDPARAMS:
3348d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3349d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3350d1a0d267SMarcel Moolenaar 		    rc = xo_escape_sdparams(xbp, rc, 0);
3351d1a0d267SMarcel Moolenaar 		    break;
3352d1a0d267SMarcel Moolenaar 
3353d1a0d267SMarcel Moolenaar 		case XO_STYLE_ENCODER:
3354d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3355d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
335631337658SMarcel Moolenaar 		    break;
335731337658SMarcel Moolenaar 		}
335831337658SMarcel Moolenaar 
335931337658SMarcel Moolenaar 		/*
3360d1a0d267SMarcel Moolenaar 		 * We can assume all the non-%s data we've
3361d1a0d267SMarcel Moolenaar 		 * added is ASCII, so the columns and bytes are the
3362d1a0d267SMarcel Moolenaar 		 * same.  xo_format_string handles all the fancy
3363d1a0d267SMarcel Moolenaar 		 * string conversions and updates xo_anchor_columns
3364d1a0d267SMarcel Moolenaar 		 * accordingly.
336531337658SMarcel Moolenaar 		 */
3366d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
336731337658SMarcel Moolenaar 		    xop->xo_columns += columns;
3368d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
336931337658SMarcel Moolenaar 		    xop->xo_anchor_columns += columns;
337031337658SMarcel Moolenaar 	    }
337131337658SMarcel Moolenaar 
337231337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
337331337658SMarcel Moolenaar 	}
337431337658SMarcel Moolenaar 
337531337658SMarcel Moolenaar 	/*
337631337658SMarcel Moolenaar 	 * Now for the tricky part: we need to move the argument pointer
337731337658SMarcel Moolenaar 	 * along by the amount needed.
337831337658SMarcel Moolenaar 	 */
3379d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
338031337658SMarcel Moolenaar 
338131337658SMarcel Moolenaar 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
338231337658SMarcel Moolenaar 		/*
338331337658SMarcel Moolenaar 		 * The 'S' and 's' formats are normally handled in
338431337658SMarcel Moolenaar 		 * xo_format_string, but if we skipped it, then we
338531337658SMarcel Moolenaar 		 * need to pop it.
338631337658SMarcel Moolenaar 		 */
338731337658SMarcel Moolenaar 		if (xf.xf_skip)
338831337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, char *);
338931337658SMarcel Moolenaar 
3390d1a0d267SMarcel Moolenaar 	    } else if (xf.xf_fc == 'm') {
3391d1a0d267SMarcel Moolenaar 		/* Nothing on the stack for "%m" */
3392d1a0d267SMarcel Moolenaar 
339331337658SMarcel Moolenaar 	    } else {
339431337658SMarcel Moolenaar 		int s;
339531337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
339631337658SMarcel Moolenaar 		    if (xf.xf_star[s])
339731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
339831337658SMarcel Moolenaar 		}
339931337658SMarcel Moolenaar 
340031337658SMarcel Moolenaar 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
340131337658SMarcel Moolenaar 		    if (xf.xf_hflag > 1) {
340231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
340331337658SMarcel Moolenaar 
340431337658SMarcel Moolenaar 		    } else if (xf.xf_hflag > 0) {
340531337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
340631337658SMarcel Moolenaar 
340731337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 1) {
340831337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long long);
340931337658SMarcel Moolenaar 
341031337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 0) {
341131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long);
341231337658SMarcel Moolenaar 
341331337658SMarcel Moolenaar 		    } else if (xf.xf_jflag > 0) {
341431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, intmax_t);
341531337658SMarcel Moolenaar 
341631337658SMarcel Moolenaar 		    } else if (xf.xf_tflag > 0) {
341731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, ptrdiff_t);
341831337658SMarcel Moolenaar 
341931337658SMarcel Moolenaar 		    } else if (xf.xf_zflag > 0) {
342031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, size_t);
342131337658SMarcel Moolenaar 
342231337658SMarcel Moolenaar 		    } else if (xf.xf_qflag > 0) {
342331337658SMarcel Moolenaar 			va_arg(xop->xo_vap, quad_t);
342431337658SMarcel Moolenaar 
342531337658SMarcel Moolenaar 		    } else {
342631337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
342731337658SMarcel Moolenaar 		    }
342831337658SMarcel Moolenaar 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
342931337658SMarcel Moolenaar 		    if (xf.xf_lflag)
343031337658SMarcel Moolenaar 			va_arg(xop->xo_vap, long double);
343131337658SMarcel Moolenaar 		    else
343231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, double);
343331337658SMarcel Moolenaar 
343431337658SMarcel Moolenaar 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
343531337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, wint_t);
343631337658SMarcel Moolenaar 
343731337658SMarcel Moolenaar 		else if (xf.xf_fc == 'c')
343831337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, int);
343931337658SMarcel Moolenaar 
344031337658SMarcel Moolenaar 		else if (xf.xf_fc == 'p')
344131337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, void *);
344231337658SMarcel Moolenaar 	    }
344331337658SMarcel Moolenaar 	}
344431337658SMarcel Moolenaar     }
344531337658SMarcel Moolenaar 
344631337658SMarcel Moolenaar     if (xp) {
344731337658SMarcel Moolenaar 	if (make_output) {
344831337658SMarcel Moolenaar 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
344931337658SMarcel Moolenaar 					   NULL, xp, cp - xp, -1,
345031337658SMarcel Moolenaar 					   need_enc, XF_ENC_UTF8);
3451d1a0d267SMarcel Moolenaar 
3452d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
345331337658SMarcel Moolenaar 		xop->xo_columns += cols;
3454d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
345531337658SMarcel Moolenaar 		xop->xo_anchor_columns += cols;
345631337658SMarcel Moolenaar 	}
345731337658SMarcel Moolenaar 
345831337658SMarcel Moolenaar 	xp = NULL;
345931337658SMarcel Moolenaar     }
346031337658SMarcel Moolenaar 
3461d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS) {
3462d1a0d267SMarcel Moolenaar 	/*
3463d1a0d267SMarcel Moolenaar 	 * Handle gettext()ing the field by looking up the value
3464d1a0d267SMarcel Moolenaar 	 * and then copying it in, while converting to locale, if
3465d1a0d267SMarcel Moolenaar 	 * needed.
3466d1a0d267SMarcel Moolenaar 	 */
34678a6eceffSPhil Shafer 	ssize_t new_cols = xo_format_gettext(xop, flags, start_offset,
3468d1a0d267SMarcel Moolenaar 					 old_cols, real_need_enc);
3469d1a0d267SMarcel Moolenaar 
3470d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_COLUMNS))
3471d1a0d267SMarcel Moolenaar 	    xop->xo_columns += new_cols - old_cols;
3472d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3473d1a0d267SMarcel Moolenaar 	    xop->xo_anchor_columns += new_cols - old_cols;
3474d1a0d267SMarcel Moolenaar     }
3475d1a0d267SMarcel Moolenaar 
347631337658SMarcel Moolenaar     return 0;
347731337658SMarcel Moolenaar }
347831337658SMarcel Moolenaar 
347931337658SMarcel Moolenaar static char *
348031337658SMarcel Moolenaar xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
348131337658SMarcel Moolenaar {
348231337658SMarcel Moolenaar     char *cp = encoding;
348331337658SMarcel Moolenaar 
348431337658SMarcel Moolenaar     if (cp[0] != '%' || !isdigit((int) cp[1]))
348531337658SMarcel Moolenaar 	return encoding;
348631337658SMarcel Moolenaar 
348731337658SMarcel Moolenaar     for (cp += 2; *cp; cp++) {
348831337658SMarcel Moolenaar 	if (!isdigit((int) *cp))
348931337658SMarcel Moolenaar 	    break;
349031337658SMarcel Moolenaar     }
349131337658SMarcel Moolenaar 
349231337658SMarcel Moolenaar     cp -= 1;
349331337658SMarcel Moolenaar     *cp = '%';
349431337658SMarcel Moolenaar 
349531337658SMarcel Moolenaar     return cp;
349631337658SMarcel Moolenaar }
349731337658SMarcel Moolenaar 
349831337658SMarcel Moolenaar static void
3499788ca347SMarcel Moolenaar xo_color_append_html (xo_handle_t *xop)
3500788ca347SMarcel Moolenaar {
3501788ca347SMarcel Moolenaar     /*
3502788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3503788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3504788ca347SMarcel Moolenaar      */
3505788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3506788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3507788ca347SMarcel Moolenaar 
3508788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3509788ca347SMarcel Moolenaar     }
3510788ca347SMarcel Moolenaar }
3511788ca347SMarcel Moolenaar 
3512d1a0d267SMarcel Moolenaar /*
3513d1a0d267SMarcel Moolenaar  * A wrapper for humanize_number that autoscales, since the
3514d1a0d267SMarcel Moolenaar  * HN_AUTOSCALE flag scales as needed based on the size of
3515d1a0d267SMarcel Moolenaar  * the output buffer, not the size of the value.  I also
3516d1a0d267SMarcel Moolenaar  * wish HN_DECIMAL was more imperative, without the <10
3517d1a0d267SMarcel Moolenaar  * test.  But the boat only goes where we want when we hold
3518d1a0d267SMarcel Moolenaar  * the rudder, so xo_humanize fixes part of the problem.
3519d1a0d267SMarcel Moolenaar  */
35208a6eceffSPhil Shafer static ssize_t
35218a6eceffSPhil Shafer xo_humanize (char *buf, ssize_t len, uint64_t value, int flags)
3522d1a0d267SMarcel Moolenaar {
3523d1a0d267SMarcel Moolenaar     int scale = 0;
3524d1a0d267SMarcel Moolenaar 
3525d1a0d267SMarcel Moolenaar     if (value) {
3526d1a0d267SMarcel Moolenaar 	uint64_t left = value;
3527d1a0d267SMarcel Moolenaar 
3528d1a0d267SMarcel Moolenaar 	if (flags & HN_DIVISOR_1000) {
3529d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3530d1a0d267SMarcel Moolenaar 		left /= 1000;
3531d1a0d267SMarcel Moolenaar 	} else {
3532d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3533d1a0d267SMarcel Moolenaar 		left /= 1024;
3534d1a0d267SMarcel Moolenaar 	}
3535d1a0d267SMarcel Moolenaar 	scale -= 1;
3536d1a0d267SMarcel Moolenaar     }
3537d1a0d267SMarcel Moolenaar 
3538d1a0d267SMarcel Moolenaar     return xo_humanize_number(buf, len, value, "", scale, flags);
3539d1a0d267SMarcel Moolenaar }
3540d1a0d267SMarcel Moolenaar 
3541d1a0d267SMarcel Moolenaar /*
3542d1a0d267SMarcel Moolenaar  * This is an area where we can save information from the handle for
3543d1a0d267SMarcel Moolenaar  * later restoration.  We need to know what data was rendered to know
3544d1a0d267SMarcel Moolenaar  * what needs cleaned up.
3545d1a0d267SMarcel Moolenaar  */
3546d1a0d267SMarcel Moolenaar typedef struct xo_humanize_save_s {
35478a6eceffSPhil Shafer     ssize_t xhs_offset;		/* Saved xo_offset */
35488a6eceffSPhil Shafer     ssize_t xhs_columns;	/* Saved xo_columns */
35498a6eceffSPhil Shafer     ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */
3550d1a0d267SMarcel Moolenaar } xo_humanize_save_t;
3551d1a0d267SMarcel Moolenaar 
3552d1a0d267SMarcel Moolenaar /*
3553d1a0d267SMarcel Moolenaar  * Format a "humanized" value for a numeric, meaning something nice
3554d1a0d267SMarcel Moolenaar  * like "44M" instead of "44470272".  We autoscale, choosing the
3555d1a0d267SMarcel Moolenaar  * most appropriate value for K/M/G/T/P/E based on the value given.
3556d1a0d267SMarcel Moolenaar  */
3557d1a0d267SMarcel Moolenaar static void
3558d1a0d267SMarcel Moolenaar xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3559d1a0d267SMarcel Moolenaar 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3560d1a0d267SMarcel Moolenaar {
3561d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3562d1a0d267SMarcel Moolenaar 	return;
3563d1a0d267SMarcel Moolenaar 
35648a6eceffSPhil Shafer     ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
3565d1a0d267SMarcel Moolenaar     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3566d1a0d267SMarcel Moolenaar 	return;
3567d1a0d267SMarcel Moolenaar 
3568d1a0d267SMarcel Moolenaar     /*
3569d1a0d267SMarcel Moolenaar      * We have a string that's allegedly a number. We want to
3570d1a0d267SMarcel Moolenaar      * humanize it, which means turning it back into a number
3571d1a0d267SMarcel Moolenaar      * and calling xo_humanize_number on it.
3572d1a0d267SMarcel Moolenaar      */
3573d1a0d267SMarcel Moolenaar     uint64_t value;
3574d1a0d267SMarcel Moolenaar     char *ep;
3575d1a0d267SMarcel Moolenaar 
3576d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3577d1a0d267SMarcel Moolenaar 
3578d1a0d267SMarcel Moolenaar     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3579d1a0d267SMarcel Moolenaar     if (!(value == ULLONG_MAX && errno == ERANGE)
3580d1a0d267SMarcel Moolenaar 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3581d1a0d267SMarcel Moolenaar 	/*
3582d1a0d267SMarcel Moolenaar 	 * There are few values where humanize_number needs
3583d1a0d267SMarcel Moolenaar 	 * more bytes than the original value.  I've used
3584d1a0d267SMarcel Moolenaar 	 * 10 as a rectal number to cover those scenarios.
3585d1a0d267SMarcel Moolenaar 	 */
3586d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, 10)) {
3587d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3588d1a0d267SMarcel Moolenaar 
35898a6eceffSPhil Shafer 	    ssize_t rc;
35908a6eceffSPhil Shafer 	    ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3591d1a0d267SMarcel Moolenaar 	    int hn_flags = HN_NOSPACE; /* On by default */
3592d1a0d267SMarcel Moolenaar 
3593d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_SPACE)
3594d1a0d267SMarcel Moolenaar 		hn_flags &= ~HN_NOSPACE;
3595d1a0d267SMarcel Moolenaar 
3596d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_DECIMAL)
3597d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DECIMAL;
3598d1a0d267SMarcel Moolenaar 
3599d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_1000)
3600d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DIVISOR_1000;
3601d1a0d267SMarcel Moolenaar 
36028a6eceffSPhil Shafer 	    rc = xo_humanize(xbp->xb_curp, left, value, hn_flags);
3603d1a0d267SMarcel Moolenaar 	    if (rc > 0) {
3604d1a0d267SMarcel Moolenaar 		xbp->xb_curp += rc;
3605d1a0d267SMarcel Moolenaar 		xop->xo_columns = savep->xhs_columns + rc;
3606d1a0d267SMarcel Moolenaar 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3607d1a0d267SMarcel Moolenaar 	    }
3608d1a0d267SMarcel Moolenaar 	}
3609d1a0d267SMarcel Moolenaar     }
3610d1a0d267SMarcel Moolenaar }
3611d1a0d267SMarcel Moolenaar 
3612788ca347SMarcel Moolenaar static void
361331337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
36148a6eceffSPhil Shafer 		   const char *name, ssize_t nlen,
36158a6eceffSPhil Shafer 		   const char *value, ssize_t vlen,
36168a6eceffSPhil Shafer 		   const char *encoding, ssize_t elen)
361731337658SMarcel Moolenaar {
361831337658SMarcel Moolenaar     static char div_start[] = "<div class=\"";
361931337658SMarcel Moolenaar     static char div_tag[] = "\" data-tag=\"";
362031337658SMarcel Moolenaar     static char div_xpath[] = "\" data-xpath=\"";
362131337658SMarcel Moolenaar     static char div_key[] = "\" data-key=\"key";
362231337658SMarcel Moolenaar     static char div_end[] = "\">";
362331337658SMarcel Moolenaar     static char div_close[] = "</div>";
362431337658SMarcel Moolenaar 
3625a321cc5dSPhil Shafer     /* The encoding format defaults to the normal format */
3626a321cc5dSPhil Shafer     if (encoding == NULL) {
3627a321cc5dSPhil Shafer 	char *enc  = alloca(vlen + 1);
3628a321cc5dSPhil Shafer 	memcpy(enc, value, vlen);
3629a321cc5dSPhil Shafer 	enc[vlen] = '\0';
3630a321cc5dSPhil Shafer 	encoding = xo_fix_encoding(xop, enc);
3631a321cc5dSPhil Shafer 	elen = strlen(encoding);
3632a321cc5dSPhil Shafer     }
3633a321cc5dSPhil Shafer 
363431337658SMarcel Moolenaar     /*
363531337658SMarcel Moolenaar      * To build our XPath predicate, we need to save the va_list before
363631337658SMarcel Moolenaar      * we format our data, and then restore it before we format the
363731337658SMarcel Moolenaar      * xpath expression.
363831337658SMarcel Moolenaar      * Display-only keys implies that we've got an encode-only key
363931337658SMarcel Moolenaar      * elsewhere, so we don't use them from making predicates.
364031337658SMarcel Moolenaar      */
364131337658SMarcel Moolenaar     int need_predidate =
364231337658SMarcel Moolenaar 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
36438a6eceffSPhil Shafer 	 && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0;
364431337658SMarcel Moolenaar 
364531337658SMarcel Moolenaar     if (need_predidate) {
364631337658SMarcel Moolenaar 	va_list va_local;
364731337658SMarcel Moolenaar 
364831337658SMarcel Moolenaar 	va_copy(va_local, xop->xo_vap);
364931337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
365031337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
365131337658SMarcel Moolenaar 
365231337658SMarcel Moolenaar 	/*
365331337658SMarcel Moolenaar 	 * Build an XPath predicate expression to match this key.
365431337658SMarcel Moolenaar 	 * We use the format buffer.
365531337658SMarcel Moolenaar 	 */
365631337658SMarcel Moolenaar 	xo_buffer_t *pbp = &xop->xo_predicate;
365731337658SMarcel Moolenaar 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
365831337658SMarcel Moolenaar 
365931337658SMarcel Moolenaar 	xo_buf_append(pbp, "[", 1);
366031337658SMarcel Moolenaar 	xo_buf_escape(xop, pbp, name, nlen, 0);
3661d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
366231337658SMarcel Moolenaar 	    xo_buf_append(pbp, " = '", 4);
366331337658SMarcel Moolenaar 	else
366431337658SMarcel Moolenaar 	    xo_buf_append(pbp, "='", 2);
366531337658SMarcel Moolenaar 
3666d1a0d267SMarcel Moolenaar 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3667d1a0d267SMarcel Moolenaar 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3668d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
366931337658SMarcel Moolenaar 
367031337658SMarcel Moolenaar 	xo_buf_append(pbp, "']", 2);
367131337658SMarcel Moolenaar 
367231337658SMarcel Moolenaar 	/* Now we record this predicate expression in the stack */
367331337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
36748a6eceffSPhil Shafer 	ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
36758a6eceffSPhil Shafer 	ssize_t dlen = pbp->xb_curp - pbp->xb_bufp;
367631337658SMarcel Moolenaar 
367731337658SMarcel Moolenaar 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
367831337658SMarcel Moolenaar 	if (cp) {
367931337658SMarcel Moolenaar 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
368031337658SMarcel Moolenaar 	    cp[olen + dlen] = '\0';
368131337658SMarcel Moolenaar 	    xsp->xs_keys = cp;
368231337658SMarcel Moolenaar 	}
368331337658SMarcel Moolenaar 
368431337658SMarcel Moolenaar 	/* Now we reset the xo_vap as if we were never here */
368531337658SMarcel Moolenaar 	va_end(xop->xo_vap);
368631337658SMarcel Moolenaar 	va_copy(xop->xo_vap, va_local);
368731337658SMarcel Moolenaar 	va_end(va_local);
368831337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
368931337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
369031337658SMarcel Moolenaar     }
369131337658SMarcel Moolenaar 
369231337658SMarcel Moolenaar     if (flags & XFF_ENCODE_ONLY) {
369331337658SMarcel Moolenaar 	/*
3694ee5cf116SPhil Shafer 	 * Even if this is encode-only, we need to go through the
369531337658SMarcel Moolenaar 	 * work of formatting it to make sure the args are cleared
369631337658SMarcel Moolenaar 	 * from xo_vap.
369731337658SMarcel Moolenaar 	 */
3698d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, encoding, elen,
369931337658SMarcel Moolenaar 		       flags | XFF_NO_OUTPUT);
370031337658SMarcel Moolenaar 	return;
370131337658SMarcel Moolenaar     }
370231337658SMarcel Moolenaar 
370331337658SMarcel Moolenaar     xo_line_ensure_open(xop, 0);
370431337658SMarcel Moolenaar 
3705d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
370631337658SMarcel Moolenaar 	xo_buf_indent(xop, xop->xo_indent_by);
370731337658SMarcel Moolenaar 
370831337658SMarcel Moolenaar     xo_data_append(xop, div_start, sizeof(div_start) - 1);
370931337658SMarcel Moolenaar     xo_data_append(xop, class, strlen(class));
371031337658SMarcel Moolenaar 
3711788ca347SMarcel Moolenaar     /*
3712788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3713788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3714788ca347SMarcel Moolenaar      */
3715788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3716788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3717788ca347SMarcel Moolenaar 
3718788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3719788ca347SMarcel Moolenaar     }
3720788ca347SMarcel Moolenaar 
372131337658SMarcel Moolenaar     if (name) {
372231337658SMarcel Moolenaar 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
372331337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
372431337658SMarcel Moolenaar 
372531337658SMarcel Moolenaar 	/*
372631337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
372731337658SMarcel Moolenaar 	 */
3728d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
3729d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
373031337658SMarcel Moolenaar 	    /*
373131337658SMarcel Moolenaar 	     * Note: We need the '+1' here because we know we've not
373231337658SMarcel Moolenaar 	     * added the closing quote.  We add one, knowing the quote
373331337658SMarcel Moolenaar 	     * will be added shortly.
373431337658SMarcel Moolenaar 	     */
373531337658SMarcel Moolenaar 	    xop->xo_units_offset =
373631337658SMarcel Moolenaar 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
373731337658SMarcel Moolenaar 	}
373831337658SMarcel Moolenaar 
3739d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_XPATH)) {
374031337658SMarcel Moolenaar 	    int i;
374131337658SMarcel Moolenaar 	    xo_stack_t *xsp;
374231337658SMarcel Moolenaar 
374331337658SMarcel Moolenaar 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
374431337658SMarcel Moolenaar 	    if (xop->xo_leading_xpath)
374531337658SMarcel Moolenaar 		xo_data_append(xop, xop->xo_leading_xpath,
374631337658SMarcel Moolenaar 			       strlen(xop->xo_leading_xpath));
374731337658SMarcel Moolenaar 
374831337658SMarcel Moolenaar 	    for (i = 0; i <= xop->xo_depth; i++) {
374931337658SMarcel Moolenaar 		xsp = &xop->xo_stack[i];
375031337658SMarcel Moolenaar 		if (xsp->xs_name == NULL)
375131337658SMarcel Moolenaar 		    continue;
375231337658SMarcel Moolenaar 
3753545ddfbeSMarcel Moolenaar 		/*
3754545ddfbeSMarcel Moolenaar 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3755545ddfbeSMarcel Moolenaar 		 * are directly under XSS_OPEN_INSTANCE frames so we
3756545ddfbeSMarcel Moolenaar 		 * don't need to put these in our XPath expressions.
3757545ddfbeSMarcel Moolenaar 		 */
3758545ddfbeSMarcel Moolenaar 		if (xsp->xs_state == XSS_OPEN_LIST
3759545ddfbeSMarcel Moolenaar 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3760545ddfbeSMarcel Moolenaar 		    continue;
3761545ddfbeSMarcel Moolenaar 
376231337658SMarcel Moolenaar 		xo_data_append(xop, "/", 1);
376331337658SMarcel Moolenaar 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
376431337658SMarcel Moolenaar 		if (xsp->xs_keys) {
376531337658SMarcel Moolenaar 		    /* Don't show keys for the key field */
376631337658SMarcel Moolenaar 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
376731337658SMarcel Moolenaar 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
376831337658SMarcel Moolenaar 		}
376931337658SMarcel Moolenaar 	    }
377031337658SMarcel Moolenaar 
377131337658SMarcel Moolenaar 	    xo_data_append(xop, "/", 1);
377231337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
377331337658SMarcel Moolenaar 	}
377431337658SMarcel Moolenaar 
3775d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
377631337658SMarcel Moolenaar 	    static char in_type[] = "\" data-type=\"";
377731337658SMarcel Moolenaar 	    static char in_help[] = "\" data-help=\"";
377831337658SMarcel Moolenaar 
377931337658SMarcel Moolenaar 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
378031337658SMarcel Moolenaar 	    if (xip) {
378131337658SMarcel Moolenaar 		if (xip->xi_type) {
378231337658SMarcel Moolenaar 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
378331337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
378431337658SMarcel Moolenaar 		}
378531337658SMarcel Moolenaar 		if (xip->xi_help) {
378631337658SMarcel Moolenaar 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
378731337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
378831337658SMarcel Moolenaar 		}
378931337658SMarcel Moolenaar 	    }
379031337658SMarcel Moolenaar 	}
379131337658SMarcel Moolenaar 
3792d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
379331337658SMarcel Moolenaar 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
379431337658SMarcel Moolenaar     }
379531337658SMarcel Moolenaar 
3796d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
37978a6eceffSPhil Shafer     ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp;
3798d1a0d267SMarcel Moolenaar 
379931337658SMarcel Moolenaar     xo_data_append(xop, div_end, sizeof(div_end) - 1);
380031337658SMarcel Moolenaar 
3801d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
3802d1a0d267SMarcel Moolenaar 
3803d1a0d267SMarcel Moolenaar     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
3804d1a0d267SMarcel Moolenaar     save.xhs_columns = xop->xo_columns;
3805d1a0d267SMarcel Moolenaar     save.xhs_anchor_columns = xop->xo_anchor_columns;
3806d1a0d267SMarcel Moolenaar 
3807d1a0d267SMarcel Moolenaar     xo_do_format_field(xop, NULL, value, vlen, flags);
3808d1a0d267SMarcel Moolenaar 
3809d1a0d267SMarcel Moolenaar     if (flags & XFF_HUMANIZE) {
3810d1a0d267SMarcel Moolenaar 	/*
3811d1a0d267SMarcel Moolenaar 	 * Unlike text style, we want to retain the original value and
3812d1a0d267SMarcel Moolenaar 	 * stuff it into the "data-number" attribute.
3813d1a0d267SMarcel Moolenaar 	 */
3814d1a0d267SMarcel Moolenaar 	static const char div_number[] = "\" data-number=\"";
38158a6eceffSPhil Shafer 	ssize_t div_len = sizeof(div_number) - 1;
3816d1a0d267SMarcel Moolenaar 
38178a6eceffSPhil Shafer 	ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
38188a6eceffSPhil Shafer 	ssize_t olen = end_offset - save.xhs_offset;
3819d1a0d267SMarcel Moolenaar 
3820d1a0d267SMarcel Moolenaar 	char *cp = alloca(olen + 1);
3821d1a0d267SMarcel Moolenaar 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
3822d1a0d267SMarcel Moolenaar 	cp[olen] = '\0';
3823d1a0d267SMarcel Moolenaar 
3824d1a0d267SMarcel Moolenaar 	xo_format_humanize(xop, xbp, &save, flags);
3825d1a0d267SMarcel Moolenaar 
3826d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, div_len + olen)) {
38278a6eceffSPhil Shafer 	    ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp;
3828d1a0d267SMarcel Moolenaar 
3829d1a0d267SMarcel Moolenaar 
3830d1a0d267SMarcel Moolenaar 	    /* Move the humanized string off to the left */
3831d1a0d267SMarcel Moolenaar 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
3832d1a0d267SMarcel Moolenaar 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
3833d1a0d267SMarcel Moolenaar 
3834d1a0d267SMarcel Moolenaar 	    /* Copy the data_number attribute name */
3835d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
3836d1a0d267SMarcel Moolenaar 
3837d1a0d267SMarcel Moolenaar 	    /* Copy the original long value */
3838d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
3839d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += div_len + olen;
3840d1a0d267SMarcel Moolenaar 	}
3841d1a0d267SMarcel Moolenaar     }
384231337658SMarcel Moolenaar 
384331337658SMarcel Moolenaar     xo_data_append(xop, div_close, sizeof(div_close) - 1);
384431337658SMarcel Moolenaar 
3845d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
384631337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
384731337658SMarcel Moolenaar }
384831337658SMarcel Moolenaar 
384931337658SMarcel Moolenaar static void
38508a6eceffSPhil Shafer xo_format_text (xo_handle_t *xop, const char *str, ssize_t len)
385131337658SMarcel Moolenaar {
3852788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
385331337658SMarcel Moolenaar     case XO_STYLE_TEXT:
385431337658SMarcel Moolenaar 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
385531337658SMarcel Moolenaar 	break;
385631337658SMarcel Moolenaar 
385731337658SMarcel Moolenaar     case XO_STYLE_HTML:
385831337658SMarcel Moolenaar 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
385931337658SMarcel Moolenaar 	break;
386031337658SMarcel Moolenaar     }
386131337658SMarcel Moolenaar }
386231337658SMarcel Moolenaar 
386331337658SMarcel Moolenaar static void
386442ff34c3SPhil Shafer xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
38658a6eceffSPhil Shafer 		 const char *str, ssize_t len)
386631337658SMarcel Moolenaar {
3867d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
38688a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
3869d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
3870d1a0d267SMarcel Moolenaar 
3871788ca347SMarcel Moolenaar     static char div_open[] = "<div class=\"title";
3872788ca347SMarcel Moolenaar     static char div_middle[] = "\">";
387331337658SMarcel Moolenaar     static char div_close[] = "</div>";
387431337658SMarcel Moolenaar 
3875545ddfbeSMarcel Moolenaar     if (flen == 0) {
3876545ddfbeSMarcel Moolenaar 	fmt = "%s";
3877545ddfbeSMarcel Moolenaar 	flen = 2;
3878545ddfbeSMarcel Moolenaar     }
3879545ddfbeSMarcel Moolenaar 
3880788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
388131337658SMarcel Moolenaar     case XO_STYLE_XML:
388231337658SMarcel Moolenaar     case XO_STYLE_JSON:
3883d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
3884d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
388531337658SMarcel Moolenaar 	/*
388631337658SMarcel Moolenaar 	 * Even though we don't care about text, we need to do
388731337658SMarcel Moolenaar 	 * enough parsing work to skip over the right bits of xo_vap.
388831337658SMarcel Moolenaar 	 */
388931337658SMarcel Moolenaar 	if (len == 0)
3890d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
389131337658SMarcel Moolenaar 	return;
389231337658SMarcel Moolenaar     }
389331337658SMarcel Moolenaar 
389431337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
38958a6eceffSPhil Shafer     ssize_t start = xbp->xb_curp - xbp->xb_bufp;
38968a6eceffSPhil Shafer     ssize_t left = xbp->xb_size - start;
38978a6eceffSPhil Shafer     ssize_t rc;
389831337658SMarcel Moolenaar 
3899788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
390031337658SMarcel Moolenaar 	xo_line_ensure_open(xop, 0);
3901d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
390231337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
390331337658SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
3904788ca347SMarcel Moolenaar 	xo_color_append_html(xop);
3905788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
390631337658SMarcel Moolenaar     }
390731337658SMarcel Moolenaar 
390831337658SMarcel Moolenaar     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
390931337658SMarcel Moolenaar     if (len) {
391031337658SMarcel Moolenaar 	char *newfmt = alloca(flen + 1);
391131337658SMarcel Moolenaar 	memcpy(newfmt, fmt, flen);
391231337658SMarcel Moolenaar 	newfmt[flen] = '\0';
391331337658SMarcel Moolenaar 
391431337658SMarcel Moolenaar 	/* If len is non-zero, the format string apply to the name */
391531337658SMarcel Moolenaar 	char *newstr = alloca(len + 1);
391631337658SMarcel Moolenaar 	memcpy(newstr, str, len);
391731337658SMarcel Moolenaar 	newstr[len] = '\0';
391831337658SMarcel Moolenaar 
391931337658SMarcel Moolenaar 	if (newstr[len - 1] == 's') {
392031337658SMarcel Moolenaar 	    char *bp;
392131337658SMarcel Moolenaar 
392231337658SMarcel Moolenaar 	    rc = snprintf(NULL, 0, newfmt, newstr);
392331337658SMarcel Moolenaar 	    if (rc > 0) {
392431337658SMarcel Moolenaar 		/*
392531337658SMarcel Moolenaar 		 * We have to do this the hard way, since we might need
392631337658SMarcel Moolenaar 		 * the columns.
392731337658SMarcel Moolenaar 		 */
392831337658SMarcel Moolenaar 		bp = alloca(rc + 1);
392931337658SMarcel Moolenaar 		rc = snprintf(bp, rc + 1, newfmt, newstr);
3930d1a0d267SMarcel Moolenaar 
3931d1a0d267SMarcel Moolenaar 		xo_data_append_content(xop, bp, rc, flags);
393231337658SMarcel Moolenaar 	    }
393331337658SMarcel Moolenaar 	    goto move_along;
393431337658SMarcel Moolenaar 
393531337658SMarcel Moolenaar 	} else {
393631337658SMarcel Moolenaar 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3937d1a0d267SMarcel Moolenaar 	    if (rc >= left) {
393831337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, rc))
393931337658SMarcel Moolenaar 		    return;
394031337658SMarcel Moolenaar 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
394131337658SMarcel Moolenaar 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
394231337658SMarcel Moolenaar 	    }
394331337658SMarcel Moolenaar 
394431337658SMarcel Moolenaar 	    if (rc > 0) {
3945d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
394631337658SMarcel Moolenaar 		    xop->xo_columns += rc;
3947d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
394831337658SMarcel Moolenaar 		    xop->xo_anchor_columns += rc;
394931337658SMarcel Moolenaar 	    }
395031337658SMarcel Moolenaar 	}
395131337658SMarcel Moolenaar 
395231337658SMarcel Moolenaar     } else {
3953d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
395431337658SMarcel Moolenaar 
3955d1a0d267SMarcel Moolenaar 	/* xo_do_format_field moved curp, so we need to reset it */
395631337658SMarcel Moolenaar 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
395731337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + start;
395831337658SMarcel Moolenaar     }
395931337658SMarcel Moolenaar 
396031337658SMarcel Moolenaar     /* If we're styling HTML, then we need to escape it */
3961788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
396231337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
396331337658SMarcel Moolenaar     }
396431337658SMarcel Moolenaar 
396531337658SMarcel Moolenaar     if (rc > 0)
396631337658SMarcel Moolenaar 	xbp->xb_curp += rc;
396731337658SMarcel Moolenaar 
396831337658SMarcel Moolenaar  move_along:
3969788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
397031337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
3971d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
397231337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
397331337658SMarcel Moolenaar     }
397431337658SMarcel Moolenaar }
397531337658SMarcel Moolenaar 
397631337658SMarcel Moolenaar static void
397731337658SMarcel Moolenaar xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
397831337658SMarcel Moolenaar {
397931337658SMarcel Moolenaar     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
398031337658SMarcel Moolenaar 	xo_data_append(xop, ",", 1);
3981d1a0d267SMarcel Moolenaar 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
398231337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
398331337658SMarcel Moolenaar     } else
398431337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
398531337658SMarcel Moolenaar }
398631337658SMarcel Moolenaar 
398731337658SMarcel Moolenaar #if 0
398831337658SMarcel Moolenaar /* Useful debugging function */
398931337658SMarcel Moolenaar void
399031337658SMarcel Moolenaar xo_arg (xo_handle_t *xop);
399131337658SMarcel Moolenaar void
399231337658SMarcel Moolenaar xo_arg (xo_handle_t *xop)
399331337658SMarcel Moolenaar {
399431337658SMarcel Moolenaar     xop = xo_default(xop);
399531337658SMarcel Moolenaar     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
399631337658SMarcel Moolenaar }
399731337658SMarcel Moolenaar #endif /* 0 */
399831337658SMarcel Moolenaar 
399931337658SMarcel Moolenaar static void
40008a6eceffSPhil Shafer xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
40018a6eceffSPhil Shafer                 const char *format, ssize_t flen,
40028a6eceffSPhil Shafer                 const char *encoding, ssize_t elen, xo_xff_flags_t flags)
400331337658SMarcel Moolenaar {
4004d1a0d267SMarcel Moolenaar     int pretty = XOF_ISSET(xop, XOF_PRETTY);
400531337658SMarcel Moolenaar     int quote;
400631337658SMarcel Moolenaar 
4007545ddfbeSMarcel Moolenaar     /*
4008545ddfbeSMarcel Moolenaar      * Before we emit a value, we need to know that the frame is ready.
4009545ddfbeSMarcel Moolenaar      */
4010545ddfbeSMarcel Moolenaar     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4011545ddfbeSMarcel Moolenaar 
4012545ddfbeSMarcel Moolenaar     if (flags & XFF_LEAF_LIST) {
4013545ddfbeSMarcel Moolenaar 	/*
4014545ddfbeSMarcel Moolenaar 	 * Check if we've already started to emit normal leafs
4015545ddfbeSMarcel Moolenaar 	 * or if we're not in a leaf list.
4016545ddfbeSMarcel Moolenaar 	 */
4017545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4018545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4019545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4020545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4021545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4022545ddfbeSMarcel Moolenaar 
40238a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4024545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4025545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4026545ddfbeSMarcel Moolenaar 	    else
4027545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4028545ddfbeSMarcel Moolenaar 	}
4029545ddfbeSMarcel Moolenaar 
4030545ddfbeSMarcel Moolenaar 	xsp = &xop->xo_stack[xop->xo_depth];
4031545ddfbeSMarcel Moolenaar 	if (xsp->xs_name) {
4032545ddfbeSMarcel Moolenaar 	    name = xsp->xs_name;
4033545ddfbeSMarcel Moolenaar 	    nlen = strlen(name);
4034545ddfbeSMarcel Moolenaar 	}
4035545ddfbeSMarcel Moolenaar 
4036545ddfbeSMarcel Moolenaar     } else if (flags & XFF_KEY) {
4037545ddfbeSMarcel Moolenaar 	/* Emitting a 'k' (key) field */
4038545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4039545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4040545ddfbeSMarcel Moolenaar 		       nlen, name);
4041545ddfbeSMarcel Moolenaar 
4042545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4043545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4044545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4045545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4046545ddfbeSMarcel Moolenaar 
40478a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4048545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4049545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4050545ddfbeSMarcel Moolenaar 	    else
4051545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4052545ddfbeSMarcel Moolenaar 
4053545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4054545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT_KEY;
4055545ddfbeSMarcel Moolenaar 	}
4056545ddfbeSMarcel Moolenaar 
4057545ddfbeSMarcel Moolenaar     } else {
4058545ddfbeSMarcel Moolenaar 	/* Emitting a normal value field */
4059545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4060545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT)) {
4061545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
4062545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
4063545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
4064545ddfbeSMarcel Moolenaar 
40658a6eceffSPhil Shafer 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4066545ddfbeSMarcel Moolenaar 	    if (rc < 0)
4067545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4068545ddfbeSMarcel Moolenaar 	    else
4069545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4070545ddfbeSMarcel Moolenaar 
4071545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
4072545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT;
4073545ddfbeSMarcel Moolenaar 	}
4074545ddfbeSMarcel Moolenaar     }
4075545ddfbeSMarcel Moolenaar 
4076d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
4077d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
4078d1a0d267SMarcel Moolenaar 
4079788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
408031337658SMarcel Moolenaar     case XO_STYLE_TEXT:
408131337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
408231337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4083d1a0d267SMarcel Moolenaar 
4084d1a0d267SMarcel Moolenaar 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4085d1a0d267SMarcel Moolenaar 	save.xhs_columns = xop->xo_columns;
4086d1a0d267SMarcel Moolenaar 	save.xhs_anchor_columns = xop->xo_anchor_columns;
4087d1a0d267SMarcel Moolenaar 
4088d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
4089d1a0d267SMarcel Moolenaar 
4090d1a0d267SMarcel Moolenaar 	if (flags & XFF_HUMANIZE)
4091d1a0d267SMarcel Moolenaar 	    xo_format_humanize(xop, xbp, &save, flags);
409231337658SMarcel Moolenaar 	break;
409331337658SMarcel Moolenaar 
409431337658SMarcel Moolenaar     case XO_STYLE_HTML:
409531337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
409631337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4097d1a0d267SMarcel Moolenaar 
409831337658SMarcel Moolenaar 	xo_buf_append_div(xop, "data", flags, name, nlen,
409931337658SMarcel Moolenaar 			  format, flen, encoding, elen);
410031337658SMarcel Moolenaar 	break;
410131337658SMarcel Moolenaar 
410231337658SMarcel Moolenaar     case XO_STYLE_XML:
410331337658SMarcel Moolenaar 	/*
410431337658SMarcel Moolenaar 	 * Even though we're not making output, we still need to
410531337658SMarcel Moolenaar 	 * let the formatting code handle the va_arg popping.
410631337658SMarcel Moolenaar 	 */
410731337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
410831337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4109d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
411031337658SMarcel Moolenaar 	    break;
411131337658SMarcel Moolenaar 	}
411231337658SMarcel Moolenaar 
411331337658SMarcel Moolenaar 	if (encoding) {
411431337658SMarcel Moolenaar    	    format = encoding;
411531337658SMarcel Moolenaar 	    flen = elen;
411631337658SMarcel Moolenaar 	} else {
411731337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
411831337658SMarcel Moolenaar 	    memcpy(enc, format, flen);
411931337658SMarcel Moolenaar 	    enc[flen] = '\0';
412031337658SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
412131337658SMarcel Moolenaar 	    flen = strlen(format);
412231337658SMarcel Moolenaar 	}
412331337658SMarcel Moolenaar 
412431337658SMarcel Moolenaar 	if (nlen == 0) {
412531337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
412631337658SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
412731337658SMarcel Moolenaar 	    name = missing;
412831337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
412931337658SMarcel Moolenaar 	}
413031337658SMarcel Moolenaar 
413131337658SMarcel Moolenaar 	if (pretty)
413231337658SMarcel Moolenaar 	    xo_buf_indent(xop, -1);
413331337658SMarcel Moolenaar 	xo_data_append(xop, "<", 1);
413431337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
413531337658SMarcel Moolenaar 
413631337658SMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
413731337658SMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
413831337658SMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
413931337658SMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
414031337658SMarcel Moolenaar 	}
414131337658SMarcel Moolenaar 
414231337658SMarcel Moolenaar 	/*
414331337658SMarcel Moolenaar 	 * We indicate 'key' fields using the 'key' attribute.  While
414431337658SMarcel Moolenaar 	 * this is really committing the crime of mixing meta-data with
414531337658SMarcel Moolenaar 	 * data, it's often useful.  Especially when format meta-data is
414631337658SMarcel Moolenaar 	 * difficult to come by.
414731337658SMarcel Moolenaar 	 */
4148d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
414931337658SMarcel Moolenaar 	    static char attr[] = " key=\"key\"";
415031337658SMarcel Moolenaar 	    xo_data_append(xop, attr, sizeof(attr) - 1);
415131337658SMarcel Moolenaar 	}
415231337658SMarcel Moolenaar 
415331337658SMarcel Moolenaar 	/*
415431337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
415531337658SMarcel Moolenaar 	 */
4156d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
4157d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
415831337658SMarcel Moolenaar 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
415931337658SMarcel Moolenaar 	}
416031337658SMarcel Moolenaar 
416131337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
4162d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
416331337658SMarcel Moolenaar 	xo_data_append(xop, "</", 2);
416431337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
416531337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
416631337658SMarcel Moolenaar 	if (pretty)
416731337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
416831337658SMarcel Moolenaar 	break;
416931337658SMarcel Moolenaar 
417031337658SMarcel Moolenaar     case XO_STYLE_JSON:
417131337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
417231337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4173d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
417431337658SMarcel Moolenaar 	    break;
417531337658SMarcel Moolenaar 	}
417631337658SMarcel Moolenaar 
417731337658SMarcel Moolenaar 	if (encoding) {
417831337658SMarcel Moolenaar 	    format = encoding;
417931337658SMarcel Moolenaar 	    flen = elen;
418031337658SMarcel Moolenaar 	} else {
418131337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
418231337658SMarcel Moolenaar 	    memcpy(enc, format, flen);
418331337658SMarcel Moolenaar 	    enc[flen] = '\0';
418431337658SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
418531337658SMarcel Moolenaar 	    flen = strlen(format);
418631337658SMarcel Moolenaar 	}
418731337658SMarcel Moolenaar 
41888a6eceffSPhil Shafer 	int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
41898a6eceffSPhil Shafer 	    ? 0 : 1;
419031337658SMarcel Moolenaar 
419131337658SMarcel Moolenaar 	xo_format_prep(xop, flags);
419231337658SMarcel Moolenaar 
419331337658SMarcel Moolenaar 	if (flags & XFF_QUOTE)
419431337658SMarcel Moolenaar 	    quote = 1;
419531337658SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
419631337658SMarcel Moolenaar 	    quote = 0;
419731337658SMarcel Moolenaar 	else if (flen == 0) {
419831337658SMarcel Moolenaar 	    quote = 0;
419931337658SMarcel Moolenaar 	    format = "true";	/* JSON encodes empty tags as a boolean true */
420031337658SMarcel Moolenaar 	    flen = 4;
42018a6eceffSPhil Shafer 	} else if (strchr("diouDOUeEfFgG", format[flen - 1]) == NULL)
420231337658SMarcel Moolenaar 	    quote = 1;
420331337658SMarcel Moolenaar 	else
420431337658SMarcel Moolenaar 	    quote = 0;
420531337658SMarcel Moolenaar 
420631337658SMarcel Moolenaar 	if (nlen == 0) {
420731337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
420831337658SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
420931337658SMarcel Moolenaar 	    name = missing;
421031337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
421131337658SMarcel Moolenaar 	}
421231337658SMarcel Moolenaar 
421331337658SMarcel Moolenaar 	if (flags & XFF_LEAF_LIST) {
4214788ca347SMarcel Moolenaar 	    if (!first && pretty)
4215788ca347SMarcel Moolenaar 		xo_data_append(xop, "\n", 1);
4216788ca347SMarcel Moolenaar 	    if (pretty)
421731337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
421831337658SMarcel Moolenaar 	} else {
421931337658SMarcel Moolenaar 	    if (pretty)
422031337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
422131337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
422231337658SMarcel Moolenaar 
422331337658SMarcel Moolenaar 	    xbp = &xop->xo_data;
42248a6eceffSPhil Shafer 	    ssize_t off = xbp->xb_curp - xbp->xb_bufp;
422531337658SMarcel Moolenaar 
422631337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
422731337658SMarcel Moolenaar 
4228d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
42298a6eceffSPhil Shafer 		ssize_t coff = xbp->xb_curp - xbp->xb_bufp;
42308a6eceffSPhil Shafer 		for ( ; off < coff; off++)
423131337658SMarcel Moolenaar 		    if (xbp->xb_bufp[off] == '-')
423231337658SMarcel Moolenaar 			xbp->xb_bufp[off] = '_';
423331337658SMarcel Moolenaar 	    }
423431337658SMarcel Moolenaar 	    xo_data_append(xop, "\":", 2);
423531337658SMarcel Moolenaar 	    if (pretty)
423631337658SMarcel Moolenaar 	        xo_data_append(xop, " ", 1);
4237788ca347SMarcel Moolenaar 	}
4238788ca347SMarcel Moolenaar 
423931337658SMarcel Moolenaar 	if (quote)
424031337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
424131337658SMarcel Moolenaar 
4242d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
424331337658SMarcel Moolenaar 
424431337658SMarcel Moolenaar 	if (quote)
424531337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
424631337658SMarcel Moolenaar 	break;
4247d1a0d267SMarcel Moolenaar 
4248d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4249d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4250d1a0d267SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4251d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
4252d1a0d267SMarcel Moolenaar 	    break;
4253d1a0d267SMarcel Moolenaar 	}
4254d1a0d267SMarcel Moolenaar 
4255d1a0d267SMarcel Moolenaar 	if (encoding) {
4256d1a0d267SMarcel Moolenaar 	    format = encoding;
4257d1a0d267SMarcel Moolenaar 	    flen = elen;
4258d1a0d267SMarcel Moolenaar 	} else {
4259d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4260d1a0d267SMarcel Moolenaar 	    memcpy(enc, format, flen);
4261d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4262d1a0d267SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
4263d1a0d267SMarcel Moolenaar 	    flen = strlen(format);
4264d1a0d267SMarcel Moolenaar 	}
4265d1a0d267SMarcel Moolenaar 
4266d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4267d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4268d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
4269d1a0d267SMarcel Moolenaar 	    name = missing;
4270d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4271d1a0d267SMarcel Moolenaar 	}
4272d1a0d267SMarcel Moolenaar 
4273d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
4274d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "=\"", 2);
4275d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
4276d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "\" ", 2);
4277d1a0d267SMarcel Moolenaar 	break;
4278d1a0d267SMarcel Moolenaar 
4279d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4280d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4281d1a0d267SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4282d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
4283d1a0d267SMarcel Moolenaar 	    break;
4284d1a0d267SMarcel Moolenaar 	}
4285d1a0d267SMarcel Moolenaar 
4286d1a0d267SMarcel Moolenaar 	if (flags & XFF_QUOTE)
4287d1a0d267SMarcel Moolenaar 	    quote = 1;
4288d1a0d267SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
4289d1a0d267SMarcel Moolenaar 	    quote = 0;
4290d1a0d267SMarcel Moolenaar 	else if (flen == 0) {
4291d1a0d267SMarcel Moolenaar 	    quote = 0;
4292d1a0d267SMarcel Moolenaar 	    format = "true";	/* JSON encodes empty tags as a boolean true */
4293d1a0d267SMarcel Moolenaar 	    flen = 4;
4294d1a0d267SMarcel Moolenaar 	} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
4295d1a0d267SMarcel Moolenaar 	    quote = 1;
4296d1a0d267SMarcel Moolenaar 	else
4297d1a0d267SMarcel Moolenaar 	    quote = 0;
4298d1a0d267SMarcel Moolenaar 
4299d1a0d267SMarcel Moolenaar 	if (encoding) {
4300d1a0d267SMarcel Moolenaar 	    format = encoding;
4301d1a0d267SMarcel Moolenaar 	    flen = elen;
4302d1a0d267SMarcel Moolenaar 	} else {
4303d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4304d1a0d267SMarcel Moolenaar 	    memcpy(enc, format, flen);
4305d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4306d1a0d267SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
4307d1a0d267SMarcel Moolenaar 	    flen = strlen(format);
4308d1a0d267SMarcel Moolenaar 	}
4309d1a0d267SMarcel Moolenaar 
4310d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4311d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4312d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
4313d1a0d267SMarcel Moolenaar 	    name = missing;
4314d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4315d1a0d267SMarcel Moolenaar 	}
4316d1a0d267SMarcel Moolenaar 
43178a6eceffSPhil Shafer 	ssize_t name_offset = xo_buf_offset(&xop->xo_data);
4318d1a0d267SMarcel Moolenaar 	xo_data_append(xop, name, nlen);
4319d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4320d1a0d267SMarcel Moolenaar 
43218a6eceffSPhil Shafer 	ssize_t value_offset = xo_buf_offset(&xop->xo_data);
4322d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
4323d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4324d1a0d267SMarcel Moolenaar 
4325d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4326d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, name_offset),
4327d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, value_offset));
4328d1a0d267SMarcel Moolenaar 	xo_buf_reset(&xop->xo_data);
4329d1a0d267SMarcel Moolenaar 	break;
433031337658SMarcel Moolenaar     }
433131337658SMarcel Moolenaar }
433231337658SMarcel Moolenaar 
433331337658SMarcel Moolenaar static void
433442ff34c3SPhil Shafer xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
43358a6eceffSPhil Shafer 		       const char *str, ssize_t len)
4336d1a0d267SMarcel Moolenaar {
4337d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
43388a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4339d1a0d267SMarcel Moolenaar 
4340d1a0d267SMarcel Moolenaar     /* Start by discarding previous domain */
4341d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
4342d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
4343d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
4344d1a0d267SMarcel Moolenaar     }
4345d1a0d267SMarcel Moolenaar 
4346d1a0d267SMarcel Moolenaar     /* An empty {G:} means no domainname */
4347d1a0d267SMarcel Moolenaar     if (len == 0 && flen == 0)
4348d1a0d267SMarcel Moolenaar 	return;
4349d1a0d267SMarcel Moolenaar 
43508a6eceffSPhil Shafer     ssize_t start_offset = -1;
4351d1a0d267SMarcel Moolenaar     if (len == 0 && flen != 0) {
4352d1a0d267SMarcel Moolenaar 	/* Need to do format the data to get the domainname from args */
4353d1a0d267SMarcel Moolenaar 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4354d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4355d1a0d267SMarcel Moolenaar 
43568a6eceffSPhil Shafer 	ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4357d1a0d267SMarcel Moolenaar 	len = end_offset - start_offset;
4358d1a0d267SMarcel Moolenaar 	str = xop->xo_data.xb_bufp + start_offset;
4359d1a0d267SMarcel Moolenaar     }
4360d1a0d267SMarcel Moolenaar 
4361d1a0d267SMarcel Moolenaar     xop->xo_gt_domain = xo_strndup(str, len);
4362d1a0d267SMarcel Moolenaar 
4363d1a0d267SMarcel Moolenaar     /* Reset the current buffer point to avoid emitting the name as output */
4364d1a0d267SMarcel Moolenaar     if (start_offset >= 0)
4365d1a0d267SMarcel Moolenaar 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4366d1a0d267SMarcel Moolenaar }
4367d1a0d267SMarcel Moolenaar 
4368d1a0d267SMarcel Moolenaar static void
436931337658SMarcel Moolenaar xo_format_content (xo_handle_t *xop, const char *class_name,
4370d1a0d267SMarcel Moolenaar 		   const char *tag_name,
43718a6eceffSPhil Shafer 		   const char *str, ssize_t len, const char *fmt, ssize_t flen,
4372d1a0d267SMarcel Moolenaar 		   xo_xff_flags_t flags)
437331337658SMarcel Moolenaar {
4374788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
437531337658SMarcel Moolenaar     case XO_STYLE_TEXT:
4376d1a0d267SMarcel Moolenaar 	if (len)
4377d1a0d267SMarcel Moolenaar 	    xo_data_append_content(xop, str, len, flags);
4378d1a0d267SMarcel Moolenaar 	else
4379d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen, flags);
438031337658SMarcel Moolenaar 	break;
438131337658SMarcel Moolenaar 
438231337658SMarcel Moolenaar     case XO_STYLE_HTML:
438331337658SMarcel Moolenaar 	if (len == 0) {
438431337658SMarcel Moolenaar 	    str = fmt;
438531337658SMarcel Moolenaar 	    len = flen;
438631337658SMarcel Moolenaar 	}
438731337658SMarcel Moolenaar 
4388d1a0d267SMarcel Moolenaar 	xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
438931337658SMarcel Moolenaar 	break;
439031337658SMarcel Moolenaar 
439131337658SMarcel Moolenaar     case XO_STYLE_XML:
4392d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
4393d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4394d1a0d267SMarcel Moolenaar 	if (tag_name) {
439531337658SMarcel Moolenaar 	    if (len == 0) {
439631337658SMarcel Moolenaar 		str = fmt;
439731337658SMarcel Moolenaar 		len = flen;
439831337658SMarcel Moolenaar 	    }
439931337658SMarcel Moolenaar 
4400d1a0d267SMarcel Moolenaar 	    xo_open_container_h(xop, tag_name);
4401d1a0d267SMarcel Moolenaar 	    xo_format_value(xop, "message", 7, str, len, NULL, 0, flags);
4402d1a0d267SMarcel Moolenaar 	    xo_close_container_h(xop, tag_name);
440331337658SMarcel Moolenaar 
440431337658SMarcel Moolenaar 	} else {
440531337658SMarcel Moolenaar 	    /*
440631337658SMarcel Moolenaar 	     * Even though we don't care about labels, we need to do
440731337658SMarcel Moolenaar 	     * enough parsing work to skip over the right bits of xo_vap.
440831337658SMarcel Moolenaar 	     */
440931337658SMarcel Moolenaar 	    if (len == 0)
4410d1a0d267SMarcel Moolenaar 		xo_do_format_field(xop, NULL, fmt, flen,
4411d1a0d267SMarcel Moolenaar 				   flags | XFF_NO_OUTPUT);
441231337658SMarcel Moolenaar 	}
441331337658SMarcel Moolenaar 	break;
441431337658SMarcel Moolenaar 
4415d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
441631337658SMarcel Moolenaar 	if (len == 0)
4417d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen,
4418d1a0d267SMarcel Moolenaar 			       flags | XFF_NO_OUTPUT);
441931337658SMarcel Moolenaar 	break;
442031337658SMarcel Moolenaar     }
442131337658SMarcel Moolenaar }
442231337658SMarcel Moolenaar 
4423788ca347SMarcel Moolenaar static const char *xo_color_names[] = {
4424788ca347SMarcel Moolenaar     "default",	/* XO_COL_DEFAULT */
4425788ca347SMarcel Moolenaar     "black",	/* XO_COL_BLACK */
4426788ca347SMarcel Moolenaar     "red",	/* XO_CLOR_RED */
4427788ca347SMarcel Moolenaar     "green",	/* XO_COL_GREEN */
4428788ca347SMarcel Moolenaar     "yellow",	/* XO_COL_YELLOW */
4429788ca347SMarcel Moolenaar     "blue",	/* XO_COL_BLUE */
4430788ca347SMarcel Moolenaar     "magenta",	/* XO_COL_MAGENTA */
4431788ca347SMarcel Moolenaar     "cyan",	/* XO_COL_CYAN */
4432788ca347SMarcel Moolenaar     "white",	/* XO_COL_WHITE */
4433788ca347SMarcel Moolenaar     NULL
4434788ca347SMarcel Moolenaar };
4435788ca347SMarcel Moolenaar 
4436788ca347SMarcel Moolenaar static int
4437788ca347SMarcel Moolenaar xo_color_find (const char *str)
4438788ca347SMarcel Moolenaar {
4439788ca347SMarcel Moolenaar     int i;
4440788ca347SMarcel Moolenaar 
4441788ca347SMarcel Moolenaar     for (i = 0; xo_color_names[i]; i++) {
4442788ca347SMarcel Moolenaar 	if (strcmp(xo_color_names[i], str) == 0)
4443788ca347SMarcel Moolenaar 	    return i;
4444788ca347SMarcel Moolenaar     }
4445788ca347SMarcel Moolenaar 
4446788ca347SMarcel Moolenaar     return -1;
4447788ca347SMarcel Moolenaar }
4448788ca347SMarcel Moolenaar 
4449788ca347SMarcel Moolenaar static const char *xo_effect_names[] = {
4450788ca347SMarcel Moolenaar     "reset",			/* XO_EFF_RESET */
4451788ca347SMarcel Moolenaar     "normal",			/* XO_EFF_NORMAL */
4452788ca347SMarcel Moolenaar     "bold",			/* XO_EFF_BOLD */
4453788ca347SMarcel Moolenaar     "underline",		/* XO_EFF_UNDERLINE */
4454788ca347SMarcel Moolenaar     "inverse",			/* XO_EFF_INVERSE */
4455788ca347SMarcel Moolenaar     NULL
4456788ca347SMarcel Moolenaar };
4457788ca347SMarcel Moolenaar 
4458788ca347SMarcel Moolenaar static const char *xo_effect_on_codes[] = {
4459788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4460788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4461788ca347SMarcel Moolenaar     "1",			/* XO_EFF_BOLD */
4462788ca347SMarcel Moolenaar     "4",			/* XO_EFF_UNDERLINE */
4463788ca347SMarcel Moolenaar     "7",			/* XO_EFF_INVERSE */
4464788ca347SMarcel Moolenaar     NULL
4465788ca347SMarcel Moolenaar };
4466788ca347SMarcel Moolenaar 
4467788ca347SMarcel Moolenaar #if 0
4468788ca347SMarcel Moolenaar /*
4469788ca347SMarcel Moolenaar  * See comment below re: joy of terminal standards.  These can
4470788ca347SMarcel Moolenaar  * be use by just adding:
4471d1a0d267SMarcel Moolenaar  * +	if (newp->xoc_effects & bit)
4472788ca347SMarcel Moolenaar  *	    code = xo_effect_on_codes[i];
4473788ca347SMarcel Moolenaar  * +	else
4474788ca347SMarcel Moolenaar  * +	    code = xo_effect_off_codes[i];
4475788ca347SMarcel Moolenaar  * in xo_color_handle_text.
4476788ca347SMarcel Moolenaar  */
4477788ca347SMarcel Moolenaar static const char *xo_effect_off_codes[] = {
4478788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4479788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4480788ca347SMarcel Moolenaar     "21",			/* XO_EFF_BOLD */
4481788ca347SMarcel Moolenaar     "24",			/* XO_EFF_UNDERLINE */
4482788ca347SMarcel Moolenaar     "27",			/* XO_EFF_INVERSE */
4483788ca347SMarcel Moolenaar     NULL
4484788ca347SMarcel Moolenaar };
4485788ca347SMarcel Moolenaar #endif /* 0 */
4486788ca347SMarcel Moolenaar 
4487788ca347SMarcel Moolenaar static int
4488788ca347SMarcel Moolenaar xo_effect_find (const char *str)
4489788ca347SMarcel Moolenaar {
4490788ca347SMarcel Moolenaar     int i;
4491788ca347SMarcel Moolenaar 
4492788ca347SMarcel Moolenaar     for (i = 0; xo_effect_names[i]; i++) {
4493788ca347SMarcel Moolenaar 	if (strcmp(xo_effect_names[i], str) == 0)
4494788ca347SMarcel Moolenaar 	    return i;
4495788ca347SMarcel Moolenaar     }
4496788ca347SMarcel Moolenaar 
4497788ca347SMarcel Moolenaar     return -1;
4498788ca347SMarcel Moolenaar }
4499788ca347SMarcel Moolenaar 
4500788ca347SMarcel Moolenaar static void
4501788ca347SMarcel Moolenaar xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4502788ca347SMarcel Moolenaar {
4503788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4504788ca347SMarcel Moolenaar     return;
4505788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4506788ca347SMarcel Moolenaar 
4507788ca347SMarcel Moolenaar     char *cp, *ep, *np, *xp;
45088a6eceffSPhil Shafer     ssize_t len = strlen(str);
4509788ca347SMarcel Moolenaar     int rc;
4510788ca347SMarcel Moolenaar 
4511788ca347SMarcel Moolenaar     /*
4512788ca347SMarcel Moolenaar      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4513788ca347SMarcel Moolenaar      */
4514788ca347SMarcel Moolenaar     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4515788ca347SMarcel Moolenaar 	/* Trim leading whitespace */
4516788ca347SMarcel Moolenaar 	while (isspace((int) *cp))
4517788ca347SMarcel Moolenaar 	    cp += 1;
4518788ca347SMarcel Moolenaar 
4519788ca347SMarcel Moolenaar 	np = strchr(cp, ',');
4520788ca347SMarcel Moolenaar 	if (np)
4521788ca347SMarcel Moolenaar 	    *np++ = '\0';
4522788ca347SMarcel Moolenaar 
4523788ca347SMarcel Moolenaar 	/* Trim trailing whitespace */
4524788ca347SMarcel Moolenaar 	xp = cp + strlen(cp) - 1;
4525788ca347SMarcel Moolenaar 	while (isspace(*xp) && xp > cp)
4526788ca347SMarcel Moolenaar 	    *xp-- = '\0';
4527788ca347SMarcel Moolenaar 
4528788ca347SMarcel Moolenaar 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4529788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4530788ca347SMarcel Moolenaar 	    if (rc < 0)
4531788ca347SMarcel Moolenaar 		goto unknown;
4532788ca347SMarcel Moolenaar 
4533788ca347SMarcel Moolenaar 	    xocp->xoc_col_fg = rc;
4534788ca347SMarcel Moolenaar 
4535788ca347SMarcel Moolenaar 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4536788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4537788ca347SMarcel Moolenaar 	    if (rc < 0)
4538788ca347SMarcel Moolenaar 		goto unknown;
4539788ca347SMarcel Moolenaar 	    xocp->xoc_col_bg = rc;
4540788ca347SMarcel Moolenaar 
4541788ca347SMarcel Moolenaar 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4542788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp + 3);
4543788ca347SMarcel Moolenaar 	    if (rc < 0)
4544788ca347SMarcel Moolenaar 		goto unknown;
4545788ca347SMarcel Moolenaar 	    xocp->xoc_effects &= ~(1 << rc);
4546788ca347SMarcel Moolenaar 
4547788ca347SMarcel Moolenaar 	} else {
4548788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp);
4549788ca347SMarcel Moolenaar 	    if (rc < 0)
4550788ca347SMarcel Moolenaar 		goto unknown;
4551788ca347SMarcel Moolenaar 	    xocp->xoc_effects |= 1 << rc;
4552788ca347SMarcel Moolenaar 
4553788ca347SMarcel Moolenaar 	    switch (1 << rc) {
4554788ca347SMarcel Moolenaar 	    case XO_EFF_RESET:
4555788ca347SMarcel Moolenaar 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4556788ca347SMarcel Moolenaar 		/* Note: not "|=" since we want to wipe out the old value */
4557788ca347SMarcel Moolenaar 		xocp->xoc_effects = XO_EFF_RESET;
4558788ca347SMarcel Moolenaar 		break;
4559788ca347SMarcel Moolenaar 
4560788ca347SMarcel Moolenaar 	    case XO_EFF_NORMAL:
4561788ca347SMarcel Moolenaar 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4562788ca347SMarcel Moolenaar 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4563788ca347SMarcel Moolenaar 		break;
4564788ca347SMarcel Moolenaar 	    }
4565788ca347SMarcel Moolenaar 	}
4566788ca347SMarcel Moolenaar 	continue;
4567788ca347SMarcel Moolenaar 
4568788ca347SMarcel Moolenaar     unknown:
4569d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN))
4570788ca347SMarcel Moolenaar 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4571788ca347SMarcel Moolenaar     }
4572788ca347SMarcel Moolenaar }
4573788ca347SMarcel Moolenaar 
4574788ca347SMarcel Moolenaar static inline int
4575788ca347SMarcel Moolenaar xo_colors_enabled (xo_handle_t *xop UNUSED)
4576788ca347SMarcel Moolenaar {
4577788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4578788ca347SMarcel Moolenaar     return 0;
4579788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
4580d1a0d267SMarcel Moolenaar     return XOF_ISSET(xop, XOF_COLOR);
4581788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4582788ca347SMarcel Moolenaar }
4583788ca347SMarcel Moolenaar 
4584788ca347SMarcel Moolenaar static void
458542ff34c3SPhil Shafer xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4586788ca347SMarcel Moolenaar {
4587788ca347SMarcel Moolenaar     char buf[BUFSIZ];
4588788ca347SMarcel Moolenaar     char *cp = buf, *ep = buf + sizeof(buf);
4589788ca347SMarcel Moolenaar     unsigned i, bit;
4590788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
459142ff34c3SPhil Shafer     const char *code = NULL;
4592788ca347SMarcel Moolenaar 
4593788ca347SMarcel Moolenaar     /*
4594788ca347SMarcel Moolenaar      * Start the buffer with an escape.  We don't want to add the '['
4595788ca347SMarcel Moolenaar      * now, since we let xo_effect_text_add unconditionally add the ';'.
4596788ca347SMarcel Moolenaar      * We'll replace the first ';' with a '[' when we're done.
4597788ca347SMarcel Moolenaar      */
4598788ca347SMarcel Moolenaar     *cp++ = 0x1b;		/* Escape */
4599788ca347SMarcel Moolenaar 
4600788ca347SMarcel Moolenaar     /*
4601788ca347SMarcel Moolenaar      * Terminals were designed back in the age before "certainty" was
4602788ca347SMarcel Moolenaar      * invented, when standards were more what you'd call "guidelines"
4603788ca347SMarcel Moolenaar      * than actual rules.  Anyway we can't depend on them to operate
4604788ca347SMarcel Moolenaar      * correctly.  So when display attributes are changed, we punt,
4605788ca347SMarcel Moolenaar      * reseting them all and turning back on the ones we want to keep.
4606788ca347SMarcel Moolenaar      * Longer, but should be completely reliable.  Savvy?
4607788ca347SMarcel Moolenaar      */
4608788ca347SMarcel Moolenaar     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4609788ca347SMarcel Moolenaar 	newp->xoc_effects |= XO_EFF_RESET;
4610788ca347SMarcel Moolenaar 	oldp->xoc_effects = 0;
4611788ca347SMarcel Moolenaar     }
4612788ca347SMarcel Moolenaar 
4613788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4614788ca347SMarcel Moolenaar 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4615788ca347SMarcel Moolenaar 	    continue;
4616788ca347SMarcel Moolenaar 
4617788ca347SMarcel Moolenaar 	code = xo_effect_on_codes[i];
4618788ca347SMarcel Moolenaar 
4619788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";%s", code);
4620788ca347SMarcel Moolenaar 	if (cp >= ep)
4621788ca347SMarcel Moolenaar 	    return;		/* Should not occur */
4622788ca347SMarcel Moolenaar 
4623788ca347SMarcel Moolenaar 	if (bit == XO_EFF_RESET) {
4624788ca347SMarcel Moolenaar 	    /* Mark up the old value so we can detect current values as new */
4625788ca347SMarcel Moolenaar 	    oldp->xoc_effects = 0;
4626788ca347SMarcel Moolenaar 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4627788ca347SMarcel Moolenaar 	}
4628788ca347SMarcel Moolenaar     }
4629788ca347SMarcel Moolenaar 
4630788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != oldp->xoc_col_fg) {
4631788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";3%u",
4632788ca347SMarcel Moolenaar 		       (newp->xoc_col_fg != XO_COL_DEFAULT)
4633788ca347SMarcel Moolenaar 		       ? newp->xoc_col_fg - 1 : 9);
4634788ca347SMarcel Moolenaar     }
4635788ca347SMarcel Moolenaar 
4636788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != oldp->xoc_col_bg) {
4637788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";4%u",
4638788ca347SMarcel Moolenaar 		       (newp->xoc_col_bg != XO_COL_DEFAULT)
4639788ca347SMarcel Moolenaar 		       ? newp->xoc_col_bg - 1 : 9);
4640788ca347SMarcel Moolenaar     }
4641788ca347SMarcel Moolenaar 
4642788ca347SMarcel Moolenaar     if (cp - buf != 1 && cp < ep - 3) {
4643788ca347SMarcel Moolenaar 	buf[1] = '[';		/* Overwrite leading ';' */
4644788ca347SMarcel Moolenaar 	*cp++ = 'm';
4645788ca347SMarcel Moolenaar 	*cp = '\0';
4646788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4647788ca347SMarcel Moolenaar     }
4648788ca347SMarcel Moolenaar }
4649788ca347SMarcel Moolenaar 
4650788ca347SMarcel Moolenaar static void
4651788ca347SMarcel Moolenaar xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4652788ca347SMarcel Moolenaar {
4653788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
4654788ca347SMarcel Moolenaar 
4655788ca347SMarcel Moolenaar     /*
4656788ca347SMarcel Moolenaar      * HTML colors are mostly trivial: fill in xo_color_buf with
4657788ca347SMarcel Moolenaar      * a set of class tags representing the colors and effects.
4658788ca347SMarcel Moolenaar      */
4659788ca347SMarcel Moolenaar 
4660788ca347SMarcel Moolenaar     /* If nothing changed, then do nothing */
4661788ca347SMarcel Moolenaar     if (oldp->xoc_effects == newp->xoc_effects
4662788ca347SMarcel Moolenaar 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4663788ca347SMarcel Moolenaar 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4664788ca347SMarcel Moolenaar 	return;
4665788ca347SMarcel Moolenaar 
4666788ca347SMarcel Moolenaar     unsigned i, bit;
4667788ca347SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_color_buf;
4668788ca347SMarcel Moolenaar 
4669788ca347SMarcel Moolenaar     xo_buf_reset(xbp);		/* We rebuild content after each change */
4670788ca347SMarcel Moolenaar 
4671788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4672788ca347SMarcel Moolenaar 	if (!(newp->xoc_effects & bit))
4673788ca347SMarcel Moolenaar 	    continue;
4674788ca347SMarcel Moolenaar 
4675788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " effect-");
4676788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, xo_effect_names[i]);
4677788ca347SMarcel Moolenaar     }
4678788ca347SMarcel Moolenaar 
4679788ca347SMarcel Moolenaar     const char *fg = NULL;
4680788ca347SMarcel Moolenaar     const char *bg = NULL;
4681788ca347SMarcel Moolenaar 
4682788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4683788ca347SMarcel Moolenaar 	fg = xo_color_names[newp->xoc_col_fg];
4684788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4685788ca347SMarcel Moolenaar 	bg = xo_color_names[newp->xoc_col_bg];
4686788ca347SMarcel Moolenaar 
4687788ca347SMarcel Moolenaar     if (newp->xoc_effects & XO_EFF_INVERSE) {
4688788ca347SMarcel Moolenaar 	const char *tmp = fg;
4689788ca347SMarcel Moolenaar 	fg = bg;
4690788ca347SMarcel Moolenaar 	bg = tmp;
4691788ca347SMarcel Moolenaar 	if (fg == NULL)
4692788ca347SMarcel Moolenaar 	    fg = "inverse";
4693788ca347SMarcel Moolenaar 	if (bg == NULL)
4694788ca347SMarcel Moolenaar 	    bg = "inverse";
4695788ca347SMarcel Moolenaar 
4696788ca347SMarcel Moolenaar     }
4697788ca347SMarcel Moolenaar 
4698788ca347SMarcel Moolenaar     if (fg) {
4699788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-fg-");
4700788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, fg);
4701788ca347SMarcel Moolenaar     }
4702788ca347SMarcel Moolenaar 
4703788ca347SMarcel Moolenaar     if (bg) {
4704788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-bg-");
4705788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, bg);
4706788ca347SMarcel Moolenaar     }
4707788ca347SMarcel Moolenaar }
4708788ca347SMarcel Moolenaar 
4709788ca347SMarcel Moolenaar static void
471042ff34c3SPhil Shafer xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
47118a6eceffSPhil Shafer 		  const char *str, ssize_t len)
4712788ca347SMarcel Moolenaar {
4713d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
47148a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4715d1a0d267SMarcel Moolenaar 
4716788ca347SMarcel Moolenaar     xo_buffer_t xb;
4717788ca347SMarcel Moolenaar 
4718788ca347SMarcel Moolenaar     /* If the string is static and we've in an encoding style, bail */
4719d1a0d267SMarcel Moolenaar     if (len != 0 && xo_style_is_encoding(xop))
4720788ca347SMarcel Moolenaar 	return;
4721788ca347SMarcel Moolenaar 
4722788ca347SMarcel Moolenaar     xo_buf_init(&xb);
4723788ca347SMarcel Moolenaar 
4724788ca347SMarcel Moolenaar     if (len)
4725788ca347SMarcel Moolenaar 	xo_buf_append(&xb, str, len);
4726788ca347SMarcel Moolenaar     else if (flen)
4727d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, &xb, fmt, flen, 0);
4728788ca347SMarcel Moolenaar     else
4729788ca347SMarcel Moolenaar 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
4730788ca347SMarcel Moolenaar 
4731788ca347SMarcel Moolenaar     if (xo_colors_enabled(xop)) {
4732788ca347SMarcel Moolenaar 	switch (xo_style(xop)) {
4733788ca347SMarcel Moolenaar 	case XO_STYLE_TEXT:
4734788ca347SMarcel Moolenaar 	case XO_STYLE_HTML:
4735788ca347SMarcel Moolenaar 	    xo_buf_append(&xb, "", 1);
4736788ca347SMarcel Moolenaar 
4737788ca347SMarcel Moolenaar 	    xo_colors_t xoc = xop->xo_colors;
4738788ca347SMarcel Moolenaar 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
4739788ca347SMarcel Moolenaar 
4740788ca347SMarcel Moolenaar 	    if (xo_style(xop) == XO_STYLE_TEXT) {
4741788ca347SMarcel Moolenaar 		/*
4742788ca347SMarcel Moolenaar 		 * Text mode means emitting the colors as ANSI character
4743788ca347SMarcel Moolenaar 		 * codes.  This will allow people who like colors to have
4744788ca347SMarcel Moolenaar 		 * colors.  The issue is, of course conflicting with the
4745788ca347SMarcel Moolenaar 		 * user's perfectly reasonable color scheme.  Which leads
4746788ca347SMarcel Moolenaar 		 * to the hell of LSCOLORS, where even app need to have
4747788ca347SMarcel Moolenaar 		 * customization hooks for adjusting colors.  Instead we
4748788ca347SMarcel Moolenaar 		 * provide a simpler-but-still-annoying answer where one
4749788ca347SMarcel Moolenaar 		 * can map colors to other colors.
4750788ca347SMarcel Moolenaar 		 */
4751788ca347SMarcel Moolenaar 		xo_colors_handle_text(xop, &xoc);
4752788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
4753788ca347SMarcel Moolenaar 
4754788ca347SMarcel Moolenaar 	    } else {
4755788ca347SMarcel Moolenaar 		/*
4756788ca347SMarcel Moolenaar 		 * HTML output is wrapped in divs, so the color information
4757788ca347SMarcel Moolenaar 		 * must appear in every div until cleared.  Most pathetic.
4758788ca347SMarcel Moolenaar 		 * Most unavoidable.
4759788ca347SMarcel Moolenaar 		 */
4760788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
4761788ca347SMarcel Moolenaar 		xo_colors_handle_html(xop, &xoc);
4762788ca347SMarcel Moolenaar 	    }
4763788ca347SMarcel Moolenaar 
4764788ca347SMarcel Moolenaar 	    xop->xo_colors = xoc;
4765788ca347SMarcel Moolenaar 	    break;
4766788ca347SMarcel Moolenaar 
4767788ca347SMarcel Moolenaar 	case XO_STYLE_XML:
4768788ca347SMarcel Moolenaar 	case XO_STYLE_JSON:
4769d1a0d267SMarcel Moolenaar 	case XO_STYLE_SDPARAMS:
4770d1a0d267SMarcel Moolenaar 	case XO_STYLE_ENCODER:
4771788ca347SMarcel Moolenaar 	    /*
4772788ca347SMarcel Moolenaar 	     * Nothing to do; we did all that work just to clear the stack of
4773788ca347SMarcel Moolenaar 	     * formatting arguments.
4774788ca347SMarcel Moolenaar 	     */
4775788ca347SMarcel Moolenaar 	    break;
4776788ca347SMarcel Moolenaar 	}
4777788ca347SMarcel Moolenaar     }
4778788ca347SMarcel Moolenaar 
4779788ca347SMarcel Moolenaar     xo_buf_cleanup(&xb);
4780788ca347SMarcel Moolenaar }
4781788ca347SMarcel Moolenaar 
478231337658SMarcel Moolenaar static void
478342ff34c3SPhil Shafer xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
47848a6eceffSPhil Shafer 		 const char *str, ssize_t len)
478531337658SMarcel Moolenaar {
4786d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
47878a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4788d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
4789d1a0d267SMarcel Moolenaar 
479031337658SMarcel Moolenaar     static char units_start_xml[] = " units=\"";
479131337658SMarcel Moolenaar     static char units_start_html[] = " data-units=\"";
479231337658SMarcel Moolenaar 
4793d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
4794d1a0d267SMarcel Moolenaar 	xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
479531337658SMarcel Moolenaar 	return;
479631337658SMarcel Moolenaar     }
479731337658SMarcel Moolenaar 
479831337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
47998a6eceffSPhil Shafer     ssize_t start = xop->xo_units_offset;
48008a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
480131337658SMarcel Moolenaar 
4802788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML)
480331337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
4804788ca347SMarcel Moolenaar     else if (xo_style(xop) == XO_STYLE_HTML)
480531337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
480631337658SMarcel Moolenaar     else
480731337658SMarcel Moolenaar 	return;
480831337658SMarcel Moolenaar 
480931337658SMarcel Moolenaar     if (len)
4810d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, str, len);
481131337658SMarcel Moolenaar     else
4812d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
481331337658SMarcel Moolenaar 
481431337658SMarcel Moolenaar     xo_buf_append(xbp, "\"", 1);
481531337658SMarcel Moolenaar 
48168a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
48178a6eceffSPhil Shafer     ssize_t delta = now - stop;
4818d1a0d267SMarcel Moolenaar     if (delta <= 0) {		/* Strange; no output to move */
481931337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
482031337658SMarcel Moolenaar 	return;
482131337658SMarcel Moolenaar     }
482231337658SMarcel Moolenaar 
482331337658SMarcel Moolenaar     /*
482431337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the unit value
482531337658SMarcel Moolenaar      * we just created into the right spot.  We make a local copy,
482631337658SMarcel Moolenaar      * move it and then insert our copy.  We know there's room in the
482731337658SMarcel Moolenaar      * buffer, since we're just moving this around.
482831337658SMarcel Moolenaar      */
482931337658SMarcel Moolenaar     char *buf = alloca(delta);
483031337658SMarcel Moolenaar 
483131337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
483231337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
483331337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
483431337658SMarcel Moolenaar }
483531337658SMarcel Moolenaar 
48368a6eceffSPhil Shafer static ssize_t
483742ff34c3SPhil Shafer xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
48388a6eceffSPhil Shafer 	       const char *str, ssize_t len)
483931337658SMarcel Moolenaar {
4840d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
48418a6eceffSPhil Shafer     ssize_t flen = xfip->xfi_flen;
4842d1a0d267SMarcel Moolenaar 
484331337658SMarcel Moolenaar     long width = 0;
484431337658SMarcel Moolenaar     char *bp;
484531337658SMarcel Moolenaar     char *cp;
484631337658SMarcel Moolenaar 
484731337658SMarcel Moolenaar     if (len) {
484831337658SMarcel Moolenaar 	bp = alloca(len + 1);	/* Make local NUL-terminated copy of str */
484931337658SMarcel Moolenaar 	memcpy(bp, str, len);
485031337658SMarcel Moolenaar 	bp[len] = '\0';
485131337658SMarcel Moolenaar 
485231337658SMarcel Moolenaar 	width = strtol(bp, &cp, 0);
485331337658SMarcel Moolenaar 	if (width == LONG_MIN || width == LONG_MAX
485431337658SMarcel Moolenaar 	    || bp == cp || *cp != '\0' ) {
485531337658SMarcel Moolenaar 	    width = 0;
485631337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
485731337658SMarcel Moolenaar 	}
485831337658SMarcel Moolenaar     } else if (flen) {
485931337658SMarcel Moolenaar 	if (flen != 2 || strncmp("%d", fmt, flen) != 0)
486031337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
4861d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
486231337658SMarcel Moolenaar 	    width = va_arg(xop->xo_vap, int);
486331337658SMarcel Moolenaar     }
486431337658SMarcel Moolenaar 
486531337658SMarcel Moolenaar     return width;
486631337658SMarcel Moolenaar }
486731337658SMarcel Moolenaar 
486831337658SMarcel Moolenaar static void
486931337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop)
487031337658SMarcel Moolenaar {
4871d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_ANCHOR);
487231337658SMarcel Moolenaar     xop->xo_anchor_offset = 0;
487331337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
487431337658SMarcel Moolenaar     xop->xo_anchor_min_width = 0;
487531337658SMarcel Moolenaar }
487631337658SMarcel Moolenaar 
487731337658SMarcel Moolenaar /*
487831337658SMarcel Moolenaar  * An anchor is a marker used to delay field width implications.
487931337658SMarcel Moolenaar  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
488031337658SMarcel Moolenaar  * We are looking for output like "     1/4/5"
488131337658SMarcel Moolenaar  *
488231337658SMarcel Moolenaar  * To make this work, we record the anchor and then return to
488331337658SMarcel Moolenaar  * format it when the end anchor tag is seen.
488431337658SMarcel Moolenaar  */
488531337658SMarcel Moolenaar static void
488642ff34c3SPhil Shafer xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
48878a6eceffSPhil Shafer 		 const char *str, ssize_t len)
488831337658SMarcel Moolenaar {
4889788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
489031337658SMarcel Moolenaar 	return;
489131337658SMarcel Moolenaar 
4892d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
489331337658SMarcel Moolenaar 	xo_failure(xop, "the anchor already recording is discarded");
489431337658SMarcel Moolenaar 
4895d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_ANCHOR);
489631337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
489731337658SMarcel Moolenaar     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
489831337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
489931337658SMarcel Moolenaar 
490031337658SMarcel Moolenaar     /*
490131337658SMarcel Moolenaar      * Now we find the width, if possible.  If it's not there,
490231337658SMarcel Moolenaar      * we'll get it on the end anchor.
490331337658SMarcel Moolenaar      */
490442ff34c3SPhil Shafer     xop->xo_anchor_min_width = xo_find_width(xop, xfip, str, len);
490531337658SMarcel Moolenaar }
490631337658SMarcel Moolenaar 
490731337658SMarcel Moolenaar static void
490842ff34c3SPhil Shafer xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
49098a6eceffSPhil Shafer 		 const char *str, ssize_t len)
491031337658SMarcel Moolenaar {
4911788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
491231337658SMarcel Moolenaar 	return;
491331337658SMarcel Moolenaar 
4914d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
491531337658SMarcel Moolenaar 	xo_failure(xop, "no start anchor");
491631337658SMarcel Moolenaar 	return;
491731337658SMarcel Moolenaar     }
491831337658SMarcel Moolenaar 
4919d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
492031337658SMarcel Moolenaar 
49218a6eceffSPhil Shafer     ssize_t width = xo_find_width(xop, xfip, str, len);
492231337658SMarcel Moolenaar     if (width == 0)
492331337658SMarcel Moolenaar 	width = xop->xo_anchor_min_width;
492431337658SMarcel Moolenaar 
492531337658SMarcel Moolenaar     if (width == 0)		/* No width given; nothing to do */
492631337658SMarcel Moolenaar 	goto done;
492731337658SMarcel Moolenaar 
492831337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
49298a6eceffSPhil Shafer     ssize_t start = xop->xo_anchor_offset;
49308a6eceffSPhil Shafer     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
49318a6eceffSPhil Shafer     ssize_t abswidth = (width > 0) ? width : -width;
49328a6eceffSPhil Shafer     ssize_t blen = abswidth - xop->xo_anchor_columns;
493331337658SMarcel Moolenaar 
493431337658SMarcel Moolenaar     if (blen <= 0)		/* Already over width */
493531337658SMarcel Moolenaar 	goto done;
493631337658SMarcel Moolenaar 
493731337658SMarcel Moolenaar     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
493831337658SMarcel Moolenaar 	xo_failure(xop, "width over %u are not supported",
493931337658SMarcel Moolenaar 		   XO_MAX_ANCHOR_WIDTH);
494031337658SMarcel Moolenaar 	goto done;
494131337658SMarcel Moolenaar     }
494231337658SMarcel Moolenaar 
494331337658SMarcel Moolenaar     /* Make a suitable padding field and emit it */
494431337658SMarcel Moolenaar     char *buf = alloca(blen);
494531337658SMarcel Moolenaar     memset(buf, ' ', blen);
4946d1a0d267SMarcel Moolenaar     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
494731337658SMarcel Moolenaar 
494831337658SMarcel Moolenaar     if (width < 0)		/* Already left justified */
494931337658SMarcel Moolenaar 	goto done;
495031337658SMarcel Moolenaar 
49518a6eceffSPhil Shafer     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
49528a6eceffSPhil Shafer     ssize_t delta = now - stop;
4953d1a0d267SMarcel Moolenaar     if (delta <= 0)		/* Strange; no output to move */
495431337658SMarcel Moolenaar 	goto done;
495531337658SMarcel Moolenaar 
495631337658SMarcel Moolenaar     /*
495731337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the padding data
495831337658SMarcel Moolenaar      * we just created (which might be an HTML <div> or text) before
495931337658SMarcel Moolenaar      * the formatted data.  We make a local copy, move it and then
496031337658SMarcel Moolenaar      * insert our copy.  We know there's room in the buffer, since
496131337658SMarcel Moolenaar      * we're just moving this around.
496231337658SMarcel Moolenaar      */
496331337658SMarcel Moolenaar     if (delta > blen)
496431337658SMarcel Moolenaar 	buf = alloca(delta);	/* Expand buffer if needed */
496531337658SMarcel Moolenaar 
496631337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
496731337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
496831337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
496931337658SMarcel Moolenaar 
497031337658SMarcel Moolenaar  done:
497131337658SMarcel Moolenaar     xo_anchor_clear(xop);
497231337658SMarcel Moolenaar }
497331337658SMarcel Moolenaar 
4974d1a0d267SMarcel Moolenaar static const char *
4975d1a0d267SMarcel Moolenaar xo_class_name (int ftype)
497631337658SMarcel Moolenaar {
4977d1a0d267SMarcel Moolenaar     switch (ftype) {
4978d1a0d267SMarcel Moolenaar     case 'D': return "decoration";
4979d1a0d267SMarcel Moolenaar     case 'E': return "error";
4980d1a0d267SMarcel Moolenaar     case 'L': return "label";
4981d1a0d267SMarcel Moolenaar     case 'N': return "note";
4982d1a0d267SMarcel Moolenaar     case 'P': return "padding";
4983d1a0d267SMarcel Moolenaar     case 'W': return "warning";
498431337658SMarcel Moolenaar     }
498531337658SMarcel Moolenaar 
4986d1a0d267SMarcel Moolenaar     return NULL;
498731337658SMarcel Moolenaar }
498831337658SMarcel Moolenaar 
4989d1a0d267SMarcel Moolenaar static const char *
4990d1a0d267SMarcel Moolenaar xo_tag_name (int ftype)
4991d1a0d267SMarcel Moolenaar {
4992d1a0d267SMarcel Moolenaar     switch (ftype) {
4993d1a0d267SMarcel Moolenaar     case 'E': return "__error";
4994d1a0d267SMarcel Moolenaar     case 'W': return "__warning";
4995d1a0d267SMarcel Moolenaar     }
4996d1a0d267SMarcel Moolenaar 
4997d1a0d267SMarcel Moolenaar     return NULL;
4998d1a0d267SMarcel Moolenaar }
4999d1a0d267SMarcel Moolenaar 
5000d1a0d267SMarcel Moolenaar static int
5001d1a0d267SMarcel Moolenaar xo_role_wants_default_format (int ftype)
5002d1a0d267SMarcel Moolenaar {
5003d1a0d267SMarcel Moolenaar     switch (ftype) {
5004d1a0d267SMarcel Moolenaar 	/* These roles can be completely empty and/or without formatting */
5005d1a0d267SMarcel Moolenaar     case 'C':
5006d1a0d267SMarcel Moolenaar     case 'G':
5007d1a0d267SMarcel Moolenaar     case '[':
5008d1a0d267SMarcel Moolenaar     case ']':
5009d1a0d267SMarcel Moolenaar 	return 0;
5010d1a0d267SMarcel Moolenaar     }
5011d1a0d267SMarcel Moolenaar 
5012d1a0d267SMarcel Moolenaar     return 1;
5013d1a0d267SMarcel Moolenaar }
5014d1a0d267SMarcel Moolenaar 
5015d1a0d267SMarcel Moolenaar static xo_mapping_t xo_role_names[] = {
5016d1a0d267SMarcel Moolenaar     { 'C', "color" },
5017d1a0d267SMarcel Moolenaar     { 'D', "decoration" },
5018d1a0d267SMarcel Moolenaar     { 'E', "error" },
5019d1a0d267SMarcel Moolenaar     { 'L', "label" },
5020d1a0d267SMarcel Moolenaar     { 'N', "note" },
5021d1a0d267SMarcel Moolenaar     { 'P', "padding" },
5022d1a0d267SMarcel Moolenaar     { 'T', "title" },
5023d1a0d267SMarcel Moolenaar     { 'U', "units" },
5024d1a0d267SMarcel Moolenaar     { 'V', "value" },
5025d1a0d267SMarcel Moolenaar     { 'W', "warning" },
5026d1a0d267SMarcel Moolenaar     { '[', "start-anchor" },
5027d1a0d267SMarcel Moolenaar     { ']', "stop-anchor" },
5028d1a0d267SMarcel Moolenaar     { 0, NULL }
5029d1a0d267SMarcel Moolenaar };
5030d1a0d267SMarcel Moolenaar 
5031d1a0d267SMarcel Moolenaar #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
5032d1a0d267SMarcel Moolenaar #define XO_ROLE_TEXT	'+'
5033d1a0d267SMarcel Moolenaar #define XO_ROLE_NEWLINE	'\n'
5034d1a0d267SMarcel Moolenaar 
5035d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_names[] = {
503642ff34c3SPhil Shafer     { XFF_ARGUMENT, "argument" },
5037d1a0d267SMarcel Moolenaar     { XFF_COLON, "colon" },
5038d1a0d267SMarcel Moolenaar     { XFF_COMMA, "comma" },
5039d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "display" },
5040d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "encoding" },
5041d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "gettext" },
5042d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "humanize" },
5043d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "hn" },
5044d1a0d267SMarcel Moolenaar     { XFF_HN_SPACE, "hn-space" },
5045d1a0d267SMarcel Moolenaar     { XFF_HN_DECIMAL, "hn-decimal" },
5046d1a0d267SMarcel Moolenaar     { XFF_HN_1000, "hn-1000" },
5047d1a0d267SMarcel Moolenaar     { XFF_KEY, "key" },
5048d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "leaf-list" },
5049d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "list" },
5050d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quotes" },
5051d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quote" },
5052d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "plural" },
5053d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quotes" },
5054d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quote" },
5055d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "trim" },
5056d1a0d267SMarcel Moolenaar     { XFF_WS, "white" },
5057d1a0d267SMarcel Moolenaar     { 0, NULL }
5058d1a0d267SMarcel Moolenaar };
5059d1a0d267SMarcel Moolenaar 
5060d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
5061d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_short_names[] = {
5062d1a0d267SMarcel Moolenaar     { XFF_COLON, "c" },
5063d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "d" },
5064d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "e" },
5065d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "g" },
5066d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "h" },
5067d1a0d267SMarcel Moolenaar     { XFF_KEY, "k" },
5068d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "l" },
5069d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "n" },
5070d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "p" },
5071d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "q" },
5072d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "t" },
5073d1a0d267SMarcel Moolenaar     { XFF_WS, "w" },
5074d1a0d267SMarcel Moolenaar     { 0, NULL }
5075d1a0d267SMarcel Moolenaar };
5076d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
5077d1a0d267SMarcel Moolenaar 
5078d1a0d267SMarcel Moolenaar static int
5079d1a0d267SMarcel Moolenaar xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5080d1a0d267SMarcel Moolenaar {
5081d1a0d267SMarcel Moolenaar     int rc = 1;
5082d1a0d267SMarcel Moolenaar     const char *cp;
5083d1a0d267SMarcel Moolenaar 
5084d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp; cp++)
5085d1a0d267SMarcel Moolenaar 	if (*cp == '{' || *cp == '\n')
5086d1a0d267SMarcel Moolenaar 	    rc += 1;
5087d1a0d267SMarcel Moolenaar 
5088d1a0d267SMarcel Moolenaar     return rc * 2 + 1;
5089d1a0d267SMarcel Moolenaar }
509031337658SMarcel Moolenaar 
509131337658SMarcel Moolenaar /*
5092d1a0d267SMarcel Moolenaar  * The field format is:
509331337658SMarcel Moolenaar  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5094d1a0d267SMarcel Moolenaar  * Roles are optional and include the following field types:
509531337658SMarcel Moolenaar  *   'D': decoration; something non-text and non-data (colons, commmas)
509631337658SMarcel Moolenaar  *   'E': error message
5097d1a0d267SMarcel Moolenaar  *   'G': gettext() the entire string; optional domainname as content
509831337658SMarcel Moolenaar  *   'L': label; text preceding data
509931337658SMarcel Moolenaar  *   'N': note; text following data
510031337658SMarcel Moolenaar  *   'P': padding; whitespace
510131337658SMarcel Moolenaar  *   'T': Title, where 'content' is a column title
510231337658SMarcel Moolenaar  *   'U': Units, where 'content' is the unit label
510331337658SMarcel Moolenaar  *   'V': value, where 'content' is the name of the field (the default)
510431337658SMarcel Moolenaar  *   'W': warning message
510531337658SMarcel Moolenaar  *   '[': start a section of anchored text
510631337658SMarcel Moolenaar  *   ']': end a section of anchored text
5107d1a0d267SMarcel Moolenaar  * The following modifiers are also supported:
510842ff34c3SPhil Shafer  *   'a': content is provided via argument (const char *), not descriptor
510931337658SMarcel Moolenaar  *   'c': flag: emit a colon after the label
5110d1a0d267SMarcel Moolenaar  *   'd': field is only emitted for display styles (text and html)
5111d1a0d267SMarcel Moolenaar  *   'e': field is only emitted for encoding styles (xml and json)
5112d1a0d267SMarcel Moolenaar  *   'g': gettext() the field
5113d1a0d267SMarcel Moolenaar  *   'h': humanize a numeric value (only for display styles)
511431337658SMarcel Moolenaar  *   'k': this field is a key, suitable for XPath predicates
511531337658SMarcel Moolenaar  *   'l': a leaf-list, a simple list of values
511631337658SMarcel Moolenaar  *   'n': no quotes around this field
5117d1a0d267SMarcel Moolenaar  *   'p': the field has plural gettext semantics (ngettext)
511831337658SMarcel Moolenaar  *   'q': add quotes around this field
511931337658SMarcel Moolenaar  *   't': trim whitespace around the value
512031337658SMarcel Moolenaar  *   'w': emit a blank after the label
512131337658SMarcel Moolenaar  * The print-fmt and encode-fmt strings is the printf-style formating
512231337658SMarcel Moolenaar  * for this data.  JSON and XML will use the encoding-fmt, if present.
512331337658SMarcel Moolenaar  * If the encode-fmt is not provided, it defaults to the print-fmt.
512431337658SMarcel Moolenaar  * If the print-fmt is not provided, it defaults to 's'.
512531337658SMarcel Moolenaar  */
5126d1a0d267SMarcel Moolenaar static const char *
5127d1a0d267SMarcel Moolenaar xo_parse_roles (xo_handle_t *xop, const char *fmt,
5128d1a0d267SMarcel Moolenaar 		const char *basep, xo_field_info_t *xfip)
5129d1a0d267SMarcel Moolenaar {
5130d1a0d267SMarcel Moolenaar     const char *sp;
5131d1a0d267SMarcel Moolenaar     unsigned ftype = 0;
5132d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = 0;
5133d1a0d267SMarcel Moolenaar     uint8_t fnum = 0;
513431337658SMarcel Moolenaar 
513542ff34c3SPhil Shafer     for (sp = basep; sp && *sp; sp++) {
513631337658SMarcel Moolenaar 	if (*sp == ':' || *sp == '/' || *sp == '}')
513731337658SMarcel Moolenaar 	    break;
513831337658SMarcel Moolenaar 
513931337658SMarcel Moolenaar 	if (*sp == '\\') {
514031337658SMarcel Moolenaar 	    if (sp[1] == '\0') {
514131337658SMarcel Moolenaar 		xo_failure(xop, "backslash at the end of string");
5142d1a0d267SMarcel Moolenaar 		return NULL;
514331337658SMarcel Moolenaar 	    }
5144d1a0d267SMarcel Moolenaar 
5145d1a0d267SMarcel Moolenaar 	    /* Anything backslashed is ignored */
514631337658SMarcel Moolenaar 	    sp += 1;
514731337658SMarcel Moolenaar 	    continue;
514831337658SMarcel Moolenaar 	}
514931337658SMarcel Moolenaar 
5150d1a0d267SMarcel Moolenaar 	if (*sp == ',') {
5151d1a0d267SMarcel Moolenaar 	    const char *np;
5152d1a0d267SMarcel Moolenaar 	    for (np = ++sp; *np; np++)
5153d1a0d267SMarcel Moolenaar 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5154d1a0d267SMarcel Moolenaar 		    break;
5155d1a0d267SMarcel Moolenaar 
51568a6eceffSPhil Shafer 	    ssize_t slen = np - sp;
5157d1a0d267SMarcel Moolenaar 	    if (slen > 0) {
5158d1a0d267SMarcel Moolenaar 		xo_xff_flags_t value;
5159d1a0d267SMarcel Moolenaar 
5160d1a0d267SMarcel Moolenaar 		value = xo_name_lookup(xo_role_names, sp, slen);
5161d1a0d267SMarcel Moolenaar 		if (value)
5162d1a0d267SMarcel Moolenaar 		    ftype = value;
5163d1a0d267SMarcel Moolenaar 		else {
5164d1a0d267SMarcel Moolenaar 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
5165d1a0d267SMarcel Moolenaar 		    if (value)
5166d1a0d267SMarcel Moolenaar 			flags |= value;
5167d1a0d267SMarcel Moolenaar 		    else
5168d1a0d267SMarcel Moolenaar 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
5169d1a0d267SMarcel Moolenaar 				   slen, sp);
5170d1a0d267SMarcel Moolenaar 		}
5171d1a0d267SMarcel Moolenaar 	    }
5172d1a0d267SMarcel Moolenaar 
5173d1a0d267SMarcel Moolenaar 	    sp = np - 1;
5174d1a0d267SMarcel Moolenaar 	    continue;
5175d1a0d267SMarcel Moolenaar 	}
5176d1a0d267SMarcel Moolenaar 
517731337658SMarcel Moolenaar 	switch (*sp) {
5178788ca347SMarcel Moolenaar 	case 'C':
517931337658SMarcel Moolenaar 	case 'D':
518031337658SMarcel Moolenaar 	case 'E':
5181d1a0d267SMarcel Moolenaar 	case 'G':
518231337658SMarcel Moolenaar 	case 'L':
518331337658SMarcel Moolenaar 	case 'N':
518431337658SMarcel Moolenaar 	case 'P':
518531337658SMarcel Moolenaar 	case 'T':
518631337658SMarcel Moolenaar 	case 'U':
518731337658SMarcel Moolenaar 	case 'V':
518831337658SMarcel Moolenaar 	case 'W':
518931337658SMarcel Moolenaar 	case '[':
519031337658SMarcel Moolenaar 	case ']':
519131337658SMarcel Moolenaar 	    if (ftype != 0) {
5192d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
5193d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5194d1a0d267SMarcel Moolenaar 		return NULL;
519531337658SMarcel Moolenaar 	    }
519631337658SMarcel Moolenaar 	    ftype = *sp;
519731337658SMarcel Moolenaar 	    break;
519831337658SMarcel Moolenaar 
5199d1a0d267SMarcel Moolenaar 	case '0':
5200d1a0d267SMarcel Moolenaar 	case '1':
5201d1a0d267SMarcel Moolenaar 	case '2':
5202d1a0d267SMarcel Moolenaar 	case '3':
5203d1a0d267SMarcel Moolenaar 	case '4':
5204d1a0d267SMarcel Moolenaar 	case '5':
5205d1a0d267SMarcel Moolenaar 	case '6':
5206d1a0d267SMarcel Moolenaar 	case '7':
5207d1a0d267SMarcel Moolenaar 	case '8':
5208d1a0d267SMarcel Moolenaar 	case '9':
5209d1a0d267SMarcel Moolenaar 	    fnum = (fnum * 10) + (*sp - '0');
5210d1a0d267SMarcel Moolenaar 	    break;
5211d1a0d267SMarcel Moolenaar 
521242ff34c3SPhil Shafer 	case 'a':
521342ff34c3SPhil Shafer 	    flags |= XFF_ARGUMENT;
521442ff34c3SPhil Shafer 	    break;
521542ff34c3SPhil Shafer 
521631337658SMarcel Moolenaar 	case 'c':
521731337658SMarcel Moolenaar 	    flags |= XFF_COLON;
521831337658SMarcel Moolenaar 	    break;
521931337658SMarcel Moolenaar 
522031337658SMarcel Moolenaar 	case 'd':
522131337658SMarcel Moolenaar 	    flags |= XFF_DISPLAY_ONLY;
522231337658SMarcel Moolenaar 	    break;
522331337658SMarcel Moolenaar 
522431337658SMarcel Moolenaar 	case 'e':
522531337658SMarcel Moolenaar 	    flags |= XFF_ENCODE_ONLY;
522631337658SMarcel Moolenaar 	    break;
522731337658SMarcel Moolenaar 
5228d1a0d267SMarcel Moolenaar 	case 'g':
5229d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_FIELD;
5230d1a0d267SMarcel Moolenaar 	    break;
5231d1a0d267SMarcel Moolenaar 
5232d1a0d267SMarcel Moolenaar 	case 'h':
5233d1a0d267SMarcel Moolenaar 	    flags |= XFF_HUMANIZE;
5234d1a0d267SMarcel Moolenaar 	    break;
5235d1a0d267SMarcel Moolenaar 
523631337658SMarcel Moolenaar 	case 'k':
523731337658SMarcel Moolenaar 	    flags |= XFF_KEY;
523831337658SMarcel Moolenaar 	    break;
523931337658SMarcel Moolenaar 
524031337658SMarcel Moolenaar 	case 'l':
524131337658SMarcel Moolenaar 	    flags |= XFF_LEAF_LIST;
524231337658SMarcel Moolenaar 	    break;
524331337658SMarcel Moolenaar 
524431337658SMarcel Moolenaar 	case 'n':
524531337658SMarcel Moolenaar 	    flags |= XFF_NOQUOTE;
524631337658SMarcel Moolenaar 	    break;
524731337658SMarcel Moolenaar 
5248d1a0d267SMarcel Moolenaar 	case 'p':
5249d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_PLURAL;
5250d1a0d267SMarcel Moolenaar 	    break;
5251d1a0d267SMarcel Moolenaar 
525231337658SMarcel Moolenaar 	case 'q':
525331337658SMarcel Moolenaar 	    flags |= XFF_QUOTE;
525431337658SMarcel Moolenaar 	    break;
525531337658SMarcel Moolenaar 
525631337658SMarcel Moolenaar 	case 't':
525731337658SMarcel Moolenaar 	    flags |= XFF_TRIM_WS;
525831337658SMarcel Moolenaar 	    break;
525931337658SMarcel Moolenaar 
526031337658SMarcel Moolenaar 	case 'w':
526131337658SMarcel Moolenaar 	    flags |= XFF_WS;
526231337658SMarcel Moolenaar 	    break;
526331337658SMarcel Moolenaar 
526431337658SMarcel Moolenaar 	default:
5265d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5266d1a0d267SMarcel Moolenaar 		       xo_printable(fmt));
526731337658SMarcel Moolenaar 	    /*
526831337658SMarcel Moolenaar 	     * No good answer here; a bad format will likely
526931337658SMarcel Moolenaar 	     * mean a core file.  We just return and hope
527031337658SMarcel Moolenaar 	     * the caller notices there's no output, and while
5271d1a0d267SMarcel Moolenaar 	     * that seems, well, bad, there's nothing better.
527231337658SMarcel Moolenaar 	     */
5273d1a0d267SMarcel Moolenaar 	    return NULL;
5274d1a0d267SMarcel Moolenaar 	}
5275d1a0d267SMarcel Moolenaar 
5276d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
5277d1a0d267SMarcel Moolenaar 	    if (flags & XFF_COLON) {
5278d1a0d267SMarcel Moolenaar 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5279d1a0d267SMarcel Moolenaar 			   "'%s'", xo_printable(fmt));
5280d1a0d267SMarcel Moolenaar 		flags &= ~XFF_COLON;
5281d1a0d267SMarcel Moolenaar 	    }
528231337658SMarcel Moolenaar 	}
528331337658SMarcel Moolenaar     }
528431337658SMarcel Moolenaar 
5285d1a0d267SMarcel Moolenaar     xfip->xfi_flags = flags;
5286d1a0d267SMarcel Moolenaar     xfip->xfi_ftype = ftype ?: 'V';
5287d1a0d267SMarcel Moolenaar     xfip->xfi_fnum = fnum;
5288d1a0d267SMarcel Moolenaar 
5289d1a0d267SMarcel Moolenaar     return sp;
5290d1a0d267SMarcel Moolenaar }
5291d1a0d267SMarcel Moolenaar 
5292d1a0d267SMarcel Moolenaar /*
5293d1a0d267SMarcel Moolenaar  * Number any remaining fields that need numbers.  Note that some
5294d1a0d267SMarcel Moolenaar  * field types (text, newline, escaped braces) never get numbers.
5295d1a0d267SMarcel Moolenaar  */
5296d1a0d267SMarcel Moolenaar static void
5297d1a0d267SMarcel Moolenaar xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5298d1a0d267SMarcel Moolenaar 				    const char *fmt UNUSED,
5299d1a0d267SMarcel Moolenaar 				    xo_field_info_t *fields)
5300d1a0d267SMarcel Moolenaar {
5301d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5302d1a0d267SMarcel Moolenaar     unsigned fnum, max_fields;
5303d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5304d1a0d267SMarcel Moolenaar 
5305d1a0d267SMarcel Moolenaar     /* First make a list of add the explicitly used bits */
5306d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5307d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5308d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5309d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5310d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5311d1a0d267SMarcel Moolenaar 	case 'G':
5312d1a0d267SMarcel Moolenaar 	    continue;
5313d1a0d267SMarcel Moolenaar 	}
5314d1a0d267SMarcel Moolenaar 
5315d1a0d267SMarcel Moolenaar 	fnum += 1;
5316d1a0d267SMarcel Moolenaar 	if (fnum >= 63)
5317d1a0d267SMarcel Moolenaar 	    break;
5318d1a0d267SMarcel Moolenaar 
5319d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5320d1a0d267SMarcel Moolenaar 	    bits |= 1 << xfip->xfi_fnum;
5321d1a0d267SMarcel Moolenaar     }
5322d1a0d267SMarcel Moolenaar 
5323d1a0d267SMarcel Moolenaar     max_fields = fnum;
5324d1a0d267SMarcel Moolenaar 
5325d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5326d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5327d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5328d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5329d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5330d1a0d267SMarcel Moolenaar 	case 'G':
5331d1a0d267SMarcel Moolenaar 	    continue;
5332d1a0d267SMarcel Moolenaar 	}
5333d1a0d267SMarcel Moolenaar 
5334d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum != 0)
5335d1a0d267SMarcel Moolenaar 	    continue;
5336d1a0d267SMarcel Moolenaar 
5337d1a0d267SMarcel Moolenaar 	/* Find the next unassigned field */
5338d1a0d267SMarcel Moolenaar 	for (fnum++; bits & (1 << fnum); fnum++)
5339d1a0d267SMarcel Moolenaar 	    continue;
5340d1a0d267SMarcel Moolenaar 
5341d1a0d267SMarcel Moolenaar 	if (fnum > max_fields)
5342d1a0d267SMarcel Moolenaar 	    break;
5343d1a0d267SMarcel Moolenaar 
5344d1a0d267SMarcel Moolenaar 	xfip->xfi_fnum = fnum;	/* Mark the field number */
5345d1a0d267SMarcel Moolenaar 	bits |= 1 << fnum;	/* Mark it used */
5346d1a0d267SMarcel Moolenaar     }
5347d1a0d267SMarcel Moolenaar }
5348d1a0d267SMarcel Moolenaar 
5349d1a0d267SMarcel Moolenaar /*
5350ee5cf116SPhil Shafer  * The format string uses field numbers, so we need to whiffle through it
5351d1a0d267SMarcel Moolenaar  * and make sure everything's sane and lovely.
5352d1a0d267SMarcel Moolenaar  */
5353d1a0d267SMarcel Moolenaar static int
5354d1a0d267SMarcel Moolenaar xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5355d1a0d267SMarcel Moolenaar 			xo_field_info_t *fields, unsigned num_fields)
5356d1a0d267SMarcel Moolenaar {
5357d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5358d1a0d267SMarcel Moolenaar     unsigned field, fnum;
5359d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5360d1a0d267SMarcel Moolenaar 
5361d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5362d1a0d267SMarcel Moolenaar 	/* Fields default to 1:1 with natural position */
5363d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == 0)
5364d1a0d267SMarcel Moolenaar 	    xfip->xfi_fnum = field + 1;
5365d1a0d267SMarcel Moolenaar 	else if (xfip->xfi_fnum > num_fields) {
5366d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5367d1a0d267SMarcel Moolenaar 	    return -1;
5368d1a0d267SMarcel Moolenaar 	}
5369d1a0d267SMarcel Moolenaar 
5370d1a0d267SMarcel Moolenaar 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5371d1a0d267SMarcel Moolenaar 	if (fnum < 64) {	/* Only test what fits */
5372d1a0d267SMarcel Moolenaar 	    if (bits & (1 << fnum)) {
5373d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field number %u reused: '%s'",
5374d1a0d267SMarcel Moolenaar 			   xfip->xfi_fnum, fmt);
5375d1a0d267SMarcel Moolenaar 		return -1;
5376d1a0d267SMarcel Moolenaar 	    }
5377d1a0d267SMarcel Moolenaar 	    bits |= 1 << fnum;
5378d1a0d267SMarcel Moolenaar 	}
5379d1a0d267SMarcel Moolenaar     }
5380d1a0d267SMarcel Moolenaar 
5381d1a0d267SMarcel Moolenaar     return 0;
5382d1a0d267SMarcel Moolenaar }
5383d1a0d267SMarcel Moolenaar 
5384d1a0d267SMarcel Moolenaar static int
5385d1a0d267SMarcel Moolenaar xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5386d1a0d267SMarcel Moolenaar 		 unsigned num_fields, const char *fmt)
5387d1a0d267SMarcel Moolenaar {
5388d1a0d267SMarcel Moolenaar     const char *cp, *sp, *ep, *basep;
5389d1a0d267SMarcel Moolenaar     unsigned field = 0;
5390d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip = fields;
5391d1a0d267SMarcel Moolenaar     unsigned seen_fnum = 0;
5392d1a0d267SMarcel Moolenaar 
5393d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5394d1a0d267SMarcel Moolenaar 	xfip->xfi_start = cp;
5395d1a0d267SMarcel Moolenaar 
5396d1a0d267SMarcel Moolenaar 	if (*cp == '\n') {
5397d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5398d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = 1;
5399d1a0d267SMarcel Moolenaar 	    cp += 1;
5400d1a0d267SMarcel Moolenaar 	    continue;
5401d1a0d267SMarcel Moolenaar 	}
5402d1a0d267SMarcel Moolenaar 
5403d1a0d267SMarcel Moolenaar 	if (*cp != '{') {
5404d1a0d267SMarcel Moolenaar 	    /* Normal text */
5405d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5406d1a0d267SMarcel Moolenaar 		if (*sp == '{' || *sp == '\n')
5407d1a0d267SMarcel Moolenaar 		    break;
5408d1a0d267SMarcel Moolenaar 	    }
5409d1a0d267SMarcel Moolenaar 
5410d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5411d1a0d267SMarcel Moolenaar 	    xfip->xfi_content = cp;
5412d1a0d267SMarcel Moolenaar 	    xfip->xfi_clen = sp - cp;
5413d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = sp;
5414d1a0d267SMarcel Moolenaar 
5415d1a0d267SMarcel Moolenaar 	    cp = sp;
5416d1a0d267SMarcel Moolenaar 	    continue;
5417d1a0d267SMarcel Moolenaar 	}
5418d1a0d267SMarcel Moolenaar 
5419d1a0d267SMarcel Moolenaar 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5420d1a0d267SMarcel Moolenaar 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5421d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5422d1a0d267SMarcel Moolenaar 
5423d1a0d267SMarcel Moolenaar 	    cp += 2;	/* Skip over _both_ characters */
5424d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5425d1a0d267SMarcel Moolenaar 		if (*sp == '}' && sp[1] == '}')
5426d1a0d267SMarcel Moolenaar 		    break;
5427d1a0d267SMarcel Moolenaar 	    }
5428d1a0d267SMarcel Moolenaar 	    if (*sp == '\0') {
5429d1a0d267SMarcel Moolenaar 		xo_failure(xop, "missing closing '}}': '%s'",
5430d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5431d1a0d267SMarcel Moolenaar 		return -1;
5432d1a0d267SMarcel Moolenaar 	    }
5433d1a0d267SMarcel Moolenaar 
5434d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5435d1a0d267SMarcel Moolenaar 
5436d1a0d267SMarcel Moolenaar 	    /* Move along the string, but don't run off the end */
5437d1a0d267SMarcel Moolenaar 	    if (*sp == '}' && sp[1] == '}')
5438d1a0d267SMarcel Moolenaar 		sp += 2;
5439d1a0d267SMarcel Moolenaar 	    cp = *sp ? sp : sp;
5440d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = cp;
5441d1a0d267SMarcel Moolenaar 	    continue;
5442d1a0d267SMarcel Moolenaar 	}
5443d1a0d267SMarcel Moolenaar 
5444d1a0d267SMarcel Moolenaar 	/* We are looking at the start of a field definition */
5445d1a0d267SMarcel Moolenaar 	xfip->xfi_start = basep = cp + 1;
5446d1a0d267SMarcel Moolenaar 
5447d1a0d267SMarcel Moolenaar 	const char *format = NULL;
54488a6eceffSPhil Shafer 	ssize_t flen = 0;
5449d1a0d267SMarcel Moolenaar 
5450d1a0d267SMarcel Moolenaar 	/* Looking at roles and modifiers */
5451d1a0d267SMarcel Moolenaar 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5452d1a0d267SMarcel Moolenaar 	if (sp == NULL) {
5453d1a0d267SMarcel Moolenaar 	    /* xo_failure has already been called */
5454d1a0d267SMarcel Moolenaar 	    return -1;
5455d1a0d267SMarcel Moolenaar 	}
5456d1a0d267SMarcel Moolenaar 
5457d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5458d1a0d267SMarcel Moolenaar 	    seen_fnum = 1;
5459d1a0d267SMarcel Moolenaar 
5460d1a0d267SMarcel Moolenaar 	/* Looking at content */
546131337658SMarcel Moolenaar 	if (*sp == ':') {
546231337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
546331337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
546431337658SMarcel Moolenaar 		    break;
546531337658SMarcel Moolenaar 		if (*sp == '\\') {
546631337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
546731337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
546831337658SMarcel Moolenaar 			return -1;
546931337658SMarcel Moolenaar 		    }
547031337658SMarcel Moolenaar 		    sp += 1;
547131337658SMarcel Moolenaar 		    continue;
547231337658SMarcel Moolenaar 		}
547331337658SMarcel Moolenaar 	    }
547431337658SMarcel Moolenaar 	    if (ep != sp) {
5475d1a0d267SMarcel Moolenaar 		xfip->xfi_clen = sp - ep;
5476d1a0d267SMarcel Moolenaar 		xfip->xfi_content = ep;
547731337658SMarcel Moolenaar 	    }
547831337658SMarcel Moolenaar 	} else {
5479d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
548031337658SMarcel Moolenaar 	    return -1;
548131337658SMarcel Moolenaar 	}
548231337658SMarcel Moolenaar 
5483d1a0d267SMarcel Moolenaar 	/* Looking at main (display) format */
548431337658SMarcel Moolenaar 	if (*sp == '/') {
548531337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
548631337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
548731337658SMarcel Moolenaar 		    break;
548831337658SMarcel Moolenaar 		if (*sp == '\\') {
548931337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
549031337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
549131337658SMarcel Moolenaar 			return -1;
549231337658SMarcel Moolenaar 		    }
549331337658SMarcel Moolenaar 		    sp += 1;
549431337658SMarcel Moolenaar 		    continue;
549531337658SMarcel Moolenaar 		}
549631337658SMarcel Moolenaar 	    }
549731337658SMarcel Moolenaar 	    flen = sp - ep;
549831337658SMarcel Moolenaar 	    format = ep;
549931337658SMarcel Moolenaar 	}
550031337658SMarcel Moolenaar 
5501d1a0d267SMarcel Moolenaar 	/* Looking at encoding format */
550231337658SMarcel Moolenaar 	if (*sp == '/') {
550331337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
550431337658SMarcel Moolenaar 		if (*sp == '}')
550531337658SMarcel Moolenaar 		    break;
550631337658SMarcel Moolenaar 	    }
5507d1a0d267SMarcel Moolenaar 
5508d1a0d267SMarcel Moolenaar 	    xfip->xfi_encoding = ep;
5509d1a0d267SMarcel Moolenaar 	    xfip->xfi_elen = sp - ep;
551031337658SMarcel Moolenaar 	}
551131337658SMarcel Moolenaar 
5512d1a0d267SMarcel Moolenaar 	if (*sp != '}') {
5513d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
551431337658SMarcel Moolenaar 	    return -1;
551531337658SMarcel Moolenaar 	}
551631337658SMarcel Moolenaar 
5517d1a0d267SMarcel Moolenaar 	xfip->xfi_len = sp - xfip->xfi_start;
5518d1a0d267SMarcel Moolenaar 	xfip->xfi_next = ++sp;
5519d1a0d267SMarcel Moolenaar 
5520d1a0d267SMarcel Moolenaar 	/* If we have content, then we have a default format */
552142ff34c3SPhil Shafer 	if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5522d1a0d267SMarcel Moolenaar 	    if (format) {
5523d1a0d267SMarcel Moolenaar 		xfip->xfi_format = format;
5524d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = flen;
5525d1a0d267SMarcel Moolenaar 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
552642ff34c3SPhil Shafer 		xfip->xfi_format = xo_default_format;
5527d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = 2;
5528d1a0d267SMarcel Moolenaar 	    }
552931337658SMarcel Moolenaar 	}
553031337658SMarcel Moolenaar 
5531d1a0d267SMarcel Moolenaar 	cp = sp;
5532d1a0d267SMarcel Moolenaar     }
5533545ddfbeSMarcel Moolenaar 
5534d1a0d267SMarcel Moolenaar     int rc = 0;
5535d1a0d267SMarcel Moolenaar 
5536d1a0d267SMarcel Moolenaar     /*
5537d1a0d267SMarcel Moolenaar      * If we saw a field number on at least one field, then we need
5538d1a0d267SMarcel Moolenaar      * to enforce some rules and/or guidelines.
5539d1a0d267SMarcel Moolenaar      */
5540d1a0d267SMarcel Moolenaar     if (seen_fnum)
5541d1a0d267SMarcel Moolenaar 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5542d1a0d267SMarcel Moolenaar 
5543d1a0d267SMarcel Moolenaar     return rc;
5544d1a0d267SMarcel Moolenaar }
5545d1a0d267SMarcel Moolenaar 
5546d1a0d267SMarcel Moolenaar /*
5547d1a0d267SMarcel Moolenaar  * We are passed a pointer to a format string just past the "{G:}"
5548d1a0d267SMarcel Moolenaar  * field.  We build a simplified version of the format string.
5549d1a0d267SMarcel Moolenaar  */
5550d1a0d267SMarcel Moolenaar static int
5551d1a0d267SMarcel Moolenaar xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5552d1a0d267SMarcel Moolenaar 		       xo_buffer_t *xbp,
5553d1a0d267SMarcel Moolenaar 		       xo_field_info_t *fields,
5554d1a0d267SMarcel Moolenaar 		       int this_field,
5555d1a0d267SMarcel Moolenaar 		       const char *fmt UNUSED,
5556d1a0d267SMarcel Moolenaar 		       xo_simplify_field_func_t field_cb)
5557d1a0d267SMarcel Moolenaar {
5558d1a0d267SMarcel Moolenaar     unsigned ftype;
5559d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags;
5560d1a0d267SMarcel Moolenaar     int field = this_field + 1;
5561d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5562d1a0d267SMarcel Moolenaar     char ch;
5563d1a0d267SMarcel Moolenaar 
5564d1a0d267SMarcel Moolenaar     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5565d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5566d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5567d1a0d267SMarcel Moolenaar 
5568d1a0d267SMarcel Moolenaar 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5569d1a0d267SMarcel Moolenaar 	    if (field_cb)
5570d1a0d267SMarcel Moolenaar 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5571d1a0d267SMarcel Moolenaar 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5572d1a0d267SMarcel Moolenaar 	}
5573d1a0d267SMarcel Moolenaar 
5574d1a0d267SMarcel Moolenaar 	switch (ftype) {
5575d1a0d267SMarcel Moolenaar 	case 'G':
5576d1a0d267SMarcel Moolenaar 	    /* Ignore gettext roles */
5577d1a0d267SMarcel Moolenaar 	    break;
5578d1a0d267SMarcel Moolenaar 
5579d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5580d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1);
5581d1a0d267SMarcel Moolenaar 	    break;
5582d1a0d267SMarcel Moolenaar 
5583d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5584d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5585d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5586d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5587d1a0d267SMarcel Moolenaar 	    break;
5588d1a0d267SMarcel Moolenaar 
5589d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5590d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5591d1a0d267SMarcel Moolenaar 	    break;
5592d1a0d267SMarcel Moolenaar 
5593d1a0d267SMarcel Moolenaar 	default:
5594d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5595d1a0d267SMarcel Moolenaar 	    if (ftype != 'V') {
5596d1a0d267SMarcel Moolenaar 		ch = ftype;
5597d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, &ch, 1);
5598d1a0d267SMarcel Moolenaar 	    }
5599d1a0d267SMarcel Moolenaar 
5600d1a0d267SMarcel Moolenaar 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5601d1a0d267SMarcel Moolenaar 	    if (fnum) {
5602d1a0d267SMarcel Moolenaar 		char num[12];
5603d1a0d267SMarcel Moolenaar 		/* Field numbers are origin 1, not 0, following printf(3) */
5604d1a0d267SMarcel Moolenaar 		snprintf(num, sizeof(num), "%u", fnum);
5605d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, num, strlen(num));
5606d1a0d267SMarcel Moolenaar 	    }
5607d1a0d267SMarcel Moolenaar 
5608d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, ":", 1);
5609d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5610d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5611d1a0d267SMarcel Moolenaar 	}
5612d1a0d267SMarcel Moolenaar     }
5613d1a0d267SMarcel Moolenaar 
5614d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1);
5615d1a0d267SMarcel Moolenaar     return 0;
5616d1a0d267SMarcel Moolenaar }
5617d1a0d267SMarcel Moolenaar 
5618d1a0d267SMarcel Moolenaar void
5619d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5620d1a0d267SMarcel Moolenaar void
5621d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *fields)
5622d1a0d267SMarcel Moolenaar {
5623d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5624d1a0d267SMarcel Moolenaar 
5625d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5626d1a0d267SMarcel Moolenaar 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5627d1a0d267SMarcel Moolenaar 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5628d1a0d267SMarcel Moolenaar 	       (unsigned long) xfip->xfi_flags,
5629d1a0d267SMarcel Moolenaar 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5630d1a0d267SMarcel Moolenaar 	       xfip->xfi_ftype,
56318a6eceffSPhil Shafer 	       (int) xfip->xfi_clen, xfip->xfi_content ?: "",
56328a6eceffSPhil Shafer 	       (int) xfip->xfi_flen, xfip->xfi_format ?: "",
56338a6eceffSPhil Shafer 	       (int) xfip->xfi_elen, xfip->xfi_encoding ?: "");
5634d1a0d267SMarcel Moolenaar     }
5635d1a0d267SMarcel Moolenaar }
5636d1a0d267SMarcel Moolenaar 
5637d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
5638d1a0d267SMarcel Moolenaar /*
5639d1a0d267SMarcel Moolenaar  * Find the field that matches the given field number
5640d1a0d267SMarcel Moolenaar  */
5641d1a0d267SMarcel Moolenaar static xo_field_info_t *
5642d1a0d267SMarcel Moolenaar xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5643d1a0d267SMarcel Moolenaar {
5644d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5645d1a0d267SMarcel Moolenaar 
5646d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++)
5647d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == fnum)
5648d1a0d267SMarcel Moolenaar 	    return xfip;
5649d1a0d267SMarcel Moolenaar 
5650d1a0d267SMarcel Moolenaar     return NULL;
5651d1a0d267SMarcel Moolenaar }
5652d1a0d267SMarcel Moolenaar 
5653d1a0d267SMarcel Moolenaar /*
5654d1a0d267SMarcel Moolenaar  * At this point, we need to consider if the fields have been reordered,
5655d1a0d267SMarcel Moolenaar  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5656d1a0d267SMarcel Moolenaar  *
5657d1a0d267SMarcel Moolenaar  * We need to rewrite the new_fields using the old fields order,
5658d1a0d267SMarcel Moolenaar  * so that we can render the message using the arguments as they
5659d1a0d267SMarcel Moolenaar  * appear on the stack.  It's a lot of work, but we don't really
5660d1a0d267SMarcel Moolenaar  * want to (eventually) fall into the standard printf code which
5661d1a0d267SMarcel Moolenaar  * means using the arguments straight (and in order) from the
5662d1a0d267SMarcel Moolenaar  * varargs we were originally passed.
5663d1a0d267SMarcel Moolenaar  */
5664d1a0d267SMarcel Moolenaar static void
5665d1a0d267SMarcel Moolenaar xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
5666d1a0d267SMarcel Moolenaar 			   xo_field_info_t *fields, unsigned max_fields)
5667d1a0d267SMarcel Moolenaar {
5668d1a0d267SMarcel Moolenaar     xo_field_info_t tmp[max_fields];
5669d1a0d267SMarcel Moolenaar     bzero(tmp, max_fields * sizeof(tmp[0]));
5670d1a0d267SMarcel Moolenaar 
5671d1a0d267SMarcel Moolenaar     unsigned fnum = 0;
5672d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *outp, *zp;
5673d1a0d267SMarcel Moolenaar     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
5674d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5675d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5676d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5677d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5678d1a0d267SMarcel Moolenaar 	case 'G':
5679d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5680d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5681d1a0d267SMarcel Moolenaar 	    continue;
5682d1a0d267SMarcel Moolenaar 	}
5683d1a0d267SMarcel Moolenaar 
5684d1a0d267SMarcel Moolenaar 	zp = xo_gettext_find_field(fields, ++fnum);
5685d1a0d267SMarcel Moolenaar 	if (zp == NULL) { 	/* Should not occur */
5686d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5687d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5688d1a0d267SMarcel Moolenaar 	    continue;
5689d1a0d267SMarcel Moolenaar 	}
5690d1a0d267SMarcel Moolenaar 
5691d1a0d267SMarcel Moolenaar 	*outp = *zp;
5692d1a0d267SMarcel Moolenaar 	outp->xfi_renum = newp->xfi_fnum;
5693d1a0d267SMarcel Moolenaar     }
5694d1a0d267SMarcel Moolenaar 
5695d1a0d267SMarcel Moolenaar     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
5696d1a0d267SMarcel Moolenaar }
5697d1a0d267SMarcel Moolenaar 
5698d1a0d267SMarcel Moolenaar /*
5699d1a0d267SMarcel Moolenaar  * We've got two lists of fields, the old list from the original
5700d1a0d267SMarcel Moolenaar  * format string and the new one from the parsed gettext reply.  The
5701d1a0d267SMarcel Moolenaar  * new list has the localized words, where the old list has the
5702d1a0d267SMarcel Moolenaar  * formatting information.  We need to combine them into a single list
5703d1a0d267SMarcel Moolenaar  * (the new list).
5704d1a0d267SMarcel Moolenaar  *
5705d1a0d267SMarcel Moolenaar  * If the list needs to be reordered, then we've got more serious work
5706d1a0d267SMarcel Moolenaar  * to do.
5707d1a0d267SMarcel Moolenaar  */
5708d1a0d267SMarcel Moolenaar static int
5709d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
5710d1a0d267SMarcel Moolenaar 		    const char *gtfmt, xo_field_info_t *old_fields,
5711d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields, unsigned new_max_fields,
5712d1a0d267SMarcel Moolenaar 		    int *reorderedp)
5713d1a0d267SMarcel Moolenaar {
5714d1a0d267SMarcel Moolenaar     int reordered = 0;
5715d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *oldp, *startp = old_fields;
5716d1a0d267SMarcel Moolenaar 
5717d1a0d267SMarcel Moolenaar     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
5718d1a0d267SMarcel Moolenaar 
5719d1a0d267SMarcel Moolenaar     for (newp = new_fields; newp->xfi_ftype; newp++) {
5720d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5721d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5722d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5723d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5724d1a0d267SMarcel Moolenaar 	    continue;
5725d1a0d267SMarcel Moolenaar 
5726d1a0d267SMarcel Moolenaar 	case 'V':
5727d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5728d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype != 'V')
5729d1a0d267SMarcel Moolenaar 		    continue;
5730d1a0d267SMarcel Moolenaar 		if (newp->xfi_clen != oldp->xfi_clen
5731d1a0d267SMarcel Moolenaar 		    || strncmp(newp->xfi_content, oldp->xfi_content,
5732d1a0d267SMarcel Moolenaar 			       oldp->xfi_clen) != 0) {
5733d1a0d267SMarcel Moolenaar 		    reordered = 1;
5734d1a0d267SMarcel Moolenaar 		    continue;
5735d1a0d267SMarcel Moolenaar 		}
5736d1a0d267SMarcel Moolenaar 		startp = oldp + 1;
5737d1a0d267SMarcel Moolenaar 		break;
5738d1a0d267SMarcel Moolenaar 	    }
5739d1a0d267SMarcel Moolenaar 
5740d1a0d267SMarcel Moolenaar 	    /* Didn't find it on the first pass (starting from start) */
5741d1a0d267SMarcel Moolenaar 	    if (oldp->xfi_ftype == 0) {
5742d1a0d267SMarcel Moolenaar 		for (oldp = old_fields; oldp < startp; oldp++) {
5743d1a0d267SMarcel Moolenaar 		    if (oldp->xfi_ftype != 'V')
5744d1a0d267SMarcel Moolenaar 			continue;
5745d1a0d267SMarcel Moolenaar 		    if (newp->xfi_clen != oldp->xfi_clen)
5746d1a0d267SMarcel Moolenaar 			continue;
5747d1a0d267SMarcel Moolenaar 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
5748d1a0d267SMarcel Moolenaar 				oldp->xfi_clen) != 0)
5749d1a0d267SMarcel Moolenaar 			continue;
5750d1a0d267SMarcel Moolenaar 		    reordered = 1;
5751d1a0d267SMarcel Moolenaar 		    break;
5752d1a0d267SMarcel Moolenaar 		}
5753d1a0d267SMarcel Moolenaar 		if (oldp == startp) {
5754d1a0d267SMarcel Moolenaar 		    /* Field not found */
5755d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "post-gettext format can't find field "
5756d1a0d267SMarcel Moolenaar 			       "'%.*s' in format '%s'",
5757d1a0d267SMarcel Moolenaar 			       newp->xfi_clen, newp->xfi_content,
5758d1a0d267SMarcel Moolenaar 			       xo_printable(gtfmt));
5759d1a0d267SMarcel Moolenaar 		    return -1;
5760d1a0d267SMarcel Moolenaar 		}
5761d1a0d267SMarcel Moolenaar 	    }
5762d1a0d267SMarcel Moolenaar 	    break;
5763d1a0d267SMarcel Moolenaar 
5764d1a0d267SMarcel Moolenaar 	default:
5765d1a0d267SMarcel Moolenaar 	    /*
5766d1a0d267SMarcel Moolenaar 	     * Other fields don't have names for us to use, so if
5767d1a0d267SMarcel Moolenaar 	     * the types aren't the same, then we'll have to assume
5768d1a0d267SMarcel Moolenaar 	     * the original field is a match.
5769d1a0d267SMarcel Moolenaar 	     */
5770d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5771d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
5772d1a0d267SMarcel Moolenaar 		    break;
5773d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == newp->xfi_ftype)
5774d1a0d267SMarcel Moolenaar 		    goto copy_it; /* Assumably we have a match */
5775d1a0d267SMarcel Moolenaar 	    }
5776d1a0d267SMarcel Moolenaar 	    continue;
5777d1a0d267SMarcel Moolenaar 	}
5778d1a0d267SMarcel Moolenaar 
5779d1a0d267SMarcel Moolenaar 	/*
5780d1a0d267SMarcel Moolenaar 	 * Found a match; copy over appropriate fields
5781d1a0d267SMarcel Moolenaar 	 */
5782d1a0d267SMarcel Moolenaar     copy_it:
5783d1a0d267SMarcel Moolenaar 	newp->xfi_flags = oldp->xfi_flags;
5784d1a0d267SMarcel Moolenaar 	newp->xfi_fnum = oldp->xfi_fnum;
5785d1a0d267SMarcel Moolenaar 	newp->xfi_format = oldp->xfi_format;
5786d1a0d267SMarcel Moolenaar 	newp->xfi_flen = oldp->xfi_flen;
5787d1a0d267SMarcel Moolenaar 	newp->xfi_encoding = oldp->xfi_encoding;
5788d1a0d267SMarcel Moolenaar 	newp->xfi_elen = oldp->xfi_elen;
5789d1a0d267SMarcel Moolenaar     }
5790d1a0d267SMarcel Moolenaar 
5791d1a0d267SMarcel Moolenaar     *reorderedp = reordered;
5792d1a0d267SMarcel Moolenaar     if (reordered) {
5793d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
5794d1a0d267SMarcel Moolenaar 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
5795d1a0d267SMarcel Moolenaar     }
5796d1a0d267SMarcel Moolenaar 
5797d1a0d267SMarcel Moolenaar     return 0;
5798d1a0d267SMarcel Moolenaar }
5799d1a0d267SMarcel Moolenaar 
5800d1a0d267SMarcel Moolenaar /*
5801d1a0d267SMarcel Moolenaar  * We don't want to make gettext() calls here with a complete format
5802d1a0d267SMarcel Moolenaar  * string, since that means changing a flag would mean a
5803d1a0d267SMarcel Moolenaar  * labor-intensive re-translation expense.  Instead we build a
5804d1a0d267SMarcel Moolenaar  * simplified form with a reduced level of detail, perform a lookup on
5805d1a0d267SMarcel Moolenaar  * that string and then re-insert the formating info.
5806d1a0d267SMarcel Moolenaar  *
5807d1a0d267SMarcel Moolenaar  * So something like:
5808d1a0d267SMarcel Moolenaar  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
5809d1a0d267SMarcel Moolenaar  * would have a lookup string of:
5810d1a0d267SMarcel Moolenaar  *   "close {:fd} returned {:error} {:test}\n"
5811d1a0d267SMarcel Moolenaar  *
5812d1a0d267SMarcel Moolenaar  * We also need to handling reordering of fields, where the gettext()
5813d1a0d267SMarcel Moolenaar  * reply string uses fields in a different order than the original
5814d1a0d267SMarcel Moolenaar  * format string:
5815d1a0d267SMarcel Moolenaar  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
5816d1a0d267SMarcel Moolenaar  * If we have to reorder fields within the message, then things get
5817d1a0d267SMarcel Moolenaar  * complicated.  See xo_gettext_rewrite_fields.
5818d1a0d267SMarcel Moolenaar  *
5819d1a0d267SMarcel Moolenaar  * Summary: i18n aighn't cheap.
5820d1a0d267SMarcel Moolenaar  */
5821d1a0d267SMarcel Moolenaar static const char *
582242ff34c3SPhil Shafer xo_gettext_build_format (xo_handle_t *xop,
582342ff34c3SPhil Shafer 			 xo_field_info_t *fields, int this_field,
5824d1a0d267SMarcel Moolenaar 			 const char *fmt, char **new_fmtp)
5825d1a0d267SMarcel Moolenaar {
5826d1a0d267SMarcel Moolenaar     if (xo_style_is_encoding(xop))
5827d1a0d267SMarcel Moolenaar 	goto bail;
5828d1a0d267SMarcel Moolenaar 
5829d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
5830d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
5831d1a0d267SMarcel Moolenaar 
5832d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields,
5833d1a0d267SMarcel Moolenaar 				   this_field, fmt, NULL))
5834d1a0d267SMarcel Moolenaar 	goto bail2;
5835d1a0d267SMarcel Moolenaar 
5836d1a0d267SMarcel Moolenaar     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
5837d1a0d267SMarcel Moolenaar     if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
5838d1a0d267SMarcel Moolenaar 	goto bail2;
5839d1a0d267SMarcel Moolenaar 
5840d1a0d267SMarcel Moolenaar     xo_buf_cleanup(&xb);
5841d1a0d267SMarcel Moolenaar 
5842d1a0d267SMarcel Moolenaar     char *new_fmt = xo_strndup(gtfmt, -1);
5843d1a0d267SMarcel Moolenaar     if (new_fmt == NULL)
5844d1a0d267SMarcel Moolenaar 	goto bail2;
5845d1a0d267SMarcel Moolenaar 
5846d1a0d267SMarcel Moolenaar     *new_fmtp = new_fmt;
5847d1a0d267SMarcel Moolenaar     return new_fmt;
5848d1a0d267SMarcel Moolenaar 
5849d1a0d267SMarcel Moolenaar  bail2:
5850d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(&xb);
5851d1a0d267SMarcel Moolenaar  bail:
5852d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
5853d1a0d267SMarcel Moolenaar     return fmt;
5854d1a0d267SMarcel Moolenaar }
5855d1a0d267SMarcel Moolenaar 
5856d1a0d267SMarcel Moolenaar static void
5857d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
58588a6eceffSPhil Shafer 			    ssize_t *fstart, unsigned min_fstart,
58598a6eceffSPhil Shafer 			    ssize_t *fend, unsigned max_fend)
5860d1a0d267SMarcel Moolenaar {
5861d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5862d1a0d267SMarcel Moolenaar     char *buf;
58638a6eceffSPhil Shafer     ssize_t base = fstart[min_fstart];
58648a6eceffSPhil Shafer     ssize_t blen = fend[max_fend] - base;
5865d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
5866d1a0d267SMarcel Moolenaar 
5867d1a0d267SMarcel Moolenaar     if (blen == 0)
5868d1a0d267SMarcel Moolenaar 	return;
5869d1a0d267SMarcel Moolenaar 
5870d1a0d267SMarcel Moolenaar     buf = xo_realloc(NULL, blen);
5871d1a0d267SMarcel Moolenaar     if (buf == NULL)
5872d1a0d267SMarcel Moolenaar 	return;
5873d1a0d267SMarcel Moolenaar 
5874d1a0d267SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
5875d1a0d267SMarcel Moolenaar 
58768a6eceffSPhil Shafer     unsigned field = min_fstart, len, fnum;
58778a6eceffSPhil Shafer     ssize_t soff, doff = base;
5878d1a0d267SMarcel Moolenaar     xo_field_info_t *zp;
5879d1a0d267SMarcel Moolenaar 
5880d1a0d267SMarcel Moolenaar     /*
5881d1a0d267SMarcel Moolenaar      * Be aware there are two competing views of "field number": we
5882d1a0d267SMarcel Moolenaar      * want the user to thing in terms of "The {1:size}" where {G:},
5883d1a0d267SMarcel Moolenaar      * newlines, escaped braces, and text don't have numbers.  But is
5884d1a0d267SMarcel Moolenaar      * also the internal view, where we have an array of
5885d1a0d267SMarcel Moolenaar      * xo_field_info_t and every field have an index.  fnum, fstart[]
5886d1a0d267SMarcel Moolenaar      * and fend[] are the latter, but xfi_renum is the former.
5887d1a0d267SMarcel Moolenaar      */
5888d1a0d267SMarcel Moolenaar     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
5889d1a0d267SMarcel Moolenaar 	fnum = field;
5890d1a0d267SMarcel Moolenaar 	if (xfip->xfi_renum) {
5891d1a0d267SMarcel Moolenaar 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
5892d1a0d267SMarcel Moolenaar 	    fnum = zp ? zp - fields : field;
5893d1a0d267SMarcel Moolenaar 	}
5894d1a0d267SMarcel Moolenaar 
5895d1a0d267SMarcel Moolenaar 	soff = fstart[fnum];
5896d1a0d267SMarcel Moolenaar 	len = fend[fnum] - soff;
5897d1a0d267SMarcel Moolenaar 
5898d1a0d267SMarcel Moolenaar 	if (len > 0) {
5899d1a0d267SMarcel Moolenaar 	    soff -= base;
5900d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
5901d1a0d267SMarcel Moolenaar 	    doff += len;
5902d1a0d267SMarcel Moolenaar 	}
5903d1a0d267SMarcel Moolenaar     }
5904d1a0d267SMarcel Moolenaar 
5905d1a0d267SMarcel Moolenaar     xo_free(buf);
5906d1a0d267SMarcel Moolenaar }
5907d1a0d267SMarcel Moolenaar #else  /* HAVE_GETTEXT */
5908d1a0d267SMarcel Moolenaar static const char *
5909d1a0d267SMarcel Moolenaar xo_gettext_build_format (xo_handle_t *xop UNUSED,
5910d1a0d267SMarcel Moolenaar 			 xo_field_info_t *fields UNUSED,
5911d1a0d267SMarcel Moolenaar 			 int this_field UNUSED,
5912d1a0d267SMarcel Moolenaar 			 const char *fmt UNUSED, char **new_fmtp)
5913d1a0d267SMarcel Moolenaar {
5914d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
5915d1a0d267SMarcel Moolenaar     return fmt;
5916d1a0d267SMarcel Moolenaar }
5917d1a0d267SMarcel Moolenaar 
5918d1a0d267SMarcel Moolenaar static int
5919d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
5920d1a0d267SMarcel Moolenaar 		    const char *gtfmt UNUSED,
5921d1a0d267SMarcel Moolenaar 		    xo_field_info_t *old_fields UNUSED,
5922d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields UNUSED,
5923d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields UNUSED,
5924d1a0d267SMarcel Moolenaar 		    int *reorderedp UNUSED)
5925d1a0d267SMarcel Moolenaar {
5926d1a0d267SMarcel Moolenaar     return -1;
5927d1a0d267SMarcel Moolenaar }
5928d1a0d267SMarcel Moolenaar 
5929d1a0d267SMarcel Moolenaar static void
5930d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
5931d1a0d267SMarcel Moolenaar 		    xo_field_info_t *fields UNUSED,
59328a6eceffSPhil Shafer 		    ssize_t *fstart UNUSED, unsigned min_fstart UNUSED,
59338a6eceffSPhil Shafer 		    ssize_t *fend UNUSED, unsigned max_fend UNUSED)
5934d1a0d267SMarcel Moolenaar {
5935d1a0d267SMarcel Moolenaar     return;
5936d1a0d267SMarcel Moolenaar }
5937d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
5938d1a0d267SMarcel Moolenaar 
5939d1a0d267SMarcel Moolenaar /*
594042ff34c3SPhil Shafer  * Emit a set of fields.  This is really the core of libxo.
5941d1a0d267SMarcel Moolenaar  */
59428a6eceffSPhil Shafer static ssize_t
594342ff34c3SPhil Shafer xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
594442ff34c3SPhil Shafer 		   unsigned max_fields, const char *fmt)
5945d1a0d267SMarcel Moolenaar {
5946d1a0d267SMarcel Moolenaar     int gettext_inuse = 0;
5947d1a0d267SMarcel Moolenaar     int gettext_changed = 0;
5948d1a0d267SMarcel Moolenaar     int gettext_reordered = 0;
594942ff34c3SPhil Shafer     unsigned ftype;
595042ff34c3SPhil Shafer     xo_xff_flags_t flags;
5951d1a0d267SMarcel Moolenaar     xo_field_info_t *new_fields = NULL;
595242ff34c3SPhil Shafer     xo_field_info_t *xfip;
595342ff34c3SPhil Shafer     unsigned field;
59548a6eceffSPhil Shafer     ssize_t rc = 0;
595542ff34c3SPhil Shafer 
5956d1a0d267SMarcel Moolenaar     int flush = XOF_ISSET(xop, XOF_FLUSH);
5957d1a0d267SMarcel Moolenaar     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
5958d1a0d267SMarcel Moolenaar     char *new_fmt = NULL;
5959d1a0d267SMarcel Moolenaar 
5960d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
5961d1a0d267SMarcel Moolenaar 	flush_line = 0;
5962d1a0d267SMarcel Moolenaar 
5963d1a0d267SMarcel Moolenaar     /*
5964d1a0d267SMarcel Moolenaar      * Some overhead for gettext; if the fields in the msgstr returned
5965d1a0d267SMarcel Moolenaar      * by gettext are reordered, then we need to record start and end
5966d1a0d267SMarcel Moolenaar      * for each field.  We'll go ahead and render the fields in the
5967d1a0d267SMarcel Moolenaar      * normal order, but later we can then reconstruct the reordered
5968d1a0d267SMarcel Moolenaar      * fields using these fstart/fend values.
5969d1a0d267SMarcel Moolenaar      */
5970d1a0d267SMarcel Moolenaar     unsigned flimit = max_fields * 2; /* Pessimistic limit */
5971d1a0d267SMarcel Moolenaar     unsigned min_fstart = flimit - 1;
5972d1a0d267SMarcel Moolenaar     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
59738a6eceffSPhil Shafer     ssize_t fstart[flimit];
5974d1a0d267SMarcel Moolenaar     bzero(fstart, flimit * sizeof(fstart[0]));
59758a6eceffSPhil Shafer     ssize_t fend[flimit];
5976d1a0d267SMarcel Moolenaar     bzero(fend, flimit * sizeof(fend[0]));
5977d1a0d267SMarcel Moolenaar 
5978d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields;
5979d1a0d267SMarcel Moolenaar 	 xfip++, field++) {
5980d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5981d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5982d1a0d267SMarcel Moolenaar 
5983d1a0d267SMarcel Moolenaar 	/* Record field start offset */
5984d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
5985d1a0d267SMarcel Moolenaar 	    fstart[field] = xo_buf_offset(&xop->xo_data);
5986d1a0d267SMarcel Moolenaar 	    if (min_fstart > field)
5987d1a0d267SMarcel Moolenaar 		min_fstart = field;
5988d1a0d267SMarcel Moolenaar 	}
5989d1a0d267SMarcel Moolenaar 
599042ff34c3SPhil Shafer 	const char *content = xfip->xfi_content;
59918a6eceffSPhil Shafer 	ssize_t clen = xfip->xfi_clen;
599242ff34c3SPhil Shafer 
599342ff34c3SPhil Shafer 	if (flags & XFF_ARGUMENT) {
599442ff34c3SPhil Shafer 	    /*
599542ff34c3SPhil Shafer 	     * Argument flag means the content isn't given in the descriptor,
599642ff34c3SPhil Shafer 	     * but as a UTF-8 string ('const char *') argument in xo_vap.
599742ff34c3SPhil Shafer 	     */
599842ff34c3SPhil Shafer 	    content = va_arg(xop->xo_vap, char *);
599942ff34c3SPhil Shafer 	    clen = content ? strlen(content) : 0;
600042ff34c3SPhil Shafer 	}
600142ff34c3SPhil Shafer 
6002d1a0d267SMarcel Moolenaar 	if (ftype == XO_ROLE_NEWLINE) {
6003d1a0d267SMarcel Moolenaar 	    xo_line_close(xop);
6004d1a0d267SMarcel Moolenaar 	    if (flush_line && xo_flush_h(xop) < 0)
6005d1a0d267SMarcel Moolenaar 		return -1;
6006d1a0d267SMarcel Moolenaar 	    goto bottom;
6007d1a0d267SMarcel Moolenaar 
6008d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_EBRACE) {
6009d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6010d1a0d267SMarcel Moolenaar 	    goto bottom;
6011d1a0d267SMarcel Moolenaar 
6012d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_TEXT) {
6013d1a0d267SMarcel Moolenaar 	    /* Normal text */
6014d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6015d1a0d267SMarcel Moolenaar 	    goto bottom;
6016d1a0d267SMarcel Moolenaar 	}
6017d1a0d267SMarcel Moolenaar 
6018d1a0d267SMarcel Moolenaar 	/*
6019d1a0d267SMarcel Moolenaar 	 * Notes and units need the 'w' flag handled before the content.
6020d1a0d267SMarcel Moolenaar 	 */
6021d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
6022d1a0d267SMarcel Moolenaar 	    if (flags & XFF_WS) {
6023d1a0d267SMarcel Moolenaar 		xo_format_content(xop, "padding", NULL, " ", 1,
6024d1a0d267SMarcel Moolenaar 				  NULL, 0, flags);
6025d1a0d267SMarcel Moolenaar 		flags &= ~XFF_WS; /* Block later handling of this */
6026d1a0d267SMarcel Moolenaar 	    }
6027d1a0d267SMarcel Moolenaar 	}
6028d1a0d267SMarcel Moolenaar 
6029d1a0d267SMarcel Moolenaar 	if (ftype == 'V')
603042ff34c3SPhil Shafer 	    xo_format_value(xop, content, clen,
6031d1a0d267SMarcel Moolenaar 			    xfip->xfi_format, xfip->xfi_flen,
6032d1a0d267SMarcel Moolenaar 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
6033d1a0d267SMarcel Moolenaar 	else if (ftype == '[')
603442ff34c3SPhil Shafer 	    xo_anchor_start(xop, xfip, content, clen);
6035545ddfbeSMarcel Moolenaar 	else if (ftype == ']')
603642ff34c3SPhil Shafer 	    xo_anchor_stop(xop, xfip, content, clen);
6037788ca347SMarcel Moolenaar 	else if (ftype == 'C')
603842ff34c3SPhil Shafer 	    xo_format_colors(xop, xfip, content, clen);
6039545ddfbeSMarcel Moolenaar 
6040d1a0d267SMarcel Moolenaar 	else if (ftype == 'G') {
6041d1a0d267SMarcel Moolenaar 	    /*
6042d1a0d267SMarcel Moolenaar 	     * A {G:domain} field; disect the domain name and translate
6043d1a0d267SMarcel Moolenaar 	     * the remaining portion of the input string.  If the user
6044d1a0d267SMarcel Moolenaar 	     * didn't put the {G:} at the start of the format string, then
6045d1a0d267SMarcel Moolenaar 	     * assumably they just want us to translate the rest of it.
6046d1a0d267SMarcel Moolenaar 	     * Since gettext returns strings in a static buffer, we make
6047d1a0d267SMarcel Moolenaar 	     * a copy in new_fmt.
6048d1a0d267SMarcel Moolenaar 	     */
604942ff34c3SPhil Shafer 	    xo_set_gettext_domain(xop, xfip, content, clen);
6050d1a0d267SMarcel Moolenaar 
6051d1a0d267SMarcel Moolenaar 	    if (!gettext_inuse) { /* Only translate once */
6052d1a0d267SMarcel Moolenaar 		gettext_inuse = 1;
6053d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6054d1a0d267SMarcel Moolenaar 		    xo_free(new_fmt);
6055d1a0d267SMarcel Moolenaar 		    new_fmt = NULL;
6056545ddfbeSMarcel Moolenaar 		}
6057545ddfbeSMarcel Moolenaar 
6058d1a0d267SMarcel Moolenaar 		xo_gettext_build_format(xop, fields, field,
6059d1a0d267SMarcel Moolenaar 					xfip->xfi_next, &new_fmt);
6060d1a0d267SMarcel Moolenaar 		if (new_fmt) {
6061d1a0d267SMarcel Moolenaar 		    gettext_changed = 1;
6062d1a0d267SMarcel Moolenaar 
6063d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6064d1a0d267SMarcel Moolenaar 
6065d1a0d267SMarcel Moolenaar 		    if (++new_max_fields < max_fields)
6066d1a0d267SMarcel Moolenaar 			new_max_fields = max_fields;
6067d1a0d267SMarcel Moolenaar 
6068d1a0d267SMarcel Moolenaar 		    /* Leave a blank slot at the beginning */
60698a6eceffSPhil Shafer 		    ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6070d1a0d267SMarcel Moolenaar 		    new_fields = alloca(sz);
6071d1a0d267SMarcel Moolenaar 		    bzero(new_fields, sz);
6072d1a0d267SMarcel Moolenaar 
6073d1a0d267SMarcel Moolenaar 		    if (!xo_parse_fields(xop, new_fields + 1,
6074d1a0d267SMarcel Moolenaar 					 new_max_fields, new_fmt)) {
6075d1a0d267SMarcel Moolenaar 			gettext_reordered = 0;
6076d1a0d267SMarcel Moolenaar 
6077d1a0d267SMarcel Moolenaar 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6078d1a0d267SMarcel Moolenaar 					fields, new_fields + 1,
6079d1a0d267SMarcel Moolenaar 					new_max_fields, &gettext_reordered)) {
6080d1a0d267SMarcel Moolenaar 
6081d1a0d267SMarcel Moolenaar 			    if (gettext_reordered) {
6082d1a0d267SMarcel Moolenaar 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6083d1a0d267SMarcel Moolenaar 				    xo_failure(xop, "gettext finds reordered "
6084d1a0d267SMarcel Moolenaar 					       "fields in '%s' and '%s'",
6085d1a0d267SMarcel Moolenaar 					       xo_printable(fmt),
6086d1a0d267SMarcel Moolenaar 					       xo_printable(new_fmt));
6087d1a0d267SMarcel Moolenaar 				flush_line = 0; /* Must keep at content */
6088d1a0d267SMarcel Moolenaar 				XOIF_SET(xop, XOIF_REORDER);
6089d1a0d267SMarcel Moolenaar 			    }
6090d1a0d267SMarcel Moolenaar 
6091d1a0d267SMarcel Moolenaar 			    field = -1; /* Will be incremented at top of loop */
6092d1a0d267SMarcel Moolenaar 			    xfip = new_fields;
6093d1a0d267SMarcel Moolenaar 			    max_fields = new_max_fields;
6094d1a0d267SMarcel Moolenaar 			}
6095d1a0d267SMarcel Moolenaar 		    }
6096d1a0d267SMarcel Moolenaar 		}
6097d1a0d267SMarcel Moolenaar 	    }
6098d1a0d267SMarcel Moolenaar 	    continue;
6099d1a0d267SMarcel Moolenaar 
610042ff34c3SPhil Shafer 	} else  if (clen || xfip->xfi_format) {
6101d1a0d267SMarcel Moolenaar 
6102d1a0d267SMarcel Moolenaar 	    const char *class_name = xo_class_name(ftype);
6103d1a0d267SMarcel Moolenaar 	    if (class_name)
6104d1a0d267SMarcel Moolenaar 		xo_format_content(xop, class_name, xo_tag_name(ftype),
610542ff34c3SPhil Shafer 				  content, clen,
6106d1a0d267SMarcel Moolenaar 				  xfip->xfi_format, xfip->xfi_flen, flags);
610731337658SMarcel Moolenaar 	    else if (ftype == 'T')
610842ff34c3SPhil Shafer 		xo_format_title(xop, xfip, content, clen);
6109d1a0d267SMarcel Moolenaar 	    else if (ftype == 'U')
611042ff34c3SPhil Shafer 		xo_format_units(xop, xfip, content, clen);
6111d1a0d267SMarcel Moolenaar 	    else
6112d1a0d267SMarcel Moolenaar 		xo_failure(xop, "unknown field type: '%c'", ftype);
6113545ddfbeSMarcel Moolenaar 	}
611431337658SMarcel Moolenaar 
611531337658SMarcel Moolenaar 	if (flags & XFF_COLON)
6116d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
611731337658SMarcel Moolenaar 
6118d1a0d267SMarcel Moolenaar 	if (flags & XFF_WS)
6119d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6120d1a0d267SMarcel Moolenaar 
6121d1a0d267SMarcel Moolenaar     bottom:
6122d1a0d267SMarcel Moolenaar 	/* Record the end-of-field offset */
6123d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
6124d1a0d267SMarcel Moolenaar 	    fend[field] = xo_buf_offset(&xop->xo_data);
6125d1a0d267SMarcel Moolenaar 	    max_fend = field;
612631337658SMarcel Moolenaar 	}
612731337658SMarcel Moolenaar     }
612831337658SMarcel Moolenaar 
6129d1a0d267SMarcel Moolenaar     if (gettext_changed && gettext_reordered) {
6130d1a0d267SMarcel Moolenaar 	/* Final step: rebuild the content using the rendered fields */
6131d1a0d267SMarcel Moolenaar 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6132d1a0d267SMarcel Moolenaar 				   fend, max_fend);
6133d1a0d267SMarcel Moolenaar     }
6134d1a0d267SMarcel Moolenaar 
6135d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_REORDER);
6136d1a0d267SMarcel Moolenaar 
6137ee5cf116SPhil Shafer     /*
6138ee5cf116SPhil Shafer      * If we've got enough data, flush it.
6139ee5cf116SPhil Shafer      */
6140ee5cf116SPhil Shafer     if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER)
6141ee5cf116SPhil Shafer 	flush = 1;
6142ee5cf116SPhil Shafer 
614331337658SMarcel Moolenaar     /* If we don't have an anchor, write the text out */
6144d1a0d267SMarcel Moolenaar     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6145545ddfbeSMarcel Moolenaar 	if (xo_write(xop) < 0)
6146545ddfbeSMarcel Moolenaar 	    rc = -1;		/* Report failure */
614742ff34c3SPhil Shafer 	else if (xo_flush_h(xop) < 0)
6148545ddfbeSMarcel Moolenaar 	    rc = -1;
6149545ddfbeSMarcel Moolenaar     }
615031337658SMarcel Moolenaar 
6151d1a0d267SMarcel Moolenaar     if (new_fmt)
6152d1a0d267SMarcel Moolenaar 	xo_free(new_fmt);
6153d1a0d267SMarcel Moolenaar 
6154d1a0d267SMarcel Moolenaar     /*
6155d1a0d267SMarcel Moolenaar      * We've carried the gettext domainname inside our handle just for
6156d1a0d267SMarcel Moolenaar      * convenience, but we need to ensure it doesn't survive across
6157d1a0d267SMarcel Moolenaar      * xo_emit calls.
6158d1a0d267SMarcel Moolenaar      */
6159d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
6160d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
6161d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
6162d1a0d267SMarcel Moolenaar     }
6163d1a0d267SMarcel Moolenaar 
61648a6eceffSPhil Shafer     return (rc < 0) ? rc : xop->xo_columns;
616531337658SMarcel Moolenaar }
616631337658SMarcel Moolenaar 
6167d1a0d267SMarcel Moolenaar /*
616842ff34c3SPhil Shafer  * Parse and emit a set of fields
616942ff34c3SPhil Shafer  */
617042ff34c3SPhil Shafer static int
617142ff34c3SPhil Shafer xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
617242ff34c3SPhil Shafer {
617342ff34c3SPhil Shafer     xop->xo_columns = 0;	/* Always reset it */
617442ff34c3SPhil Shafer     xop->xo_errno = errno;	/* Save for "%m" */
617542ff34c3SPhil Shafer 
617642ff34c3SPhil Shafer     if (fmt == NULL)
617742ff34c3SPhil Shafer 	return 0;
617842ff34c3SPhil Shafer 
617942ff34c3SPhil Shafer     unsigned max_fields;
618042ff34c3SPhil Shafer     xo_field_info_t *fields = NULL;
618142ff34c3SPhil Shafer 
618242ff34c3SPhil Shafer     /* Adjust XOEF_RETAIN based on global flags */
618342ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_ALL))
618442ff34c3SPhil Shafer 	flags |= XOEF_RETAIN;
618542ff34c3SPhil Shafer     if (XOF_ISSET(xop, XOF_RETAIN_NONE))
618642ff34c3SPhil Shafer 	flags &= ~XOEF_RETAIN;
618742ff34c3SPhil Shafer 
618842ff34c3SPhil Shafer     /*
618942ff34c3SPhil Shafer      * Check for 'retain' flag, telling us to retain the field
619042ff34c3SPhil Shafer      * information.  If we've already saved it, then we can avoid
619142ff34c3SPhil Shafer      * re-parsing the format string.
619242ff34c3SPhil Shafer      */
619342ff34c3SPhil Shafer     if (!(flags & XOEF_RETAIN)
619442ff34c3SPhil Shafer 	|| xo_retain_find(fmt, &fields, &max_fields) != 0
619542ff34c3SPhil Shafer 	|| fields == NULL) {
619642ff34c3SPhil Shafer 
619742ff34c3SPhil Shafer 	/* Nothing retained; parse the format string */
619842ff34c3SPhil Shafer 	max_fields = xo_count_fields(xop, fmt);
619942ff34c3SPhil Shafer 	fields = alloca(max_fields * sizeof(fields[0]));
620042ff34c3SPhil Shafer 	bzero(fields, max_fields * sizeof(fields[0]));
620142ff34c3SPhil Shafer 
620242ff34c3SPhil Shafer 	if (xo_parse_fields(xop, fields, max_fields, fmt))
620342ff34c3SPhil Shafer 	    return -1;		/* Warning already displayed */
620442ff34c3SPhil Shafer 
620542ff34c3SPhil Shafer 	if (flags & XOEF_RETAIN) {
620642ff34c3SPhil Shafer 	    /* Retain the info */
620742ff34c3SPhil Shafer 	    xo_retain_add(fmt, fields, max_fields);
620842ff34c3SPhil Shafer 	}
620942ff34c3SPhil Shafer     }
621042ff34c3SPhil Shafer 
621142ff34c3SPhil Shafer     return xo_do_emit_fields(xop, fields, max_fields, fmt);
621242ff34c3SPhil Shafer }
621342ff34c3SPhil Shafer 
621442ff34c3SPhil Shafer /*
6215d1a0d267SMarcel Moolenaar  * Rebuild a format string in a gettext-friendly format.  This function
6216d1a0d267SMarcel Moolenaar  * is exposed to tools can perform this function.  See xo(1).
6217d1a0d267SMarcel Moolenaar  */
6218d1a0d267SMarcel Moolenaar char *
6219d1a0d267SMarcel Moolenaar xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6220d1a0d267SMarcel Moolenaar 		    xo_simplify_field_func_t field_cb)
6221d1a0d267SMarcel Moolenaar {
6222d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
6223d1a0d267SMarcel Moolenaar 
6224d1a0d267SMarcel Moolenaar     xop->xo_columns = 0;	/* Always reset it */
6225d1a0d267SMarcel Moolenaar     xop->xo_errno = errno;	/* Save for "%m" */
6226d1a0d267SMarcel Moolenaar 
6227d1a0d267SMarcel Moolenaar     unsigned max_fields = xo_count_fields(xop, fmt);
6228d1a0d267SMarcel Moolenaar     xo_field_info_t fields[max_fields];
6229d1a0d267SMarcel Moolenaar 
6230d1a0d267SMarcel Moolenaar     bzero(fields, max_fields * sizeof(fields[0]));
6231d1a0d267SMarcel Moolenaar 
6232d1a0d267SMarcel Moolenaar     if (xo_parse_fields(xop, fields, max_fields, fmt))
6233d1a0d267SMarcel Moolenaar 	return NULL;		/* Warning already displayed */
6234d1a0d267SMarcel Moolenaar 
6235d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
6236d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
6237d1a0d267SMarcel Moolenaar 
6238d1a0d267SMarcel Moolenaar     if (with_numbers)
6239d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
6240d1a0d267SMarcel Moolenaar 
6241d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6242d1a0d267SMarcel Moolenaar 	return NULL;
6243d1a0d267SMarcel Moolenaar 
6244d1a0d267SMarcel Moolenaar     return xb.xb_bufp;
6245d1a0d267SMarcel Moolenaar }
6246d1a0d267SMarcel Moolenaar 
62478a6eceffSPhil Shafer xo_ssize_t
624831337658SMarcel Moolenaar xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
624931337658SMarcel Moolenaar {
62508a6eceffSPhil Shafer     ssize_t rc;
625131337658SMarcel Moolenaar 
625231337658SMarcel Moolenaar     xop = xo_default(xop);
625331337658SMarcel Moolenaar     va_copy(xop->xo_vap, vap);
625442ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
625531337658SMarcel Moolenaar     va_end(xop->xo_vap);
625631337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
625731337658SMarcel Moolenaar 
625831337658SMarcel Moolenaar     return rc;
625931337658SMarcel Moolenaar }
626031337658SMarcel Moolenaar 
62618a6eceffSPhil Shafer xo_ssize_t
626231337658SMarcel Moolenaar xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
626331337658SMarcel Moolenaar {
62648a6eceffSPhil Shafer     ssize_t rc;
626531337658SMarcel Moolenaar 
626631337658SMarcel Moolenaar     xop = xo_default(xop);
626731337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
626842ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
626931337658SMarcel Moolenaar     va_end(xop->xo_vap);
627031337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
627131337658SMarcel Moolenaar 
627231337658SMarcel Moolenaar     return rc;
627331337658SMarcel Moolenaar }
627431337658SMarcel Moolenaar 
62758a6eceffSPhil Shafer xo_ssize_t
627631337658SMarcel Moolenaar xo_emit (const char *fmt, ...)
627731337658SMarcel Moolenaar {
627831337658SMarcel Moolenaar     xo_handle_t *xop = xo_default(NULL);
62798a6eceffSPhil Shafer     ssize_t rc;
628031337658SMarcel Moolenaar 
628131337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
628242ff34c3SPhil Shafer     rc = xo_do_emit(xop, 0, fmt);
628331337658SMarcel Moolenaar     va_end(xop->xo_vap);
628431337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
628531337658SMarcel Moolenaar 
628631337658SMarcel Moolenaar     return rc;
628731337658SMarcel Moolenaar }
628831337658SMarcel Moolenaar 
62898a6eceffSPhil Shafer xo_ssize_t
629042ff34c3SPhil Shafer xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
629142ff34c3SPhil Shafer 	     const char *fmt, va_list vap)
629242ff34c3SPhil Shafer {
62938a6eceffSPhil Shafer     ssize_t rc;
629442ff34c3SPhil Shafer 
629542ff34c3SPhil Shafer     xop = xo_default(xop);
629642ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
629742ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
629842ff34c3SPhil Shafer     va_end(xop->xo_vap);
629942ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
630042ff34c3SPhil Shafer 
630142ff34c3SPhil Shafer     return rc;
630242ff34c3SPhil Shafer }
630342ff34c3SPhil Shafer 
63048a6eceffSPhil Shafer xo_ssize_t
630542ff34c3SPhil Shafer xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
630642ff34c3SPhil Shafer {
63078a6eceffSPhil Shafer     ssize_t rc;
630842ff34c3SPhil Shafer 
630942ff34c3SPhil Shafer     xop = xo_default(xop);
631042ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
631142ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
631242ff34c3SPhil Shafer     va_end(xop->xo_vap);
631342ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
631442ff34c3SPhil Shafer 
631542ff34c3SPhil Shafer     return rc;
631642ff34c3SPhil Shafer }
631742ff34c3SPhil Shafer 
63188a6eceffSPhil Shafer xo_ssize_t
631942ff34c3SPhil Shafer xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
632042ff34c3SPhil Shafer {
632142ff34c3SPhil Shafer     xo_handle_t *xop = xo_default(NULL);
63228a6eceffSPhil Shafer     ssize_t rc;
632342ff34c3SPhil Shafer 
632442ff34c3SPhil Shafer     va_start(xop->xo_vap, fmt);
632542ff34c3SPhil Shafer     rc = xo_do_emit(xop, flags, fmt);
632642ff34c3SPhil Shafer     va_end(xop->xo_vap);
632742ff34c3SPhil Shafer     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
632842ff34c3SPhil Shafer 
632942ff34c3SPhil Shafer     return rc;
633042ff34c3SPhil Shafer }
633142ff34c3SPhil Shafer 
633242ff34c3SPhil Shafer /*
633342ff34c3SPhil Shafer  * Emit a single field by providing the info information typically provided
633442ff34c3SPhil Shafer  * inside the field description (role, modifiers, and formats).  This is
633542ff34c3SPhil Shafer  * a convenience function to avoid callers using snprintf to build field
633642ff34c3SPhil Shafer  * descriptions.
633742ff34c3SPhil Shafer  */
63388a6eceffSPhil Shafer xo_ssize_t
633942ff34c3SPhil Shafer xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
634042ff34c3SPhil Shafer 		  const char *fmt, const char *efmt,
634142ff34c3SPhil Shafer 		  va_list vap)
634242ff34c3SPhil Shafer {
63438a6eceffSPhil Shafer     ssize_t rc;
634442ff34c3SPhil Shafer 
634542ff34c3SPhil Shafer     xop = xo_default(xop);
634642ff34c3SPhil Shafer 
634742ff34c3SPhil Shafer     if (rolmod == NULL)
634842ff34c3SPhil Shafer 	rolmod = "V";
634942ff34c3SPhil Shafer 
635042ff34c3SPhil Shafer     xo_field_info_t xfi;
635142ff34c3SPhil Shafer 
635242ff34c3SPhil Shafer     bzero(&xfi, sizeof(xfi));
635342ff34c3SPhil Shafer 
635442ff34c3SPhil Shafer     const char *cp;
635542ff34c3SPhil Shafer     cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
635642ff34c3SPhil Shafer     if (cp == NULL)
635742ff34c3SPhil Shafer 	return -1;
635842ff34c3SPhil Shafer 
635942ff34c3SPhil Shafer     xfi.xfi_start = fmt;
636042ff34c3SPhil Shafer     xfi.xfi_content = contents;
636142ff34c3SPhil Shafer     xfi.xfi_format = fmt;
636242ff34c3SPhil Shafer     xfi.xfi_encoding = efmt;
636342ff34c3SPhil Shafer     xfi.xfi_clen = contents ? strlen(contents) : 0;
636442ff34c3SPhil Shafer     xfi.xfi_flen = fmt ? strlen(fmt) : 0;
636542ff34c3SPhil Shafer     xfi.xfi_elen = efmt ? strlen(efmt) : 0;
636642ff34c3SPhil Shafer 
636742ff34c3SPhil Shafer     /* If we have content, then we have a default format */
636842ff34c3SPhil Shafer     if (contents && fmt == NULL
636942ff34c3SPhil Shafer 		&& xo_role_wants_default_format(xfi.xfi_ftype)) {
637042ff34c3SPhil Shafer 	xfi.xfi_format = xo_default_format;
637142ff34c3SPhil Shafer 	xfi.xfi_flen = 2;
637242ff34c3SPhil Shafer     }
637342ff34c3SPhil Shafer 
637442ff34c3SPhil Shafer     va_copy(xop->xo_vap, vap);
637542ff34c3SPhil Shafer 
637642ff34c3SPhil Shafer     rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
637742ff34c3SPhil Shafer 
637842ff34c3SPhil Shafer     va_end(xop->xo_vap);
637942ff34c3SPhil Shafer 
638042ff34c3SPhil Shafer     return rc;
638142ff34c3SPhil Shafer }
638242ff34c3SPhil Shafer 
63838a6eceffSPhil Shafer xo_ssize_t
638442ff34c3SPhil Shafer xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
638542ff34c3SPhil Shafer 		 const char *fmt, const char *efmt, ...)
638642ff34c3SPhil Shafer {
63878a6eceffSPhil Shafer     ssize_t rc;
638842ff34c3SPhil Shafer     va_list vap;
638942ff34c3SPhil Shafer 
639042ff34c3SPhil Shafer     va_start(vap, efmt);
639142ff34c3SPhil Shafer     rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
639242ff34c3SPhil Shafer     va_end(vap);
639342ff34c3SPhil Shafer 
639442ff34c3SPhil Shafer     return rc;
639542ff34c3SPhil Shafer }
639642ff34c3SPhil Shafer 
63978a6eceffSPhil Shafer xo_ssize_t
639842ff34c3SPhil Shafer xo_emit_field (const char *rolmod, const char *contents,
639942ff34c3SPhil Shafer 	       const char *fmt, const char *efmt, ...)
640042ff34c3SPhil Shafer {
64018a6eceffSPhil Shafer     ssize_t rc;
640242ff34c3SPhil Shafer     va_list vap;
640342ff34c3SPhil Shafer 
640442ff34c3SPhil Shafer     va_start(vap, efmt);
640542ff34c3SPhil Shafer     rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
640642ff34c3SPhil Shafer     va_end(vap);
640742ff34c3SPhil Shafer 
640842ff34c3SPhil Shafer     return rc;
640942ff34c3SPhil Shafer }
641042ff34c3SPhil Shafer 
64118a6eceffSPhil Shafer xo_ssize_t
641231337658SMarcel Moolenaar xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
641331337658SMarcel Moolenaar {
64148a6eceffSPhil Shafer     const ssize_t extra = 5; 	/* space, equals, quote, quote, and nul */
641531337658SMarcel Moolenaar     xop = xo_default(xop);
641631337658SMarcel Moolenaar 
64178a6eceffSPhil Shafer     ssize_t rc = 0;
64188a6eceffSPhil Shafer     ssize_t nlen = strlen(name);
641931337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_attrs;
64208a6eceffSPhil Shafer     ssize_t name_offset, value_offset;
642131337658SMarcel Moolenaar 
6422d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6423d1a0d267SMarcel Moolenaar     case XO_STYLE_XML:
642431337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, nlen + extra))
642531337658SMarcel Moolenaar 	    return -1;
642631337658SMarcel Moolenaar 
642731337658SMarcel Moolenaar 	*xbp->xb_curp++ = ' ';
642831337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, name, nlen);
642931337658SMarcel Moolenaar 	xbp->xb_curp += nlen;
643031337658SMarcel Moolenaar 	*xbp->xb_curp++ = '=';
643131337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
643231337658SMarcel Moolenaar 
6433d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
643431337658SMarcel Moolenaar 
6435d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
643631337658SMarcel Moolenaar 	    rc = xo_escape_xml(xbp, rc, 1);
643731337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
643831337658SMarcel Moolenaar 	}
643931337658SMarcel Moolenaar 
644031337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, 2))
644131337658SMarcel Moolenaar 	    return -1;
644231337658SMarcel Moolenaar 
644331337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
644431337658SMarcel Moolenaar 	*xbp->xb_curp = '\0';
644531337658SMarcel Moolenaar 
6446d1a0d267SMarcel Moolenaar 	rc += nlen + extra;
6447d1a0d267SMarcel Moolenaar 	break;
6448d1a0d267SMarcel Moolenaar 
6449d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6450d1a0d267SMarcel Moolenaar 	name_offset = xo_buf_offset(xbp);
6451d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, name, nlen);
6452d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "", 1);
6453d1a0d267SMarcel Moolenaar 
6454d1a0d267SMarcel Moolenaar 	value_offset = xo_buf_offset(xbp);
6455d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6456d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
6457d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += rc;
6458d1a0d267SMarcel Moolenaar 	    *xbp->xb_curp = '\0';
6459d1a0d267SMarcel Moolenaar 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6460d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, name_offset),
6461d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, value_offset));
6462d1a0d267SMarcel Moolenaar 	}
6463d1a0d267SMarcel Moolenaar     }
6464d1a0d267SMarcel Moolenaar 
6465d1a0d267SMarcel Moolenaar     return rc;
646631337658SMarcel Moolenaar }
646731337658SMarcel Moolenaar 
64688a6eceffSPhil Shafer xo_ssize_t
646931337658SMarcel Moolenaar xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
647031337658SMarcel Moolenaar {
64718a6eceffSPhil Shafer     ssize_t rc;
647231337658SMarcel Moolenaar     va_list vap;
647331337658SMarcel Moolenaar 
647431337658SMarcel Moolenaar     va_start(vap, fmt);
647531337658SMarcel Moolenaar     rc = xo_attr_hv(xop, name, fmt, vap);
647631337658SMarcel Moolenaar     va_end(vap);
647731337658SMarcel Moolenaar 
647831337658SMarcel Moolenaar     return rc;
647931337658SMarcel Moolenaar }
648031337658SMarcel Moolenaar 
64818a6eceffSPhil Shafer xo_ssize_t
648231337658SMarcel Moolenaar xo_attr (const char *name, const char *fmt, ...)
648331337658SMarcel Moolenaar {
64848a6eceffSPhil Shafer     ssize_t rc;
648531337658SMarcel Moolenaar     va_list vap;
648631337658SMarcel Moolenaar 
648731337658SMarcel Moolenaar     va_start(vap, fmt);
648831337658SMarcel Moolenaar     rc = xo_attr_hv(NULL, name, fmt, vap);
648931337658SMarcel Moolenaar     va_end(vap);
649031337658SMarcel Moolenaar 
649131337658SMarcel Moolenaar     return rc;
649231337658SMarcel Moolenaar }
649331337658SMarcel Moolenaar 
649431337658SMarcel Moolenaar static void
649531337658SMarcel Moolenaar xo_stack_set_flags (xo_handle_t *xop)
649631337658SMarcel Moolenaar {
6497d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
649831337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
649931337658SMarcel Moolenaar 
650031337658SMarcel Moolenaar 	xsp->xs_flags |= XSF_NOT_FIRST;
6501d1a0d267SMarcel Moolenaar 	XOF_CLEAR(xop, XOF_NOT_FIRST);
650231337658SMarcel Moolenaar     }
650331337658SMarcel Moolenaar }
650431337658SMarcel Moolenaar 
650531337658SMarcel Moolenaar static void
650631337658SMarcel Moolenaar xo_depth_change (xo_handle_t *xop, const char *name,
6507545ddfbeSMarcel Moolenaar 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
650831337658SMarcel Moolenaar {
6509788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6510545ddfbeSMarcel Moolenaar 	indent = 0;
6511545ddfbeSMarcel Moolenaar 
6512d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_DTRT))
651331337658SMarcel Moolenaar 	flags |= XSF_DTRT;
651431337658SMarcel Moolenaar 
651531337658SMarcel Moolenaar     if (delta >= 0) {			/* Push operation */
651631337658SMarcel Moolenaar 	if (xo_depth_check(xop, xop->xo_depth + delta))
651731337658SMarcel Moolenaar 	    return;
651831337658SMarcel Moolenaar 
651931337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
652031337658SMarcel Moolenaar 	xsp->xs_flags = flags;
6521545ddfbeSMarcel Moolenaar 	xsp->xs_state = state;
652231337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
652331337658SMarcel Moolenaar 
6524545ddfbeSMarcel Moolenaar 	if (name == NULL)
6525545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
652631337658SMarcel Moolenaar 
6527d1a0d267SMarcel Moolenaar 	xsp->xs_name = xo_strndup(name, -1);
652831337658SMarcel Moolenaar 
652931337658SMarcel Moolenaar     } else {			/* Pop operation */
653031337658SMarcel Moolenaar 	if (xop->xo_depth == 0) {
6531d1a0d267SMarcel Moolenaar 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
653231337658SMarcel Moolenaar 		xo_failure(xop, "close with empty stack: '%s'", name);
653331337658SMarcel Moolenaar 	    return;
653431337658SMarcel Moolenaar 	}
653531337658SMarcel Moolenaar 
653631337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6537d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN)) {
653831337658SMarcel Moolenaar 	    const char *top = xsp->xs_name;
653931337658SMarcel Moolenaar 	    if (top && strcmp(name, top) != 0) {
654031337658SMarcel Moolenaar 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
654131337658SMarcel Moolenaar 			      name, top);
654231337658SMarcel Moolenaar 		return;
654331337658SMarcel Moolenaar 	    }
654431337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
654531337658SMarcel Moolenaar 		xo_failure(xop, "list close on list confict: '%s'",
654631337658SMarcel Moolenaar 			      name);
654731337658SMarcel Moolenaar 		return;
654831337658SMarcel Moolenaar 	    }
654931337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
655031337658SMarcel Moolenaar 		xo_failure(xop, "list close on instance confict: '%s'",
655131337658SMarcel Moolenaar 			      name);
655231337658SMarcel Moolenaar 		return;
655331337658SMarcel Moolenaar 	    }
655431337658SMarcel Moolenaar 	}
655531337658SMarcel Moolenaar 
655631337658SMarcel Moolenaar 	if (xsp->xs_name) {
655731337658SMarcel Moolenaar 	    xo_free(xsp->xs_name);
655831337658SMarcel Moolenaar 	    xsp->xs_name = NULL;
655931337658SMarcel Moolenaar 	}
656031337658SMarcel Moolenaar 	if (xsp->xs_keys) {
656131337658SMarcel Moolenaar 	    xo_free(xsp->xs_keys);
656231337658SMarcel Moolenaar 	    xsp->xs_keys = NULL;
656331337658SMarcel Moolenaar 	}
656431337658SMarcel Moolenaar     }
656531337658SMarcel Moolenaar 
656631337658SMarcel Moolenaar     xop->xo_depth += delta;	/* Record new depth */
656731337658SMarcel Moolenaar     xop->xo_indent += indent;
656831337658SMarcel Moolenaar }
656931337658SMarcel Moolenaar 
657031337658SMarcel Moolenaar void
657131337658SMarcel Moolenaar xo_set_depth (xo_handle_t *xop, int depth)
657231337658SMarcel Moolenaar {
657331337658SMarcel Moolenaar     xop = xo_default(xop);
657431337658SMarcel Moolenaar 
657531337658SMarcel Moolenaar     if (xo_depth_check(xop, depth))
657631337658SMarcel Moolenaar 	return;
657731337658SMarcel Moolenaar 
657831337658SMarcel Moolenaar     xop->xo_depth += depth;
657931337658SMarcel Moolenaar     xop->xo_indent += depth;
658031337658SMarcel Moolenaar }
658131337658SMarcel Moolenaar 
658231337658SMarcel Moolenaar static xo_xsf_flags_t
65838a6eceffSPhil Shafer xo_stack_flags (xo_xof_flags_t xflags)
658431337658SMarcel Moolenaar {
658531337658SMarcel Moolenaar     if (xflags & XOF_DTRT)
658631337658SMarcel Moolenaar 	return XSF_DTRT;
658731337658SMarcel Moolenaar     return 0;
658831337658SMarcel Moolenaar }
658931337658SMarcel Moolenaar 
6590788ca347SMarcel Moolenaar static void
6591788ca347SMarcel Moolenaar xo_emit_top (xo_handle_t *xop, const char *ppn)
6592788ca347SMarcel Moolenaar {
6593788ca347SMarcel Moolenaar     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6594d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_TOP_EMITTED);
6595788ca347SMarcel Moolenaar 
6596788ca347SMarcel Moolenaar     if (xop->xo_version) {
6597788ca347SMarcel Moolenaar 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6598788ca347SMarcel Moolenaar 		  xo_indent(xop), "", xop->xo_version, ppn);
6599788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
6600788ca347SMarcel Moolenaar 	xop->xo_version = NULL;
6601788ca347SMarcel Moolenaar     }
6602788ca347SMarcel Moolenaar }
6603788ca347SMarcel Moolenaar 
66048a6eceffSPhil Shafer static ssize_t
6605545ddfbeSMarcel Moolenaar xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
660631337658SMarcel Moolenaar {
66078a6eceffSPhil Shafer     ssize_t rc = 0;
6608d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
660931337658SMarcel Moolenaar     const char *pre_nl = "";
661031337658SMarcel Moolenaar 
661131337658SMarcel Moolenaar     if (name == NULL) {
661231337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for container name");
661331337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
661431337658SMarcel Moolenaar     }
661531337658SMarcel Moolenaar 
661631337658SMarcel Moolenaar     flags |= xop->xo_flags;	/* Pick up handle flags */
661731337658SMarcel Moolenaar 
6618788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
661931337658SMarcel Moolenaar     case XO_STYLE_XML:
6620545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
6621545ddfbeSMarcel Moolenaar 
6622545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6623545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6624545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6625545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6626545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6627545ddfbeSMarcel Moolenaar 	}
6628545ddfbeSMarcel Moolenaar 
6629545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
663031337658SMarcel Moolenaar 	break;
663131337658SMarcel Moolenaar 
663231337658SMarcel Moolenaar     case XO_STYLE_JSON:
663331337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
663431337658SMarcel Moolenaar 
6635d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6636d1a0d267SMarcel Moolenaar 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6637788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
663831337658SMarcel Moolenaar 
663931337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6640d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
664131337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
664231337658SMarcel Moolenaar 
664331337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
664431337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
664531337658SMarcel Moolenaar 	break;
6646d1a0d267SMarcel Moolenaar 
6647d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6648d1a0d267SMarcel Moolenaar 	break;
6649d1a0d267SMarcel Moolenaar 
6650d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6651d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL);
6652d1a0d267SMarcel Moolenaar 	break;
665331337658SMarcel Moolenaar     }
665431337658SMarcel Moolenaar 
6655545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
6656545ddfbeSMarcel Moolenaar 		    xo_stack_flags(flags));
6657545ddfbeSMarcel Moolenaar 
665831337658SMarcel Moolenaar     return rc;
665931337658SMarcel Moolenaar }
666031337658SMarcel Moolenaar 
6661545ddfbeSMarcel Moolenaar static int
6662545ddfbeSMarcel Moolenaar xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6663545ddfbeSMarcel Moolenaar {
6664545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
6665545ddfbeSMarcel Moolenaar }
6666545ddfbeSMarcel Moolenaar 
66678a6eceffSPhil Shafer xo_ssize_t
666831337658SMarcel Moolenaar xo_open_container_h (xo_handle_t *xop, const char *name)
666931337658SMarcel Moolenaar {
667031337658SMarcel Moolenaar     return xo_open_container_hf(xop, 0, name);
667131337658SMarcel Moolenaar }
667231337658SMarcel Moolenaar 
66738a6eceffSPhil Shafer xo_ssize_t
667431337658SMarcel Moolenaar xo_open_container (const char *name)
667531337658SMarcel Moolenaar {
667631337658SMarcel Moolenaar     return xo_open_container_hf(NULL, 0, name);
667731337658SMarcel Moolenaar }
667831337658SMarcel Moolenaar 
66798a6eceffSPhil Shafer xo_ssize_t
668031337658SMarcel Moolenaar xo_open_container_hd (xo_handle_t *xop, const char *name)
668131337658SMarcel Moolenaar {
668231337658SMarcel Moolenaar     return xo_open_container_hf(xop, XOF_DTRT, name);
668331337658SMarcel Moolenaar }
668431337658SMarcel Moolenaar 
66858a6eceffSPhil Shafer xo_ssize_t
668631337658SMarcel Moolenaar xo_open_container_d (const char *name)
668731337658SMarcel Moolenaar {
668831337658SMarcel Moolenaar     return xo_open_container_hf(NULL, XOF_DTRT, name);
668931337658SMarcel Moolenaar }
669031337658SMarcel Moolenaar 
6691545ddfbeSMarcel Moolenaar static int
6692545ddfbeSMarcel Moolenaar xo_do_close_container (xo_handle_t *xop, const char *name)
669331337658SMarcel Moolenaar {
669431337658SMarcel Moolenaar     xop = xo_default(xop);
669531337658SMarcel Moolenaar 
66968a6eceffSPhil Shafer     ssize_t rc = 0;
6697d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
669831337658SMarcel Moolenaar     const char *pre_nl = "";
669931337658SMarcel Moolenaar 
670031337658SMarcel Moolenaar     if (name == NULL) {
670131337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
670231337658SMarcel Moolenaar 
670331337658SMarcel Moolenaar 	name = xsp->xs_name;
670431337658SMarcel Moolenaar 	if (name) {
67058a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
670631337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
670731337658SMarcel Moolenaar 	    char *cp = alloca(len);
670831337658SMarcel Moolenaar 	    memcpy(cp, name, len);
670931337658SMarcel Moolenaar 	    name = cp;
6710545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6711545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
671231337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
671331337658SMarcel Moolenaar 	}
6714545ddfbeSMarcel Moolenaar     }
671531337658SMarcel Moolenaar 
6716788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
671731337658SMarcel Moolenaar     case XO_STYLE_XML:
6718545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
671931337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
672031337658SMarcel Moolenaar 	break;
672131337658SMarcel Moolenaar 
672231337658SMarcel Moolenaar     case XO_STYLE_JSON:
6723d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
672431337658SMarcel Moolenaar 	ppn = (xop->xo_depth <= 1) ? "\n" : "";
672531337658SMarcel Moolenaar 
6726545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
672731337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
672831337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
672931337658SMarcel Moolenaar 	break;
673031337658SMarcel Moolenaar 
673131337658SMarcel Moolenaar     case XO_STYLE_HTML:
673231337658SMarcel Moolenaar     case XO_STYLE_TEXT:
6733545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
673431337658SMarcel Moolenaar 	break;
6735d1a0d267SMarcel Moolenaar 
6736d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6737d1a0d267SMarcel Moolenaar 	break;
6738d1a0d267SMarcel Moolenaar 
6739d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6740d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
6741d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL);
6742d1a0d267SMarcel Moolenaar 	break;
674331337658SMarcel Moolenaar     }
674431337658SMarcel Moolenaar 
674531337658SMarcel Moolenaar     return rc;
674631337658SMarcel Moolenaar }
674731337658SMarcel Moolenaar 
67488a6eceffSPhil Shafer xo_ssize_t
6749545ddfbeSMarcel Moolenaar xo_close_container_h (xo_handle_t *xop, const char *name)
6750545ddfbeSMarcel Moolenaar {
6751545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
6752545ddfbeSMarcel Moolenaar }
6753545ddfbeSMarcel Moolenaar 
67548a6eceffSPhil Shafer xo_ssize_t
675531337658SMarcel Moolenaar xo_close_container (const char *name)
675631337658SMarcel Moolenaar {
675731337658SMarcel Moolenaar     return xo_close_container_h(NULL, name);
675831337658SMarcel Moolenaar }
675931337658SMarcel Moolenaar 
67608a6eceffSPhil Shafer xo_ssize_t
676131337658SMarcel Moolenaar xo_close_container_hd (xo_handle_t *xop)
676231337658SMarcel Moolenaar {
676331337658SMarcel Moolenaar     return xo_close_container_h(xop, NULL);
676431337658SMarcel Moolenaar }
676531337658SMarcel Moolenaar 
67668a6eceffSPhil Shafer xo_ssize_t
676731337658SMarcel Moolenaar xo_close_container_d (void)
676831337658SMarcel Moolenaar {
676931337658SMarcel Moolenaar     return xo_close_container_h(NULL, NULL);
677031337658SMarcel Moolenaar }
677131337658SMarcel Moolenaar 
677231337658SMarcel Moolenaar static int
6773545ddfbeSMarcel Moolenaar xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
677431337658SMarcel Moolenaar {
67758a6eceffSPhil Shafer     ssize_t rc = 0;
6776545ddfbeSMarcel Moolenaar     int indent = 0;
6777545ddfbeSMarcel Moolenaar 
677831337658SMarcel Moolenaar     xop = xo_default(xop);
677931337658SMarcel Moolenaar 
6780d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
678131337658SMarcel Moolenaar     const char *pre_nl = "";
678231337658SMarcel Moolenaar 
6783d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6784d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6785d1a0d267SMarcel Moolenaar 
6786545ddfbeSMarcel Moolenaar 	indent = 1;
6787d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6788d1a0d267SMarcel Moolenaar 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6789788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
679031337658SMarcel Moolenaar 
679131337658SMarcel Moolenaar 	if (name == NULL) {
679231337658SMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
679331337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
679431337658SMarcel Moolenaar 	}
679531337658SMarcel Moolenaar 
679631337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
679731337658SMarcel Moolenaar 
679831337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6799d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
680031337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
680131337658SMarcel Moolenaar 
680231337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
680331337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
6804d1a0d267SMarcel Moolenaar 	break;
6805d1a0d267SMarcel Moolenaar 
6806d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6807d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL);
6808d1a0d267SMarcel Moolenaar 	break;
6809545ddfbeSMarcel Moolenaar     }
6810545ddfbeSMarcel Moolenaar 
6811545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
6812545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
681331337658SMarcel Moolenaar 
681431337658SMarcel Moolenaar     return rc;
681531337658SMarcel Moolenaar }
681631337658SMarcel Moolenaar 
6817545ddfbeSMarcel Moolenaar static int
6818545ddfbeSMarcel Moolenaar xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6819545ddfbeSMarcel Moolenaar {
6820545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
6821545ddfbeSMarcel Moolenaar }
6822545ddfbeSMarcel Moolenaar 
68238a6eceffSPhil Shafer xo_ssize_t
682442ff34c3SPhil Shafer xo_open_list_h (xo_handle_t *xop, const char *name)
682531337658SMarcel Moolenaar {
682631337658SMarcel Moolenaar     return xo_open_list_hf(xop, 0, name);
682731337658SMarcel Moolenaar }
682831337658SMarcel Moolenaar 
68298a6eceffSPhil Shafer xo_ssize_t
683031337658SMarcel Moolenaar xo_open_list (const char *name)
683131337658SMarcel Moolenaar {
683231337658SMarcel Moolenaar     return xo_open_list_hf(NULL, 0, name);
683331337658SMarcel Moolenaar }
683431337658SMarcel Moolenaar 
68358a6eceffSPhil Shafer xo_ssize_t
683642ff34c3SPhil Shafer xo_open_list_hd (xo_handle_t *xop, const char *name)
683731337658SMarcel Moolenaar {
683831337658SMarcel Moolenaar     return xo_open_list_hf(xop, XOF_DTRT, name);
683931337658SMarcel Moolenaar }
684031337658SMarcel Moolenaar 
68418a6eceffSPhil Shafer xo_ssize_t
684231337658SMarcel Moolenaar xo_open_list_d (const char *name)
684331337658SMarcel Moolenaar {
684431337658SMarcel Moolenaar     return xo_open_list_hf(NULL, XOF_DTRT, name);
684531337658SMarcel Moolenaar }
684631337658SMarcel Moolenaar 
6847545ddfbeSMarcel Moolenaar static int
6848545ddfbeSMarcel Moolenaar xo_do_close_list (xo_handle_t *xop, const char *name)
684931337658SMarcel Moolenaar {
68508a6eceffSPhil Shafer     ssize_t rc = 0;
685131337658SMarcel Moolenaar     const char *pre_nl = "";
685231337658SMarcel Moolenaar 
685331337658SMarcel Moolenaar     if (name == NULL) {
685431337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
685531337658SMarcel Moolenaar 
685631337658SMarcel Moolenaar 	name = xsp->xs_name;
685731337658SMarcel Moolenaar 	if (name) {
68588a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
685931337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
686031337658SMarcel Moolenaar 	    char *cp = alloca(len);
686131337658SMarcel Moolenaar 	    memcpy(cp, name, len);
686231337658SMarcel Moolenaar 	    name = cp;
6863545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6864545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
686531337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
686631337658SMarcel Moolenaar 	}
6867545ddfbeSMarcel Moolenaar     }
686831337658SMarcel Moolenaar 
6869d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6870d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
687131337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6872d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
687331337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
687431337658SMarcel Moolenaar 
6875545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
687631337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
687731337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6878d1a0d267SMarcel Moolenaar 	break;
687931337658SMarcel Moolenaar 
6880d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6881d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6882d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL);
6883d1a0d267SMarcel Moolenaar 	break;
6884d1a0d267SMarcel Moolenaar 
6885d1a0d267SMarcel Moolenaar     default:
6886545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6887545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6888d1a0d267SMarcel Moolenaar 	break;
6889545ddfbeSMarcel Moolenaar     }
6890545ddfbeSMarcel Moolenaar 
6891a0f704ffSMarcel Moolenaar     return rc;
689231337658SMarcel Moolenaar }
689331337658SMarcel Moolenaar 
68948a6eceffSPhil Shafer xo_ssize_t
6895545ddfbeSMarcel Moolenaar xo_close_list_h (xo_handle_t *xop, const char *name)
6896545ddfbeSMarcel Moolenaar {
6897545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
6898545ddfbeSMarcel Moolenaar }
6899545ddfbeSMarcel Moolenaar 
69008a6eceffSPhil Shafer xo_ssize_t
690131337658SMarcel Moolenaar xo_close_list (const char *name)
690231337658SMarcel Moolenaar {
690331337658SMarcel Moolenaar     return xo_close_list_h(NULL, name);
690431337658SMarcel Moolenaar }
690531337658SMarcel Moolenaar 
69068a6eceffSPhil Shafer xo_ssize_t
690731337658SMarcel Moolenaar xo_close_list_hd (xo_handle_t *xop)
690831337658SMarcel Moolenaar {
690931337658SMarcel Moolenaar     return xo_close_list_h(xop, NULL);
691031337658SMarcel Moolenaar }
691131337658SMarcel Moolenaar 
69128a6eceffSPhil Shafer xo_ssize_t
691331337658SMarcel Moolenaar xo_close_list_d (void)
691431337658SMarcel Moolenaar {
691531337658SMarcel Moolenaar     return xo_close_list_h(NULL, NULL);
691631337658SMarcel Moolenaar }
691731337658SMarcel Moolenaar 
691831337658SMarcel Moolenaar static int
6919545ddfbeSMarcel Moolenaar xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6920545ddfbeSMarcel Moolenaar {
69218a6eceffSPhil Shafer     ssize_t rc = 0;
6922545ddfbeSMarcel Moolenaar     int indent = 0;
6923545ddfbeSMarcel Moolenaar 
6924545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
6925545ddfbeSMarcel Moolenaar 
6926d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6927545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
6928545ddfbeSMarcel Moolenaar 
6929d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6930d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6931545ddfbeSMarcel Moolenaar 	indent = 1;
6932545ddfbeSMarcel Moolenaar 
6933d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
6934d1a0d267SMarcel Moolenaar 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
6935545ddfbeSMarcel Moolenaar 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6936d1a0d267SMarcel Moolenaar 		XOIF_SET(xop, XOIF_TOP_EMITTED);
6937545ddfbeSMarcel Moolenaar 	    }
6938545ddfbeSMarcel Moolenaar 	}
6939545ddfbeSMarcel Moolenaar 
6940545ddfbeSMarcel Moolenaar 	if (name == NULL) {
6941545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
6942545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
6943545ddfbeSMarcel Moolenaar 	}
6944545ddfbeSMarcel Moolenaar 
6945545ddfbeSMarcel Moolenaar 	xo_stack_set_flags(xop);
6946545ddfbeSMarcel Moolenaar 
6947545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6948d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6949545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6950545ddfbeSMarcel Moolenaar 
6951545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
6952545ddfbeSMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
6953d1a0d267SMarcel Moolenaar 	break;
6954d1a0d267SMarcel Moolenaar 
6955d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6956d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL);
6957d1a0d267SMarcel Moolenaar 	break;
6958545ddfbeSMarcel Moolenaar     }
6959545ddfbeSMarcel Moolenaar 
6960545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
6961545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
6962545ddfbeSMarcel Moolenaar 
6963545ddfbeSMarcel Moolenaar     return rc;
6964545ddfbeSMarcel Moolenaar }
6965545ddfbeSMarcel Moolenaar 
6966545ddfbeSMarcel Moolenaar static int
6967545ddfbeSMarcel Moolenaar xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
6968545ddfbeSMarcel Moolenaar {
69698a6eceffSPhil Shafer     ssize_t rc = 0;
6970545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
6971545ddfbeSMarcel Moolenaar 
6972545ddfbeSMarcel Moolenaar     if (name == NULL) {
6973545ddfbeSMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6974545ddfbeSMarcel Moolenaar 
6975545ddfbeSMarcel Moolenaar 	name = xsp->xs_name;
6976545ddfbeSMarcel Moolenaar 	if (name) {
69778a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
6978545ddfbeSMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
6979545ddfbeSMarcel Moolenaar 	    char *cp = alloca(len);
6980545ddfbeSMarcel Moolenaar 	    memcpy(cp, name, len);
6981545ddfbeSMarcel Moolenaar 	    name = cp;
6982545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6983545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
6984545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
6985545ddfbeSMarcel Moolenaar 	}
6986545ddfbeSMarcel Moolenaar     }
6987545ddfbeSMarcel Moolenaar 
6988d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6989d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6990545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6991d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6992545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6993545ddfbeSMarcel Moolenaar 
6994545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
6995545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
6996545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6997d1a0d267SMarcel Moolenaar 	break;
6998545ddfbeSMarcel Moolenaar 
6999d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7000d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL);
7001ee5cf116SPhil Shafer 	/* FALLTHRU */
7002d1a0d267SMarcel Moolenaar 
7003d1a0d267SMarcel Moolenaar     default:
7004545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7005545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7006d1a0d267SMarcel Moolenaar 	break;
7007545ddfbeSMarcel Moolenaar     }
7008545ddfbeSMarcel Moolenaar 
7009545ddfbeSMarcel Moolenaar     return rc;
7010545ddfbeSMarcel Moolenaar }
7011545ddfbeSMarcel Moolenaar 
7012545ddfbeSMarcel Moolenaar static int
7013545ddfbeSMarcel Moolenaar xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
701431337658SMarcel Moolenaar {
701531337658SMarcel Moolenaar     xop = xo_default(xop);
701631337658SMarcel Moolenaar 
70178a6eceffSPhil Shafer     ssize_t rc = 0;
7018d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
701931337658SMarcel Moolenaar     const char *pre_nl = "";
702031337658SMarcel Moolenaar 
702131337658SMarcel Moolenaar     flags |= xop->xo_flags;
702231337658SMarcel Moolenaar 
702331337658SMarcel Moolenaar     if (name == NULL) {
702431337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for instance name");
702531337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
702631337658SMarcel Moolenaar     }
702731337658SMarcel Moolenaar 
7028788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
702931337658SMarcel Moolenaar     case XO_STYLE_XML:
7030545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
7031545ddfbeSMarcel Moolenaar 
7032545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7033545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7034545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
7035545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7036545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7037545ddfbeSMarcel Moolenaar 	}
7038545ddfbeSMarcel Moolenaar 
7039545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
704031337658SMarcel Moolenaar 	break;
704131337658SMarcel Moolenaar 
704231337658SMarcel Moolenaar     case XO_STYLE_JSON:
704331337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
704431337658SMarcel Moolenaar 
704531337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7046d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
704731337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
704831337658SMarcel Moolenaar 
704931337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s{%s",
705031337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", ppn);
705131337658SMarcel Moolenaar 	break;
7052d1a0d267SMarcel Moolenaar 
7053d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7054d1a0d267SMarcel Moolenaar 	break;
7055d1a0d267SMarcel Moolenaar 
7056d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7057d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL);
7058d1a0d267SMarcel Moolenaar 	break;
705931337658SMarcel Moolenaar     }
706031337658SMarcel Moolenaar 
7061545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7062545ddfbeSMarcel Moolenaar 
706331337658SMarcel Moolenaar     return rc;
706431337658SMarcel Moolenaar }
706531337658SMarcel Moolenaar 
7066545ddfbeSMarcel Moolenaar static int
7067545ddfbeSMarcel Moolenaar xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7068545ddfbeSMarcel Moolenaar {
7069545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7070545ddfbeSMarcel Moolenaar }
7071545ddfbeSMarcel Moolenaar 
70728a6eceffSPhil Shafer xo_ssize_t
707331337658SMarcel Moolenaar xo_open_instance_h (xo_handle_t *xop, const char *name)
707431337658SMarcel Moolenaar {
707531337658SMarcel Moolenaar     return xo_open_instance_hf(xop, 0, name);
707631337658SMarcel Moolenaar }
707731337658SMarcel Moolenaar 
70788a6eceffSPhil Shafer xo_ssize_t
707931337658SMarcel Moolenaar xo_open_instance (const char *name)
708031337658SMarcel Moolenaar {
708131337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, 0, name);
708231337658SMarcel Moolenaar }
708331337658SMarcel Moolenaar 
70848a6eceffSPhil Shafer xo_ssize_t
708531337658SMarcel Moolenaar xo_open_instance_hd (xo_handle_t *xop, const char *name)
708631337658SMarcel Moolenaar {
708731337658SMarcel Moolenaar     return xo_open_instance_hf(xop, XOF_DTRT, name);
708831337658SMarcel Moolenaar }
708931337658SMarcel Moolenaar 
70908a6eceffSPhil Shafer xo_ssize_t
709131337658SMarcel Moolenaar xo_open_instance_d (const char *name)
709231337658SMarcel Moolenaar {
709331337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, XOF_DTRT, name);
709431337658SMarcel Moolenaar }
709531337658SMarcel Moolenaar 
7096545ddfbeSMarcel Moolenaar static int
7097545ddfbeSMarcel Moolenaar xo_do_close_instance (xo_handle_t *xop, const char *name)
709831337658SMarcel Moolenaar {
709931337658SMarcel Moolenaar     xop = xo_default(xop);
710031337658SMarcel Moolenaar 
71018a6eceffSPhil Shafer     ssize_t rc = 0;
7102d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
710331337658SMarcel Moolenaar     const char *pre_nl = "";
710431337658SMarcel Moolenaar 
710531337658SMarcel Moolenaar     if (name == NULL) {
710631337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
710731337658SMarcel Moolenaar 
710831337658SMarcel Moolenaar 	name = xsp->xs_name;
710931337658SMarcel Moolenaar 	if (name) {
71108a6eceffSPhil Shafer 	    ssize_t len = strlen(name) + 1;
711131337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
711231337658SMarcel Moolenaar 	    char *cp = alloca(len);
711331337658SMarcel Moolenaar 	    memcpy(cp, name, len);
711431337658SMarcel Moolenaar 	    name = cp;
7115545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7116545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
711731337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
711831337658SMarcel Moolenaar 	}
7119545ddfbeSMarcel Moolenaar     }
712031337658SMarcel Moolenaar 
7121788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
712231337658SMarcel Moolenaar     case XO_STYLE_XML:
7123545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
712431337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
712531337658SMarcel Moolenaar 	break;
712631337658SMarcel Moolenaar 
712731337658SMarcel Moolenaar     case XO_STYLE_JSON:
7128d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
712931337658SMarcel Moolenaar 
7130545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
713131337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
713231337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
713331337658SMarcel Moolenaar 	break;
713431337658SMarcel Moolenaar 
713531337658SMarcel Moolenaar     case XO_STYLE_HTML:
713631337658SMarcel Moolenaar     case XO_STYLE_TEXT:
7137545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
713831337658SMarcel Moolenaar 	break;
7139d1a0d267SMarcel Moolenaar 
7140d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7141d1a0d267SMarcel Moolenaar 	break;
7142d1a0d267SMarcel Moolenaar 
7143d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7144d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7145d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL);
7146d1a0d267SMarcel Moolenaar 	break;
714731337658SMarcel Moolenaar     }
714831337658SMarcel Moolenaar 
714931337658SMarcel Moolenaar     return rc;
715031337658SMarcel Moolenaar }
715131337658SMarcel Moolenaar 
71528a6eceffSPhil Shafer xo_ssize_t
7153545ddfbeSMarcel Moolenaar xo_close_instance_h (xo_handle_t *xop, const char *name)
7154545ddfbeSMarcel Moolenaar {
7155545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7156545ddfbeSMarcel Moolenaar }
7157545ddfbeSMarcel Moolenaar 
71588a6eceffSPhil Shafer xo_ssize_t
715931337658SMarcel Moolenaar xo_close_instance (const char *name)
716031337658SMarcel Moolenaar {
716131337658SMarcel Moolenaar     return xo_close_instance_h(NULL, name);
716231337658SMarcel Moolenaar }
716331337658SMarcel Moolenaar 
71648a6eceffSPhil Shafer xo_ssize_t
716531337658SMarcel Moolenaar xo_close_instance_hd (xo_handle_t *xop)
716631337658SMarcel Moolenaar {
716731337658SMarcel Moolenaar     return xo_close_instance_h(xop, NULL);
716831337658SMarcel Moolenaar }
716931337658SMarcel Moolenaar 
71708a6eceffSPhil Shafer xo_ssize_t
717131337658SMarcel Moolenaar xo_close_instance_d (void)
717231337658SMarcel Moolenaar {
717331337658SMarcel Moolenaar     return xo_close_instance_h(NULL, NULL);
717431337658SMarcel Moolenaar }
717531337658SMarcel Moolenaar 
7176545ddfbeSMarcel Moolenaar static int
7177545ddfbeSMarcel Moolenaar xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7178545ddfbeSMarcel Moolenaar {
7179545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
71808a6eceffSPhil Shafer     ssize_t rc = 0;
7181545ddfbeSMarcel Moolenaar     xo_xsf_flags_t flags;
7182545ddfbeSMarcel Moolenaar 
7183545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7184545ddfbeSMarcel Moolenaar 	switch (xsp->xs_state) {
7185545ddfbeSMarcel Moolenaar 	case XSS_INIT:
7186545ddfbeSMarcel Moolenaar 	    /* Nothing */
7187545ddfbeSMarcel Moolenaar 	    rc = 0;
7188545ddfbeSMarcel Moolenaar 	    break;
7189545ddfbeSMarcel Moolenaar 
7190545ddfbeSMarcel Moolenaar 	case XSS_OPEN_CONTAINER:
7191545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_container(xop, NULL);
7192545ddfbeSMarcel Moolenaar 	    break;
7193545ddfbeSMarcel Moolenaar 
7194545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LIST:
7195545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_list(xop, NULL);
7196545ddfbeSMarcel Moolenaar 	    break;
7197545ddfbeSMarcel Moolenaar 
7198545ddfbeSMarcel Moolenaar 	case XSS_OPEN_INSTANCE:
7199545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7200545ddfbeSMarcel Moolenaar 	    break;
7201545ddfbeSMarcel Moolenaar 
7202545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LEAF_LIST:
7203545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_leaf_list(xop, NULL);
7204545ddfbeSMarcel Moolenaar 	    break;
7205545ddfbeSMarcel Moolenaar 
7206545ddfbeSMarcel Moolenaar 	case XSS_MARKER:
7207545ddfbeSMarcel Moolenaar 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7208545ddfbeSMarcel Moolenaar 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7209545ddfbeSMarcel Moolenaar 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7210545ddfbeSMarcel Moolenaar 	    rc = 0;
7211545ddfbeSMarcel Moolenaar 	    break;
7212545ddfbeSMarcel Moolenaar 	}
7213545ddfbeSMarcel Moolenaar 
7214545ddfbeSMarcel Moolenaar 	if (rc < 0)
7215545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7216545ddfbeSMarcel Moolenaar     }
7217545ddfbeSMarcel Moolenaar 
7218545ddfbeSMarcel Moolenaar     return 0;
7219545ddfbeSMarcel Moolenaar }
7220545ddfbeSMarcel Moolenaar 
7221545ddfbeSMarcel Moolenaar /*
7222545ddfbeSMarcel Moolenaar  * This function is responsible for clearing out whatever is needed
7223545ddfbeSMarcel Moolenaar  * to get to the desired state, if possible.
7224545ddfbeSMarcel Moolenaar  */
7225545ddfbeSMarcel Moolenaar static int
7226545ddfbeSMarcel Moolenaar xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7227545ddfbeSMarcel Moolenaar {
7228545ddfbeSMarcel Moolenaar     xo_stack_t *xsp, *limit = NULL;
72298a6eceffSPhil Shafer     ssize_t rc;
7230545ddfbeSMarcel Moolenaar     xo_state_t need_state = new_state;
7231545ddfbeSMarcel Moolenaar 
7232545ddfbeSMarcel Moolenaar     if (new_state == XSS_CLOSE_CONTAINER)
7233545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_CONTAINER;
7234545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LIST)
7235545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LIST;
7236545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_INSTANCE)
7237545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_INSTANCE;
7238545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LEAF_LIST)
7239545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LEAF_LIST;
7240545ddfbeSMarcel Moolenaar     else if (new_state == XSS_MARKER)
7241545ddfbeSMarcel Moolenaar 	need_state = XSS_MARKER;
7242545ddfbeSMarcel Moolenaar     else
7243545ddfbeSMarcel Moolenaar 	return 0; /* Unknown or useless new states are ignored */
7244545ddfbeSMarcel Moolenaar 
7245545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7246545ddfbeSMarcel Moolenaar 	/*
7247545ddfbeSMarcel Moolenaar 	 * Marker's normally stop us from going any further, unless
7248545ddfbeSMarcel Moolenaar 	 * we are popping a marker (new_state == XSS_MARKER).
7249545ddfbeSMarcel Moolenaar 	 */
7250545ddfbeSMarcel Moolenaar 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7251545ddfbeSMarcel Moolenaar 	    if (name) {
7252545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7253545ddfbeSMarcel Moolenaar 			   "not found '%s'",
7254545ddfbeSMarcel Moolenaar 			   xo_state_name(new_state),
7255545ddfbeSMarcel Moolenaar 			   xsp->xs_name, name);
7256545ddfbeSMarcel Moolenaar 		return 0;
7257545ddfbeSMarcel Moolenaar 
7258545ddfbeSMarcel Moolenaar 	    } else {
7259545ddfbeSMarcel Moolenaar 		limit = xsp;
7260545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7261545ddfbeSMarcel Moolenaar 	    }
7262545ddfbeSMarcel Moolenaar 	    break;
7263545ddfbeSMarcel Moolenaar 	}
7264545ddfbeSMarcel Moolenaar 
7265545ddfbeSMarcel Moolenaar 	if (xsp->xs_state != need_state)
7266545ddfbeSMarcel Moolenaar 	    continue;
7267545ddfbeSMarcel Moolenaar 
7268545ddfbeSMarcel Moolenaar 	if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
7269545ddfbeSMarcel Moolenaar 	    continue;
7270545ddfbeSMarcel Moolenaar 
7271545ddfbeSMarcel Moolenaar 	limit = xsp;
7272545ddfbeSMarcel Moolenaar 	break;
7273545ddfbeSMarcel Moolenaar     }
7274545ddfbeSMarcel Moolenaar 
7275545ddfbeSMarcel Moolenaar     if (limit == NULL) {
7276545ddfbeSMarcel Moolenaar 	xo_failure(xop, "xo_%s can't find match for '%s'",
7277545ddfbeSMarcel Moolenaar 		   xo_state_name(new_state), name);
7278545ddfbeSMarcel Moolenaar 	return 0;
7279545ddfbeSMarcel Moolenaar     }
7280545ddfbeSMarcel Moolenaar 
7281545ddfbeSMarcel Moolenaar     rc = xo_do_close_all(xop, limit);
7282545ddfbeSMarcel Moolenaar 
7283545ddfbeSMarcel Moolenaar     return rc;
7284545ddfbeSMarcel Moolenaar }
7285545ddfbeSMarcel Moolenaar 
7286545ddfbeSMarcel Moolenaar /*
7287545ddfbeSMarcel Moolenaar  * We are in a given state and need to transition to the new state.
7288545ddfbeSMarcel Moolenaar  */
72898a6eceffSPhil Shafer static ssize_t
7290545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
7291545ddfbeSMarcel Moolenaar 	       xo_state_t new_state)
7292545ddfbeSMarcel Moolenaar {
7293545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
72948a6eceffSPhil Shafer     ssize_t rc = 0;
7295545ddfbeSMarcel Moolenaar     int old_state, on_marker;
7296545ddfbeSMarcel Moolenaar 
7297545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7298545ddfbeSMarcel Moolenaar 
7299545ddfbeSMarcel Moolenaar     xsp = &xop->xo_stack[xop->xo_depth];
7300545ddfbeSMarcel Moolenaar     old_state = xsp->xs_state;
7301545ddfbeSMarcel Moolenaar     on_marker = (old_state == XSS_MARKER);
7302545ddfbeSMarcel Moolenaar 
7303545ddfbeSMarcel Moolenaar     /* If there's a marker on top of the stack, we need to find a real state */
7304545ddfbeSMarcel Moolenaar     while (old_state == XSS_MARKER) {
7305545ddfbeSMarcel Moolenaar 	if (xsp == xop->xo_stack)
7306545ddfbeSMarcel Moolenaar 	    break;
7307545ddfbeSMarcel Moolenaar 	xsp -= 1;
7308545ddfbeSMarcel Moolenaar 	old_state = xsp->xs_state;
7309545ddfbeSMarcel Moolenaar     }
7310545ddfbeSMarcel Moolenaar 
7311545ddfbeSMarcel Moolenaar     /*
7312545ddfbeSMarcel Moolenaar      * At this point, the list of possible states are:
7313545ddfbeSMarcel Moolenaar      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7314545ddfbeSMarcel Moolenaar      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7315545ddfbeSMarcel Moolenaar      */
7316545ddfbeSMarcel Moolenaar     switch (XSS_TRANSITION(old_state, new_state)) {
7317545ddfbeSMarcel Moolenaar 
7318545ddfbeSMarcel Moolenaar     open_container:
7319545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7320545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7321545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7322545ddfbeSMarcel Moolenaar        rc = xo_do_open_container(xop, flags, name);
7323545ddfbeSMarcel Moolenaar        break;
7324545ddfbeSMarcel Moolenaar 
7325545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7326545ddfbeSMarcel Moolenaar 	if (on_marker)
7327545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7328545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7329545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7330545ddfbeSMarcel Moolenaar 	    goto open_container;
7331545ddfbeSMarcel Moolenaar 	break;
7332545ddfbeSMarcel Moolenaar 
7333545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7334545ddfbeSMarcel Moolenaar 	if (on_marker)
7335545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7336545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7337545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7338545ddfbeSMarcel Moolenaar 	    goto open_container;
7339545ddfbeSMarcel Moolenaar 	break;
7340545ddfbeSMarcel Moolenaar 
7341545ddfbeSMarcel Moolenaar     /*close_container:*/
7342545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7343545ddfbeSMarcel Moolenaar 	if (on_marker)
7344545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7345545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7346545ddfbeSMarcel Moolenaar 	break;
7347545ddfbeSMarcel Moolenaar 
7348545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7349545ddfbeSMarcel Moolenaar 	/* This is an exception for "xo --close" */
7350545ddfbeSMarcel Moolenaar 	rc = xo_do_close_container(xop, name);
7351545ddfbeSMarcel Moolenaar 	break;
7352545ddfbeSMarcel Moolenaar 
7353545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7354545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7355545ddfbeSMarcel Moolenaar 	if (on_marker)
7356545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7357545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7358545ddfbeSMarcel Moolenaar 	break;
7359545ddfbeSMarcel Moolenaar 
7360545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7361545ddfbeSMarcel Moolenaar 	if (on_marker)
7362545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7363545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7364545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7365545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7366545ddfbeSMarcel Moolenaar 	break;
7367545ddfbeSMarcel Moolenaar 
7368545ddfbeSMarcel Moolenaar     open_list:
7369545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7370545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7371545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7372545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7373545ddfbeSMarcel Moolenaar 	break;
7374545ddfbeSMarcel Moolenaar 
7375545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7376545ddfbeSMarcel Moolenaar 	if (on_marker)
7377545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7378545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7379545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7380545ddfbeSMarcel Moolenaar 	    goto open_list;
7381545ddfbeSMarcel Moolenaar 	break;
7382545ddfbeSMarcel Moolenaar 
7383545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7384545ddfbeSMarcel Moolenaar 	if (on_marker)
7385545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7386545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7387545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7388545ddfbeSMarcel Moolenaar 	    goto open_list;
7389545ddfbeSMarcel Moolenaar 	break;
7390545ddfbeSMarcel Moolenaar 
7391545ddfbeSMarcel Moolenaar     /*close_list:*/
7392545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7393545ddfbeSMarcel Moolenaar 	if (on_marker)
7394545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7395545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7396545ddfbeSMarcel Moolenaar 	break;
7397545ddfbeSMarcel Moolenaar 
7398545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7399545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7400545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7401545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7402545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7403545ddfbeSMarcel Moolenaar 	break;
7404545ddfbeSMarcel Moolenaar 
7405545ddfbeSMarcel Moolenaar     open_instance:
7406545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7407545ddfbeSMarcel Moolenaar 	rc = xo_do_open_instance(xop, flags, name);
7408545ddfbeSMarcel Moolenaar 	break;
7409545ddfbeSMarcel Moolenaar 
7410545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7411788ca347SMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7412545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
7413545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7414545ddfbeSMarcel Moolenaar 	    goto open_instance;
7415545ddfbeSMarcel Moolenaar 	break;
7416545ddfbeSMarcel Moolenaar 
7417545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7418545ddfbeSMarcel Moolenaar 	if (on_marker) {
7419545ddfbeSMarcel Moolenaar 	    rc = xo_do_open_list(xop, flags, name);
7420545ddfbeSMarcel Moolenaar 	} else {
7421545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
7422545ddfbeSMarcel Moolenaar 	}
7423545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7424545ddfbeSMarcel Moolenaar 	    goto open_instance;
7425545ddfbeSMarcel Moolenaar 	break;
7426545ddfbeSMarcel Moolenaar 
7427545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7428545ddfbeSMarcel Moolenaar 	if (on_marker)
7429545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7430545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7431545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7432545ddfbeSMarcel Moolenaar 	    goto open_instance;
7433545ddfbeSMarcel Moolenaar 	break;
7434545ddfbeSMarcel Moolenaar 
7435545ddfbeSMarcel Moolenaar     /*close_instance:*/
7436545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7437545ddfbeSMarcel Moolenaar 	if (on_marker)
7438545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7439545ddfbeSMarcel Moolenaar 	rc = xo_do_close_instance(xop, name);
7440545ddfbeSMarcel Moolenaar 	break;
7441545ddfbeSMarcel Moolenaar 
7442545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7443545ddfbeSMarcel Moolenaar 	/* This one makes no sense; ignore it */
7444788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_instance ignored when called from "
7445788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7446545ddfbeSMarcel Moolenaar 	break;
7447545ddfbeSMarcel Moolenaar 
7448545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7449545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7450545ddfbeSMarcel Moolenaar 	if (on_marker)
7451545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7452545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7453545ddfbeSMarcel Moolenaar 	break;
7454545ddfbeSMarcel Moolenaar 
7455545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7456545ddfbeSMarcel Moolenaar 	if (on_marker)
7457545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7458545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7459545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7460545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7461545ddfbeSMarcel Moolenaar 	break;
7462545ddfbeSMarcel Moolenaar 
7463545ddfbeSMarcel Moolenaar     open_leaf_list:
7464545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7465545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7466545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7467545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7468545ddfbeSMarcel Moolenaar 	break;
7469545ddfbeSMarcel Moolenaar 
7470545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7471545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7472545ddfbeSMarcel Moolenaar 	if (on_marker)
7473545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7474545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7475545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7476545ddfbeSMarcel Moolenaar 	    goto open_leaf_list;
7477545ddfbeSMarcel Moolenaar 	break;
7478545ddfbeSMarcel Moolenaar 
7479545ddfbeSMarcel Moolenaar     /*close_leaf_list:*/
7480545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7481545ddfbeSMarcel Moolenaar 	if (on_marker)
7482545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7483545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, name);
7484545ddfbeSMarcel Moolenaar 	break;
7485545ddfbeSMarcel Moolenaar 
7486545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7487545ddfbeSMarcel Moolenaar 	/* Makes no sense; ignore */
7488788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7489788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7490545ddfbeSMarcel Moolenaar 	break;
7491545ddfbeSMarcel Moolenaar 
7492545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7493545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7494545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7495545ddfbeSMarcel Moolenaar 	if (on_marker)
7496545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7497545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7498545ddfbeSMarcel Moolenaar 	break;
7499545ddfbeSMarcel Moolenaar 
7500545ddfbeSMarcel Moolenaar     /*emit:*/
7501545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7502545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7503545ddfbeSMarcel Moolenaar 	break;
7504545ddfbeSMarcel Moolenaar 
7505545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7506545ddfbeSMarcel Moolenaar 	if (on_marker)
7507545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7508545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7509545ddfbeSMarcel Moolenaar 	break;
7510545ddfbeSMarcel Moolenaar 
7511545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7512545ddfbeSMarcel Moolenaar 	break;
7513545ddfbeSMarcel Moolenaar 
7514545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7515545ddfbeSMarcel Moolenaar 	if (on_marker)
7516545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7517545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7518545ddfbeSMarcel Moolenaar 	break;
7519545ddfbeSMarcel Moolenaar 
7520545ddfbeSMarcel Moolenaar     /*emit_leaf_list:*/
7521545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7522545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7523545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7524545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7525545ddfbeSMarcel Moolenaar 	break;
7526545ddfbeSMarcel Moolenaar 
7527545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7528545ddfbeSMarcel Moolenaar 	break;
7529545ddfbeSMarcel Moolenaar 
7530545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7531545ddfbeSMarcel Moolenaar 	/*
7532545ddfbeSMarcel Moolenaar 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7533545ddfbeSMarcel Moolenaar 	 * API, where both lists and leaf-lists were opened as lists.  So
7534545ddfbeSMarcel Moolenaar 	 * if we find an open list that hasn't had anything written to it,
7535545ddfbeSMarcel Moolenaar 	 * we'll accept it.
7536545ddfbeSMarcel Moolenaar 	 */
7537545ddfbeSMarcel Moolenaar 	break;
7538545ddfbeSMarcel Moolenaar 
7539545ddfbeSMarcel Moolenaar     default:
7540545ddfbeSMarcel Moolenaar 	xo_failure(xop, "unknown transition: (%u -> %u)",
7541545ddfbeSMarcel Moolenaar 		   xsp->xs_state, new_state);
7542545ddfbeSMarcel Moolenaar     }
7543545ddfbeSMarcel Moolenaar 
754442ff34c3SPhil Shafer     /* Handle the flush flag */
754542ff34c3SPhil Shafer     if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
754642ff34c3SPhil Shafer 	if (xo_flush_h(xop))
754742ff34c3SPhil Shafer 	    rc = -1;
754842ff34c3SPhil Shafer 
7549545ddfbeSMarcel Moolenaar     return rc;
7550545ddfbeSMarcel Moolenaar 
7551545ddfbeSMarcel Moolenaar  marker_prevents_close:
7552545ddfbeSMarcel Moolenaar     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7553545ddfbeSMarcel Moolenaar 	       xop->xo_stack[xop->xo_depth].xs_name,
7554545ddfbeSMarcel Moolenaar 	       xo_state_name(old_state), xo_state_name(new_state));
7555545ddfbeSMarcel Moolenaar     return -1;
7556545ddfbeSMarcel Moolenaar }
7557545ddfbeSMarcel Moolenaar 
75588a6eceffSPhil Shafer xo_ssize_t
7559545ddfbeSMarcel Moolenaar xo_open_marker_h (xo_handle_t *xop, const char *name)
7560545ddfbeSMarcel Moolenaar {
7561545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7562545ddfbeSMarcel Moolenaar 
7563545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7564545ddfbeSMarcel Moolenaar 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7565545ddfbeSMarcel Moolenaar 
7566545ddfbeSMarcel Moolenaar     return 0;
7567545ddfbeSMarcel Moolenaar }
7568545ddfbeSMarcel Moolenaar 
75698a6eceffSPhil Shafer xo_ssize_t
7570545ddfbeSMarcel Moolenaar xo_open_marker (const char *name)
7571545ddfbeSMarcel Moolenaar {
7572545ddfbeSMarcel Moolenaar     return xo_open_marker_h(NULL, name);
7573545ddfbeSMarcel Moolenaar }
7574545ddfbeSMarcel Moolenaar 
75758a6eceffSPhil Shafer xo_ssize_t
7576545ddfbeSMarcel Moolenaar xo_close_marker_h (xo_handle_t *xop, const char *name)
7577545ddfbeSMarcel Moolenaar {
7578545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7579545ddfbeSMarcel Moolenaar 
7580545ddfbeSMarcel Moolenaar     return xo_do_close(xop, name, XSS_MARKER);
7581545ddfbeSMarcel Moolenaar }
7582545ddfbeSMarcel Moolenaar 
75838a6eceffSPhil Shafer xo_ssize_t
7584545ddfbeSMarcel Moolenaar xo_close_marker (const char *name)
7585545ddfbeSMarcel Moolenaar {
7586545ddfbeSMarcel Moolenaar     return xo_close_marker_h(NULL, name);
7587545ddfbeSMarcel Moolenaar }
7588545ddfbeSMarcel Moolenaar 
7589d1a0d267SMarcel Moolenaar /*
7590d1a0d267SMarcel Moolenaar  * Record custom output functions into the xo handle, allowing
7591d1a0d267SMarcel Moolenaar  * integration with a variety of output frameworks.
7592d1a0d267SMarcel Moolenaar  */
759331337658SMarcel Moolenaar void
759431337658SMarcel Moolenaar xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7595545ddfbeSMarcel Moolenaar 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
759631337658SMarcel Moolenaar {
759731337658SMarcel Moolenaar     xop = xo_default(xop);
759831337658SMarcel Moolenaar 
759931337658SMarcel Moolenaar     xop->xo_opaque = opaque;
760031337658SMarcel Moolenaar     xop->xo_write = write_func;
760131337658SMarcel Moolenaar     xop->xo_close = close_func;
7602545ddfbeSMarcel Moolenaar     xop->xo_flush = flush_func;
760331337658SMarcel Moolenaar }
760431337658SMarcel Moolenaar 
760531337658SMarcel Moolenaar void
760631337658SMarcel Moolenaar xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
760731337658SMarcel Moolenaar {
760831337658SMarcel Moolenaar     xo_realloc = realloc_func;
760931337658SMarcel Moolenaar     xo_free = free_func;
761031337658SMarcel Moolenaar }
761131337658SMarcel Moolenaar 
76128a6eceffSPhil Shafer xo_ssize_t
761331337658SMarcel Moolenaar xo_flush_h (xo_handle_t *xop)
761431337658SMarcel Moolenaar {
76158a6eceffSPhil Shafer     ssize_t rc;
761631337658SMarcel Moolenaar 
761731337658SMarcel Moolenaar     xop = xo_default(xop);
761831337658SMarcel Moolenaar 
7619788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
7620d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7621d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL);
762231337658SMarcel Moolenaar     }
762331337658SMarcel Moolenaar 
7624545ddfbeSMarcel Moolenaar     rc = xo_write(xop);
7625545ddfbeSMarcel Moolenaar     if (rc >= 0 && xop->xo_flush)
7626545ddfbeSMarcel Moolenaar 	if (xop->xo_flush(xop->xo_opaque) < 0)
7627545ddfbeSMarcel Moolenaar 	    return -1;
7628545ddfbeSMarcel Moolenaar 
7629545ddfbeSMarcel Moolenaar     return rc;
763031337658SMarcel Moolenaar }
763131337658SMarcel Moolenaar 
76328a6eceffSPhil Shafer xo_ssize_t
763331337658SMarcel Moolenaar xo_flush (void)
763431337658SMarcel Moolenaar {
7635545ddfbeSMarcel Moolenaar     return xo_flush_h(NULL);
763631337658SMarcel Moolenaar }
763731337658SMarcel Moolenaar 
76388a6eceffSPhil Shafer xo_ssize_t
763931337658SMarcel Moolenaar xo_finish_h (xo_handle_t *xop)
764031337658SMarcel Moolenaar {
764131337658SMarcel Moolenaar     const char *cp = "";
764231337658SMarcel Moolenaar     xop = xo_default(xop);
764331337658SMarcel Moolenaar 
7644d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
7645545ddfbeSMarcel Moolenaar 	xo_do_close_all(xop, xop->xo_stack);
7646545ddfbeSMarcel Moolenaar 
7647788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
764831337658SMarcel Moolenaar     case XO_STYLE_JSON:
7649d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7650d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7651d1a0d267SMarcel Moolenaar 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
765231337658SMarcel Moolenaar 	    else
765331337658SMarcel Moolenaar 		cp = "{ ";
765431337658SMarcel Moolenaar 	    xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
765531337658SMarcel Moolenaar 	}
765631337658SMarcel Moolenaar 	break;
7657d1a0d267SMarcel Moolenaar 
7658d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7659d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL);
7660d1a0d267SMarcel Moolenaar 	break;
766131337658SMarcel Moolenaar     }
766231337658SMarcel Moolenaar 
7663545ddfbeSMarcel Moolenaar     return xo_flush_h(xop);
766431337658SMarcel Moolenaar }
766531337658SMarcel Moolenaar 
76668a6eceffSPhil Shafer xo_ssize_t
766731337658SMarcel Moolenaar xo_finish (void)
766831337658SMarcel Moolenaar {
7669545ddfbeSMarcel Moolenaar     return xo_finish_h(NULL);
767031337658SMarcel Moolenaar }
767131337658SMarcel Moolenaar 
767231337658SMarcel Moolenaar /*
7673d1a0d267SMarcel Moolenaar  * xo_finish_atexit is suitable for atexit() calls, to force clear up
7674d1a0d267SMarcel Moolenaar  * and finalizing output.
7675d1a0d267SMarcel Moolenaar  */
7676d1a0d267SMarcel Moolenaar void
7677d1a0d267SMarcel Moolenaar xo_finish_atexit (void)
7678d1a0d267SMarcel Moolenaar {
7679d1a0d267SMarcel Moolenaar     (void) xo_finish_h(NULL);
7680d1a0d267SMarcel Moolenaar }
7681d1a0d267SMarcel Moolenaar 
7682d1a0d267SMarcel Moolenaar /*
768331337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
768431337658SMarcel Moolenaar  */
768531337658SMarcel Moolenaar void
768631337658SMarcel Moolenaar xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
768731337658SMarcel Moolenaar {
768831337658SMarcel Moolenaar     xop = xo_default(xop);
768931337658SMarcel Moolenaar 
769031337658SMarcel Moolenaar     /*
769131337658SMarcel Moolenaar      * If the format string doesn't end with a newline, we pop
769231337658SMarcel Moolenaar      * one on ourselves.
769331337658SMarcel Moolenaar      */
76948a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
769531337658SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
769631337658SMarcel Moolenaar 	char *newfmt = alloca(len + 2);
769731337658SMarcel Moolenaar 	memcpy(newfmt, fmt, len);
769831337658SMarcel Moolenaar 	newfmt[len] = '\n';
769931337658SMarcel Moolenaar 	newfmt[len] = '\0';
770031337658SMarcel Moolenaar 	fmt = newfmt;
770131337658SMarcel Moolenaar     }
770231337658SMarcel Moolenaar 
7703788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
770431337658SMarcel Moolenaar     case XO_STYLE_TEXT:
770531337658SMarcel Moolenaar 	vfprintf(stderr, fmt, vap);
770631337658SMarcel Moolenaar 	break;
770731337658SMarcel Moolenaar 
770831337658SMarcel Moolenaar     case XO_STYLE_HTML:
770931337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
771031337658SMarcel Moolenaar 
771131337658SMarcel Moolenaar 	xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
771231337658SMarcel Moolenaar 
7713d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
771431337658SMarcel Moolenaar 	    xo_line_close(xop);
771531337658SMarcel Moolenaar 
771631337658SMarcel Moolenaar 	xo_write(xop);
771731337658SMarcel Moolenaar 
771831337658SMarcel Moolenaar 	va_end(xop->xo_vap);
771931337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
772031337658SMarcel Moolenaar 	break;
772131337658SMarcel Moolenaar 
772231337658SMarcel Moolenaar     case XO_STYLE_XML:
7723545ddfbeSMarcel Moolenaar     case XO_STYLE_JSON:
772431337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
772531337658SMarcel Moolenaar 
772631337658SMarcel Moolenaar 	xo_open_container_h(xop, "error");
772731337658SMarcel Moolenaar 	xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
772831337658SMarcel Moolenaar 	xo_close_container_h(xop, "error");
772931337658SMarcel Moolenaar 
773031337658SMarcel Moolenaar 	va_end(xop->xo_vap);
773131337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
773231337658SMarcel Moolenaar 	break;
7733d1a0d267SMarcel Moolenaar 
7734d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7735d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7736d1a0d267SMarcel Moolenaar 	break;
773731337658SMarcel Moolenaar     }
773831337658SMarcel Moolenaar }
773931337658SMarcel Moolenaar 
774031337658SMarcel Moolenaar void
774131337658SMarcel Moolenaar xo_error_h (xo_handle_t *xop, const char *fmt, ...)
774231337658SMarcel Moolenaar {
774331337658SMarcel Moolenaar     va_list vap;
774431337658SMarcel Moolenaar 
774531337658SMarcel Moolenaar     va_start(vap, fmt);
774631337658SMarcel Moolenaar     xo_error_hv(xop, fmt, vap);
774731337658SMarcel Moolenaar     va_end(vap);
774831337658SMarcel Moolenaar }
774931337658SMarcel Moolenaar 
775031337658SMarcel Moolenaar /*
775131337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
775231337658SMarcel Moolenaar  */
775331337658SMarcel Moolenaar void
775431337658SMarcel Moolenaar xo_error (const char *fmt, ...)
775531337658SMarcel Moolenaar {
775631337658SMarcel Moolenaar     va_list vap;
775731337658SMarcel Moolenaar 
775831337658SMarcel Moolenaar     va_start(vap, fmt);
775931337658SMarcel Moolenaar     xo_error_hv(NULL, fmt, vap);
776031337658SMarcel Moolenaar     va_end(vap);
776131337658SMarcel Moolenaar }
776231337658SMarcel Moolenaar 
7763d1a0d267SMarcel Moolenaar /*
7764d1a0d267SMarcel Moolenaar  * Parse any libxo-specific options from the command line, removing them
7765d1a0d267SMarcel Moolenaar  * so the main() argument parsing won't see them.  We return the new value
7766d1a0d267SMarcel Moolenaar  * for argc or -1 for error.  If an error occurred, the program should
7767d1a0d267SMarcel Moolenaar  * exit.  A suitable error message has already been displayed.
7768d1a0d267SMarcel Moolenaar  */
776931337658SMarcel Moolenaar int
777031337658SMarcel Moolenaar xo_parse_args (int argc, char **argv)
777131337658SMarcel Moolenaar {
777231337658SMarcel Moolenaar     static char libxo_opt[] = "--libxo";
777331337658SMarcel Moolenaar     char *cp;
777431337658SMarcel Moolenaar     int i, save;
777531337658SMarcel Moolenaar 
777631337658SMarcel Moolenaar     /* Save our program name for xo_err and friends */
777731337658SMarcel Moolenaar     xo_program = argv[0];
777831337658SMarcel Moolenaar     cp = strrchr(xo_program, '/');
777931337658SMarcel Moolenaar     if (cp)
778031337658SMarcel Moolenaar 	xo_program = cp + 1;
778131337658SMarcel Moolenaar 
778231337658SMarcel Moolenaar     for (save = i = 1; i < argc; i++) {
778331337658SMarcel Moolenaar 	if (argv[i] == NULL
778431337658SMarcel Moolenaar 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
778531337658SMarcel Moolenaar 	    if (save != i)
778631337658SMarcel Moolenaar 		argv[save] = argv[i];
778731337658SMarcel Moolenaar 	    save += 1;
778831337658SMarcel Moolenaar 	    continue;
778931337658SMarcel Moolenaar 	}
779031337658SMarcel Moolenaar 
779131337658SMarcel Moolenaar 	cp = argv[i] + sizeof(libxo_opt) - 1;
7792ee5cf116SPhil Shafer 	if (*cp == '\0') {
779331337658SMarcel Moolenaar 	    cp = argv[++i];
7794ee5cf116SPhil Shafer 	    if (cp == NULL) {
779531337658SMarcel Moolenaar 		xo_warnx("missing libxo option");
779631337658SMarcel Moolenaar 		return -1;
779731337658SMarcel Moolenaar 	    }
779831337658SMarcel Moolenaar 
779931337658SMarcel Moolenaar 	    if (xo_set_options(NULL, cp) < 0)
780031337658SMarcel Moolenaar 		return -1;
780131337658SMarcel Moolenaar 	} else if (*cp == ':') {
780231337658SMarcel Moolenaar 	    if (xo_set_options(NULL, cp) < 0)
780331337658SMarcel Moolenaar 		return -1;
780431337658SMarcel Moolenaar 
780531337658SMarcel Moolenaar 	} else if (*cp == '=') {
780631337658SMarcel Moolenaar 	    if (xo_set_options(NULL, ++cp) < 0)
780731337658SMarcel Moolenaar 		return -1;
780831337658SMarcel Moolenaar 
780931337658SMarcel Moolenaar 	} else if (*cp == '-') {
781031337658SMarcel Moolenaar 	    cp += 1;
781131337658SMarcel Moolenaar 	    if (strcmp(cp, "check") == 0) {
781231337658SMarcel Moolenaar 		exit(XO_HAS_LIBXO);
781331337658SMarcel Moolenaar 
781431337658SMarcel Moolenaar 	    } else {
781531337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
781631337658SMarcel Moolenaar 		return -1;
781731337658SMarcel Moolenaar 	    }
781831337658SMarcel Moolenaar 	} else {
781931337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
782031337658SMarcel Moolenaar 	    return -1;
782131337658SMarcel Moolenaar 	}
782231337658SMarcel Moolenaar     }
782331337658SMarcel Moolenaar 
782431337658SMarcel Moolenaar     argv[save] = NULL;
782531337658SMarcel Moolenaar     return save;
782631337658SMarcel Moolenaar }
782731337658SMarcel Moolenaar 
7828d1a0d267SMarcel Moolenaar /*
7829d1a0d267SMarcel Moolenaar  * Debugging function that dumps the current stack of open libxo constructs,
7830d1a0d267SMarcel Moolenaar  * suitable for calling from the debugger.
7831d1a0d267SMarcel Moolenaar  */
7832545ddfbeSMarcel Moolenaar void
7833545ddfbeSMarcel Moolenaar xo_dump_stack (xo_handle_t *xop)
7834545ddfbeSMarcel Moolenaar {
7835545ddfbeSMarcel Moolenaar     int i;
7836545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
7837545ddfbeSMarcel Moolenaar 
7838545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7839545ddfbeSMarcel Moolenaar 
7840545ddfbeSMarcel Moolenaar     fprintf(stderr, "Stack dump:\n");
7841545ddfbeSMarcel Moolenaar 
7842545ddfbeSMarcel Moolenaar     xsp = xop->xo_stack;
7843545ddfbeSMarcel Moolenaar     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
7844545ddfbeSMarcel Moolenaar 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
7845545ddfbeSMarcel Moolenaar 		i, xo_state_name(xsp->xs_state),
7846545ddfbeSMarcel Moolenaar 		xsp->xs_name ?: "--", xsp->xs_flags);
7847545ddfbeSMarcel Moolenaar     }
7848545ddfbeSMarcel Moolenaar }
7849545ddfbeSMarcel Moolenaar 
7850d1a0d267SMarcel Moolenaar /*
7851d1a0d267SMarcel Moolenaar  * Record the program name used for error messages
7852d1a0d267SMarcel Moolenaar  */
7853545ddfbeSMarcel Moolenaar void
7854545ddfbeSMarcel Moolenaar xo_set_program (const char *name)
7855545ddfbeSMarcel Moolenaar {
7856545ddfbeSMarcel Moolenaar     xo_program = name;
7857545ddfbeSMarcel Moolenaar }
7858545ddfbeSMarcel Moolenaar 
7859788ca347SMarcel Moolenaar void
786042ff34c3SPhil Shafer xo_set_version_h (xo_handle_t *xop, const char *version)
7861788ca347SMarcel Moolenaar {
7862788ca347SMarcel Moolenaar     xop = xo_default(xop);
7863788ca347SMarcel Moolenaar 
7864788ca347SMarcel Moolenaar     if (version == NULL || strchr(version, '"') != NULL)
7865788ca347SMarcel Moolenaar 	return;
7866788ca347SMarcel Moolenaar 
7867d1a0d267SMarcel Moolenaar     if (!xo_style_is_encoding(xop))
7868d1a0d267SMarcel Moolenaar 	return;
7869d1a0d267SMarcel Moolenaar 
7870788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
7871788ca347SMarcel Moolenaar     case XO_STYLE_XML:
7872788ca347SMarcel Moolenaar 	/* For XML, we record this as an attribute for the first tag */
78738a6eceffSPhil Shafer 	xo_attr_h(xop, "version", "%s", version);
7874788ca347SMarcel Moolenaar 	break;
7875788ca347SMarcel Moolenaar 
7876788ca347SMarcel Moolenaar     case XO_STYLE_JSON:
7877788ca347SMarcel Moolenaar 	/*
7878d1a0d267SMarcel Moolenaar 	 * For JSON, we record the version string in our handle, and emit
7879788ca347SMarcel Moolenaar 	 * it in xo_emit_top.
7880788ca347SMarcel Moolenaar 	 */
7881d1a0d267SMarcel Moolenaar 	xop->xo_version = xo_strndup(version, -1);
7882d1a0d267SMarcel Moolenaar 	break;
7883d1a0d267SMarcel Moolenaar 
7884d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7885d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version);
7886788ca347SMarcel Moolenaar 	break;
7887788ca347SMarcel Moolenaar     }
7888788ca347SMarcel Moolenaar }
7889788ca347SMarcel Moolenaar 
7890d1a0d267SMarcel Moolenaar /*
7891ee5cf116SPhil Shafer  * Set the version number for the API content being carried through
7892d1a0d267SMarcel Moolenaar  * the xo handle.
7893d1a0d267SMarcel Moolenaar  */
7894788ca347SMarcel Moolenaar void
7895788ca347SMarcel Moolenaar xo_set_version (const char *version)
7896788ca347SMarcel Moolenaar {
7897788ca347SMarcel Moolenaar     xo_set_version_h(NULL, version);
7898788ca347SMarcel Moolenaar }
7899788ca347SMarcel Moolenaar 
7900d1a0d267SMarcel Moolenaar /*
7901d1a0d267SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
7902d1a0d267SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
7903d1a0d267SMarcel Moolenaar  * XMLified content on standard output.
7904d1a0d267SMarcel Moolenaar  */
7905d1a0d267SMarcel Moolenaar void
7906d1a0d267SMarcel Moolenaar xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
7907d1a0d267SMarcel Moolenaar 		  const char *fmt, va_list vap)
790831337658SMarcel Moolenaar {
7909d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
791031337658SMarcel Moolenaar 
7911d1a0d267SMarcel Moolenaar     if (fmt == NULL)
7912d1a0d267SMarcel Moolenaar 	return;
791331337658SMarcel Moolenaar 
7914d1a0d267SMarcel Moolenaar     xo_open_marker_h(xop, "xo_emit_warn_hcv");
7915d1a0d267SMarcel Moolenaar     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
791631337658SMarcel Moolenaar 
7917d1a0d267SMarcel Moolenaar     if (xo_program)
7918d1a0d267SMarcel Moolenaar 	xo_emit("{wc:program}", xo_program);
791931337658SMarcel Moolenaar 
7920d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
7921d1a0d267SMarcel Moolenaar 	va_list ap;
7922d1a0d267SMarcel Moolenaar 	xo_handle_t temp;
792331337658SMarcel Moolenaar 
7924d1a0d267SMarcel Moolenaar 	bzero(&temp, sizeof(temp));
7925d1a0d267SMarcel Moolenaar 	temp.xo_style = XO_STYLE_TEXT;
7926d1a0d267SMarcel Moolenaar 	xo_buf_init(&temp.xo_data);
7927d1a0d267SMarcel Moolenaar 	xo_depth_check(&temp, XO_DEPTH);
792831337658SMarcel Moolenaar 
7929d1a0d267SMarcel Moolenaar 	va_copy(ap, vap);
7930d1a0d267SMarcel Moolenaar 	(void) xo_emit_hv(&temp, fmt, ap);
7931d1a0d267SMarcel Moolenaar 	va_end(ap);
793231337658SMarcel Moolenaar 
7933d1a0d267SMarcel Moolenaar 	xo_buffer_t *src = &temp.xo_data;
7934d1a0d267SMarcel Moolenaar 	xo_format_value(xop, "message", 7, src->xb_bufp,
7935d1a0d267SMarcel Moolenaar 			src->xb_curp - src->xb_bufp, NULL, 0, 0);
793631337658SMarcel Moolenaar 
7937d1a0d267SMarcel Moolenaar 	xo_free(temp.xo_stack);
7938d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(src);
793931337658SMarcel Moolenaar     }
794031337658SMarcel Moolenaar 
7941d1a0d267SMarcel Moolenaar     (void) xo_emit_hv(xop, fmt, vap);
794231337658SMarcel Moolenaar 
79438a6eceffSPhil Shafer     ssize_t len = strlen(fmt);
7944d1a0d267SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
7945d1a0d267SMarcel Moolenaar 	if (code > 0) {
7946d1a0d267SMarcel Moolenaar 	    const char *msg = strerror(code);
7947d1a0d267SMarcel Moolenaar 	    if (msg)
7948d1a0d267SMarcel Moolenaar 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
7949d1a0d267SMarcel Moolenaar 	}
7950d1a0d267SMarcel Moolenaar 	xo_emit("\n");
795131337658SMarcel Moolenaar     }
795231337658SMarcel Moolenaar 
7953d1a0d267SMarcel Moolenaar     xo_close_marker_h(xop, "xo_emit_warn_hcv");
7954d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
795531337658SMarcel Moolenaar }
795631337658SMarcel Moolenaar 
7957d1a0d267SMarcel Moolenaar void
7958d1a0d267SMarcel Moolenaar xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
7959d1a0d267SMarcel Moolenaar {
7960d1a0d267SMarcel Moolenaar     va_list vap;
796131337658SMarcel Moolenaar 
7962d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7963d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
7964d1a0d267SMarcel Moolenaar     va_end(vap);
796531337658SMarcel Moolenaar }
796631337658SMarcel Moolenaar 
7967d1a0d267SMarcel Moolenaar void
7968d1a0d267SMarcel Moolenaar xo_emit_warn_c (int code, const char *fmt, ...)
7969d1a0d267SMarcel Moolenaar {
7970d1a0d267SMarcel Moolenaar     va_list vap;
797131337658SMarcel Moolenaar 
7972d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7973d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7974d1a0d267SMarcel Moolenaar     va_end(vap);
7975d1a0d267SMarcel Moolenaar }
797631337658SMarcel Moolenaar 
7977d1a0d267SMarcel Moolenaar void
7978d1a0d267SMarcel Moolenaar xo_emit_warn (const char *fmt, ...)
7979d1a0d267SMarcel Moolenaar {
7980d1a0d267SMarcel Moolenaar     int code = errno;
7981d1a0d267SMarcel Moolenaar     va_list vap;
7982d1a0d267SMarcel Moolenaar 
7983d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7984d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7985d1a0d267SMarcel Moolenaar     va_end(vap);
7986d1a0d267SMarcel Moolenaar }
7987d1a0d267SMarcel Moolenaar 
7988d1a0d267SMarcel Moolenaar void
7989d1a0d267SMarcel Moolenaar xo_emit_warnx (const char *fmt, ...)
7990d1a0d267SMarcel Moolenaar {
7991d1a0d267SMarcel Moolenaar     va_list vap;
7992d1a0d267SMarcel Moolenaar 
7993d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7994d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
7995d1a0d267SMarcel Moolenaar     va_end(vap);
7996d1a0d267SMarcel Moolenaar }
7997d1a0d267SMarcel Moolenaar 
7998d1a0d267SMarcel Moolenaar void
7999d1a0d267SMarcel Moolenaar xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
8000d1a0d267SMarcel Moolenaar {
8001d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
800231337658SMarcel Moolenaar     xo_finish();
8003d1a0d267SMarcel Moolenaar     exit(eval);
800431337658SMarcel Moolenaar }
8005d1a0d267SMarcel Moolenaar 
8006d1a0d267SMarcel Moolenaar void
8007d1a0d267SMarcel Moolenaar xo_emit_err (int eval, const char *fmt, ...)
8008d1a0d267SMarcel Moolenaar {
8009d1a0d267SMarcel Moolenaar     int code = errno;
8010d1a0d267SMarcel Moolenaar     va_list vap;
8011d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8012d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, code, fmt, vap);
8013d1a0d267SMarcel Moolenaar     va_end(vap);
8014d1a0d267SMarcel Moolenaar     exit(eval);
8015d1a0d267SMarcel Moolenaar }
8016d1a0d267SMarcel Moolenaar 
8017d1a0d267SMarcel Moolenaar void
8018d1a0d267SMarcel Moolenaar xo_emit_errx (int eval, const char *fmt, ...)
8019d1a0d267SMarcel Moolenaar {
8020d1a0d267SMarcel Moolenaar     va_list vap;
8021d1a0d267SMarcel Moolenaar 
8022d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8023d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, -1, fmt, vap);
8024d1a0d267SMarcel Moolenaar     va_end(vap);
8025d1a0d267SMarcel Moolenaar     xo_finish();
8026d1a0d267SMarcel Moolenaar     exit(eval);
8027d1a0d267SMarcel Moolenaar }
8028d1a0d267SMarcel Moolenaar 
8029d1a0d267SMarcel Moolenaar void
8030d1a0d267SMarcel Moolenaar xo_emit_errc (int eval, int code, const char *fmt, ...)
8031d1a0d267SMarcel Moolenaar {
8032d1a0d267SMarcel Moolenaar     va_list vap;
8033d1a0d267SMarcel Moolenaar 
8034d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
8035d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8036d1a0d267SMarcel Moolenaar     va_end(vap);
8037d1a0d267SMarcel Moolenaar     xo_finish();
8038d1a0d267SMarcel Moolenaar     exit(eval);
8039d1a0d267SMarcel Moolenaar }
8040d1a0d267SMarcel Moolenaar 
8041d1a0d267SMarcel Moolenaar /*
8042d1a0d267SMarcel Moolenaar  * Get the opaque private pointer for an xo handle
8043d1a0d267SMarcel Moolenaar  */
8044d1a0d267SMarcel Moolenaar void *
8045d1a0d267SMarcel Moolenaar xo_get_private (xo_handle_t *xop)
8046d1a0d267SMarcel Moolenaar {
8047d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8048d1a0d267SMarcel Moolenaar     return xop->xo_private;
8049d1a0d267SMarcel Moolenaar }
8050d1a0d267SMarcel Moolenaar 
8051d1a0d267SMarcel Moolenaar /*
8052d1a0d267SMarcel Moolenaar  * Set the opaque private pointer for an xo handle.
8053d1a0d267SMarcel Moolenaar  */
8054d1a0d267SMarcel Moolenaar void
8055d1a0d267SMarcel Moolenaar xo_set_private (xo_handle_t *xop, void *opaque)
8056d1a0d267SMarcel Moolenaar {
8057d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8058d1a0d267SMarcel Moolenaar     xop->xo_private = opaque;
8059d1a0d267SMarcel Moolenaar }
8060d1a0d267SMarcel Moolenaar 
8061d1a0d267SMarcel Moolenaar /*
8062d1a0d267SMarcel Moolenaar  * Get the encoder function
8063d1a0d267SMarcel Moolenaar  */
8064d1a0d267SMarcel Moolenaar xo_encoder_func_t
8065d1a0d267SMarcel Moolenaar xo_get_encoder (xo_handle_t *xop)
8066d1a0d267SMarcel Moolenaar {
8067d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8068d1a0d267SMarcel Moolenaar     return xop->xo_encoder;
8069d1a0d267SMarcel Moolenaar }
8070d1a0d267SMarcel Moolenaar 
8071d1a0d267SMarcel Moolenaar /*
8072d1a0d267SMarcel Moolenaar  * Record an encoder callback function in an xo handle.
8073d1a0d267SMarcel Moolenaar  */
8074d1a0d267SMarcel Moolenaar void
8075d1a0d267SMarcel Moolenaar xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8076d1a0d267SMarcel Moolenaar {
8077d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
8078d1a0d267SMarcel Moolenaar 
8079d1a0d267SMarcel Moolenaar     xop->xo_style = XO_STYLE_ENCODER;
8080d1a0d267SMarcel Moolenaar     xop->xo_encoder = encoder;
8081d1a0d267SMarcel Moolenaar }
8082