xref: /freebsd/contrib/libxo/libxo/libxo.c (revision a321cc5d)
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:
22d1a0d267SMarcel Moolenaar  * - xo_do_emit() -- the central function of the library
23d1a0d267SMarcel Moolenaar  * - xo_do_format_field() -- handles formatting a single field
24d1a0d267SMarcel Moolenaar  * - xo_transiton() -- the state machine that keeps things sane
25d1a0d267SMarcel Moolenaar  * and of course the "xo_handle_t" data structure, which carries all
26d1a0d267SMarcel Moolenaar  * configuration and state.
2731337658SMarcel Moolenaar  */
2831337658SMarcel Moolenaar 
2931337658SMarcel Moolenaar #include <stdio.h>
3031337658SMarcel Moolenaar #include <stdlib.h>
3131337658SMarcel Moolenaar #include <stdint.h>
32545ddfbeSMarcel Moolenaar #include <unistd.h>
3331337658SMarcel Moolenaar #include <stddef.h>
3431337658SMarcel Moolenaar #include <wchar.h>
3531337658SMarcel Moolenaar #include <locale.h>
3631337658SMarcel Moolenaar #include <sys/types.h>
3731337658SMarcel Moolenaar #include <stdarg.h>
3831337658SMarcel Moolenaar #include <string.h>
3931337658SMarcel Moolenaar #include <errno.h>
4031337658SMarcel Moolenaar #include <limits.h>
4131337658SMarcel Moolenaar #include <ctype.h>
4231337658SMarcel Moolenaar #include <wctype.h>
4331337658SMarcel Moolenaar #include <getopt.h>
4431337658SMarcel Moolenaar 
45d1a0d267SMarcel Moolenaar #include "xo_config.h"
4631337658SMarcel Moolenaar #include "xo.h"
47d1a0d267SMarcel Moolenaar #include "xo_encoder.h"
48d1a0d267SMarcel Moolenaar #include "xo_buf.h"
49d1a0d267SMarcel Moolenaar 
50d1a0d267SMarcel Moolenaar /*
51d1a0d267SMarcel Moolenaar  * We ask wcwidth() to do an impossible job, really.  It's supposed to
52d1a0d267SMarcel Moolenaar  * need to tell us the number of columns consumed to display a unicode
53d1a0d267SMarcel Moolenaar  * character.  It returns that number without any sort of context, but
54d1a0d267SMarcel Moolenaar  * we know they are characters whose glyph differs based on placement
55d1a0d267SMarcel Moolenaar  * (end of word, middle of word, etc) and many that affect characters
56d1a0d267SMarcel Moolenaar  * previously emitted.  Without content, it can't hope to tell us.
57d1a0d267SMarcel Moolenaar  * But it's the only standard tool we've got, so we use it.  We would
58d1a0d267SMarcel Moolenaar  * use wcswidth() but it typically just loops thru adding the results
59d1a0d267SMarcel Moolenaar  * of wcwidth() calls in an entirely unhelpful way.
60d1a0d267SMarcel Moolenaar  *
61d1a0d267SMarcel Moolenaar  * Even then, there are many poor implementations (macosx), so we have
62d1a0d267SMarcel Moolenaar  * to carry our own.  We could have configure.ac test this (with
63d1a0d267SMarcel Moolenaar  * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
64d1a0d267SMarcel Moolenaar  * to run a binary, which breaks cross-compilation.  Hmm... I could
65d1a0d267SMarcel Moolenaar  * run this test at init time and make a warning for our dear user.
66d1a0d267SMarcel Moolenaar  *
67d1a0d267SMarcel Moolenaar  * Anyhow, it remains a best-effort sort of thing.  And it's all made
68d1a0d267SMarcel Moolenaar  * more hopeless because we assume the display code doing the rendering is
69d1a0d267SMarcel Moolenaar  * playing by the same rules we are.  If it display 0x200d as a square
70d1a0d267SMarcel Moolenaar  * box or a funky question mark, the output will be hosed.
71d1a0d267SMarcel Moolenaar  */
72d1a0d267SMarcel Moolenaar #ifdef LIBXO_WCWIDTH
73d1a0d267SMarcel Moolenaar #include "xo_wcwidth.h"
74d1a0d267SMarcel Moolenaar #else /* LIBXO_WCWIDTH */
75d1a0d267SMarcel Moolenaar #define xo_wcwidth(_x) wcwidth(_x)
76d1a0d267SMarcel Moolenaar #endif /* LIBXO_WCWIDTH */
7731337658SMarcel Moolenaar 
78545ddfbeSMarcel Moolenaar #ifdef HAVE_STDIO_EXT_H
79545ddfbeSMarcel Moolenaar #include <stdio_ext.h>
80545ddfbeSMarcel Moolenaar #endif /* HAVE_STDIO_EXT_H */
81545ddfbeSMarcel Moolenaar 
82d1a0d267SMarcel Moolenaar /*
83d1a0d267SMarcel Moolenaar  * humanize_number is a great function, unless you don't have it.  So
84d1a0d267SMarcel Moolenaar  * we carry one in our pocket.
85d1a0d267SMarcel Moolenaar  */
86d1a0d267SMarcel Moolenaar #ifdef HAVE_HUMANIZE_NUMBER
87d1a0d267SMarcel Moolenaar #include <libutil.h>
88d1a0d267SMarcel Moolenaar #define xo_humanize_number humanize_number
89d1a0d267SMarcel Moolenaar #else /* HAVE_HUMANIZE_NUMBER */
90d1a0d267SMarcel Moolenaar #include "xo_humanize.h"
91d1a0d267SMarcel Moolenaar #endif /* HAVE_HUMANIZE_NUMBER */
92d1a0d267SMarcel Moolenaar 
93d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
94d1a0d267SMarcel Moolenaar #include <libintl.h>
95d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
96d1a0d267SMarcel Moolenaar 
97d1a0d267SMarcel Moolenaar /*
98d1a0d267SMarcel Moolenaar  * Three styles of specifying thread-local variables are supported.
99d1a0d267SMarcel Moolenaar  * configure.ac has the brains to run each possibility thru the
100d1a0d267SMarcel Moolenaar  * compiler and see what works; we are left to define the THREAD_LOCAL
101d1a0d267SMarcel Moolenaar  * macro to the right value.  Most toolchains (clang, gcc) use
102d1a0d267SMarcel Moolenaar  * "before", but some (borland) use "after" and I've heard of some
103d1a0d267SMarcel Moolenaar  * (ms) that use __declspec.  Any others out there?
104d1a0d267SMarcel Moolenaar  */
105d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_before 1
106d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_after 2
107d1a0d267SMarcel Moolenaar #define THREAD_LOCAL_declspec 3
108d1a0d267SMarcel Moolenaar 
109d1a0d267SMarcel Moolenaar #ifndef HAVE_THREAD_LOCAL
110d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x
111d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
112d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __thread _x
113d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
114d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) _x __thread
115d1a0d267SMarcel Moolenaar #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
116d1a0d267SMarcel Moolenaar #define THREAD_LOCAL(_x) __declspec(_x)
117d1a0d267SMarcel Moolenaar #else
118d1a0d267SMarcel Moolenaar #error unknown thread-local setting
119d1a0d267SMarcel Moolenaar #endif /* HAVE_THREADS_H */
120d1a0d267SMarcel Moolenaar 
12131337658SMarcel Moolenaar const char xo_version[] = LIBXO_VERSION;
12231337658SMarcel Moolenaar const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
12331337658SMarcel Moolenaar 
12431337658SMarcel Moolenaar #ifndef UNUSED
12531337658SMarcel Moolenaar #define UNUSED __attribute__ ((__unused__))
12631337658SMarcel Moolenaar #endif /* UNUSED */
12731337658SMarcel Moolenaar 
12831337658SMarcel Moolenaar #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
129d1a0d267SMarcel Moolenaar #define XO_DEPTH	128	 /* Default stack depth */
13031337658SMarcel Moolenaar #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
13131337658SMarcel Moolenaar 
13231337658SMarcel Moolenaar #define XO_FAILURE_NAME	"failure"
13331337658SMarcel Moolenaar 
13431337658SMarcel Moolenaar /* Flags for the stack frame */
13531337658SMarcel Moolenaar typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
13631337658SMarcel Moolenaar #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
13731337658SMarcel Moolenaar #define XSF_LIST	(1<<1)	/* Frame is a list */
13831337658SMarcel Moolenaar #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
13931337658SMarcel Moolenaar #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
14031337658SMarcel Moolenaar 
141545ddfbeSMarcel Moolenaar #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
142545ddfbeSMarcel Moolenaar #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
143545ddfbeSMarcel Moolenaar #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
144545ddfbeSMarcel Moolenaar #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
145545ddfbeSMarcel Moolenaar 
146545ddfbeSMarcel Moolenaar /* These are the flags we propagate between markers and their parents */
147545ddfbeSMarcel Moolenaar #define XSF_MARKER_FLAGS \
148545ddfbeSMarcel Moolenaar  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
149545ddfbeSMarcel Moolenaar 
150545ddfbeSMarcel Moolenaar /*
151d1a0d267SMarcel Moolenaar  * A word about states: We use a finite state machine (FMS) approach
152d1a0d267SMarcel Moolenaar  * to help remove fragility from the caller's code.  Instead of
153d1a0d267SMarcel Moolenaar  * requiring a specific order of calls, we'll allow the caller more
154545ddfbeSMarcel Moolenaar  * flexibility and make the library responsible for recovering from
155d1a0d267SMarcel Moolenaar  * missed steps.  The goal is that the library should not be capable
156d1a0d267SMarcel Moolenaar  * of emitting invalid xml or json, but the developer shouldn't need
157545ddfbeSMarcel Moolenaar  * to know or understand all the details about these encodings.
158545ddfbeSMarcel Moolenaar  *
159d1a0d267SMarcel Moolenaar  * You can think of states as either states or events, since they
160545ddfbeSMarcel Moolenaar  * function rather like both.  None of the XO_CLOSE_* events will
161d1a0d267SMarcel Moolenaar  * persist as states, since the matching stack frame will be popped.
162545ddfbeSMarcel Moolenaar  * Same is true of XSS_EMIT, which is an event that asks us to
163545ddfbeSMarcel Moolenaar  * prep for emitting output fields.
164545ddfbeSMarcel Moolenaar  */
165545ddfbeSMarcel Moolenaar 
166545ddfbeSMarcel Moolenaar /* Stack frame states */
167545ddfbeSMarcel Moolenaar typedef unsigned xo_state_t;
168545ddfbeSMarcel Moolenaar #define XSS_INIT		0      	/* Initial stack state */
169545ddfbeSMarcel Moolenaar #define XSS_OPEN_CONTAINER	1
170545ddfbeSMarcel Moolenaar #define XSS_CLOSE_CONTAINER	2
171545ddfbeSMarcel Moolenaar #define XSS_OPEN_LIST		3
172545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LIST		4
173545ddfbeSMarcel Moolenaar #define XSS_OPEN_INSTANCE	5
174545ddfbeSMarcel Moolenaar #define XSS_CLOSE_INSTANCE	6
175545ddfbeSMarcel Moolenaar #define XSS_OPEN_LEAF_LIST	7
176545ddfbeSMarcel Moolenaar #define XSS_CLOSE_LEAF_LIST	8
177545ddfbeSMarcel Moolenaar #define XSS_DISCARDING		9	/* Discarding data until recovered */
178545ddfbeSMarcel Moolenaar #define XSS_MARKER		10	/* xo_open_marker's marker */
179545ddfbeSMarcel Moolenaar #define XSS_EMIT		11	/* xo_emit has a leaf field */
180545ddfbeSMarcel Moolenaar #define XSS_EMIT_LEAF_LIST	12	/* xo_emit has a leaf-list ({l:}) */
181545ddfbeSMarcel Moolenaar #define XSS_FINISH		13	/* xo_finish was called */
182545ddfbeSMarcel Moolenaar 
183545ddfbeSMarcel Moolenaar #define XSS_MAX			13
184545ddfbeSMarcel Moolenaar 
185545ddfbeSMarcel Moolenaar #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
186545ddfbeSMarcel Moolenaar 
18731337658SMarcel Moolenaar /*
18831337658SMarcel Moolenaar  * xo_stack_t: As we open and close containers and levels, we
18931337658SMarcel Moolenaar  * create a stack of frames to track them.  This is needed for
19031337658SMarcel Moolenaar  * XOF_WARN and XOF_XPATH.
19131337658SMarcel Moolenaar  */
19231337658SMarcel Moolenaar typedef struct xo_stack_s {
19331337658SMarcel Moolenaar     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
194545ddfbeSMarcel Moolenaar     xo_state_t xs_state;	/* State for this stack frame */
19531337658SMarcel Moolenaar     char *xs_name;		/* Name (for XPath value) */
19631337658SMarcel Moolenaar     char *xs_keys;		/* XPath predicate for any key fields */
19731337658SMarcel Moolenaar } xo_stack_t;
19831337658SMarcel Moolenaar 
199d1a0d267SMarcel Moolenaar /*
200d1a0d267SMarcel Moolenaar  * libxo supports colors and effects, for those who like them.
201d1a0d267SMarcel Moolenaar  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
202d1a0d267SMarcel Moolenaar  * ("effects") are bits since we need to maintain state.
203d1a0d267SMarcel Moolenaar  */
204788ca347SMarcel Moolenaar #define XO_COL_DEFAULT		0
205788ca347SMarcel Moolenaar #define XO_COL_BLACK		1
206788ca347SMarcel Moolenaar #define XO_COL_RED		2
207788ca347SMarcel Moolenaar #define XO_COL_GREEN		3
208788ca347SMarcel Moolenaar #define XO_COL_YELLOW		4
209788ca347SMarcel Moolenaar #define XO_COL_BLUE		5
210788ca347SMarcel Moolenaar #define XO_COL_MAGENTA		6
211788ca347SMarcel Moolenaar #define XO_COL_CYAN		7
212788ca347SMarcel Moolenaar #define XO_COL_WHITE		8
213788ca347SMarcel Moolenaar 
214788ca347SMarcel Moolenaar #define XO_NUM_COLORS		9
215788ca347SMarcel Moolenaar 
216788ca347SMarcel Moolenaar /*
217788ca347SMarcel Moolenaar  * Yes, there's no blink.  We're civilized.  We like users.  Blink
218788ca347SMarcel Moolenaar  * isn't something one does to someone you like.  Friends don't let
219788ca347SMarcel Moolenaar  * friends use blink.  On friends.  You know what I mean.  Blink is
220788ca347SMarcel Moolenaar  * like, well, it's like bursting into show tunes at a funeral.  It's
221788ca347SMarcel Moolenaar  * just not done.  Not something anyone wants.  And on those rare
222d1a0d267SMarcel Moolenaar  * instances where it might actually be appropriate, it's still wrong,
223d1a0d267SMarcel Moolenaar  * since it's likely done by the wrong person for the wrong reason.
224d1a0d267SMarcel Moolenaar  * Just like blink.  And if I implemented blink, I'd be like a funeral
225788ca347SMarcel Moolenaar  * director who adds "Would you like us to burst into show tunes?" on
226d1a0d267SMarcel Moolenaar  * the list of questions asked while making funeral arrangements.
227788ca347SMarcel Moolenaar  * It's formalizing wrongness in the wrong way.  And we're just too
228788ca347SMarcel Moolenaar  * civilized to do that.  Hhhmph!
229788ca347SMarcel Moolenaar  */
230788ca347SMarcel Moolenaar #define XO_EFF_RESET		(1<<0)
231788ca347SMarcel Moolenaar #define XO_EFF_NORMAL		(1<<1)
232788ca347SMarcel Moolenaar #define XO_EFF_BOLD		(1<<2)
233788ca347SMarcel Moolenaar #define XO_EFF_UNDERLINE	(1<<3)
234788ca347SMarcel Moolenaar #define XO_EFF_INVERSE		(1<<4)
235788ca347SMarcel Moolenaar 
236d1a0d267SMarcel Moolenaar #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
237788ca347SMarcel Moolenaar 
238788ca347SMarcel Moolenaar typedef uint8_t xo_effect_t;
239788ca347SMarcel Moolenaar typedef uint8_t xo_color_t;
240788ca347SMarcel Moolenaar typedef struct xo_colors_s {
241788ca347SMarcel Moolenaar     xo_effect_t xoc_effects;	/* Current effect set */
242788ca347SMarcel Moolenaar     xo_color_t xoc_col_fg;	/* Foreground color */
243788ca347SMarcel Moolenaar     xo_color_t xoc_col_bg;	/* Background color */
244788ca347SMarcel Moolenaar } xo_colors_t;
245788ca347SMarcel Moolenaar 
24631337658SMarcel Moolenaar /*
24731337658SMarcel Moolenaar  * xo_handle_t: this is the principle data structure for libxo.
248d1a0d267SMarcel Moolenaar  * It's used as a store for state, options, content, and all manor
249d1a0d267SMarcel Moolenaar  * of other information.
25031337658SMarcel Moolenaar  */
25131337658SMarcel Moolenaar struct xo_handle_s {
252d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
253d1a0d267SMarcel Moolenaar     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
254d1a0d267SMarcel Moolenaar     xo_style_t xo_style;	/* XO_STYLE_* value */
25531337658SMarcel Moolenaar     unsigned short xo_indent;	/* Indent level (if pretty) */
25631337658SMarcel Moolenaar     unsigned short xo_indent_by; /* Indent amount (tab stop) */
25731337658SMarcel Moolenaar     xo_write_func_t xo_write;	/* Write callback */
258a0f704ffSMarcel Moolenaar     xo_close_func_t xo_close;	/* Close callback */
259545ddfbeSMarcel Moolenaar     xo_flush_func_t xo_flush;	/* Flush callback */
26031337658SMarcel Moolenaar     xo_formatter_t xo_formatter; /* Custom formating function */
26131337658SMarcel Moolenaar     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
26231337658SMarcel Moolenaar     void *xo_opaque;		/* Opaque data for write function */
26331337658SMarcel Moolenaar     xo_buffer_t xo_data;	/* Output data */
26431337658SMarcel Moolenaar     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
26531337658SMarcel Moolenaar     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
26631337658SMarcel Moolenaar     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
26731337658SMarcel Moolenaar     xo_stack_t *xo_stack;	/* Stack pointer */
26831337658SMarcel Moolenaar     int xo_depth;		/* Depth of stack */
26931337658SMarcel Moolenaar     int xo_stack_size;		/* Size of the stack */
27031337658SMarcel Moolenaar     xo_info_t *xo_info;		/* Info fields for all elements */
27131337658SMarcel Moolenaar     int xo_info_count;		/* Number of info entries */
27231337658SMarcel Moolenaar     va_list xo_vap;		/* Variable arguments (stdargs) */
27331337658SMarcel Moolenaar     char *xo_leading_xpath;	/* A leading XPath expression */
27431337658SMarcel Moolenaar     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
27531337658SMarcel Moolenaar     unsigned xo_anchor_offset;	/* Start of anchored text */
27631337658SMarcel Moolenaar     unsigned xo_anchor_columns;	/* Number of columns since the start anchor */
27731337658SMarcel Moolenaar     int xo_anchor_min_width;	/* Desired width of anchored text */
27831337658SMarcel Moolenaar     unsigned xo_units_offset;	/* Start of units insertion point */
27931337658SMarcel Moolenaar     unsigned xo_columns;	/* Columns emitted during this xo_emit call */
280788ca347SMarcel Moolenaar     uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
281788ca347SMarcel Moolenaar     uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
282788ca347SMarcel Moolenaar     xo_colors_t xo_colors;	/* Current color and effect values */
283788ca347SMarcel Moolenaar     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
284788ca347SMarcel Moolenaar     char *xo_version;		/* Version string */
285d1a0d267SMarcel Moolenaar     int xo_errno;		/* Saved errno for "%m" */
286d1a0d267SMarcel Moolenaar     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
287d1a0d267SMarcel Moolenaar     xo_encoder_func_t xo_encoder; /* Encoding function */
288d1a0d267SMarcel Moolenaar     void *xo_private;		/* Private data for external encoders */
28931337658SMarcel Moolenaar };
29031337658SMarcel Moolenaar 
291d1a0d267SMarcel Moolenaar /* Flag operations */
292d1a0d267SMarcel Moolenaar #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
293d1a0d267SMarcel Moolenaar #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
294d1a0d267SMarcel Moolenaar #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
295d1a0d267SMarcel Moolenaar 
296d1a0d267SMarcel Moolenaar #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
297d1a0d267SMarcel Moolenaar #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
298d1a0d267SMarcel Moolenaar #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
299d1a0d267SMarcel Moolenaar 
300d1a0d267SMarcel Moolenaar #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
301d1a0d267SMarcel Moolenaar #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
302d1a0d267SMarcel Moolenaar #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
303d1a0d267SMarcel Moolenaar 
304d1a0d267SMarcel Moolenaar /* Internal flags */
305d1a0d267SMarcel Moolenaar #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
306d1a0d267SMarcel Moolenaar #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
307d1a0d267SMarcel Moolenaar #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
308d1a0d267SMarcel Moolenaar #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
309d1a0d267SMarcel Moolenaar 
310d1a0d267SMarcel Moolenaar #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
311d1a0d267SMarcel Moolenaar #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
312d1a0d267SMarcel Moolenaar 
31331337658SMarcel Moolenaar /* Flags for formatting functions */
31431337658SMarcel Moolenaar typedef unsigned long xo_xff_flags_t;
31531337658SMarcel Moolenaar #define XFF_COLON	(1<<0)	/* Append a ":" */
31631337658SMarcel Moolenaar #define XFF_COMMA	(1<<1)	/* Append a "," iff there's more output */
31731337658SMarcel Moolenaar #define XFF_WS		(1<<2)	/* Append a blank */
318d1a0d267SMarcel Moolenaar #define XFF_ENCODE_ONLY	(1<<3)	/* Only emit for encoding styles (XML, JSON) */
31931337658SMarcel Moolenaar 
32031337658SMarcel Moolenaar #define XFF_QUOTE	(1<<4)	/* Force quotes */
32131337658SMarcel Moolenaar #define XFF_NOQUOTE	(1<<5)	/* Force no quotes */
322d1a0d267SMarcel Moolenaar #define XFF_DISPLAY_ONLY (1<<6)	/* Only emit for display styles (text, html) */
32331337658SMarcel Moolenaar #define XFF_KEY		(1<<7)	/* Field is a key (for XPath) */
32431337658SMarcel Moolenaar 
32531337658SMarcel Moolenaar #define XFF_XML		(1<<8)	/* Force XML encoding style (for XPath) */
32631337658SMarcel Moolenaar #define XFF_ATTR	(1<<9)	/* Escape value using attribute rules (XML) */
32731337658SMarcel Moolenaar #define XFF_BLANK_LINE	(1<<10)	/* Emit a blank line */
32831337658SMarcel Moolenaar #define XFF_NO_OUTPUT	(1<<11)	/* Do not make any output */
32931337658SMarcel Moolenaar 
33031337658SMarcel Moolenaar #define XFF_TRIM_WS	(1<<12)	/* Trim whitespace off encoded values */
33131337658SMarcel Moolenaar #define XFF_LEAF_LIST	(1<<13)	/* A leaf-list (list of values) */
33231337658SMarcel Moolenaar #define XFF_UNESCAPE	(1<<14)	/* Need to printf-style unescape the value */
333d1a0d267SMarcel Moolenaar #define XFF_HUMANIZE	(1<<15)	/* Humanize the value (for display styles) */
334d1a0d267SMarcel Moolenaar 
335d1a0d267SMarcel Moolenaar #define XFF_HN_SPACE	(1<<16)	/* Humanize: put space before suffix */
336d1a0d267SMarcel Moolenaar #define XFF_HN_DECIMAL	(1<<17)	/* Humanize: add one decimal place if <10 */
337d1a0d267SMarcel Moolenaar #define XFF_HN_1000	(1<<18)	/* Humanize: use 1000, not 1024 */
338d1a0d267SMarcel Moolenaar #define XFF_GT_FIELD	(1<<19) /* Call gettext() on a field */
339d1a0d267SMarcel Moolenaar 
340d1a0d267SMarcel Moolenaar #define XFF_GT_PLURAL	(1<<20)	/* Call dngettext to find plural form */
341d1a0d267SMarcel Moolenaar 
342d1a0d267SMarcel Moolenaar /* Flags to turn off when we don't want i18n processing */
343d1a0d267SMarcel Moolenaar #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
34431337658SMarcel Moolenaar 
34531337658SMarcel Moolenaar /*
34631337658SMarcel Moolenaar  * Normal printf has width and precision, which for strings operate as
34731337658SMarcel Moolenaar  * min and max number of columns.  But this depends on the idea that
34831337658SMarcel Moolenaar  * one byte means one column, which UTF-8 and multi-byte characters
34931337658SMarcel Moolenaar  * pitches on its ear.  It may take 40 bytes of data to populate 14
35031337658SMarcel Moolenaar  * columns, but we can't go off looking at 40 bytes of data without the
35131337658SMarcel Moolenaar  * caller's permission for fear/knowledge that we'll generate core files.
35231337658SMarcel Moolenaar  *
35331337658SMarcel Moolenaar  * So we make three values, distinguishing between "max column" and
35431337658SMarcel Moolenaar  * "number of bytes that we will inspect inspect safely" We call the
35531337658SMarcel Moolenaar  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
35631337658SMarcel Moolenaar  *
35731337658SMarcel Moolenaar  * Under the "first do no harm" theory, we default "max" to "size".
35831337658SMarcel Moolenaar  * This is a reasonable assumption for folks that don't grok the
35931337658SMarcel Moolenaar  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
36031337658SMarcel Moolenaar  * be evil.
36131337658SMarcel Moolenaar  *
36231337658SMarcel Moolenaar  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
36331337658SMarcel Moolenaar  * columns of output, but will never look at more than 14 bytes of the
36431337658SMarcel Moolenaar  * input buffer.  This is mostly compatible with printf and caller's
36531337658SMarcel Moolenaar  * expectations.
36631337658SMarcel Moolenaar  *
36731337658SMarcel Moolenaar  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
36831337658SMarcel Moolenaar  * many bytes (or until a NUL is seen) are needed to fill 14 columns
36931337658SMarcel Moolenaar  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
37031337658SMarcel Moolenaar  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
37131337658SMarcel Moolenaar  * of output.
37231337658SMarcel Moolenaar  *
37331337658SMarcel Moolenaar  * It's fairly amazing how a good idea (handle all languages of the
37431337658SMarcel Moolenaar  * world) blows such a big hole in the bottom of the fairly weak boat
37531337658SMarcel Moolenaar  * that is C string handling.  The simplicity and completenesss are
37631337658SMarcel Moolenaar  * sunk in ways we haven't even begun to understand.
37731337658SMarcel Moolenaar  */
37831337658SMarcel Moolenaar #define XF_WIDTH_MIN	0	/* Minimal width */
37931337658SMarcel Moolenaar #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
38031337658SMarcel Moolenaar #define XF_WIDTH_MAX	2	/* Maximum width */
38131337658SMarcel Moolenaar #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
38231337658SMarcel Moolenaar 
38331337658SMarcel Moolenaar /* Input and output string encodings */
38431337658SMarcel Moolenaar #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
38531337658SMarcel Moolenaar #define XF_ENC_UTF8	2	/* UTF-8 */
38631337658SMarcel Moolenaar #define XF_ENC_LOCALE	3	/* Current locale */
38731337658SMarcel Moolenaar 
38831337658SMarcel Moolenaar /*
38931337658SMarcel Moolenaar  * A place to parse printf-style format flags for each field
39031337658SMarcel Moolenaar  */
39131337658SMarcel Moolenaar typedef struct xo_format_s {
39231337658SMarcel Moolenaar     unsigned char xf_fc;	/* Format character */
39331337658SMarcel Moolenaar     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
39431337658SMarcel Moolenaar     unsigned char xf_skip;	/* Skip this field */
39531337658SMarcel Moolenaar     unsigned char xf_lflag;	/* 'l' (long) */
39631337658SMarcel Moolenaar     unsigned char xf_hflag;;	/* 'h' (half) */
39731337658SMarcel Moolenaar     unsigned char xf_jflag;	/* 'j' (intmax_t) */
39831337658SMarcel Moolenaar     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
39931337658SMarcel Moolenaar     unsigned char xf_zflag;	/* 'z' (size_t) */
40031337658SMarcel Moolenaar     unsigned char xf_qflag;	/* 'q' (quad_t) */
40131337658SMarcel Moolenaar     unsigned char xf_seen_minus; /* Seen a minus */
40231337658SMarcel Moolenaar     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
40331337658SMarcel Moolenaar     unsigned xf_dots;		/* Seen one or more '.'s */
40431337658SMarcel Moolenaar     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
40531337658SMarcel Moolenaar     unsigned xf_stars;		/* Seen one or more '*'s */
40631337658SMarcel Moolenaar     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
40731337658SMarcel Moolenaar } xo_format_t;
40831337658SMarcel Moolenaar 
40931337658SMarcel Moolenaar /*
410d1a0d267SMarcel Moolenaar  * This structure represents the parsed field information, suitable for
411d1a0d267SMarcel Moolenaar  * processing by xo_do_emit and anything else that needs to parse fields.
412d1a0d267SMarcel Moolenaar  * Note that all pointers point to the main format string.
413d1a0d267SMarcel Moolenaar  *
414d1a0d267SMarcel Moolenaar  * XXX This is a first step toward compilable or cachable format
415d1a0d267SMarcel Moolenaar  * strings.  We can also cache the results of dgettext when no format
416d1a0d267SMarcel Moolenaar  * is used, assuming the 'p' modifier has _not_ been set.
41731337658SMarcel Moolenaar  */
418d1a0d267SMarcel Moolenaar typedef struct xo_field_info_s {
419d1a0d267SMarcel Moolenaar     xo_xff_flags_t xfi_flags;	/* Flags for this field */
420d1a0d267SMarcel Moolenaar     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
421d1a0d267SMarcel Moolenaar     const char *xfi_start;   /* Start of field in the format string */
422d1a0d267SMarcel Moolenaar     const char *xfi_content;	/* Field's content */
423d1a0d267SMarcel Moolenaar     const char *xfi_format;	/* Field's Format */
424d1a0d267SMarcel Moolenaar     const char *xfi_encoding;	/* Field's encoding format */
425d1a0d267SMarcel Moolenaar     const char *xfi_next;	/* Next character in format string */
426d1a0d267SMarcel Moolenaar     unsigned xfi_len;		/* Length of field */
427d1a0d267SMarcel Moolenaar     unsigned xfi_clen;		/* Content length */
428d1a0d267SMarcel Moolenaar     unsigned xfi_flen;		/* Format length */
429d1a0d267SMarcel Moolenaar     unsigned xfi_elen;		/* Encoding length */
430d1a0d267SMarcel Moolenaar     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
431d1a0d267SMarcel Moolenaar     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
432d1a0d267SMarcel Moolenaar } xo_field_info_t;
433d1a0d267SMarcel Moolenaar 
434d1a0d267SMarcel Moolenaar /*
435d1a0d267SMarcel Moolenaar  * We keep a 'default' handle to allow callers to avoid having to
436d1a0d267SMarcel Moolenaar  * allocate one.  Passing NULL to any of our functions will use
437d1a0d267SMarcel Moolenaar  * this default handle.  Most functions have a variant that doesn't
438d1a0d267SMarcel Moolenaar  * require a handle at all, since most output is to stdout, which
439d1a0d267SMarcel Moolenaar  * the default handle handles handily.
440d1a0d267SMarcel Moolenaar  */
441d1a0d267SMarcel Moolenaar static THREAD_LOCAL(xo_handle_t) xo_default_handle;
442d1a0d267SMarcel Moolenaar static THREAD_LOCAL(int) xo_default_inited;
44331337658SMarcel Moolenaar static int xo_locale_inited;
444545ddfbeSMarcel Moolenaar static const char *xo_program;
44531337658SMarcel Moolenaar 
44631337658SMarcel Moolenaar /*
44731337658SMarcel Moolenaar  * To allow libxo to be used in diverse environment, we allow the
44831337658SMarcel Moolenaar  * caller to give callbacks for memory allocation.
44931337658SMarcel Moolenaar  */
450d1a0d267SMarcel Moolenaar xo_realloc_func_t xo_realloc = realloc;
451d1a0d267SMarcel Moolenaar xo_free_func_t xo_free = free;
45231337658SMarcel Moolenaar 
45331337658SMarcel Moolenaar /* Forward declarations */
45431337658SMarcel Moolenaar static void
45531337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...);
45631337658SMarcel Moolenaar 
457545ddfbeSMarcel Moolenaar static int
458545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
459545ddfbeSMarcel Moolenaar 	       xo_state_t new_state);
460545ddfbeSMarcel Moolenaar 
46131337658SMarcel Moolenaar static void
46231337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
46331337658SMarcel Moolenaar 		   const char *name, int nlen,
46431337658SMarcel Moolenaar 		   const char *value, int vlen,
46531337658SMarcel Moolenaar 		   const char *encoding, int elen);
46631337658SMarcel Moolenaar 
46731337658SMarcel Moolenaar static void
46831337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop);
46931337658SMarcel Moolenaar 
47031337658SMarcel Moolenaar /*
471788ca347SMarcel Moolenaar  * xo_style is used to retrieve the current style.  When we're built
472788ca347SMarcel Moolenaar  * for "text only" mode, we use this function to drive the removal
473788ca347SMarcel Moolenaar  * of most of the code in libxo.  We return a constant and the compiler
474788ca347SMarcel Moolenaar  * happily removes the non-text code that is not longer executed.  This
475788ca347SMarcel Moolenaar  * trims our code nicely without needing to trampel perfectly readable
476788ca347SMarcel Moolenaar  * code with ifdefs.
477788ca347SMarcel Moolenaar  */
478d1a0d267SMarcel Moolenaar static inline xo_style_t
479788ca347SMarcel Moolenaar xo_style (xo_handle_t *xop UNUSED)
480788ca347SMarcel Moolenaar {
481788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
482788ca347SMarcel Moolenaar     return XO_STYLE_TEXT;
483788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
484788ca347SMarcel Moolenaar     return xop->xo_style;
485788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
486788ca347SMarcel Moolenaar }
487788ca347SMarcel Moolenaar 
488788ca347SMarcel Moolenaar /*
48931337658SMarcel Moolenaar  * Callback to write data to a FILE pointer
49031337658SMarcel Moolenaar  */
49131337658SMarcel Moolenaar static int
49231337658SMarcel Moolenaar xo_write_to_file (void *opaque, const char *data)
49331337658SMarcel Moolenaar {
49431337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
495545ddfbeSMarcel Moolenaar 
49631337658SMarcel Moolenaar     return fprintf(fp, "%s", data);
49731337658SMarcel Moolenaar }
49831337658SMarcel Moolenaar 
49931337658SMarcel Moolenaar /*
50031337658SMarcel Moolenaar  * Callback to close a file
50131337658SMarcel Moolenaar  */
50231337658SMarcel Moolenaar static void
50331337658SMarcel Moolenaar xo_close_file (void *opaque)
50431337658SMarcel Moolenaar {
50531337658SMarcel Moolenaar     FILE *fp = (FILE *) opaque;
506545ddfbeSMarcel Moolenaar 
50731337658SMarcel Moolenaar     fclose(fp);
50831337658SMarcel Moolenaar }
50931337658SMarcel Moolenaar 
51031337658SMarcel Moolenaar /*
511545ddfbeSMarcel Moolenaar  * Callback to flush a FILE pointer
512545ddfbeSMarcel Moolenaar  */
513545ddfbeSMarcel Moolenaar static int
514545ddfbeSMarcel Moolenaar xo_flush_file (void *opaque)
515545ddfbeSMarcel Moolenaar {
516545ddfbeSMarcel Moolenaar     FILE *fp = (FILE *) opaque;
517545ddfbeSMarcel Moolenaar 
518545ddfbeSMarcel Moolenaar     return fflush(fp);
519545ddfbeSMarcel Moolenaar }
520545ddfbeSMarcel Moolenaar 
521545ddfbeSMarcel Moolenaar /*
522d1a0d267SMarcel Moolenaar  * Use a rotating stock of buffers to make a printable string
52331337658SMarcel Moolenaar  */
524d1a0d267SMarcel Moolenaar #define XO_NUMBUFS 8
525d1a0d267SMarcel Moolenaar #define XO_SMBUFSZ 128
526d1a0d267SMarcel Moolenaar 
527d1a0d267SMarcel Moolenaar static const char *
528d1a0d267SMarcel Moolenaar xo_printable (const char *str)
52931337658SMarcel Moolenaar {
530d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
531d1a0d267SMarcel Moolenaar     static THREAD_LOCAL(int) bufnum = 0;
532d1a0d267SMarcel Moolenaar 
533d1a0d267SMarcel Moolenaar     if (str == NULL)
534d1a0d267SMarcel Moolenaar 	return "";
535d1a0d267SMarcel Moolenaar 
536d1a0d267SMarcel Moolenaar     if (++bufnum == XO_NUMBUFS)
537d1a0d267SMarcel Moolenaar 	bufnum = 0;
538d1a0d267SMarcel Moolenaar 
539d1a0d267SMarcel Moolenaar     char *res = bufset[bufnum], *cp, *ep;
540d1a0d267SMarcel Moolenaar 
541d1a0d267SMarcel Moolenaar     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
542d1a0d267SMarcel Moolenaar 	if (*str == '\n') {
543d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
544d1a0d267SMarcel Moolenaar 	    *cp = 'n';
545d1a0d267SMarcel Moolenaar 	} else if (*str == '\r') {
546d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
547d1a0d267SMarcel Moolenaar 	    *cp = 'r';
548d1a0d267SMarcel Moolenaar 	} else if (*str == '\"') {
549d1a0d267SMarcel Moolenaar 	    *cp++ = '\\';
550d1a0d267SMarcel Moolenaar 	    *cp = '"';
551d1a0d267SMarcel Moolenaar 	} else
552d1a0d267SMarcel Moolenaar 	    *cp = *str;
55331337658SMarcel Moolenaar     }
55431337658SMarcel Moolenaar 
555d1a0d267SMarcel Moolenaar     *cp = '\0';
556d1a0d267SMarcel Moolenaar     return res;
55731337658SMarcel Moolenaar }
55831337658SMarcel Moolenaar 
55931337658SMarcel Moolenaar static int
56031337658SMarcel Moolenaar xo_depth_check (xo_handle_t *xop, int depth)
56131337658SMarcel Moolenaar {
56231337658SMarcel Moolenaar     xo_stack_t *xsp;
56331337658SMarcel Moolenaar 
56431337658SMarcel Moolenaar     if (depth >= xop->xo_stack_size) {
565d1a0d267SMarcel Moolenaar 	depth += XO_DEPTH;	/* Extra room */
566d1a0d267SMarcel Moolenaar 
56731337658SMarcel Moolenaar 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
56831337658SMarcel Moolenaar 	if (xsp == NULL) {
56931337658SMarcel Moolenaar 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
570d1a0d267SMarcel Moolenaar 	    return -1;
57131337658SMarcel Moolenaar 	}
57231337658SMarcel Moolenaar 
57331337658SMarcel Moolenaar 	int count = depth - xop->xo_stack_size;
57431337658SMarcel Moolenaar 
57531337658SMarcel Moolenaar 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
57631337658SMarcel Moolenaar 	xop->xo_stack_size = depth;
57731337658SMarcel Moolenaar 	xop->xo_stack = xsp;
57831337658SMarcel Moolenaar     }
57931337658SMarcel Moolenaar 
58031337658SMarcel Moolenaar     return 0;
58131337658SMarcel Moolenaar }
58231337658SMarcel Moolenaar 
58331337658SMarcel Moolenaar void
58431337658SMarcel Moolenaar xo_no_setlocale (void)
58531337658SMarcel Moolenaar {
58631337658SMarcel Moolenaar     xo_locale_inited = 1;	/* Skip initialization */
58731337658SMarcel Moolenaar }
58831337658SMarcel Moolenaar 
58931337658SMarcel Moolenaar /*
590545ddfbeSMarcel Moolenaar  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
591545ddfbeSMarcel Moolenaar  * standard way to decide this (e.g. getlinebuf()), we have configure
592788ca347SMarcel Moolenaar  * look to find __flbf, which glibc supported.  If not, we'll rely on
593788ca347SMarcel Moolenaar  * isatty, with the assumption that terminals are the only thing
594545ddfbeSMarcel Moolenaar  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
595545ddfbeSMarcel Moolenaar  * which is all __flbf does, but that's even tackier.  Like a
596545ddfbeSMarcel Moolenaar  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
597545ddfbeSMarcel Moolenaar  * something we're willing to do.
598545ddfbeSMarcel Moolenaar  */
599545ddfbeSMarcel Moolenaar static int
600545ddfbeSMarcel Moolenaar xo_is_line_buffered (FILE *stream)
601545ddfbeSMarcel Moolenaar {
602545ddfbeSMarcel Moolenaar #if HAVE___FLBF
603545ddfbeSMarcel Moolenaar     if (__flbf(stream))
604545ddfbeSMarcel Moolenaar 	return 1;
605545ddfbeSMarcel Moolenaar #else /* HAVE___FLBF */
606545ddfbeSMarcel Moolenaar     if (isatty(fileno(stream)))
607545ddfbeSMarcel Moolenaar 	return 1;
608545ddfbeSMarcel Moolenaar #endif /* HAVE___FLBF */
609545ddfbeSMarcel Moolenaar     return 0;
610545ddfbeSMarcel Moolenaar }
611545ddfbeSMarcel Moolenaar 
612545ddfbeSMarcel Moolenaar /*
61331337658SMarcel Moolenaar  * Initialize an xo_handle_t, using both static defaults and
61431337658SMarcel Moolenaar  * the global settings from the LIBXO_OPTIONS environment
61531337658SMarcel Moolenaar  * variable.
61631337658SMarcel Moolenaar  */
61731337658SMarcel Moolenaar static void
61831337658SMarcel Moolenaar xo_init_handle (xo_handle_t *xop)
61931337658SMarcel Moolenaar {
62031337658SMarcel Moolenaar     xop->xo_opaque = stdout;
62131337658SMarcel Moolenaar     xop->xo_write = xo_write_to_file;
622545ddfbeSMarcel Moolenaar     xop->xo_flush = xo_flush_file;
623545ddfbeSMarcel Moolenaar 
624545ddfbeSMarcel Moolenaar     if (xo_is_line_buffered(stdout))
625d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_FLUSH_LINE);
62631337658SMarcel Moolenaar 
62731337658SMarcel Moolenaar     /*
628788ca347SMarcel Moolenaar      * We only want to do color output on terminals, but we only want
629788ca347SMarcel Moolenaar      * to do this if the user has asked for color.
630788ca347SMarcel Moolenaar      */
631d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
632d1a0d267SMarcel Moolenaar 	XOF_SET(xop, XOF_COLOR);
633788ca347SMarcel Moolenaar 
634788ca347SMarcel Moolenaar     /*
63531337658SMarcel Moolenaar      * We need to initialize the locale, which isn't really pretty.
63631337658SMarcel Moolenaar      * Libraries should depend on their caller to set up the
63731337658SMarcel Moolenaar      * environment.  But we really can't count on the caller to do
63831337658SMarcel Moolenaar      * this, because well, they won't.  Trust me.
63931337658SMarcel Moolenaar      */
64031337658SMarcel Moolenaar     if (!xo_locale_inited) {
64131337658SMarcel Moolenaar 	xo_locale_inited = 1;	/* Only do this once */
64231337658SMarcel Moolenaar 
64331337658SMarcel Moolenaar 	const char *cp = getenv("LC_CTYPE");
64431337658SMarcel Moolenaar 	if (cp == NULL)
64531337658SMarcel Moolenaar 	    cp = getenv("LANG");
64631337658SMarcel Moolenaar 	if (cp == NULL)
64731337658SMarcel Moolenaar 	    cp = getenv("LC_ALL");
64831337658SMarcel Moolenaar 	if (cp == NULL)
649d1a0d267SMarcel Moolenaar 	    cp = "C";		/* Default for C programs */
650c600d307SMarcel Moolenaar 	(void) setlocale(LC_CTYPE, cp);
65131337658SMarcel Moolenaar     }
65231337658SMarcel Moolenaar 
65331337658SMarcel Moolenaar     /*
65431337658SMarcel Moolenaar      * Initialize only the xo_buffers we know we'll need; the others
65531337658SMarcel Moolenaar      * can be allocated as needed.
65631337658SMarcel Moolenaar      */
65731337658SMarcel Moolenaar     xo_buf_init(&xop->xo_data);
65831337658SMarcel Moolenaar     xo_buf_init(&xop->xo_fmt);
65931337658SMarcel Moolenaar 
660d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
661d1a0d267SMarcel Moolenaar 	return;
662d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
663d1a0d267SMarcel Moolenaar 
66431337658SMarcel Moolenaar     xop->xo_indent_by = XO_INDENT_BY;
66531337658SMarcel Moolenaar     xo_depth_check(xop, XO_DEPTH);
66631337658SMarcel Moolenaar 
66731337658SMarcel Moolenaar #if !defined(NO_LIBXO_OPTIONS)
668d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
66931337658SMarcel Moolenaar 	char *env = getenv("LIBXO_OPTIONS");
67031337658SMarcel Moolenaar 	if (env)
67131337658SMarcel Moolenaar 	    xo_set_options(xop, env);
672d1a0d267SMarcel Moolenaar 
67331337658SMarcel Moolenaar     }
67431337658SMarcel Moolenaar #endif /* NO_GETENV */
675d1a0d267SMarcel Moolenaar 
676d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
67731337658SMarcel Moolenaar }
67831337658SMarcel Moolenaar 
67931337658SMarcel Moolenaar /*
68031337658SMarcel Moolenaar  * Initialize the default handle.
68131337658SMarcel Moolenaar  */
68231337658SMarcel Moolenaar static void
68331337658SMarcel Moolenaar xo_default_init (void)
68431337658SMarcel Moolenaar {
68531337658SMarcel Moolenaar     xo_handle_t *xop = &xo_default_handle;
68631337658SMarcel Moolenaar 
68731337658SMarcel Moolenaar     xo_init_handle(xop);
68831337658SMarcel Moolenaar 
68931337658SMarcel Moolenaar     xo_default_inited = 1;
69031337658SMarcel Moolenaar }
69131337658SMarcel Moolenaar 
69231337658SMarcel Moolenaar /*
69331337658SMarcel Moolenaar  * Cheap convenience function to return either the argument, or
69431337658SMarcel Moolenaar  * the internal handle, after it has been initialized.  The usage
69531337658SMarcel Moolenaar  * is:
69631337658SMarcel Moolenaar  *    xop = xo_default(xop);
69731337658SMarcel Moolenaar  */
69831337658SMarcel Moolenaar static xo_handle_t *
69931337658SMarcel Moolenaar xo_default (xo_handle_t *xop)
70031337658SMarcel Moolenaar {
70131337658SMarcel Moolenaar     if (xop == NULL) {
70231337658SMarcel Moolenaar 	if (xo_default_inited == 0)
70331337658SMarcel Moolenaar 	    xo_default_init();
70431337658SMarcel Moolenaar 	xop = &xo_default_handle;
70531337658SMarcel Moolenaar     }
70631337658SMarcel Moolenaar 
70731337658SMarcel Moolenaar     return xop;
70831337658SMarcel Moolenaar }
70931337658SMarcel Moolenaar 
71031337658SMarcel Moolenaar /*
71131337658SMarcel Moolenaar  * Return the number of spaces we should be indenting.  If
712788ca347SMarcel Moolenaar  * we are pretty-printing, this is indent * indent_by.
71331337658SMarcel Moolenaar  */
71431337658SMarcel Moolenaar static int
71531337658SMarcel Moolenaar xo_indent (xo_handle_t *xop)
71631337658SMarcel Moolenaar {
71731337658SMarcel Moolenaar     int rc = 0;
71831337658SMarcel Moolenaar 
71931337658SMarcel Moolenaar     xop = xo_default(xop);
72031337658SMarcel Moolenaar 
721d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY)) {
72231337658SMarcel Moolenaar 	rc = xop->xo_indent * xop->xo_indent_by;
723d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
72431337658SMarcel Moolenaar 	    rc += xop->xo_indent_by;
72531337658SMarcel Moolenaar     }
72631337658SMarcel Moolenaar 
727545ddfbeSMarcel Moolenaar     return (rc > 0) ? rc : 0;
72831337658SMarcel Moolenaar }
72931337658SMarcel Moolenaar 
73031337658SMarcel Moolenaar static void
73131337658SMarcel Moolenaar xo_buf_indent (xo_handle_t *xop, int indent)
73231337658SMarcel Moolenaar {
73331337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
73431337658SMarcel Moolenaar 
73531337658SMarcel Moolenaar     if (indent <= 0)
73631337658SMarcel Moolenaar 	indent = xo_indent(xop);
73731337658SMarcel Moolenaar 
73831337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, indent))
73931337658SMarcel Moolenaar 	return;
74031337658SMarcel Moolenaar 
74131337658SMarcel Moolenaar     memset(xbp->xb_curp, ' ', indent);
74231337658SMarcel Moolenaar     xbp->xb_curp += indent;
74331337658SMarcel Moolenaar }
74431337658SMarcel Moolenaar 
74531337658SMarcel Moolenaar static char xo_xml_amp[] = "&amp;";
74631337658SMarcel Moolenaar static char xo_xml_lt[] = "&lt;";
74731337658SMarcel Moolenaar static char xo_xml_gt[] = "&gt;";
74831337658SMarcel Moolenaar static char xo_xml_quot[] = "&quot;";
74931337658SMarcel Moolenaar 
75031337658SMarcel Moolenaar static int
751d1a0d267SMarcel Moolenaar xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags)
75231337658SMarcel Moolenaar {
75331337658SMarcel Moolenaar     int slen;
75431337658SMarcel Moolenaar     unsigned delta = 0;
75531337658SMarcel Moolenaar     char *cp, *ep, *ip;
75631337658SMarcel Moolenaar     const char *sp;
757d1a0d267SMarcel Moolenaar     int attr = (flags & XFF_ATTR);
75831337658SMarcel Moolenaar 
75931337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
76031337658SMarcel Moolenaar 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
76131337658SMarcel Moolenaar 	if (*cp == '<')
76231337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_lt) - 2;
76331337658SMarcel Moolenaar 	else if (*cp == '>')
76431337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_gt) - 2;
76531337658SMarcel Moolenaar 	else if (*cp == '&')
76631337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_amp) - 2;
76731337658SMarcel Moolenaar 	else if (attr && *cp == '"')
76831337658SMarcel Moolenaar 	    delta += sizeof(xo_xml_quot) - 2;
76931337658SMarcel Moolenaar     }
77031337658SMarcel Moolenaar 
77131337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
77231337658SMarcel Moolenaar 	return len;
77331337658SMarcel Moolenaar 
77431337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
77531337658SMarcel Moolenaar 	return 0;
77631337658SMarcel Moolenaar 
77731337658SMarcel Moolenaar     ep = xbp->xb_curp;
77831337658SMarcel Moolenaar     cp = ep + len;
77931337658SMarcel Moolenaar     ip = cp + delta;
78031337658SMarcel Moolenaar     do {
78131337658SMarcel Moolenaar 	cp -= 1;
78231337658SMarcel Moolenaar 	ip -= 1;
78331337658SMarcel Moolenaar 
78431337658SMarcel Moolenaar 	if (*cp == '<')
78531337658SMarcel Moolenaar 	    sp = xo_xml_lt;
78631337658SMarcel Moolenaar 	else if (*cp == '>')
78731337658SMarcel Moolenaar 	    sp = xo_xml_gt;
78831337658SMarcel Moolenaar 	else if (*cp == '&')
78931337658SMarcel Moolenaar 	    sp = xo_xml_amp;
79031337658SMarcel Moolenaar 	else if (attr && *cp == '"')
79131337658SMarcel Moolenaar 	    sp = xo_xml_quot;
79231337658SMarcel Moolenaar 	else {
79331337658SMarcel Moolenaar 	    *ip = *cp;
79431337658SMarcel Moolenaar 	    continue;
79531337658SMarcel Moolenaar 	}
79631337658SMarcel Moolenaar 
79731337658SMarcel Moolenaar 	slen = strlen(sp);
79831337658SMarcel Moolenaar 	ip -= slen - 1;
79931337658SMarcel Moolenaar 	memcpy(ip, sp, slen);
80031337658SMarcel Moolenaar 
80131337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
80231337658SMarcel Moolenaar 
80331337658SMarcel Moolenaar     return len + delta;
80431337658SMarcel Moolenaar }
80531337658SMarcel Moolenaar 
80631337658SMarcel Moolenaar static int
807d1a0d267SMarcel Moolenaar xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
80831337658SMarcel Moolenaar {
80931337658SMarcel Moolenaar     unsigned delta = 0;
81031337658SMarcel Moolenaar     char *cp, *ep, *ip;
81131337658SMarcel Moolenaar 
81231337658SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
813545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"')
81431337658SMarcel Moolenaar 	    delta += 1;
815545ddfbeSMarcel Moolenaar 	else if (*cp == '\n' || *cp == '\r')
81631337658SMarcel Moolenaar 	    delta += 1;
81731337658SMarcel Moolenaar     }
81831337658SMarcel Moolenaar 
81931337658SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
82031337658SMarcel Moolenaar 	return len;
82131337658SMarcel Moolenaar 
82231337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
82331337658SMarcel Moolenaar 	return 0;
82431337658SMarcel Moolenaar 
82531337658SMarcel Moolenaar     ep = xbp->xb_curp;
82631337658SMarcel Moolenaar     cp = ep + len;
82731337658SMarcel Moolenaar     ip = cp + delta;
82831337658SMarcel Moolenaar     do {
82931337658SMarcel Moolenaar 	cp -= 1;
83031337658SMarcel Moolenaar 	ip -= 1;
83131337658SMarcel Moolenaar 
832545ddfbeSMarcel Moolenaar 	if (*cp == '\\' || *cp == '"') {
83331337658SMarcel Moolenaar 	    *ip-- = *cp;
83431337658SMarcel Moolenaar 	    *ip = '\\';
835545ddfbeSMarcel Moolenaar 	} else if (*cp == '\n') {
836545ddfbeSMarcel Moolenaar 	    *ip-- = 'n';
837545ddfbeSMarcel Moolenaar 	    *ip = '\\';
838545ddfbeSMarcel Moolenaar 	} else if (*cp == '\r') {
839545ddfbeSMarcel Moolenaar 	    *ip-- = 'r';
840545ddfbeSMarcel Moolenaar 	    *ip = '\\';
841545ddfbeSMarcel Moolenaar 	} else {
842545ddfbeSMarcel Moolenaar 	    *ip = *cp;
843545ddfbeSMarcel Moolenaar 	}
84431337658SMarcel Moolenaar 
84531337658SMarcel Moolenaar     } while (cp > ep && cp != ip);
84631337658SMarcel Moolenaar 
84731337658SMarcel Moolenaar     return len + delta;
84831337658SMarcel Moolenaar }
84931337658SMarcel Moolenaar 
85031337658SMarcel Moolenaar /*
851d1a0d267SMarcel Moolenaar  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
852d1a0d267SMarcel Moolenaar  *                                ; ']' MUST be escaped.
85331337658SMarcel Moolenaar  */
854d1a0d267SMarcel Moolenaar static int
855d1a0d267SMarcel Moolenaar xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
85631337658SMarcel Moolenaar {
857d1a0d267SMarcel Moolenaar     unsigned delta = 0;
858d1a0d267SMarcel Moolenaar     char *cp, *ep, *ip;
85931337658SMarcel Moolenaar 
860d1a0d267SMarcel Moolenaar     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
861d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']')
862d1a0d267SMarcel Moolenaar 	    delta += 1;
86331337658SMarcel Moolenaar     }
86431337658SMarcel Moolenaar 
865d1a0d267SMarcel Moolenaar     if (delta == 0)		/* Nothing to escape; bail */
866d1a0d267SMarcel Moolenaar 	return len;
867788ca347SMarcel Moolenaar 
868d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
869d1a0d267SMarcel Moolenaar 	return 0;
870788ca347SMarcel Moolenaar 
871d1a0d267SMarcel Moolenaar     ep = xbp->xb_curp;
872d1a0d267SMarcel Moolenaar     cp = ep + len;
873d1a0d267SMarcel Moolenaar     ip = cp + delta;
874d1a0d267SMarcel Moolenaar     do {
875d1a0d267SMarcel Moolenaar 	cp -= 1;
876d1a0d267SMarcel Moolenaar 	ip -= 1;
877d1a0d267SMarcel Moolenaar 
878d1a0d267SMarcel Moolenaar 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
879d1a0d267SMarcel Moolenaar 	    *ip-- = *cp;
880d1a0d267SMarcel Moolenaar 	    *ip = '\\';
881d1a0d267SMarcel Moolenaar 	} else {
882d1a0d267SMarcel Moolenaar 	    *ip = *cp;
883d1a0d267SMarcel Moolenaar 	}
884d1a0d267SMarcel Moolenaar 
885d1a0d267SMarcel Moolenaar     } while (cp > ep && cp != ip);
886d1a0d267SMarcel Moolenaar 
887d1a0d267SMarcel Moolenaar     return len + delta;
888788ca347SMarcel Moolenaar }
889788ca347SMarcel Moolenaar 
89031337658SMarcel Moolenaar static void
89131337658SMarcel Moolenaar xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
89231337658SMarcel Moolenaar 	       const char *str, int len, xo_xff_flags_t flags)
89331337658SMarcel Moolenaar {
89431337658SMarcel Moolenaar     if (!xo_buf_has_room(xbp, len))
89531337658SMarcel Moolenaar 	return;
89631337658SMarcel Moolenaar 
89731337658SMarcel Moolenaar     memcpy(xbp->xb_curp, str, len);
89831337658SMarcel Moolenaar 
899788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
90031337658SMarcel Moolenaar     case XO_STYLE_XML:
90131337658SMarcel Moolenaar     case XO_STYLE_HTML:
902d1a0d267SMarcel Moolenaar 	len = xo_escape_xml(xbp, len, flags);
90331337658SMarcel Moolenaar 	break;
90431337658SMarcel Moolenaar 
90531337658SMarcel Moolenaar     case XO_STYLE_JSON:
906d1a0d267SMarcel Moolenaar 	len = xo_escape_json(xbp, len, flags);
907d1a0d267SMarcel Moolenaar 	break;
908d1a0d267SMarcel Moolenaar 
909d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
910d1a0d267SMarcel Moolenaar 	len = xo_escape_sdparams(xbp, len, flags);
91131337658SMarcel Moolenaar 	break;
91231337658SMarcel Moolenaar     }
91331337658SMarcel Moolenaar 
91431337658SMarcel Moolenaar     xbp->xb_curp += len;
91531337658SMarcel Moolenaar }
91631337658SMarcel Moolenaar 
91731337658SMarcel Moolenaar /*
91831337658SMarcel Moolenaar  * Write the current contents of the data buffer using the handle's
91931337658SMarcel Moolenaar  * xo_write function.
92031337658SMarcel Moolenaar  */
921545ddfbeSMarcel Moolenaar static int
92231337658SMarcel Moolenaar xo_write (xo_handle_t *xop)
92331337658SMarcel Moolenaar {
924545ddfbeSMarcel Moolenaar     int rc = 0;
92531337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
92631337658SMarcel Moolenaar 
92731337658SMarcel Moolenaar     if (xbp->xb_curp != xbp->xb_bufp) {
92831337658SMarcel Moolenaar 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
92931337658SMarcel Moolenaar 	xo_anchor_clear(xop);
930d1a0d267SMarcel Moolenaar 	if (xop->xo_write)
931545ddfbeSMarcel Moolenaar 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
93231337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp;
93331337658SMarcel Moolenaar     }
93431337658SMarcel Moolenaar 
93531337658SMarcel Moolenaar     /* Turn off the flags that don't survive across writes */
936d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
937545ddfbeSMarcel Moolenaar 
938545ddfbeSMarcel Moolenaar     return rc;
93931337658SMarcel Moolenaar }
94031337658SMarcel Moolenaar 
94131337658SMarcel Moolenaar /*
94231337658SMarcel Moolenaar  * Format arguments into our buffer.  If a custom formatter has been set,
94331337658SMarcel Moolenaar  * we use that to do the work; otherwise we vsnprintf().
94431337658SMarcel Moolenaar  */
94531337658SMarcel Moolenaar static int
94631337658SMarcel Moolenaar xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
94731337658SMarcel Moolenaar {
94831337658SMarcel Moolenaar     va_list va_local;
94931337658SMarcel Moolenaar     int rc;
95031337658SMarcel Moolenaar     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
95131337658SMarcel Moolenaar 
95231337658SMarcel Moolenaar     va_copy(va_local, vap);
95331337658SMarcel Moolenaar 
95431337658SMarcel Moolenaar     if (xop->xo_formatter)
95531337658SMarcel Moolenaar 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
95631337658SMarcel Moolenaar     else
95731337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
95831337658SMarcel Moolenaar 
959788ca347SMarcel Moolenaar     if (rc >= left) {
960c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
961c600d307SMarcel Moolenaar 	    va_end(va_local);
96231337658SMarcel Moolenaar 	    return -1;
963c600d307SMarcel Moolenaar 	}
96431337658SMarcel Moolenaar 
96531337658SMarcel Moolenaar 	/*
96631337658SMarcel Moolenaar 	 * After we call vsnprintf(), the stage of vap is not defined.
96731337658SMarcel Moolenaar 	 * We need to copy it before we pass.  Then we have to do our
96831337658SMarcel Moolenaar 	 * own logic below to move it along.  This is because the
969788ca347SMarcel Moolenaar 	 * implementation can have va_list be a pointer (bsd) or a
97031337658SMarcel Moolenaar 	 * structure (macosx) or anything in between.
97131337658SMarcel Moolenaar 	 */
97231337658SMarcel Moolenaar 
97331337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
97431337658SMarcel Moolenaar 	va_copy(va_local, vap);
97531337658SMarcel Moolenaar 
97631337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
97731337658SMarcel Moolenaar 	if (xop->xo_formatter)
978788ca347SMarcel Moolenaar 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
97931337658SMarcel Moolenaar 	else
98031337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
98131337658SMarcel Moolenaar     }
98231337658SMarcel Moolenaar     va_end(va_local);
98331337658SMarcel Moolenaar 
98431337658SMarcel Moolenaar     return rc;
98531337658SMarcel Moolenaar }
98631337658SMarcel Moolenaar 
98731337658SMarcel Moolenaar /*
98831337658SMarcel Moolenaar  * Print some data thru the handle.
98931337658SMarcel Moolenaar  */
99031337658SMarcel Moolenaar static int
99131337658SMarcel Moolenaar xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
99231337658SMarcel Moolenaar {
99331337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
99431337658SMarcel Moolenaar     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
99531337658SMarcel Moolenaar     int rc;
99631337658SMarcel Moolenaar     va_list va_local;
99731337658SMarcel Moolenaar 
99831337658SMarcel Moolenaar     va_copy(va_local, vap);
99931337658SMarcel Moolenaar 
100031337658SMarcel Moolenaar     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
100131337658SMarcel Moolenaar 
1002d1a0d267SMarcel Moolenaar     if (rc >= left) {
1003c600d307SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, rc)) {
1004c600d307SMarcel Moolenaar 	    va_end(va_local);
100531337658SMarcel Moolenaar 	    return -1;
1006c600d307SMarcel Moolenaar 	}
100731337658SMarcel Moolenaar 
100831337658SMarcel Moolenaar 	va_end(va_local);	/* Reset vap to the start */
100931337658SMarcel Moolenaar 	va_copy(va_local, vap);
101031337658SMarcel Moolenaar 
101131337658SMarcel Moolenaar 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
101231337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
101331337658SMarcel Moolenaar     }
101431337658SMarcel Moolenaar 
101531337658SMarcel Moolenaar     va_end(va_local);
101631337658SMarcel Moolenaar 
101731337658SMarcel Moolenaar     if (rc > 0)
101831337658SMarcel Moolenaar 	xbp->xb_curp += rc;
101931337658SMarcel Moolenaar 
102031337658SMarcel Moolenaar     return rc;
102131337658SMarcel Moolenaar }
102231337658SMarcel Moolenaar 
102331337658SMarcel Moolenaar static int
102431337658SMarcel Moolenaar xo_printf (xo_handle_t *xop, const char *fmt, ...)
102531337658SMarcel Moolenaar {
102631337658SMarcel Moolenaar     int rc;
102731337658SMarcel Moolenaar     va_list vap;
102831337658SMarcel Moolenaar 
102931337658SMarcel Moolenaar     va_start(vap, fmt);
103031337658SMarcel Moolenaar 
103131337658SMarcel Moolenaar     rc = xo_printf_v(xop, fmt, vap);
103231337658SMarcel Moolenaar 
103331337658SMarcel Moolenaar     va_end(vap);
103431337658SMarcel Moolenaar     return rc;
103531337658SMarcel Moolenaar }
103631337658SMarcel Moolenaar 
103731337658SMarcel Moolenaar /*
103831337658SMarcel Moolenaar  * These next few function are make The Essential UTF-8 Ginsu Knife.
103931337658SMarcel Moolenaar  * Identify an input and output character, and convert it.
104031337658SMarcel Moolenaar  */
104131337658SMarcel Moolenaar static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
104231337658SMarcel Moolenaar 
104331337658SMarcel Moolenaar static int
104431337658SMarcel Moolenaar xo_is_utf8 (char ch)
104531337658SMarcel Moolenaar {
104631337658SMarcel Moolenaar     return (ch & 0x80);
104731337658SMarcel Moolenaar }
104831337658SMarcel Moolenaar 
104931337658SMarcel Moolenaar static int
105031337658SMarcel Moolenaar xo_utf8_to_wc_len (const char *buf)
105131337658SMarcel Moolenaar {
105231337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
105331337658SMarcel Moolenaar     int len;
105431337658SMarcel Moolenaar 
105531337658SMarcel Moolenaar     if ((b & 0x80) == 0x0)
105631337658SMarcel Moolenaar 	len = 1;
105731337658SMarcel Moolenaar     else if ((b & 0xe0) == 0xc0)
105831337658SMarcel Moolenaar 	len = 2;
105931337658SMarcel Moolenaar     else if ((b & 0xf0) == 0xe0)
106031337658SMarcel Moolenaar 	len = 3;
106131337658SMarcel Moolenaar     else if ((b & 0xf8) == 0xf0)
106231337658SMarcel Moolenaar 	len = 4;
106331337658SMarcel Moolenaar     else if ((b & 0xfc) == 0xf8)
106431337658SMarcel Moolenaar 	len = 5;
106531337658SMarcel Moolenaar     else if ((b & 0xfe) == 0xfc)
106631337658SMarcel Moolenaar 	len = 6;
106731337658SMarcel Moolenaar     else
106831337658SMarcel Moolenaar 	len = -1;
106931337658SMarcel Moolenaar 
107031337658SMarcel Moolenaar     return len;
107131337658SMarcel Moolenaar }
107231337658SMarcel Moolenaar 
107331337658SMarcel Moolenaar static int
107431337658SMarcel Moolenaar xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
107531337658SMarcel Moolenaar {
107631337658SMarcel Moolenaar 
107731337658SMarcel Moolenaar     unsigned b = (unsigned char) *buf;
107831337658SMarcel Moolenaar     int len, i;
107931337658SMarcel Moolenaar 
108031337658SMarcel Moolenaar     len = xo_utf8_to_wc_len(buf);
108131337658SMarcel Moolenaar     if (len == -1) {
108231337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
108331337658SMarcel Moolenaar 	return -1;
108431337658SMarcel Moolenaar     }
108531337658SMarcel Moolenaar 
108631337658SMarcel Moolenaar     if (len > bufsiz) {
108731337658SMarcel Moolenaar         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
108831337658SMarcel Moolenaar 		   b, len, bufsiz);
108931337658SMarcel Moolenaar 	return -1;
109031337658SMarcel Moolenaar     }
109131337658SMarcel Moolenaar 
109231337658SMarcel Moolenaar     for (i = 2; i < len; i++) {
109331337658SMarcel Moolenaar 	b = (unsigned char ) buf[i];
109431337658SMarcel Moolenaar 	if ((b & 0xc0) != 0x80) {
109531337658SMarcel Moolenaar 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
109631337658SMarcel Moolenaar 	    return -1;
109731337658SMarcel Moolenaar 	}
109831337658SMarcel Moolenaar     }
109931337658SMarcel Moolenaar 
110031337658SMarcel Moolenaar     return len;
110131337658SMarcel Moolenaar }
110231337658SMarcel Moolenaar 
110331337658SMarcel Moolenaar /*
110431337658SMarcel Moolenaar  * Build a wide character from the input buffer; the number of
110531337658SMarcel Moolenaar  * bits we pull off the first character is dependent on the length,
110631337658SMarcel Moolenaar  * but we put 6 bits off all other bytes.
110731337658SMarcel Moolenaar  */
110831337658SMarcel Moolenaar static wchar_t
110931337658SMarcel Moolenaar xo_utf8_char (const char *buf, int len)
111031337658SMarcel Moolenaar {
111131337658SMarcel Moolenaar     int i;
111231337658SMarcel Moolenaar     wchar_t wc;
111331337658SMarcel Moolenaar     const unsigned char *cp = (const unsigned char *) buf;
111431337658SMarcel Moolenaar 
111531337658SMarcel Moolenaar     wc = *cp & xo_utf8_bits[len];
111631337658SMarcel Moolenaar     for (i = 1; i < len; i++) {
111731337658SMarcel Moolenaar 	wc <<= 6;
111831337658SMarcel Moolenaar 	wc |= cp[i] & 0x3f;
111931337658SMarcel Moolenaar 	if ((cp[i] & 0xc0) != 0x80)
112031337658SMarcel Moolenaar 	    return (wchar_t) -1;
112131337658SMarcel Moolenaar     }
112231337658SMarcel Moolenaar 
112331337658SMarcel Moolenaar     return wc;
112431337658SMarcel Moolenaar }
112531337658SMarcel Moolenaar 
112631337658SMarcel Moolenaar /*
112731337658SMarcel Moolenaar  * Determine the number of bytes needed to encode a wide character.
112831337658SMarcel Moolenaar  */
112931337658SMarcel Moolenaar static int
113031337658SMarcel Moolenaar xo_utf8_emit_len (wchar_t wc)
113131337658SMarcel Moolenaar {
113231337658SMarcel Moolenaar     int len;
113331337658SMarcel Moolenaar 
113431337658SMarcel Moolenaar     if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
113531337658SMarcel Moolenaar 	len = 1;
113631337658SMarcel Moolenaar     else if ((wc & ((1<<11) - 1)) == wc)
113731337658SMarcel Moolenaar 	len = 2;
113831337658SMarcel Moolenaar     else if ((wc & ((1<<16) - 1)) == wc)
113931337658SMarcel Moolenaar 	len = 3;
114031337658SMarcel Moolenaar     else if ((wc & ((1<<21) - 1)) == wc)
114131337658SMarcel Moolenaar 	len = 4;
114231337658SMarcel Moolenaar     else if ((wc & ((1<<26) - 1)) == wc)
114331337658SMarcel Moolenaar 	len = 5;
114431337658SMarcel Moolenaar     else
114531337658SMarcel Moolenaar 	len = 6;
114631337658SMarcel Moolenaar 
114731337658SMarcel Moolenaar     return len;
114831337658SMarcel Moolenaar }
114931337658SMarcel Moolenaar 
115031337658SMarcel Moolenaar static void
115131337658SMarcel Moolenaar xo_utf8_emit_char (char *buf, int len, wchar_t wc)
115231337658SMarcel Moolenaar {
115331337658SMarcel Moolenaar     int i;
115431337658SMarcel Moolenaar 
115531337658SMarcel Moolenaar     if (len == 1) { /* Simple case */
115631337658SMarcel Moolenaar 	buf[0] = wc & 0x7f;
115731337658SMarcel Moolenaar 	return;
115831337658SMarcel Moolenaar     }
115931337658SMarcel Moolenaar 
116031337658SMarcel Moolenaar     for (i = len - 1; i >= 0; i--) {
116131337658SMarcel Moolenaar 	buf[i] = 0x80 | (wc & 0x3f);
116231337658SMarcel Moolenaar 	wc >>= 6;
116331337658SMarcel Moolenaar     }
116431337658SMarcel Moolenaar 
116531337658SMarcel Moolenaar     buf[0] &= xo_utf8_bits[len];
116631337658SMarcel Moolenaar     buf[0] |= ~xo_utf8_bits[len] << 1;
116731337658SMarcel Moolenaar }
116831337658SMarcel Moolenaar 
116931337658SMarcel Moolenaar static int
117031337658SMarcel Moolenaar xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
117131337658SMarcel Moolenaar 				const char *ibuf, int ilen)
117231337658SMarcel Moolenaar {
117331337658SMarcel Moolenaar     wchar_t wc;
117431337658SMarcel Moolenaar     int len;
117531337658SMarcel Moolenaar 
117631337658SMarcel Moolenaar     /*
117731337658SMarcel Moolenaar      * Build our wide character from the input buffer; the number of
117831337658SMarcel Moolenaar      * bits we pull off the first character is dependent on the length,
117931337658SMarcel Moolenaar      * but we put 6 bits off all other bytes.
118031337658SMarcel Moolenaar      */
118131337658SMarcel Moolenaar     wc = xo_utf8_char(ibuf, ilen);
118231337658SMarcel Moolenaar     if (wc == (wchar_t) -1) {
118331337658SMarcel Moolenaar 	xo_failure(xop, "invalid utf-8 byte sequence");
118431337658SMarcel Moolenaar 	return 0;
118531337658SMarcel Moolenaar     }
118631337658SMarcel Moolenaar 
1187d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
118831337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, ilen))
118931337658SMarcel Moolenaar 	    return 0;
119031337658SMarcel Moolenaar 
119131337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, ibuf, ilen);
119231337658SMarcel Moolenaar 	xbp->xb_curp += ilen;
119331337658SMarcel Moolenaar 
119431337658SMarcel Moolenaar     } else {
119531337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
119631337658SMarcel Moolenaar 	    return 0;
119731337658SMarcel Moolenaar 
119831337658SMarcel Moolenaar 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
119931337658SMarcel Moolenaar 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
120031337658SMarcel Moolenaar 
120131337658SMarcel Moolenaar 	if (len <= 0) {
120231337658SMarcel Moolenaar 	    xo_failure(xop, "could not convert wide char: %lx",
120331337658SMarcel Moolenaar 		       (unsigned long) wc);
120431337658SMarcel Moolenaar 	    return 0;
120531337658SMarcel Moolenaar 	}
120631337658SMarcel Moolenaar 	xbp->xb_curp += len;
120731337658SMarcel Moolenaar     }
120831337658SMarcel Moolenaar 
1209d1a0d267SMarcel Moolenaar     return xo_wcwidth(wc);
121031337658SMarcel Moolenaar }
121131337658SMarcel Moolenaar 
121231337658SMarcel Moolenaar static void
121331337658SMarcel Moolenaar xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
121431337658SMarcel Moolenaar 		      const char *cp, int len)
121531337658SMarcel Moolenaar {
121631337658SMarcel Moolenaar     const char *sp = cp, *ep = cp + len;
121731337658SMarcel Moolenaar     unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
121831337658SMarcel Moolenaar     int slen;
121931337658SMarcel Moolenaar     int cols = 0;
122031337658SMarcel Moolenaar 
122131337658SMarcel Moolenaar     for ( ; cp < ep; cp++) {
122231337658SMarcel Moolenaar 	if (!xo_is_utf8(*cp)) {
122331337658SMarcel Moolenaar 	    cols += 1;
122431337658SMarcel Moolenaar 	    continue;
122531337658SMarcel Moolenaar 	}
122631337658SMarcel Moolenaar 
122731337658SMarcel Moolenaar 	/*
122831337658SMarcel Moolenaar 	 * We're looking at a non-ascii UTF-8 character.
122931337658SMarcel Moolenaar 	 * First we copy the previous data.
123031337658SMarcel Moolenaar 	 * Then we need find the length and validate it.
123131337658SMarcel Moolenaar 	 * Then we turn it into a wide string.
123231337658SMarcel Moolenaar 	 * Then we turn it into a localized string.
123331337658SMarcel Moolenaar 	 * Then we repeat.  Isn't i18n fun?
123431337658SMarcel Moolenaar 	 */
123531337658SMarcel Moolenaar 	if (sp != cp)
123631337658SMarcel Moolenaar 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
123731337658SMarcel Moolenaar 
123831337658SMarcel Moolenaar 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
123931337658SMarcel Moolenaar 	if (slen <= 0) {
124031337658SMarcel Moolenaar 	    /* Bad data; back it all out */
124131337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + save_off;
124231337658SMarcel Moolenaar 	    return;
124331337658SMarcel Moolenaar 	}
124431337658SMarcel Moolenaar 
124531337658SMarcel Moolenaar 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
124631337658SMarcel Moolenaar 
124731337658SMarcel Moolenaar 	/* Next time thru, we'll start at the next character */
124831337658SMarcel Moolenaar 	cp += slen - 1;
124931337658SMarcel Moolenaar 	sp = cp + 1;
125031337658SMarcel Moolenaar     }
125131337658SMarcel Moolenaar 
125231337658SMarcel Moolenaar     /* Update column values */
1253d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
125431337658SMarcel Moolenaar 	xop->xo_columns += cols;
1255d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
125631337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
125731337658SMarcel Moolenaar 
125831337658SMarcel Moolenaar     /* Before we fall into the basic logic below, we need reset len */
125931337658SMarcel Moolenaar     len = ep - sp;
126031337658SMarcel Moolenaar     if (len != 0) /* Append trailing data */
126131337658SMarcel Moolenaar 	xo_buf_append(xbp, sp, len);
126231337658SMarcel Moolenaar }
126331337658SMarcel Moolenaar 
126431337658SMarcel Moolenaar /*
1265d1a0d267SMarcel Moolenaar  * Append the given string to the given buffer, without escaping or
1266d1a0d267SMarcel Moolenaar  * character set conversion.  This is the straight copy to the data
1267d1a0d267SMarcel Moolenaar  * buffer with no fanciness.
126831337658SMarcel Moolenaar  */
126931337658SMarcel Moolenaar static void
127031337658SMarcel Moolenaar xo_data_append (xo_handle_t *xop, const char *str, int len)
127131337658SMarcel Moolenaar {
127231337658SMarcel Moolenaar     xo_buf_append(&xop->xo_data, str, len);
127331337658SMarcel Moolenaar }
127431337658SMarcel Moolenaar 
127531337658SMarcel Moolenaar /*
127631337658SMarcel Moolenaar  * Append the given string to the given buffer
127731337658SMarcel Moolenaar  */
127831337658SMarcel Moolenaar static void
127931337658SMarcel Moolenaar xo_data_escape (xo_handle_t *xop, const char *str, int len)
128031337658SMarcel Moolenaar {
128131337658SMarcel Moolenaar     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
128231337658SMarcel Moolenaar }
128331337658SMarcel Moolenaar 
128431337658SMarcel Moolenaar /*
128531337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
128631337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
128731337658SMarcel Moolenaar  * XMLified content on standard output.
128831337658SMarcel Moolenaar  */
128931337658SMarcel Moolenaar static void
129031337658SMarcel Moolenaar xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
129131337658SMarcel Moolenaar 	     const char *fmt, va_list vap)
129231337658SMarcel Moolenaar {
129331337658SMarcel Moolenaar     xop = xo_default(xop);
1294d1a0d267SMarcel Moolenaar     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
129531337658SMarcel Moolenaar 	return;
129631337658SMarcel Moolenaar 
129731337658SMarcel Moolenaar     if (fmt == NULL)
129831337658SMarcel Moolenaar 	return;
129931337658SMarcel Moolenaar 
130031337658SMarcel Moolenaar     int len = strlen(fmt);
130131337658SMarcel Moolenaar     int plen = xo_program ? strlen(xo_program) : 0;
1302545ddfbeSMarcel Moolenaar     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
130331337658SMarcel Moolenaar 
130431337658SMarcel Moolenaar     if (plen) {
130531337658SMarcel Moolenaar 	memcpy(newfmt, xo_program, plen);
130631337658SMarcel Moolenaar 	newfmt[plen++] = ':';
130731337658SMarcel Moolenaar 	newfmt[plen++] = ' ';
130831337658SMarcel Moolenaar     }
130931337658SMarcel Moolenaar     memcpy(newfmt + plen, fmt, len);
131031337658SMarcel Moolenaar     newfmt[len + plen] = '\0';
131131337658SMarcel Moolenaar 
1312d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_WARN_XML)) {
131331337658SMarcel Moolenaar 	static char err_open[] = "<error>";
131431337658SMarcel Moolenaar 	static char err_close[] = "</error>";
131531337658SMarcel Moolenaar 	static char msg_open[] = "<message>";
131631337658SMarcel Moolenaar 	static char msg_close[] = "</message>";
131731337658SMarcel Moolenaar 
131831337658SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_data;
131931337658SMarcel Moolenaar 
132031337658SMarcel Moolenaar 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
132131337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
132231337658SMarcel Moolenaar 
132331337658SMarcel Moolenaar 	va_list va_local;
132431337658SMarcel Moolenaar 	va_copy(va_local, vap);
132531337658SMarcel Moolenaar 
132631337658SMarcel Moolenaar 	int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
132731337658SMarcel Moolenaar 	int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1328d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1329c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1330c600d307SMarcel Moolenaar 		va_end(va_local);
133131337658SMarcel Moolenaar 		return;
1332c600d307SMarcel Moolenaar 	    }
133331337658SMarcel Moolenaar 
133431337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
133531337658SMarcel Moolenaar 	    va_copy(vap, va_local);
133631337658SMarcel Moolenaar 
133731337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
133831337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
133931337658SMarcel Moolenaar 	}
134031337658SMarcel Moolenaar 	va_end(va_local);
134131337658SMarcel Moolenaar 
134231337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 1);
134331337658SMarcel Moolenaar 	xbp->xb_curp += rc;
134431337658SMarcel Moolenaar 
134531337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
134631337658SMarcel Moolenaar 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
134731337658SMarcel Moolenaar 
1348545ddfbeSMarcel Moolenaar 	if (code >= 0) {
134931337658SMarcel Moolenaar 	    const char *msg = strerror(code);
135031337658SMarcel Moolenaar 	    if (msg) {
135131337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
135231337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
135331337658SMarcel Moolenaar 	    }
135431337658SMarcel Moolenaar 	}
135531337658SMarcel Moolenaar 
1356d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1357545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
135831337658SMarcel Moolenaar 
135931337658SMarcel Moolenaar     } else {
136031337658SMarcel Moolenaar 	vfprintf(stderr, newfmt, vap);
1361545ddfbeSMarcel Moolenaar 	if (code >= 0) {
1362545ddfbeSMarcel Moolenaar 	    const char *msg = strerror(code);
1363545ddfbeSMarcel Moolenaar 	    if (msg)
1364545ddfbeSMarcel Moolenaar 		fprintf(stderr, ": %s", msg);
1365545ddfbeSMarcel Moolenaar 	}
1366545ddfbeSMarcel Moolenaar 	fprintf(stderr, "\n");
136731337658SMarcel Moolenaar     }
136831337658SMarcel Moolenaar }
136931337658SMarcel Moolenaar 
137031337658SMarcel Moolenaar void
137131337658SMarcel Moolenaar xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
137231337658SMarcel Moolenaar {
137331337658SMarcel Moolenaar     va_list vap;
137431337658SMarcel Moolenaar 
137531337658SMarcel Moolenaar     va_start(vap, fmt);
137631337658SMarcel Moolenaar     xo_warn_hcv(xop, code, 0, fmt, vap);
137731337658SMarcel Moolenaar     va_end(vap);
137831337658SMarcel Moolenaar }
137931337658SMarcel Moolenaar 
138031337658SMarcel Moolenaar void
138131337658SMarcel Moolenaar xo_warn_c (int code, const char *fmt, ...)
138231337658SMarcel Moolenaar {
138331337658SMarcel Moolenaar     va_list vap;
138431337658SMarcel Moolenaar 
138531337658SMarcel Moolenaar     va_start(vap, fmt);
1386545ddfbeSMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
138731337658SMarcel Moolenaar     va_end(vap);
138831337658SMarcel Moolenaar }
138931337658SMarcel Moolenaar 
139031337658SMarcel Moolenaar void
139131337658SMarcel Moolenaar xo_warn (const char *fmt, ...)
139231337658SMarcel Moolenaar {
139331337658SMarcel Moolenaar     int code = errno;
139431337658SMarcel Moolenaar     va_list vap;
139531337658SMarcel Moolenaar 
139631337658SMarcel Moolenaar     va_start(vap, fmt);
139731337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
139831337658SMarcel Moolenaar     va_end(vap);
139931337658SMarcel Moolenaar }
140031337658SMarcel Moolenaar 
140131337658SMarcel Moolenaar void
140231337658SMarcel Moolenaar xo_warnx (const char *fmt, ...)
140331337658SMarcel Moolenaar {
140431337658SMarcel Moolenaar     va_list vap;
140531337658SMarcel Moolenaar 
140631337658SMarcel Moolenaar     va_start(vap, fmt);
140731337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
140831337658SMarcel Moolenaar     va_end(vap);
140931337658SMarcel Moolenaar }
141031337658SMarcel Moolenaar 
141131337658SMarcel Moolenaar void
141231337658SMarcel Moolenaar xo_err (int eval, const char *fmt, ...)
141331337658SMarcel Moolenaar {
141431337658SMarcel Moolenaar     int code = errno;
141531337658SMarcel Moolenaar     va_list vap;
141631337658SMarcel Moolenaar 
141731337658SMarcel Moolenaar     va_start(vap, fmt);
141831337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
141931337658SMarcel Moolenaar     va_end(vap);
142031337658SMarcel Moolenaar     xo_finish();
142131337658SMarcel Moolenaar     exit(eval);
142231337658SMarcel Moolenaar }
142331337658SMarcel Moolenaar 
142431337658SMarcel Moolenaar void
142531337658SMarcel Moolenaar xo_errx (int eval, const char *fmt, ...)
142631337658SMarcel Moolenaar {
142731337658SMarcel Moolenaar     va_list vap;
142831337658SMarcel Moolenaar 
142931337658SMarcel Moolenaar     va_start(vap, fmt);
143031337658SMarcel Moolenaar     xo_warn_hcv(NULL, -1, 0, fmt, vap);
143131337658SMarcel Moolenaar     va_end(vap);
143231337658SMarcel Moolenaar     xo_finish();
143331337658SMarcel Moolenaar     exit(eval);
143431337658SMarcel Moolenaar }
143531337658SMarcel Moolenaar 
143631337658SMarcel Moolenaar void
143731337658SMarcel Moolenaar xo_errc (int eval, int code, const char *fmt, ...)
143831337658SMarcel Moolenaar {
143931337658SMarcel Moolenaar     va_list vap;
144031337658SMarcel Moolenaar 
144131337658SMarcel Moolenaar     va_start(vap, fmt);
144231337658SMarcel Moolenaar     xo_warn_hcv(NULL, code, 0, fmt, vap);
144331337658SMarcel Moolenaar     va_end(vap);
144431337658SMarcel Moolenaar     xo_finish();
144531337658SMarcel Moolenaar     exit(eval);
144631337658SMarcel Moolenaar }
144731337658SMarcel Moolenaar 
144831337658SMarcel Moolenaar /*
144931337658SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
145031337658SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
145131337658SMarcel Moolenaar  * XMLified content on standard output.
145231337658SMarcel Moolenaar  */
145331337658SMarcel Moolenaar void
145431337658SMarcel Moolenaar xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
145531337658SMarcel Moolenaar {
145631337658SMarcel Moolenaar     static char msg_open[] = "<message>";
145731337658SMarcel Moolenaar     static char msg_close[] = "</message>";
145831337658SMarcel Moolenaar     xo_buffer_t *xbp;
145931337658SMarcel Moolenaar     int rc;
146031337658SMarcel Moolenaar     va_list va_local;
146131337658SMarcel Moolenaar 
146231337658SMarcel Moolenaar     xop = xo_default(xop);
146331337658SMarcel Moolenaar 
146431337658SMarcel Moolenaar     if (fmt == NULL || *fmt == '\0')
146531337658SMarcel Moolenaar 	return;
146631337658SMarcel Moolenaar 
146731337658SMarcel Moolenaar     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
146831337658SMarcel Moolenaar 
1469788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
147031337658SMarcel Moolenaar     case XO_STYLE_XML:
147131337658SMarcel Moolenaar 	xbp = &xop->xo_data;
1472d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
147331337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
147431337658SMarcel Moolenaar 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
147531337658SMarcel Moolenaar 
147631337658SMarcel Moolenaar 	va_copy(va_local, vap);
147731337658SMarcel Moolenaar 
147831337658SMarcel Moolenaar 	int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
147931337658SMarcel Moolenaar 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1480d1a0d267SMarcel Moolenaar 	if (rc >= left) {
1481c600d307SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, rc)) {
1482c600d307SMarcel Moolenaar 		va_end(va_local);
148331337658SMarcel Moolenaar 		return;
1484c600d307SMarcel Moolenaar 	    }
148531337658SMarcel Moolenaar 
148631337658SMarcel Moolenaar 	    va_end(vap);	/* Reset vap to the start */
148731337658SMarcel Moolenaar 	    va_copy(vap, va_local);
148831337658SMarcel Moolenaar 
148931337658SMarcel Moolenaar 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
149031337658SMarcel Moolenaar 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
149131337658SMarcel Moolenaar 	}
149231337658SMarcel Moolenaar 	va_end(va_local);
149331337658SMarcel Moolenaar 
1494d1a0d267SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
149531337658SMarcel Moolenaar 	xbp->xb_curp += rc;
149631337658SMarcel Moolenaar 
149731337658SMarcel Moolenaar 	if (need_nl && code > 0) {
149831337658SMarcel Moolenaar 	    const char *msg = strerror(code);
149931337658SMarcel Moolenaar 	    if (msg) {
150031337658SMarcel Moolenaar 		xo_buf_append(xbp, ": ", 2);
150131337658SMarcel Moolenaar 		xo_buf_append(xbp, msg, strlen(msg));
150231337658SMarcel Moolenaar 	    }
150331337658SMarcel Moolenaar 	}
150431337658SMarcel Moolenaar 
150531337658SMarcel Moolenaar 	if (need_nl)
1506d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1507d1a0d267SMarcel Moolenaar 
1508d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1509d1a0d267SMarcel Moolenaar 
1510d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
1511d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1512d1a0d267SMarcel Moolenaar 
1513545ddfbeSMarcel Moolenaar 	(void) xo_write(xop);
151431337658SMarcel Moolenaar 	break;
151531337658SMarcel Moolenaar 
151631337658SMarcel Moolenaar     case XO_STYLE_HTML:
151731337658SMarcel Moolenaar 	{
151831337658SMarcel Moolenaar 	    char buf[BUFSIZ], *bp = buf, *cp;
151931337658SMarcel Moolenaar 	    int bufsiz = sizeof(buf);
152031337658SMarcel Moolenaar 	    int rc2;
152131337658SMarcel Moolenaar 
152231337658SMarcel Moolenaar 	    va_copy(va_local, vap);
152331337658SMarcel Moolenaar 
1524c600d307SMarcel Moolenaar 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
152531337658SMarcel Moolenaar 	    if (rc > bufsiz) {
152631337658SMarcel Moolenaar 		bufsiz = rc + BUFSIZ;
152731337658SMarcel Moolenaar 		bp = alloca(bufsiz);
152831337658SMarcel Moolenaar 		va_end(va_local);
152931337658SMarcel Moolenaar 		va_copy(va_local, vap);
1530c600d307SMarcel Moolenaar 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
153131337658SMarcel Moolenaar 	    }
1532c600d307SMarcel Moolenaar 	    va_end(va_local);
153331337658SMarcel Moolenaar 	    cp = bp + rc;
153431337658SMarcel Moolenaar 
153531337658SMarcel Moolenaar 	    if (need_nl) {
153631337658SMarcel Moolenaar 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
153731337658SMarcel Moolenaar 			       (code > 0) ? ": " : "",
153831337658SMarcel Moolenaar 			       (code > 0) ? strerror(code) : "");
153931337658SMarcel Moolenaar 		if (rc2 > 0)
154031337658SMarcel Moolenaar 		    rc += rc2;
154131337658SMarcel Moolenaar 	    }
154231337658SMarcel Moolenaar 
154331337658SMarcel Moolenaar 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
154431337658SMarcel Moolenaar 	}
154531337658SMarcel Moolenaar 	break;
154631337658SMarcel Moolenaar 
154731337658SMarcel Moolenaar     case XO_STYLE_JSON:
1548d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
1549d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
1550d1a0d267SMarcel Moolenaar 	/* No means of representing messages */
1551d1a0d267SMarcel Moolenaar 	return;
155231337658SMarcel Moolenaar 
155331337658SMarcel Moolenaar     case XO_STYLE_TEXT:
155431337658SMarcel Moolenaar 	rc = xo_printf_v(xop, fmt, vap);
155531337658SMarcel Moolenaar 	/*
155631337658SMarcel Moolenaar 	 * XXX need to handle UTF-8 widths
155731337658SMarcel Moolenaar 	 */
155831337658SMarcel Moolenaar 	if (rc > 0) {
1559d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
156031337658SMarcel Moolenaar 		xop->xo_columns += rc;
1561d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
156231337658SMarcel Moolenaar 		xop->xo_anchor_columns += rc;
156331337658SMarcel Moolenaar 	}
156431337658SMarcel Moolenaar 
156531337658SMarcel Moolenaar 	if (need_nl && code > 0) {
156631337658SMarcel Moolenaar 	    const char *msg = strerror(code);
156731337658SMarcel Moolenaar 	    if (msg) {
156831337658SMarcel Moolenaar 		xo_printf(xop, ": %s", msg);
156931337658SMarcel Moolenaar 	    }
157031337658SMarcel Moolenaar 	}
157131337658SMarcel Moolenaar 	if (need_nl)
157231337658SMarcel Moolenaar 	    xo_printf(xop, "\n");
157331337658SMarcel Moolenaar 
157431337658SMarcel Moolenaar 	break;
157531337658SMarcel Moolenaar     }
157631337658SMarcel Moolenaar 
1577545ddfbeSMarcel Moolenaar     (void) xo_flush_h(xop);
157831337658SMarcel Moolenaar }
157931337658SMarcel Moolenaar 
158031337658SMarcel Moolenaar void
158131337658SMarcel Moolenaar xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
158231337658SMarcel Moolenaar {
158331337658SMarcel Moolenaar     va_list vap;
158431337658SMarcel Moolenaar 
158531337658SMarcel Moolenaar     va_start(vap, fmt);
158631337658SMarcel Moolenaar     xo_message_hcv(xop, code, fmt, vap);
158731337658SMarcel Moolenaar     va_end(vap);
158831337658SMarcel Moolenaar }
158931337658SMarcel Moolenaar 
159031337658SMarcel Moolenaar void
159131337658SMarcel Moolenaar xo_message_c (int code, const char *fmt, ...)
159231337658SMarcel Moolenaar {
159331337658SMarcel Moolenaar     va_list vap;
159431337658SMarcel Moolenaar 
159531337658SMarcel Moolenaar     va_start(vap, fmt);
159631337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
159731337658SMarcel Moolenaar     va_end(vap);
159831337658SMarcel Moolenaar }
159931337658SMarcel Moolenaar 
160031337658SMarcel Moolenaar void
1601d1a0d267SMarcel Moolenaar xo_message_e (const char *fmt, ...)
160231337658SMarcel Moolenaar {
160331337658SMarcel Moolenaar     int code = errno;
160431337658SMarcel Moolenaar     va_list vap;
160531337658SMarcel Moolenaar 
160631337658SMarcel Moolenaar     va_start(vap, fmt);
160731337658SMarcel Moolenaar     xo_message_hcv(NULL, code, fmt, vap);
160831337658SMarcel Moolenaar     va_end(vap);
160931337658SMarcel Moolenaar }
161031337658SMarcel Moolenaar 
1611d1a0d267SMarcel Moolenaar void
1612d1a0d267SMarcel Moolenaar xo_message (const char *fmt, ...)
1613d1a0d267SMarcel Moolenaar {
1614d1a0d267SMarcel Moolenaar     va_list vap;
1615d1a0d267SMarcel Moolenaar 
1616d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
1617d1a0d267SMarcel Moolenaar     xo_message_hcv(NULL, 0, fmt, vap);
1618d1a0d267SMarcel Moolenaar     va_end(vap);
1619d1a0d267SMarcel Moolenaar }
1620d1a0d267SMarcel Moolenaar 
162131337658SMarcel Moolenaar static void
162231337658SMarcel Moolenaar xo_failure (xo_handle_t *xop, const char *fmt, ...)
162331337658SMarcel Moolenaar {
1624d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_WARN))
162531337658SMarcel Moolenaar 	return;
162631337658SMarcel Moolenaar 
162731337658SMarcel Moolenaar     va_list vap;
162831337658SMarcel Moolenaar 
162931337658SMarcel Moolenaar     va_start(vap, fmt);
163031337658SMarcel Moolenaar     xo_warn_hcv(xop, -1, 1, fmt, vap);
163131337658SMarcel Moolenaar     va_end(vap);
163231337658SMarcel Moolenaar }
163331337658SMarcel Moolenaar 
163431337658SMarcel Moolenaar /**
163531337658SMarcel Moolenaar  * Create a handle for use by later libxo functions.
163631337658SMarcel Moolenaar  *
163731337658SMarcel Moolenaar  * Note: normal use of libxo does not require a distinct handle, since
163831337658SMarcel Moolenaar  * the default handle (used when NULL is passed) generates text on stdout.
163931337658SMarcel Moolenaar  *
164031337658SMarcel Moolenaar  * @style Style of output desired (XO_STYLE_* value)
164131337658SMarcel Moolenaar  * @flags Set of XOF_* flags in use with this handle
164231337658SMarcel Moolenaar  */
164331337658SMarcel Moolenaar xo_handle_t *
164431337658SMarcel Moolenaar xo_create (xo_style_t style, xo_xof_flags_t flags)
164531337658SMarcel Moolenaar {
164631337658SMarcel Moolenaar     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
164731337658SMarcel Moolenaar 
164831337658SMarcel Moolenaar     if (xop) {
164931337658SMarcel Moolenaar 	bzero(xop, sizeof(*xop));
165031337658SMarcel Moolenaar 
165131337658SMarcel Moolenaar 	xop->xo_style = style;
1652d1a0d267SMarcel Moolenaar 	XOF_SET(xop, flags);
165331337658SMarcel Moolenaar 	xo_init_handle(xop);
1654d1a0d267SMarcel Moolenaar 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
165531337658SMarcel Moolenaar     }
165631337658SMarcel Moolenaar 
165731337658SMarcel Moolenaar     return xop;
165831337658SMarcel Moolenaar }
165931337658SMarcel Moolenaar 
166031337658SMarcel Moolenaar /**
166131337658SMarcel Moolenaar  * Create a handle that will write to the given file.  Use
166231337658SMarcel Moolenaar  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
166331337658SMarcel Moolenaar  * @fp FILE pointer to use
166431337658SMarcel Moolenaar  * @style Style of output desired (XO_STYLE_* value)
166531337658SMarcel Moolenaar  * @flags Set of XOF_* flags to use with this handle
166631337658SMarcel Moolenaar  */
166731337658SMarcel Moolenaar xo_handle_t *
166831337658SMarcel Moolenaar xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
166931337658SMarcel Moolenaar {
167031337658SMarcel Moolenaar     xo_handle_t *xop = xo_create(style, flags);
167131337658SMarcel Moolenaar 
167231337658SMarcel Moolenaar     if (xop) {
167331337658SMarcel Moolenaar 	xop->xo_opaque = fp;
167431337658SMarcel Moolenaar 	xop->xo_write = xo_write_to_file;
167531337658SMarcel Moolenaar 	xop->xo_close = xo_close_file;
1676545ddfbeSMarcel Moolenaar 	xop->xo_flush = xo_flush_file;
167731337658SMarcel Moolenaar     }
167831337658SMarcel Moolenaar 
167931337658SMarcel Moolenaar     return xop;
168031337658SMarcel Moolenaar }
168131337658SMarcel Moolenaar 
168231337658SMarcel Moolenaar /**
168331337658SMarcel Moolenaar  * Release any resources held by the handle.
168431337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
168531337658SMarcel Moolenaar  */
168631337658SMarcel Moolenaar void
1687c600d307SMarcel Moolenaar xo_destroy (xo_handle_t *xop_arg)
168831337658SMarcel Moolenaar {
1689c600d307SMarcel Moolenaar     xo_handle_t *xop = xo_default(xop_arg);
169031337658SMarcel Moolenaar 
1691d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
1692d1a0d267SMarcel Moolenaar 
1693d1a0d267SMarcel Moolenaar     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
169431337658SMarcel Moolenaar 	xop->xo_close(xop->xo_opaque);
169531337658SMarcel Moolenaar 
169631337658SMarcel Moolenaar     xo_free(xop->xo_stack);
169731337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_data);
169831337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_fmt);
169931337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_predicate);
170031337658SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_attrs);
1701788ca347SMarcel Moolenaar     xo_buf_cleanup(&xop->xo_color_buf);
1702788ca347SMarcel Moolenaar 
1703788ca347SMarcel Moolenaar     if (xop->xo_version)
1704788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
170531337658SMarcel Moolenaar 
1706c600d307SMarcel Moolenaar     if (xop_arg == NULL) {
1707545ddfbeSMarcel Moolenaar 	bzero(&xo_default_handle, sizeof(xo_default_handle));
170831337658SMarcel Moolenaar 	xo_default_inited = 0;
170931337658SMarcel Moolenaar     } else
171031337658SMarcel Moolenaar 	xo_free(xop);
171131337658SMarcel Moolenaar }
171231337658SMarcel Moolenaar 
171331337658SMarcel Moolenaar /**
171431337658SMarcel Moolenaar  * Record a new output style to use for the given handle (or default if
171531337658SMarcel Moolenaar  * handle is NULL).  This output style will be used for any future output.
171631337658SMarcel Moolenaar  *
171731337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
171831337658SMarcel Moolenaar  * @style new output style (XO_STYLE_*)
171931337658SMarcel Moolenaar  */
172031337658SMarcel Moolenaar void
172131337658SMarcel Moolenaar xo_set_style (xo_handle_t *xop, xo_style_t style)
172231337658SMarcel Moolenaar {
172331337658SMarcel Moolenaar     xop = xo_default(xop);
172431337658SMarcel Moolenaar     xop->xo_style = style;
172531337658SMarcel Moolenaar }
172631337658SMarcel Moolenaar 
172731337658SMarcel Moolenaar xo_style_t
172831337658SMarcel Moolenaar xo_get_style (xo_handle_t *xop)
172931337658SMarcel Moolenaar {
173031337658SMarcel Moolenaar     xop = xo_default(xop);
1731788ca347SMarcel Moolenaar     return xo_style(xop);
173231337658SMarcel Moolenaar }
173331337658SMarcel Moolenaar 
173431337658SMarcel Moolenaar static int
173531337658SMarcel Moolenaar xo_name_to_style (const char *name)
173631337658SMarcel Moolenaar {
173731337658SMarcel Moolenaar     if (strcmp(name, "xml") == 0)
173831337658SMarcel Moolenaar 	return XO_STYLE_XML;
173931337658SMarcel Moolenaar     else if (strcmp(name, "json") == 0)
174031337658SMarcel Moolenaar 	return XO_STYLE_JSON;
1741d1a0d267SMarcel Moolenaar     else if (strcmp(name, "encoder") == 0)
1742d1a0d267SMarcel Moolenaar 	return XO_STYLE_ENCODER;
174331337658SMarcel Moolenaar     else if (strcmp(name, "text") == 0)
174431337658SMarcel Moolenaar 	return XO_STYLE_TEXT;
174531337658SMarcel Moolenaar     else if (strcmp(name, "html") == 0)
174631337658SMarcel Moolenaar 	return XO_STYLE_HTML;
1747d1a0d267SMarcel Moolenaar     else if (strcmp(name, "sdparams") == 0)
1748d1a0d267SMarcel Moolenaar 	return XO_STYLE_SDPARAMS;
174931337658SMarcel Moolenaar 
175031337658SMarcel Moolenaar     return -1;
175131337658SMarcel Moolenaar }
175231337658SMarcel Moolenaar 
175331337658SMarcel Moolenaar /*
1754d1a0d267SMarcel Moolenaar  * Indicate if the style is an "encoding" one as opposed to a "display" one.
1755d1a0d267SMarcel Moolenaar  */
1756d1a0d267SMarcel Moolenaar static int
1757d1a0d267SMarcel Moolenaar xo_style_is_encoding (xo_handle_t *xop)
1758d1a0d267SMarcel Moolenaar {
1759d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_JSON
1760d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_XML
1761d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_SDPARAMS
1762d1a0d267SMarcel Moolenaar 	|| xo_style(xop) == XO_STYLE_ENCODER)
1763d1a0d267SMarcel Moolenaar 	return 1;
1764d1a0d267SMarcel Moolenaar     return 0;
1765d1a0d267SMarcel Moolenaar }
1766d1a0d267SMarcel Moolenaar 
1767d1a0d267SMarcel Moolenaar /* Simple name-value mapping */
1768d1a0d267SMarcel Moolenaar typedef struct xo_mapping_s {
1769d1a0d267SMarcel Moolenaar     xo_xff_flags_t xm_value;
1770d1a0d267SMarcel Moolenaar     const char *xm_name;
1771d1a0d267SMarcel Moolenaar } xo_mapping_t;
1772d1a0d267SMarcel Moolenaar 
1773d1a0d267SMarcel Moolenaar static xo_xff_flags_t
1774d1a0d267SMarcel Moolenaar xo_name_lookup (xo_mapping_t *map, const char *value, int len)
1775d1a0d267SMarcel Moolenaar {
1776d1a0d267SMarcel Moolenaar     if (len == 0)
1777d1a0d267SMarcel Moolenaar 	return 0;
1778d1a0d267SMarcel Moolenaar 
1779d1a0d267SMarcel Moolenaar     if (len < 0)
1780d1a0d267SMarcel Moolenaar 	len = strlen(value);
1781d1a0d267SMarcel Moolenaar 
1782d1a0d267SMarcel Moolenaar     while (isspace((int) *value)) {
1783d1a0d267SMarcel Moolenaar 	value += 1;
1784d1a0d267SMarcel Moolenaar 	len -= 1;
1785d1a0d267SMarcel Moolenaar     }
1786d1a0d267SMarcel Moolenaar 
1787d1a0d267SMarcel Moolenaar     while (isspace((int) value[len]))
1788d1a0d267SMarcel Moolenaar 	len -= 1;
1789d1a0d267SMarcel Moolenaar 
1790d1a0d267SMarcel Moolenaar     if (*value == '\0')
1791d1a0d267SMarcel Moolenaar 	return 0;
1792d1a0d267SMarcel Moolenaar 
1793d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
1794d1a0d267SMarcel Moolenaar 	if (strncmp(map->xm_name, value, len) == 0)
1795d1a0d267SMarcel Moolenaar 	    return map->xm_value;
1796d1a0d267SMarcel Moolenaar 
1797d1a0d267SMarcel Moolenaar     return 0;
1798d1a0d267SMarcel Moolenaar }
1799d1a0d267SMarcel Moolenaar 
1800d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
1801d1a0d267SMarcel Moolenaar static const char *
1802d1a0d267SMarcel Moolenaar xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
1803d1a0d267SMarcel Moolenaar {
1804d1a0d267SMarcel Moolenaar     if (value == 0)
1805d1a0d267SMarcel Moolenaar 	return NULL;
1806d1a0d267SMarcel Moolenaar 
1807d1a0d267SMarcel Moolenaar     for ( ; map->xm_name; map++)
1808d1a0d267SMarcel Moolenaar 	if (map->xm_value == value)
1809d1a0d267SMarcel Moolenaar 	    return map->xm_name;
1810d1a0d267SMarcel Moolenaar 
1811d1a0d267SMarcel Moolenaar     return NULL;
1812d1a0d267SMarcel Moolenaar }
1813d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
1814d1a0d267SMarcel Moolenaar 
1815d1a0d267SMarcel Moolenaar static xo_mapping_t xo_xof_names[] = {
1816d1a0d267SMarcel Moolenaar     { XOF_COLOR_ALLOWED, "color" },
1817d1a0d267SMarcel Moolenaar     { XOF_COLUMNS, "columns" },
1818d1a0d267SMarcel Moolenaar     { XOF_DTRT, "dtrt" },
1819d1a0d267SMarcel Moolenaar     { XOF_FLUSH, "flush" },
1820d1a0d267SMarcel Moolenaar     { XOF_IGNORE_CLOSE, "ignore-close" },
1821d1a0d267SMarcel Moolenaar     { XOF_INFO, "info" },
1822d1a0d267SMarcel Moolenaar     { XOF_KEYS, "keys" },
1823d1a0d267SMarcel Moolenaar     { XOF_LOG_GETTEXT, "log-gettext" },
1824d1a0d267SMarcel Moolenaar     { XOF_LOG_SYSLOG, "log-syslog" },
1825d1a0d267SMarcel Moolenaar     { XOF_NO_HUMANIZE, "no-humanize" },
1826d1a0d267SMarcel Moolenaar     { XOF_NO_LOCALE, "no-locale" },
1827d1a0d267SMarcel Moolenaar     { XOF_NO_TOP, "no-top" },
1828d1a0d267SMarcel Moolenaar     { XOF_NOT_FIRST, "not-first" },
1829d1a0d267SMarcel Moolenaar     { XOF_PRETTY, "pretty" },
1830d1a0d267SMarcel Moolenaar     { XOF_UNDERSCORES, "underscores" },
1831d1a0d267SMarcel Moolenaar     { XOF_UNITS, "units" },
1832d1a0d267SMarcel Moolenaar     { XOF_WARN, "warn" },
1833d1a0d267SMarcel Moolenaar     { XOF_WARN_XML, "warn-xml" },
1834d1a0d267SMarcel Moolenaar     { XOF_XPATH, "xpath" },
1835d1a0d267SMarcel Moolenaar     { 0, NULL }
1836d1a0d267SMarcel Moolenaar };
1837d1a0d267SMarcel Moolenaar 
1838d1a0d267SMarcel Moolenaar /*
183931337658SMarcel Moolenaar  * Convert string name to XOF_* flag value.
184031337658SMarcel Moolenaar  * Not all are useful.  Or safe.  Or sane.
184131337658SMarcel Moolenaar  */
184231337658SMarcel Moolenaar static unsigned
184331337658SMarcel Moolenaar xo_name_to_flag (const char *name)
184431337658SMarcel Moolenaar {
1845d1a0d267SMarcel Moolenaar     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
184631337658SMarcel Moolenaar }
184731337658SMarcel Moolenaar 
184831337658SMarcel Moolenaar int
184931337658SMarcel Moolenaar xo_set_style_name (xo_handle_t *xop, const char *name)
185031337658SMarcel Moolenaar {
185131337658SMarcel Moolenaar     if (name == NULL)
185231337658SMarcel Moolenaar 	return -1;
185331337658SMarcel Moolenaar 
185431337658SMarcel Moolenaar     int style = xo_name_to_style(name);
185531337658SMarcel Moolenaar     if (style < 0)
185631337658SMarcel Moolenaar 	return -1;
185731337658SMarcel Moolenaar 
185831337658SMarcel Moolenaar     xo_set_style(xop, style);
185931337658SMarcel Moolenaar     return 0;
186031337658SMarcel Moolenaar }
186131337658SMarcel Moolenaar 
186231337658SMarcel Moolenaar /*
186331337658SMarcel Moolenaar  * Set the options for a handle using a string of options
186431337658SMarcel Moolenaar  * passed in.  The input is a comma-separated set of names
186531337658SMarcel Moolenaar  * and optional values: "xml,pretty,indent=4"
186631337658SMarcel Moolenaar  */
186731337658SMarcel Moolenaar int
186831337658SMarcel Moolenaar xo_set_options (xo_handle_t *xop, const char *input)
186931337658SMarcel Moolenaar {
187031337658SMarcel Moolenaar     char *cp, *ep, *vp, *np, *bp;
187131337658SMarcel Moolenaar     int style = -1, new_style, len, rc = 0;
187231337658SMarcel Moolenaar     xo_xof_flags_t new_flag;
187331337658SMarcel Moolenaar 
187431337658SMarcel Moolenaar     if (input == NULL)
187531337658SMarcel Moolenaar 	return 0;
187631337658SMarcel Moolenaar 
187731337658SMarcel Moolenaar     xop = xo_default(xop);
187831337658SMarcel Moolenaar 
1879788ca347SMarcel Moolenaar #ifdef LIBXO_COLOR_ON_BY_DEFAULT
1880788ca347SMarcel Moolenaar     /* If the installer used --enable-color-on-by-default, then we allow it */
1881d1a0d267SMarcel Moolenaar     XOF_SET(xop, XOF_COLOR_ALLOWED);
1882788ca347SMarcel Moolenaar #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
1883788ca347SMarcel Moolenaar 
188431337658SMarcel Moolenaar     /*
188531337658SMarcel Moolenaar      * We support a simpler, old-school style of giving option
188631337658SMarcel Moolenaar      * also, using a single character for each option.  It's
188731337658SMarcel Moolenaar      * ideal for lazy people, such as myself.
188831337658SMarcel Moolenaar      */
188931337658SMarcel Moolenaar     if (*input == ':') {
189031337658SMarcel Moolenaar 	int sz;
189131337658SMarcel Moolenaar 
189231337658SMarcel Moolenaar 	for (input++ ; *input; input++) {
189331337658SMarcel Moolenaar 	    switch (*input) {
1894788ca347SMarcel Moolenaar 	    case 'c':
1895d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_COLOR_ALLOWED);
1896788ca347SMarcel Moolenaar 		break;
1897788ca347SMarcel Moolenaar 
189831337658SMarcel Moolenaar 	    case 'f':
1899d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH);
190031337658SMarcel Moolenaar 		break;
190131337658SMarcel Moolenaar 
1902545ddfbeSMarcel Moolenaar 	    case 'F':
1903d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_FLUSH_LINE);
1904d1a0d267SMarcel Moolenaar 		break;
1905d1a0d267SMarcel Moolenaar 
1906d1a0d267SMarcel Moolenaar 	    case 'g':
1907d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_LOG_GETTEXT);
1908545ddfbeSMarcel Moolenaar 		break;
1909545ddfbeSMarcel Moolenaar 
191031337658SMarcel Moolenaar 	    case 'H':
191131337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_HTML;
191231337658SMarcel Moolenaar 		break;
191331337658SMarcel Moolenaar 
191431337658SMarcel Moolenaar 	    case 'I':
1915d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_INFO);
191631337658SMarcel Moolenaar 		break;
191731337658SMarcel Moolenaar 
191831337658SMarcel Moolenaar 	    case 'i':
191931337658SMarcel Moolenaar 		sz = strspn(input + 1, "0123456789");
192031337658SMarcel Moolenaar 		if (sz > 0) {
192131337658SMarcel Moolenaar 		    xop->xo_indent_by = atoi(input + 1);
192231337658SMarcel Moolenaar 		    input += sz - 1;	/* Skip value */
192331337658SMarcel Moolenaar 		}
192431337658SMarcel Moolenaar 		break;
192531337658SMarcel Moolenaar 
192631337658SMarcel Moolenaar 	    case 'J':
192731337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_JSON;
192831337658SMarcel Moolenaar 		break;
192931337658SMarcel Moolenaar 
1930d1a0d267SMarcel Moolenaar 	    case 'k':
1931d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_KEYS);
1932d1a0d267SMarcel Moolenaar 		break;
1933d1a0d267SMarcel Moolenaar 
1934d1a0d267SMarcel Moolenaar 	    case 'n':
1935d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_NO_HUMANIZE);
1936d1a0d267SMarcel Moolenaar 		break;
1937d1a0d267SMarcel Moolenaar 
193831337658SMarcel Moolenaar 	    case 'P':
1939d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_PRETTY);
194031337658SMarcel Moolenaar 		break;
194131337658SMarcel Moolenaar 
194231337658SMarcel Moolenaar 	    case 'T':
194331337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_TEXT;
194431337658SMarcel Moolenaar 		break;
194531337658SMarcel Moolenaar 
194631337658SMarcel Moolenaar 	    case 'U':
1947d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNITS);
194831337658SMarcel Moolenaar 		break;
194931337658SMarcel Moolenaar 
195031337658SMarcel Moolenaar 	    case 'u':
1951d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_UNDERSCORES);
195231337658SMarcel Moolenaar 		break;
195331337658SMarcel Moolenaar 
195431337658SMarcel Moolenaar 	    case 'W':
1955d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_WARN);
195631337658SMarcel Moolenaar 		break;
195731337658SMarcel Moolenaar 
195831337658SMarcel Moolenaar 	    case 'X':
195931337658SMarcel Moolenaar 		xop->xo_style = XO_STYLE_XML;
196031337658SMarcel Moolenaar 		break;
196131337658SMarcel Moolenaar 
196231337658SMarcel Moolenaar 	    case 'x':
1963d1a0d267SMarcel Moolenaar 		XOF_SET(xop, XOF_XPATH);
196431337658SMarcel Moolenaar 		break;
196531337658SMarcel Moolenaar 	    }
196631337658SMarcel Moolenaar 	}
196731337658SMarcel Moolenaar 	return 0;
196831337658SMarcel Moolenaar     }
196931337658SMarcel Moolenaar 
197031337658SMarcel Moolenaar     len = strlen(input) + 1;
197131337658SMarcel Moolenaar     bp = alloca(len);
197231337658SMarcel Moolenaar     memcpy(bp, input, len);
197331337658SMarcel Moolenaar 
197431337658SMarcel Moolenaar     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
197531337658SMarcel Moolenaar 	np = strchr(cp, ',');
197631337658SMarcel Moolenaar 	if (np)
197731337658SMarcel Moolenaar 	    *np++ = '\0';
197831337658SMarcel Moolenaar 
197931337658SMarcel Moolenaar 	vp = strchr(cp, '=');
198031337658SMarcel Moolenaar 	if (vp)
198131337658SMarcel Moolenaar 	    *vp++ = '\0';
198231337658SMarcel Moolenaar 
1983788ca347SMarcel Moolenaar 	if (strcmp("colors", cp) == 0) {
1984788ca347SMarcel Moolenaar 	    /* XXX Look for colors=red-blue+green-yellow */
1985788ca347SMarcel Moolenaar 	    continue;
1986788ca347SMarcel Moolenaar 	}
1987788ca347SMarcel Moolenaar 
1988d1a0d267SMarcel Moolenaar 	/*
1989d1a0d267SMarcel Moolenaar 	 * For options, we don't allow "encoder" since we want to
1990d1a0d267SMarcel Moolenaar 	 * handle it explicitly below as "encoder=xxx".
1991d1a0d267SMarcel Moolenaar 	 */
199231337658SMarcel Moolenaar 	new_style = xo_name_to_style(cp);
1993d1a0d267SMarcel Moolenaar 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
199431337658SMarcel Moolenaar 	    if (style >= 0)
199531337658SMarcel Moolenaar 		xo_warnx("ignoring multiple styles: '%s'", cp);
199631337658SMarcel Moolenaar 	    else
199731337658SMarcel Moolenaar 		style = new_style;
199831337658SMarcel Moolenaar 	} else {
199931337658SMarcel Moolenaar 	    new_flag = xo_name_to_flag(cp);
200031337658SMarcel Moolenaar 	    if (new_flag != 0)
2001d1a0d267SMarcel Moolenaar 		XOF_SET(xop, new_flag);
200231337658SMarcel Moolenaar 	    else {
2003788ca347SMarcel Moolenaar 		if (strcmp(cp, "no-color") == 0) {
2004d1a0d267SMarcel Moolenaar 		    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2005788ca347SMarcel Moolenaar 		} else if (strcmp(cp, "indent") == 0) {
2006d1a0d267SMarcel Moolenaar 		    if (vp)
200731337658SMarcel Moolenaar 			xop->xo_indent_by = atoi(vp);
2008d1a0d267SMarcel Moolenaar 		    else
2009d1a0d267SMarcel Moolenaar 			xo_failure(xop, "missing value for indent option");
2010d1a0d267SMarcel Moolenaar 		} else if (strcmp(cp, "encoder") == 0) {
2011d1a0d267SMarcel Moolenaar 		    if (vp == NULL)
2012d1a0d267SMarcel Moolenaar 			xo_failure(xop, "missing value for encoder option");
2013d1a0d267SMarcel Moolenaar 		    else {
2014d1a0d267SMarcel Moolenaar 			if (xo_encoder_init(xop, vp)) {
2015d1a0d267SMarcel Moolenaar 			    xo_failure(xop, "encoder not found: %s", vp);
2016d1a0d267SMarcel Moolenaar 			    rc = -1;
2017d1a0d267SMarcel Moolenaar 			}
2018d1a0d267SMarcel Moolenaar 		    }
2019d1a0d267SMarcel Moolenaar 
202031337658SMarcel Moolenaar 		} else {
2021d1a0d267SMarcel Moolenaar 		    xo_warnx("unknown libxo option value: '%s'", cp);
202231337658SMarcel Moolenaar 		    rc = -1;
202331337658SMarcel Moolenaar 		}
202431337658SMarcel Moolenaar 	    }
202531337658SMarcel Moolenaar 	}
202631337658SMarcel Moolenaar     }
202731337658SMarcel Moolenaar 
202831337658SMarcel Moolenaar     if (style > 0)
202931337658SMarcel Moolenaar 	xop->xo_style= style;
203031337658SMarcel Moolenaar 
203131337658SMarcel Moolenaar     return rc;
203231337658SMarcel Moolenaar }
203331337658SMarcel Moolenaar 
203431337658SMarcel Moolenaar /**
203531337658SMarcel Moolenaar  * Set one or more flags for a given handle (or default if handle is NULL).
203631337658SMarcel Moolenaar  * These flags will affect future output.
203731337658SMarcel Moolenaar  *
203831337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
203931337658SMarcel Moolenaar  * @flags Flags to be set (XOF_*)
204031337658SMarcel Moolenaar  */
204131337658SMarcel Moolenaar void
204231337658SMarcel Moolenaar xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
204331337658SMarcel Moolenaar {
204431337658SMarcel Moolenaar     xop = xo_default(xop);
204531337658SMarcel Moolenaar 
2046d1a0d267SMarcel Moolenaar     XOF_SET(xop, flags);
204731337658SMarcel Moolenaar }
204831337658SMarcel Moolenaar 
204931337658SMarcel Moolenaar xo_xof_flags_t
205031337658SMarcel Moolenaar xo_get_flags (xo_handle_t *xop)
205131337658SMarcel Moolenaar {
205231337658SMarcel Moolenaar     xop = xo_default(xop);
205331337658SMarcel Moolenaar 
205431337658SMarcel Moolenaar     return xop->xo_flags;
205531337658SMarcel Moolenaar }
205631337658SMarcel Moolenaar 
2057d1a0d267SMarcel Moolenaar /*
2058d1a0d267SMarcel Moolenaar  * strndup with a twist: len < 0 means strlen
2059d1a0d267SMarcel Moolenaar  */
2060d1a0d267SMarcel Moolenaar static char *
2061d1a0d267SMarcel Moolenaar xo_strndup (const char *str, int len)
2062d1a0d267SMarcel Moolenaar {
2063d1a0d267SMarcel Moolenaar     if (len < 0)
2064d1a0d267SMarcel Moolenaar 	len = strlen(str);
2065d1a0d267SMarcel Moolenaar 
2066d1a0d267SMarcel Moolenaar     char *cp = xo_realloc(NULL, len + 1);
2067d1a0d267SMarcel Moolenaar     if (cp) {
2068d1a0d267SMarcel Moolenaar 	memcpy(cp, str, len);
2069d1a0d267SMarcel Moolenaar 	cp[len] = '\0';
2070d1a0d267SMarcel Moolenaar     }
2071d1a0d267SMarcel Moolenaar 
2072d1a0d267SMarcel Moolenaar     return cp;
2073d1a0d267SMarcel Moolenaar }
2074d1a0d267SMarcel Moolenaar 
207531337658SMarcel Moolenaar /**
207631337658SMarcel Moolenaar  * Record a leading prefix for the XPath we generate.  This allows the
207731337658SMarcel Moolenaar  * generated data to be placed within an XML hierarchy but still have
207831337658SMarcel Moolenaar  * accurate XPath expressions.
207931337658SMarcel Moolenaar  *
208031337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
208131337658SMarcel Moolenaar  * @path The XPath expression
208231337658SMarcel Moolenaar  */
208331337658SMarcel Moolenaar void
208431337658SMarcel Moolenaar xo_set_leading_xpath (xo_handle_t *xop, const char *path)
208531337658SMarcel Moolenaar {
208631337658SMarcel Moolenaar     xop = xo_default(xop);
208731337658SMarcel Moolenaar 
208831337658SMarcel Moolenaar     if (xop->xo_leading_xpath) {
208931337658SMarcel Moolenaar 	xo_free(xop->xo_leading_xpath);
209031337658SMarcel Moolenaar 	xop->xo_leading_xpath = NULL;
209131337658SMarcel Moolenaar     }
209231337658SMarcel Moolenaar 
209331337658SMarcel Moolenaar     if (path == NULL)
209431337658SMarcel Moolenaar 	return;
209531337658SMarcel Moolenaar 
2096d1a0d267SMarcel Moolenaar     xop->xo_leading_xpath = xo_strndup(path, -1);
209731337658SMarcel Moolenaar }
209831337658SMarcel Moolenaar 
209931337658SMarcel Moolenaar /**
210031337658SMarcel Moolenaar  * Record the info data for a set of tags
210131337658SMarcel Moolenaar  *
210231337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
210331337658SMarcel Moolenaar  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
210431337658SMarcel Moolenaar  * @count Number of entries in info (or -1 to count them ourselves)
210531337658SMarcel Moolenaar  */
210631337658SMarcel Moolenaar void
210731337658SMarcel Moolenaar xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
210831337658SMarcel Moolenaar {
210931337658SMarcel Moolenaar     xop = xo_default(xop);
211031337658SMarcel Moolenaar 
211131337658SMarcel Moolenaar     if (count < 0 && infop) {
211231337658SMarcel Moolenaar 	xo_info_t *xip;
211331337658SMarcel Moolenaar 
211431337658SMarcel Moolenaar 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
211531337658SMarcel Moolenaar 	    continue;
211631337658SMarcel Moolenaar     }
211731337658SMarcel Moolenaar 
211831337658SMarcel Moolenaar     xop->xo_info = infop;
211931337658SMarcel Moolenaar     xop->xo_info_count = count;
212031337658SMarcel Moolenaar }
212131337658SMarcel Moolenaar 
212231337658SMarcel Moolenaar /**
212331337658SMarcel Moolenaar  * Set the formatter callback for a handle.  The callback should
212431337658SMarcel Moolenaar  * return a newly formatting contents of a formatting instruction,
212531337658SMarcel Moolenaar  * meaning the bits inside the braces.
212631337658SMarcel Moolenaar  */
212731337658SMarcel Moolenaar void
212831337658SMarcel Moolenaar xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
212931337658SMarcel Moolenaar 		  xo_checkpointer_t cfunc)
213031337658SMarcel Moolenaar {
213131337658SMarcel Moolenaar     xop = xo_default(xop);
213231337658SMarcel Moolenaar 
213331337658SMarcel Moolenaar     xop->xo_formatter = func;
213431337658SMarcel Moolenaar     xop->xo_checkpointer = cfunc;
213531337658SMarcel Moolenaar }
213631337658SMarcel Moolenaar 
213731337658SMarcel Moolenaar /**
213831337658SMarcel Moolenaar  * Clear one or more flags for a given handle (or default if handle is NULL).
213931337658SMarcel Moolenaar  * These flags will affect future output.
214031337658SMarcel Moolenaar  *
214131337658SMarcel Moolenaar  * @xop XO handle to alter (or NULL for default handle)
214231337658SMarcel Moolenaar  * @flags Flags to be cleared (XOF_*)
214331337658SMarcel Moolenaar  */
214431337658SMarcel Moolenaar void
214531337658SMarcel Moolenaar xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
214631337658SMarcel Moolenaar {
214731337658SMarcel Moolenaar     xop = xo_default(xop);
214831337658SMarcel Moolenaar 
2149d1a0d267SMarcel Moolenaar     XOF_CLEAR(xop, flags);
215031337658SMarcel Moolenaar }
215131337658SMarcel Moolenaar 
2152545ddfbeSMarcel Moolenaar static const char *
2153545ddfbeSMarcel Moolenaar xo_state_name (xo_state_t state)
2154545ddfbeSMarcel Moolenaar {
2155545ddfbeSMarcel Moolenaar     static const char *names[] = {
2156545ddfbeSMarcel Moolenaar 	"init",
2157545ddfbeSMarcel Moolenaar 	"open_container",
2158545ddfbeSMarcel Moolenaar 	"close_container",
2159545ddfbeSMarcel Moolenaar 	"open_list",
2160545ddfbeSMarcel Moolenaar 	"close_list",
2161545ddfbeSMarcel Moolenaar 	"open_instance",
2162545ddfbeSMarcel Moolenaar 	"close_instance",
2163545ddfbeSMarcel Moolenaar 	"open_leaf_list",
2164545ddfbeSMarcel Moolenaar 	"close_leaf_list",
2165545ddfbeSMarcel Moolenaar 	"discarding",
2166545ddfbeSMarcel Moolenaar 	"marker",
2167545ddfbeSMarcel Moolenaar 	"emit",
2168545ddfbeSMarcel Moolenaar 	"emit_leaf_list",
2169545ddfbeSMarcel Moolenaar 	"finish",
2170545ddfbeSMarcel Moolenaar 	NULL
2171545ddfbeSMarcel Moolenaar     };
2172545ddfbeSMarcel Moolenaar 
2173545ddfbeSMarcel Moolenaar     if (state < (sizeof(names) / sizeof(names[0])))
2174545ddfbeSMarcel Moolenaar 	return names[state];
2175545ddfbeSMarcel Moolenaar 
2176545ddfbeSMarcel Moolenaar     return "unknown";
2177545ddfbeSMarcel Moolenaar }
2178545ddfbeSMarcel Moolenaar 
217931337658SMarcel Moolenaar static void
218031337658SMarcel Moolenaar xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
218131337658SMarcel Moolenaar {
218231337658SMarcel Moolenaar     static char div_open[] = "<div class=\"line\">";
218331337658SMarcel Moolenaar     static char div_open_blank[] = "<div class=\"blank-line\">";
218431337658SMarcel Moolenaar 
2185d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
218631337658SMarcel Moolenaar 	return;
218731337658SMarcel Moolenaar 
2188788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_HTML)
218931337658SMarcel Moolenaar 	return;
219031337658SMarcel Moolenaar 
2191d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_DIV_OPEN);
219231337658SMarcel Moolenaar     if (flags & XFF_BLANK_LINE)
219331337658SMarcel Moolenaar 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
219431337658SMarcel Moolenaar     else
219531337658SMarcel Moolenaar 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
219631337658SMarcel Moolenaar 
2197d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
219831337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
219931337658SMarcel Moolenaar }
220031337658SMarcel Moolenaar 
220131337658SMarcel Moolenaar static void
220231337658SMarcel Moolenaar xo_line_close (xo_handle_t *xop)
220331337658SMarcel Moolenaar {
220431337658SMarcel Moolenaar     static char div_close[] = "</div>";
220531337658SMarcel Moolenaar 
2206788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
220731337658SMarcel Moolenaar     case XO_STYLE_HTML:
2208d1a0d267SMarcel Moolenaar 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
220931337658SMarcel Moolenaar 	    xo_line_ensure_open(xop, 0);
221031337658SMarcel Moolenaar 
2211d1a0d267SMarcel Moolenaar 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
221231337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
221331337658SMarcel Moolenaar 
2214d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
221531337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
221631337658SMarcel Moolenaar 	break;
221731337658SMarcel Moolenaar 
221831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
221931337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
222031337658SMarcel Moolenaar 	break;
222131337658SMarcel Moolenaar     }
222231337658SMarcel Moolenaar }
222331337658SMarcel Moolenaar 
222431337658SMarcel Moolenaar static int
222531337658SMarcel Moolenaar xo_info_compare (const void *key, const void *data)
222631337658SMarcel Moolenaar {
222731337658SMarcel Moolenaar     const char *name = key;
222831337658SMarcel Moolenaar     const xo_info_t *xip = data;
222931337658SMarcel Moolenaar 
223031337658SMarcel Moolenaar     return strcmp(name, xip->xi_name);
223131337658SMarcel Moolenaar }
223231337658SMarcel Moolenaar 
223331337658SMarcel Moolenaar 
223431337658SMarcel Moolenaar static xo_info_t *
223531337658SMarcel Moolenaar xo_info_find (xo_handle_t *xop, const char *name, int nlen)
223631337658SMarcel Moolenaar {
223731337658SMarcel Moolenaar     xo_info_t *xip;
223831337658SMarcel Moolenaar     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
223931337658SMarcel Moolenaar 
224031337658SMarcel Moolenaar     memcpy(cp, name, nlen);
224131337658SMarcel Moolenaar     cp[nlen] = '\0';
224231337658SMarcel Moolenaar 
224331337658SMarcel Moolenaar     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
224431337658SMarcel Moolenaar 		  sizeof(xop->xo_info[0]), xo_info_compare);
224531337658SMarcel Moolenaar     return xip;
224631337658SMarcel Moolenaar }
224731337658SMarcel Moolenaar 
224831337658SMarcel Moolenaar #define CONVERT(_have, _need) (((_have) << 8) | (_need))
224931337658SMarcel Moolenaar 
225031337658SMarcel Moolenaar /*
225131337658SMarcel Moolenaar  * Check to see that the conversion is safe and sane.
225231337658SMarcel Moolenaar  */
225331337658SMarcel Moolenaar static int
225431337658SMarcel Moolenaar xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
225531337658SMarcel Moolenaar {
225631337658SMarcel Moolenaar     switch (CONVERT(have_enc, need_enc)) {
225731337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
225831337658SMarcel Moolenaar     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
225931337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
226031337658SMarcel Moolenaar     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
226131337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
226231337658SMarcel Moolenaar     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
226331337658SMarcel Moolenaar 	return 0;
226431337658SMarcel Moolenaar 
226531337658SMarcel Moolenaar     default:
226631337658SMarcel Moolenaar 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
226731337658SMarcel Moolenaar 	return 1;
226831337658SMarcel Moolenaar     }
226931337658SMarcel Moolenaar }
227031337658SMarcel Moolenaar 
227131337658SMarcel Moolenaar static int
227231337658SMarcel Moolenaar xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
227331337658SMarcel Moolenaar 			 xo_xff_flags_t flags,
227431337658SMarcel Moolenaar 			 const wchar_t *wcp, const char *cp, int len, int max,
227531337658SMarcel Moolenaar 			 int need_enc, int have_enc)
227631337658SMarcel Moolenaar {
227731337658SMarcel Moolenaar     int cols = 0;
2278c600d307SMarcel Moolenaar     wchar_t wc = 0;
227931337658SMarcel Moolenaar     int ilen, olen, width;
228031337658SMarcel Moolenaar     int attr = (flags & XFF_ATTR);
228131337658SMarcel Moolenaar     const char *sp;
228231337658SMarcel Moolenaar 
228331337658SMarcel Moolenaar     if (len > 0 && !xo_buf_has_room(xbp, len))
228431337658SMarcel Moolenaar 	return 0;
228531337658SMarcel Moolenaar 
228631337658SMarcel Moolenaar     for (;;) {
228731337658SMarcel Moolenaar 	if (len == 0)
228831337658SMarcel Moolenaar 	    break;
228931337658SMarcel Moolenaar 
229031337658SMarcel Moolenaar 	if (cp) {
229131337658SMarcel Moolenaar 	    if (*cp == '\0')
229231337658SMarcel Moolenaar 		break;
229331337658SMarcel Moolenaar 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
229431337658SMarcel Moolenaar 		cp += 1;
229531337658SMarcel Moolenaar 		len -= 1;
229631337658SMarcel Moolenaar 	    }
229731337658SMarcel Moolenaar 	}
229831337658SMarcel Moolenaar 
229931337658SMarcel Moolenaar 	if (wcp && *wcp == L'\0')
230031337658SMarcel Moolenaar 	    break;
230131337658SMarcel Moolenaar 
230231337658SMarcel Moolenaar 	ilen = 0;
230331337658SMarcel Moolenaar 
230431337658SMarcel Moolenaar 	switch (have_enc) {
230531337658SMarcel Moolenaar 	case XF_ENC_WIDE:		/* Wide character */
230631337658SMarcel Moolenaar 	    wc = *wcp++;
230731337658SMarcel Moolenaar 	    ilen = 1;
230831337658SMarcel Moolenaar 	    break;
230931337658SMarcel Moolenaar 
231031337658SMarcel Moolenaar 	case XF_ENC_UTF8:		/* UTF-8 */
231131337658SMarcel Moolenaar 	    ilen = xo_utf8_to_wc_len(cp);
231231337658SMarcel Moolenaar 	    if (ilen < 0) {
231331337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2314d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
231531337658SMarcel Moolenaar 	    }
231631337658SMarcel Moolenaar 
231731337658SMarcel Moolenaar 	    if (len > 0 && len < ilen) {
231831337658SMarcel Moolenaar 		len = 0;	/* Break out of the loop */
231931337658SMarcel Moolenaar 		continue;
232031337658SMarcel Moolenaar 	    }
232131337658SMarcel Moolenaar 
232231337658SMarcel Moolenaar 	    wc = xo_utf8_char(cp, ilen);
232331337658SMarcel Moolenaar 	    if (wc == (wchar_t) -1) {
232431337658SMarcel Moolenaar 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
232531337658SMarcel Moolenaar 			   *cp, ilen);
2326d1a0d267SMarcel Moolenaar 		return -1;	/* Can't continue; we can't find the end */
232731337658SMarcel Moolenaar 	    }
232831337658SMarcel Moolenaar 	    cp += ilen;
232931337658SMarcel Moolenaar 	    break;
233031337658SMarcel Moolenaar 
233131337658SMarcel Moolenaar 	case XF_ENC_LOCALE:		/* Native locale */
233231337658SMarcel Moolenaar 	    ilen = (len > 0) ? len : MB_LEN_MAX;
233331337658SMarcel Moolenaar 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
233431337658SMarcel Moolenaar 	    if (ilen < 0) {		/* Invalid data; skip */
233531337658SMarcel Moolenaar 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2336dbf26257SAlexander Kabaev 		wc = L'?';
2337dbf26257SAlexander Kabaev 		ilen = 1;
233831337658SMarcel Moolenaar 	    }
2339d1a0d267SMarcel Moolenaar 
234031337658SMarcel Moolenaar 	    if (ilen == 0) {		/* Hit a wide NUL character */
234131337658SMarcel Moolenaar 		len = 0;
234231337658SMarcel Moolenaar 		continue;
234331337658SMarcel Moolenaar 	    }
234431337658SMarcel Moolenaar 
234531337658SMarcel Moolenaar 	    cp += ilen;
234631337658SMarcel Moolenaar 	    break;
234731337658SMarcel Moolenaar 	}
234831337658SMarcel Moolenaar 
234931337658SMarcel Moolenaar 	/* Reduce len, but not below zero */
235031337658SMarcel Moolenaar 	if (len > 0) {
235131337658SMarcel Moolenaar 	    len -= ilen;
235231337658SMarcel Moolenaar 	    if (len < 0)
235331337658SMarcel Moolenaar 		len = 0;
235431337658SMarcel Moolenaar 	}
235531337658SMarcel Moolenaar 
235631337658SMarcel Moolenaar 	/*
235731337658SMarcel Moolenaar 	 * Find the width-in-columns of this character, which must be done
235831337658SMarcel Moolenaar 	 * in wide characters, since we lack a mbswidth() function.  If
235931337658SMarcel Moolenaar 	 * it doesn't fit
236031337658SMarcel Moolenaar 	 */
2361d1a0d267SMarcel Moolenaar 	width = xo_wcwidth(wc);
236231337658SMarcel Moolenaar 	if (width < 0)
236331337658SMarcel Moolenaar 	    width = iswcntrl(wc) ? 0 : 1;
236431337658SMarcel Moolenaar 
2365788ca347SMarcel Moolenaar 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
236631337658SMarcel Moolenaar 	    if (max > 0 && cols + width > max)
236731337658SMarcel Moolenaar 		break;
236831337658SMarcel Moolenaar 	}
236931337658SMarcel Moolenaar 
237031337658SMarcel Moolenaar 	switch (need_enc) {
237131337658SMarcel Moolenaar 	case XF_ENC_UTF8:
237231337658SMarcel Moolenaar 
237331337658SMarcel Moolenaar 	    /* Output in UTF-8 needs to be escaped, based on the style */
2374788ca347SMarcel Moolenaar 	    switch (xo_style(xop)) {
237531337658SMarcel Moolenaar 	    case XO_STYLE_XML:
237631337658SMarcel Moolenaar 	    case XO_STYLE_HTML:
237731337658SMarcel Moolenaar 		if (wc == '<')
237831337658SMarcel Moolenaar 		    sp = xo_xml_lt;
237931337658SMarcel Moolenaar 		else if (wc == '>')
238031337658SMarcel Moolenaar 		    sp = xo_xml_gt;
238131337658SMarcel Moolenaar 		else if (wc == '&')
238231337658SMarcel Moolenaar 		    sp = xo_xml_amp;
238331337658SMarcel Moolenaar 		else if (attr && wc == '"')
238431337658SMarcel Moolenaar 		    sp = xo_xml_quot;
238531337658SMarcel Moolenaar 		else
238631337658SMarcel Moolenaar 		    break;
238731337658SMarcel Moolenaar 
238831337658SMarcel Moolenaar 		int slen = strlen(sp);
238931337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, slen - 1))
239031337658SMarcel Moolenaar 		    return -1;
239131337658SMarcel Moolenaar 
239231337658SMarcel Moolenaar 		memcpy(xbp->xb_curp, sp, slen);
239331337658SMarcel Moolenaar 		xbp->xb_curp += slen;
239431337658SMarcel Moolenaar 		goto done_with_encoding; /* Need multi-level 'break' */
239531337658SMarcel Moolenaar 
239631337658SMarcel Moolenaar 	    case XO_STYLE_JSON:
2397545ddfbeSMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
239831337658SMarcel Moolenaar 		    break;
239931337658SMarcel Moolenaar 
240031337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
240131337658SMarcel Moolenaar 		    return -1;
240231337658SMarcel Moolenaar 
240331337658SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2404545ddfbeSMarcel Moolenaar 		if (wc == '\n')
2405545ddfbeSMarcel Moolenaar 		    wc = 'n';
2406545ddfbeSMarcel Moolenaar 		else if (wc == '\r')
2407545ddfbeSMarcel Moolenaar 		    wc = 'r';
2408545ddfbeSMarcel Moolenaar 		else wc = wc & 0x7f;
2409545ddfbeSMarcel Moolenaar 
2410545ddfbeSMarcel Moolenaar 		*xbp->xb_curp++ = wc;
241131337658SMarcel Moolenaar 		goto done_with_encoding;
2412d1a0d267SMarcel Moolenaar 
2413d1a0d267SMarcel Moolenaar 	    case XO_STYLE_SDPARAMS:
2414d1a0d267SMarcel Moolenaar 		if (wc != '\\' && wc != '"' && wc != ']')
2415d1a0d267SMarcel Moolenaar 		    break;
2416d1a0d267SMarcel Moolenaar 
2417d1a0d267SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, 2))
2418d1a0d267SMarcel Moolenaar 		    return -1;
2419d1a0d267SMarcel Moolenaar 
2420d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = '\\';
2421d1a0d267SMarcel Moolenaar 		wc = wc & 0x7f;
2422d1a0d267SMarcel Moolenaar 		*xbp->xb_curp++ = wc;
2423d1a0d267SMarcel Moolenaar 		goto done_with_encoding;
242431337658SMarcel Moolenaar 	    }
242531337658SMarcel Moolenaar 
242631337658SMarcel Moolenaar 	    olen = xo_utf8_emit_len(wc);
242731337658SMarcel Moolenaar 	    if (olen < 0) {
242831337658SMarcel Moolenaar 		xo_failure(xop, "ignoring bad length");
242931337658SMarcel Moolenaar 		continue;
243031337658SMarcel Moolenaar 	    }
243131337658SMarcel Moolenaar 
243231337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, olen))
243331337658SMarcel Moolenaar 		return -1;
243431337658SMarcel Moolenaar 
243531337658SMarcel Moolenaar 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
243631337658SMarcel Moolenaar 	    xbp->xb_curp += olen;
243731337658SMarcel Moolenaar 	    break;
243831337658SMarcel Moolenaar 
243931337658SMarcel Moolenaar 	case XF_ENC_LOCALE:
244031337658SMarcel Moolenaar 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
244131337658SMarcel Moolenaar 		return -1;
244231337658SMarcel Moolenaar 
244331337658SMarcel Moolenaar 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
244431337658SMarcel Moolenaar 	    if (olen <= 0) {
244531337658SMarcel Moolenaar 		xo_failure(xop, "could not convert wide char: %lx",
244631337658SMarcel Moolenaar 			   (unsigned long) wc);
244731337658SMarcel Moolenaar 		width = 1;
244831337658SMarcel Moolenaar 		*xbp->xb_curp++ = '?';
244931337658SMarcel Moolenaar 	    } else
245031337658SMarcel Moolenaar 		xbp->xb_curp += olen;
245131337658SMarcel Moolenaar 	    break;
245231337658SMarcel Moolenaar 	}
245331337658SMarcel Moolenaar 
245431337658SMarcel Moolenaar     done_with_encoding:
245531337658SMarcel Moolenaar 	cols += width;
245631337658SMarcel Moolenaar     }
245731337658SMarcel Moolenaar 
245831337658SMarcel Moolenaar     return cols;
245931337658SMarcel Moolenaar }
246031337658SMarcel Moolenaar 
246131337658SMarcel Moolenaar static int
2462d1a0d267SMarcel Moolenaar xo_needed_encoding (xo_handle_t *xop)
2463d1a0d267SMarcel Moolenaar {
2464d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2465d1a0d267SMarcel Moolenaar 	return XF_ENC_UTF8;
2466d1a0d267SMarcel Moolenaar 
2467d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2468d1a0d267SMarcel Moolenaar 	return XF_ENC_LOCALE;
2469d1a0d267SMarcel Moolenaar 
2470d1a0d267SMarcel Moolenaar     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2471d1a0d267SMarcel Moolenaar }
2472d1a0d267SMarcel Moolenaar 
2473d1a0d267SMarcel Moolenaar static int
247431337658SMarcel Moolenaar xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
247531337658SMarcel Moolenaar 		  xo_format_t *xfp)
247631337658SMarcel Moolenaar {
247731337658SMarcel Moolenaar     static char null[] = "(null)";
2478d1a0d267SMarcel Moolenaar     static char null_no_quotes[] = "null";
2479a0f704ffSMarcel Moolenaar 
248031337658SMarcel Moolenaar     char *cp = NULL;
248131337658SMarcel Moolenaar     wchar_t *wcp = NULL;
248231337658SMarcel Moolenaar     int len, cols = 0, rc = 0;
248331337658SMarcel Moolenaar     int off = xbp->xb_curp - xbp->xb_bufp, off2;
2484d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
248531337658SMarcel Moolenaar 
248631337658SMarcel Moolenaar     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
248731337658SMarcel Moolenaar 	return 0;
248831337658SMarcel Moolenaar 
2489a0f704ffSMarcel Moolenaar     len = xfp->xf_width[XF_WIDTH_SIZE];
2490a0f704ffSMarcel Moolenaar 
2491d1a0d267SMarcel Moolenaar     if (xfp->xf_fc == 'm') {
2492d1a0d267SMarcel Moolenaar 	cp = strerror(xop->xo_errno);
2493d1a0d267SMarcel Moolenaar 	if (len < 0)
2494d1a0d267SMarcel Moolenaar 	    len = cp ? strlen(cp) : 0;
2495d1a0d267SMarcel Moolenaar 	goto normal_string;
2496d1a0d267SMarcel Moolenaar 
2497d1a0d267SMarcel Moolenaar     } else if (xfp->xf_enc == XF_ENC_WIDE) {
249831337658SMarcel Moolenaar 	wcp = va_arg(xop->xo_vap, wchar_t *);
249931337658SMarcel Moolenaar 	if (xfp->xf_skip)
250031337658SMarcel Moolenaar 	    return 0;
250131337658SMarcel Moolenaar 
2502a0f704ffSMarcel Moolenaar 	/*
2503a0f704ffSMarcel Moolenaar 	 * Dont' deref NULL; use the traditional "(null)" instead
2504a0f704ffSMarcel Moolenaar 	 * of the more accurate "who's been a naughty boy, then?".
2505a0f704ffSMarcel Moolenaar 	 */
2506a0f704ffSMarcel Moolenaar 	if (wcp == NULL) {
2507a0f704ffSMarcel Moolenaar 	    cp = null;
2508a0f704ffSMarcel Moolenaar 	    len = sizeof(null) - 1;
2509a0f704ffSMarcel Moolenaar 	}
2510a0f704ffSMarcel Moolenaar 
251131337658SMarcel Moolenaar     } else {
251231337658SMarcel Moolenaar 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2513d1a0d267SMarcel Moolenaar 
2514d1a0d267SMarcel Moolenaar     normal_string:
251531337658SMarcel Moolenaar 	if (xfp->xf_skip)
251631337658SMarcel Moolenaar 	    return 0;
251731337658SMarcel Moolenaar 
2518a0f704ffSMarcel Moolenaar 	/* Echo "Dont' deref NULL" logic */
2519a0f704ffSMarcel Moolenaar 	if (cp == NULL) {
2520d1a0d267SMarcel Moolenaar 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2521d1a0d267SMarcel Moolenaar 		cp = null_no_quotes;
2522d1a0d267SMarcel Moolenaar 		len = sizeof(null_no_quotes) - 1;
2523d1a0d267SMarcel Moolenaar 	    } else {
2524a0f704ffSMarcel Moolenaar 		cp = null;
2525a0f704ffSMarcel Moolenaar 		len = sizeof(null) - 1;
2526a0f704ffSMarcel Moolenaar 	    }
2527d1a0d267SMarcel Moolenaar 	}
2528a0f704ffSMarcel Moolenaar 
252931337658SMarcel Moolenaar 	/*
253031337658SMarcel Moolenaar 	 * Optimize the most common case, which is "%s".  We just
253131337658SMarcel Moolenaar 	 * need to copy the complete string to the output buffer.
253231337658SMarcel Moolenaar 	 */
253331337658SMarcel Moolenaar 	if (xfp->xf_enc == need_enc
253431337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
253531337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
253631337658SMarcel Moolenaar 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2537d1a0d267SMarcel Moolenaar 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2538d1a0d267SMarcel Moolenaar 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
253931337658SMarcel Moolenaar 	    len = strlen(cp);
254031337658SMarcel Moolenaar 	    xo_buf_escape(xop, xbp, cp, len, flags);
254131337658SMarcel Moolenaar 
254231337658SMarcel Moolenaar 	    /*
254331337658SMarcel Moolenaar 	     * Our caller expects xb_curp left untouched, so we have
254431337658SMarcel Moolenaar 	     * to reset it and return the number of bytes written to
254531337658SMarcel Moolenaar 	     * the buffer.
254631337658SMarcel Moolenaar 	     */
254731337658SMarcel Moolenaar 	    off2 = xbp->xb_curp - xbp->xb_bufp;
254831337658SMarcel Moolenaar 	    rc = off2 - off;
254931337658SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + off;
255031337658SMarcel Moolenaar 
255131337658SMarcel Moolenaar 	    return rc;
255231337658SMarcel Moolenaar 	}
255331337658SMarcel Moolenaar     }
255431337658SMarcel Moolenaar 
255531337658SMarcel Moolenaar     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
255631337658SMarcel Moolenaar 				   xfp->xf_width[XF_WIDTH_MAX],
255731337658SMarcel Moolenaar 				   need_enc, xfp->xf_enc);
255831337658SMarcel Moolenaar     if (cols < 0)
255931337658SMarcel Moolenaar 	goto bail;
256031337658SMarcel Moolenaar 
256131337658SMarcel Moolenaar     /*
256231337658SMarcel Moolenaar      * xo_buf_append* will move xb_curp, so we save/restore it.
256331337658SMarcel Moolenaar      */
256431337658SMarcel Moolenaar     off2 = xbp->xb_curp - xbp->xb_bufp;
256531337658SMarcel Moolenaar     rc = off2 - off;
256631337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
256731337658SMarcel Moolenaar 
256831337658SMarcel Moolenaar     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
256931337658SMarcel Moolenaar 	/*
257031337658SMarcel Moolenaar 	 * Find the number of columns needed to display the string.
257131337658SMarcel Moolenaar 	 * If we have the original wide string, we just call wcswidth,
257231337658SMarcel Moolenaar 	 * but if we did the work ourselves, then we need to do it.
257331337658SMarcel Moolenaar 	 */
257431337658SMarcel Moolenaar 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
257531337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, delta))
257631337658SMarcel Moolenaar 	    goto bail;
257731337658SMarcel Moolenaar 
257831337658SMarcel Moolenaar 	/*
257931337658SMarcel Moolenaar 	 * If seen_minus, then pad on the right; otherwise move it so
258031337658SMarcel Moolenaar 	 * we can pad on the left.
258131337658SMarcel Moolenaar 	 */
258231337658SMarcel Moolenaar 	if (xfp->xf_seen_minus) {
258331337658SMarcel Moolenaar 	    cp = xbp->xb_curp + rc;
258431337658SMarcel Moolenaar 	} else {
258531337658SMarcel Moolenaar 	    cp = xbp->xb_curp;
258631337658SMarcel Moolenaar 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
258731337658SMarcel Moolenaar 	}
258831337658SMarcel Moolenaar 
258931337658SMarcel Moolenaar 	/* Set the padding */
259031337658SMarcel Moolenaar 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
259131337658SMarcel Moolenaar 	rc += delta;
259231337658SMarcel Moolenaar 	cols += delta;
259331337658SMarcel Moolenaar     }
259431337658SMarcel Moolenaar 
2595d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
259631337658SMarcel Moolenaar 	xop->xo_columns += cols;
2597d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
259831337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
259931337658SMarcel Moolenaar 
260031337658SMarcel Moolenaar     return rc;
260131337658SMarcel Moolenaar 
260231337658SMarcel Moolenaar  bail:
260331337658SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + off;
260431337658SMarcel Moolenaar     return 0;
260531337658SMarcel Moolenaar }
260631337658SMarcel Moolenaar 
2607d1a0d267SMarcel Moolenaar /*
2608d1a0d267SMarcel Moolenaar  * Look backwards in a buffer to find a numeric value
2609d1a0d267SMarcel Moolenaar  */
2610d1a0d267SMarcel Moolenaar static int
2611d1a0d267SMarcel Moolenaar xo_buf_find_last_number (xo_buffer_t *xbp, int start_offset)
2612d1a0d267SMarcel Moolenaar {
2613d1a0d267SMarcel Moolenaar     int rc = 0;			/* Fail with zero */
2614d1a0d267SMarcel Moolenaar     int digit = 1;
2615d1a0d267SMarcel Moolenaar     char *sp = xbp->xb_bufp;
2616d1a0d267SMarcel Moolenaar     char *cp = sp + start_offset;
2617d1a0d267SMarcel Moolenaar 
2618d1a0d267SMarcel Moolenaar     while (--cp >= sp)
2619d1a0d267SMarcel Moolenaar 	if (isdigit((int) *cp))
2620d1a0d267SMarcel Moolenaar 	    break;
2621d1a0d267SMarcel Moolenaar 
2622d1a0d267SMarcel Moolenaar     for ( ; cp >= sp; cp--) {
2623d1a0d267SMarcel Moolenaar 	if (!isdigit((int) *cp))
2624d1a0d267SMarcel Moolenaar 	    break;
2625d1a0d267SMarcel Moolenaar 	rc += (*cp - '0') * digit;
2626d1a0d267SMarcel Moolenaar 	digit *= 10;
2627d1a0d267SMarcel Moolenaar     }
2628d1a0d267SMarcel Moolenaar 
2629d1a0d267SMarcel Moolenaar     return rc;
2630d1a0d267SMarcel Moolenaar }
2631d1a0d267SMarcel Moolenaar 
2632d1a0d267SMarcel Moolenaar static int
2633d1a0d267SMarcel Moolenaar xo_count_utf8_cols (const char *str, int len)
2634d1a0d267SMarcel Moolenaar {
2635d1a0d267SMarcel Moolenaar     int tlen;
2636d1a0d267SMarcel Moolenaar     wchar_t wc;
2637d1a0d267SMarcel Moolenaar     int cols = 0;
2638d1a0d267SMarcel Moolenaar     const char *ep = str + len;
2639d1a0d267SMarcel Moolenaar 
2640d1a0d267SMarcel Moolenaar     while (str < ep) {
2641d1a0d267SMarcel Moolenaar 	tlen = xo_utf8_to_wc_len(str);
2642d1a0d267SMarcel Moolenaar 	if (tlen < 0)		/* Broken input is very bad */
2643d1a0d267SMarcel Moolenaar 	    return cols;
2644d1a0d267SMarcel Moolenaar 
2645d1a0d267SMarcel Moolenaar 	wc = xo_utf8_char(str, tlen);
2646d1a0d267SMarcel Moolenaar 	if (wc == (wchar_t) -1)
2647d1a0d267SMarcel Moolenaar 	    return cols;
2648d1a0d267SMarcel Moolenaar 
2649d1a0d267SMarcel Moolenaar 	/* We only print printable characters */
2650d1a0d267SMarcel Moolenaar 	if (iswprint((wint_t) wc)) {
2651d1a0d267SMarcel Moolenaar 	    /*
2652d1a0d267SMarcel Moolenaar 	     * Find the width-in-columns of this character, which must be done
2653d1a0d267SMarcel Moolenaar 	     * in wide characters, since we lack a mbswidth() function.
2654d1a0d267SMarcel Moolenaar 	     */
2655d1a0d267SMarcel Moolenaar 	    int width = xo_wcwidth(wc);
2656d1a0d267SMarcel Moolenaar 	    if (width < 0)
2657d1a0d267SMarcel Moolenaar 		width = iswcntrl(wc) ? 0 : 1;
2658d1a0d267SMarcel Moolenaar 
2659d1a0d267SMarcel Moolenaar 	    cols += width;
2660d1a0d267SMarcel Moolenaar 	}
2661d1a0d267SMarcel Moolenaar 
2662d1a0d267SMarcel Moolenaar 	str += tlen;
2663d1a0d267SMarcel Moolenaar     }
2664d1a0d267SMarcel Moolenaar 
2665d1a0d267SMarcel Moolenaar     return cols;
2666d1a0d267SMarcel Moolenaar }
2667d1a0d267SMarcel Moolenaar 
2668d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
2669d1a0d267SMarcel Moolenaar static inline const char *
2670d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop, const char *str)
2671d1a0d267SMarcel Moolenaar {
2672d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
2673d1a0d267SMarcel Moolenaar     const char *res;
2674d1a0d267SMarcel Moolenaar 
2675d1a0d267SMarcel Moolenaar     res = dgettext(domainname, str);
2676d1a0d267SMarcel Moolenaar 
2677d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2678d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
2679d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "", xo_printable(domainname),
2680d1a0d267SMarcel Moolenaar 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
2681d1a0d267SMarcel Moolenaar 
2682d1a0d267SMarcel Moolenaar     return res;
2683d1a0d267SMarcel Moolenaar }
2684d1a0d267SMarcel Moolenaar 
2685d1a0d267SMarcel Moolenaar static inline const char *
2686d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
2687d1a0d267SMarcel Moolenaar 	      unsigned long int n)
2688d1a0d267SMarcel Moolenaar {
2689d1a0d267SMarcel Moolenaar     const char *domainname = xop->xo_gt_domain;
2690d1a0d267SMarcel Moolenaar     const char *res;
2691d1a0d267SMarcel Moolenaar 
2692d1a0d267SMarcel Moolenaar     res = dngettext(domainname, sing, plural, n);
2693d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2694d1a0d267SMarcel Moolenaar 	fprintf(stderr, "xo: gettext: %s%s%s"
2695d1a0d267SMarcel Moolenaar 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
2696d1a0d267SMarcel Moolenaar 		domainname ? "domain \"" : "",
2697d1a0d267SMarcel Moolenaar 		xo_printable(domainname), domainname ? "\", " : "",
2698d1a0d267SMarcel Moolenaar 		xo_printable(sing),
2699d1a0d267SMarcel Moolenaar 		xo_printable(plural), n, xo_printable(res));
2700d1a0d267SMarcel Moolenaar 
2701d1a0d267SMarcel Moolenaar     return res;
2702d1a0d267SMarcel Moolenaar }
2703d1a0d267SMarcel Moolenaar #else /* HAVE_GETTEXT */
2704d1a0d267SMarcel Moolenaar static inline const char *
2705d1a0d267SMarcel Moolenaar xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
2706d1a0d267SMarcel Moolenaar {
2707d1a0d267SMarcel Moolenaar     return str;
2708d1a0d267SMarcel Moolenaar }
2709d1a0d267SMarcel Moolenaar 
2710d1a0d267SMarcel Moolenaar static inline const char *
2711d1a0d267SMarcel Moolenaar xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
2712d1a0d267SMarcel Moolenaar 	      const char *plural, unsigned long int n)
2713d1a0d267SMarcel Moolenaar {
2714d1a0d267SMarcel Moolenaar     return (n == 1) ? singular : plural;
2715d1a0d267SMarcel Moolenaar }
2716d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
2717d1a0d267SMarcel Moolenaar 
2718d1a0d267SMarcel Moolenaar /*
2719d1a0d267SMarcel Moolenaar  * This is really _re_formatting, since the normal format code has
2720d1a0d267SMarcel Moolenaar  * generated a beautiful string into xo_data, starting at
2721d1a0d267SMarcel Moolenaar  * start_offset.  We need to see if it's plural, which means
2722d1a0d267SMarcel Moolenaar  * comma-separated options, or singular.  Then we make the appropriate
2723d1a0d267SMarcel Moolenaar  * call to d[n]gettext() to get the locale-based version.  Note that
2724d1a0d267SMarcel Moolenaar  * both input and output of gettext() this should be UTF-8.
2725d1a0d267SMarcel Moolenaar  */
2726d1a0d267SMarcel Moolenaar static int
2727d1a0d267SMarcel Moolenaar xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
2728d1a0d267SMarcel Moolenaar 		   int start_offset, int cols, int need_enc)
2729d1a0d267SMarcel Moolenaar {
2730d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
2731d1a0d267SMarcel Moolenaar 
2732d1a0d267SMarcel Moolenaar     if (!xo_buf_has_room(xbp, 1))
2733d1a0d267SMarcel Moolenaar 	return cols;
2734d1a0d267SMarcel Moolenaar 
2735d1a0d267SMarcel Moolenaar     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
2736d1a0d267SMarcel Moolenaar 
2737d1a0d267SMarcel Moolenaar     char *cp = xbp->xb_bufp + start_offset;
2738d1a0d267SMarcel Moolenaar     int len = xbp->xb_curp - cp;
2739d1a0d267SMarcel Moolenaar     const char *newstr = NULL;
2740d1a0d267SMarcel Moolenaar 
2741d1a0d267SMarcel Moolenaar     /*
2742d1a0d267SMarcel Moolenaar      * The plural flag asks us to look backwards at the last numeric
2743d1a0d267SMarcel Moolenaar      * value rendered and disect the string into two pieces.
2744d1a0d267SMarcel Moolenaar      */
2745d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_PLURAL) {
2746d1a0d267SMarcel Moolenaar 	int n = xo_buf_find_last_number(xbp, start_offset);
2747d1a0d267SMarcel Moolenaar 	char *two = memchr(cp, (int) ',', len);
2748d1a0d267SMarcel Moolenaar 	if (two == NULL) {
2749d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
2750d1a0d267SMarcel Moolenaar 	    return cols;
2751d1a0d267SMarcel Moolenaar 	}
2752d1a0d267SMarcel Moolenaar 
2753d1a0d267SMarcel Moolenaar 	if (two == cp) {
2754d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing before comma in plural gettext "
2755d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
2756d1a0d267SMarcel Moolenaar 	    return cols;
2757d1a0d267SMarcel Moolenaar 	}
2758d1a0d267SMarcel Moolenaar 
2759d1a0d267SMarcel Moolenaar 	if (two == xbp->xb_curp) {
2760d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "nothing after comma in plural gettext "
2761d1a0d267SMarcel Moolenaar 		       "field: '%s'", cp);
2762d1a0d267SMarcel Moolenaar 	    return cols;
2763d1a0d267SMarcel Moolenaar 	}
2764d1a0d267SMarcel Moolenaar 
2765d1a0d267SMarcel Moolenaar 	*two++ = '\0';
2766d1a0d267SMarcel Moolenaar 	if (flags & XFF_GT_FIELD) {
2767d1a0d267SMarcel Moolenaar 	    newstr = xo_dngettext(xop, cp, two, n);
2768d1a0d267SMarcel Moolenaar 	} else {
2769d1a0d267SMarcel Moolenaar 	    /* Don't do a gettext() look up, just get the plural form */
2770d1a0d267SMarcel Moolenaar 	    newstr = (n == 1) ? cp : two;
2771d1a0d267SMarcel Moolenaar 	}
2772d1a0d267SMarcel Moolenaar 
2773d1a0d267SMarcel Moolenaar 	/*
2774d1a0d267SMarcel Moolenaar 	 * If we returned the first string, optimize a bit by
2775d1a0d267SMarcel Moolenaar 	 * backing up over comma
2776d1a0d267SMarcel Moolenaar 	 */
2777d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
2778d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = two - 1; /* One for comma */
2779d1a0d267SMarcel Moolenaar 	    /*
2780d1a0d267SMarcel Moolenaar 	     * If the caller wanted UTF8, we're done; nothing changed,
2781d1a0d267SMarcel Moolenaar 	     * but we need to count the columns used.
2782d1a0d267SMarcel Moolenaar 	     */
2783d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
2784d1a0d267SMarcel Moolenaar 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
2785d1a0d267SMarcel Moolenaar 	}
2786d1a0d267SMarcel Moolenaar 
2787d1a0d267SMarcel Moolenaar     } else {
2788d1a0d267SMarcel Moolenaar 	/* The simple case (singular) */
2789d1a0d267SMarcel Moolenaar 	newstr = xo_dgettext(xop, cp);
2790d1a0d267SMarcel Moolenaar 
2791d1a0d267SMarcel Moolenaar 	if (newstr == cp) {
2792d1a0d267SMarcel Moolenaar 	    /* If the caller wanted UTF8, we're done; nothing changed */
2793d1a0d267SMarcel Moolenaar 	    if (need_enc == XF_ENC_UTF8)
2794d1a0d267SMarcel Moolenaar 		return cols;
2795d1a0d267SMarcel Moolenaar 	}
2796d1a0d267SMarcel Moolenaar     }
2797d1a0d267SMarcel Moolenaar 
2798d1a0d267SMarcel Moolenaar     /*
2799d1a0d267SMarcel Moolenaar      * Since the new string string might be in gettext's buffer or
2800d1a0d267SMarcel Moolenaar      * in the buffer (as the plural form), we make a copy.
2801d1a0d267SMarcel Moolenaar      */
2802d1a0d267SMarcel Moolenaar     int nlen = strlen(newstr);
2803d1a0d267SMarcel Moolenaar     char *newcopy = alloca(nlen + 1);
2804d1a0d267SMarcel Moolenaar     memcpy(newcopy, newstr, nlen + 1);
2805d1a0d267SMarcel Moolenaar 
2806d1a0d267SMarcel Moolenaar     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
2807d1a0d267SMarcel Moolenaar     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
2808d1a0d267SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
2809d1a0d267SMarcel Moolenaar }
2810d1a0d267SMarcel Moolenaar 
281131337658SMarcel Moolenaar static void
2812d1a0d267SMarcel Moolenaar xo_data_append_content (xo_handle_t *xop, const char *str, int len,
2813d1a0d267SMarcel Moolenaar 			xo_xff_flags_t flags)
281431337658SMarcel Moolenaar {
281531337658SMarcel Moolenaar     int cols;
2816d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
2817d1a0d267SMarcel Moolenaar     int start_offset = xo_buf_offset(&xop->xo_data);
281831337658SMarcel Moolenaar 
2819d1a0d267SMarcel Moolenaar     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
282031337658SMarcel Moolenaar 				   NULL, str, len, -1,
282131337658SMarcel Moolenaar 				   need_enc, XF_ENC_UTF8);
2822d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS)
2823d1a0d267SMarcel Moolenaar 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
282431337658SMarcel Moolenaar 
2825d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_COLUMNS))
282631337658SMarcel Moolenaar 	xop->xo_columns += cols;
2827d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
282831337658SMarcel Moolenaar 	xop->xo_anchor_columns += cols;
282931337658SMarcel Moolenaar }
283031337658SMarcel Moolenaar 
283131337658SMarcel Moolenaar static void
283231337658SMarcel Moolenaar xo_bump_width (xo_format_t *xfp, int digit)
283331337658SMarcel Moolenaar {
283431337658SMarcel Moolenaar     int *ip = &xfp->xf_width[xfp->xf_dots];
283531337658SMarcel Moolenaar 
283631337658SMarcel Moolenaar     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
283731337658SMarcel Moolenaar }
283831337658SMarcel Moolenaar 
283931337658SMarcel Moolenaar static int
284031337658SMarcel Moolenaar xo_trim_ws (xo_buffer_t *xbp, int len)
284131337658SMarcel Moolenaar {
284231337658SMarcel Moolenaar     char *cp, *sp, *ep;
284331337658SMarcel Moolenaar     int delta;
284431337658SMarcel Moolenaar 
284531337658SMarcel Moolenaar     /* First trim leading space */
284631337658SMarcel Moolenaar     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
284731337658SMarcel Moolenaar 	if (*cp != ' ')
284831337658SMarcel Moolenaar 	    break;
284931337658SMarcel Moolenaar     }
285031337658SMarcel Moolenaar 
285131337658SMarcel Moolenaar     delta = cp - sp;
285231337658SMarcel Moolenaar     if (delta) {
285331337658SMarcel Moolenaar 	len -= delta;
285431337658SMarcel Moolenaar 	memmove(sp, cp, len);
285531337658SMarcel Moolenaar     }
285631337658SMarcel Moolenaar 
285731337658SMarcel Moolenaar     /* Then trim off the end */
285831337658SMarcel Moolenaar     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
285931337658SMarcel Moolenaar 	if (ep[-1] != ' ')
286031337658SMarcel Moolenaar 	    break;
286131337658SMarcel Moolenaar     }
286231337658SMarcel Moolenaar 
286331337658SMarcel Moolenaar     delta = sp - ep;
286431337658SMarcel Moolenaar     if (delta) {
286531337658SMarcel Moolenaar 	len -= delta;
286631337658SMarcel Moolenaar 	cp[len] = '\0';
286731337658SMarcel Moolenaar     }
286831337658SMarcel Moolenaar 
286931337658SMarcel Moolenaar     return len;
287031337658SMarcel Moolenaar }
287131337658SMarcel Moolenaar 
2872d1a0d267SMarcel Moolenaar /*
2873d1a0d267SMarcel Moolenaar  * Interface to format a single field.  The arguments are in xo_vap,
2874d1a0d267SMarcel Moolenaar  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
2875d1a0d267SMarcel Moolenaar  * this is the most common case.
2876d1a0d267SMarcel Moolenaar  */
287731337658SMarcel Moolenaar static int
2878d1a0d267SMarcel Moolenaar xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
287931337658SMarcel Moolenaar 		const char *fmt, int flen, xo_xff_flags_t flags)
288031337658SMarcel Moolenaar {
288131337658SMarcel Moolenaar     xo_format_t xf;
288231337658SMarcel Moolenaar     const char *cp, *ep, *sp, *xp = NULL;
288331337658SMarcel Moolenaar     int rc, cols;
2884788ca347SMarcel Moolenaar     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
288531337658SMarcel Moolenaar     unsigned make_output = !(flags & XFF_NO_OUTPUT);
2886d1a0d267SMarcel Moolenaar     int need_enc = xo_needed_encoding(xop);
2887d1a0d267SMarcel Moolenaar     int real_need_enc = need_enc;
2888d1a0d267SMarcel Moolenaar     int old_cols = xop->xo_columns;
2889d1a0d267SMarcel Moolenaar 
2890d1a0d267SMarcel Moolenaar     /* The gettext interface is UTF-8, so we'll need that for now */
2891d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FIELD)
2892d1a0d267SMarcel Moolenaar 	need_enc = XF_ENC_UTF8;
289331337658SMarcel Moolenaar 
289431337658SMarcel Moolenaar     if (xbp == NULL)
289531337658SMarcel Moolenaar 	xbp = &xop->xo_data;
289631337658SMarcel Moolenaar 
2897d1a0d267SMarcel Moolenaar     unsigned start_offset = xo_buf_offset(xbp);
2898d1a0d267SMarcel Moolenaar 
289931337658SMarcel Moolenaar     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
2900d1a0d267SMarcel Moolenaar 	/*
2901d1a0d267SMarcel Moolenaar 	 * Since we're starting a new field, save the starting offset.
2902d1a0d267SMarcel Moolenaar 	 * We'll need this later for field-related operations.
2903d1a0d267SMarcel Moolenaar 	 */
2904d1a0d267SMarcel Moolenaar 
290531337658SMarcel Moolenaar 	if (*cp != '%') {
290631337658SMarcel Moolenaar 	add_one:
290731337658SMarcel Moolenaar 	    if (xp == NULL)
290831337658SMarcel Moolenaar 		xp = cp;
290931337658SMarcel Moolenaar 
291031337658SMarcel Moolenaar 	    if (*cp == '\\' && cp[1] != '\0')
291131337658SMarcel Moolenaar 		cp += 1;
291231337658SMarcel Moolenaar 	    continue;
291331337658SMarcel Moolenaar 
291431337658SMarcel Moolenaar 	} if (cp + 1 < ep && cp[1] == '%') {
291531337658SMarcel Moolenaar 	    cp += 1;
291631337658SMarcel Moolenaar 	    goto add_one;
291731337658SMarcel Moolenaar 	}
291831337658SMarcel Moolenaar 
291931337658SMarcel Moolenaar 	if (xp) {
292031337658SMarcel Moolenaar 	    if (make_output) {
292131337658SMarcel Moolenaar 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
292231337658SMarcel Moolenaar 					       NULL, xp, cp - xp, -1,
292331337658SMarcel Moolenaar 					       need_enc, XF_ENC_UTF8);
2924d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
292531337658SMarcel Moolenaar 		    xop->xo_columns += cols;
2926d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
292731337658SMarcel Moolenaar 		    xop->xo_anchor_columns += cols;
292831337658SMarcel Moolenaar 	    }
292931337658SMarcel Moolenaar 
293031337658SMarcel Moolenaar 	    xp = NULL;
293131337658SMarcel Moolenaar 	}
293231337658SMarcel Moolenaar 
293331337658SMarcel Moolenaar 	bzero(&xf, sizeof(xf));
293431337658SMarcel Moolenaar 	xf.xf_leading_zero = -1;
293531337658SMarcel Moolenaar 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
293631337658SMarcel Moolenaar 
293731337658SMarcel Moolenaar 	/*
293831337658SMarcel Moolenaar 	 * "%@" starts an XO-specific set of flags:
293931337658SMarcel Moolenaar 	 *   @X@ - XML-only field; ignored if style isn't XML
294031337658SMarcel Moolenaar 	 */
294131337658SMarcel Moolenaar 	if (cp[1] == '@') {
294231337658SMarcel Moolenaar 	    for (cp += 2; cp < ep; cp++) {
294331337658SMarcel Moolenaar 		if (*cp == '@') {
294431337658SMarcel Moolenaar 		    break;
294531337658SMarcel Moolenaar 		}
294631337658SMarcel Moolenaar 		if (*cp == '*') {
294731337658SMarcel Moolenaar 		    /*
294831337658SMarcel Moolenaar 		     * '*' means there's a "%*.*s" value in vap that
294931337658SMarcel Moolenaar 		     * we want to ignore
295031337658SMarcel Moolenaar 		     */
2951d1a0d267SMarcel Moolenaar 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
295231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
295331337658SMarcel Moolenaar 		}
295431337658SMarcel Moolenaar 	    }
295531337658SMarcel Moolenaar 	}
295631337658SMarcel Moolenaar 
295731337658SMarcel Moolenaar 	/* Hidden fields are only visible to JSON and XML */
2958d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
295931337658SMarcel Moolenaar 	    if (style != XO_STYLE_XML
2960d1a0d267SMarcel Moolenaar 		    && !xo_style_is_encoding(xop))
296131337658SMarcel Moolenaar 		xf.xf_skip = 1;
2962d1a0d267SMarcel Moolenaar 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
296331337658SMarcel Moolenaar 	    if (style != XO_STYLE_TEXT
2964788ca347SMarcel Moolenaar 		    && xo_style(xop) != XO_STYLE_HTML)
296531337658SMarcel Moolenaar 		xf.xf_skip = 1;
296631337658SMarcel Moolenaar 	}
296731337658SMarcel Moolenaar 
296831337658SMarcel Moolenaar 	if (!make_output)
296931337658SMarcel Moolenaar 	    xf.xf_skip = 1;
297031337658SMarcel Moolenaar 
297131337658SMarcel Moolenaar 	/*
297231337658SMarcel Moolenaar 	 * Looking at one piece of a format; find the end and
297331337658SMarcel Moolenaar 	 * call snprintf.  Then advance xo_vap on our own.
297431337658SMarcel Moolenaar 	 *
297531337658SMarcel Moolenaar 	 * Note that 'n', 'v', and '$' are not supported.
297631337658SMarcel Moolenaar 	 */
297731337658SMarcel Moolenaar 	sp = cp;		/* Save start pointer */
297831337658SMarcel Moolenaar 	for (cp += 1; cp < ep; cp++) {
297931337658SMarcel Moolenaar 	    if (*cp == 'l')
298031337658SMarcel Moolenaar 		xf.xf_lflag += 1;
298131337658SMarcel Moolenaar 	    else if (*cp == 'h')
298231337658SMarcel Moolenaar 		xf.xf_hflag += 1;
298331337658SMarcel Moolenaar 	    else if (*cp == 'j')
298431337658SMarcel Moolenaar 		xf.xf_jflag += 1;
298531337658SMarcel Moolenaar 	    else if (*cp == 't')
298631337658SMarcel Moolenaar 		xf.xf_tflag += 1;
298731337658SMarcel Moolenaar 	    else if (*cp == 'z')
298831337658SMarcel Moolenaar 		xf.xf_zflag += 1;
298931337658SMarcel Moolenaar 	    else if (*cp == 'q')
299031337658SMarcel Moolenaar 		xf.xf_qflag += 1;
299131337658SMarcel Moolenaar 	    else if (*cp == '.') {
299231337658SMarcel Moolenaar 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
299331337658SMarcel Moolenaar 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
299431337658SMarcel Moolenaar 		    return -1;
299531337658SMarcel Moolenaar 		}
299631337658SMarcel Moolenaar 	    } else if (*cp == '-')
299731337658SMarcel Moolenaar 		xf.xf_seen_minus = 1;
299831337658SMarcel Moolenaar 	    else if (isdigit((int) *cp)) {
299931337658SMarcel Moolenaar 		if (xf.xf_leading_zero < 0)
300031337658SMarcel Moolenaar 		    xf.xf_leading_zero = (*cp == '0');
300131337658SMarcel Moolenaar 		xo_bump_width(&xf, *cp - '0');
300231337658SMarcel Moolenaar 	    } else if (*cp == '*') {
300331337658SMarcel Moolenaar 		xf.xf_stars += 1;
300431337658SMarcel Moolenaar 		xf.xf_star[xf.xf_dots] = 1;
3005d1a0d267SMarcel Moolenaar 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
300631337658SMarcel Moolenaar 		break;
300731337658SMarcel Moolenaar 	    else if (*cp == 'n' || *cp == 'v') {
300831337658SMarcel Moolenaar 		xo_failure(xop, "unsupported format: '%s'", fmt);
300931337658SMarcel Moolenaar 		return -1;
301031337658SMarcel Moolenaar 	    }
301131337658SMarcel Moolenaar 	}
301231337658SMarcel Moolenaar 
301331337658SMarcel Moolenaar 	if (cp == ep)
301431337658SMarcel Moolenaar 	    xo_failure(xop, "field format missing format character: %s",
301531337658SMarcel Moolenaar 			  fmt);
301631337658SMarcel Moolenaar 
301731337658SMarcel Moolenaar 	xf.xf_fc = *cp;
301831337658SMarcel Moolenaar 
3019d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
302031337658SMarcel Moolenaar 	    if (*cp == 's' || *cp == 'S') {
302131337658SMarcel Moolenaar 		/* Handle "%*.*.*s" */
302231337658SMarcel Moolenaar 		int s;
302331337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
302431337658SMarcel Moolenaar 		    if (xf.xf_star[s]) {
302531337658SMarcel Moolenaar 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
302631337658SMarcel Moolenaar 
302731337658SMarcel Moolenaar 			/* Normalize a negative width value */
302831337658SMarcel Moolenaar 			if (xf.xf_width[s] < 0) {
302931337658SMarcel Moolenaar 			    if (s == 0) {
303031337658SMarcel Moolenaar 				xf.xf_width[0] = -xf.xf_width[0];
303131337658SMarcel Moolenaar 				xf.xf_seen_minus = 1;
303231337658SMarcel Moolenaar 			    } else
303331337658SMarcel Moolenaar 				xf.xf_width[s] = -1; /* Ignore negative values */
303431337658SMarcel Moolenaar 			}
303531337658SMarcel Moolenaar 		    }
303631337658SMarcel Moolenaar 		}
303731337658SMarcel Moolenaar 	    }
303831337658SMarcel Moolenaar 	}
303931337658SMarcel Moolenaar 
304031337658SMarcel Moolenaar 	/* If no max is given, it defaults to size */
304131337658SMarcel Moolenaar 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
304231337658SMarcel Moolenaar 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
304331337658SMarcel Moolenaar 
304431337658SMarcel Moolenaar 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
304531337658SMarcel Moolenaar 	    xf.xf_lflag = 1;
304631337658SMarcel Moolenaar 
304731337658SMarcel Moolenaar 	if (!xf.xf_skip) {
304831337658SMarcel Moolenaar 	    xo_buffer_t *fbp = &xop->xo_fmt;
304931337658SMarcel Moolenaar 	    int len = cp - sp + 1;
305031337658SMarcel Moolenaar 	    if (!xo_buf_has_room(fbp, len + 1))
305131337658SMarcel Moolenaar 		return -1;
305231337658SMarcel Moolenaar 
305331337658SMarcel Moolenaar 	    char *newfmt = fbp->xb_curp;
305431337658SMarcel Moolenaar 	    memcpy(newfmt, sp, len);
305531337658SMarcel Moolenaar 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
305631337658SMarcel Moolenaar 	    newfmt[len] = '\0';
305731337658SMarcel Moolenaar 
305831337658SMarcel Moolenaar 	    /*
305931337658SMarcel Moolenaar 	     * Bad news: our strings are UTF-8, but the stock printf
306031337658SMarcel Moolenaar 	     * functions won't handle field widths for wide characters
306131337658SMarcel Moolenaar 	     * correctly.  So we have to handle this ourselves.
306231337658SMarcel Moolenaar 	     */
306331337658SMarcel Moolenaar 	    if (xop->xo_formatter == NULL
3064d1a0d267SMarcel Moolenaar 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3065d1a0d267SMarcel Moolenaar 			|| xf.xf_fc == 'm')) {
3066d1a0d267SMarcel Moolenaar 
3067d1a0d267SMarcel Moolenaar 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3068d1a0d267SMarcel Moolenaar 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3069d1a0d267SMarcel Moolenaar 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3070d1a0d267SMarcel Moolenaar 
307131337658SMarcel Moolenaar 		rc = xo_format_string(xop, xbp, flags, &xf);
307231337658SMarcel Moolenaar 
3073d1a0d267SMarcel Moolenaar 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
307431337658SMarcel Moolenaar 		    rc = xo_trim_ws(xbp, rc);
307531337658SMarcel Moolenaar 
307631337658SMarcel Moolenaar 	    } else {
307731337658SMarcel Moolenaar 		int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
307831337658SMarcel Moolenaar 
307931337658SMarcel Moolenaar 		/*
308031337658SMarcel Moolenaar 		 * For XML and HTML, we need "&<>" processing; for JSON,
308131337658SMarcel Moolenaar 		 * it's quotes.  Text gets nothing.
308231337658SMarcel Moolenaar 		 */
308331337658SMarcel Moolenaar 		switch (style) {
308431337658SMarcel Moolenaar 		case XO_STYLE_XML:
308531337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
308631337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
308731337658SMarcel Moolenaar 		    /* fall thru */
308831337658SMarcel Moolenaar 		case XO_STYLE_HTML:
308931337658SMarcel Moolenaar 		    rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
309031337658SMarcel Moolenaar 		    break;
309131337658SMarcel Moolenaar 
309231337658SMarcel Moolenaar 		case XO_STYLE_JSON:
309331337658SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
309431337658SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3095d1a0d267SMarcel Moolenaar 		    rc = xo_escape_json(xbp, rc, 0);
3096d1a0d267SMarcel Moolenaar 		    break;
3097d1a0d267SMarcel Moolenaar 
3098d1a0d267SMarcel Moolenaar 		case XO_STYLE_SDPARAMS:
3099d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3100d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
3101d1a0d267SMarcel Moolenaar 		    rc = xo_escape_sdparams(xbp, rc, 0);
3102d1a0d267SMarcel Moolenaar 		    break;
3103d1a0d267SMarcel Moolenaar 
3104d1a0d267SMarcel Moolenaar 		case XO_STYLE_ENCODER:
3105d1a0d267SMarcel Moolenaar 		    if (flags & XFF_TRIM_WS)
3106d1a0d267SMarcel Moolenaar 			columns = rc = xo_trim_ws(xbp, rc);
310731337658SMarcel Moolenaar 		    break;
310831337658SMarcel Moolenaar 		}
310931337658SMarcel Moolenaar 
311031337658SMarcel Moolenaar 		/*
3111d1a0d267SMarcel Moolenaar 		 * We can assume all the non-%s data we've
3112d1a0d267SMarcel Moolenaar 		 * added is ASCII, so the columns and bytes are the
3113d1a0d267SMarcel Moolenaar 		 * same.  xo_format_string handles all the fancy
3114d1a0d267SMarcel Moolenaar 		 * string conversions and updates xo_anchor_columns
3115d1a0d267SMarcel Moolenaar 		 * accordingly.
311631337658SMarcel Moolenaar 		 */
3117d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
311831337658SMarcel Moolenaar 		    xop->xo_columns += columns;
3119d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
312031337658SMarcel Moolenaar 		    xop->xo_anchor_columns += columns;
312131337658SMarcel Moolenaar 	    }
312231337658SMarcel Moolenaar 
312331337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
312431337658SMarcel Moolenaar 	}
312531337658SMarcel Moolenaar 
312631337658SMarcel Moolenaar 	/*
312731337658SMarcel Moolenaar 	 * Now for the tricky part: we need to move the argument pointer
312831337658SMarcel Moolenaar 	 * along by the amount needed.
312931337658SMarcel Moolenaar 	 */
3130d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
313131337658SMarcel Moolenaar 
313231337658SMarcel Moolenaar 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
313331337658SMarcel Moolenaar 		/*
313431337658SMarcel Moolenaar 		 * The 'S' and 's' formats are normally handled in
313531337658SMarcel Moolenaar 		 * xo_format_string, but if we skipped it, then we
313631337658SMarcel Moolenaar 		 * need to pop it.
313731337658SMarcel Moolenaar 		 */
313831337658SMarcel Moolenaar 		if (xf.xf_skip)
313931337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, char *);
314031337658SMarcel Moolenaar 
3141d1a0d267SMarcel Moolenaar 	    } else if (xf.xf_fc == 'm') {
3142d1a0d267SMarcel Moolenaar 		/* Nothing on the stack for "%m" */
3143d1a0d267SMarcel Moolenaar 
314431337658SMarcel Moolenaar 	    } else {
314531337658SMarcel Moolenaar 		int s;
314631337658SMarcel Moolenaar 		for (s = 0; s < XF_WIDTH_NUM; s++) {
314731337658SMarcel Moolenaar 		    if (xf.xf_star[s])
314831337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
314931337658SMarcel Moolenaar 		}
315031337658SMarcel Moolenaar 
315131337658SMarcel Moolenaar 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
315231337658SMarcel Moolenaar 		    if (xf.xf_hflag > 1) {
315331337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
315431337658SMarcel Moolenaar 
315531337658SMarcel Moolenaar 		    } else if (xf.xf_hflag > 0) {
315631337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
315731337658SMarcel Moolenaar 
315831337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 1) {
315931337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long long);
316031337658SMarcel Moolenaar 
316131337658SMarcel Moolenaar 		    } else if (xf.xf_lflag > 0) {
316231337658SMarcel Moolenaar 			va_arg(xop->xo_vap, unsigned long);
316331337658SMarcel Moolenaar 
316431337658SMarcel Moolenaar 		    } else if (xf.xf_jflag > 0) {
316531337658SMarcel Moolenaar 			va_arg(xop->xo_vap, intmax_t);
316631337658SMarcel Moolenaar 
316731337658SMarcel Moolenaar 		    } else if (xf.xf_tflag > 0) {
316831337658SMarcel Moolenaar 			va_arg(xop->xo_vap, ptrdiff_t);
316931337658SMarcel Moolenaar 
317031337658SMarcel Moolenaar 		    } else if (xf.xf_zflag > 0) {
317131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, size_t);
317231337658SMarcel Moolenaar 
317331337658SMarcel Moolenaar 		    } else if (xf.xf_qflag > 0) {
317431337658SMarcel Moolenaar 			va_arg(xop->xo_vap, quad_t);
317531337658SMarcel Moolenaar 
317631337658SMarcel Moolenaar 		    } else {
317731337658SMarcel Moolenaar 			va_arg(xop->xo_vap, int);
317831337658SMarcel Moolenaar 		    }
317931337658SMarcel Moolenaar 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
318031337658SMarcel Moolenaar 		    if (xf.xf_lflag)
318131337658SMarcel Moolenaar 			va_arg(xop->xo_vap, long double);
318231337658SMarcel Moolenaar 		    else
318331337658SMarcel Moolenaar 			va_arg(xop->xo_vap, double);
318431337658SMarcel Moolenaar 
318531337658SMarcel Moolenaar 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
318631337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, wint_t);
318731337658SMarcel Moolenaar 
318831337658SMarcel Moolenaar 		else if (xf.xf_fc == 'c')
318931337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, int);
319031337658SMarcel Moolenaar 
319131337658SMarcel Moolenaar 		else if (xf.xf_fc == 'p')
319231337658SMarcel Moolenaar 		    va_arg(xop->xo_vap, void *);
319331337658SMarcel Moolenaar 	    }
319431337658SMarcel Moolenaar 	}
319531337658SMarcel Moolenaar     }
319631337658SMarcel Moolenaar 
319731337658SMarcel Moolenaar     if (xp) {
319831337658SMarcel Moolenaar 	if (make_output) {
319931337658SMarcel Moolenaar 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
320031337658SMarcel Moolenaar 					   NULL, xp, cp - xp, -1,
320131337658SMarcel Moolenaar 					   need_enc, XF_ENC_UTF8);
3202d1a0d267SMarcel Moolenaar 
3203d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_COLUMNS))
320431337658SMarcel Moolenaar 		xop->xo_columns += cols;
3205d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
320631337658SMarcel Moolenaar 		xop->xo_anchor_columns += cols;
320731337658SMarcel Moolenaar 	}
320831337658SMarcel Moolenaar 
320931337658SMarcel Moolenaar 	xp = NULL;
321031337658SMarcel Moolenaar     }
321131337658SMarcel Moolenaar 
3212d1a0d267SMarcel Moolenaar     if (flags & XFF_GT_FLAGS) {
3213d1a0d267SMarcel Moolenaar 	/*
3214d1a0d267SMarcel Moolenaar 	 * Handle gettext()ing the field by looking up the value
3215d1a0d267SMarcel Moolenaar 	 * and then copying it in, while converting to locale, if
3216d1a0d267SMarcel Moolenaar 	 * needed.
3217d1a0d267SMarcel Moolenaar 	 */
3218d1a0d267SMarcel Moolenaar 	int new_cols = xo_format_gettext(xop, flags, start_offset,
3219d1a0d267SMarcel Moolenaar 					 old_cols, real_need_enc);
3220d1a0d267SMarcel Moolenaar 
3221d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_COLUMNS))
3222d1a0d267SMarcel Moolenaar 	    xop->xo_columns += new_cols - old_cols;
3223d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3224d1a0d267SMarcel Moolenaar 	    xop->xo_anchor_columns += new_cols - old_cols;
3225d1a0d267SMarcel Moolenaar     }
3226d1a0d267SMarcel Moolenaar 
322731337658SMarcel Moolenaar     return 0;
322831337658SMarcel Moolenaar }
322931337658SMarcel Moolenaar 
323031337658SMarcel Moolenaar static char *
323131337658SMarcel Moolenaar xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
323231337658SMarcel Moolenaar {
323331337658SMarcel Moolenaar     char *cp = encoding;
323431337658SMarcel Moolenaar 
323531337658SMarcel Moolenaar     if (cp[0] != '%' || !isdigit((int) cp[1]))
323631337658SMarcel Moolenaar 	return encoding;
323731337658SMarcel Moolenaar 
323831337658SMarcel Moolenaar     for (cp += 2; *cp; cp++) {
323931337658SMarcel Moolenaar 	if (!isdigit((int) *cp))
324031337658SMarcel Moolenaar 	    break;
324131337658SMarcel Moolenaar     }
324231337658SMarcel Moolenaar 
324331337658SMarcel Moolenaar     cp -= 1;
324431337658SMarcel Moolenaar     *cp = '%';
324531337658SMarcel Moolenaar 
324631337658SMarcel Moolenaar     return cp;
324731337658SMarcel Moolenaar }
324831337658SMarcel Moolenaar 
324931337658SMarcel Moolenaar static void
3250788ca347SMarcel Moolenaar xo_color_append_html (xo_handle_t *xop)
3251788ca347SMarcel Moolenaar {
3252788ca347SMarcel Moolenaar     /*
3253788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3254788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3255788ca347SMarcel Moolenaar      */
3256788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3257788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3258788ca347SMarcel Moolenaar 
3259788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3260788ca347SMarcel Moolenaar     }
3261788ca347SMarcel Moolenaar }
3262788ca347SMarcel Moolenaar 
3263d1a0d267SMarcel Moolenaar /*
3264d1a0d267SMarcel Moolenaar  * A wrapper for humanize_number that autoscales, since the
3265d1a0d267SMarcel Moolenaar  * HN_AUTOSCALE flag scales as needed based on the size of
3266d1a0d267SMarcel Moolenaar  * the output buffer, not the size of the value.  I also
3267d1a0d267SMarcel Moolenaar  * wish HN_DECIMAL was more imperative, without the <10
3268d1a0d267SMarcel Moolenaar  * test.  But the boat only goes where we want when we hold
3269d1a0d267SMarcel Moolenaar  * the rudder, so xo_humanize fixes part of the problem.
3270d1a0d267SMarcel Moolenaar  */
3271d1a0d267SMarcel Moolenaar static int
3272d1a0d267SMarcel Moolenaar xo_humanize (char *buf, int len, uint64_t value, int flags)
3273d1a0d267SMarcel Moolenaar {
3274d1a0d267SMarcel Moolenaar     int scale = 0;
3275d1a0d267SMarcel Moolenaar 
3276d1a0d267SMarcel Moolenaar     if (value) {
3277d1a0d267SMarcel Moolenaar 	uint64_t left = value;
3278d1a0d267SMarcel Moolenaar 
3279d1a0d267SMarcel Moolenaar 	if (flags & HN_DIVISOR_1000) {
3280d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3281d1a0d267SMarcel Moolenaar 		left /= 1000;
3282d1a0d267SMarcel Moolenaar 	} else {
3283d1a0d267SMarcel Moolenaar 	    for ( ; left; scale++)
3284d1a0d267SMarcel Moolenaar 		left /= 1024;
3285d1a0d267SMarcel Moolenaar 	}
3286d1a0d267SMarcel Moolenaar 	scale -= 1;
3287d1a0d267SMarcel Moolenaar     }
3288d1a0d267SMarcel Moolenaar 
3289d1a0d267SMarcel Moolenaar     return xo_humanize_number(buf, len, value, "", scale, flags);
3290d1a0d267SMarcel Moolenaar }
3291d1a0d267SMarcel Moolenaar 
3292d1a0d267SMarcel Moolenaar /*
3293d1a0d267SMarcel Moolenaar  * This is an area where we can save information from the handle for
3294d1a0d267SMarcel Moolenaar  * later restoration.  We need to know what data was rendered to know
3295d1a0d267SMarcel Moolenaar  * what needs cleaned up.
3296d1a0d267SMarcel Moolenaar  */
3297d1a0d267SMarcel Moolenaar typedef struct xo_humanize_save_s {
3298d1a0d267SMarcel Moolenaar     unsigned xhs_offset;	/* Saved xo_offset */
3299d1a0d267SMarcel Moolenaar     unsigned xhs_columns;	/* Saved xo_columns */
3300d1a0d267SMarcel Moolenaar     unsigned xhs_anchor_columns; /* Saved xo_anchor_columns */
3301d1a0d267SMarcel Moolenaar } xo_humanize_save_t;
3302d1a0d267SMarcel Moolenaar 
3303d1a0d267SMarcel Moolenaar /*
3304d1a0d267SMarcel Moolenaar  * Format a "humanized" value for a numeric, meaning something nice
3305d1a0d267SMarcel Moolenaar  * like "44M" instead of "44470272".  We autoscale, choosing the
3306d1a0d267SMarcel Moolenaar  * most appropriate value for K/M/G/T/P/E based on the value given.
3307d1a0d267SMarcel Moolenaar  */
3308d1a0d267SMarcel Moolenaar static void
3309d1a0d267SMarcel Moolenaar xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3310d1a0d267SMarcel Moolenaar 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3311d1a0d267SMarcel Moolenaar {
3312d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3313d1a0d267SMarcel Moolenaar 	return;
3314d1a0d267SMarcel Moolenaar 
3315d1a0d267SMarcel Moolenaar     unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
3316d1a0d267SMarcel Moolenaar     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3317d1a0d267SMarcel Moolenaar 	return;
3318d1a0d267SMarcel Moolenaar 
3319d1a0d267SMarcel Moolenaar     /*
3320d1a0d267SMarcel Moolenaar      * We have a string that's allegedly a number. We want to
3321d1a0d267SMarcel Moolenaar      * humanize it, which means turning it back into a number
3322d1a0d267SMarcel Moolenaar      * and calling xo_humanize_number on it.
3323d1a0d267SMarcel Moolenaar      */
3324d1a0d267SMarcel Moolenaar     uint64_t value;
3325d1a0d267SMarcel Moolenaar     char *ep;
3326d1a0d267SMarcel Moolenaar 
3327d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3328d1a0d267SMarcel Moolenaar 
3329d1a0d267SMarcel Moolenaar     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3330d1a0d267SMarcel Moolenaar     if (!(value == ULLONG_MAX && errno == ERANGE)
3331d1a0d267SMarcel Moolenaar 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3332d1a0d267SMarcel Moolenaar 	/*
3333d1a0d267SMarcel Moolenaar 	 * There are few values where humanize_number needs
3334d1a0d267SMarcel Moolenaar 	 * more bytes than the original value.  I've used
3335d1a0d267SMarcel Moolenaar 	 * 10 as a rectal number to cover those scenarios.
3336d1a0d267SMarcel Moolenaar 	 */
3337d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, 10)) {
3338d1a0d267SMarcel Moolenaar 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3339d1a0d267SMarcel Moolenaar 
3340d1a0d267SMarcel Moolenaar 	    int rc;
3341d1a0d267SMarcel Moolenaar 	    int left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3342d1a0d267SMarcel Moolenaar 	    int hn_flags = HN_NOSPACE; /* On by default */
3343d1a0d267SMarcel Moolenaar 
3344d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_SPACE)
3345d1a0d267SMarcel Moolenaar 		hn_flags &= ~HN_NOSPACE;
3346d1a0d267SMarcel Moolenaar 
3347d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_DECIMAL)
3348d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DECIMAL;
3349d1a0d267SMarcel Moolenaar 
3350d1a0d267SMarcel Moolenaar 	    if (flags & XFF_HN_1000)
3351d1a0d267SMarcel Moolenaar 		hn_flags |= HN_DIVISOR_1000;
3352d1a0d267SMarcel Moolenaar 
3353d1a0d267SMarcel Moolenaar 	    rc = xo_humanize(xbp->xb_curp,
3354d1a0d267SMarcel Moolenaar 			     left, value, hn_flags);
3355d1a0d267SMarcel Moolenaar 	    if (rc > 0) {
3356d1a0d267SMarcel Moolenaar 		xbp->xb_curp += rc;
3357d1a0d267SMarcel Moolenaar 		xop->xo_columns = savep->xhs_columns + rc;
3358d1a0d267SMarcel Moolenaar 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3359d1a0d267SMarcel Moolenaar 	    }
3360d1a0d267SMarcel Moolenaar 	}
3361d1a0d267SMarcel Moolenaar     }
3362d1a0d267SMarcel Moolenaar }
3363d1a0d267SMarcel Moolenaar 
3364788ca347SMarcel Moolenaar static void
336531337658SMarcel Moolenaar xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
336631337658SMarcel Moolenaar 		   const char *name, int nlen,
336731337658SMarcel Moolenaar 		   const char *value, int vlen,
336831337658SMarcel Moolenaar 		   const char *encoding, int elen)
336931337658SMarcel Moolenaar {
337031337658SMarcel Moolenaar     static char div_start[] = "<div class=\"";
337131337658SMarcel Moolenaar     static char div_tag[] = "\" data-tag=\"";
337231337658SMarcel Moolenaar     static char div_xpath[] = "\" data-xpath=\"";
337331337658SMarcel Moolenaar     static char div_key[] = "\" data-key=\"key";
337431337658SMarcel Moolenaar     static char div_end[] = "\">";
337531337658SMarcel Moolenaar     static char div_close[] = "</div>";
337631337658SMarcel Moolenaar 
3377a321cc5dSPhil Shafer     /* The encoding format defaults to the normal format */
3378a321cc5dSPhil Shafer     if (encoding == NULL) {
3379a321cc5dSPhil Shafer 	char *enc  = alloca(vlen + 1);
3380a321cc5dSPhil Shafer 	memcpy(enc, value, vlen);
3381a321cc5dSPhil Shafer 	enc[vlen] = '\0';
3382a321cc5dSPhil Shafer 	encoding = xo_fix_encoding(xop, enc);
3383a321cc5dSPhil Shafer 	elen = strlen(encoding);
3384a321cc5dSPhil Shafer     }
3385a321cc5dSPhil Shafer 
338631337658SMarcel Moolenaar     /*
338731337658SMarcel Moolenaar      * To build our XPath predicate, we need to save the va_list before
338831337658SMarcel Moolenaar      * we format our data, and then restore it before we format the
338931337658SMarcel Moolenaar      * xpath expression.
339031337658SMarcel Moolenaar      * Display-only keys implies that we've got an encode-only key
339131337658SMarcel Moolenaar      * elsewhere, so we don't use them from making predicates.
339231337658SMarcel Moolenaar      */
339331337658SMarcel Moolenaar     int need_predidate =
339431337658SMarcel Moolenaar 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
3395d1a0d267SMarcel Moolenaar 	     && XOF_ISSET(xop, XOF_XPATH));
339631337658SMarcel Moolenaar 
339731337658SMarcel Moolenaar     if (need_predidate) {
339831337658SMarcel Moolenaar 	va_list va_local;
339931337658SMarcel Moolenaar 
340031337658SMarcel Moolenaar 	va_copy(va_local, xop->xo_vap);
340131337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
340231337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
340331337658SMarcel Moolenaar 
340431337658SMarcel Moolenaar 	/*
340531337658SMarcel Moolenaar 	 * Build an XPath predicate expression to match this key.
340631337658SMarcel Moolenaar 	 * We use the format buffer.
340731337658SMarcel Moolenaar 	 */
340831337658SMarcel Moolenaar 	xo_buffer_t *pbp = &xop->xo_predicate;
340931337658SMarcel Moolenaar 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
341031337658SMarcel Moolenaar 
341131337658SMarcel Moolenaar 	xo_buf_append(pbp, "[", 1);
341231337658SMarcel Moolenaar 	xo_buf_escape(xop, pbp, name, nlen, 0);
3413d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
341431337658SMarcel Moolenaar 	    xo_buf_append(pbp, " = '", 4);
341531337658SMarcel Moolenaar 	else
341631337658SMarcel Moolenaar 	    xo_buf_append(pbp, "='", 2);
341731337658SMarcel Moolenaar 
3418d1a0d267SMarcel Moolenaar 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3419d1a0d267SMarcel Moolenaar 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3420d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
342131337658SMarcel Moolenaar 
342231337658SMarcel Moolenaar 	xo_buf_append(pbp, "']", 2);
342331337658SMarcel Moolenaar 
342431337658SMarcel Moolenaar 	/* Now we record this predicate expression in the stack */
342531337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
342631337658SMarcel Moolenaar 	int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
342731337658SMarcel Moolenaar 	int dlen = pbp->xb_curp - pbp->xb_bufp;
342831337658SMarcel Moolenaar 
342931337658SMarcel Moolenaar 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
343031337658SMarcel Moolenaar 	if (cp) {
343131337658SMarcel Moolenaar 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
343231337658SMarcel Moolenaar 	    cp[olen + dlen] = '\0';
343331337658SMarcel Moolenaar 	    xsp->xs_keys = cp;
343431337658SMarcel Moolenaar 	}
343531337658SMarcel Moolenaar 
343631337658SMarcel Moolenaar 	/* Now we reset the xo_vap as if we were never here */
343731337658SMarcel Moolenaar 	va_end(xop->xo_vap);
343831337658SMarcel Moolenaar 	va_copy(xop->xo_vap, va_local);
343931337658SMarcel Moolenaar 	va_end(va_local);
344031337658SMarcel Moolenaar 	if (xop->xo_checkpointer)
344131337658SMarcel Moolenaar 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
344231337658SMarcel Moolenaar     }
344331337658SMarcel Moolenaar 
344431337658SMarcel Moolenaar     if (flags & XFF_ENCODE_ONLY) {
344531337658SMarcel Moolenaar 	/*
344631337658SMarcel Moolenaar 	 * Even if this is encode-only, we need to go thru the
344731337658SMarcel Moolenaar 	 * work of formatting it to make sure the args are cleared
344831337658SMarcel Moolenaar 	 * from xo_vap.
344931337658SMarcel Moolenaar 	 */
3450d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, encoding, elen,
345131337658SMarcel Moolenaar 		       flags | XFF_NO_OUTPUT);
345231337658SMarcel Moolenaar 	return;
345331337658SMarcel Moolenaar     }
345431337658SMarcel Moolenaar 
345531337658SMarcel Moolenaar     xo_line_ensure_open(xop, 0);
345631337658SMarcel Moolenaar 
3457d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
345831337658SMarcel Moolenaar 	xo_buf_indent(xop, xop->xo_indent_by);
345931337658SMarcel Moolenaar 
346031337658SMarcel Moolenaar     xo_data_append(xop, div_start, sizeof(div_start) - 1);
346131337658SMarcel Moolenaar     xo_data_append(xop, class, strlen(class));
346231337658SMarcel Moolenaar 
3463788ca347SMarcel Moolenaar     /*
3464788ca347SMarcel Moolenaar      * If the color buffer has content, we add it now.  It's already
3465788ca347SMarcel Moolenaar      * prebuilt and ready, since we want to add it to every <div>.
3466788ca347SMarcel Moolenaar      */
3467788ca347SMarcel Moolenaar     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3468788ca347SMarcel Moolenaar 	xo_buffer_t *xbp = &xop->xo_color_buf;
3469788ca347SMarcel Moolenaar 
3470788ca347SMarcel Moolenaar 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3471788ca347SMarcel Moolenaar     }
3472788ca347SMarcel Moolenaar 
347331337658SMarcel Moolenaar     if (name) {
347431337658SMarcel Moolenaar 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
347531337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
347631337658SMarcel Moolenaar 
347731337658SMarcel Moolenaar 	/*
347831337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
347931337658SMarcel Moolenaar 	 */
3480d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
3481d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
348231337658SMarcel Moolenaar 	    /*
348331337658SMarcel Moolenaar 	     * Note: We need the '+1' here because we know we've not
348431337658SMarcel Moolenaar 	     * added the closing quote.  We add one, knowing the quote
348531337658SMarcel Moolenaar 	     * will be added shortly.
348631337658SMarcel Moolenaar 	     */
348731337658SMarcel Moolenaar 	    xop->xo_units_offset =
348831337658SMarcel Moolenaar 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
348931337658SMarcel Moolenaar 	}
349031337658SMarcel Moolenaar 
3491d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_XPATH)) {
349231337658SMarcel Moolenaar 	    int i;
349331337658SMarcel Moolenaar 	    xo_stack_t *xsp;
349431337658SMarcel Moolenaar 
349531337658SMarcel Moolenaar 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
349631337658SMarcel Moolenaar 	    if (xop->xo_leading_xpath)
349731337658SMarcel Moolenaar 		xo_data_append(xop, xop->xo_leading_xpath,
349831337658SMarcel Moolenaar 			       strlen(xop->xo_leading_xpath));
349931337658SMarcel Moolenaar 
350031337658SMarcel Moolenaar 	    for (i = 0; i <= xop->xo_depth; i++) {
350131337658SMarcel Moolenaar 		xsp = &xop->xo_stack[i];
350231337658SMarcel Moolenaar 		if (xsp->xs_name == NULL)
350331337658SMarcel Moolenaar 		    continue;
350431337658SMarcel Moolenaar 
3505545ddfbeSMarcel Moolenaar 		/*
3506545ddfbeSMarcel Moolenaar 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3507545ddfbeSMarcel Moolenaar 		 * are directly under XSS_OPEN_INSTANCE frames so we
3508545ddfbeSMarcel Moolenaar 		 * don't need to put these in our XPath expressions.
3509545ddfbeSMarcel Moolenaar 		 */
3510545ddfbeSMarcel Moolenaar 		if (xsp->xs_state == XSS_OPEN_LIST
3511545ddfbeSMarcel Moolenaar 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3512545ddfbeSMarcel Moolenaar 		    continue;
3513545ddfbeSMarcel Moolenaar 
351431337658SMarcel Moolenaar 		xo_data_append(xop, "/", 1);
351531337658SMarcel Moolenaar 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
351631337658SMarcel Moolenaar 		if (xsp->xs_keys) {
351731337658SMarcel Moolenaar 		    /* Don't show keys for the key field */
351831337658SMarcel Moolenaar 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
351931337658SMarcel Moolenaar 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
352031337658SMarcel Moolenaar 		}
352131337658SMarcel Moolenaar 	    }
352231337658SMarcel Moolenaar 
352331337658SMarcel Moolenaar 	    xo_data_append(xop, "/", 1);
352431337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
352531337658SMarcel Moolenaar 	}
352631337658SMarcel Moolenaar 
3527d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
352831337658SMarcel Moolenaar 	    static char in_type[] = "\" data-type=\"";
352931337658SMarcel Moolenaar 	    static char in_help[] = "\" data-help=\"";
353031337658SMarcel Moolenaar 
353131337658SMarcel Moolenaar 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
353231337658SMarcel Moolenaar 	    if (xip) {
353331337658SMarcel Moolenaar 		if (xip->xi_type) {
353431337658SMarcel Moolenaar 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
353531337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
353631337658SMarcel Moolenaar 		}
353731337658SMarcel Moolenaar 		if (xip->xi_help) {
353831337658SMarcel Moolenaar 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
353931337658SMarcel Moolenaar 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
354031337658SMarcel Moolenaar 		}
354131337658SMarcel Moolenaar 	    }
354231337658SMarcel Moolenaar 	}
354331337658SMarcel Moolenaar 
3544d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
354531337658SMarcel Moolenaar 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
354631337658SMarcel Moolenaar     }
354731337658SMarcel Moolenaar 
3548d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
3549d1a0d267SMarcel Moolenaar     unsigned base_offset = xbp->xb_curp - xbp->xb_bufp;
3550d1a0d267SMarcel Moolenaar 
355131337658SMarcel Moolenaar     xo_data_append(xop, div_end, sizeof(div_end) - 1);
355231337658SMarcel Moolenaar 
3553d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
3554d1a0d267SMarcel Moolenaar 
3555d1a0d267SMarcel Moolenaar     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
3556d1a0d267SMarcel Moolenaar     save.xhs_columns = xop->xo_columns;
3557d1a0d267SMarcel Moolenaar     save.xhs_anchor_columns = xop->xo_anchor_columns;
3558d1a0d267SMarcel Moolenaar 
3559d1a0d267SMarcel Moolenaar     xo_do_format_field(xop, NULL, value, vlen, flags);
3560d1a0d267SMarcel Moolenaar 
3561d1a0d267SMarcel Moolenaar     if (flags & XFF_HUMANIZE) {
3562d1a0d267SMarcel Moolenaar 	/*
3563d1a0d267SMarcel Moolenaar 	 * Unlike text style, we want to retain the original value and
3564d1a0d267SMarcel Moolenaar 	 * stuff it into the "data-number" attribute.
3565d1a0d267SMarcel Moolenaar 	 */
3566d1a0d267SMarcel Moolenaar 	static const char div_number[] = "\" data-number=\"";
3567d1a0d267SMarcel Moolenaar 	int div_len = sizeof(div_number) - 1;
3568d1a0d267SMarcel Moolenaar 
3569d1a0d267SMarcel Moolenaar 	unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
3570d1a0d267SMarcel Moolenaar 	int olen = end_offset - save.xhs_offset;
3571d1a0d267SMarcel Moolenaar 
3572d1a0d267SMarcel Moolenaar 	char *cp = alloca(olen + 1);
3573d1a0d267SMarcel Moolenaar 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
3574d1a0d267SMarcel Moolenaar 	cp[olen] = '\0';
3575d1a0d267SMarcel Moolenaar 
3576d1a0d267SMarcel Moolenaar 	xo_format_humanize(xop, xbp, &save, flags);
3577d1a0d267SMarcel Moolenaar 
3578d1a0d267SMarcel Moolenaar 	if (xo_buf_has_room(xbp, div_len + olen)) {
3579d1a0d267SMarcel Moolenaar 	    unsigned new_offset = xbp->xb_curp - xbp->xb_bufp;
3580d1a0d267SMarcel Moolenaar 
3581d1a0d267SMarcel Moolenaar 
3582d1a0d267SMarcel Moolenaar 	    /* Move the humanized string off to the left */
3583d1a0d267SMarcel Moolenaar 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
3584d1a0d267SMarcel Moolenaar 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
3585d1a0d267SMarcel Moolenaar 
3586d1a0d267SMarcel Moolenaar 	    /* Copy the data_number attribute name */
3587d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
3588d1a0d267SMarcel Moolenaar 
3589d1a0d267SMarcel Moolenaar 	    /* Copy the original long value */
3590d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
3591d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += div_len + olen;
3592d1a0d267SMarcel Moolenaar 	}
3593d1a0d267SMarcel Moolenaar     }
359431337658SMarcel Moolenaar 
359531337658SMarcel Moolenaar     xo_data_append(xop, div_close, sizeof(div_close) - 1);
359631337658SMarcel Moolenaar 
3597d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_PRETTY))
359831337658SMarcel Moolenaar 	xo_data_append(xop, "\n", 1);
359931337658SMarcel Moolenaar }
360031337658SMarcel Moolenaar 
360131337658SMarcel Moolenaar static void
360231337658SMarcel Moolenaar xo_format_text (xo_handle_t *xop, const char *str, int len)
360331337658SMarcel Moolenaar {
3604788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
360531337658SMarcel Moolenaar     case XO_STYLE_TEXT:
360631337658SMarcel Moolenaar 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
360731337658SMarcel Moolenaar 	break;
360831337658SMarcel Moolenaar 
360931337658SMarcel Moolenaar     case XO_STYLE_HTML:
361031337658SMarcel Moolenaar 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
361131337658SMarcel Moolenaar 	break;
361231337658SMarcel Moolenaar     }
361331337658SMarcel Moolenaar }
361431337658SMarcel Moolenaar 
361531337658SMarcel Moolenaar static void
3616d1a0d267SMarcel Moolenaar xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip)
361731337658SMarcel Moolenaar {
3618d1a0d267SMarcel Moolenaar     const char *str = xfip->xfi_content;
3619d1a0d267SMarcel Moolenaar     unsigned len = xfip->xfi_clen;
3620d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
3621d1a0d267SMarcel Moolenaar     unsigned flen = xfip->xfi_flen;
3622d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
3623d1a0d267SMarcel Moolenaar 
3624788ca347SMarcel Moolenaar     static char div_open[] = "<div class=\"title";
3625788ca347SMarcel Moolenaar     static char div_middle[] = "\">";
362631337658SMarcel Moolenaar     static char div_close[] = "</div>";
362731337658SMarcel Moolenaar 
3628545ddfbeSMarcel Moolenaar     if (flen == 0) {
3629545ddfbeSMarcel Moolenaar 	fmt = "%s";
3630545ddfbeSMarcel Moolenaar 	flen = 2;
3631545ddfbeSMarcel Moolenaar     }
3632545ddfbeSMarcel Moolenaar 
3633788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
363431337658SMarcel Moolenaar     case XO_STYLE_XML:
363531337658SMarcel Moolenaar     case XO_STYLE_JSON:
3636d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
3637d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
363831337658SMarcel Moolenaar 	/*
363931337658SMarcel Moolenaar 	 * Even though we don't care about text, we need to do
364031337658SMarcel Moolenaar 	 * enough parsing work to skip over the right bits of xo_vap.
364131337658SMarcel Moolenaar 	 */
364231337658SMarcel Moolenaar 	if (len == 0)
3643d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
364431337658SMarcel Moolenaar 	return;
364531337658SMarcel Moolenaar     }
364631337658SMarcel Moolenaar 
364731337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
364831337658SMarcel Moolenaar     int start = xbp->xb_curp - xbp->xb_bufp;
364931337658SMarcel Moolenaar     int left = xbp->xb_size - start;
365031337658SMarcel Moolenaar     int rc;
365131337658SMarcel Moolenaar 
3652788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
365331337658SMarcel Moolenaar 	xo_line_ensure_open(xop, 0);
3654d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
365531337658SMarcel Moolenaar 	    xo_buf_indent(xop, xop->xo_indent_by);
365631337658SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
3657788ca347SMarcel Moolenaar 	xo_color_append_html(xop);
3658788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
365931337658SMarcel Moolenaar     }
366031337658SMarcel Moolenaar 
366131337658SMarcel Moolenaar     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
366231337658SMarcel Moolenaar     if (len) {
366331337658SMarcel Moolenaar 	char *newfmt = alloca(flen + 1);
366431337658SMarcel Moolenaar 	memcpy(newfmt, fmt, flen);
366531337658SMarcel Moolenaar 	newfmt[flen] = '\0';
366631337658SMarcel Moolenaar 
366731337658SMarcel Moolenaar 	/* If len is non-zero, the format string apply to the name */
366831337658SMarcel Moolenaar 	char *newstr = alloca(len + 1);
366931337658SMarcel Moolenaar 	memcpy(newstr, str, len);
367031337658SMarcel Moolenaar 	newstr[len] = '\0';
367131337658SMarcel Moolenaar 
367231337658SMarcel Moolenaar 	if (newstr[len - 1] == 's') {
367331337658SMarcel Moolenaar 	    char *bp;
367431337658SMarcel Moolenaar 
367531337658SMarcel Moolenaar 	    rc = snprintf(NULL, 0, newfmt, newstr);
367631337658SMarcel Moolenaar 	    if (rc > 0) {
367731337658SMarcel Moolenaar 		/*
367831337658SMarcel Moolenaar 		 * We have to do this the hard way, since we might need
367931337658SMarcel Moolenaar 		 * the columns.
368031337658SMarcel Moolenaar 		 */
368131337658SMarcel Moolenaar 		bp = alloca(rc + 1);
368231337658SMarcel Moolenaar 		rc = snprintf(bp, rc + 1, newfmt, newstr);
3683d1a0d267SMarcel Moolenaar 
3684d1a0d267SMarcel Moolenaar 		xo_data_append_content(xop, bp, rc, flags);
368531337658SMarcel Moolenaar 	    }
368631337658SMarcel Moolenaar 	    goto move_along;
368731337658SMarcel Moolenaar 
368831337658SMarcel Moolenaar 	} else {
368931337658SMarcel Moolenaar 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3690d1a0d267SMarcel Moolenaar 	    if (rc >= left) {
369131337658SMarcel Moolenaar 		if (!xo_buf_has_room(xbp, rc))
369231337658SMarcel Moolenaar 		    return;
369331337658SMarcel Moolenaar 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
369431337658SMarcel Moolenaar 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
369531337658SMarcel Moolenaar 	    }
369631337658SMarcel Moolenaar 
369731337658SMarcel Moolenaar 	    if (rc > 0) {
3698d1a0d267SMarcel Moolenaar 		if (XOF_ISSET(xop, XOF_COLUMNS))
369931337658SMarcel Moolenaar 		    xop->xo_columns += rc;
3700d1a0d267SMarcel Moolenaar 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
370131337658SMarcel Moolenaar 		    xop->xo_anchor_columns += rc;
370231337658SMarcel Moolenaar 	    }
370331337658SMarcel Moolenaar 	}
370431337658SMarcel Moolenaar 
370531337658SMarcel Moolenaar     } else {
3706d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
370731337658SMarcel Moolenaar 
3708d1a0d267SMarcel Moolenaar 	/* xo_do_format_field moved curp, so we need to reset it */
370931337658SMarcel Moolenaar 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
371031337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + start;
371131337658SMarcel Moolenaar     }
371231337658SMarcel Moolenaar 
371331337658SMarcel Moolenaar     /* If we're styling HTML, then we need to escape it */
3714788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
371531337658SMarcel Moolenaar 	rc = xo_escape_xml(xbp, rc, 0);
371631337658SMarcel Moolenaar     }
371731337658SMarcel Moolenaar 
371831337658SMarcel Moolenaar     if (rc > 0)
371931337658SMarcel Moolenaar 	xbp->xb_curp += rc;
372031337658SMarcel Moolenaar 
372131337658SMarcel Moolenaar  move_along:
3722788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML) {
372331337658SMarcel Moolenaar 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
3724d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_PRETTY))
372531337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
372631337658SMarcel Moolenaar     }
372731337658SMarcel Moolenaar }
372831337658SMarcel Moolenaar 
372931337658SMarcel Moolenaar static void
373031337658SMarcel Moolenaar xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
373131337658SMarcel Moolenaar {
373231337658SMarcel Moolenaar     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
373331337658SMarcel Moolenaar 	xo_data_append(xop, ",", 1);
3734d1a0d267SMarcel Moolenaar 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
373531337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
373631337658SMarcel Moolenaar     } else
373731337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
373831337658SMarcel Moolenaar }
373931337658SMarcel Moolenaar 
374031337658SMarcel Moolenaar #if 0
374131337658SMarcel Moolenaar /* Useful debugging function */
374231337658SMarcel Moolenaar void
374331337658SMarcel Moolenaar xo_arg (xo_handle_t *xop);
374431337658SMarcel Moolenaar void
374531337658SMarcel Moolenaar xo_arg (xo_handle_t *xop)
374631337658SMarcel Moolenaar {
374731337658SMarcel Moolenaar     xop = xo_default(xop);
374831337658SMarcel Moolenaar     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
374931337658SMarcel Moolenaar }
375031337658SMarcel Moolenaar #endif /* 0 */
375131337658SMarcel Moolenaar 
375231337658SMarcel Moolenaar static void
375331337658SMarcel Moolenaar xo_format_value (xo_handle_t *xop, const char *name, int nlen,
375431337658SMarcel Moolenaar                 const char *format, int flen,
375531337658SMarcel Moolenaar                 const char *encoding, int elen, xo_xff_flags_t flags)
375631337658SMarcel Moolenaar {
3757d1a0d267SMarcel Moolenaar     int pretty = XOF_ISSET(xop, XOF_PRETTY);
375831337658SMarcel Moolenaar     int quote;
375931337658SMarcel Moolenaar 
3760545ddfbeSMarcel Moolenaar     /*
3761545ddfbeSMarcel Moolenaar      * Before we emit a value, we need to know that the frame is ready.
3762545ddfbeSMarcel Moolenaar      */
3763545ddfbeSMarcel Moolenaar     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3764545ddfbeSMarcel Moolenaar 
3765545ddfbeSMarcel Moolenaar     if (flags & XFF_LEAF_LIST) {
3766545ddfbeSMarcel Moolenaar 	/*
3767545ddfbeSMarcel Moolenaar 	 * Check if we've already started to emit normal leafs
3768545ddfbeSMarcel Moolenaar 	 * or if we're not in a leaf list.
3769545ddfbeSMarcel Moolenaar 	 */
3770545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
3771545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
3772545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
3773545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
3774545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
3775545ddfbeSMarcel Moolenaar 
3776545ddfbeSMarcel Moolenaar 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
3777545ddfbeSMarcel Moolenaar 	    if (rc < 0)
3778545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3779545ddfbeSMarcel Moolenaar 	    else
3780545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
3781545ddfbeSMarcel Moolenaar 	}
3782545ddfbeSMarcel Moolenaar 
3783545ddfbeSMarcel Moolenaar 	xsp = &xop->xo_stack[xop->xo_depth];
3784545ddfbeSMarcel Moolenaar 	if (xsp->xs_name) {
3785545ddfbeSMarcel Moolenaar 	    name = xsp->xs_name;
3786545ddfbeSMarcel Moolenaar 	    nlen = strlen(name);
3787545ddfbeSMarcel Moolenaar 	}
3788545ddfbeSMarcel Moolenaar 
3789545ddfbeSMarcel Moolenaar     } else if (flags & XFF_KEY) {
3790545ddfbeSMarcel Moolenaar 	/* Emitting a 'k' (key) field */
3791545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
3792545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
3793545ddfbeSMarcel Moolenaar 		       nlen, name);
3794545ddfbeSMarcel Moolenaar 
3795545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
3796545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
3797545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
3798545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
3799545ddfbeSMarcel Moolenaar 
3800545ddfbeSMarcel Moolenaar 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3801545ddfbeSMarcel Moolenaar 	    if (rc < 0)
3802545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3803545ddfbeSMarcel Moolenaar 	    else
3804545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
3805545ddfbeSMarcel Moolenaar 
3806545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
3807545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT_KEY;
3808545ddfbeSMarcel Moolenaar 	}
3809545ddfbeSMarcel Moolenaar 
3810545ddfbeSMarcel Moolenaar     } else {
3811545ddfbeSMarcel Moolenaar 	/* Emitting a normal value field */
3812545ddfbeSMarcel Moolenaar 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
3813545ddfbeSMarcel Moolenaar 	    || !(xsp->xs_flags & XSF_EMIT)) {
3814545ddfbeSMarcel Moolenaar 	    char nbuf[nlen + 1];
3815545ddfbeSMarcel Moolenaar 	    memcpy(nbuf, name, nlen);
3816545ddfbeSMarcel Moolenaar 	    nbuf[nlen] = '\0';
3817545ddfbeSMarcel Moolenaar 
3818545ddfbeSMarcel Moolenaar 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
3819545ddfbeSMarcel Moolenaar 	    if (rc < 0)
3820545ddfbeSMarcel Moolenaar 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
3821545ddfbeSMarcel Moolenaar 	    else
3822545ddfbeSMarcel Moolenaar 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
3823545ddfbeSMarcel Moolenaar 
3824545ddfbeSMarcel Moolenaar 	    xsp = &xop->xo_stack[xop->xo_depth];
3825545ddfbeSMarcel Moolenaar 	    xsp->xs_flags |= XSF_EMIT;
3826545ddfbeSMarcel Moolenaar 	}
3827545ddfbeSMarcel Moolenaar     }
3828545ddfbeSMarcel Moolenaar 
3829d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
3830d1a0d267SMarcel Moolenaar     xo_humanize_save_t save;	/* Save values for humanizing logic */
3831d1a0d267SMarcel Moolenaar 
3832788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
383331337658SMarcel Moolenaar     case XO_STYLE_TEXT:
383431337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
383531337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
3836d1a0d267SMarcel Moolenaar 
3837d1a0d267SMarcel Moolenaar 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
3838d1a0d267SMarcel Moolenaar 	save.xhs_columns = xop->xo_columns;
3839d1a0d267SMarcel Moolenaar 	save.xhs_anchor_columns = xop->xo_anchor_columns;
3840d1a0d267SMarcel Moolenaar 
3841d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
3842d1a0d267SMarcel Moolenaar 
3843d1a0d267SMarcel Moolenaar 	if (flags & XFF_HUMANIZE)
3844d1a0d267SMarcel Moolenaar 	    xo_format_humanize(xop, xbp, &save, flags);
384531337658SMarcel Moolenaar 	break;
384631337658SMarcel Moolenaar 
384731337658SMarcel Moolenaar     case XO_STYLE_HTML:
384831337658SMarcel Moolenaar 	if (flags & XFF_ENCODE_ONLY)
384931337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
3850d1a0d267SMarcel Moolenaar 
385131337658SMarcel Moolenaar 	xo_buf_append_div(xop, "data", flags, name, nlen,
385231337658SMarcel Moolenaar 			  format, flen, encoding, elen);
385331337658SMarcel Moolenaar 	break;
385431337658SMarcel Moolenaar 
385531337658SMarcel Moolenaar     case XO_STYLE_XML:
385631337658SMarcel Moolenaar 	/*
385731337658SMarcel Moolenaar 	 * Even though we're not making output, we still need to
385831337658SMarcel Moolenaar 	 * let the formatting code handle the va_arg popping.
385931337658SMarcel Moolenaar 	 */
386031337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
386131337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
3862d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
386331337658SMarcel Moolenaar 	    break;
386431337658SMarcel Moolenaar 	}
386531337658SMarcel Moolenaar 
386631337658SMarcel Moolenaar 	if (encoding) {
386731337658SMarcel Moolenaar    	    format = encoding;
386831337658SMarcel Moolenaar 	    flen = elen;
386931337658SMarcel Moolenaar 	} else {
387031337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
387131337658SMarcel Moolenaar 	    memcpy(enc, format, flen);
387231337658SMarcel Moolenaar 	    enc[flen] = '\0';
387331337658SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
387431337658SMarcel Moolenaar 	    flen = strlen(format);
387531337658SMarcel Moolenaar 	}
387631337658SMarcel Moolenaar 
387731337658SMarcel Moolenaar 	if (nlen == 0) {
387831337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
387931337658SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
388031337658SMarcel Moolenaar 	    name = missing;
388131337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
388231337658SMarcel Moolenaar 	}
388331337658SMarcel Moolenaar 
388431337658SMarcel Moolenaar 	if (pretty)
388531337658SMarcel Moolenaar 	    xo_buf_indent(xop, -1);
388631337658SMarcel Moolenaar 	xo_data_append(xop, "<", 1);
388731337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
388831337658SMarcel Moolenaar 
388931337658SMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
389031337658SMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
389131337658SMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
389231337658SMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
389331337658SMarcel Moolenaar 	}
389431337658SMarcel Moolenaar 
389531337658SMarcel Moolenaar 	/*
389631337658SMarcel Moolenaar 	 * We indicate 'key' fields using the 'key' attribute.  While
389731337658SMarcel Moolenaar 	 * this is really committing the crime of mixing meta-data with
389831337658SMarcel Moolenaar 	 * data, it's often useful.  Especially when format meta-data is
389931337658SMarcel Moolenaar 	 * difficult to come by.
390031337658SMarcel Moolenaar 	 */
3901d1a0d267SMarcel Moolenaar 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
390231337658SMarcel Moolenaar 	    static char attr[] = " key=\"key\"";
390331337658SMarcel Moolenaar 	    xo_data_append(xop, attr, sizeof(attr) - 1);
390431337658SMarcel Moolenaar 	}
390531337658SMarcel Moolenaar 
390631337658SMarcel Moolenaar 	/*
390731337658SMarcel Moolenaar 	 * Save the offset at which we'd place units.  See xo_format_units.
390831337658SMarcel Moolenaar 	 */
3909d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_UNITS)) {
3910d1a0d267SMarcel Moolenaar 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
391131337658SMarcel Moolenaar 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
391231337658SMarcel Moolenaar 	}
391331337658SMarcel Moolenaar 
391431337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
3915d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
391631337658SMarcel Moolenaar 	xo_data_append(xop, "</", 2);
391731337658SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
391831337658SMarcel Moolenaar 	xo_data_append(xop, ">", 1);
391931337658SMarcel Moolenaar 	if (pretty)
392031337658SMarcel Moolenaar 	    xo_data_append(xop, "\n", 1);
392131337658SMarcel Moolenaar 	break;
392231337658SMarcel Moolenaar 
392331337658SMarcel Moolenaar     case XO_STYLE_JSON:
392431337658SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
392531337658SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
3926d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
392731337658SMarcel Moolenaar 	    break;
392831337658SMarcel Moolenaar 	}
392931337658SMarcel Moolenaar 
393031337658SMarcel Moolenaar 	if (encoding) {
393131337658SMarcel Moolenaar 	    format = encoding;
393231337658SMarcel Moolenaar 	    flen = elen;
393331337658SMarcel Moolenaar 	} else {
393431337658SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
393531337658SMarcel Moolenaar 	    memcpy(enc, format, flen);
393631337658SMarcel Moolenaar 	    enc[flen] = '\0';
393731337658SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
393831337658SMarcel Moolenaar 	    flen = strlen(format);
393931337658SMarcel Moolenaar 	}
394031337658SMarcel Moolenaar 
394131337658SMarcel Moolenaar 	int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
394231337658SMarcel Moolenaar 
394331337658SMarcel Moolenaar 	xo_format_prep(xop, flags);
394431337658SMarcel Moolenaar 
394531337658SMarcel Moolenaar 	if (flags & XFF_QUOTE)
394631337658SMarcel Moolenaar 	    quote = 1;
394731337658SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
394831337658SMarcel Moolenaar 	    quote = 0;
394931337658SMarcel Moolenaar 	else if (flen == 0) {
395031337658SMarcel Moolenaar 	    quote = 0;
395131337658SMarcel Moolenaar 	    format = "true";	/* JSON encodes empty tags as a boolean true */
395231337658SMarcel Moolenaar 	    flen = 4;
395331337658SMarcel Moolenaar 	} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
395431337658SMarcel Moolenaar 	    quote = 1;
395531337658SMarcel Moolenaar 	else
395631337658SMarcel Moolenaar 	    quote = 0;
395731337658SMarcel Moolenaar 
395831337658SMarcel Moolenaar 	if (nlen == 0) {
395931337658SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
396031337658SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
396131337658SMarcel Moolenaar 	    name = missing;
396231337658SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
396331337658SMarcel Moolenaar 	}
396431337658SMarcel Moolenaar 
396531337658SMarcel Moolenaar 	if (flags & XFF_LEAF_LIST) {
3966788ca347SMarcel Moolenaar 	    if (!first && pretty)
3967788ca347SMarcel Moolenaar 		xo_data_append(xop, "\n", 1);
3968788ca347SMarcel Moolenaar 	    if (pretty)
396931337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
397031337658SMarcel Moolenaar 	} else {
397131337658SMarcel Moolenaar 	    if (pretty)
397231337658SMarcel Moolenaar 		xo_buf_indent(xop, -1);
397331337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
397431337658SMarcel Moolenaar 
397531337658SMarcel Moolenaar 	    xbp = &xop->xo_data;
397631337658SMarcel Moolenaar 	    int off = xbp->xb_curp - xbp->xb_bufp;
397731337658SMarcel Moolenaar 
397831337658SMarcel Moolenaar 	    xo_data_escape(xop, name, nlen);
397931337658SMarcel Moolenaar 
3980d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
398131337658SMarcel Moolenaar 		int now = xbp->xb_curp - xbp->xb_bufp;
398231337658SMarcel Moolenaar 		for ( ; off < now; off++)
398331337658SMarcel Moolenaar 		    if (xbp->xb_bufp[off] == '-')
398431337658SMarcel Moolenaar 			xbp->xb_bufp[off] = '_';
398531337658SMarcel Moolenaar 	    }
398631337658SMarcel Moolenaar 	    xo_data_append(xop, "\":", 2);
398731337658SMarcel Moolenaar 	    if (pretty)
398831337658SMarcel Moolenaar 	        xo_data_append(xop, " ", 1);
3989788ca347SMarcel Moolenaar 	}
3990788ca347SMarcel Moolenaar 
399131337658SMarcel Moolenaar 	if (quote)
399231337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
399331337658SMarcel Moolenaar 
3994d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
399531337658SMarcel Moolenaar 
399631337658SMarcel Moolenaar 	if (quote)
399731337658SMarcel Moolenaar 	    xo_data_append(xop, "\"", 1);
399831337658SMarcel Moolenaar 	break;
3999d1a0d267SMarcel Moolenaar 
4000d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4001d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4002d1a0d267SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4003d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
4004d1a0d267SMarcel Moolenaar 	    break;
4005d1a0d267SMarcel Moolenaar 	}
4006d1a0d267SMarcel Moolenaar 
4007d1a0d267SMarcel Moolenaar 	if (encoding) {
4008d1a0d267SMarcel Moolenaar 	    format = encoding;
4009d1a0d267SMarcel Moolenaar 	    flen = elen;
4010d1a0d267SMarcel Moolenaar 	} else {
4011d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4012d1a0d267SMarcel Moolenaar 	    memcpy(enc, format, flen);
4013d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4014d1a0d267SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
4015d1a0d267SMarcel Moolenaar 	    flen = strlen(format);
4016d1a0d267SMarcel Moolenaar 	}
4017d1a0d267SMarcel Moolenaar 
4018d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4019d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4020d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
4021d1a0d267SMarcel Moolenaar 	    name = missing;
4022d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4023d1a0d267SMarcel Moolenaar 	}
4024d1a0d267SMarcel Moolenaar 
4025d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, name, nlen);
4026d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "=\"", 2);
4027d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
4028d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "\" ", 2);
4029d1a0d267SMarcel Moolenaar 	break;
4030d1a0d267SMarcel Moolenaar 
4031d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
4032d1a0d267SMarcel Moolenaar 	if (flags & XFF_DISPLAY_ONLY) {
4033d1a0d267SMarcel Moolenaar 	    flags |= XFF_NO_OUTPUT;
4034d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, format, flen, flags);
4035d1a0d267SMarcel Moolenaar 	    break;
4036d1a0d267SMarcel Moolenaar 	}
4037d1a0d267SMarcel Moolenaar 
4038d1a0d267SMarcel Moolenaar 	if (flags & XFF_QUOTE)
4039d1a0d267SMarcel Moolenaar 	    quote = 1;
4040d1a0d267SMarcel Moolenaar 	else if (flags & XFF_NOQUOTE)
4041d1a0d267SMarcel Moolenaar 	    quote = 0;
4042d1a0d267SMarcel Moolenaar 	else if (flen == 0) {
4043d1a0d267SMarcel Moolenaar 	    quote = 0;
4044d1a0d267SMarcel Moolenaar 	    format = "true";	/* JSON encodes empty tags as a boolean true */
4045d1a0d267SMarcel Moolenaar 	    flen = 4;
4046d1a0d267SMarcel Moolenaar 	} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
4047d1a0d267SMarcel Moolenaar 	    quote = 1;
4048d1a0d267SMarcel Moolenaar 	else
4049d1a0d267SMarcel Moolenaar 	    quote = 0;
4050d1a0d267SMarcel Moolenaar 
4051d1a0d267SMarcel Moolenaar 	if (encoding) {
4052d1a0d267SMarcel Moolenaar 	    format = encoding;
4053d1a0d267SMarcel Moolenaar 	    flen = elen;
4054d1a0d267SMarcel Moolenaar 	} else {
4055d1a0d267SMarcel Moolenaar 	    char *enc  = alloca(flen + 1);
4056d1a0d267SMarcel Moolenaar 	    memcpy(enc, format, flen);
4057d1a0d267SMarcel Moolenaar 	    enc[flen] = '\0';
4058d1a0d267SMarcel Moolenaar 	    format = xo_fix_encoding(xop, enc);
4059d1a0d267SMarcel Moolenaar 	    flen = strlen(format);
4060d1a0d267SMarcel Moolenaar 	}
4061d1a0d267SMarcel Moolenaar 
4062d1a0d267SMarcel Moolenaar 	if (nlen == 0) {
4063d1a0d267SMarcel Moolenaar 	    static char missing[] = "missing-field-name";
4064d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing field name: %s", format);
4065d1a0d267SMarcel Moolenaar 	    name = missing;
4066d1a0d267SMarcel Moolenaar 	    nlen = sizeof(missing) - 1;
4067d1a0d267SMarcel Moolenaar 	}
4068d1a0d267SMarcel Moolenaar 
4069d1a0d267SMarcel Moolenaar 	unsigned name_offset = xo_buf_offset(&xop->xo_data);
4070d1a0d267SMarcel Moolenaar 	xo_data_append(xop, name, nlen);
4071d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4072d1a0d267SMarcel Moolenaar 
4073d1a0d267SMarcel Moolenaar 	unsigned value_offset = xo_buf_offset(&xop->xo_data);
4074d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, format, flen, flags);
4075d1a0d267SMarcel Moolenaar 	xo_data_append(xop, "", 1);
4076d1a0d267SMarcel Moolenaar 
4077d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4078d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, name_offset),
4079d1a0d267SMarcel Moolenaar 			  xo_buf_data(&xop->xo_data, value_offset));
4080d1a0d267SMarcel Moolenaar 	xo_buf_reset(&xop->xo_data);
4081d1a0d267SMarcel Moolenaar 	break;
408231337658SMarcel Moolenaar     }
408331337658SMarcel Moolenaar }
408431337658SMarcel Moolenaar 
408531337658SMarcel Moolenaar static void
4086d1a0d267SMarcel Moolenaar xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip)
4087d1a0d267SMarcel Moolenaar {
4088d1a0d267SMarcel Moolenaar     const char *str = xfip->xfi_content;
4089d1a0d267SMarcel Moolenaar     unsigned len = xfip->xfi_clen;
4090d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
4091d1a0d267SMarcel Moolenaar     unsigned flen = xfip->xfi_flen;
4092d1a0d267SMarcel Moolenaar 
4093d1a0d267SMarcel Moolenaar     /* Start by discarding previous domain */
4094d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
4095d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
4096d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
4097d1a0d267SMarcel Moolenaar     }
4098d1a0d267SMarcel Moolenaar 
4099d1a0d267SMarcel Moolenaar     /* An empty {G:} means no domainname */
4100d1a0d267SMarcel Moolenaar     if (len == 0 && flen == 0)
4101d1a0d267SMarcel Moolenaar 	return;
4102d1a0d267SMarcel Moolenaar 
4103d1a0d267SMarcel Moolenaar     int start_offset = -1;
4104d1a0d267SMarcel Moolenaar     if (len == 0 && flen != 0) {
4105d1a0d267SMarcel Moolenaar 	/* Need to do format the data to get the domainname from args */
4106d1a0d267SMarcel Moolenaar 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4107d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4108d1a0d267SMarcel Moolenaar 
4109d1a0d267SMarcel Moolenaar 	int end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4110d1a0d267SMarcel Moolenaar 	len = end_offset - start_offset;
4111d1a0d267SMarcel Moolenaar 	str = xop->xo_data.xb_bufp + start_offset;
4112d1a0d267SMarcel Moolenaar     }
4113d1a0d267SMarcel Moolenaar 
4114d1a0d267SMarcel Moolenaar     xop->xo_gt_domain = xo_strndup(str, len);
4115d1a0d267SMarcel Moolenaar 
4116d1a0d267SMarcel Moolenaar     /* Reset the current buffer point to avoid emitting the name as output */
4117d1a0d267SMarcel Moolenaar     if (start_offset >= 0)
4118d1a0d267SMarcel Moolenaar 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4119d1a0d267SMarcel Moolenaar }
4120d1a0d267SMarcel Moolenaar 
4121d1a0d267SMarcel Moolenaar static void
412231337658SMarcel Moolenaar xo_format_content (xo_handle_t *xop, const char *class_name,
4123d1a0d267SMarcel Moolenaar 		   const char *tag_name,
4124d1a0d267SMarcel Moolenaar 		   const char *str, int len, const char *fmt, int flen,
4125d1a0d267SMarcel Moolenaar 		   xo_xff_flags_t flags)
412631337658SMarcel Moolenaar {
4127788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
412831337658SMarcel Moolenaar     case XO_STYLE_TEXT:
4129d1a0d267SMarcel Moolenaar 	if (len)
4130d1a0d267SMarcel Moolenaar 	    xo_data_append_content(xop, str, len, flags);
4131d1a0d267SMarcel Moolenaar 	else
4132d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen, flags);
413331337658SMarcel Moolenaar 	break;
413431337658SMarcel Moolenaar 
413531337658SMarcel Moolenaar     case XO_STYLE_HTML:
413631337658SMarcel Moolenaar 	if (len == 0) {
413731337658SMarcel Moolenaar 	    str = fmt;
413831337658SMarcel Moolenaar 	    len = flen;
413931337658SMarcel Moolenaar 	}
414031337658SMarcel Moolenaar 
4141d1a0d267SMarcel Moolenaar 	xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
414231337658SMarcel Moolenaar 	break;
414331337658SMarcel Moolenaar 
414431337658SMarcel Moolenaar     case XO_STYLE_XML:
4145d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
4146d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
4147d1a0d267SMarcel Moolenaar 	if (tag_name) {
414831337658SMarcel Moolenaar 	    if (len == 0) {
414931337658SMarcel Moolenaar 		str = fmt;
415031337658SMarcel Moolenaar 		len = flen;
415131337658SMarcel Moolenaar 	    }
415231337658SMarcel Moolenaar 
4153d1a0d267SMarcel Moolenaar 	    xo_open_container_h(xop, tag_name);
4154d1a0d267SMarcel Moolenaar 	    xo_format_value(xop, "message", 7, str, len, NULL, 0, flags);
4155d1a0d267SMarcel Moolenaar 	    xo_close_container_h(xop, tag_name);
415631337658SMarcel Moolenaar 
415731337658SMarcel Moolenaar 	} else {
415831337658SMarcel Moolenaar 	    /*
415931337658SMarcel Moolenaar 	     * Even though we don't care about labels, we need to do
416031337658SMarcel Moolenaar 	     * enough parsing work to skip over the right bits of xo_vap.
416131337658SMarcel Moolenaar 	     */
416231337658SMarcel Moolenaar 	    if (len == 0)
4163d1a0d267SMarcel Moolenaar 		xo_do_format_field(xop, NULL, fmt, flen,
4164d1a0d267SMarcel Moolenaar 				   flags | XFF_NO_OUTPUT);
416531337658SMarcel Moolenaar 	}
416631337658SMarcel Moolenaar 	break;
416731337658SMarcel Moolenaar 
4168d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
416931337658SMarcel Moolenaar 	if (len == 0)
4170d1a0d267SMarcel Moolenaar 	    xo_do_format_field(xop, NULL, fmt, flen,
4171d1a0d267SMarcel Moolenaar 			       flags | XFF_NO_OUTPUT);
417231337658SMarcel Moolenaar 	break;
417331337658SMarcel Moolenaar     }
417431337658SMarcel Moolenaar }
417531337658SMarcel Moolenaar 
4176788ca347SMarcel Moolenaar static const char *xo_color_names[] = {
4177788ca347SMarcel Moolenaar     "default",	/* XO_COL_DEFAULT */
4178788ca347SMarcel Moolenaar     "black",	/* XO_COL_BLACK */
4179788ca347SMarcel Moolenaar     "red",	/* XO_CLOR_RED */
4180788ca347SMarcel Moolenaar     "green",	/* XO_COL_GREEN */
4181788ca347SMarcel Moolenaar     "yellow",	/* XO_COL_YELLOW */
4182788ca347SMarcel Moolenaar     "blue",	/* XO_COL_BLUE */
4183788ca347SMarcel Moolenaar     "magenta",	/* XO_COL_MAGENTA */
4184788ca347SMarcel Moolenaar     "cyan",	/* XO_COL_CYAN */
4185788ca347SMarcel Moolenaar     "white",	/* XO_COL_WHITE */
4186788ca347SMarcel Moolenaar     NULL
4187788ca347SMarcel Moolenaar };
4188788ca347SMarcel Moolenaar 
4189788ca347SMarcel Moolenaar static int
4190788ca347SMarcel Moolenaar xo_color_find (const char *str)
4191788ca347SMarcel Moolenaar {
4192788ca347SMarcel Moolenaar     int i;
4193788ca347SMarcel Moolenaar 
4194788ca347SMarcel Moolenaar     for (i = 0; xo_color_names[i]; i++) {
4195788ca347SMarcel Moolenaar 	if (strcmp(xo_color_names[i], str) == 0)
4196788ca347SMarcel Moolenaar 	    return i;
4197788ca347SMarcel Moolenaar     }
4198788ca347SMarcel Moolenaar 
4199788ca347SMarcel Moolenaar     return -1;
4200788ca347SMarcel Moolenaar }
4201788ca347SMarcel Moolenaar 
4202788ca347SMarcel Moolenaar static const char *xo_effect_names[] = {
4203788ca347SMarcel Moolenaar     "reset",			/* XO_EFF_RESET */
4204788ca347SMarcel Moolenaar     "normal",			/* XO_EFF_NORMAL */
4205788ca347SMarcel Moolenaar     "bold",			/* XO_EFF_BOLD */
4206788ca347SMarcel Moolenaar     "underline",		/* XO_EFF_UNDERLINE */
4207788ca347SMarcel Moolenaar     "inverse",			/* XO_EFF_INVERSE */
4208788ca347SMarcel Moolenaar     NULL
4209788ca347SMarcel Moolenaar };
4210788ca347SMarcel Moolenaar 
4211788ca347SMarcel Moolenaar static const char *xo_effect_on_codes[] = {
4212788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4213788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4214788ca347SMarcel Moolenaar     "1",			/* XO_EFF_BOLD */
4215788ca347SMarcel Moolenaar     "4",			/* XO_EFF_UNDERLINE */
4216788ca347SMarcel Moolenaar     "7",			/* XO_EFF_INVERSE */
4217788ca347SMarcel Moolenaar     NULL
4218788ca347SMarcel Moolenaar };
4219788ca347SMarcel Moolenaar 
4220788ca347SMarcel Moolenaar #if 0
4221788ca347SMarcel Moolenaar /*
4222788ca347SMarcel Moolenaar  * See comment below re: joy of terminal standards.  These can
4223788ca347SMarcel Moolenaar  * be use by just adding:
4224d1a0d267SMarcel Moolenaar  * +	if (newp->xoc_effects & bit)
4225788ca347SMarcel Moolenaar  *	    code = xo_effect_on_codes[i];
4226788ca347SMarcel Moolenaar  * +	else
4227788ca347SMarcel Moolenaar  * +	    code = xo_effect_off_codes[i];
4228788ca347SMarcel Moolenaar  * in xo_color_handle_text.
4229788ca347SMarcel Moolenaar  */
4230788ca347SMarcel Moolenaar static const char *xo_effect_off_codes[] = {
4231788ca347SMarcel Moolenaar     "0",			/* XO_EFF_RESET */
4232788ca347SMarcel Moolenaar     "0",			/* XO_EFF_NORMAL */
4233788ca347SMarcel Moolenaar     "21",			/* XO_EFF_BOLD */
4234788ca347SMarcel Moolenaar     "24",			/* XO_EFF_UNDERLINE */
4235788ca347SMarcel Moolenaar     "27",			/* XO_EFF_INVERSE */
4236788ca347SMarcel Moolenaar     NULL
4237788ca347SMarcel Moolenaar };
4238788ca347SMarcel Moolenaar #endif /* 0 */
4239788ca347SMarcel Moolenaar 
4240788ca347SMarcel Moolenaar static int
4241788ca347SMarcel Moolenaar xo_effect_find (const char *str)
4242788ca347SMarcel Moolenaar {
4243788ca347SMarcel Moolenaar     int i;
4244788ca347SMarcel Moolenaar 
4245788ca347SMarcel Moolenaar     for (i = 0; xo_effect_names[i]; i++) {
4246788ca347SMarcel Moolenaar 	if (strcmp(xo_effect_names[i], str) == 0)
4247788ca347SMarcel Moolenaar 	    return i;
4248788ca347SMarcel Moolenaar     }
4249788ca347SMarcel Moolenaar 
4250788ca347SMarcel Moolenaar     return -1;
4251788ca347SMarcel Moolenaar }
4252788ca347SMarcel Moolenaar 
4253788ca347SMarcel Moolenaar static void
4254788ca347SMarcel Moolenaar xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4255788ca347SMarcel Moolenaar {
4256788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4257788ca347SMarcel Moolenaar     return;
4258788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4259788ca347SMarcel Moolenaar 
4260788ca347SMarcel Moolenaar     char *cp, *ep, *np, *xp;
4261788ca347SMarcel Moolenaar     int len = strlen(str);
4262788ca347SMarcel Moolenaar     int rc;
4263788ca347SMarcel Moolenaar 
4264788ca347SMarcel Moolenaar     /*
4265788ca347SMarcel Moolenaar      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4266788ca347SMarcel Moolenaar      */
4267788ca347SMarcel Moolenaar     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4268788ca347SMarcel Moolenaar 	/* Trim leading whitespace */
4269788ca347SMarcel Moolenaar 	while (isspace((int) *cp))
4270788ca347SMarcel Moolenaar 	    cp += 1;
4271788ca347SMarcel Moolenaar 
4272788ca347SMarcel Moolenaar 	np = strchr(cp, ',');
4273788ca347SMarcel Moolenaar 	if (np)
4274788ca347SMarcel Moolenaar 	    *np++ = '\0';
4275788ca347SMarcel Moolenaar 
4276788ca347SMarcel Moolenaar 	/* Trim trailing whitespace */
4277788ca347SMarcel Moolenaar 	xp = cp + strlen(cp) - 1;
4278788ca347SMarcel Moolenaar 	while (isspace(*xp) && xp > cp)
4279788ca347SMarcel Moolenaar 	    *xp-- = '\0';
4280788ca347SMarcel Moolenaar 
4281788ca347SMarcel Moolenaar 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4282788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4283788ca347SMarcel Moolenaar 	    if (rc < 0)
4284788ca347SMarcel Moolenaar 		goto unknown;
4285788ca347SMarcel Moolenaar 
4286788ca347SMarcel Moolenaar 	    xocp->xoc_col_fg = rc;
4287788ca347SMarcel Moolenaar 
4288788ca347SMarcel Moolenaar 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4289788ca347SMarcel Moolenaar 	    rc = xo_color_find(cp + 3);
4290788ca347SMarcel Moolenaar 	    if (rc < 0)
4291788ca347SMarcel Moolenaar 		goto unknown;
4292788ca347SMarcel Moolenaar 	    xocp->xoc_col_bg = rc;
4293788ca347SMarcel Moolenaar 
4294788ca347SMarcel Moolenaar 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4295788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp + 3);
4296788ca347SMarcel Moolenaar 	    if (rc < 0)
4297788ca347SMarcel Moolenaar 		goto unknown;
4298788ca347SMarcel Moolenaar 	    xocp->xoc_effects &= ~(1 << rc);
4299788ca347SMarcel Moolenaar 
4300788ca347SMarcel Moolenaar 	} else {
4301788ca347SMarcel Moolenaar 	    rc = xo_effect_find(cp);
4302788ca347SMarcel Moolenaar 	    if (rc < 0)
4303788ca347SMarcel Moolenaar 		goto unknown;
4304788ca347SMarcel Moolenaar 	    xocp->xoc_effects |= 1 << rc;
4305788ca347SMarcel Moolenaar 
4306788ca347SMarcel Moolenaar 	    switch (1 << rc) {
4307788ca347SMarcel Moolenaar 	    case XO_EFF_RESET:
4308788ca347SMarcel Moolenaar 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4309788ca347SMarcel Moolenaar 		/* Note: not "|=" since we want to wipe out the old value */
4310788ca347SMarcel Moolenaar 		xocp->xoc_effects = XO_EFF_RESET;
4311788ca347SMarcel Moolenaar 		break;
4312788ca347SMarcel Moolenaar 
4313788ca347SMarcel Moolenaar 	    case XO_EFF_NORMAL:
4314788ca347SMarcel Moolenaar 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4315788ca347SMarcel Moolenaar 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4316788ca347SMarcel Moolenaar 		break;
4317788ca347SMarcel Moolenaar 	    }
4318788ca347SMarcel Moolenaar 	}
4319788ca347SMarcel Moolenaar 	continue;
4320788ca347SMarcel Moolenaar 
4321788ca347SMarcel Moolenaar     unknown:
4322d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN))
4323788ca347SMarcel Moolenaar 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4324788ca347SMarcel Moolenaar     }
4325788ca347SMarcel Moolenaar }
4326788ca347SMarcel Moolenaar 
4327788ca347SMarcel Moolenaar static inline int
4328788ca347SMarcel Moolenaar xo_colors_enabled (xo_handle_t *xop UNUSED)
4329788ca347SMarcel Moolenaar {
4330788ca347SMarcel Moolenaar #ifdef LIBXO_TEXT_ONLY
4331788ca347SMarcel Moolenaar     return 0;
4332788ca347SMarcel Moolenaar #else /* LIBXO_TEXT_ONLY */
4333d1a0d267SMarcel Moolenaar     return XOF_ISSET(xop, XOF_COLOR);
4334788ca347SMarcel Moolenaar #endif /* LIBXO_TEXT_ONLY */
4335788ca347SMarcel Moolenaar }
4336788ca347SMarcel Moolenaar 
4337788ca347SMarcel Moolenaar static void
4338788ca347SMarcel Moolenaar xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
4339788ca347SMarcel Moolenaar {
4340788ca347SMarcel Moolenaar     char buf[BUFSIZ];
4341788ca347SMarcel Moolenaar     char *cp = buf, *ep = buf + sizeof(buf);
4342788ca347SMarcel Moolenaar     unsigned i, bit;
4343788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
434490d30dd1SPhil Shafer     const char *code;
4345788ca347SMarcel Moolenaar 
4346788ca347SMarcel Moolenaar     /*
4347788ca347SMarcel Moolenaar      * Start the buffer with an escape.  We don't want to add the '['
4348788ca347SMarcel Moolenaar      * now, since we let xo_effect_text_add unconditionally add the ';'.
4349788ca347SMarcel Moolenaar      * We'll replace the first ';' with a '[' when we're done.
4350788ca347SMarcel Moolenaar      */
4351788ca347SMarcel Moolenaar     *cp++ = 0x1b;		/* Escape */
4352788ca347SMarcel Moolenaar 
4353788ca347SMarcel Moolenaar     /*
4354788ca347SMarcel Moolenaar      * Terminals were designed back in the age before "certainty" was
4355788ca347SMarcel Moolenaar      * invented, when standards were more what you'd call "guidelines"
4356788ca347SMarcel Moolenaar      * than actual rules.  Anyway we can't depend on them to operate
4357788ca347SMarcel Moolenaar      * correctly.  So when display attributes are changed, we punt,
4358788ca347SMarcel Moolenaar      * reseting them all and turning back on the ones we want to keep.
4359788ca347SMarcel Moolenaar      * Longer, but should be completely reliable.  Savvy?
4360788ca347SMarcel Moolenaar      */
4361788ca347SMarcel Moolenaar     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4362788ca347SMarcel Moolenaar 	newp->xoc_effects |= XO_EFF_RESET;
4363788ca347SMarcel Moolenaar 	oldp->xoc_effects = 0;
4364788ca347SMarcel Moolenaar     }
4365788ca347SMarcel Moolenaar 
4366788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4367788ca347SMarcel Moolenaar 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4368788ca347SMarcel Moolenaar 	    continue;
4369788ca347SMarcel Moolenaar 
4370788ca347SMarcel Moolenaar 	code = xo_effect_on_codes[i];
4371788ca347SMarcel Moolenaar 
4372788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";%s", code);
4373788ca347SMarcel Moolenaar 	if (cp >= ep)
4374788ca347SMarcel Moolenaar 	    return;		/* Should not occur */
4375788ca347SMarcel Moolenaar 
4376788ca347SMarcel Moolenaar 	if (bit == XO_EFF_RESET) {
4377788ca347SMarcel Moolenaar 	    /* Mark up the old value so we can detect current values as new */
4378788ca347SMarcel Moolenaar 	    oldp->xoc_effects = 0;
4379788ca347SMarcel Moolenaar 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4380788ca347SMarcel Moolenaar 	}
4381788ca347SMarcel Moolenaar     }
4382788ca347SMarcel Moolenaar 
4383788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != oldp->xoc_col_fg) {
4384788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";3%u",
4385788ca347SMarcel Moolenaar 		       (newp->xoc_col_fg != XO_COL_DEFAULT)
4386788ca347SMarcel Moolenaar 		       ? newp->xoc_col_fg - 1 : 9);
4387788ca347SMarcel Moolenaar     }
4388788ca347SMarcel Moolenaar 
4389788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != oldp->xoc_col_bg) {
4390788ca347SMarcel Moolenaar 	cp += snprintf(cp, ep - cp, ";4%u",
4391788ca347SMarcel Moolenaar 		       (newp->xoc_col_bg != XO_COL_DEFAULT)
4392788ca347SMarcel Moolenaar 		       ? newp->xoc_col_bg - 1 : 9);
4393788ca347SMarcel Moolenaar     }
4394788ca347SMarcel Moolenaar 
4395788ca347SMarcel Moolenaar     if (cp - buf != 1 && cp < ep - 3) {
4396788ca347SMarcel Moolenaar 	buf[1] = '[';		/* Overwrite leading ';' */
4397788ca347SMarcel Moolenaar 	*cp++ = 'm';
4398788ca347SMarcel Moolenaar 	*cp = '\0';
4399788ca347SMarcel Moolenaar 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4400788ca347SMarcel Moolenaar     }
4401788ca347SMarcel Moolenaar }
4402788ca347SMarcel Moolenaar 
4403788ca347SMarcel Moolenaar static void
4404788ca347SMarcel Moolenaar xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4405788ca347SMarcel Moolenaar {
4406788ca347SMarcel Moolenaar     xo_colors_t *oldp = &xop->xo_colors;
4407788ca347SMarcel Moolenaar 
4408788ca347SMarcel Moolenaar     /*
4409788ca347SMarcel Moolenaar      * HTML colors are mostly trivial: fill in xo_color_buf with
4410788ca347SMarcel Moolenaar      * a set of class tags representing the colors and effects.
4411788ca347SMarcel Moolenaar      */
4412788ca347SMarcel Moolenaar 
4413788ca347SMarcel Moolenaar     /* If nothing changed, then do nothing */
4414788ca347SMarcel Moolenaar     if (oldp->xoc_effects == newp->xoc_effects
4415788ca347SMarcel Moolenaar 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4416788ca347SMarcel Moolenaar 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4417788ca347SMarcel Moolenaar 	return;
4418788ca347SMarcel Moolenaar 
4419788ca347SMarcel Moolenaar     unsigned i, bit;
4420788ca347SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_color_buf;
4421788ca347SMarcel Moolenaar 
4422788ca347SMarcel Moolenaar     xo_buf_reset(xbp);		/* We rebuild content after each change */
4423788ca347SMarcel Moolenaar 
4424788ca347SMarcel Moolenaar     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4425788ca347SMarcel Moolenaar 	if (!(newp->xoc_effects & bit))
4426788ca347SMarcel Moolenaar 	    continue;
4427788ca347SMarcel Moolenaar 
4428788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " effect-");
4429788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, xo_effect_names[i]);
4430788ca347SMarcel Moolenaar     }
4431788ca347SMarcel Moolenaar 
4432788ca347SMarcel Moolenaar     const char *fg = NULL;
4433788ca347SMarcel Moolenaar     const char *bg = NULL;
4434788ca347SMarcel Moolenaar 
4435788ca347SMarcel Moolenaar     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4436788ca347SMarcel Moolenaar 	fg = xo_color_names[newp->xoc_col_fg];
4437788ca347SMarcel Moolenaar     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4438788ca347SMarcel Moolenaar 	bg = xo_color_names[newp->xoc_col_bg];
4439788ca347SMarcel Moolenaar 
4440788ca347SMarcel Moolenaar     if (newp->xoc_effects & XO_EFF_INVERSE) {
4441788ca347SMarcel Moolenaar 	const char *tmp = fg;
4442788ca347SMarcel Moolenaar 	fg = bg;
4443788ca347SMarcel Moolenaar 	bg = tmp;
4444788ca347SMarcel Moolenaar 	if (fg == NULL)
4445788ca347SMarcel Moolenaar 	    fg = "inverse";
4446788ca347SMarcel Moolenaar 	if (bg == NULL)
4447788ca347SMarcel Moolenaar 	    bg = "inverse";
4448788ca347SMarcel Moolenaar 
4449788ca347SMarcel Moolenaar     }
4450788ca347SMarcel Moolenaar 
4451788ca347SMarcel Moolenaar     if (fg) {
4452788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-fg-");
4453788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, fg);
4454788ca347SMarcel Moolenaar     }
4455788ca347SMarcel Moolenaar 
4456788ca347SMarcel Moolenaar     if (bg) {
4457788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, " color-bg-");
4458788ca347SMarcel Moolenaar 	xo_buf_append_str(xbp, bg);
4459788ca347SMarcel Moolenaar     }
4460788ca347SMarcel Moolenaar }
4461788ca347SMarcel Moolenaar 
4462788ca347SMarcel Moolenaar static void
4463d1a0d267SMarcel Moolenaar xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip)
4464788ca347SMarcel Moolenaar {
4465d1a0d267SMarcel Moolenaar     const char *str = xfip->xfi_content;
4466d1a0d267SMarcel Moolenaar     unsigned len = xfip->xfi_clen;
4467d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
4468d1a0d267SMarcel Moolenaar     unsigned flen = xfip->xfi_flen;
4469d1a0d267SMarcel Moolenaar 
4470788ca347SMarcel Moolenaar     xo_buffer_t xb;
4471788ca347SMarcel Moolenaar 
4472788ca347SMarcel Moolenaar     /* If the string is static and we've in an encoding style, bail */
4473d1a0d267SMarcel Moolenaar     if (len != 0 && xo_style_is_encoding(xop))
4474788ca347SMarcel Moolenaar 	return;
4475788ca347SMarcel Moolenaar 
4476788ca347SMarcel Moolenaar     xo_buf_init(&xb);
4477788ca347SMarcel Moolenaar 
4478788ca347SMarcel Moolenaar     if (len)
4479788ca347SMarcel Moolenaar 	xo_buf_append(&xb, str, len);
4480788ca347SMarcel Moolenaar     else if (flen)
4481d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, &xb, fmt, flen, 0);
4482788ca347SMarcel Moolenaar     else
4483788ca347SMarcel Moolenaar 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
4484788ca347SMarcel Moolenaar 
4485788ca347SMarcel Moolenaar     if (xo_colors_enabled(xop)) {
4486788ca347SMarcel Moolenaar 	switch (xo_style(xop)) {
4487788ca347SMarcel Moolenaar 	case XO_STYLE_TEXT:
4488788ca347SMarcel Moolenaar 	case XO_STYLE_HTML:
4489788ca347SMarcel Moolenaar 	    xo_buf_append(&xb, "", 1);
4490788ca347SMarcel Moolenaar 
4491788ca347SMarcel Moolenaar 	    xo_colors_t xoc = xop->xo_colors;
4492788ca347SMarcel Moolenaar 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
4493788ca347SMarcel Moolenaar 
4494788ca347SMarcel Moolenaar 	    if (xo_style(xop) == XO_STYLE_TEXT) {
4495788ca347SMarcel Moolenaar 		/*
4496788ca347SMarcel Moolenaar 		 * Text mode means emitting the colors as ANSI character
4497788ca347SMarcel Moolenaar 		 * codes.  This will allow people who like colors to have
4498788ca347SMarcel Moolenaar 		 * colors.  The issue is, of course conflicting with the
4499788ca347SMarcel Moolenaar 		 * user's perfectly reasonable color scheme.  Which leads
4500788ca347SMarcel Moolenaar 		 * to the hell of LSCOLORS, where even app need to have
4501788ca347SMarcel Moolenaar 		 * customization hooks for adjusting colors.  Instead we
4502788ca347SMarcel Moolenaar 		 * provide a simpler-but-still-annoying answer where one
4503788ca347SMarcel Moolenaar 		 * can map colors to other colors.
4504788ca347SMarcel Moolenaar 		 */
4505788ca347SMarcel Moolenaar 		xo_colors_handle_text(xop, &xoc);
4506788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
4507788ca347SMarcel Moolenaar 
4508788ca347SMarcel Moolenaar 	    } else {
4509788ca347SMarcel Moolenaar 		/*
4510788ca347SMarcel Moolenaar 		 * HTML output is wrapped in divs, so the color information
4511788ca347SMarcel Moolenaar 		 * must appear in every div until cleared.  Most pathetic.
4512788ca347SMarcel Moolenaar 		 * Most unavoidable.
4513788ca347SMarcel Moolenaar 		 */
4514788ca347SMarcel Moolenaar 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
4515788ca347SMarcel Moolenaar 		xo_colors_handle_html(xop, &xoc);
4516788ca347SMarcel Moolenaar 	    }
4517788ca347SMarcel Moolenaar 
4518788ca347SMarcel Moolenaar 	    xop->xo_colors = xoc;
4519788ca347SMarcel Moolenaar 	    break;
4520788ca347SMarcel Moolenaar 
4521788ca347SMarcel Moolenaar 	case XO_STYLE_XML:
4522788ca347SMarcel Moolenaar 	case XO_STYLE_JSON:
4523d1a0d267SMarcel Moolenaar 	case XO_STYLE_SDPARAMS:
4524d1a0d267SMarcel Moolenaar 	case XO_STYLE_ENCODER:
4525788ca347SMarcel Moolenaar 	    /*
4526788ca347SMarcel Moolenaar 	     * Nothing to do; we did all that work just to clear the stack of
4527788ca347SMarcel Moolenaar 	     * formatting arguments.
4528788ca347SMarcel Moolenaar 	     */
4529788ca347SMarcel Moolenaar 	    break;
4530788ca347SMarcel Moolenaar 	}
4531788ca347SMarcel Moolenaar     }
4532788ca347SMarcel Moolenaar 
4533788ca347SMarcel Moolenaar     xo_buf_cleanup(&xb);
4534788ca347SMarcel Moolenaar }
4535788ca347SMarcel Moolenaar 
453631337658SMarcel Moolenaar static void
4537d1a0d267SMarcel Moolenaar xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip)
453831337658SMarcel Moolenaar {
4539d1a0d267SMarcel Moolenaar     const char *str = xfip->xfi_content;
4540d1a0d267SMarcel Moolenaar     unsigned len = xfip->xfi_clen;
4541d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
4542d1a0d267SMarcel Moolenaar     unsigned flen = xfip->xfi_flen;
4543d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = xfip->xfi_flags;
4544d1a0d267SMarcel Moolenaar 
454531337658SMarcel Moolenaar     static char units_start_xml[] = " units=\"";
454631337658SMarcel Moolenaar     static char units_start_html[] = " data-units=\"";
454731337658SMarcel Moolenaar 
4548d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
4549d1a0d267SMarcel Moolenaar 	xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
455031337658SMarcel Moolenaar 	return;
455131337658SMarcel Moolenaar     }
455231337658SMarcel Moolenaar 
455331337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
455431337658SMarcel Moolenaar     int start = xop->xo_units_offset;
455531337658SMarcel Moolenaar     int stop = xbp->xb_curp - xbp->xb_bufp;
455631337658SMarcel Moolenaar 
4557788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML)
455831337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
4559788ca347SMarcel Moolenaar     else if (xo_style(xop) == XO_STYLE_HTML)
456031337658SMarcel Moolenaar 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
456131337658SMarcel Moolenaar     else
456231337658SMarcel Moolenaar 	return;
456331337658SMarcel Moolenaar 
456431337658SMarcel Moolenaar     if (len)
4565d1a0d267SMarcel Moolenaar 	xo_data_escape(xop, str, len);
456631337658SMarcel Moolenaar     else
4567d1a0d267SMarcel Moolenaar 	xo_do_format_field(xop, NULL, fmt, flen, flags);
456831337658SMarcel Moolenaar 
456931337658SMarcel Moolenaar     xo_buf_append(xbp, "\"", 1);
457031337658SMarcel Moolenaar 
457131337658SMarcel Moolenaar     int now = xbp->xb_curp - xbp->xb_bufp;
457231337658SMarcel Moolenaar     int delta = now - stop;
4573d1a0d267SMarcel Moolenaar     if (delta <= 0) {		/* Strange; no output to move */
457431337658SMarcel Moolenaar 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
457531337658SMarcel Moolenaar 	return;
457631337658SMarcel Moolenaar     }
457731337658SMarcel Moolenaar 
457831337658SMarcel Moolenaar     /*
457931337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the unit value
458031337658SMarcel Moolenaar      * we just created into the right spot.  We make a local copy,
458131337658SMarcel Moolenaar      * move it and then insert our copy.  We know there's room in the
458231337658SMarcel Moolenaar      * buffer, since we're just moving this around.
458331337658SMarcel Moolenaar      */
458431337658SMarcel Moolenaar     char *buf = alloca(delta);
458531337658SMarcel Moolenaar 
458631337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
458731337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
458831337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
458931337658SMarcel Moolenaar }
459031337658SMarcel Moolenaar 
459131337658SMarcel Moolenaar static int
4592d1a0d267SMarcel Moolenaar xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip)
459331337658SMarcel Moolenaar {
4594d1a0d267SMarcel Moolenaar     const char *str = xfip->xfi_content;
4595d1a0d267SMarcel Moolenaar     unsigned len = xfip->xfi_clen;
4596d1a0d267SMarcel Moolenaar     const char *fmt = xfip->xfi_format;
4597d1a0d267SMarcel Moolenaar     unsigned flen = xfip->xfi_flen;
4598d1a0d267SMarcel Moolenaar 
459931337658SMarcel Moolenaar     long width = 0;
460031337658SMarcel Moolenaar     char *bp;
460131337658SMarcel Moolenaar     char *cp;
460231337658SMarcel Moolenaar 
460331337658SMarcel Moolenaar     if (len) {
460431337658SMarcel Moolenaar 	bp = alloca(len + 1);	/* Make local NUL-terminated copy of str */
460531337658SMarcel Moolenaar 	memcpy(bp, str, len);
460631337658SMarcel Moolenaar 	bp[len] = '\0';
460731337658SMarcel Moolenaar 
460831337658SMarcel Moolenaar 	width = strtol(bp, &cp, 0);
460931337658SMarcel Moolenaar 	if (width == LONG_MIN || width == LONG_MAX
461031337658SMarcel Moolenaar 	    || bp == cp || *cp != '\0' ) {
461131337658SMarcel Moolenaar 	    width = 0;
461231337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
461331337658SMarcel Moolenaar 	}
461431337658SMarcel Moolenaar     } else if (flen) {
461531337658SMarcel Moolenaar 	if (flen != 2 || strncmp("%d", fmt, flen) != 0)
461631337658SMarcel Moolenaar 	    xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
4617d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
461831337658SMarcel Moolenaar 	    width = va_arg(xop->xo_vap, int);
461931337658SMarcel Moolenaar     }
462031337658SMarcel Moolenaar 
462131337658SMarcel Moolenaar     return width;
462231337658SMarcel Moolenaar }
462331337658SMarcel Moolenaar 
462431337658SMarcel Moolenaar static void
462531337658SMarcel Moolenaar xo_anchor_clear (xo_handle_t *xop)
462631337658SMarcel Moolenaar {
4627d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_ANCHOR);
462831337658SMarcel Moolenaar     xop->xo_anchor_offset = 0;
462931337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
463031337658SMarcel Moolenaar     xop->xo_anchor_min_width = 0;
463131337658SMarcel Moolenaar }
463231337658SMarcel Moolenaar 
463331337658SMarcel Moolenaar /*
463431337658SMarcel Moolenaar  * An anchor is a marker used to delay field width implications.
463531337658SMarcel Moolenaar  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
463631337658SMarcel Moolenaar  * We are looking for output like "     1/4/5"
463731337658SMarcel Moolenaar  *
463831337658SMarcel Moolenaar  * To make this work, we record the anchor and then return to
463931337658SMarcel Moolenaar  * format it when the end anchor tag is seen.
464031337658SMarcel Moolenaar  */
464131337658SMarcel Moolenaar static void
4642d1a0d267SMarcel Moolenaar xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip)
464331337658SMarcel Moolenaar {
4644788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
464531337658SMarcel Moolenaar 	return;
464631337658SMarcel Moolenaar 
4647d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_ANCHOR))
464831337658SMarcel Moolenaar 	xo_failure(xop, "the anchor already recording is discarded");
464931337658SMarcel Moolenaar 
4650d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_ANCHOR);
465131337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
465231337658SMarcel Moolenaar     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
465331337658SMarcel Moolenaar     xop->xo_anchor_columns = 0;
465431337658SMarcel Moolenaar 
465531337658SMarcel Moolenaar     /*
465631337658SMarcel Moolenaar      * Now we find the width, if possible.  If it's not there,
465731337658SMarcel Moolenaar      * we'll get it on the end anchor.
465831337658SMarcel Moolenaar      */
4659d1a0d267SMarcel Moolenaar     xop->xo_anchor_min_width = xo_find_width(xop, xfip);
466031337658SMarcel Moolenaar }
466131337658SMarcel Moolenaar 
466231337658SMarcel Moolenaar static void
4663d1a0d267SMarcel Moolenaar xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip)
466431337658SMarcel Moolenaar {
4665788ca347SMarcel Moolenaar     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
466631337658SMarcel Moolenaar 	return;
466731337658SMarcel Moolenaar 
4668d1a0d267SMarcel Moolenaar     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
466931337658SMarcel Moolenaar 	xo_failure(xop, "no start anchor");
467031337658SMarcel Moolenaar 	return;
467131337658SMarcel Moolenaar     }
467231337658SMarcel Moolenaar 
4673d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
467431337658SMarcel Moolenaar 
4675d1a0d267SMarcel Moolenaar     int width = xo_find_width(xop, xfip);
467631337658SMarcel Moolenaar     if (width == 0)
467731337658SMarcel Moolenaar 	width = xop->xo_anchor_min_width;
467831337658SMarcel Moolenaar 
467931337658SMarcel Moolenaar     if (width == 0)		/* No width given; nothing to do */
468031337658SMarcel Moolenaar 	goto done;
468131337658SMarcel Moolenaar 
468231337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
468331337658SMarcel Moolenaar     int start = xop->xo_anchor_offset;
468431337658SMarcel Moolenaar     int stop = xbp->xb_curp - xbp->xb_bufp;
468531337658SMarcel Moolenaar     int abswidth = (width > 0) ? width : -width;
468631337658SMarcel Moolenaar     int blen = abswidth - xop->xo_anchor_columns;
468731337658SMarcel Moolenaar 
468831337658SMarcel Moolenaar     if (blen <= 0)		/* Already over width */
468931337658SMarcel Moolenaar 	goto done;
469031337658SMarcel Moolenaar 
469131337658SMarcel Moolenaar     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
469231337658SMarcel Moolenaar 	xo_failure(xop, "width over %u are not supported",
469331337658SMarcel Moolenaar 		   XO_MAX_ANCHOR_WIDTH);
469431337658SMarcel Moolenaar 	goto done;
469531337658SMarcel Moolenaar     }
469631337658SMarcel Moolenaar 
469731337658SMarcel Moolenaar     /* Make a suitable padding field and emit it */
469831337658SMarcel Moolenaar     char *buf = alloca(blen);
469931337658SMarcel Moolenaar     memset(buf, ' ', blen);
4700d1a0d267SMarcel Moolenaar     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
470131337658SMarcel Moolenaar 
470231337658SMarcel Moolenaar     if (width < 0)		/* Already left justified */
470331337658SMarcel Moolenaar 	goto done;
470431337658SMarcel Moolenaar 
470531337658SMarcel Moolenaar     int now = xbp->xb_curp - xbp->xb_bufp;
470631337658SMarcel Moolenaar     int delta = now - stop;
4707d1a0d267SMarcel Moolenaar     if (delta <= 0)		/* Strange; no output to move */
470831337658SMarcel Moolenaar 	goto done;
470931337658SMarcel Moolenaar 
471031337658SMarcel Moolenaar     /*
471131337658SMarcel Moolenaar      * Now we're in it alright.  We've need to insert the padding data
471231337658SMarcel Moolenaar      * we just created (which might be an HTML <div> or text) before
471331337658SMarcel Moolenaar      * the formatted data.  We make a local copy, move it and then
471431337658SMarcel Moolenaar      * insert our copy.  We know there's room in the buffer, since
471531337658SMarcel Moolenaar      * we're just moving this around.
471631337658SMarcel Moolenaar      */
471731337658SMarcel Moolenaar     if (delta > blen)
471831337658SMarcel Moolenaar 	buf = alloca(delta);	/* Expand buffer if needed */
471931337658SMarcel Moolenaar 
472031337658SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + stop, delta);
472131337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
472231337658SMarcel Moolenaar     memmove(xbp->xb_bufp + start, buf, delta);
472331337658SMarcel Moolenaar 
472431337658SMarcel Moolenaar  done:
472531337658SMarcel Moolenaar     xo_anchor_clear(xop);
472631337658SMarcel Moolenaar }
472731337658SMarcel Moolenaar 
4728d1a0d267SMarcel Moolenaar static const char *
4729d1a0d267SMarcel Moolenaar xo_class_name (int ftype)
473031337658SMarcel Moolenaar {
4731d1a0d267SMarcel Moolenaar     switch (ftype) {
4732d1a0d267SMarcel Moolenaar     case 'D': return "decoration";
4733d1a0d267SMarcel Moolenaar     case 'E': return "error";
4734d1a0d267SMarcel Moolenaar     case 'L': return "label";
4735d1a0d267SMarcel Moolenaar     case 'N': return "note";
4736d1a0d267SMarcel Moolenaar     case 'P': return "padding";
4737d1a0d267SMarcel Moolenaar     case 'W': return "warning";
473831337658SMarcel Moolenaar     }
473931337658SMarcel Moolenaar 
4740d1a0d267SMarcel Moolenaar     return NULL;
474131337658SMarcel Moolenaar }
474231337658SMarcel Moolenaar 
4743d1a0d267SMarcel Moolenaar static const char *
4744d1a0d267SMarcel Moolenaar xo_tag_name (int ftype)
4745d1a0d267SMarcel Moolenaar {
4746d1a0d267SMarcel Moolenaar     switch (ftype) {
4747d1a0d267SMarcel Moolenaar     case 'E': return "__error";
4748d1a0d267SMarcel Moolenaar     case 'W': return "__warning";
4749d1a0d267SMarcel Moolenaar     }
4750d1a0d267SMarcel Moolenaar 
4751d1a0d267SMarcel Moolenaar     return NULL;
4752d1a0d267SMarcel Moolenaar }
4753d1a0d267SMarcel Moolenaar 
4754d1a0d267SMarcel Moolenaar static int
4755d1a0d267SMarcel Moolenaar xo_role_wants_default_format (int ftype)
4756d1a0d267SMarcel Moolenaar {
4757d1a0d267SMarcel Moolenaar     switch (ftype) {
4758d1a0d267SMarcel Moolenaar 	/* These roles can be completely empty and/or without formatting */
4759d1a0d267SMarcel Moolenaar     case 'C':
4760d1a0d267SMarcel Moolenaar     case 'G':
4761d1a0d267SMarcel Moolenaar     case '[':
4762d1a0d267SMarcel Moolenaar     case ']':
4763d1a0d267SMarcel Moolenaar 	return 0;
4764d1a0d267SMarcel Moolenaar     }
4765d1a0d267SMarcel Moolenaar 
4766d1a0d267SMarcel Moolenaar     return 1;
4767d1a0d267SMarcel Moolenaar }
4768d1a0d267SMarcel Moolenaar 
4769d1a0d267SMarcel Moolenaar static xo_mapping_t xo_role_names[] = {
4770d1a0d267SMarcel Moolenaar     { 'C', "color" },
4771d1a0d267SMarcel Moolenaar     { 'D', "decoration" },
4772d1a0d267SMarcel Moolenaar     { 'E', "error" },
4773d1a0d267SMarcel Moolenaar     { 'L', "label" },
4774d1a0d267SMarcel Moolenaar     { 'N', "note" },
4775d1a0d267SMarcel Moolenaar     { 'P', "padding" },
4776d1a0d267SMarcel Moolenaar     { 'T', "title" },
4777d1a0d267SMarcel Moolenaar     { 'U', "units" },
4778d1a0d267SMarcel Moolenaar     { 'V', "value" },
4779d1a0d267SMarcel Moolenaar     { 'W', "warning" },
4780d1a0d267SMarcel Moolenaar     { '[', "start-anchor" },
4781d1a0d267SMarcel Moolenaar     { ']', "stop-anchor" },
4782d1a0d267SMarcel Moolenaar     { 0, NULL }
4783d1a0d267SMarcel Moolenaar };
4784d1a0d267SMarcel Moolenaar 
4785d1a0d267SMarcel Moolenaar #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
4786d1a0d267SMarcel Moolenaar #define XO_ROLE_TEXT	'+'
4787d1a0d267SMarcel Moolenaar #define XO_ROLE_NEWLINE	'\n'
4788d1a0d267SMarcel Moolenaar 
4789d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_names[] = {
4790d1a0d267SMarcel Moolenaar     { XFF_COLON, "colon" },
4791d1a0d267SMarcel Moolenaar     { XFF_COMMA, "comma" },
4792d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "display" },
4793d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "encoding" },
4794d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "gettext" },
4795d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "humanize" },
4796d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "hn" },
4797d1a0d267SMarcel Moolenaar     { XFF_HN_SPACE, "hn-space" },
4798d1a0d267SMarcel Moolenaar     { XFF_HN_DECIMAL, "hn-decimal" },
4799d1a0d267SMarcel Moolenaar     { XFF_HN_1000, "hn-1000" },
4800d1a0d267SMarcel Moolenaar     { XFF_KEY, "key" },
4801d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "leaf-list" },
4802d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "list" },
4803d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quotes" },
4804d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "no-quote" },
4805d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "plural" },
4806d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quotes" },
4807d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "quote" },
4808d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "trim" },
4809d1a0d267SMarcel Moolenaar     { XFF_WS, "white" },
4810d1a0d267SMarcel Moolenaar     { 0, NULL }
4811d1a0d267SMarcel Moolenaar };
4812d1a0d267SMarcel Moolenaar 
4813d1a0d267SMarcel Moolenaar #ifdef NOT_NEEDED_YET
4814d1a0d267SMarcel Moolenaar static xo_mapping_t xo_modifier_short_names[] = {
4815d1a0d267SMarcel Moolenaar     { XFF_COLON, "c" },
4816d1a0d267SMarcel Moolenaar     { XFF_DISPLAY_ONLY, "d" },
4817d1a0d267SMarcel Moolenaar     { XFF_ENCODE_ONLY, "e" },
4818d1a0d267SMarcel Moolenaar     { XFF_GT_FIELD, "g" },
4819d1a0d267SMarcel Moolenaar     { XFF_HUMANIZE, "h" },
4820d1a0d267SMarcel Moolenaar     { XFF_KEY, "k" },
4821d1a0d267SMarcel Moolenaar     { XFF_LEAF_LIST, "l" },
4822d1a0d267SMarcel Moolenaar     { XFF_NOQUOTE, "n" },
4823d1a0d267SMarcel Moolenaar     { XFF_GT_PLURAL, "p" },
4824d1a0d267SMarcel Moolenaar     { XFF_QUOTE, "q" },
4825d1a0d267SMarcel Moolenaar     { XFF_TRIM_WS, "t" },
4826d1a0d267SMarcel Moolenaar     { XFF_WS, "w" },
4827d1a0d267SMarcel Moolenaar     { 0, NULL }
4828d1a0d267SMarcel Moolenaar };
4829d1a0d267SMarcel Moolenaar #endif /* NOT_NEEDED_YET */
4830d1a0d267SMarcel Moolenaar 
4831d1a0d267SMarcel Moolenaar static int
4832d1a0d267SMarcel Moolenaar xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
4833d1a0d267SMarcel Moolenaar {
4834d1a0d267SMarcel Moolenaar     int rc = 1;
4835d1a0d267SMarcel Moolenaar     const char *cp;
4836d1a0d267SMarcel Moolenaar 
4837d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp; cp++)
4838d1a0d267SMarcel Moolenaar 	if (*cp == '{' || *cp == '\n')
4839d1a0d267SMarcel Moolenaar 	    rc += 1;
4840d1a0d267SMarcel Moolenaar 
4841d1a0d267SMarcel Moolenaar     return rc * 2 + 1;
4842d1a0d267SMarcel Moolenaar }
484331337658SMarcel Moolenaar 
484431337658SMarcel Moolenaar /*
4845d1a0d267SMarcel Moolenaar  * The field format is:
484631337658SMarcel Moolenaar  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
4847d1a0d267SMarcel Moolenaar  * Roles are optional and include the following field types:
484831337658SMarcel Moolenaar  *   'D': decoration; something non-text and non-data (colons, commmas)
484931337658SMarcel Moolenaar  *   'E': error message
4850d1a0d267SMarcel Moolenaar  *   'G': gettext() the entire string; optional domainname as content
485131337658SMarcel Moolenaar  *   'L': label; text preceding data
485231337658SMarcel Moolenaar  *   'N': note; text following data
485331337658SMarcel Moolenaar  *   'P': padding; whitespace
485431337658SMarcel Moolenaar  *   'T': Title, where 'content' is a column title
485531337658SMarcel Moolenaar  *   'U': Units, where 'content' is the unit label
485631337658SMarcel Moolenaar  *   'V': value, where 'content' is the name of the field (the default)
485731337658SMarcel Moolenaar  *   'W': warning message
485831337658SMarcel Moolenaar  *   '[': start a section of anchored text
485931337658SMarcel Moolenaar  *   ']': end a section of anchored text
4860d1a0d267SMarcel Moolenaar  * The following modifiers are also supported:
486131337658SMarcel Moolenaar  *   'c': flag: emit a colon after the label
4862d1a0d267SMarcel Moolenaar  *   'd': field is only emitted for display styles (text and html)
4863d1a0d267SMarcel Moolenaar  *   'e': field is only emitted for encoding styles (xml and json)
4864d1a0d267SMarcel Moolenaar  *   'g': gettext() the field
4865d1a0d267SMarcel Moolenaar  *   'h': humanize a numeric value (only for display styles)
486631337658SMarcel Moolenaar  *   'k': this field is a key, suitable for XPath predicates
486731337658SMarcel Moolenaar  *   'l': a leaf-list, a simple list of values
486831337658SMarcel Moolenaar  *   'n': no quotes around this field
4869d1a0d267SMarcel Moolenaar  *   'p': the field has plural gettext semantics (ngettext)
487031337658SMarcel Moolenaar  *   'q': add quotes around this field
487131337658SMarcel Moolenaar  *   't': trim whitespace around the value
487231337658SMarcel Moolenaar  *   'w': emit a blank after the label
487331337658SMarcel Moolenaar  * The print-fmt and encode-fmt strings is the printf-style formating
487431337658SMarcel Moolenaar  * for this data.  JSON and XML will use the encoding-fmt, if present.
487531337658SMarcel Moolenaar  * If the encode-fmt is not provided, it defaults to the print-fmt.
487631337658SMarcel Moolenaar  * If the print-fmt is not provided, it defaults to 's'.
487731337658SMarcel Moolenaar  */
4878d1a0d267SMarcel Moolenaar static const char *
4879d1a0d267SMarcel Moolenaar xo_parse_roles (xo_handle_t *xop, const char *fmt,
4880d1a0d267SMarcel Moolenaar 		const char *basep, xo_field_info_t *xfip)
4881d1a0d267SMarcel Moolenaar {
4882d1a0d267SMarcel Moolenaar     const char *sp;
4883d1a0d267SMarcel Moolenaar     unsigned ftype = 0;
4884d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags = 0;
4885d1a0d267SMarcel Moolenaar     uint8_t fnum = 0;
488631337658SMarcel Moolenaar 
488731337658SMarcel Moolenaar     for (sp = basep; sp; sp++) {
488831337658SMarcel Moolenaar 	if (*sp == ':' || *sp == '/' || *sp == '}')
488931337658SMarcel Moolenaar 	    break;
489031337658SMarcel Moolenaar 
489131337658SMarcel Moolenaar 	if (*sp == '\\') {
489231337658SMarcel Moolenaar 	    if (sp[1] == '\0') {
489331337658SMarcel Moolenaar 		xo_failure(xop, "backslash at the end of string");
4894d1a0d267SMarcel Moolenaar 		return NULL;
489531337658SMarcel Moolenaar 	    }
4896d1a0d267SMarcel Moolenaar 
4897d1a0d267SMarcel Moolenaar 	    /* Anything backslashed is ignored */
489831337658SMarcel Moolenaar 	    sp += 1;
489931337658SMarcel Moolenaar 	    continue;
490031337658SMarcel Moolenaar 	}
490131337658SMarcel Moolenaar 
4902d1a0d267SMarcel Moolenaar 	if (*sp == ',') {
4903d1a0d267SMarcel Moolenaar 	    const char *np;
4904d1a0d267SMarcel Moolenaar 	    for (np = ++sp; *np; np++)
4905d1a0d267SMarcel Moolenaar 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
4906d1a0d267SMarcel Moolenaar 		    break;
4907d1a0d267SMarcel Moolenaar 
4908d1a0d267SMarcel Moolenaar 	    int slen = np - sp;
4909d1a0d267SMarcel Moolenaar 	    if (slen > 0) {
4910d1a0d267SMarcel Moolenaar 		xo_xff_flags_t value;
4911d1a0d267SMarcel Moolenaar 
4912d1a0d267SMarcel Moolenaar 		value = xo_name_lookup(xo_role_names, sp, slen);
4913d1a0d267SMarcel Moolenaar 		if (value)
4914d1a0d267SMarcel Moolenaar 		    ftype = value;
4915d1a0d267SMarcel Moolenaar 		else {
4916d1a0d267SMarcel Moolenaar 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
4917d1a0d267SMarcel Moolenaar 		    if (value)
4918d1a0d267SMarcel Moolenaar 			flags |= value;
4919d1a0d267SMarcel Moolenaar 		    else
4920d1a0d267SMarcel Moolenaar 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
4921d1a0d267SMarcel Moolenaar 				   slen, sp);
4922d1a0d267SMarcel Moolenaar 		}
4923d1a0d267SMarcel Moolenaar 	    }
4924d1a0d267SMarcel Moolenaar 
4925d1a0d267SMarcel Moolenaar 	    sp = np - 1;
4926d1a0d267SMarcel Moolenaar 	    continue;
4927d1a0d267SMarcel Moolenaar 	}
4928d1a0d267SMarcel Moolenaar 
492931337658SMarcel Moolenaar 	switch (*sp) {
4930788ca347SMarcel Moolenaar 	case 'C':
493131337658SMarcel Moolenaar 	case 'D':
493231337658SMarcel Moolenaar 	case 'E':
4933d1a0d267SMarcel Moolenaar 	case 'G':
493431337658SMarcel Moolenaar 	case 'L':
493531337658SMarcel Moolenaar 	case 'N':
493631337658SMarcel Moolenaar 	case 'P':
493731337658SMarcel Moolenaar 	case 'T':
493831337658SMarcel Moolenaar 	case 'U':
493931337658SMarcel Moolenaar 	case 'V':
494031337658SMarcel Moolenaar 	case 'W':
494131337658SMarcel Moolenaar 	case '[':
494231337658SMarcel Moolenaar 	case ']':
494331337658SMarcel Moolenaar 	    if (ftype != 0) {
4944d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
4945d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
4946d1a0d267SMarcel Moolenaar 		return NULL;
494731337658SMarcel Moolenaar 	    }
494831337658SMarcel Moolenaar 	    ftype = *sp;
494931337658SMarcel Moolenaar 	    break;
495031337658SMarcel Moolenaar 
4951d1a0d267SMarcel Moolenaar 	case '0':
4952d1a0d267SMarcel Moolenaar 	case '1':
4953d1a0d267SMarcel Moolenaar 	case '2':
4954d1a0d267SMarcel Moolenaar 	case '3':
4955d1a0d267SMarcel Moolenaar 	case '4':
4956d1a0d267SMarcel Moolenaar 	case '5':
4957d1a0d267SMarcel Moolenaar 	case '6':
4958d1a0d267SMarcel Moolenaar 	case '7':
4959d1a0d267SMarcel Moolenaar 	case '8':
4960d1a0d267SMarcel Moolenaar 	case '9':
4961d1a0d267SMarcel Moolenaar 	    fnum = (fnum * 10) + (*sp - '0');
4962d1a0d267SMarcel Moolenaar 	    break;
4963d1a0d267SMarcel Moolenaar 
496431337658SMarcel Moolenaar 	case 'c':
496531337658SMarcel Moolenaar 	    flags |= XFF_COLON;
496631337658SMarcel Moolenaar 	    break;
496731337658SMarcel Moolenaar 
496831337658SMarcel Moolenaar 	case 'd':
496931337658SMarcel Moolenaar 	    flags |= XFF_DISPLAY_ONLY;
497031337658SMarcel Moolenaar 	    break;
497131337658SMarcel Moolenaar 
497231337658SMarcel Moolenaar 	case 'e':
497331337658SMarcel Moolenaar 	    flags |= XFF_ENCODE_ONLY;
497431337658SMarcel Moolenaar 	    break;
497531337658SMarcel Moolenaar 
4976d1a0d267SMarcel Moolenaar 	case 'g':
4977d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_FIELD;
4978d1a0d267SMarcel Moolenaar 	    break;
4979d1a0d267SMarcel Moolenaar 
4980d1a0d267SMarcel Moolenaar 	case 'h':
4981d1a0d267SMarcel Moolenaar 	    flags |= XFF_HUMANIZE;
4982d1a0d267SMarcel Moolenaar 	    break;
4983d1a0d267SMarcel Moolenaar 
498431337658SMarcel Moolenaar 	case 'k':
498531337658SMarcel Moolenaar 	    flags |= XFF_KEY;
498631337658SMarcel Moolenaar 	    break;
498731337658SMarcel Moolenaar 
498831337658SMarcel Moolenaar 	case 'l':
498931337658SMarcel Moolenaar 	    flags |= XFF_LEAF_LIST;
499031337658SMarcel Moolenaar 	    break;
499131337658SMarcel Moolenaar 
499231337658SMarcel Moolenaar 	case 'n':
499331337658SMarcel Moolenaar 	    flags |= XFF_NOQUOTE;
499431337658SMarcel Moolenaar 	    break;
499531337658SMarcel Moolenaar 
4996d1a0d267SMarcel Moolenaar 	case 'p':
4997d1a0d267SMarcel Moolenaar 	    flags |= XFF_GT_PLURAL;
4998d1a0d267SMarcel Moolenaar 	    break;
4999d1a0d267SMarcel Moolenaar 
500031337658SMarcel Moolenaar 	case 'q':
500131337658SMarcel Moolenaar 	    flags |= XFF_QUOTE;
500231337658SMarcel Moolenaar 	    break;
500331337658SMarcel Moolenaar 
500431337658SMarcel Moolenaar 	case 't':
500531337658SMarcel Moolenaar 	    flags |= XFF_TRIM_WS;
500631337658SMarcel Moolenaar 	    break;
500731337658SMarcel Moolenaar 
500831337658SMarcel Moolenaar 	case 'w':
500931337658SMarcel Moolenaar 	    flags |= XFF_WS;
501031337658SMarcel Moolenaar 	    break;
501131337658SMarcel Moolenaar 
501231337658SMarcel Moolenaar 	default:
5013d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5014d1a0d267SMarcel Moolenaar 		       xo_printable(fmt));
501531337658SMarcel Moolenaar 	    /*
501631337658SMarcel Moolenaar 	     * No good answer here; a bad format will likely
501731337658SMarcel Moolenaar 	     * mean a core file.  We just return and hope
501831337658SMarcel Moolenaar 	     * the caller notices there's no output, and while
5019d1a0d267SMarcel Moolenaar 	     * that seems, well, bad, there's nothing better.
502031337658SMarcel Moolenaar 	     */
5021d1a0d267SMarcel Moolenaar 	    return NULL;
5022d1a0d267SMarcel Moolenaar 	}
5023d1a0d267SMarcel Moolenaar 
5024d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
5025d1a0d267SMarcel Moolenaar 	    if (flags & XFF_COLON) {
5026d1a0d267SMarcel Moolenaar 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5027d1a0d267SMarcel Moolenaar 			   "'%s'", xo_printable(fmt));
5028d1a0d267SMarcel Moolenaar 		flags &= ~XFF_COLON;
5029d1a0d267SMarcel Moolenaar 	    }
503031337658SMarcel Moolenaar 	}
503131337658SMarcel Moolenaar     }
503231337658SMarcel Moolenaar 
5033d1a0d267SMarcel Moolenaar     xfip->xfi_flags = flags;
5034d1a0d267SMarcel Moolenaar     xfip->xfi_ftype = ftype ?: 'V';
5035d1a0d267SMarcel Moolenaar     xfip->xfi_fnum = fnum;
5036d1a0d267SMarcel Moolenaar 
5037d1a0d267SMarcel Moolenaar     return sp;
5038d1a0d267SMarcel Moolenaar }
5039d1a0d267SMarcel Moolenaar 
5040d1a0d267SMarcel Moolenaar /*
5041d1a0d267SMarcel Moolenaar  * Number any remaining fields that need numbers.  Note that some
5042d1a0d267SMarcel Moolenaar  * field types (text, newline, escaped braces) never get numbers.
5043d1a0d267SMarcel Moolenaar  */
5044d1a0d267SMarcel Moolenaar static void
5045d1a0d267SMarcel Moolenaar xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5046d1a0d267SMarcel Moolenaar 				    const char *fmt UNUSED,
5047d1a0d267SMarcel Moolenaar 				    xo_field_info_t *fields)
5048d1a0d267SMarcel Moolenaar {
5049d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5050d1a0d267SMarcel Moolenaar     unsigned fnum, max_fields;
5051d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5052d1a0d267SMarcel Moolenaar 
5053d1a0d267SMarcel Moolenaar     /* First make a list of add the explicitly used bits */
5054d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5055d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5056d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5057d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5058d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5059d1a0d267SMarcel Moolenaar 	case 'G':
5060d1a0d267SMarcel Moolenaar 	    continue;
5061d1a0d267SMarcel Moolenaar 	}
5062d1a0d267SMarcel Moolenaar 
5063d1a0d267SMarcel Moolenaar 	fnum += 1;
5064d1a0d267SMarcel Moolenaar 	if (fnum >= 63)
5065d1a0d267SMarcel Moolenaar 	    break;
5066d1a0d267SMarcel Moolenaar 
5067d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5068d1a0d267SMarcel Moolenaar 	    bits |= 1 << xfip->xfi_fnum;
5069d1a0d267SMarcel Moolenaar     }
5070d1a0d267SMarcel Moolenaar 
5071d1a0d267SMarcel Moolenaar     max_fields = fnum;
5072d1a0d267SMarcel Moolenaar 
5073d1a0d267SMarcel Moolenaar     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5074d1a0d267SMarcel Moolenaar 	switch (xfip->xfi_ftype) {
5075d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5076d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5077d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5078d1a0d267SMarcel Moolenaar 	case 'G':
5079d1a0d267SMarcel Moolenaar 	    continue;
5080d1a0d267SMarcel Moolenaar 	}
5081d1a0d267SMarcel Moolenaar 
5082d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum != 0)
5083d1a0d267SMarcel Moolenaar 	    continue;
5084d1a0d267SMarcel Moolenaar 
5085d1a0d267SMarcel Moolenaar 	/* Find the next unassigned field */
5086d1a0d267SMarcel Moolenaar 	for (fnum++; bits & (1 << fnum); fnum++)
5087d1a0d267SMarcel Moolenaar 	    continue;
5088d1a0d267SMarcel Moolenaar 
5089d1a0d267SMarcel Moolenaar 	if (fnum > max_fields)
5090d1a0d267SMarcel Moolenaar 	    break;
5091d1a0d267SMarcel Moolenaar 
5092d1a0d267SMarcel Moolenaar 	xfip->xfi_fnum = fnum;	/* Mark the field number */
5093d1a0d267SMarcel Moolenaar 	bits |= 1 << fnum;	/* Mark it used */
5094d1a0d267SMarcel Moolenaar     }
5095d1a0d267SMarcel Moolenaar }
5096d1a0d267SMarcel Moolenaar 
5097d1a0d267SMarcel Moolenaar /*
5098d1a0d267SMarcel Moolenaar  * The format string uses field numbers, so we need to whiffle thru it
5099d1a0d267SMarcel Moolenaar  * and make sure everything's sane and lovely.
5100d1a0d267SMarcel Moolenaar  */
5101d1a0d267SMarcel Moolenaar static int
5102d1a0d267SMarcel Moolenaar xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5103d1a0d267SMarcel Moolenaar 			xo_field_info_t *fields, unsigned num_fields)
5104d1a0d267SMarcel Moolenaar {
5105d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5106d1a0d267SMarcel Moolenaar     unsigned field, fnum;
5107d1a0d267SMarcel Moolenaar     uint64_t bits = 0;
5108d1a0d267SMarcel Moolenaar 
5109d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5110d1a0d267SMarcel Moolenaar 	/* Fields default to 1:1 with natural position */
5111d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == 0)
5112d1a0d267SMarcel Moolenaar 	    xfip->xfi_fnum = field + 1;
5113d1a0d267SMarcel Moolenaar 	else if (xfip->xfi_fnum > num_fields) {
5114d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5115d1a0d267SMarcel Moolenaar 	    return -1;
5116d1a0d267SMarcel Moolenaar 	}
5117d1a0d267SMarcel Moolenaar 
5118d1a0d267SMarcel Moolenaar 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5119d1a0d267SMarcel Moolenaar 	if (fnum < 64) {	/* Only test what fits */
5120d1a0d267SMarcel Moolenaar 	    if (bits & (1 << fnum)) {
5121d1a0d267SMarcel Moolenaar 		xo_failure(xop, "field number %u reused: '%s'",
5122d1a0d267SMarcel Moolenaar 			   xfip->xfi_fnum, fmt);
5123d1a0d267SMarcel Moolenaar 		return -1;
5124d1a0d267SMarcel Moolenaar 	    }
5125d1a0d267SMarcel Moolenaar 	    bits |= 1 << fnum;
5126d1a0d267SMarcel Moolenaar 	}
5127d1a0d267SMarcel Moolenaar     }
5128d1a0d267SMarcel Moolenaar 
5129d1a0d267SMarcel Moolenaar     return 0;
5130d1a0d267SMarcel Moolenaar }
5131d1a0d267SMarcel Moolenaar 
5132d1a0d267SMarcel Moolenaar static int
5133d1a0d267SMarcel Moolenaar xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5134d1a0d267SMarcel Moolenaar 		 unsigned num_fields, const char *fmt)
5135d1a0d267SMarcel Moolenaar {
5136d1a0d267SMarcel Moolenaar     static const char default_format[] = "%s";
5137d1a0d267SMarcel Moolenaar     const char *cp, *sp, *ep, *basep;
5138d1a0d267SMarcel Moolenaar     unsigned field = 0;
5139d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip = fields;
5140d1a0d267SMarcel Moolenaar     unsigned seen_fnum = 0;
5141d1a0d267SMarcel Moolenaar 
5142d1a0d267SMarcel Moolenaar     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5143d1a0d267SMarcel Moolenaar 	xfip->xfi_start = cp;
5144d1a0d267SMarcel Moolenaar 
5145d1a0d267SMarcel Moolenaar 	if (*cp == '\n') {
5146d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5147d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = 1;
5148d1a0d267SMarcel Moolenaar 	    cp += 1;
5149d1a0d267SMarcel Moolenaar 	    continue;
5150d1a0d267SMarcel Moolenaar 	}
5151d1a0d267SMarcel Moolenaar 
5152d1a0d267SMarcel Moolenaar 	if (*cp != '{') {
5153d1a0d267SMarcel Moolenaar 	    /* Normal text */
5154d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5155d1a0d267SMarcel Moolenaar 		if (*sp == '{' || *sp == '\n')
5156d1a0d267SMarcel Moolenaar 		    break;
5157d1a0d267SMarcel Moolenaar 	    }
5158d1a0d267SMarcel Moolenaar 
5159d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5160d1a0d267SMarcel Moolenaar 	    xfip->xfi_content = cp;
5161d1a0d267SMarcel Moolenaar 	    xfip->xfi_clen = sp - cp;
5162d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = sp;
5163d1a0d267SMarcel Moolenaar 
5164d1a0d267SMarcel Moolenaar 	    cp = sp;
5165d1a0d267SMarcel Moolenaar 	    continue;
5166d1a0d267SMarcel Moolenaar 	}
5167d1a0d267SMarcel Moolenaar 
5168d1a0d267SMarcel Moolenaar 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5169d1a0d267SMarcel Moolenaar 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5170d1a0d267SMarcel Moolenaar 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5171d1a0d267SMarcel Moolenaar 
5172d1a0d267SMarcel Moolenaar 	    cp += 2;	/* Skip over _both_ characters */
5173d1a0d267SMarcel Moolenaar 	    for (sp = cp; *sp; sp++) {
5174d1a0d267SMarcel Moolenaar 		if (*sp == '}' && sp[1] == '}')
5175d1a0d267SMarcel Moolenaar 		    break;
5176d1a0d267SMarcel Moolenaar 	    }
5177d1a0d267SMarcel Moolenaar 	    if (*sp == '\0') {
5178d1a0d267SMarcel Moolenaar 		xo_failure(xop, "missing closing '}}': '%s'",
5179d1a0d267SMarcel Moolenaar 			   xo_printable(fmt));
5180d1a0d267SMarcel Moolenaar 		return -1;
5181d1a0d267SMarcel Moolenaar 	    }
5182d1a0d267SMarcel Moolenaar 
5183d1a0d267SMarcel Moolenaar 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5184d1a0d267SMarcel Moolenaar 
5185d1a0d267SMarcel Moolenaar 	    /* Move along the string, but don't run off the end */
5186d1a0d267SMarcel Moolenaar 	    if (*sp == '}' && sp[1] == '}')
5187d1a0d267SMarcel Moolenaar 		sp += 2;
5188d1a0d267SMarcel Moolenaar 	    cp = *sp ? sp : sp;
5189d1a0d267SMarcel Moolenaar 	    xfip->xfi_next = cp;
5190d1a0d267SMarcel Moolenaar 	    continue;
5191d1a0d267SMarcel Moolenaar 	}
5192d1a0d267SMarcel Moolenaar 
5193d1a0d267SMarcel Moolenaar 	/* We are looking at the start of a field definition */
5194d1a0d267SMarcel Moolenaar 	xfip->xfi_start = basep = cp + 1;
5195d1a0d267SMarcel Moolenaar 
5196d1a0d267SMarcel Moolenaar 	const char *format = NULL;
5197d1a0d267SMarcel Moolenaar 	int flen = 0;
5198d1a0d267SMarcel Moolenaar 
5199d1a0d267SMarcel Moolenaar 	/* Looking at roles and modifiers */
5200d1a0d267SMarcel Moolenaar 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5201d1a0d267SMarcel Moolenaar 	if (sp == NULL) {
5202d1a0d267SMarcel Moolenaar 	    /* xo_failure has already been called */
5203d1a0d267SMarcel Moolenaar 	    return -1;
5204d1a0d267SMarcel Moolenaar 	}
5205d1a0d267SMarcel Moolenaar 
5206d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum)
5207d1a0d267SMarcel Moolenaar 	    seen_fnum = 1;
5208d1a0d267SMarcel Moolenaar 
5209d1a0d267SMarcel Moolenaar 	/* Looking at content */
521031337658SMarcel Moolenaar 	if (*sp == ':') {
521131337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
521231337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
521331337658SMarcel Moolenaar 		    break;
521431337658SMarcel Moolenaar 		if (*sp == '\\') {
521531337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
521631337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
521731337658SMarcel Moolenaar 			return -1;
521831337658SMarcel Moolenaar 		    }
521931337658SMarcel Moolenaar 		    sp += 1;
522031337658SMarcel Moolenaar 		    continue;
522131337658SMarcel Moolenaar 		}
522231337658SMarcel Moolenaar 	    }
522331337658SMarcel Moolenaar 	    if (ep != sp) {
5224d1a0d267SMarcel Moolenaar 		xfip->xfi_clen = sp - ep;
5225d1a0d267SMarcel Moolenaar 		xfip->xfi_content = ep;
522631337658SMarcel Moolenaar 	    }
522731337658SMarcel Moolenaar 	} else {
5228d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
522931337658SMarcel Moolenaar 	    return -1;
523031337658SMarcel Moolenaar 	}
523131337658SMarcel Moolenaar 
5232d1a0d267SMarcel Moolenaar 	/* Looking at main (display) format */
523331337658SMarcel Moolenaar 	if (*sp == '/') {
523431337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
523531337658SMarcel Moolenaar 		if (*sp == '}' || *sp == '/')
523631337658SMarcel Moolenaar 		    break;
523731337658SMarcel Moolenaar 		if (*sp == '\\') {
523831337658SMarcel Moolenaar 		    if (sp[1] == '\0') {
523931337658SMarcel Moolenaar 			xo_failure(xop, "backslash at the end of string");
524031337658SMarcel Moolenaar 			return -1;
524131337658SMarcel Moolenaar 		    }
524231337658SMarcel Moolenaar 		    sp += 1;
524331337658SMarcel Moolenaar 		    continue;
524431337658SMarcel Moolenaar 		}
524531337658SMarcel Moolenaar 	    }
524631337658SMarcel Moolenaar 	    flen = sp - ep;
524731337658SMarcel Moolenaar 	    format = ep;
524831337658SMarcel Moolenaar 	}
524931337658SMarcel Moolenaar 
5250d1a0d267SMarcel Moolenaar 	/* Looking at encoding format */
525131337658SMarcel Moolenaar 	if (*sp == '/') {
525231337658SMarcel Moolenaar 	    for (ep = ++sp; *sp; sp++) {
525331337658SMarcel Moolenaar 		if (*sp == '}')
525431337658SMarcel Moolenaar 		    break;
525531337658SMarcel Moolenaar 	    }
5256d1a0d267SMarcel Moolenaar 
5257d1a0d267SMarcel Moolenaar 	    xfip->xfi_encoding = ep;
5258d1a0d267SMarcel Moolenaar 	    xfip->xfi_elen = sp - ep;
525931337658SMarcel Moolenaar 	}
526031337658SMarcel Moolenaar 
5261d1a0d267SMarcel Moolenaar 	if (*sp != '}') {
5262d1a0d267SMarcel Moolenaar 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
526331337658SMarcel Moolenaar 	    return -1;
526431337658SMarcel Moolenaar 	}
526531337658SMarcel Moolenaar 
5266d1a0d267SMarcel Moolenaar 	xfip->xfi_len = sp - xfip->xfi_start;
5267d1a0d267SMarcel Moolenaar 	xfip->xfi_next = ++sp;
5268d1a0d267SMarcel Moolenaar 
5269d1a0d267SMarcel Moolenaar 	/* If we have content, then we have a default format */
5270d1a0d267SMarcel Moolenaar 	if (xfip->xfi_clen || format) {
5271d1a0d267SMarcel Moolenaar 	    if (format) {
5272d1a0d267SMarcel Moolenaar 		xfip->xfi_format = format;
5273d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = flen;
5274d1a0d267SMarcel Moolenaar 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
5275d1a0d267SMarcel Moolenaar 		xfip->xfi_format = default_format;
5276d1a0d267SMarcel Moolenaar 		xfip->xfi_flen = 2;
5277d1a0d267SMarcel Moolenaar 	    }
527831337658SMarcel Moolenaar 	}
527931337658SMarcel Moolenaar 
5280d1a0d267SMarcel Moolenaar 	cp = sp;
5281d1a0d267SMarcel Moolenaar     }
5282545ddfbeSMarcel Moolenaar 
5283d1a0d267SMarcel Moolenaar     int rc = 0;
5284d1a0d267SMarcel Moolenaar 
5285d1a0d267SMarcel Moolenaar     /*
5286d1a0d267SMarcel Moolenaar      * If we saw a field number on at least one field, then we need
5287d1a0d267SMarcel Moolenaar      * to enforce some rules and/or guidelines.
5288d1a0d267SMarcel Moolenaar      */
5289d1a0d267SMarcel Moolenaar     if (seen_fnum)
5290d1a0d267SMarcel Moolenaar 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5291d1a0d267SMarcel Moolenaar 
5292d1a0d267SMarcel Moolenaar     return rc;
5293d1a0d267SMarcel Moolenaar }
5294d1a0d267SMarcel Moolenaar 
5295d1a0d267SMarcel Moolenaar /*
5296d1a0d267SMarcel Moolenaar  * We are passed a pointer to a format string just past the "{G:}"
5297d1a0d267SMarcel Moolenaar  * field.  We build a simplified version of the format string.
5298d1a0d267SMarcel Moolenaar  */
5299d1a0d267SMarcel Moolenaar static int
5300d1a0d267SMarcel Moolenaar xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5301d1a0d267SMarcel Moolenaar 		       xo_buffer_t *xbp,
5302d1a0d267SMarcel Moolenaar 		       xo_field_info_t *fields,
5303d1a0d267SMarcel Moolenaar 		       int this_field,
5304d1a0d267SMarcel Moolenaar 		       const char *fmt UNUSED,
5305d1a0d267SMarcel Moolenaar 		       xo_simplify_field_func_t field_cb)
5306d1a0d267SMarcel Moolenaar {
5307d1a0d267SMarcel Moolenaar     unsigned ftype;
5308d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags;
5309d1a0d267SMarcel Moolenaar     int field = this_field + 1;
5310d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5311d1a0d267SMarcel Moolenaar     char ch;
5312d1a0d267SMarcel Moolenaar 
5313d1a0d267SMarcel Moolenaar     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5314d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5315d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5316d1a0d267SMarcel Moolenaar 
5317d1a0d267SMarcel Moolenaar 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5318d1a0d267SMarcel Moolenaar 	    if (field_cb)
5319d1a0d267SMarcel Moolenaar 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5320d1a0d267SMarcel Moolenaar 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5321d1a0d267SMarcel Moolenaar 	}
5322d1a0d267SMarcel Moolenaar 
5323d1a0d267SMarcel Moolenaar 	switch (ftype) {
5324d1a0d267SMarcel Moolenaar 	case 'G':
5325d1a0d267SMarcel Moolenaar 	    /* Ignore gettext roles */
5326d1a0d267SMarcel Moolenaar 	    break;
5327d1a0d267SMarcel Moolenaar 
5328d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5329d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "\n", 1);
5330d1a0d267SMarcel Moolenaar 	    break;
5331d1a0d267SMarcel Moolenaar 
5332d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5333d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5334d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5335d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5336d1a0d267SMarcel Moolenaar 	    break;
5337d1a0d267SMarcel Moolenaar 
5338d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5339d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5340d1a0d267SMarcel Moolenaar 	    break;
5341d1a0d267SMarcel Moolenaar 
5342d1a0d267SMarcel Moolenaar 	default:
5343d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "{", 1);
5344d1a0d267SMarcel Moolenaar 	    if (ftype != 'V') {
5345d1a0d267SMarcel Moolenaar 		ch = ftype;
5346d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, &ch, 1);
5347d1a0d267SMarcel Moolenaar 	    }
5348d1a0d267SMarcel Moolenaar 
5349d1a0d267SMarcel Moolenaar 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5350d1a0d267SMarcel Moolenaar 	    if (fnum) {
5351d1a0d267SMarcel Moolenaar 		char num[12];
5352d1a0d267SMarcel Moolenaar 		/* Field numbers are origin 1, not 0, following printf(3) */
5353d1a0d267SMarcel Moolenaar 		snprintf(num, sizeof(num), "%u", fnum);
5354d1a0d267SMarcel Moolenaar 		xo_buf_append(xbp, num, strlen(num));
5355d1a0d267SMarcel Moolenaar 	    }
5356d1a0d267SMarcel Moolenaar 
5357d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, ":", 1);
5358d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5359d1a0d267SMarcel Moolenaar 	    xo_buf_append(xbp, "}", 1);
5360d1a0d267SMarcel Moolenaar 	}
5361d1a0d267SMarcel Moolenaar     }
5362d1a0d267SMarcel Moolenaar 
5363d1a0d267SMarcel Moolenaar     xo_buf_append(xbp, "", 1);
5364d1a0d267SMarcel Moolenaar     return 0;
5365d1a0d267SMarcel Moolenaar }
5366d1a0d267SMarcel Moolenaar 
5367d1a0d267SMarcel Moolenaar void
5368d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5369d1a0d267SMarcel Moolenaar void
5370d1a0d267SMarcel Moolenaar xo_dump_fields (xo_field_info_t *fields)
5371d1a0d267SMarcel Moolenaar {
5372d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5373d1a0d267SMarcel Moolenaar 
5374d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5375d1a0d267SMarcel Moolenaar 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5376d1a0d267SMarcel Moolenaar 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5377d1a0d267SMarcel Moolenaar 	       (unsigned long) xfip->xfi_flags,
5378d1a0d267SMarcel Moolenaar 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5379d1a0d267SMarcel Moolenaar 	       xfip->xfi_ftype,
5380d1a0d267SMarcel Moolenaar 	       xfip->xfi_clen, xfip->xfi_content ?: "",
5381d1a0d267SMarcel Moolenaar 	       xfip->xfi_flen, xfip->xfi_format ?: "",
5382d1a0d267SMarcel Moolenaar 	       xfip->xfi_elen, xfip->xfi_encoding ?: "");
5383d1a0d267SMarcel Moolenaar     }
5384d1a0d267SMarcel Moolenaar }
5385d1a0d267SMarcel Moolenaar 
5386d1a0d267SMarcel Moolenaar #ifdef HAVE_GETTEXT
5387d1a0d267SMarcel Moolenaar /*
5388d1a0d267SMarcel Moolenaar  * Find the field that matches the given field number
5389d1a0d267SMarcel Moolenaar  */
5390d1a0d267SMarcel Moolenaar static xo_field_info_t *
5391d1a0d267SMarcel Moolenaar xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5392d1a0d267SMarcel Moolenaar {
5393d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5394d1a0d267SMarcel Moolenaar 
5395d1a0d267SMarcel Moolenaar     for (xfip = fields; xfip->xfi_ftype; xfip++)
5396d1a0d267SMarcel Moolenaar 	if (xfip->xfi_fnum == fnum)
5397d1a0d267SMarcel Moolenaar 	    return xfip;
5398d1a0d267SMarcel Moolenaar 
5399d1a0d267SMarcel Moolenaar     return NULL;
5400d1a0d267SMarcel Moolenaar }
5401d1a0d267SMarcel Moolenaar 
5402d1a0d267SMarcel Moolenaar /*
5403d1a0d267SMarcel Moolenaar  * At this point, we need to consider if the fields have been reordered,
5404d1a0d267SMarcel Moolenaar  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5405d1a0d267SMarcel Moolenaar  *
5406d1a0d267SMarcel Moolenaar  * We need to rewrite the new_fields using the old fields order,
5407d1a0d267SMarcel Moolenaar  * so that we can render the message using the arguments as they
5408d1a0d267SMarcel Moolenaar  * appear on the stack.  It's a lot of work, but we don't really
5409d1a0d267SMarcel Moolenaar  * want to (eventually) fall into the standard printf code which
5410d1a0d267SMarcel Moolenaar  * means using the arguments straight (and in order) from the
5411d1a0d267SMarcel Moolenaar  * varargs we were originally passed.
5412d1a0d267SMarcel Moolenaar  */
5413d1a0d267SMarcel Moolenaar static void
5414d1a0d267SMarcel Moolenaar xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
5415d1a0d267SMarcel Moolenaar 			   xo_field_info_t *fields, unsigned max_fields)
5416d1a0d267SMarcel Moolenaar {
5417d1a0d267SMarcel Moolenaar     xo_field_info_t tmp[max_fields];
5418d1a0d267SMarcel Moolenaar     bzero(tmp, max_fields * sizeof(tmp[0]));
5419d1a0d267SMarcel Moolenaar 
5420d1a0d267SMarcel Moolenaar     unsigned fnum = 0;
5421d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *outp, *zp;
5422d1a0d267SMarcel Moolenaar     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
5423d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5424d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5425d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5426d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5427d1a0d267SMarcel Moolenaar 	case 'G':
5428d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5429d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5430d1a0d267SMarcel Moolenaar 	    continue;
5431d1a0d267SMarcel Moolenaar 	}
5432d1a0d267SMarcel Moolenaar 
5433d1a0d267SMarcel Moolenaar 	zp = xo_gettext_find_field(fields, ++fnum);
5434d1a0d267SMarcel Moolenaar 	if (zp == NULL) { 	/* Should not occur */
5435d1a0d267SMarcel Moolenaar 	    *outp = *newp;
5436d1a0d267SMarcel Moolenaar 	    outp->xfi_renum = 0;
5437d1a0d267SMarcel Moolenaar 	    continue;
5438d1a0d267SMarcel Moolenaar 	}
5439d1a0d267SMarcel Moolenaar 
5440d1a0d267SMarcel Moolenaar 	*outp = *zp;
5441d1a0d267SMarcel Moolenaar 	outp->xfi_renum = newp->xfi_fnum;
5442d1a0d267SMarcel Moolenaar     }
5443d1a0d267SMarcel Moolenaar 
5444d1a0d267SMarcel Moolenaar     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
5445d1a0d267SMarcel Moolenaar }
5446d1a0d267SMarcel Moolenaar 
5447d1a0d267SMarcel Moolenaar /*
5448d1a0d267SMarcel Moolenaar  * We've got two lists of fields, the old list from the original
5449d1a0d267SMarcel Moolenaar  * format string and the new one from the parsed gettext reply.  The
5450d1a0d267SMarcel Moolenaar  * new list has the localized words, where the old list has the
5451d1a0d267SMarcel Moolenaar  * formatting information.  We need to combine them into a single list
5452d1a0d267SMarcel Moolenaar  * (the new list).
5453d1a0d267SMarcel Moolenaar  *
5454d1a0d267SMarcel Moolenaar  * If the list needs to be reordered, then we've got more serious work
5455d1a0d267SMarcel Moolenaar  * to do.
5456d1a0d267SMarcel Moolenaar  */
5457d1a0d267SMarcel Moolenaar static int
5458d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
5459d1a0d267SMarcel Moolenaar 		    const char *gtfmt, xo_field_info_t *old_fields,
5460d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields, unsigned new_max_fields,
5461d1a0d267SMarcel Moolenaar 		    int *reorderedp)
5462d1a0d267SMarcel Moolenaar {
5463d1a0d267SMarcel Moolenaar     int reordered = 0;
5464d1a0d267SMarcel Moolenaar     xo_field_info_t *newp, *oldp, *startp = old_fields;
5465d1a0d267SMarcel Moolenaar 
5466d1a0d267SMarcel Moolenaar     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
5467d1a0d267SMarcel Moolenaar 
5468d1a0d267SMarcel Moolenaar     for (newp = new_fields; newp->xfi_ftype; newp++) {
5469d1a0d267SMarcel Moolenaar 	switch (newp->xfi_ftype) {
5470d1a0d267SMarcel Moolenaar 	case XO_ROLE_NEWLINE:
5471d1a0d267SMarcel Moolenaar 	case XO_ROLE_TEXT:
5472d1a0d267SMarcel Moolenaar 	case XO_ROLE_EBRACE:
5473d1a0d267SMarcel Moolenaar 	    continue;
5474d1a0d267SMarcel Moolenaar 
5475d1a0d267SMarcel Moolenaar 	case 'V':
5476d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5477d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype != 'V')
5478d1a0d267SMarcel Moolenaar 		    continue;
5479d1a0d267SMarcel Moolenaar 		if (newp->xfi_clen != oldp->xfi_clen
5480d1a0d267SMarcel Moolenaar 		    || strncmp(newp->xfi_content, oldp->xfi_content,
5481d1a0d267SMarcel Moolenaar 			       oldp->xfi_clen) != 0) {
5482d1a0d267SMarcel Moolenaar 		    reordered = 1;
5483d1a0d267SMarcel Moolenaar 		    continue;
5484d1a0d267SMarcel Moolenaar 		}
5485d1a0d267SMarcel Moolenaar 		startp = oldp + 1;
5486d1a0d267SMarcel Moolenaar 		break;
5487d1a0d267SMarcel Moolenaar 	    }
5488d1a0d267SMarcel Moolenaar 
5489d1a0d267SMarcel Moolenaar 	    /* Didn't find it on the first pass (starting from start) */
5490d1a0d267SMarcel Moolenaar 	    if (oldp->xfi_ftype == 0) {
5491d1a0d267SMarcel Moolenaar 		for (oldp = old_fields; oldp < startp; oldp++) {
5492d1a0d267SMarcel Moolenaar 		    if (oldp->xfi_ftype != 'V')
5493d1a0d267SMarcel Moolenaar 			continue;
5494d1a0d267SMarcel Moolenaar 		    if (newp->xfi_clen != oldp->xfi_clen)
5495d1a0d267SMarcel Moolenaar 			continue;
5496d1a0d267SMarcel Moolenaar 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
5497d1a0d267SMarcel Moolenaar 				oldp->xfi_clen) != 0)
5498d1a0d267SMarcel Moolenaar 			continue;
5499d1a0d267SMarcel Moolenaar 		    reordered = 1;
5500d1a0d267SMarcel Moolenaar 		    break;
5501d1a0d267SMarcel Moolenaar 		}
5502d1a0d267SMarcel Moolenaar 		if (oldp == startp) {
5503d1a0d267SMarcel Moolenaar 		    /* Field not found */
5504d1a0d267SMarcel Moolenaar 		    xo_failure(xop, "post-gettext format can't find field "
5505d1a0d267SMarcel Moolenaar 			       "'%.*s' in format '%s'",
5506d1a0d267SMarcel Moolenaar 			       newp->xfi_clen, newp->xfi_content,
5507d1a0d267SMarcel Moolenaar 			       xo_printable(gtfmt));
5508d1a0d267SMarcel Moolenaar 		    return -1;
5509d1a0d267SMarcel Moolenaar 		}
5510d1a0d267SMarcel Moolenaar 	    }
5511d1a0d267SMarcel Moolenaar 	    break;
5512d1a0d267SMarcel Moolenaar 
5513d1a0d267SMarcel Moolenaar 	default:
5514d1a0d267SMarcel Moolenaar 	    /*
5515d1a0d267SMarcel Moolenaar 	     * Other fields don't have names for us to use, so if
5516d1a0d267SMarcel Moolenaar 	     * the types aren't the same, then we'll have to assume
5517d1a0d267SMarcel Moolenaar 	     * the original field is a match.
5518d1a0d267SMarcel Moolenaar 	     */
5519d1a0d267SMarcel Moolenaar 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5520d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
5521d1a0d267SMarcel Moolenaar 		    break;
5522d1a0d267SMarcel Moolenaar 		if (oldp->xfi_ftype == newp->xfi_ftype)
5523d1a0d267SMarcel Moolenaar 		    goto copy_it; /* Assumably we have a match */
5524d1a0d267SMarcel Moolenaar 	    }
5525d1a0d267SMarcel Moolenaar 	    continue;
5526d1a0d267SMarcel Moolenaar 	}
5527d1a0d267SMarcel Moolenaar 
5528d1a0d267SMarcel Moolenaar 	/*
5529d1a0d267SMarcel Moolenaar 	 * Found a match; copy over appropriate fields
5530d1a0d267SMarcel Moolenaar 	 */
5531d1a0d267SMarcel Moolenaar     copy_it:
5532d1a0d267SMarcel Moolenaar 	newp->xfi_flags = oldp->xfi_flags;
5533d1a0d267SMarcel Moolenaar 	newp->xfi_fnum = oldp->xfi_fnum;
5534d1a0d267SMarcel Moolenaar 	newp->xfi_format = oldp->xfi_format;
5535d1a0d267SMarcel Moolenaar 	newp->xfi_flen = oldp->xfi_flen;
5536d1a0d267SMarcel Moolenaar 	newp->xfi_encoding = oldp->xfi_encoding;
5537d1a0d267SMarcel Moolenaar 	newp->xfi_elen = oldp->xfi_elen;
5538d1a0d267SMarcel Moolenaar     }
5539d1a0d267SMarcel Moolenaar 
5540d1a0d267SMarcel Moolenaar     *reorderedp = reordered;
5541d1a0d267SMarcel Moolenaar     if (reordered) {
5542d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
5543d1a0d267SMarcel Moolenaar 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
5544d1a0d267SMarcel Moolenaar     }
5545d1a0d267SMarcel Moolenaar 
5546d1a0d267SMarcel Moolenaar     return 0;
5547d1a0d267SMarcel Moolenaar }
5548d1a0d267SMarcel Moolenaar 
5549d1a0d267SMarcel Moolenaar /*
5550d1a0d267SMarcel Moolenaar  * We don't want to make gettext() calls here with a complete format
5551d1a0d267SMarcel Moolenaar  * string, since that means changing a flag would mean a
5552d1a0d267SMarcel Moolenaar  * labor-intensive re-translation expense.  Instead we build a
5553d1a0d267SMarcel Moolenaar  * simplified form with a reduced level of detail, perform a lookup on
5554d1a0d267SMarcel Moolenaar  * that string and then re-insert the formating info.
5555d1a0d267SMarcel Moolenaar  *
5556d1a0d267SMarcel Moolenaar  * So something like:
5557d1a0d267SMarcel Moolenaar  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
5558d1a0d267SMarcel Moolenaar  * would have a lookup string of:
5559d1a0d267SMarcel Moolenaar  *   "close {:fd} returned {:error} {:test}\n"
5560d1a0d267SMarcel Moolenaar  *
5561d1a0d267SMarcel Moolenaar  * We also need to handling reordering of fields, where the gettext()
5562d1a0d267SMarcel Moolenaar  * reply string uses fields in a different order than the original
5563d1a0d267SMarcel Moolenaar  * format string:
5564d1a0d267SMarcel Moolenaar  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
5565d1a0d267SMarcel Moolenaar  * If we have to reorder fields within the message, then things get
5566d1a0d267SMarcel Moolenaar  * complicated.  See xo_gettext_rewrite_fields.
5567d1a0d267SMarcel Moolenaar  *
5568d1a0d267SMarcel Moolenaar  * Summary: i18n aighn't cheap.
5569d1a0d267SMarcel Moolenaar  */
5570d1a0d267SMarcel Moolenaar static const char *
5571d1a0d267SMarcel Moolenaar xo_gettext_build_format (xo_handle_t *xop UNUSED,
5572d1a0d267SMarcel Moolenaar 			 xo_field_info_t *fields UNUSED,
5573d1a0d267SMarcel Moolenaar 			 int this_field UNUSED,
5574d1a0d267SMarcel Moolenaar 			 const char *fmt, char **new_fmtp)
5575d1a0d267SMarcel Moolenaar {
5576d1a0d267SMarcel Moolenaar     if (xo_style_is_encoding(xop))
5577d1a0d267SMarcel Moolenaar 	goto bail;
5578d1a0d267SMarcel Moolenaar 
5579d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
5580d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
5581d1a0d267SMarcel Moolenaar 
5582d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields,
5583d1a0d267SMarcel Moolenaar 				   this_field, fmt, NULL))
5584d1a0d267SMarcel Moolenaar 	goto bail2;
5585d1a0d267SMarcel Moolenaar 
5586d1a0d267SMarcel Moolenaar     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
5587d1a0d267SMarcel Moolenaar     if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
5588d1a0d267SMarcel Moolenaar 	goto bail2;
5589d1a0d267SMarcel Moolenaar 
5590d1a0d267SMarcel Moolenaar     xo_buf_cleanup(&xb);
5591d1a0d267SMarcel Moolenaar 
5592d1a0d267SMarcel Moolenaar     char *new_fmt = xo_strndup(gtfmt, -1);
5593d1a0d267SMarcel Moolenaar     if (new_fmt == NULL)
5594d1a0d267SMarcel Moolenaar 	goto bail2;
5595d1a0d267SMarcel Moolenaar 
5596d1a0d267SMarcel Moolenaar     *new_fmtp = new_fmt;
5597d1a0d267SMarcel Moolenaar     return new_fmt;
5598d1a0d267SMarcel Moolenaar 
5599d1a0d267SMarcel Moolenaar  bail2:
5600d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(&xb);
5601d1a0d267SMarcel Moolenaar  bail:
5602d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
5603d1a0d267SMarcel Moolenaar     return fmt;
5604d1a0d267SMarcel Moolenaar }
5605d1a0d267SMarcel Moolenaar 
5606d1a0d267SMarcel Moolenaar static void
5607d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
5608d1a0d267SMarcel Moolenaar 			    unsigned *fstart, unsigned min_fstart,
5609d1a0d267SMarcel Moolenaar 			    unsigned *fend, unsigned max_fend)
5610d1a0d267SMarcel Moolenaar {
5611d1a0d267SMarcel Moolenaar     xo_field_info_t *xfip;
5612d1a0d267SMarcel Moolenaar     char *buf;
5613d1a0d267SMarcel Moolenaar     unsigned base = fstart[min_fstart];
5614d1a0d267SMarcel Moolenaar     unsigned blen = fend[max_fend] - base;
5615d1a0d267SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_data;
5616d1a0d267SMarcel Moolenaar 
5617d1a0d267SMarcel Moolenaar     if (blen == 0)
5618d1a0d267SMarcel Moolenaar 	return;
5619d1a0d267SMarcel Moolenaar 
5620d1a0d267SMarcel Moolenaar     buf = xo_realloc(NULL, blen);
5621d1a0d267SMarcel Moolenaar     if (buf == NULL)
5622d1a0d267SMarcel Moolenaar 	return;
5623d1a0d267SMarcel Moolenaar 
5624d1a0d267SMarcel Moolenaar     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
5625d1a0d267SMarcel Moolenaar 
5626d1a0d267SMarcel Moolenaar     unsigned field = min_fstart, soff, doff = base, len, fnum;
5627d1a0d267SMarcel Moolenaar     xo_field_info_t *zp;
5628d1a0d267SMarcel Moolenaar 
5629d1a0d267SMarcel Moolenaar     /*
5630d1a0d267SMarcel Moolenaar      * Be aware there are two competing views of "field number": we
5631d1a0d267SMarcel Moolenaar      * want the user to thing in terms of "The {1:size}" where {G:},
5632d1a0d267SMarcel Moolenaar      * newlines, escaped braces, and text don't have numbers.  But is
5633d1a0d267SMarcel Moolenaar      * also the internal view, where we have an array of
5634d1a0d267SMarcel Moolenaar      * xo_field_info_t and every field have an index.  fnum, fstart[]
5635d1a0d267SMarcel Moolenaar      * and fend[] are the latter, but xfi_renum is the former.
5636d1a0d267SMarcel Moolenaar      */
5637d1a0d267SMarcel Moolenaar     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
5638d1a0d267SMarcel Moolenaar 	fnum = field;
5639d1a0d267SMarcel Moolenaar 	if (xfip->xfi_renum) {
5640d1a0d267SMarcel Moolenaar 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
5641d1a0d267SMarcel Moolenaar 	    fnum = zp ? zp - fields : field;
5642d1a0d267SMarcel Moolenaar 	}
5643d1a0d267SMarcel Moolenaar 
5644d1a0d267SMarcel Moolenaar 	soff = fstart[fnum];
5645d1a0d267SMarcel Moolenaar 	len = fend[fnum] - soff;
5646d1a0d267SMarcel Moolenaar 
5647d1a0d267SMarcel Moolenaar 	if (len > 0) {
5648d1a0d267SMarcel Moolenaar 	    soff -= base;
5649d1a0d267SMarcel Moolenaar 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
5650d1a0d267SMarcel Moolenaar 	    doff += len;
5651d1a0d267SMarcel Moolenaar 	}
5652d1a0d267SMarcel Moolenaar     }
5653d1a0d267SMarcel Moolenaar 
5654d1a0d267SMarcel Moolenaar     xo_free(buf);
5655d1a0d267SMarcel Moolenaar }
5656d1a0d267SMarcel Moolenaar #else  /* HAVE_GETTEXT */
5657d1a0d267SMarcel Moolenaar static const char *
5658d1a0d267SMarcel Moolenaar xo_gettext_build_format (xo_handle_t *xop UNUSED,
5659d1a0d267SMarcel Moolenaar 			 xo_field_info_t *fields UNUSED,
5660d1a0d267SMarcel Moolenaar 			 int this_field UNUSED,
5661d1a0d267SMarcel Moolenaar 			 const char *fmt UNUSED, char **new_fmtp)
5662d1a0d267SMarcel Moolenaar {
5663d1a0d267SMarcel Moolenaar     *new_fmtp = NULL;
5664d1a0d267SMarcel Moolenaar     return fmt;
5665d1a0d267SMarcel Moolenaar }
5666d1a0d267SMarcel Moolenaar 
5667d1a0d267SMarcel Moolenaar static int
5668d1a0d267SMarcel Moolenaar xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
5669d1a0d267SMarcel Moolenaar 		    const char *gtfmt UNUSED,
5670d1a0d267SMarcel Moolenaar 		    xo_field_info_t *old_fields UNUSED,
5671d1a0d267SMarcel Moolenaar 		    xo_field_info_t *new_fields UNUSED,
5672d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields UNUSED,
5673d1a0d267SMarcel Moolenaar 		    int *reorderedp UNUSED)
5674d1a0d267SMarcel Moolenaar {
5675d1a0d267SMarcel Moolenaar     return -1;
5676d1a0d267SMarcel Moolenaar }
5677d1a0d267SMarcel Moolenaar 
5678d1a0d267SMarcel Moolenaar static void
5679d1a0d267SMarcel Moolenaar xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
5680d1a0d267SMarcel Moolenaar 		    xo_field_info_t *fields UNUSED,
5681d1a0d267SMarcel Moolenaar 		    unsigned *fstart UNUSED, unsigned min_fstart UNUSED,
5682d1a0d267SMarcel Moolenaar 		    unsigned *fend UNUSED, unsigned max_fend UNUSED)
5683d1a0d267SMarcel Moolenaar {
5684d1a0d267SMarcel Moolenaar     return;
5685d1a0d267SMarcel Moolenaar }
5686d1a0d267SMarcel Moolenaar #endif /* HAVE_GETTEXT */
5687d1a0d267SMarcel Moolenaar 
5688d1a0d267SMarcel Moolenaar /*
5689d1a0d267SMarcel Moolenaar  * The central function for emitting libxo output.
5690d1a0d267SMarcel Moolenaar  */
5691d1a0d267SMarcel Moolenaar static int
5692d1a0d267SMarcel Moolenaar xo_do_emit (xo_handle_t *xop, const char *fmt)
5693d1a0d267SMarcel Moolenaar {
5694d1a0d267SMarcel Moolenaar     int gettext_inuse = 0;
5695d1a0d267SMarcel Moolenaar     int gettext_changed = 0;
5696d1a0d267SMarcel Moolenaar     int gettext_reordered = 0;
5697d1a0d267SMarcel Moolenaar     xo_field_info_t *new_fields = NULL;
5698d1a0d267SMarcel Moolenaar 
5699d1a0d267SMarcel Moolenaar     int rc = 0;
5700d1a0d267SMarcel Moolenaar     int flush = XOF_ISSET(xop, XOF_FLUSH);
5701d1a0d267SMarcel Moolenaar     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
5702d1a0d267SMarcel Moolenaar     char *new_fmt = NULL;
5703d1a0d267SMarcel Moolenaar 
5704d1a0d267SMarcel Moolenaar     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
5705d1a0d267SMarcel Moolenaar 	flush_line = 0;
5706d1a0d267SMarcel Moolenaar 
5707d1a0d267SMarcel Moolenaar     xop->xo_columns = 0;	/* Always reset it */
5708d1a0d267SMarcel Moolenaar     xop->xo_errno = errno;	/* Save for "%m" */
5709d1a0d267SMarcel Moolenaar 
5710d1a0d267SMarcel Moolenaar     unsigned max_fields = xo_count_fields(xop, fmt), field;
5711d1a0d267SMarcel Moolenaar     xo_field_info_t fields[max_fields], *xfip;
5712d1a0d267SMarcel Moolenaar 
5713d1a0d267SMarcel Moolenaar     bzero(fields, max_fields * sizeof(fields[0]));
5714d1a0d267SMarcel Moolenaar 
5715d1a0d267SMarcel Moolenaar     if (xo_parse_fields(xop, fields, max_fields, fmt))
5716d1a0d267SMarcel Moolenaar 	return -1;		/* Warning already displayed */
5717d1a0d267SMarcel Moolenaar 
5718d1a0d267SMarcel Moolenaar     unsigned ftype;
5719d1a0d267SMarcel Moolenaar     xo_xff_flags_t flags;
5720d1a0d267SMarcel Moolenaar 
5721d1a0d267SMarcel Moolenaar     /*
5722d1a0d267SMarcel Moolenaar      * Some overhead for gettext; if the fields in the msgstr returned
5723d1a0d267SMarcel Moolenaar      * by gettext are reordered, then we need to record start and end
5724d1a0d267SMarcel Moolenaar      * for each field.  We'll go ahead and render the fields in the
5725d1a0d267SMarcel Moolenaar      * normal order, but later we can then reconstruct the reordered
5726d1a0d267SMarcel Moolenaar      * fields using these fstart/fend values.
5727d1a0d267SMarcel Moolenaar      */
5728d1a0d267SMarcel Moolenaar     unsigned flimit = max_fields * 2; /* Pessimistic limit */
5729d1a0d267SMarcel Moolenaar     unsigned min_fstart = flimit - 1;
5730d1a0d267SMarcel Moolenaar     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
5731d1a0d267SMarcel Moolenaar     unsigned fstart[flimit];
5732d1a0d267SMarcel Moolenaar     bzero(fstart, flimit * sizeof(fstart[0]));
5733d1a0d267SMarcel Moolenaar     unsigned fend[flimit];
5734d1a0d267SMarcel Moolenaar     bzero(fend, flimit * sizeof(fend[0]));
5735d1a0d267SMarcel Moolenaar 
5736d1a0d267SMarcel Moolenaar     for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields;
5737d1a0d267SMarcel Moolenaar 	 xfip++, field++) {
5738d1a0d267SMarcel Moolenaar 	ftype = xfip->xfi_ftype;
5739d1a0d267SMarcel Moolenaar 	flags = xfip->xfi_flags;
5740d1a0d267SMarcel Moolenaar 
5741d1a0d267SMarcel Moolenaar 	/* Record field start offset */
5742d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
5743d1a0d267SMarcel Moolenaar 	    fstart[field] = xo_buf_offset(&xop->xo_data);
5744d1a0d267SMarcel Moolenaar 	    if (min_fstart > field)
5745d1a0d267SMarcel Moolenaar 		min_fstart = field;
5746d1a0d267SMarcel Moolenaar 	}
5747d1a0d267SMarcel Moolenaar 
5748d1a0d267SMarcel Moolenaar 	if (ftype == XO_ROLE_NEWLINE) {
5749d1a0d267SMarcel Moolenaar 	    xo_line_close(xop);
5750d1a0d267SMarcel Moolenaar 	    if (flush_line && xo_flush_h(xop) < 0)
5751d1a0d267SMarcel Moolenaar 		return -1;
5752d1a0d267SMarcel Moolenaar 	    goto bottom;
5753d1a0d267SMarcel Moolenaar 
5754d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_EBRACE) {
5755d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
5756d1a0d267SMarcel Moolenaar 	    goto bottom;
5757d1a0d267SMarcel Moolenaar 
5758d1a0d267SMarcel Moolenaar 	} else if (ftype == XO_ROLE_TEXT) {
5759d1a0d267SMarcel Moolenaar 	    /* Normal text */
5760d1a0d267SMarcel Moolenaar 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
5761d1a0d267SMarcel Moolenaar 	    goto bottom;
5762d1a0d267SMarcel Moolenaar 	}
5763d1a0d267SMarcel Moolenaar 
5764d1a0d267SMarcel Moolenaar 	/*
5765d1a0d267SMarcel Moolenaar 	 * Notes and units need the 'w' flag handled before the content.
5766d1a0d267SMarcel Moolenaar 	 */
5767d1a0d267SMarcel Moolenaar 	if (ftype == 'N' || ftype == 'U') {
5768d1a0d267SMarcel Moolenaar 	    if (flags & XFF_WS) {
5769d1a0d267SMarcel Moolenaar 		xo_format_content(xop, "padding", NULL, " ", 1,
5770d1a0d267SMarcel Moolenaar 				  NULL, 0, flags);
5771d1a0d267SMarcel Moolenaar 		flags &= ~XFF_WS; /* Block later handling of this */
5772d1a0d267SMarcel Moolenaar 	    }
5773d1a0d267SMarcel Moolenaar 	}
5774d1a0d267SMarcel Moolenaar 
5775d1a0d267SMarcel Moolenaar 	if (ftype == 'V')
5776d1a0d267SMarcel Moolenaar 	    xo_format_value(xop, xfip->xfi_content, xfip->xfi_clen,
5777d1a0d267SMarcel Moolenaar 			    xfip->xfi_format, xfip->xfi_flen,
5778d1a0d267SMarcel Moolenaar 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
5779d1a0d267SMarcel Moolenaar 	else if (ftype == '[')
5780d1a0d267SMarcel Moolenaar 	    xo_anchor_start(xop, xfip);
5781545ddfbeSMarcel Moolenaar 	else if (ftype == ']')
5782d1a0d267SMarcel Moolenaar 	    xo_anchor_stop(xop, xfip);
5783788ca347SMarcel Moolenaar 	else if (ftype == 'C')
5784d1a0d267SMarcel Moolenaar 	    xo_format_colors(xop, xfip);
5785545ddfbeSMarcel Moolenaar 
5786d1a0d267SMarcel Moolenaar 	else if (ftype == 'G') {
5787d1a0d267SMarcel Moolenaar 	    /*
5788d1a0d267SMarcel Moolenaar 	     * A {G:domain} field; disect the domain name and translate
5789d1a0d267SMarcel Moolenaar 	     * the remaining portion of the input string.  If the user
5790d1a0d267SMarcel Moolenaar 	     * didn't put the {G:} at the start of the format string, then
5791d1a0d267SMarcel Moolenaar 	     * assumably they just want us to translate the rest of it.
5792d1a0d267SMarcel Moolenaar 	     * Since gettext returns strings in a static buffer, we make
5793d1a0d267SMarcel Moolenaar 	     * a copy in new_fmt.
5794d1a0d267SMarcel Moolenaar 	     */
5795d1a0d267SMarcel Moolenaar 	    xo_set_gettext_domain(xop, xfip);
5796d1a0d267SMarcel Moolenaar 
5797d1a0d267SMarcel Moolenaar 	    if (!gettext_inuse) { /* Only translate once */
5798d1a0d267SMarcel Moolenaar 		gettext_inuse = 1;
5799d1a0d267SMarcel Moolenaar 		if (new_fmt) {
5800d1a0d267SMarcel Moolenaar 		    xo_free(new_fmt);
5801d1a0d267SMarcel Moolenaar 		    new_fmt = NULL;
5802545ddfbeSMarcel Moolenaar 		}
5803545ddfbeSMarcel Moolenaar 
5804d1a0d267SMarcel Moolenaar 		xo_gettext_build_format(xop, fields, field,
5805d1a0d267SMarcel Moolenaar 					xfip->xfi_next, &new_fmt);
5806d1a0d267SMarcel Moolenaar 		if (new_fmt) {
5807d1a0d267SMarcel Moolenaar 		    gettext_changed = 1;
5808d1a0d267SMarcel Moolenaar 
5809d1a0d267SMarcel Moolenaar 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
5810d1a0d267SMarcel Moolenaar 
5811d1a0d267SMarcel Moolenaar 		    if (++new_max_fields < max_fields)
5812d1a0d267SMarcel Moolenaar 			new_max_fields = max_fields;
5813d1a0d267SMarcel Moolenaar 
5814d1a0d267SMarcel Moolenaar 		    /* Leave a blank slot at the beginning */
5815d1a0d267SMarcel Moolenaar 		    int sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
5816d1a0d267SMarcel Moolenaar 		    new_fields = alloca(sz);
5817d1a0d267SMarcel Moolenaar 		    bzero(new_fields, sz);
5818d1a0d267SMarcel Moolenaar 
5819d1a0d267SMarcel Moolenaar 		    if (!xo_parse_fields(xop, new_fields + 1,
5820d1a0d267SMarcel Moolenaar 					 new_max_fields, new_fmt)) {
5821d1a0d267SMarcel Moolenaar 			gettext_reordered = 0;
5822d1a0d267SMarcel Moolenaar 
5823d1a0d267SMarcel Moolenaar 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
5824d1a0d267SMarcel Moolenaar 					fields, new_fields + 1,
5825d1a0d267SMarcel Moolenaar 					new_max_fields, &gettext_reordered)) {
5826d1a0d267SMarcel Moolenaar 
5827d1a0d267SMarcel Moolenaar 			    if (gettext_reordered) {
5828d1a0d267SMarcel Moolenaar 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
5829d1a0d267SMarcel Moolenaar 				    xo_failure(xop, "gettext finds reordered "
5830d1a0d267SMarcel Moolenaar 					       "fields in '%s' and '%s'",
5831d1a0d267SMarcel Moolenaar 					       xo_printable(fmt),
5832d1a0d267SMarcel Moolenaar 					       xo_printable(new_fmt));
5833d1a0d267SMarcel Moolenaar 				flush_line = 0; /* Must keep at content */
5834d1a0d267SMarcel Moolenaar 				XOIF_SET(xop, XOIF_REORDER);
5835d1a0d267SMarcel Moolenaar 			    }
5836d1a0d267SMarcel Moolenaar 
5837d1a0d267SMarcel Moolenaar 			    field = -1; /* Will be incremented at top of loop */
5838d1a0d267SMarcel Moolenaar 			    xfip = new_fields;
5839d1a0d267SMarcel Moolenaar 			    max_fields = new_max_fields;
5840d1a0d267SMarcel Moolenaar 			}
5841d1a0d267SMarcel Moolenaar 		    }
5842d1a0d267SMarcel Moolenaar 		}
5843d1a0d267SMarcel Moolenaar 	    }
5844d1a0d267SMarcel Moolenaar 	    continue;
5845d1a0d267SMarcel Moolenaar 
5846d1a0d267SMarcel Moolenaar 	} else  if (xfip->xfi_clen || xfip->xfi_format) {
5847d1a0d267SMarcel Moolenaar 
5848d1a0d267SMarcel Moolenaar 	    const char *class_name = xo_class_name(ftype);
5849d1a0d267SMarcel Moolenaar 	    if (class_name)
5850d1a0d267SMarcel Moolenaar 		xo_format_content(xop, class_name, xo_tag_name(ftype),
5851d1a0d267SMarcel Moolenaar 				  xfip->xfi_content, xfip->xfi_clen,
5852d1a0d267SMarcel Moolenaar 				  xfip->xfi_format, xfip->xfi_flen, flags);
585331337658SMarcel Moolenaar 	    else if (ftype == 'T')
5854d1a0d267SMarcel Moolenaar 		xo_format_title(xop, xfip);
5855d1a0d267SMarcel Moolenaar 	    else if (ftype == 'U')
5856d1a0d267SMarcel Moolenaar 		xo_format_units(xop, xfip);
5857d1a0d267SMarcel Moolenaar 	    else
5858d1a0d267SMarcel Moolenaar 		xo_failure(xop, "unknown field type: '%c'", ftype);
5859545ddfbeSMarcel Moolenaar 	}
586031337658SMarcel Moolenaar 
586131337658SMarcel Moolenaar 	if (flags & XFF_COLON)
5862d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
586331337658SMarcel Moolenaar 
5864d1a0d267SMarcel Moolenaar 	if (flags & XFF_WS)
5865d1a0d267SMarcel Moolenaar 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
5866d1a0d267SMarcel Moolenaar 
5867d1a0d267SMarcel Moolenaar     bottom:
5868d1a0d267SMarcel Moolenaar 	/* Record the end-of-field offset */
5869d1a0d267SMarcel Moolenaar 	if (gettext_reordered) {
5870d1a0d267SMarcel Moolenaar 	    fend[field] = xo_buf_offset(&xop->xo_data);
5871d1a0d267SMarcel Moolenaar 	    max_fend = field;
587231337658SMarcel Moolenaar 	}
587331337658SMarcel Moolenaar     }
587431337658SMarcel Moolenaar 
5875d1a0d267SMarcel Moolenaar     if (gettext_changed && gettext_reordered) {
5876d1a0d267SMarcel Moolenaar 	/* Final step: rebuild the content using the rendered fields */
5877d1a0d267SMarcel Moolenaar 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
5878d1a0d267SMarcel Moolenaar 				   fend, max_fend);
5879d1a0d267SMarcel Moolenaar     }
5880d1a0d267SMarcel Moolenaar 
5881d1a0d267SMarcel Moolenaar     XOIF_CLEAR(xop, XOIF_REORDER);
5882d1a0d267SMarcel Moolenaar 
588331337658SMarcel Moolenaar     /* If we don't have an anchor, write the text out */
5884d1a0d267SMarcel Moolenaar     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
5885545ddfbeSMarcel Moolenaar 	if (xo_write(xop) < 0)
5886545ddfbeSMarcel Moolenaar 	    rc = -1;		/* Report failure */
5887545ddfbeSMarcel Moolenaar 	else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0)
5888545ddfbeSMarcel Moolenaar 	    rc = -1;
5889545ddfbeSMarcel Moolenaar     }
589031337658SMarcel Moolenaar 
5891d1a0d267SMarcel Moolenaar     if (new_fmt)
5892d1a0d267SMarcel Moolenaar 	xo_free(new_fmt);
5893d1a0d267SMarcel Moolenaar 
5894d1a0d267SMarcel Moolenaar     /*
5895d1a0d267SMarcel Moolenaar      * We've carried the gettext domainname inside our handle just for
5896d1a0d267SMarcel Moolenaar      * convenience, but we need to ensure it doesn't survive across
5897d1a0d267SMarcel Moolenaar      * xo_emit calls.
5898d1a0d267SMarcel Moolenaar      */
5899d1a0d267SMarcel Moolenaar     if (xop->xo_gt_domain) {
5900d1a0d267SMarcel Moolenaar 	xo_free(xop->xo_gt_domain);
5901d1a0d267SMarcel Moolenaar 	xop->xo_gt_domain = NULL;
5902d1a0d267SMarcel Moolenaar     }
5903d1a0d267SMarcel Moolenaar 
590431337658SMarcel Moolenaar     return (rc < 0) ? rc : (int) xop->xo_columns;
590531337658SMarcel Moolenaar }
590631337658SMarcel Moolenaar 
5907d1a0d267SMarcel Moolenaar /*
5908d1a0d267SMarcel Moolenaar  * Rebuild a format string in a gettext-friendly format.  This function
5909d1a0d267SMarcel Moolenaar  * is exposed to tools can perform this function.  See xo(1).
5910d1a0d267SMarcel Moolenaar  */
5911d1a0d267SMarcel Moolenaar char *
5912d1a0d267SMarcel Moolenaar xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
5913d1a0d267SMarcel Moolenaar 		    xo_simplify_field_func_t field_cb)
5914d1a0d267SMarcel Moolenaar {
5915d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
5916d1a0d267SMarcel Moolenaar 
5917d1a0d267SMarcel Moolenaar     xop->xo_columns = 0;	/* Always reset it */
5918d1a0d267SMarcel Moolenaar     xop->xo_errno = errno;	/* Save for "%m" */
5919d1a0d267SMarcel Moolenaar 
5920d1a0d267SMarcel Moolenaar     unsigned max_fields = xo_count_fields(xop, fmt);
5921d1a0d267SMarcel Moolenaar     xo_field_info_t fields[max_fields];
5922d1a0d267SMarcel Moolenaar 
5923d1a0d267SMarcel Moolenaar     bzero(fields, max_fields * sizeof(fields[0]));
5924d1a0d267SMarcel Moolenaar 
5925d1a0d267SMarcel Moolenaar     if (xo_parse_fields(xop, fields, max_fields, fmt))
5926d1a0d267SMarcel Moolenaar 	return NULL;		/* Warning already displayed */
5927d1a0d267SMarcel Moolenaar 
5928d1a0d267SMarcel Moolenaar     xo_buffer_t xb;
5929d1a0d267SMarcel Moolenaar     xo_buf_init(&xb);
5930d1a0d267SMarcel Moolenaar 
5931d1a0d267SMarcel Moolenaar     if (with_numbers)
5932d1a0d267SMarcel Moolenaar 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
5933d1a0d267SMarcel Moolenaar 
5934d1a0d267SMarcel Moolenaar     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
5935d1a0d267SMarcel Moolenaar 	return NULL;
5936d1a0d267SMarcel Moolenaar 
5937d1a0d267SMarcel Moolenaar     return xb.xb_bufp;
5938d1a0d267SMarcel Moolenaar }
5939d1a0d267SMarcel Moolenaar 
594031337658SMarcel Moolenaar int
594131337658SMarcel Moolenaar xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
594231337658SMarcel Moolenaar {
594331337658SMarcel Moolenaar     int rc;
594431337658SMarcel Moolenaar 
594531337658SMarcel Moolenaar     xop = xo_default(xop);
594631337658SMarcel Moolenaar     va_copy(xop->xo_vap, vap);
594731337658SMarcel Moolenaar     rc = xo_do_emit(xop, fmt);
594831337658SMarcel Moolenaar     va_end(xop->xo_vap);
594931337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
595031337658SMarcel Moolenaar 
595131337658SMarcel Moolenaar     return rc;
595231337658SMarcel Moolenaar }
595331337658SMarcel Moolenaar 
595431337658SMarcel Moolenaar int
595531337658SMarcel Moolenaar xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
595631337658SMarcel Moolenaar {
595731337658SMarcel Moolenaar     int rc;
595831337658SMarcel Moolenaar 
595931337658SMarcel Moolenaar     xop = xo_default(xop);
596031337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
596131337658SMarcel Moolenaar     rc = xo_do_emit(xop, fmt);
596231337658SMarcel Moolenaar     va_end(xop->xo_vap);
596331337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
596431337658SMarcel Moolenaar 
596531337658SMarcel Moolenaar     return rc;
596631337658SMarcel Moolenaar }
596731337658SMarcel Moolenaar 
596831337658SMarcel Moolenaar int
596931337658SMarcel Moolenaar xo_emit (const char *fmt, ...)
597031337658SMarcel Moolenaar {
597131337658SMarcel Moolenaar     xo_handle_t *xop = xo_default(NULL);
597231337658SMarcel Moolenaar     int rc;
597331337658SMarcel Moolenaar 
597431337658SMarcel Moolenaar     va_start(xop->xo_vap, fmt);
597531337658SMarcel Moolenaar     rc = xo_do_emit(xop, fmt);
597631337658SMarcel Moolenaar     va_end(xop->xo_vap);
597731337658SMarcel Moolenaar     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
597831337658SMarcel Moolenaar 
597931337658SMarcel Moolenaar     return rc;
598031337658SMarcel Moolenaar }
598131337658SMarcel Moolenaar 
598231337658SMarcel Moolenaar int
598331337658SMarcel Moolenaar xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
598431337658SMarcel Moolenaar {
598531337658SMarcel Moolenaar     const int extra = 5; 	/* space, equals, quote, quote, and nul */
598631337658SMarcel Moolenaar     xop = xo_default(xop);
598731337658SMarcel Moolenaar 
5988d1a0d267SMarcel Moolenaar     int rc = 0;
598931337658SMarcel Moolenaar     int nlen = strlen(name);
599031337658SMarcel Moolenaar     xo_buffer_t *xbp = &xop->xo_attrs;
5991d1a0d267SMarcel Moolenaar     unsigned name_offset, value_offset;
599231337658SMarcel Moolenaar 
5993d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
5994d1a0d267SMarcel Moolenaar     case XO_STYLE_XML:
599531337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, nlen + extra))
599631337658SMarcel Moolenaar 	    return -1;
599731337658SMarcel Moolenaar 
599831337658SMarcel Moolenaar 	*xbp->xb_curp++ = ' ';
599931337658SMarcel Moolenaar 	memcpy(xbp->xb_curp, name, nlen);
600031337658SMarcel Moolenaar 	xbp->xb_curp += nlen;
600131337658SMarcel Moolenaar 	*xbp->xb_curp++ = '=';
600231337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
600331337658SMarcel Moolenaar 
6004d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
600531337658SMarcel Moolenaar 
6006d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
600731337658SMarcel Moolenaar 	    rc = xo_escape_xml(xbp, rc, 1);
600831337658SMarcel Moolenaar 	    xbp->xb_curp += rc;
600931337658SMarcel Moolenaar 	}
601031337658SMarcel Moolenaar 
601131337658SMarcel Moolenaar 	if (!xo_buf_has_room(xbp, 2))
601231337658SMarcel Moolenaar 	    return -1;
601331337658SMarcel Moolenaar 
601431337658SMarcel Moolenaar 	*xbp->xb_curp++ = '"';
601531337658SMarcel Moolenaar 	*xbp->xb_curp = '\0';
601631337658SMarcel Moolenaar 
6017d1a0d267SMarcel Moolenaar 	rc += nlen + extra;
6018d1a0d267SMarcel Moolenaar 	break;
6019d1a0d267SMarcel Moolenaar 
6020d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6021d1a0d267SMarcel Moolenaar 	name_offset = xo_buf_offset(xbp);
6022d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, name, nlen);
6023d1a0d267SMarcel Moolenaar 	xo_buf_append(xbp, "", 1);
6024d1a0d267SMarcel Moolenaar 
6025d1a0d267SMarcel Moolenaar 	value_offset = xo_buf_offset(xbp);
6026d1a0d267SMarcel Moolenaar 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6027d1a0d267SMarcel Moolenaar 	if (rc >= 0) {
6028d1a0d267SMarcel Moolenaar 	    xbp->xb_curp += rc;
6029d1a0d267SMarcel Moolenaar 	    *xbp->xb_curp = '\0';
6030d1a0d267SMarcel Moolenaar 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6031d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, name_offset),
6032d1a0d267SMarcel Moolenaar 				   xo_buf_data(xbp, value_offset));
6033d1a0d267SMarcel Moolenaar 	}
6034d1a0d267SMarcel Moolenaar     }
6035d1a0d267SMarcel Moolenaar 
6036d1a0d267SMarcel Moolenaar     return rc;
603731337658SMarcel Moolenaar }
603831337658SMarcel Moolenaar 
603931337658SMarcel Moolenaar int
604031337658SMarcel Moolenaar xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
604131337658SMarcel Moolenaar {
604231337658SMarcel Moolenaar     int rc;
604331337658SMarcel Moolenaar     va_list vap;
604431337658SMarcel Moolenaar 
604531337658SMarcel Moolenaar     va_start(vap, fmt);
604631337658SMarcel Moolenaar     rc = xo_attr_hv(xop, name, fmt, vap);
604731337658SMarcel Moolenaar     va_end(vap);
604831337658SMarcel Moolenaar 
604931337658SMarcel Moolenaar     return rc;
605031337658SMarcel Moolenaar }
605131337658SMarcel Moolenaar 
605231337658SMarcel Moolenaar int
605331337658SMarcel Moolenaar xo_attr (const char *name, const char *fmt, ...)
605431337658SMarcel Moolenaar {
605531337658SMarcel Moolenaar     int rc;
605631337658SMarcel Moolenaar     va_list vap;
605731337658SMarcel Moolenaar 
605831337658SMarcel Moolenaar     va_start(vap, fmt);
605931337658SMarcel Moolenaar     rc = xo_attr_hv(NULL, name, fmt, vap);
606031337658SMarcel Moolenaar     va_end(vap);
606131337658SMarcel Moolenaar 
606231337658SMarcel Moolenaar     return rc;
606331337658SMarcel Moolenaar }
606431337658SMarcel Moolenaar 
606531337658SMarcel Moolenaar static void
606631337658SMarcel Moolenaar xo_stack_set_flags (xo_handle_t *xop)
606731337658SMarcel Moolenaar {
6068d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
606931337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
607031337658SMarcel Moolenaar 
607131337658SMarcel Moolenaar 	xsp->xs_flags |= XSF_NOT_FIRST;
6072d1a0d267SMarcel Moolenaar 	XOF_CLEAR(xop, XOF_NOT_FIRST);
607331337658SMarcel Moolenaar     }
607431337658SMarcel Moolenaar }
607531337658SMarcel Moolenaar 
607631337658SMarcel Moolenaar static void
607731337658SMarcel Moolenaar xo_depth_change (xo_handle_t *xop, const char *name,
6078545ddfbeSMarcel Moolenaar 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
607931337658SMarcel Moolenaar {
6080788ca347SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6081545ddfbeSMarcel Moolenaar 	indent = 0;
6082545ddfbeSMarcel Moolenaar 
6083d1a0d267SMarcel Moolenaar     if (XOF_ISSET(xop, XOF_DTRT))
608431337658SMarcel Moolenaar 	flags |= XSF_DTRT;
608531337658SMarcel Moolenaar 
608631337658SMarcel Moolenaar     if (delta >= 0) {			/* Push operation */
608731337658SMarcel Moolenaar 	if (xo_depth_check(xop, xop->xo_depth + delta))
608831337658SMarcel Moolenaar 	    return;
608931337658SMarcel Moolenaar 
609031337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
609131337658SMarcel Moolenaar 	xsp->xs_flags = flags;
6092545ddfbeSMarcel Moolenaar 	xsp->xs_state = state;
609331337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
609431337658SMarcel Moolenaar 
6095545ddfbeSMarcel Moolenaar 	if (name == NULL)
6096545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
609731337658SMarcel Moolenaar 
6098d1a0d267SMarcel Moolenaar 	xsp->xs_name = xo_strndup(name, -1);
609931337658SMarcel Moolenaar 
610031337658SMarcel Moolenaar     } else {			/* Pop operation */
610131337658SMarcel Moolenaar 	if (xop->xo_depth == 0) {
6102d1a0d267SMarcel Moolenaar 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
610331337658SMarcel Moolenaar 		xo_failure(xop, "close with empty stack: '%s'", name);
610431337658SMarcel Moolenaar 	    return;
610531337658SMarcel Moolenaar 	}
610631337658SMarcel Moolenaar 
610731337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6108d1a0d267SMarcel Moolenaar 	if (XOF_ISSET(xop, XOF_WARN)) {
610931337658SMarcel Moolenaar 	    const char *top = xsp->xs_name;
611031337658SMarcel Moolenaar 	    if (top && strcmp(name, top) != 0) {
611131337658SMarcel Moolenaar 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
611231337658SMarcel Moolenaar 			      name, top);
611331337658SMarcel Moolenaar 		return;
611431337658SMarcel Moolenaar 	    }
611531337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
611631337658SMarcel Moolenaar 		xo_failure(xop, "list close on list confict: '%s'",
611731337658SMarcel Moolenaar 			      name);
611831337658SMarcel Moolenaar 		return;
611931337658SMarcel Moolenaar 	    }
612031337658SMarcel Moolenaar 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
612131337658SMarcel Moolenaar 		xo_failure(xop, "list close on instance confict: '%s'",
612231337658SMarcel Moolenaar 			      name);
612331337658SMarcel Moolenaar 		return;
612431337658SMarcel Moolenaar 	    }
612531337658SMarcel Moolenaar 	}
612631337658SMarcel Moolenaar 
612731337658SMarcel Moolenaar 	if (xsp->xs_name) {
612831337658SMarcel Moolenaar 	    xo_free(xsp->xs_name);
612931337658SMarcel Moolenaar 	    xsp->xs_name = NULL;
613031337658SMarcel Moolenaar 	}
613131337658SMarcel Moolenaar 	if (xsp->xs_keys) {
613231337658SMarcel Moolenaar 	    xo_free(xsp->xs_keys);
613331337658SMarcel Moolenaar 	    xsp->xs_keys = NULL;
613431337658SMarcel Moolenaar 	}
613531337658SMarcel Moolenaar     }
613631337658SMarcel Moolenaar 
613731337658SMarcel Moolenaar     xop->xo_depth += delta;	/* Record new depth */
613831337658SMarcel Moolenaar     xop->xo_indent += indent;
613931337658SMarcel Moolenaar }
614031337658SMarcel Moolenaar 
614131337658SMarcel Moolenaar void
614231337658SMarcel Moolenaar xo_set_depth (xo_handle_t *xop, int depth)
614331337658SMarcel Moolenaar {
614431337658SMarcel Moolenaar     xop = xo_default(xop);
614531337658SMarcel Moolenaar 
614631337658SMarcel Moolenaar     if (xo_depth_check(xop, depth))
614731337658SMarcel Moolenaar 	return;
614831337658SMarcel Moolenaar 
614931337658SMarcel Moolenaar     xop->xo_depth += depth;
615031337658SMarcel Moolenaar     xop->xo_indent += depth;
615131337658SMarcel Moolenaar }
615231337658SMarcel Moolenaar 
615331337658SMarcel Moolenaar static xo_xsf_flags_t
615431337658SMarcel Moolenaar xo_stack_flags (unsigned xflags)
615531337658SMarcel Moolenaar {
615631337658SMarcel Moolenaar     if (xflags & XOF_DTRT)
615731337658SMarcel Moolenaar 	return XSF_DTRT;
615831337658SMarcel Moolenaar     return 0;
615931337658SMarcel Moolenaar }
616031337658SMarcel Moolenaar 
6161788ca347SMarcel Moolenaar static void
6162788ca347SMarcel Moolenaar xo_emit_top (xo_handle_t *xop, const char *ppn)
6163788ca347SMarcel Moolenaar {
6164788ca347SMarcel Moolenaar     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6165d1a0d267SMarcel Moolenaar     XOIF_SET(xop, XOIF_TOP_EMITTED);
6166788ca347SMarcel Moolenaar 
6167788ca347SMarcel Moolenaar     if (xop->xo_version) {
6168788ca347SMarcel Moolenaar 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6169788ca347SMarcel Moolenaar 		  xo_indent(xop), "", xop->xo_version, ppn);
6170788ca347SMarcel Moolenaar 	xo_free(xop->xo_version);
6171788ca347SMarcel Moolenaar 	xop->xo_version = NULL;
6172788ca347SMarcel Moolenaar     }
6173788ca347SMarcel Moolenaar }
6174788ca347SMarcel Moolenaar 
617531337658SMarcel Moolenaar static int
6176545ddfbeSMarcel Moolenaar xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
617731337658SMarcel Moolenaar {
617831337658SMarcel Moolenaar     int rc = 0;
6179d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
618031337658SMarcel Moolenaar     const char *pre_nl = "";
618131337658SMarcel Moolenaar 
618231337658SMarcel Moolenaar     if (name == NULL) {
618331337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for container name");
618431337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
618531337658SMarcel Moolenaar     }
618631337658SMarcel Moolenaar 
618731337658SMarcel Moolenaar     flags |= xop->xo_flags;	/* Pick up handle flags */
618831337658SMarcel Moolenaar 
6189788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
619031337658SMarcel Moolenaar     case XO_STYLE_XML:
6191545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
6192545ddfbeSMarcel Moolenaar 
6193545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6194545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6195545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6196545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6197545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6198545ddfbeSMarcel Moolenaar 	}
6199545ddfbeSMarcel Moolenaar 
6200545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
620131337658SMarcel Moolenaar 	break;
620231337658SMarcel Moolenaar 
620331337658SMarcel Moolenaar     case XO_STYLE_JSON:
620431337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
620531337658SMarcel Moolenaar 
6206d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6207d1a0d267SMarcel Moolenaar 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6208788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
620931337658SMarcel Moolenaar 
621031337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6211d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
621231337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
621331337658SMarcel Moolenaar 
621431337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
621531337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
621631337658SMarcel Moolenaar 	break;
6217d1a0d267SMarcel Moolenaar 
6218d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6219d1a0d267SMarcel Moolenaar 	break;
6220d1a0d267SMarcel Moolenaar 
6221d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6222d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL);
6223d1a0d267SMarcel Moolenaar 	break;
622431337658SMarcel Moolenaar     }
622531337658SMarcel Moolenaar 
6226545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
6227545ddfbeSMarcel Moolenaar 		    xo_stack_flags(flags));
6228545ddfbeSMarcel Moolenaar 
622931337658SMarcel Moolenaar     return rc;
623031337658SMarcel Moolenaar }
623131337658SMarcel Moolenaar 
6232545ddfbeSMarcel Moolenaar static int
6233545ddfbeSMarcel Moolenaar xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6234545ddfbeSMarcel Moolenaar {
6235545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
6236545ddfbeSMarcel Moolenaar }
6237545ddfbeSMarcel Moolenaar 
623831337658SMarcel Moolenaar int
623931337658SMarcel Moolenaar xo_open_container_h (xo_handle_t *xop, const char *name)
624031337658SMarcel Moolenaar {
624131337658SMarcel Moolenaar     return xo_open_container_hf(xop, 0, name);
624231337658SMarcel Moolenaar }
624331337658SMarcel Moolenaar 
624431337658SMarcel Moolenaar int
624531337658SMarcel Moolenaar xo_open_container (const char *name)
624631337658SMarcel Moolenaar {
624731337658SMarcel Moolenaar     return xo_open_container_hf(NULL, 0, name);
624831337658SMarcel Moolenaar }
624931337658SMarcel Moolenaar 
625031337658SMarcel Moolenaar int
625131337658SMarcel Moolenaar xo_open_container_hd (xo_handle_t *xop, const char *name)
625231337658SMarcel Moolenaar {
625331337658SMarcel Moolenaar     return xo_open_container_hf(xop, XOF_DTRT, name);
625431337658SMarcel Moolenaar }
625531337658SMarcel Moolenaar 
625631337658SMarcel Moolenaar int
625731337658SMarcel Moolenaar xo_open_container_d (const char *name)
625831337658SMarcel Moolenaar {
625931337658SMarcel Moolenaar     return xo_open_container_hf(NULL, XOF_DTRT, name);
626031337658SMarcel Moolenaar }
626131337658SMarcel Moolenaar 
6262545ddfbeSMarcel Moolenaar static int
6263545ddfbeSMarcel Moolenaar xo_do_close_container (xo_handle_t *xop, const char *name)
626431337658SMarcel Moolenaar {
626531337658SMarcel Moolenaar     xop = xo_default(xop);
626631337658SMarcel Moolenaar 
626731337658SMarcel Moolenaar     int rc = 0;
6268d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
626931337658SMarcel Moolenaar     const char *pre_nl = "";
627031337658SMarcel Moolenaar 
627131337658SMarcel Moolenaar     if (name == NULL) {
627231337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
627331337658SMarcel Moolenaar 
627431337658SMarcel Moolenaar 	name = xsp->xs_name;
627531337658SMarcel Moolenaar 	if (name) {
627631337658SMarcel Moolenaar 	    int len = strlen(name) + 1;
627731337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
627831337658SMarcel Moolenaar 	    char *cp = alloca(len);
627931337658SMarcel Moolenaar 	    memcpy(cp, name, len);
628031337658SMarcel Moolenaar 	    name = cp;
6281545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6282545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
628331337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
628431337658SMarcel Moolenaar 	}
6285545ddfbeSMarcel Moolenaar     }
628631337658SMarcel Moolenaar 
6287788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
628831337658SMarcel Moolenaar     case XO_STYLE_XML:
6289545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
629031337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
629131337658SMarcel Moolenaar 	break;
629231337658SMarcel Moolenaar 
629331337658SMarcel Moolenaar     case XO_STYLE_JSON:
6294d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
629531337658SMarcel Moolenaar 	ppn = (xop->xo_depth <= 1) ? "\n" : "";
629631337658SMarcel Moolenaar 
6297545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
629831337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
629931337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
630031337658SMarcel Moolenaar 	break;
630131337658SMarcel Moolenaar 
630231337658SMarcel Moolenaar     case XO_STYLE_HTML:
630331337658SMarcel Moolenaar     case XO_STYLE_TEXT:
6304545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
630531337658SMarcel Moolenaar 	break;
6306d1a0d267SMarcel Moolenaar 
6307d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6308d1a0d267SMarcel Moolenaar 	break;
6309d1a0d267SMarcel Moolenaar 
6310d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6311d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
6312d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL);
6313d1a0d267SMarcel Moolenaar 	break;
631431337658SMarcel Moolenaar     }
631531337658SMarcel Moolenaar 
631631337658SMarcel Moolenaar     return rc;
631731337658SMarcel Moolenaar }
631831337658SMarcel Moolenaar 
631931337658SMarcel Moolenaar int
6320545ddfbeSMarcel Moolenaar xo_close_container_h (xo_handle_t *xop, const char *name)
6321545ddfbeSMarcel Moolenaar {
6322545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
6323545ddfbeSMarcel Moolenaar }
6324545ddfbeSMarcel Moolenaar 
6325545ddfbeSMarcel Moolenaar int
632631337658SMarcel Moolenaar xo_close_container (const char *name)
632731337658SMarcel Moolenaar {
632831337658SMarcel Moolenaar     return xo_close_container_h(NULL, name);
632931337658SMarcel Moolenaar }
633031337658SMarcel Moolenaar 
633131337658SMarcel Moolenaar int
633231337658SMarcel Moolenaar xo_close_container_hd (xo_handle_t *xop)
633331337658SMarcel Moolenaar {
633431337658SMarcel Moolenaar     return xo_close_container_h(xop, NULL);
633531337658SMarcel Moolenaar }
633631337658SMarcel Moolenaar 
633731337658SMarcel Moolenaar int
633831337658SMarcel Moolenaar xo_close_container_d (void)
633931337658SMarcel Moolenaar {
634031337658SMarcel Moolenaar     return xo_close_container_h(NULL, NULL);
634131337658SMarcel Moolenaar }
634231337658SMarcel Moolenaar 
634331337658SMarcel Moolenaar static int
6344545ddfbeSMarcel Moolenaar xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
634531337658SMarcel Moolenaar {
6346545ddfbeSMarcel Moolenaar     int rc = 0;
6347545ddfbeSMarcel Moolenaar     int indent = 0;
6348545ddfbeSMarcel Moolenaar 
634931337658SMarcel Moolenaar     xop = xo_default(xop);
635031337658SMarcel Moolenaar 
6351d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
635231337658SMarcel Moolenaar     const char *pre_nl = "";
635331337658SMarcel Moolenaar 
6354d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6355d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6356d1a0d267SMarcel Moolenaar 
6357545ddfbeSMarcel Moolenaar 	indent = 1;
6358d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6359d1a0d267SMarcel Moolenaar 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6360788ca347SMarcel Moolenaar 	    xo_emit_top(xop, ppn);
636131337658SMarcel Moolenaar 
636231337658SMarcel Moolenaar 	if (name == NULL) {
636331337658SMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
636431337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
636531337658SMarcel Moolenaar 	}
636631337658SMarcel Moolenaar 
636731337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
636831337658SMarcel Moolenaar 
636931337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6370d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
637131337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
637231337658SMarcel Moolenaar 
637331337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
637431337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
6375d1a0d267SMarcel Moolenaar 	break;
6376d1a0d267SMarcel Moolenaar 
6377d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6378d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL);
6379d1a0d267SMarcel Moolenaar 	break;
6380545ddfbeSMarcel Moolenaar     }
6381545ddfbeSMarcel Moolenaar 
6382545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
6383545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
638431337658SMarcel Moolenaar 
638531337658SMarcel Moolenaar     return rc;
638631337658SMarcel Moolenaar }
638731337658SMarcel Moolenaar 
6388545ddfbeSMarcel Moolenaar static int
6389545ddfbeSMarcel Moolenaar xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6390545ddfbeSMarcel Moolenaar {
6391545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
6392545ddfbeSMarcel Moolenaar }
6393545ddfbeSMarcel Moolenaar 
639431337658SMarcel Moolenaar int
639531337658SMarcel Moolenaar xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
639631337658SMarcel Moolenaar {
639731337658SMarcel Moolenaar     return xo_open_list_hf(xop, 0, name);
639831337658SMarcel Moolenaar }
639931337658SMarcel Moolenaar 
640031337658SMarcel Moolenaar int
640131337658SMarcel Moolenaar xo_open_list (const char *name)
640231337658SMarcel Moolenaar {
640331337658SMarcel Moolenaar     return xo_open_list_hf(NULL, 0, name);
640431337658SMarcel Moolenaar }
640531337658SMarcel Moolenaar 
640631337658SMarcel Moolenaar int
640731337658SMarcel Moolenaar xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
640831337658SMarcel Moolenaar {
640931337658SMarcel Moolenaar     return xo_open_list_hf(xop, XOF_DTRT, name);
641031337658SMarcel Moolenaar }
641131337658SMarcel Moolenaar 
641231337658SMarcel Moolenaar int
641331337658SMarcel Moolenaar xo_open_list_d (const char *name)
641431337658SMarcel Moolenaar {
641531337658SMarcel Moolenaar     return xo_open_list_hf(NULL, XOF_DTRT, name);
641631337658SMarcel Moolenaar }
641731337658SMarcel Moolenaar 
6418545ddfbeSMarcel Moolenaar static int
6419545ddfbeSMarcel Moolenaar xo_do_close_list (xo_handle_t *xop, const char *name)
642031337658SMarcel Moolenaar {
642131337658SMarcel Moolenaar     int rc = 0;
642231337658SMarcel Moolenaar     const char *pre_nl = "";
642331337658SMarcel Moolenaar 
642431337658SMarcel Moolenaar     if (name == NULL) {
642531337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
642631337658SMarcel Moolenaar 
642731337658SMarcel Moolenaar 	name = xsp->xs_name;
642831337658SMarcel Moolenaar 	if (name) {
642931337658SMarcel Moolenaar 	    int len = strlen(name) + 1;
643031337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
643131337658SMarcel Moolenaar 	    char *cp = alloca(len);
643231337658SMarcel Moolenaar 	    memcpy(cp, name, len);
643331337658SMarcel Moolenaar 	    name = cp;
6434545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6435545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
643631337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
643731337658SMarcel Moolenaar 	}
6438545ddfbeSMarcel Moolenaar     }
643931337658SMarcel Moolenaar 
6440d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6441d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
644231337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6443d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
644431337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
644531337658SMarcel Moolenaar 
6446545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
644731337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
644831337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6449d1a0d267SMarcel Moolenaar 	break;
645031337658SMarcel Moolenaar 
6451d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6452d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6453d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL);
6454d1a0d267SMarcel Moolenaar 	break;
6455d1a0d267SMarcel Moolenaar 
6456d1a0d267SMarcel Moolenaar     default:
6457545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6458545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6459d1a0d267SMarcel Moolenaar 	break;
6460545ddfbeSMarcel Moolenaar     }
6461545ddfbeSMarcel Moolenaar 
6462a0f704ffSMarcel Moolenaar     return rc;
646331337658SMarcel Moolenaar }
646431337658SMarcel Moolenaar 
646531337658SMarcel Moolenaar int
6466545ddfbeSMarcel Moolenaar xo_close_list_h (xo_handle_t *xop, const char *name)
6467545ddfbeSMarcel Moolenaar {
6468545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
6469545ddfbeSMarcel Moolenaar }
6470545ddfbeSMarcel Moolenaar 
6471545ddfbeSMarcel Moolenaar int
647231337658SMarcel Moolenaar xo_close_list (const char *name)
647331337658SMarcel Moolenaar {
647431337658SMarcel Moolenaar     return xo_close_list_h(NULL, name);
647531337658SMarcel Moolenaar }
647631337658SMarcel Moolenaar 
647731337658SMarcel Moolenaar int
647831337658SMarcel Moolenaar xo_close_list_hd (xo_handle_t *xop)
647931337658SMarcel Moolenaar {
648031337658SMarcel Moolenaar     return xo_close_list_h(xop, NULL);
648131337658SMarcel Moolenaar }
648231337658SMarcel Moolenaar 
648331337658SMarcel Moolenaar int
648431337658SMarcel Moolenaar xo_close_list_d (void)
648531337658SMarcel Moolenaar {
648631337658SMarcel Moolenaar     return xo_close_list_h(NULL, NULL);
648731337658SMarcel Moolenaar }
648831337658SMarcel Moolenaar 
648931337658SMarcel Moolenaar static int
6490545ddfbeSMarcel Moolenaar xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6491545ddfbeSMarcel Moolenaar {
6492545ddfbeSMarcel Moolenaar     int rc = 0;
6493545ddfbeSMarcel Moolenaar     int indent = 0;
6494545ddfbeSMarcel Moolenaar 
6495545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
6496545ddfbeSMarcel Moolenaar 
6497d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6498545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
6499545ddfbeSMarcel Moolenaar 
6500d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6501d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6502545ddfbeSMarcel Moolenaar 	indent = 1;
6503545ddfbeSMarcel Moolenaar 
6504d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
6505d1a0d267SMarcel Moolenaar 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
6506545ddfbeSMarcel Moolenaar 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6507d1a0d267SMarcel Moolenaar 		XOIF_SET(xop, XOIF_TOP_EMITTED);
6508545ddfbeSMarcel Moolenaar 	    }
6509545ddfbeSMarcel Moolenaar 	}
6510545ddfbeSMarcel Moolenaar 
6511545ddfbeSMarcel Moolenaar 	if (name == NULL) {
6512545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "NULL passed for list name");
6513545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
6514545ddfbeSMarcel Moolenaar 	}
6515545ddfbeSMarcel Moolenaar 
6516545ddfbeSMarcel Moolenaar 	xo_stack_set_flags(xop);
6517545ddfbeSMarcel Moolenaar 
6518545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6519d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6520545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6521545ddfbeSMarcel Moolenaar 
6522545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
6523545ddfbeSMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", name, ppn);
6524d1a0d267SMarcel Moolenaar 	break;
6525d1a0d267SMarcel Moolenaar 
6526d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6527d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL);
6528d1a0d267SMarcel Moolenaar 	break;
6529545ddfbeSMarcel Moolenaar     }
6530545ddfbeSMarcel Moolenaar 
6531545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
6532545ddfbeSMarcel Moolenaar 		    XSF_LIST | xo_stack_flags(flags));
6533545ddfbeSMarcel Moolenaar 
6534545ddfbeSMarcel Moolenaar     return rc;
6535545ddfbeSMarcel Moolenaar }
6536545ddfbeSMarcel Moolenaar 
6537545ddfbeSMarcel Moolenaar static int
6538545ddfbeSMarcel Moolenaar xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
6539545ddfbeSMarcel Moolenaar {
6540545ddfbeSMarcel Moolenaar     int rc = 0;
6541545ddfbeSMarcel Moolenaar     const char *pre_nl = "";
6542545ddfbeSMarcel Moolenaar 
6543545ddfbeSMarcel Moolenaar     if (name == NULL) {
6544545ddfbeSMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6545545ddfbeSMarcel Moolenaar 
6546545ddfbeSMarcel Moolenaar 	name = xsp->xs_name;
6547545ddfbeSMarcel Moolenaar 	if (name) {
6548545ddfbeSMarcel Moolenaar 	    int len = strlen(name) + 1;
6549545ddfbeSMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
6550545ddfbeSMarcel Moolenaar 	    char *cp = alloca(len);
6551545ddfbeSMarcel Moolenaar 	    memcpy(cp, name, len);
6552545ddfbeSMarcel Moolenaar 	    name = cp;
6553545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6554545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
6555545ddfbeSMarcel Moolenaar 	    name = XO_FAILURE_NAME;
6556545ddfbeSMarcel Moolenaar 	}
6557545ddfbeSMarcel Moolenaar     }
6558545ddfbeSMarcel Moolenaar 
6559d1a0d267SMarcel Moolenaar     switch (xo_style(xop)) {
6560d1a0d267SMarcel Moolenaar     case XO_STYLE_JSON:
6561545ddfbeSMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6562d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6563545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6564545ddfbeSMarcel Moolenaar 
6565545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
6566545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
6567545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6568d1a0d267SMarcel Moolenaar 	break;
6569545ddfbeSMarcel Moolenaar 
6570d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6571d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL);
6572d1a0d267SMarcel Moolenaar 	/*fallthru*/
6573d1a0d267SMarcel Moolenaar 
6574d1a0d267SMarcel Moolenaar     default:
6575545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
6576545ddfbeSMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6577d1a0d267SMarcel Moolenaar 	break;
6578545ddfbeSMarcel Moolenaar     }
6579545ddfbeSMarcel Moolenaar 
6580545ddfbeSMarcel Moolenaar     return rc;
6581545ddfbeSMarcel Moolenaar }
6582545ddfbeSMarcel Moolenaar 
6583545ddfbeSMarcel Moolenaar static int
6584545ddfbeSMarcel Moolenaar xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
658531337658SMarcel Moolenaar {
658631337658SMarcel Moolenaar     xop = xo_default(xop);
658731337658SMarcel Moolenaar 
658831337658SMarcel Moolenaar     int rc = 0;
6589d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
659031337658SMarcel Moolenaar     const char *pre_nl = "";
659131337658SMarcel Moolenaar 
659231337658SMarcel Moolenaar     flags |= xop->xo_flags;
659331337658SMarcel Moolenaar 
659431337658SMarcel Moolenaar     if (name == NULL) {
659531337658SMarcel Moolenaar 	xo_failure(xop, "NULL passed for instance name");
659631337658SMarcel Moolenaar 	name = XO_FAILURE_NAME;
659731337658SMarcel Moolenaar     }
659831337658SMarcel Moolenaar 
6599788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
660031337658SMarcel Moolenaar     case XO_STYLE_XML:
6601545ddfbeSMarcel Moolenaar 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
6602545ddfbeSMarcel Moolenaar 
6603545ddfbeSMarcel Moolenaar 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6604545ddfbeSMarcel Moolenaar 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6605545ddfbeSMarcel Moolenaar 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6606545ddfbeSMarcel Moolenaar 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6607545ddfbeSMarcel Moolenaar 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6608545ddfbeSMarcel Moolenaar 	}
6609545ddfbeSMarcel Moolenaar 
6610545ddfbeSMarcel Moolenaar 	rc += xo_printf(xop, ">%s", ppn);
661131337658SMarcel Moolenaar 	break;
661231337658SMarcel Moolenaar 
661331337658SMarcel Moolenaar     case XO_STYLE_JSON:
661431337658SMarcel Moolenaar 	xo_stack_set_flags(xop);
661531337658SMarcel Moolenaar 
661631337658SMarcel Moolenaar 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6617d1a0d267SMarcel Moolenaar 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
661831337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
661931337658SMarcel Moolenaar 
662031337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s{%s",
662131337658SMarcel Moolenaar 		       pre_nl, xo_indent(xop), "", ppn);
662231337658SMarcel Moolenaar 	break;
6623d1a0d267SMarcel Moolenaar 
6624d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6625d1a0d267SMarcel Moolenaar 	break;
6626d1a0d267SMarcel Moolenaar 
6627d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6628d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL);
6629d1a0d267SMarcel Moolenaar 	break;
663031337658SMarcel Moolenaar     }
663131337658SMarcel Moolenaar 
6632545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
6633545ddfbeSMarcel Moolenaar 
663431337658SMarcel Moolenaar     return rc;
663531337658SMarcel Moolenaar }
663631337658SMarcel Moolenaar 
6637545ddfbeSMarcel Moolenaar static int
6638545ddfbeSMarcel Moolenaar xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6639545ddfbeSMarcel Moolenaar {
6640545ddfbeSMarcel Moolenaar     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
6641545ddfbeSMarcel Moolenaar }
6642545ddfbeSMarcel Moolenaar 
664331337658SMarcel Moolenaar int
664431337658SMarcel Moolenaar xo_open_instance_h (xo_handle_t *xop, const char *name)
664531337658SMarcel Moolenaar {
664631337658SMarcel Moolenaar     return xo_open_instance_hf(xop, 0, name);
664731337658SMarcel Moolenaar }
664831337658SMarcel Moolenaar 
664931337658SMarcel Moolenaar int
665031337658SMarcel Moolenaar xo_open_instance (const char *name)
665131337658SMarcel Moolenaar {
665231337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, 0, name);
665331337658SMarcel Moolenaar }
665431337658SMarcel Moolenaar 
665531337658SMarcel Moolenaar int
665631337658SMarcel Moolenaar xo_open_instance_hd (xo_handle_t *xop, const char *name)
665731337658SMarcel Moolenaar {
665831337658SMarcel Moolenaar     return xo_open_instance_hf(xop, XOF_DTRT, name);
665931337658SMarcel Moolenaar }
666031337658SMarcel Moolenaar 
666131337658SMarcel Moolenaar int
666231337658SMarcel Moolenaar xo_open_instance_d (const char *name)
666331337658SMarcel Moolenaar {
666431337658SMarcel Moolenaar     return xo_open_instance_hf(NULL, XOF_DTRT, name);
666531337658SMarcel Moolenaar }
666631337658SMarcel Moolenaar 
6667545ddfbeSMarcel Moolenaar static int
6668545ddfbeSMarcel Moolenaar xo_do_close_instance (xo_handle_t *xop, const char *name)
666931337658SMarcel Moolenaar {
667031337658SMarcel Moolenaar     xop = xo_default(xop);
667131337658SMarcel Moolenaar 
667231337658SMarcel Moolenaar     int rc = 0;
6673d1a0d267SMarcel Moolenaar     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
667431337658SMarcel Moolenaar     const char *pre_nl = "";
667531337658SMarcel Moolenaar 
667631337658SMarcel Moolenaar     if (name == NULL) {
667731337658SMarcel Moolenaar 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
667831337658SMarcel Moolenaar 
667931337658SMarcel Moolenaar 	name = xsp->xs_name;
668031337658SMarcel Moolenaar 	if (name) {
668131337658SMarcel Moolenaar 	    int len = strlen(name) + 1;
668231337658SMarcel Moolenaar 	    /* We need to make a local copy; xo_depth_change will free it */
668331337658SMarcel Moolenaar 	    char *cp = alloca(len);
668431337658SMarcel Moolenaar 	    memcpy(cp, name, len);
668531337658SMarcel Moolenaar 	    name = cp;
6686545ddfbeSMarcel Moolenaar 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6687545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "missing name without 'dtrt' mode");
668831337658SMarcel Moolenaar 	    name = XO_FAILURE_NAME;
668931337658SMarcel Moolenaar 	}
6690545ddfbeSMarcel Moolenaar     }
669131337658SMarcel Moolenaar 
6692788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
669331337658SMarcel Moolenaar     case XO_STYLE_XML:
6694545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
669531337658SMarcel Moolenaar 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
669631337658SMarcel Moolenaar 	break;
669731337658SMarcel Moolenaar 
669831337658SMarcel Moolenaar     case XO_STYLE_JSON:
6699d1a0d267SMarcel Moolenaar 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
670031337658SMarcel Moolenaar 
6701545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
670231337658SMarcel Moolenaar 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
670331337658SMarcel Moolenaar 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
670431337658SMarcel Moolenaar 	break;
670531337658SMarcel Moolenaar 
670631337658SMarcel Moolenaar     case XO_STYLE_HTML:
670731337658SMarcel Moolenaar     case XO_STYLE_TEXT:
6708545ddfbeSMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
670931337658SMarcel Moolenaar 	break;
6710d1a0d267SMarcel Moolenaar 
6711d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
6712d1a0d267SMarcel Moolenaar 	break;
6713d1a0d267SMarcel Moolenaar 
6714d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
6715d1a0d267SMarcel Moolenaar 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
6716d1a0d267SMarcel Moolenaar 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL);
6717d1a0d267SMarcel Moolenaar 	break;
671831337658SMarcel Moolenaar     }
671931337658SMarcel Moolenaar 
672031337658SMarcel Moolenaar     return rc;
672131337658SMarcel Moolenaar }
672231337658SMarcel Moolenaar 
672331337658SMarcel Moolenaar int
6724545ddfbeSMarcel Moolenaar xo_close_instance_h (xo_handle_t *xop, const char *name)
6725545ddfbeSMarcel Moolenaar {
6726545ddfbeSMarcel Moolenaar     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
6727545ddfbeSMarcel Moolenaar }
6728545ddfbeSMarcel Moolenaar 
6729545ddfbeSMarcel Moolenaar int
673031337658SMarcel Moolenaar xo_close_instance (const char *name)
673131337658SMarcel Moolenaar {
673231337658SMarcel Moolenaar     return xo_close_instance_h(NULL, name);
673331337658SMarcel Moolenaar }
673431337658SMarcel Moolenaar 
673531337658SMarcel Moolenaar int
673631337658SMarcel Moolenaar xo_close_instance_hd (xo_handle_t *xop)
673731337658SMarcel Moolenaar {
673831337658SMarcel Moolenaar     return xo_close_instance_h(xop, NULL);
673931337658SMarcel Moolenaar }
674031337658SMarcel Moolenaar 
674131337658SMarcel Moolenaar int
674231337658SMarcel Moolenaar xo_close_instance_d (void)
674331337658SMarcel Moolenaar {
674431337658SMarcel Moolenaar     return xo_close_instance_h(NULL, NULL);
674531337658SMarcel Moolenaar }
674631337658SMarcel Moolenaar 
6747545ddfbeSMarcel Moolenaar static int
6748545ddfbeSMarcel Moolenaar xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
6749545ddfbeSMarcel Moolenaar {
6750545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
6751545ddfbeSMarcel Moolenaar     int rc = 0;
6752545ddfbeSMarcel Moolenaar     xo_xsf_flags_t flags;
6753545ddfbeSMarcel Moolenaar 
6754545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
6755545ddfbeSMarcel Moolenaar 	switch (xsp->xs_state) {
6756545ddfbeSMarcel Moolenaar 	case XSS_INIT:
6757545ddfbeSMarcel Moolenaar 	    /* Nothing */
6758545ddfbeSMarcel Moolenaar 	    rc = 0;
6759545ddfbeSMarcel Moolenaar 	    break;
6760545ddfbeSMarcel Moolenaar 
6761545ddfbeSMarcel Moolenaar 	case XSS_OPEN_CONTAINER:
6762545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_container(xop, NULL);
6763545ddfbeSMarcel Moolenaar 	    break;
6764545ddfbeSMarcel Moolenaar 
6765545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LIST:
6766545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_list(xop, NULL);
6767545ddfbeSMarcel Moolenaar 	    break;
6768545ddfbeSMarcel Moolenaar 
6769545ddfbeSMarcel Moolenaar 	case XSS_OPEN_INSTANCE:
6770545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
6771545ddfbeSMarcel Moolenaar 	    break;
6772545ddfbeSMarcel Moolenaar 
6773545ddfbeSMarcel Moolenaar 	case XSS_OPEN_LEAF_LIST:
6774545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_leaf_list(xop, NULL);
6775545ddfbeSMarcel Moolenaar 	    break;
6776545ddfbeSMarcel Moolenaar 
6777545ddfbeSMarcel Moolenaar 	case XSS_MARKER:
6778545ddfbeSMarcel Moolenaar 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
6779545ddfbeSMarcel Moolenaar 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
6780545ddfbeSMarcel Moolenaar 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
6781545ddfbeSMarcel Moolenaar 	    rc = 0;
6782545ddfbeSMarcel Moolenaar 	    break;
6783545ddfbeSMarcel Moolenaar 	}
6784545ddfbeSMarcel Moolenaar 
6785545ddfbeSMarcel Moolenaar 	if (rc < 0)
6786545ddfbeSMarcel Moolenaar 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
6787545ddfbeSMarcel Moolenaar     }
6788545ddfbeSMarcel Moolenaar 
6789545ddfbeSMarcel Moolenaar     return 0;
6790545ddfbeSMarcel Moolenaar }
6791545ddfbeSMarcel Moolenaar 
6792545ddfbeSMarcel Moolenaar /*
6793545ddfbeSMarcel Moolenaar  * This function is responsible for clearing out whatever is needed
6794545ddfbeSMarcel Moolenaar  * to get to the desired state, if possible.
6795545ddfbeSMarcel Moolenaar  */
6796545ddfbeSMarcel Moolenaar static int
6797545ddfbeSMarcel Moolenaar xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
6798545ddfbeSMarcel Moolenaar {
6799545ddfbeSMarcel Moolenaar     xo_stack_t *xsp, *limit = NULL;
6800545ddfbeSMarcel Moolenaar     int rc;
6801545ddfbeSMarcel Moolenaar     xo_state_t need_state = new_state;
6802545ddfbeSMarcel Moolenaar 
6803545ddfbeSMarcel Moolenaar     if (new_state == XSS_CLOSE_CONTAINER)
6804545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_CONTAINER;
6805545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LIST)
6806545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LIST;
6807545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_INSTANCE)
6808545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_INSTANCE;
6809545ddfbeSMarcel Moolenaar     else if (new_state == XSS_CLOSE_LEAF_LIST)
6810545ddfbeSMarcel Moolenaar 	need_state = XSS_OPEN_LEAF_LIST;
6811545ddfbeSMarcel Moolenaar     else if (new_state == XSS_MARKER)
6812545ddfbeSMarcel Moolenaar 	need_state = XSS_MARKER;
6813545ddfbeSMarcel Moolenaar     else
6814545ddfbeSMarcel Moolenaar 	return 0; /* Unknown or useless new states are ignored */
6815545ddfbeSMarcel Moolenaar 
6816545ddfbeSMarcel Moolenaar     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
6817545ddfbeSMarcel Moolenaar 	/*
6818545ddfbeSMarcel Moolenaar 	 * Marker's normally stop us from going any further, unless
6819545ddfbeSMarcel Moolenaar 	 * we are popping a marker (new_state == XSS_MARKER).
6820545ddfbeSMarcel Moolenaar 	 */
6821545ddfbeSMarcel Moolenaar 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
6822545ddfbeSMarcel Moolenaar 	    if (name) {
6823545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
6824545ddfbeSMarcel Moolenaar 			   "not found '%s'",
6825545ddfbeSMarcel Moolenaar 			   xo_state_name(new_state),
6826545ddfbeSMarcel Moolenaar 			   xsp->xs_name, name);
6827545ddfbeSMarcel Moolenaar 		return 0;
6828545ddfbeSMarcel Moolenaar 
6829545ddfbeSMarcel Moolenaar 	    } else {
6830545ddfbeSMarcel Moolenaar 		limit = xsp;
6831545ddfbeSMarcel Moolenaar 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
6832545ddfbeSMarcel Moolenaar 	    }
6833545ddfbeSMarcel Moolenaar 	    break;
6834545ddfbeSMarcel Moolenaar 	}
6835545ddfbeSMarcel Moolenaar 
6836545ddfbeSMarcel Moolenaar 	if (xsp->xs_state != need_state)
6837545ddfbeSMarcel Moolenaar 	    continue;
6838545ddfbeSMarcel Moolenaar 
6839545ddfbeSMarcel Moolenaar 	if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
6840545ddfbeSMarcel Moolenaar 	    continue;
6841545ddfbeSMarcel Moolenaar 
6842545ddfbeSMarcel Moolenaar 	limit = xsp;
6843545ddfbeSMarcel Moolenaar 	break;
6844545ddfbeSMarcel Moolenaar     }
6845545ddfbeSMarcel Moolenaar 
6846545ddfbeSMarcel Moolenaar     if (limit == NULL) {
6847545ddfbeSMarcel Moolenaar 	xo_failure(xop, "xo_%s can't find match for '%s'",
6848545ddfbeSMarcel Moolenaar 		   xo_state_name(new_state), name);
6849545ddfbeSMarcel Moolenaar 	return 0;
6850545ddfbeSMarcel Moolenaar     }
6851545ddfbeSMarcel Moolenaar 
6852545ddfbeSMarcel Moolenaar     rc = xo_do_close_all(xop, limit);
6853545ddfbeSMarcel Moolenaar 
6854545ddfbeSMarcel Moolenaar     return rc;
6855545ddfbeSMarcel Moolenaar }
6856545ddfbeSMarcel Moolenaar 
6857545ddfbeSMarcel Moolenaar /*
6858545ddfbeSMarcel Moolenaar  * We are in a given state and need to transition to the new state.
6859545ddfbeSMarcel Moolenaar  */
6860545ddfbeSMarcel Moolenaar static int
6861545ddfbeSMarcel Moolenaar xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
6862545ddfbeSMarcel Moolenaar 	       xo_state_t new_state)
6863545ddfbeSMarcel Moolenaar {
6864545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
6865545ddfbeSMarcel Moolenaar     int rc;
6866545ddfbeSMarcel Moolenaar     int old_state, on_marker;
6867545ddfbeSMarcel Moolenaar 
6868545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
6869545ddfbeSMarcel Moolenaar 
6870545ddfbeSMarcel Moolenaar     rc = 0;
6871545ddfbeSMarcel Moolenaar     xsp = &xop->xo_stack[xop->xo_depth];
6872545ddfbeSMarcel Moolenaar     old_state = xsp->xs_state;
6873545ddfbeSMarcel Moolenaar     on_marker = (old_state == XSS_MARKER);
6874545ddfbeSMarcel Moolenaar 
6875545ddfbeSMarcel Moolenaar     /* If there's a marker on top of the stack, we need to find a real state */
6876545ddfbeSMarcel Moolenaar     while (old_state == XSS_MARKER) {
6877545ddfbeSMarcel Moolenaar 	if (xsp == xop->xo_stack)
6878545ddfbeSMarcel Moolenaar 	    break;
6879545ddfbeSMarcel Moolenaar 	xsp -= 1;
6880545ddfbeSMarcel Moolenaar 	old_state = xsp->xs_state;
6881545ddfbeSMarcel Moolenaar     }
6882545ddfbeSMarcel Moolenaar 
6883545ddfbeSMarcel Moolenaar     /*
6884545ddfbeSMarcel Moolenaar      * At this point, the list of possible states are:
6885545ddfbeSMarcel Moolenaar      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
6886545ddfbeSMarcel Moolenaar      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
6887545ddfbeSMarcel Moolenaar      */
6888545ddfbeSMarcel Moolenaar     switch (XSS_TRANSITION(old_state, new_state)) {
6889545ddfbeSMarcel Moolenaar 
6890545ddfbeSMarcel Moolenaar     open_container:
6891545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
6892545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
6893545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
6894545ddfbeSMarcel Moolenaar        rc = xo_do_open_container(xop, flags, name);
6895545ddfbeSMarcel Moolenaar        break;
6896545ddfbeSMarcel Moolenaar 
6897545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
6898545ddfbeSMarcel Moolenaar 	if (on_marker)
6899545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6900545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
6901545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6902545ddfbeSMarcel Moolenaar 	    goto open_container;
6903545ddfbeSMarcel Moolenaar 	break;
6904545ddfbeSMarcel Moolenaar 
6905545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
6906545ddfbeSMarcel Moolenaar 	if (on_marker)
6907545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6908545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
6909545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6910545ddfbeSMarcel Moolenaar 	    goto open_container;
6911545ddfbeSMarcel Moolenaar 	break;
6912545ddfbeSMarcel Moolenaar 
6913545ddfbeSMarcel Moolenaar     /*close_container:*/
6914545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
6915545ddfbeSMarcel Moolenaar 	if (on_marker)
6916545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6917545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
6918545ddfbeSMarcel Moolenaar 	break;
6919545ddfbeSMarcel Moolenaar 
6920545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
6921545ddfbeSMarcel Moolenaar 	/* This is an exception for "xo --close" */
6922545ddfbeSMarcel Moolenaar 	rc = xo_do_close_container(xop, name);
6923545ddfbeSMarcel Moolenaar 	break;
6924545ddfbeSMarcel Moolenaar 
6925545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
6926545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
6927545ddfbeSMarcel Moolenaar 	if (on_marker)
6928545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6929545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
6930545ddfbeSMarcel Moolenaar 	break;
6931545ddfbeSMarcel Moolenaar 
6932545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
6933545ddfbeSMarcel Moolenaar 	if (on_marker)
6934545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6935545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
6936545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6937545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
6938545ddfbeSMarcel Moolenaar 	break;
6939545ddfbeSMarcel Moolenaar 
6940545ddfbeSMarcel Moolenaar     open_list:
6941545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
6942545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
6943545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
6944545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
6945545ddfbeSMarcel Moolenaar 	break;
6946545ddfbeSMarcel Moolenaar 
6947545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
6948545ddfbeSMarcel Moolenaar 	if (on_marker)
6949545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6950545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
6951545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6952545ddfbeSMarcel Moolenaar 	    goto open_list;
6953545ddfbeSMarcel Moolenaar 	break;
6954545ddfbeSMarcel Moolenaar 
6955545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
6956545ddfbeSMarcel Moolenaar 	if (on_marker)
6957545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6958545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
6959545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6960545ddfbeSMarcel Moolenaar 	    goto open_list;
6961545ddfbeSMarcel Moolenaar 	break;
6962545ddfbeSMarcel Moolenaar 
6963545ddfbeSMarcel Moolenaar     /*close_list:*/
6964545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
6965545ddfbeSMarcel Moolenaar 	if (on_marker)
6966545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
6967545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
6968545ddfbeSMarcel Moolenaar 	break;
6969545ddfbeSMarcel Moolenaar 
6970545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
6971545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
6972545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
6973545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
6974545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
6975545ddfbeSMarcel Moolenaar 	break;
6976545ddfbeSMarcel Moolenaar 
6977545ddfbeSMarcel Moolenaar     open_instance:
6978545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
6979545ddfbeSMarcel Moolenaar 	rc = xo_do_open_instance(xop, flags, name);
6980545ddfbeSMarcel Moolenaar 	break;
6981545ddfbeSMarcel Moolenaar 
6982545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
6983788ca347SMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
6984545ddfbeSMarcel Moolenaar 	rc = xo_do_open_list(xop, flags, name);
6985545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6986545ddfbeSMarcel Moolenaar 	    goto open_instance;
6987545ddfbeSMarcel Moolenaar 	break;
6988545ddfbeSMarcel Moolenaar 
6989545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
6990545ddfbeSMarcel Moolenaar 	if (on_marker) {
6991545ddfbeSMarcel Moolenaar 	    rc = xo_do_open_list(xop, flags, name);
6992545ddfbeSMarcel Moolenaar 	} else {
6993545ddfbeSMarcel Moolenaar 	    rc = xo_do_close_instance(xop, NULL);
6994545ddfbeSMarcel Moolenaar 	}
6995545ddfbeSMarcel Moolenaar 	if (rc >= 0)
6996545ddfbeSMarcel Moolenaar 	    goto open_instance;
6997545ddfbeSMarcel Moolenaar 	break;
6998545ddfbeSMarcel Moolenaar 
6999545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7000545ddfbeSMarcel Moolenaar 	if (on_marker)
7001545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7002545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7003545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7004545ddfbeSMarcel Moolenaar 	    goto open_instance;
7005545ddfbeSMarcel Moolenaar 	break;
7006545ddfbeSMarcel Moolenaar 
7007545ddfbeSMarcel Moolenaar     /*close_instance:*/
7008545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7009545ddfbeSMarcel Moolenaar 	if (on_marker)
7010545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7011545ddfbeSMarcel Moolenaar 	rc = xo_do_close_instance(xop, name);
7012545ddfbeSMarcel Moolenaar 	break;
7013545ddfbeSMarcel Moolenaar 
7014545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7015545ddfbeSMarcel Moolenaar 	/* This one makes no sense; ignore it */
7016788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_instance ignored when called from "
7017788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7018545ddfbeSMarcel Moolenaar 	break;
7019545ddfbeSMarcel Moolenaar 
7020545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7021545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7022545ddfbeSMarcel Moolenaar 	if (on_marker)
7023545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7024545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7025545ddfbeSMarcel Moolenaar 	break;
7026545ddfbeSMarcel Moolenaar 
7027545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7028545ddfbeSMarcel Moolenaar 	if (on_marker)
7029545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7030545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7031545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7032545ddfbeSMarcel Moolenaar 	    rc = xo_do_close(xop, name, new_state);
7033545ddfbeSMarcel Moolenaar 	break;
7034545ddfbeSMarcel Moolenaar 
7035545ddfbeSMarcel Moolenaar     open_leaf_list:
7036545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7037545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7038545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7039545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7040545ddfbeSMarcel Moolenaar 	break;
7041545ddfbeSMarcel Moolenaar 
7042545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7043545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7044545ddfbeSMarcel Moolenaar 	if (on_marker)
7045545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7046545ddfbeSMarcel Moolenaar 	rc = xo_do_close_list(xop, NULL);
7047545ddfbeSMarcel Moolenaar 	if (rc >= 0)
7048545ddfbeSMarcel Moolenaar 	    goto open_leaf_list;
7049545ddfbeSMarcel Moolenaar 	break;
7050545ddfbeSMarcel Moolenaar 
7051545ddfbeSMarcel Moolenaar     /*close_leaf_list:*/
7052545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7053545ddfbeSMarcel Moolenaar 	if (on_marker)
7054545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7055545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, name);
7056545ddfbeSMarcel Moolenaar 	break;
7057545ddfbeSMarcel Moolenaar 
7058545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7059545ddfbeSMarcel Moolenaar 	/* Makes no sense; ignore */
7060788ca347SMarcel Moolenaar 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7061788ca347SMarcel Moolenaar 		   "initial state ('%s')", name ?: "(unknown)");
7062545ddfbeSMarcel Moolenaar 	break;
7063545ddfbeSMarcel Moolenaar 
7064545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7065545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7066545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7067545ddfbeSMarcel Moolenaar 	if (on_marker)
7068545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7069545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, name, new_state);
7070545ddfbeSMarcel Moolenaar 	break;
7071545ddfbeSMarcel Moolenaar 
7072545ddfbeSMarcel Moolenaar     /*emit:*/
7073545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7074545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7075545ddfbeSMarcel Moolenaar 	break;
7076545ddfbeSMarcel Moolenaar 
7077545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7078545ddfbeSMarcel Moolenaar 	if (on_marker)
7079545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7080545ddfbeSMarcel Moolenaar 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7081545ddfbeSMarcel Moolenaar 	break;
7082545ddfbeSMarcel Moolenaar 
7083545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7084545ddfbeSMarcel Moolenaar 	break;
7085545ddfbeSMarcel Moolenaar 
7086545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7087545ddfbeSMarcel Moolenaar 	if (on_marker)
7088545ddfbeSMarcel Moolenaar 	    goto marker_prevents_close;
7089545ddfbeSMarcel Moolenaar 	rc = xo_do_close_leaf_list(xop, NULL);
7090545ddfbeSMarcel Moolenaar 	break;
7091545ddfbeSMarcel Moolenaar 
7092545ddfbeSMarcel Moolenaar     /*emit_leaf_list:*/
7093545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7094545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7095545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7096545ddfbeSMarcel Moolenaar 	rc = xo_do_open_leaf_list(xop, flags, name);
7097545ddfbeSMarcel Moolenaar 	break;
7098545ddfbeSMarcel Moolenaar 
7099545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7100545ddfbeSMarcel Moolenaar 	break;
7101545ddfbeSMarcel Moolenaar 
7102545ddfbeSMarcel Moolenaar     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7103545ddfbeSMarcel Moolenaar 	/*
7104545ddfbeSMarcel Moolenaar 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7105545ddfbeSMarcel Moolenaar 	 * API, where both lists and leaf-lists were opened as lists.  So
7106545ddfbeSMarcel Moolenaar 	 * if we find an open list that hasn't had anything written to it,
7107545ddfbeSMarcel Moolenaar 	 * we'll accept it.
7108545ddfbeSMarcel Moolenaar 	 */
7109545ddfbeSMarcel Moolenaar 	break;
7110545ddfbeSMarcel Moolenaar 
7111545ddfbeSMarcel Moolenaar     default:
7112545ddfbeSMarcel Moolenaar 	xo_failure(xop, "unknown transition: (%u -> %u)",
7113545ddfbeSMarcel Moolenaar 		   xsp->xs_state, new_state);
7114545ddfbeSMarcel Moolenaar     }
7115545ddfbeSMarcel Moolenaar 
7116545ddfbeSMarcel Moolenaar     return rc;
7117545ddfbeSMarcel Moolenaar 
7118545ddfbeSMarcel Moolenaar  marker_prevents_close:
7119545ddfbeSMarcel Moolenaar     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7120545ddfbeSMarcel Moolenaar 	       xop->xo_stack[xop->xo_depth].xs_name,
7121545ddfbeSMarcel Moolenaar 	       xo_state_name(old_state), xo_state_name(new_state));
7122545ddfbeSMarcel Moolenaar     return -1;
7123545ddfbeSMarcel Moolenaar }
7124545ddfbeSMarcel Moolenaar 
7125545ddfbeSMarcel Moolenaar int
7126545ddfbeSMarcel Moolenaar xo_open_marker_h (xo_handle_t *xop, const char *name)
7127545ddfbeSMarcel Moolenaar {
7128545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7129545ddfbeSMarcel Moolenaar 
7130545ddfbeSMarcel Moolenaar     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7131545ddfbeSMarcel Moolenaar 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7132545ddfbeSMarcel Moolenaar 
7133545ddfbeSMarcel Moolenaar     return 0;
7134545ddfbeSMarcel Moolenaar }
7135545ddfbeSMarcel Moolenaar 
7136545ddfbeSMarcel Moolenaar int
7137545ddfbeSMarcel Moolenaar xo_open_marker (const char *name)
7138545ddfbeSMarcel Moolenaar {
7139545ddfbeSMarcel Moolenaar     return xo_open_marker_h(NULL, name);
7140545ddfbeSMarcel Moolenaar }
7141545ddfbeSMarcel Moolenaar 
7142545ddfbeSMarcel Moolenaar int
7143545ddfbeSMarcel Moolenaar xo_close_marker_h (xo_handle_t *xop, const char *name)
7144545ddfbeSMarcel Moolenaar {
7145545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7146545ddfbeSMarcel Moolenaar 
7147545ddfbeSMarcel Moolenaar     return xo_do_close(xop, name, XSS_MARKER);
7148545ddfbeSMarcel Moolenaar }
7149545ddfbeSMarcel Moolenaar 
7150545ddfbeSMarcel Moolenaar int
7151545ddfbeSMarcel Moolenaar xo_close_marker (const char *name)
7152545ddfbeSMarcel Moolenaar {
7153545ddfbeSMarcel Moolenaar     return xo_close_marker_h(NULL, name);
7154545ddfbeSMarcel Moolenaar }
7155545ddfbeSMarcel Moolenaar 
7156d1a0d267SMarcel Moolenaar /*
7157d1a0d267SMarcel Moolenaar  * Record custom output functions into the xo handle, allowing
7158d1a0d267SMarcel Moolenaar  * integration with a variety of output frameworks.
7159d1a0d267SMarcel Moolenaar  */
716031337658SMarcel Moolenaar void
716131337658SMarcel Moolenaar xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7162545ddfbeSMarcel Moolenaar 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
716331337658SMarcel Moolenaar {
716431337658SMarcel Moolenaar     xop = xo_default(xop);
716531337658SMarcel Moolenaar 
716631337658SMarcel Moolenaar     xop->xo_opaque = opaque;
716731337658SMarcel Moolenaar     xop->xo_write = write_func;
716831337658SMarcel Moolenaar     xop->xo_close = close_func;
7169545ddfbeSMarcel Moolenaar     xop->xo_flush = flush_func;
717031337658SMarcel Moolenaar }
717131337658SMarcel Moolenaar 
717231337658SMarcel Moolenaar void
717331337658SMarcel Moolenaar xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
717431337658SMarcel Moolenaar {
717531337658SMarcel Moolenaar     xo_realloc = realloc_func;
717631337658SMarcel Moolenaar     xo_free = free_func;
717731337658SMarcel Moolenaar }
717831337658SMarcel Moolenaar 
7179545ddfbeSMarcel Moolenaar int
718031337658SMarcel Moolenaar xo_flush_h (xo_handle_t *xop)
718131337658SMarcel Moolenaar {
718231337658SMarcel Moolenaar     static char div_close[] = "</div>";
7183545ddfbeSMarcel Moolenaar     int rc;
718431337658SMarcel Moolenaar 
718531337658SMarcel Moolenaar     xop = xo_default(xop);
718631337658SMarcel Moolenaar 
7187788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
718831337658SMarcel Moolenaar     case XO_STYLE_HTML:
7189d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
7190d1a0d267SMarcel Moolenaar 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
719131337658SMarcel Moolenaar 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
719231337658SMarcel Moolenaar 
7193d1a0d267SMarcel Moolenaar 	    if (XOF_ISSET(xop, XOF_PRETTY))
719431337658SMarcel Moolenaar 		xo_data_append(xop, "\n", 1);
719531337658SMarcel Moolenaar 	}
719631337658SMarcel Moolenaar 	break;
7197d1a0d267SMarcel Moolenaar 
7198d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7199d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL);
720031337658SMarcel Moolenaar     }
720131337658SMarcel Moolenaar 
7202545ddfbeSMarcel Moolenaar     rc = xo_write(xop);
7203545ddfbeSMarcel Moolenaar     if (rc >= 0 && xop->xo_flush)
7204545ddfbeSMarcel Moolenaar 	if (xop->xo_flush(xop->xo_opaque) < 0)
7205545ddfbeSMarcel Moolenaar 	    return -1;
7206545ddfbeSMarcel Moolenaar 
7207545ddfbeSMarcel Moolenaar     return rc;
720831337658SMarcel Moolenaar }
720931337658SMarcel Moolenaar 
7210545ddfbeSMarcel Moolenaar int
721131337658SMarcel Moolenaar xo_flush (void)
721231337658SMarcel Moolenaar {
7213545ddfbeSMarcel Moolenaar     return xo_flush_h(NULL);
721431337658SMarcel Moolenaar }
721531337658SMarcel Moolenaar 
7216545ddfbeSMarcel Moolenaar int
721731337658SMarcel Moolenaar xo_finish_h (xo_handle_t *xop)
721831337658SMarcel Moolenaar {
721931337658SMarcel Moolenaar     const char *cp = "";
722031337658SMarcel Moolenaar     xop = xo_default(xop);
722131337658SMarcel Moolenaar 
7222d1a0d267SMarcel Moolenaar     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
7223545ddfbeSMarcel Moolenaar 	xo_do_close_all(xop, xop->xo_stack);
7224545ddfbeSMarcel Moolenaar 
7225788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
722631337658SMarcel Moolenaar     case XO_STYLE_JSON:
7227d1a0d267SMarcel Moolenaar 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7228d1a0d267SMarcel Moolenaar 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7229d1a0d267SMarcel Moolenaar 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
723031337658SMarcel Moolenaar 	    else
723131337658SMarcel Moolenaar 		cp = "{ ";
723231337658SMarcel Moolenaar 	    xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
723331337658SMarcel Moolenaar 	}
723431337658SMarcel Moolenaar 	break;
7235d1a0d267SMarcel Moolenaar 
7236d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7237d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL);
7238d1a0d267SMarcel Moolenaar 	break;
723931337658SMarcel Moolenaar     }
724031337658SMarcel Moolenaar 
7241545ddfbeSMarcel Moolenaar     return xo_flush_h(xop);
724231337658SMarcel Moolenaar }
724331337658SMarcel Moolenaar 
7244545ddfbeSMarcel Moolenaar int
724531337658SMarcel Moolenaar xo_finish (void)
724631337658SMarcel Moolenaar {
7247545ddfbeSMarcel Moolenaar     return xo_finish_h(NULL);
724831337658SMarcel Moolenaar }
724931337658SMarcel Moolenaar 
725031337658SMarcel Moolenaar /*
7251d1a0d267SMarcel Moolenaar  * xo_finish_atexit is suitable for atexit() calls, to force clear up
7252d1a0d267SMarcel Moolenaar  * and finalizing output.
7253d1a0d267SMarcel Moolenaar  */
7254d1a0d267SMarcel Moolenaar void
7255d1a0d267SMarcel Moolenaar xo_finish_atexit (void)
7256d1a0d267SMarcel Moolenaar {
7257d1a0d267SMarcel Moolenaar     (void) xo_finish_h(NULL);
7258d1a0d267SMarcel Moolenaar }
7259d1a0d267SMarcel Moolenaar 
7260d1a0d267SMarcel Moolenaar /*
726131337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
726231337658SMarcel Moolenaar  */
726331337658SMarcel Moolenaar void
726431337658SMarcel Moolenaar xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
726531337658SMarcel Moolenaar {
726631337658SMarcel Moolenaar     xop = xo_default(xop);
726731337658SMarcel Moolenaar 
726831337658SMarcel Moolenaar     /*
726931337658SMarcel Moolenaar      * If the format string doesn't end with a newline, we pop
727031337658SMarcel Moolenaar      * one on ourselves.
727131337658SMarcel Moolenaar      */
727231337658SMarcel Moolenaar     int len = strlen(fmt);
727331337658SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
727431337658SMarcel Moolenaar 	char *newfmt = alloca(len + 2);
727531337658SMarcel Moolenaar 	memcpy(newfmt, fmt, len);
727631337658SMarcel Moolenaar 	newfmt[len] = '\n';
727731337658SMarcel Moolenaar 	newfmt[len] = '\0';
727831337658SMarcel Moolenaar 	fmt = newfmt;
727931337658SMarcel Moolenaar     }
728031337658SMarcel Moolenaar 
7281788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
728231337658SMarcel Moolenaar     case XO_STYLE_TEXT:
728331337658SMarcel Moolenaar 	vfprintf(stderr, fmt, vap);
728431337658SMarcel Moolenaar 	break;
728531337658SMarcel Moolenaar 
728631337658SMarcel Moolenaar     case XO_STYLE_HTML:
728731337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
728831337658SMarcel Moolenaar 
728931337658SMarcel Moolenaar 	xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
729031337658SMarcel Moolenaar 
7291d1a0d267SMarcel Moolenaar 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
729231337658SMarcel Moolenaar 	    xo_line_close(xop);
729331337658SMarcel Moolenaar 
729431337658SMarcel Moolenaar 	xo_write(xop);
729531337658SMarcel Moolenaar 
729631337658SMarcel Moolenaar 	va_end(xop->xo_vap);
729731337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
729831337658SMarcel Moolenaar 	break;
729931337658SMarcel Moolenaar 
730031337658SMarcel Moolenaar     case XO_STYLE_XML:
7301545ddfbeSMarcel Moolenaar     case XO_STYLE_JSON:
730231337658SMarcel Moolenaar 	va_copy(xop->xo_vap, vap);
730331337658SMarcel Moolenaar 
730431337658SMarcel Moolenaar 	xo_open_container_h(xop, "error");
730531337658SMarcel Moolenaar 	xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
730631337658SMarcel Moolenaar 	xo_close_container_h(xop, "error");
730731337658SMarcel Moolenaar 
730831337658SMarcel Moolenaar 	va_end(xop->xo_vap);
730931337658SMarcel Moolenaar 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
731031337658SMarcel Moolenaar 	break;
7311d1a0d267SMarcel Moolenaar 
7312d1a0d267SMarcel Moolenaar     case XO_STYLE_SDPARAMS:
7313d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7314d1a0d267SMarcel Moolenaar 	break;
731531337658SMarcel Moolenaar     }
731631337658SMarcel Moolenaar }
731731337658SMarcel Moolenaar 
731831337658SMarcel Moolenaar void
731931337658SMarcel Moolenaar xo_error_h (xo_handle_t *xop, const char *fmt, ...)
732031337658SMarcel Moolenaar {
732131337658SMarcel Moolenaar     va_list vap;
732231337658SMarcel Moolenaar 
732331337658SMarcel Moolenaar     va_start(vap, fmt);
732431337658SMarcel Moolenaar     xo_error_hv(xop, fmt, vap);
732531337658SMarcel Moolenaar     va_end(vap);
732631337658SMarcel Moolenaar }
732731337658SMarcel Moolenaar 
732831337658SMarcel Moolenaar /*
732931337658SMarcel Moolenaar  * Generate an error message, such as would be displayed on stderr
733031337658SMarcel Moolenaar  */
733131337658SMarcel Moolenaar void
733231337658SMarcel Moolenaar xo_error (const char *fmt, ...)
733331337658SMarcel Moolenaar {
733431337658SMarcel Moolenaar     va_list vap;
733531337658SMarcel Moolenaar 
733631337658SMarcel Moolenaar     va_start(vap, fmt);
733731337658SMarcel Moolenaar     xo_error_hv(NULL, fmt, vap);
733831337658SMarcel Moolenaar     va_end(vap);
733931337658SMarcel Moolenaar }
734031337658SMarcel Moolenaar 
7341d1a0d267SMarcel Moolenaar /*
7342d1a0d267SMarcel Moolenaar  * Parse any libxo-specific options from the command line, removing them
7343d1a0d267SMarcel Moolenaar  * so the main() argument parsing won't see them.  We return the new value
7344d1a0d267SMarcel Moolenaar  * for argc or -1 for error.  If an error occurred, the program should
7345d1a0d267SMarcel Moolenaar  * exit.  A suitable error message has already been displayed.
7346d1a0d267SMarcel Moolenaar  */
734731337658SMarcel Moolenaar int
734831337658SMarcel Moolenaar xo_parse_args (int argc, char **argv)
734931337658SMarcel Moolenaar {
735031337658SMarcel Moolenaar     static char libxo_opt[] = "--libxo";
735131337658SMarcel Moolenaar     char *cp;
735231337658SMarcel Moolenaar     int i, save;
735331337658SMarcel Moolenaar 
735431337658SMarcel Moolenaar     /* Save our program name for xo_err and friends */
735531337658SMarcel Moolenaar     xo_program = argv[0];
735631337658SMarcel Moolenaar     cp = strrchr(xo_program, '/');
735731337658SMarcel Moolenaar     if (cp)
735831337658SMarcel Moolenaar 	xo_program = cp + 1;
735931337658SMarcel Moolenaar 
736031337658SMarcel Moolenaar     for (save = i = 1; i < argc; i++) {
736131337658SMarcel Moolenaar 	if (argv[i] == NULL
736231337658SMarcel Moolenaar 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
736331337658SMarcel Moolenaar 	    if (save != i)
736431337658SMarcel Moolenaar 		argv[save] = argv[i];
736531337658SMarcel Moolenaar 	    save += 1;
736631337658SMarcel Moolenaar 	    continue;
736731337658SMarcel Moolenaar 	}
736831337658SMarcel Moolenaar 
736931337658SMarcel Moolenaar 	cp = argv[i] + sizeof(libxo_opt) - 1;
737031337658SMarcel Moolenaar 	if (*cp == 0) {
737131337658SMarcel Moolenaar 	    cp = argv[++i];
737231337658SMarcel Moolenaar 	    if (cp == 0) {
737331337658SMarcel Moolenaar 		xo_warnx("missing libxo option");
737431337658SMarcel Moolenaar 		return -1;
737531337658SMarcel Moolenaar 	    }
737631337658SMarcel Moolenaar 
737731337658SMarcel Moolenaar 	    if (xo_set_options(NULL, cp) < 0)
737831337658SMarcel Moolenaar 		return -1;
737931337658SMarcel Moolenaar 	} else if (*cp == ':') {
738031337658SMarcel Moolenaar 	    if (xo_set_options(NULL, cp) < 0)
738131337658SMarcel Moolenaar 		return -1;
738231337658SMarcel Moolenaar 
738331337658SMarcel Moolenaar 	} else if (*cp == '=') {
738431337658SMarcel Moolenaar 	    if (xo_set_options(NULL, ++cp) < 0)
738531337658SMarcel Moolenaar 		return -1;
738631337658SMarcel Moolenaar 
738731337658SMarcel Moolenaar 	} else if (*cp == '-') {
738831337658SMarcel Moolenaar 	    cp += 1;
738931337658SMarcel Moolenaar 	    if (strcmp(cp, "check") == 0) {
739031337658SMarcel Moolenaar 		exit(XO_HAS_LIBXO);
739131337658SMarcel Moolenaar 
739231337658SMarcel Moolenaar 	    } else {
739331337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
739431337658SMarcel Moolenaar 		return -1;
739531337658SMarcel Moolenaar 	    }
739631337658SMarcel Moolenaar 	} else {
739731337658SMarcel Moolenaar 		xo_warnx("unknown libxo option: '%s'", argv[i]);
739831337658SMarcel Moolenaar 	    return -1;
739931337658SMarcel Moolenaar 	}
740031337658SMarcel Moolenaar     }
740131337658SMarcel Moolenaar 
740231337658SMarcel Moolenaar     argv[save] = NULL;
740331337658SMarcel Moolenaar     return save;
740431337658SMarcel Moolenaar }
740531337658SMarcel Moolenaar 
7406d1a0d267SMarcel Moolenaar /*
7407d1a0d267SMarcel Moolenaar  * Debugging function that dumps the current stack of open libxo constructs,
7408d1a0d267SMarcel Moolenaar  * suitable for calling from the debugger.
7409d1a0d267SMarcel Moolenaar  */
7410545ddfbeSMarcel Moolenaar void
7411545ddfbeSMarcel Moolenaar xo_dump_stack (xo_handle_t *xop)
7412545ddfbeSMarcel Moolenaar {
7413545ddfbeSMarcel Moolenaar     int i;
7414545ddfbeSMarcel Moolenaar     xo_stack_t *xsp;
7415545ddfbeSMarcel Moolenaar 
7416545ddfbeSMarcel Moolenaar     xop = xo_default(xop);
7417545ddfbeSMarcel Moolenaar 
7418545ddfbeSMarcel Moolenaar     fprintf(stderr, "Stack dump:\n");
7419545ddfbeSMarcel Moolenaar 
7420545ddfbeSMarcel Moolenaar     xsp = xop->xo_stack;
7421545ddfbeSMarcel Moolenaar     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
7422545ddfbeSMarcel Moolenaar 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
7423545ddfbeSMarcel Moolenaar 		i, xo_state_name(xsp->xs_state),
7424545ddfbeSMarcel Moolenaar 		xsp->xs_name ?: "--", xsp->xs_flags);
7425545ddfbeSMarcel Moolenaar     }
7426545ddfbeSMarcel Moolenaar }
7427545ddfbeSMarcel Moolenaar 
7428d1a0d267SMarcel Moolenaar /*
7429d1a0d267SMarcel Moolenaar  * Record the program name used for error messages
7430d1a0d267SMarcel Moolenaar  */
7431545ddfbeSMarcel Moolenaar void
7432545ddfbeSMarcel Moolenaar xo_set_program (const char *name)
7433545ddfbeSMarcel Moolenaar {
7434545ddfbeSMarcel Moolenaar     xo_program = name;
7435545ddfbeSMarcel Moolenaar }
7436545ddfbeSMarcel Moolenaar 
7437788ca347SMarcel Moolenaar void
7438788ca347SMarcel Moolenaar xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
7439788ca347SMarcel Moolenaar {
7440788ca347SMarcel Moolenaar     xop = xo_default(xop);
7441788ca347SMarcel Moolenaar 
7442788ca347SMarcel Moolenaar     if (version == NULL || strchr(version, '"') != NULL)
7443788ca347SMarcel Moolenaar 	return;
7444788ca347SMarcel Moolenaar 
7445d1a0d267SMarcel Moolenaar     if (!xo_style_is_encoding(xop))
7446d1a0d267SMarcel Moolenaar 	return;
7447d1a0d267SMarcel Moolenaar 
7448788ca347SMarcel Moolenaar     switch (xo_style(xop)) {
7449788ca347SMarcel Moolenaar     case XO_STYLE_XML:
7450788ca347SMarcel Moolenaar 	/* For XML, we record this as an attribute for the first tag */
7451788ca347SMarcel Moolenaar 	xo_attr_h(xop, "__version", "%s", version);
7452788ca347SMarcel Moolenaar 	break;
7453788ca347SMarcel Moolenaar 
7454788ca347SMarcel Moolenaar     case XO_STYLE_JSON:
7455788ca347SMarcel Moolenaar 	/*
7456d1a0d267SMarcel Moolenaar 	 * For JSON, we record the version string in our handle, and emit
7457788ca347SMarcel Moolenaar 	 * it in xo_emit_top.
7458788ca347SMarcel Moolenaar 	 */
7459d1a0d267SMarcel Moolenaar 	xop->xo_version = xo_strndup(version, -1);
7460d1a0d267SMarcel Moolenaar 	break;
7461d1a0d267SMarcel Moolenaar 
7462d1a0d267SMarcel Moolenaar     case XO_STYLE_ENCODER:
7463d1a0d267SMarcel Moolenaar 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version);
7464788ca347SMarcel Moolenaar 	break;
7465788ca347SMarcel Moolenaar     }
7466788ca347SMarcel Moolenaar }
7467788ca347SMarcel Moolenaar 
7468d1a0d267SMarcel Moolenaar /*
7469d1a0d267SMarcel Moolenaar  * Set the version number for the API content being carried thru
7470d1a0d267SMarcel Moolenaar  * the xo handle.
7471d1a0d267SMarcel Moolenaar  */
7472788ca347SMarcel Moolenaar void
7473788ca347SMarcel Moolenaar xo_set_version (const char *version)
7474788ca347SMarcel Moolenaar {
7475788ca347SMarcel Moolenaar     xo_set_version_h(NULL, version);
7476788ca347SMarcel Moolenaar }
7477788ca347SMarcel Moolenaar 
7478d1a0d267SMarcel Moolenaar /*
7479d1a0d267SMarcel Moolenaar  * Generate a warning.  Normally, this is a text message written to
7480d1a0d267SMarcel Moolenaar  * standard error.  If the XOF_WARN_XML flag is set, then we generate
7481d1a0d267SMarcel Moolenaar  * XMLified content on standard output.
7482d1a0d267SMarcel Moolenaar  */
7483d1a0d267SMarcel Moolenaar void
7484d1a0d267SMarcel Moolenaar xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
7485d1a0d267SMarcel Moolenaar 		  const char *fmt, va_list vap)
748631337658SMarcel Moolenaar {
7487d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
748831337658SMarcel Moolenaar 
7489d1a0d267SMarcel Moolenaar     if (fmt == NULL)
7490d1a0d267SMarcel Moolenaar 	return;
749131337658SMarcel Moolenaar 
7492d1a0d267SMarcel Moolenaar     xo_open_marker_h(xop, "xo_emit_warn_hcv");
7493d1a0d267SMarcel Moolenaar     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
749431337658SMarcel Moolenaar 
7495d1a0d267SMarcel Moolenaar     if (xo_program)
7496d1a0d267SMarcel Moolenaar 	xo_emit("{wc:program}", xo_program);
749731337658SMarcel Moolenaar 
7498d1a0d267SMarcel Moolenaar     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
7499d1a0d267SMarcel Moolenaar 	va_list ap;
7500d1a0d267SMarcel Moolenaar 	xo_handle_t temp;
750131337658SMarcel Moolenaar 
7502d1a0d267SMarcel Moolenaar 	bzero(&temp, sizeof(temp));
7503d1a0d267SMarcel Moolenaar 	temp.xo_style = XO_STYLE_TEXT;
7504d1a0d267SMarcel Moolenaar 	xo_buf_init(&temp.xo_data);
7505d1a0d267SMarcel Moolenaar 	xo_depth_check(&temp, XO_DEPTH);
750631337658SMarcel Moolenaar 
7507d1a0d267SMarcel Moolenaar 	va_copy(ap, vap);
7508d1a0d267SMarcel Moolenaar 	(void) xo_emit_hv(&temp, fmt, ap);
7509d1a0d267SMarcel Moolenaar 	va_end(ap);
751031337658SMarcel Moolenaar 
7511d1a0d267SMarcel Moolenaar 	xo_buffer_t *src = &temp.xo_data;
7512d1a0d267SMarcel Moolenaar 	xo_format_value(xop, "message", 7, src->xb_bufp,
7513d1a0d267SMarcel Moolenaar 			src->xb_curp - src->xb_bufp, NULL, 0, 0);
751431337658SMarcel Moolenaar 
7515d1a0d267SMarcel Moolenaar 	xo_free(temp.xo_stack);
7516d1a0d267SMarcel Moolenaar 	xo_buf_cleanup(src);
751731337658SMarcel Moolenaar     }
751831337658SMarcel Moolenaar 
7519d1a0d267SMarcel Moolenaar     (void) xo_emit_hv(xop, fmt, vap);
752031337658SMarcel Moolenaar 
7521d1a0d267SMarcel Moolenaar     int len = strlen(fmt);
7522d1a0d267SMarcel Moolenaar     if (len > 0 && fmt[len - 1] != '\n') {
7523d1a0d267SMarcel Moolenaar 	if (code > 0) {
7524d1a0d267SMarcel Moolenaar 	    const char *msg = strerror(code);
7525d1a0d267SMarcel Moolenaar 	    if (msg)
7526d1a0d267SMarcel Moolenaar 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
7527d1a0d267SMarcel Moolenaar 	}
7528d1a0d267SMarcel Moolenaar 	xo_emit("\n");
752931337658SMarcel Moolenaar     }
753031337658SMarcel Moolenaar 
7531d1a0d267SMarcel Moolenaar     xo_close_marker_h(xop, "xo_emit_warn_hcv");
7532d1a0d267SMarcel Moolenaar     xo_flush_h(xop);
753331337658SMarcel Moolenaar }
753431337658SMarcel Moolenaar 
7535d1a0d267SMarcel Moolenaar void
7536d1a0d267SMarcel Moolenaar xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
7537d1a0d267SMarcel Moolenaar {
7538d1a0d267SMarcel Moolenaar     va_list vap;
753931337658SMarcel Moolenaar 
7540d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7541d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
7542d1a0d267SMarcel Moolenaar     va_end(vap);
754331337658SMarcel Moolenaar }
754431337658SMarcel Moolenaar 
7545d1a0d267SMarcel Moolenaar void
7546d1a0d267SMarcel Moolenaar xo_emit_warn_c (int code, const char *fmt, ...)
7547d1a0d267SMarcel Moolenaar {
7548d1a0d267SMarcel Moolenaar     va_list vap;
754931337658SMarcel Moolenaar 
7550d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7551d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7552d1a0d267SMarcel Moolenaar     va_end(vap);
7553d1a0d267SMarcel Moolenaar }
755431337658SMarcel Moolenaar 
7555d1a0d267SMarcel Moolenaar void
7556d1a0d267SMarcel Moolenaar xo_emit_warn (const char *fmt, ...)
7557d1a0d267SMarcel Moolenaar {
7558d1a0d267SMarcel Moolenaar     int code = errno;
7559d1a0d267SMarcel Moolenaar     va_list vap;
7560d1a0d267SMarcel Moolenaar 
7561d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7562d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7563d1a0d267SMarcel Moolenaar     va_end(vap);
7564d1a0d267SMarcel Moolenaar }
7565d1a0d267SMarcel Moolenaar 
7566d1a0d267SMarcel Moolenaar void
7567d1a0d267SMarcel Moolenaar xo_emit_warnx (const char *fmt, ...)
7568d1a0d267SMarcel Moolenaar {
7569d1a0d267SMarcel Moolenaar     va_list vap;
7570d1a0d267SMarcel Moolenaar 
7571d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7572d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
7573d1a0d267SMarcel Moolenaar     va_end(vap);
7574d1a0d267SMarcel Moolenaar }
7575d1a0d267SMarcel Moolenaar 
7576d1a0d267SMarcel Moolenaar void
7577d1a0d267SMarcel Moolenaar xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
7578d1a0d267SMarcel Moolenaar {
7579d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
758031337658SMarcel Moolenaar     xo_finish();
7581d1a0d267SMarcel Moolenaar     exit(eval);
758231337658SMarcel Moolenaar }
7583d1a0d267SMarcel Moolenaar 
7584d1a0d267SMarcel Moolenaar void
7585d1a0d267SMarcel Moolenaar xo_emit_err (int eval, const char *fmt, ...)
7586d1a0d267SMarcel Moolenaar {
7587d1a0d267SMarcel Moolenaar     int code = errno;
7588d1a0d267SMarcel Moolenaar     va_list vap;
7589d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7590d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, code, fmt, vap);
7591d1a0d267SMarcel Moolenaar     va_end(vap);
7592d1a0d267SMarcel Moolenaar     exit(eval);
7593d1a0d267SMarcel Moolenaar }
7594d1a0d267SMarcel Moolenaar 
7595d1a0d267SMarcel Moolenaar void
7596d1a0d267SMarcel Moolenaar xo_emit_errx (int eval, const char *fmt, ...)
7597d1a0d267SMarcel Moolenaar {
7598d1a0d267SMarcel Moolenaar     va_list vap;
7599d1a0d267SMarcel Moolenaar 
7600d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7601d1a0d267SMarcel Moolenaar     xo_emit_err_v(0, -1, fmt, vap);
7602d1a0d267SMarcel Moolenaar     va_end(vap);
7603d1a0d267SMarcel Moolenaar     xo_finish();
7604d1a0d267SMarcel Moolenaar     exit(eval);
7605d1a0d267SMarcel Moolenaar }
7606d1a0d267SMarcel Moolenaar 
7607d1a0d267SMarcel Moolenaar void
7608d1a0d267SMarcel Moolenaar xo_emit_errc (int eval, int code, const char *fmt, ...)
7609d1a0d267SMarcel Moolenaar {
7610d1a0d267SMarcel Moolenaar     va_list vap;
7611d1a0d267SMarcel Moolenaar 
7612d1a0d267SMarcel Moolenaar     va_start(vap, fmt);
7613d1a0d267SMarcel Moolenaar     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
7614d1a0d267SMarcel Moolenaar     va_end(vap);
7615d1a0d267SMarcel Moolenaar     xo_finish();
7616d1a0d267SMarcel Moolenaar     exit(eval);
7617d1a0d267SMarcel Moolenaar }
7618d1a0d267SMarcel Moolenaar 
7619d1a0d267SMarcel Moolenaar /*
7620d1a0d267SMarcel Moolenaar  * Get the opaque private pointer for an xo handle
7621d1a0d267SMarcel Moolenaar  */
7622d1a0d267SMarcel Moolenaar void *
7623d1a0d267SMarcel Moolenaar xo_get_private (xo_handle_t *xop)
7624d1a0d267SMarcel Moolenaar {
7625d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
7626d1a0d267SMarcel Moolenaar     return xop->xo_private;
7627d1a0d267SMarcel Moolenaar }
7628d1a0d267SMarcel Moolenaar 
7629d1a0d267SMarcel Moolenaar /*
7630d1a0d267SMarcel Moolenaar  * Set the opaque private pointer for an xo handle.
7631d1a0d267SMarcel Moolenaar  */
7632d1a0d267SMarcel Moolenaar void
7633d1a0d267SMarcel Moolenaar xo_set_private (xo_handle_t *xop, void *opaque)
7634d1a0d267SMarcel Moolenaar {
7635d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
7636d1a0d267SMarcel Moolenaar     xop->xo_private = opaque;
7637d1a0d267SMarcel Moolenaar }
7638d1a0d267SMarcel Moolenaar 
7639d1a0d267SMarcel Moolenaar /*
7640d1a0d267SMarcel Moolenaar  * Get the encoder function
7641d1a0d267SMarcel Moolenaar  */
7642d1a0d267SMarcel Moolenaar xo_encoder_func_t
7643d1a0d267SMarcel Moolenaar xo_get_encoder (xo_handle_t *xop)
7644d1a0d267SMarcel Moolenaar {
7645d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
7646d1a0d267SMarcel Moolenaar     return xop->xo_encoder;
7647d1a0d267SMarcel Moolenaar }
7648d1a0d267SMarcel Moolenaar 
7649d1a0d267SMarcel Moolenaar /*
7650d1a0d267SMarcel Moolenaar  * Record an encoder callback function in an xo handle.
7651d1a0d267SMarcel Moolenaar  */
7652d1a0d267SMarcel Moolenaar void
7653d1a0d267SMarcel Moolenaar xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
7654d1a0d267SMarcel Moolenaar {
7655d1a0d267SMarcel Moolenaar     xop = xo_default(xop);
7656d1a0d267SMarcel Moolenaar 
7657d1a0d267SMarcel Moolenaar     xop->xo_style = XO_STYLE_ENCODER;
7658d1a0d267SMarcel Moolenaar     xop->xo_encoder = encoder;
7659d1a0d267SMarcel Moolenaar }
7660