1 /*
2  * Copyright (c) 2014-2015, Juniper Networks, Inc.
3  * All rights reserved.
4  * This SOFTWARE is licensed under the LICENSE provided in the
5  * ../Copyright file. By downloading, installing, copying, or otherwise
6  * using the SOFTWARE, you agree to be bound by the terms of that
7  * LICENSE.
8  * Phil Shafer, July 2014
9  *
10  * This is the implementation of libxo, the formatting library that
11  * generates multiple styles of output from a single code path.
12  * Command line utilities can have their normal text output while
13  * automation tools can see XML or JSON output, and web tools can use
14  * HTML output that encodes the text output annotated with additional
15  * information.  Specialized encoders can be built that allow custom
16  * encoding including binary ones like CBOR, thrift, protobufs, etc.
17  *
18  * Full documentation is available in ./doc/libxo.txt or online at:
19  *   http://juniper.github.io/libxo/libxo-manual.html
20  *
21  * For first time readers, the core bits of code to start looking at are:
22  * - xo_do_emit() -- parse and emit a set of fields
23  * - xo_do_emit_fields -- the central function of the library
24  * - xo_do_format_field() -- handles formatting a single field
25  * - xo_transiton() -- the state machine that keeps things sane
26  * and of course the "xo_handle_t" data structure, which carries all
27  * configuration and state.
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdint.h>
33 #include <unistd.h>
34 #include <stddef.h>
35 #include <wchar.h>
36 #include <locale.h>
37 #include <sys/types.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <ctype.h>
43 #include <wctype.h>
44 #include <getopt.h>
45 
46 #include "xo_config.h"
47 #include "xo.h"
48 #include "xo_encoder.h"
49 #include "xo_buf.h"
50 
51 /*
52  * We ask wcwidth() to do an impossible job, really.  It's supposed to
53  * need to tell us the number of columns consumed to display a unicode
54  * character.  It returns that number without any sort of context, but
55  * we know they are characters whose glyph differs based on placement
56  * (end of word, middle of word, etc) and many that affect characters
57  * previously emitted.  Without content, it can't hope to tell us.
58  * But it's the only standard tool we've got, so we use it.  We would
59  * use wcswidth() but it typically just loops thru adding the results
60  * of wcwidth() calls in an entirely unhelpful way.
61  *
62  * Even then, there are many poor implementations (macosx), so we have
63  * to carry our own.  We could have configure.ac test this (with
64  * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
65  * to run a binary, which breaks cross-compilation.  Hmm... I could
66  * run this test at init time and make a warning for our dear user.
67  *
68  * Anyhow, it remains a best-effort sort of thing.  And it's all made
69  * more hopeless because we assume the display code doing the rendering is
70  * playing by the same rules we are.  If it display 0x200d as a square
71  * box or a funky question mark, the output will be hosed.
72  */
73 #ifdef LIBXO_WCWIDTH
74 #include "xo_wcwidth.h"
75 #else /* LIBXO_WCWIDTH */
76 #define xo_wcwidth(_x) wcwidth(_x)
77 #endif /* LIBXO_WCWIDTH */
78 
79 #ifdef HAVE_STDIO_EXT_H
80 #include <stdio_ext.h>
81 #endif /* HAVE_STDIO_EXT_H */
82 
83 /*
84  * humanize_number is a great function, unless you don't have it.  So
85  * we carry one in our pocket.
86  */
87 #ifdef HAVE_HUMANIZE_NUMBER
88 #include <libutil.h>
89 #define xo_humanize_number humanize_number
90 #else /* HAVE_HUMANIZE_NUMBER */
91 #include "xo_humanize.h"
92 #endif /* HAVE_HUMANIZE_NUMBER */
93 
94 #ifdef HAVE_GETTEXT
95 #include <libintl.h>
96 #endif /* HAVE_GETTEXT */
97 
98 /*
99  * Three styles of specifying thread-local variables are supported.
100  * configure.ac has the brains to run each possibility thru the
101  * compiler and see what works; we are left to define the THREAD_LOCAL
102  * macro to the right value.  Most toolchains (clang, gcc) use
103  * "before", but some (borland) use "after" and I've heard of some
104  * (ms) that use __declspec.  Any others out there?
105  */
106 #define THREAD_LOCAL_before 1
107 #define THREAD_LOCAL_after 2
108 #define THREAD_LOCAL_declspec 3
109 
110 #ifndef HAVE_THREAD_LOCAL
111 #define THREAD_LOCAL(_x) _x
112 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
113 #define THREAD_LOCAL(_x) __thread _x
114 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
115 #define THREAD_LOCAL(_x) _x __thread
116 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
117 #define THREAD_LOCAL(_x) __declspec(_x)
118 #else
119 #error unknown thread-local setting
120 #endif /* HAVE_THREADS_H */
121 
122 const char xo_version[] = LIBXO_VERSION;
123 const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
124 static const char xo_default_format[] = "%s";
125 
126 #ifndef UNUSED
127 #define UNUSED __attribute__ ((__unused__))
128 #endif /* UNUSED */
129 
130 #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
131 #define XO_DEPTH	128	 /* Default stack depth */
132 #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
133 
134 #define XO_FAILURE_NAME	"failure"
135 
136 /* Flags for the stack frame */
137 typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
138 #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
139 #define XSF_LIST	(1<<1)	/* Frame is a list */
140 #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
141 #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
142 
143 #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
144 #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
145 #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
146 #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
147 
148 /* These are the flags we propagate between markers and their parents */
149 #define XSF_MARKER_FLAGS \
150  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
151 
152 /*
153  * A word about states: We use a finite state machine (FMS) approach
154  * to help remove fragility from the caller's code.  Instead of
155  * requiring a specific order of calls, we'll allow the caller more
156  * flexibility and make the library responsible for recovering from
157  * missed steps.  The goal is that the library should not be capable
158  * of emitting invalid xml or json, but the developer shouldn't need
159  * to know or understand all the details about these encodings.
160  *
161  * You can think of states as either states or events, since they
162  * function rather like both.  None of the XO_CLOSE_* events will
163  * persist as states, since the matching stack frame will be popped.
164  * Same is true of XSS_EMIT, which is an event that asks us to
165  * prep for emitting output fields.
166  */
167 
168 /* Stack frame states */
169 typedef unsigned xo_state_t;
170 #define XSS_INIT		0      	/* Initial stack state */
171 #define XSS_OPEN_CONTAINER	1
172 #define XSS_CLOSE_CONTAINER	2
173 #define XSS_OPEN_LIST		3
174 #define XSS_CLOSE_LIST		4
175 #define XSS_OPEN_INSTANCE	5
176 #define XSS_CLOSE_INSTANCE	6
177 #define XSS_OPEN_LEAF_LIST	7
178 #define XSS_CLOSE_LEAF_LIST	8
179 #define XSS_DISCARDING		9	/* Discarding data until recovered */
180 #define XSS_MARKER		10	/* xo_open_marker's marker */
181 #define XSS_EMIT		11	/* xo_emit has a leaf field */
182 #define XSS_EMIT_LEAF_LIST	12	/* xo_emit has a leaf-list ({l:}) */
183 #define XSS_FINISH		13	/* xo_finish was called */
184 
185 #define XSS_MAX			13
186 
187 #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
188 
189 /*
190  * xo_stack_t: As we open and close containers and levels, we
191  * create a stack of frames to track them.  This is needed for
192  * XOF_WARN and XOF_XPATH.
193  */
194 typedef struct xo_stack_s {
195     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
196     xo_state_t xs_state;	/* State for this stack frame */
197     char *xs_name;		/* Name (for XPath value) */
198     char *xs_keys;		/* XPath predicate for any key fields */
199 } xo_stack_t;
200 
201 /*
202  * libxo supports colors and effects, for those who like them.
203  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
204  * ("effects") are bits since we need to maintain state.
205  */
206 #define XO_COL_DEFAULT		0
207 #define XO_COL_BLACK		1
208 #define XO_COL_RED		2
209 #define XO_COL_GREEN		3
210 #define XO_COL_YELLOW		4
211 #define XO_COL_BLUE		5
212 #define XO_COL_MAGENTA		6
213 #define XO_COL_CYAN		7
214 #define XO_COL_WHITE		8
215 
216 #define XO_NUM_COLORS		9
217 
218 /*
219  * Yes, there's no blink.  We're civilized.  We like users.  Blink
220  * isn't something one does to someone you like.  Friends don't let
221  * friends use blink.  On friends.  You know what I mean.  Blink is
222  * like, well, it's like bursting into show tunes at a funeral.  It's
223  * just not done.  Not something anyone wants.  And on those rare
224  * instances where it might actually be appropriate, it's still wrong,
225  * since it's likely done by the wrong person for the wrong reason.
226  * Just like blink.  And if I implemented blink, I'd be like a funeral
227  * director who adds "Would you like us to burst into show tunes?" on
228  * the list of questions asked while making funeral arrangements.
229  * It's formalizing wrongness in the wrong way.  And we're just too
230  * civilized to do that.  Hhhmph!
231  */
232 #define XO_EFF_RESET		(1<<0)
233 #define XO_EFF_NORMAL		(1<<1)
234 #define XO_EFF_BOLD		(1<<2)
235 #define XO_EFF_UNDERLINE	(1<<3)
236 #define XO_EFF_INVERSE		(1<<4)
237 
238 #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
239 
240 typedef uint8_t xo_effect_t;
241 typedef uint8_t xo_color_t;
242 typedef struct xo_colors_s {
243     xo_effect_t xoc_effects;	/* Current effect set */
244     xo_color_t xoc_col_fg;	/* Foreground color */
245     xo_color_t xoc_col_bg;	/* Background color */
246 } xo_colors_t;
247 
248 /*
249  * xo_handle_t: this is the principle data structure for libxo.
250  * It's used as a store for state, options, content, and all manor
251  * of other information.
252  */
253 struct xo_handle_s {
254     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
255     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
256     xo_style_t xo_style;	/* XO_STYLE_* value */
257     unsigned short xo_indent;	/* Indent level (if pretty) */
258     unsigned short xo_indent_by; /* Indent amount (tab stop) */
259     xo_write_func_t xo_write;	/* Write callback */
260     xo_close_func_t xo_close;	/* Close callback */
261     xo_flush_func_t xo_flush;	/* Flush callback */
262     xo_formatter_t xo_formatter; /* Custom formating function */
263     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
264     void *xo_opaque;		/* Opaque data for write function */
265     xo_buffer_t xo_data;	/* Output data */
266     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
267     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
268     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
269     xo_stack_t *xo_stack;	/* Stack pointer */
270     int xo_depth;		/* Depth of stack */
271     int xo_stack_size;		/* Size of the stack */
272     xo_info_t *xo_info;		/* Info fields for all elements */
273     int xo_info_count;		/* Number of info entries */
274     va_list xo_vap;		/* Variable arguments (stdargs) */
275     char *xo_leading_xpath;	/* A leading XPath expression */
276     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
277     unsigned xo_anchor_offset;	/* Start of anchored text */
278     unsigned xo_anchor_columns;	/* Number of columns since the start anchor */
279     int xo_anchor_min_width;	/* Desired width of anchored text */
280     unsigned xo_units_offset;	/* Start of units insertion point */
281     unsigned xo_columns;	/* Columns emitted during this xo_emit call */
282     uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
283     uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
284     xo_colors_t xo_colors;	/* Current color and effect values */
285     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
286     char *xo_version;		/* Version string */
287     int xo_errno;		/* Saved errno for "%m" */
288     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
289     xo_encoder_func_t xo_encoder; /* Encoding function */
290     void *xo_private;		/* Private data for external encoders */
291 };
292 
293 /* Flag operations */
294 #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
295 #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
296 #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
297 
298 #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
299 #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
300 #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
301 
302 #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
303 #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
304 #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
305 
306 /* Internal flags */
307 #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
308 #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
309 #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
310 #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
311 
312 #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
313 #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
314 
315 /* Flags for formatting functions */
316 typedef unsigned long xo_xff_flags_t;
317 #define XFF_COLON	(1<<0)	/* Append a ":" */
318 #define XFF_COMMA	(1<<1)	/* Append a "," iff there's more output */
319 #define XFF_WS		(1<<2)	/* Append a blank */
320 #define XFF_ENCODE_ONLY	(1<<3)	/* Only emit for encoding styles (XML, JSON) */
321 
322 #define XFF_QUOTE	(1<<4)	/* Force quotes */
323 #define XFF_NOQUOTE	(1<<5)	/* Force no quotes */
324 #define XFF_DISPLAY_ONLY (1<<6)	/* Only emit for display styles (text, html) */
325 #define XFF_KEY		(1<<7)	/* Field is a key (for XPath) */
326 
327 #define XFF_XML		(1<<8)	/* Force XML encoding style (for XPath) */
328 #define XFF_ATTR	(1<<9)	/* Escape value using attribute rules (XML) */
329 #define XFF_BLANK_LINE	(1<<10)	/* Emit a blank line */
330 #define XFF_NO_OUTPUT	(1<<11)	/* Do not make any output */
331 
332 #define XFF_TRIM_WS	(1<<12)	/* Trim whitespace off encoded values */
333 #define XFF_LEAF_LIST	(1<<13)	/* A leaf-list (list of values) */
334 #define XFF_UNESCAPE	(1<<14)	/* Need to printf-style unescape the value */
335 #define XFF_HUMANIZE	(1<<15)	/* Humanize the value (for display styles) */
336 
337 #define XFF_HN_SPACE	(1<<16)	/* Humanize: put space before suffix */
338 #define XFF_HN_DECIMAL	(1<<17)	/* Humanize: add one decimal place if <10 */
339 #define XFF_HN_1000	(1<<18)	/* Humanize: use 1000, not 1024 */
340 #define XFF_GT_FIELD	(1<<19) /* Call gettext() on a field */
341 
342 #define XFF_GT_PLURAL	(1<<20)	/* Call dngettext to find plural form */
343 #define XFF_ARGUMENT	(1<<21)	/* Content provided via argument */
344 
345 /* Flags to turn off when we don't want i18n processing */
346 #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
347 
348 /*
349  * Normal printf has width and precision, which for strings operate as
350  * min and max number of columns.  But this depends on the idea that
351  * one byte means one column, which UTF-8 and multi-byte characters
352  * pitches on its ear.  It may take 40 bytes of data to populate 14
353  * columns, but we can't go off looking at 40 bytes of data without the
354  * caller's permission for fear/knowledge that we'll generate core files.
355  *
356  * So we make three values, distinguishing between "max column" and
357  * "number of bytes that we will inspect inspect safely" We call the
358  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
359  *
360  * Under the "first do no harm" theory, we default "max" to "size".
361  * This is a reasonable assumption for folks that don't grok the
362  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
363  * be evil.
364  *
365  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
366  * columns of output, but will never look at more than 14 bytes of the
367  * input buffer.  This is mostly compatible with printf and caller's
368  * expectations.
369  *
370  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
371  * many bytes (or until a NUL is seen) are needed to fill 14 columns
372  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
373  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
374  * of output.
375  *
376  * It's fairly amazing how a good idea (handle all languages of the
377  * world) blows such a big hole in the bottom of the fairly weak boat
378  * that is C string handling.  The simplicity and completenesss are
379  * sunk in ways we haven't even begun to understand.
380  */
381 #define XF_WIDTH_MIN	0	/* Minimal width */
382 #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
383 #define XF_WIDTH_MAX	2	/* Maximum width */
384 #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
385 
386 /* Input and output string encodings */
387 #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
388 #define XF_ENC_UTF8	2	/* UTF-8 */
389 #define XF_ENC_LOCALE	3	/* Current locale */
390 
391 /*
392  * A place to parse printf-style format flags for each field
393  */
394 typedef struct xo_format_s {
395     unsigned char xf_fc;	/* Format character */
396     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
397     unsigned char xf_skip;	/* Skip this field */
398     unsigned char xf_lflag;	/* 'l' (long) */
399     unsigned char xf_hflag;;	/* 'h' (half) */
400     unsigned char xf_jflag;	/* 'j' (intmax_t) */
401     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
402     unsigned char xf_zflag;	/* 'z' (size_t) */
403     unsigned char xf_qflag;	/* 'q' (quad_t) */
404     unsigned char xf_seen_minus; /* Seen a minus */
405     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
406     unsigned xf_dots;		/* Seen one or more '.'s */
407     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
408     unsigned xf_stars;		/* Seen one or more '*'s */
409     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
410 } xo_format_t;
411 
412 /*
413  * This structure represents the parsed field information, suitable for
414  * processing by xo_do_emit and anything else that needs to parse fields.
415  * Note that all pointers point to the main format string.
416  *
417  * XXX This is a first step toward compilable or cachable format
418  * strings.  We can also cache the results of dgettext when no format
419  * is used, assuming the 'p' modifier has _not_ been set.
420  */
421 typedef struct xo_field_info_s {
422     xo_xff_flags_t xfi_flags;	/* Flags for this field */
423     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
424     const char *xfi_start;   /* Start of field in the format string */
425     const char *xfi_content;	/* Field's content */
426     const char *xfi_format;	/* Field's Format */
427     const char *xfi_encoding;	/* Field's encoding format */
428     const char *xfi_next;	/* Next character in format string */
429     unsigned xfi_len;		/* Length of field */
430     unsigned xfi_clen;		/* Content length */
431     unsigned xfi_flen;		/* Format length */
432     unsigned xfi_elen;		/* Encoding length */
433     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
434     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
435 } xo_field_info_t;
436 
437 /*
438  * We keep a 'default' handle to allow callers to avoid having to
439  * allocate one.  Passing NULL to any of our functions will use
440  * this default handle.  Most functions have a variant that doesn't
441  * require a handle at all, since most output is to stdout, which
442  * the default handle handles handily.
443  */
444 static THREAD_LOCAL(xo_handle_t) xo_default_handle;
445 static THREAD_LOCAL(int) xo_default_inited;
446 static int xo_locale_inited;
447 static const char *xo_program;
448 
449 /*
450  * To allow libxo to be used in diverse environment, we allow the
451  * caller to give callbacks for memory allocation.
452  */
453 xo_realloc_func_t xo_realloc = realloc;
454 xo_free_func_t xo_free = free;
455 
456 /* Forward declarations */
457 static void
458 xo_failure (xo_handle_t *xop, const char *fmt, ...);
459 
460 static int
461 xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
462 	       xo_state_t new_state);
463 
464 static void
465 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
466 		   const char *name, int nlen,
467 		   const char *value, int vlen,
468 		   const char *encoding, int elen);
469 
470 static void
471 xo_anchor_clear (xo_handle_t *xop);
472 
473 /*
474  * xo_style is used to retrieve the current style.  When we're built
475  * for "text only" mode, we use this function to drive the removal
476  * of most of the code in libxo.  We return a constant and the compiler
477  * happily removes the non-text code that is not longer executed.  This
478  * trims our code nicely without needing to trampel perfectly readable
479  * code with ifdefs.
480  */
481 static inline xo_style_t
xo_style(xo_handle_t * xop UNUSED)482 xo_style (xo_handle_t *xop UNUSED)
483 {
484 #ifdef LIBXO_TEXT_ONLY
485     return XO_STYLE_TEXT;
486 #else /* LIBXO_TEXT_ONLY */
487     return xop->xo_style;
488 #endif /* LIBXO_TEXT_ONLY */
489 }
490 
491 /*
492  * Callback to write data to a FILE pointer
493  */
494 static int
xo_write_to_file(void * opaque,const char * data)495 xo_write_to_file (void *opaque, const char *data)
496 {
497     FILE *fp = (FILE *) opaque;
498 
499     return fprintf(fp, "%s", data);
500 }
501 
502 /*
503  * Callback to close a file
504  */
505 static void
xo_close_file(void * opaque)506 xo_close_file (void *opaque)
507 {
508     FILE *fp = (FILE *) opaque;
509 
510     fclose(fp);
511 }
512 
513 /*
514  * Callback to flush a FILE pointer
515  */
516 static int
xo_flush_file(void * opaque)517 xo_flush_file (void *opaque)
518 {
519     FILE *fp = (FILE *) opaque;
520 
521     return fflush(fp);
522 }
523 
524 /*
525  * Use a rotating stock of buffers to make a printable string
526  */
527 #define XO_NUMBUFS 8
528 #define XO_SMBUFSZ 128
529 
530 static const char *
xo_printable(const char * str)531 xo_printable (const char *str)
532 {
533     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
534     static THREAD_LOCAL(int) bufnum = 0;
535 
536     if (str == NULL)
537 	return "";
538 
539     if (++bufnum == XO_NUMBUFS)
540 	bufnum = 0;
541 
542     char *res = bufset[bufnum], *cp, *ep;
543 
544     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
545 	if (*str == '\n') {
546 	    *cp++ = '\\';
547 	    *cp = 'n';
548 	} else if (*str == '\r') {
549 	    *cp++ = '\\';
550 	    *cp = 'r';
551 	} else if (*str == '\"') {
552 	    *cp++ = '\\';
553 	    *cp = '"';
554 	} else
555 	    *cp = *str;
556     }
557 
558     *cp = '\0';
559     return res;
560 }
561 
562 static int
xo_depth_check(xo_handle_t * xop,int depth)563 xo_depth_check (xo_handle_t *xop, int depth)
564 {
565     xo_stack_t *xsp;
566 
567     if (depth >= xop->xo_stack_size) {
568 	depth += XO_DEPTH;	/* Extra room */
569 
570 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
571 	if (xsp == NULL) {
572 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
573 	    return -1;
574 	}
575 
576 	int count = depth - xop->xo_stack_size;
577 
578 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
579 	xop->xo_stack_size = depth;
580 	xop->xo_stack = xsp;
581     }
582 
583     return 0;
584 }
585 
586 void
xo_no_setlocale(void)587 xo_no_setlocale (void)
588 {
589     xo_locale_inited = 1;	/* Skip initialization */
590 }
591 
592 /*
593  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
594  * standard way to decide this (e.g. getlinebuf()), we have configure
595  * look to find __flbf, which glibc supported.  If not, we'll rely on
596  * isatty, with the assumption that terminals are the only thing
597  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
598  * which is all __flbf does, but that's even tackier.  Like a
599  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
600  * something we're willing to do.
601  */
602 static int
xo_is_line_buffered(FILE * stream)603 xo_is_line_buffered (FILE *stream)
604 {
605 #if HAVE___FLBF
606     if (__flbf(stream))
607 	return 1;
608 #else /* HAVE___FLBF */
609     if (isatty(fileno(stream)))
610 	return 1;
611 #endif /* HAVE___FLBF */
612     return 0;
613 }
614 
615 /*
616  * Initialize an xo_handle_t, using both static defaults and
617  * the global settings from the LIBXO_OPTIONS environment
618  * variable.
619  */
620 static void
xo_init_handle(xo_handle_t * xop)621 xo_init_handle (xo_handle_t *xop)
622 {
623     xop->xo_opaque = stdout;
624     xop->xo_write = xo_write_to_file;
625     xop->xo_flush = xo_flush_file;
626 
627     if (xo_is_line_buffered(stdout))
628 	XOF_SET(xop, XOF_FLUSH_LINE);
629 
630     /*
631      * We only want to do color output on terminals, but we only want
632      * to do this if the user has asked for color.
633      */
634     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
635 	XOF_SET(xop, XOF_COLOR);
636 
637     /*
638      * We need to initialize the locale, which isn't really pretty.
639      * Libraries should depend on their caller to set up the
640      * environment.  But we really can't count on the caller to do
641      * this, because well, they won't.  Trust me.
642      */
643     if (!xo_locale_inited) {
644 	xo_locale_inited = 1;	/* Only do this once */
645 
646 	const char *cp = getenv("LC_CTYPE");
647 	if (cp == NULL)
648 	    cp = getenv("LANG");
649 	if (cp == NULL)
650 	    cp = getenv("LC_ALL");
651 	if (cp == NULL)
652 	    cp = "C";		/* Default for C programs */
653 	(void) setlocale(LC_CTYPE, cp);
654     }
655 
656     /*
657      * Initialize only the xo_buffers we know we'll need; the others
658      * can be allocated as needed.
659      */
660     xo_buf_init(&xop->xo_data);
661     xo_buf_init(&xop->xo_fmt);
662 
663     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
664 	return;
665     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
666 
667     xop->xo_indent_by = XO_INDENT_BY;
668     xo_depth_check(xop, XO_DEPTH);
669 
670 #if !defined(NO_LIBXO_OPTIONS)
671     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
672 	char *env = getenv("LIBXO_OPTIONS");
673 	if (env)
674 	    xo_set_options(xop, env);
675 
676     }
677 #endif /* NO_GETENV */
678 
679     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
680 }
681 
682 /*
683  * Initialize the default handle.
684  */
685 static void
xo_default_init(void)686 xo_default_init (void)
687 {
688     xo_handle_t *xop = &xo_default_handle;
689 
690     xo_init_handle(xop);
691 
692     xo_default_inited = 1;
693 }
694 
695 /*
696  * Cheap convenience function to return either the argument, or
697  * the internal handle, after it has been initialized.  The usage
698  * is:
699  *    xop = xo_default(xop);
700  */
701 static xo_handle_t *
xo_default(xo_handle_t * xop)702 xo_default (xo_handle_t *xop)
703 {
704     if (xop == NULL) {
705 	if (xo_default_inited == 0)
706 	    xo_default_init();
707 	xop = &xo_default_handle;
708     }
709 
710     return xop;
711 }
712 
713 /*
714  * Return the number of spaces we should be indenting.  If
715  * we are pretty-printing, this is indent * indent_by.
716  */
717 static int
xo_indent(xo_handle_t * xop)718 xo_indent (xo_handle_t *xop)
719 {
720     int rc = 0;
721 
722     xop = xo_default(xop);
723 
724     if (XOF_ISSET(xop, XOF_PRETTY)) {
725 	rc = xop->xo_indent * xop->xo_indent_by;
726 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
727 	    rc += xop->xo_indent_by;
728     }
729 
730     return (rc > 0) ? rc : 0;
731 }
732 
733 static void
xo_buf_indent(xo_handle_t * xop,int indent)734 xo_buf_indent (xo_handle_t *xop, int indent)
735 {
736     xo_buffer_t *xbp = &xop->xo_data;
737 
738     if (indent <= 0)
739 	indent = xo_indent(xop);
740 
741     if (!xo_buf_has_room(xbp, indent))
742 	return;
743 
744     memset(xbp->xb_curp, ' ', indent);
745     xbp->xb_curp += indent;
746 }
747 
748 static char xo_xml_amp[] = "&amp;";
749 static char xo_xml_lt[] = "&lt;";
750 static char xo_xml_gt[] = "&gt;";
751 static char xo_xml_quot[] = "&quot;";
752 
753 static int
xo_escape_xml(xo_buffer_t * xbp,int len,xo_xff_flags_t flags)754 xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags)
755 {
756     int slen;
757     unsigned delta = 0;
758     char *cp, *ep, *ip;
759     const char *sp;
760     int attr = (flags & XFF_ATTR);
761 
762     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
763 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
764 	if (*cp == '<')
765 	    delta += sizeof(xo_xml_lt) - 2;
766 	else if (*cp == '>')
767 	    delta += sizeof(xo_xml_gt) - 2;
768 	else if (*cp == '&')
769 	    delta += sizeof(xo_xml_amp) - 2;
770 	else if (attr && *cp == '"')
771 	    delta += sizeof(xo_xml_quot) - 2;
772     }
773 
774     if (delta == 0)		/* Nothing to escape; bail */
775 	return len;
776 
777     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
778 	return 0;
779 
780     ep = xbp->xb_curp;
781     cp = ep + len;
782     ip = cp + delta;
783     do {
784 	cp -= 1;
785 	ip -= 1;
786 
787 	if (*cp == '<')
788 	    sp = xo_xml_lt;
789 	else if (*cp == '>')
790 	    sp = xo_xml_gt;
791 	else if (*cp == '&')
792 	    sp = xo_xml_amp;
793 	else if (attr && *cp == '"')
794 	    sp = xo_xml_quot;
795 	else {
796 	    *ip = *cp;
797 	    continue;
798 	}
799 
800 	slen = strlen(sp);
801 	ip -= slen - 1;
802 	memcpy(ip, sp, slen);
803 
804     } while (cp > ep && cp != ip);
805 
806     return len + delta;
807 }
808 
809 static int
xo_escape_json(xo_buffer_t * xbp,int len,xo_xff_flags_t flags UNUSED)810 xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
811 {
812     unsigned delta = 0;
813     char *cp, *ep, *ip;
814 
815     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
816 	if (*cp == '\\' || *cp == '"')
817 	    delta += 1;
818 	else if (*cp == '\n' || *cp == '\r')
819 	    delta += 1;
820     }
821 
822     if (delta == 0)		/* Nothing to escape; bail */
823 	return len;
824 
825     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
826 	return 0;
827 
828     ep = xbp->xb_curp;
829     cp = ep + len;
830     ip = cp + delta;
831     do {
832 	cp -= 1;
833 	ip -= 1;
834 
835 	if (*cp == '\\' || *cp == '"') {
836 	    *ip-- = *cp;
837 	    *ip = '\\';
838 	} else if (*cp == '\n') {
839 	    *ip-- = 'n';
840 	    *ip = '\\';
841 	} else if (*cp == '\r') {
842 	    *ip-- = 'r';
843 	    *ip = '\\';
844 	} else {
845 	    *ip = *cp;
846 	}
847 
848     } while (cp > ep && cp != ip);
849 
850     return len + delta;
851 }
852 
853 /*
854  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
855  *                                ; ']' MUST be escaped.
856  */
857 static int
xo_escape_sdparams(xo_buffer_t * xbp,int len,xo_xff_flags_t flags UNUSED)858 xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
859 {
860     unsigned delta = 0;
861     char *cp, *ep, *ip;
862 
863     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
864 	if (*cp == '\\' || *cp == '"' || *cp == ']')
865 	    delta += 1;
866     }
867 
868     if (delta == 0)		/* Nothing to escape; bail */
869 	return len;
870 
871     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
872 	return 0;
873 
874     ep = xbp->xb_curp;
875     cp = ep + len;
876     ip = cp + delta;
877     do {
878 	cp -= 1;
879 	ip -= 1;
880 
881 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
882 	    *ip-- = *cp;
883 	    *ip = '\\';
884 	} else {
885 	    *ip = *cp;
886 	}
887 
888     } while (cp > ep && cp != ip);
889 
890     return len + delta;
891 }
892 
893 static void
xo_buf_escape(xo_handle_t * xop,xo_buffer_t * xbp,const char * str,int len,xo_xff_flags_t flags)894 xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
895 	       const char *str, int len, xo_xff_flags_t flags)
896 {
897     if (!xo_buf_has_room(xbp, len))
898 	return;
899 
900     memcpy(xbp->xb_curp, str, len);
901 
902     switch (xo_style(xop)) {
903     case XO_STYLE_XML:
904     case XO_STYLE_HTML:
905 	len = xo_escape_xml(xbp, len, flags);
906 	break;
907 
908     case XO_STYLE_JSON:
909 	len = xo_escape_json(xbp, len, flags);
910 	break;
911 
912     case XO_STYLE_SDPARAMS:
913 	len = xo_escape_sdparams(xbp, len, flags);
914 	break;
915     }
916 
917     xbp->xb_curp += len;
918 }
919 
920 /*
921  * Write the current contents of the data buffer using the handle's
922  * xo_write function.
923  */
924 static int
xo_write(xo_handle_t * xop)925 xo_write (xo_handle_t *xop)
926 {
927     int rc = 0;
928     xo_buffer_t *xbp = &xop->xo_data;
929 
930     if (xbp->xb_curp != xbp->xb_bufp) {
931 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
932 	xo_anchor_clear(xop);
933 	if (xop->xo_write)
934 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
935 	xbp->xb_curp = xbp->xb_bufp;
936     }
937 
938     /* Turn off the flags that don't survive across writes */
939     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
940 
941     return rc;
942 }
943 
944 /*
945  * Format arguments into our buffer.  If a custom formatter has been set,
946  * we use that to do the work; otherwise we vsnprintf().
947  */
948 static int
xo_vsnprintf(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,va_list vap)949 xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
950 {
951     va_list va_local;
952     int rc;
953     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
954 
955     va_copy(va_local, vap);
956 
957     if (xop->xo_formatter)
958 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
959     else
960 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
961 
962     if (rc >= left) {
963 	if (!xo_buf_has_room(xbp, rc)) {
964 	    va_end(va_local);
965 	    return -1;
966 	}
967 
968 	/*
969 	 * After we call vsnprintf(), the stage of vap is not defined.
970 	 * We need to copy it before we pass.  Then we have to do our
971 	 * own logic below to move it along.  This is because the
972 	 * implementation can have va_list be a pointer (bsd) or a
973 	 * structure (macosx) or anything in between.
974 	 */
975 
976 	va_end(va_local);	/* Reset vap to the start */
977 	va_copy(va_local, vap);
978 
979 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
980 	if (xop->xo_formatter)
981 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
982 	else
983 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
984     }
985     va_end(va_local);
986 
987     return rc;
988 }
989 
990 /*
991  * Print some data thru the handle.
992  */
993 static int
xo_printf_v(xo_handle_t * xop,const char * fmt,va_list vap)994 xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
995 {
996     xo_buffer_t *xbp = &xop->xo_data;
997     int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
998     int rc;
999     va_list va_local;
1000 
1001     va_copy(va_local, vap);
1002 
1003     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1004 
1005     if (rc >= left) {
1006 	if (!xo_buf_has_room(xbp, rc)) {
1007 	    va_end(va_local);
1008 	    return -1;
1009 	}
1010 
1011 	va_end(va_local);	/* Reset vap to the start */
1012 	va_copy(va_local, vap);
1013 
1014 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1015 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1016     }
1017 
1018     va_end(va_local);
1019 
1020     if (rc > 0)
1021 	xbp->xb_curp += rc;
1022 
1023     return rc;
1024 }
1025 
1026 static int
xo_printf(xo_handle_t * xop,const char * fmt,...)1027 xo_printf (xo_handle_t *xop, const char *fmt, ...)
1028 {
1029     int rc;
1030     va_list vap;
1031 
1032     va_start(vap, fmt);
1033 
1034     rc = xo_printf_v(xop, fmt, vap);
1035 
1036     va_end(vap);
1037     return rc;
1038 }
1039 
1040 /*
1041  * These next few function are make The Essential UTF-8 Ginsu Knife.
1042  * Identify an input and output character, and convert it.
1043  */
1044 static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
1045 
1046 static int
xo_is_utf8(char ch)1047 xo_is_utf8 (char ch)
1048 {
1049     return (ch & 0x80);
1050 }
1051 
1052 static inline int
xo_utf8_to_wc_len(const char * buf)1053 xo_utf8_to_wc_len (const char *buf)
1054 {
1055     unsigned b = (unsigned char) *buf;
1056     int len;
1057 
1058     if ((b & 0x80) == 0x0)
1059 	len = 1;
1060     else if ((b & 0xe0) == 0xc0)
1061 	len = 2;
1062     else if ((b & 0xf0) == 0xe0)
1063 	len = 3;
1064     else if ((b & 0xf8) == 0xf0)
1065 	len = 4;
1066     else if ((b & 0xfc) == 0xf8)
1067 	len = 5;
1068     else if ((b & 0xfe) == 0xfc)
1069 	len = 6;
1070     else
1071 	len = -1;
1072 
1073     return len;
1074 }
1075 
1076 static int
xo_buf_utf8_len(xo_handle_t * xop,const char * buf,int bufsiz)1077 xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
1078 {
1079 
1080     unsigned b = (unsigned char) *buf;
1081     int len, i;
1082 
1083     len = xo_utf8_to_wc_len(buf);
1084     if (len == -1) {
1085         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
1086 	return -1;
1087     }
1088 
1089     if (len > bufsiz) {
1090         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
1091 		   b, len, bufsiz);
1092 	return -1;
1093     }
1094 
1095     for (i = 2; i < len; i++) {
1096 	b = (unsigned char ) buf[i];
1097 	if ((b & 0xc0) != 0x80) {
1098 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
1099 	    return -1;
1100 	}
1101     }
1102 
1103     return len;
1104 }
1105 
1106 /*
1107  * Build a wide character from the input buffer; the number of
1108  * bits we pull off the first character is dependent on the length,
1109  * but we put 6 bits off all other bytes.
1110  */
1111 static inline wchar_t
xo_utf8_char(const char * buf,int len)1112 xo_utf8_char (const char *buf, int len)
1113 {
1114     /* Most common case: singleton byte */
1115     if (len == 1)
1116 	return (unsigned char) buf[0];
1117 
1118     int i;
1119     wchar_t wc;
1120     const unsigned char *cp = (const unsigned char *) buf;
1121 
1122     wc = *cp & xo_utf8_bits[len];
1123     for (i = 1; i < len; i++) {
1124 	wc <<= 6;
1125 	wc |= cp[i] & 0x3f;
1126 	if ((cp[i] & 0xc0) != 0x80)
1127 	    return (wchar_t) -1;
1128     }
1129 
1130     return wc;
1131 }
1132 
1133 /*
1134  * Determine the number of bytes needed to encode a wide character.
1135  */
1136 static int
xo_utf8_emit_len(wchar_t wc)1137 xo_utf8_emit_len (wchar_t wc)
1138 {
1139     int len;
1140 
1141     if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
1142 	len = 1;
1143     else if ((wc & ((1<<11) - 1)) == wc)
1144 	len = 2;
1145     else if ((wc & ((1<<16) - 1)) == wc)
1146 	len = 3;
1147     else if ((wc & ((1<<21) - 1)) == wc)
1148 	len = 4;
1149     else if ((wc & ((1<<26) - 1)) == wc)
1150 	len = 5;
1151     else
1152 	len = 6;
1153 
1154     return len;
1155 }
1156 
1157 static void
xo_utf8_emit_char(char * buf,int len,wchar_t wc)1158 xo_utf8_emit_char (char *buf, int len, wchar_t wc)
1159 {
1160     int i;
1161 
1162     if (len == 1) { /* Simple case */
1163 	buf[0] = wc & 0x7f;
1164 	return;
1165     }
1166 
1167     for (i = len - 1; i >= 0; i--) {
1168 	buf[i] = 0x80 | (wc & 0x3f);
1169 	wc >>= 6;
1170     }
1171 
1172     buf[0] &= xo_utf8_bits[len];
1173     buf[0] |= ~xo_utf8_bits[len] << 1;
1174 }
1175 
1176 static int
xo_buf_append_locale_from_utf8(xo_handle_t * xop,xo_buffer_t * xbp,const char * ibuf,int ilen)1177 xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
1178 				const char *ibuf, int ilen)
1179 {
1180     wchar_t wc;
1181     int len;
1182 
1183     /*
1184      * Build our wide character from the input buffer; the number of
1185      * bits we pull off the first character is dependent on the length,
1186      * but we put 6 bits off all other bytes.
1187      */
1188     wc = xo_utf8_char(ibuf, ilen);
1189     if (wc == (wchar_t) -1) {
1190 	xo_failure(xop, "invalid utf-8 byte sequence");
1191 	return 0;
1192     }
1193 
1194     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
1195 	if (!xo_buf_has_room(xbp, ilen))
1196 	    return 0;
1197 
1198 	memcpy(xbp->xb_curp, ibuf, ilen);
1199 	xbp->xb_curp += ilen;
1200 
1201     } else {
1202 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1203 	    return 0;
1204 
1205 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
1206 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1207 
1208 	if (len <= 0) {
1209 	    xo_failure(xop, "could not convert wide char: %lx",
1210 		       (unsigned long) wc);
1211 	    return 0;
1212 	}
1213 	xbp->xb_curp += len;
1214     }
1215 
1216     return xo_wcwidth(wc);
1217 }
1218 
1219 static void
xo_buf_append_locale(xo_handle_t * xop,xo_buffer_t * xbp,const char * cp,int len)1220 xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
1221 		      const char *cp, int len)
1222 {
1223     const char *sp = cp, *ep = cp + len;
1224     unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
1225     int slen;
1226     int cols = 0;
1227 
1228     for ( ; cp < ep; cp++) {
1229 	if (!xo_is_utf8(*cp)) {
1230 	    cols += 1;
1231 	    continue;
1232 	}
1233 
1234 	/*
1235 	 * We're looking at a non-ascii UTF-8 character.
1236 	 * First we copy the previous data.
1237 	 * Then we need find the length and validate it.
1238 	 * Then we turn it into a wide string.
1239 	 * Then we turn it into a localized string.
1240 	 * Then we repeat.  Isn't i18n fun?
1241 	 */
1242 	if (sp != cp)
1243 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
1244 
1245 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
1246 	if (slen <= 0) {
1247 	    /* Bad data; back it all out */
1248 	    xbp->xb_curp = xbp->xb_bufp + save_off;
1249 	    return;
1250 	}
1251 
1252 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
1253 
1254 	/* Next time thru, we'll start at the next character */
1255 	cp += slen - 1;
1256 	sp = cp + 1;
1257     }
1258 
1259     /* Update column values */
1260     if (XOF_ISSET(xop, XOF_COLUMNS))
1261 	xop->xo_columns += cols;
1262     if (XOIF_ISSET(xop, XOIF_ANCHOR))
1263 	xop->xo_anchor_columns += cols;
1264 
1265     /* Before we fall into the basic logic below, we need reset len */
1266     len = ep - sp;
1267     if (len != 0) /* Append trailing data */
1268 	xo_buf_append(xbp, sp, len);
1269 }
1270 
1271 /*
1272  * Append the given string to the given buffer, without escaping or
1273  * character set conversion.  This is the straight copy to the data
1274  * buffer with no fanciness.
1275  */
1276 static void
xo_data_append(xo_handle_t * xop,const char * str,int len)1277 xo_data_append (xo_handle_t *xop, const char *str, int len)
1278 {
1279     xo_buf_append(&xop->xo_data, str, len);
1280 }
1281 
1282 /*
1283  * Append the given string to the given buffer
1284  */
1285 static void
xo_data_escape(xo_handle_t * xop,const char * str,int len)1286 xo_data_escape (xo_handle_t *xop, const char *str, int len)
1287 {
1288     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
1289 }
1290 
1291 #ifdef LIBXO_NO_RETAIN
1292 /*
1293  * Empty implementations of the retain logic
1294  */
1295 
1296 void
xo_retain_clear_all(void)1297 xo_retain_clear_all (void)
1298 {
1299     return;
1300 }
1301 
1302 void
xo_retain_clear(const char * fmt UNUSED)1303 xo_retain_clear (const char *fmt UNUSED)
1304 {
1305     return;
1306 }
1307 static void
xo_retain_add(const char * fmt UNUSED,xo_field_info_t * fields UNUSED,unsigned num_fields UNUSED)1308 xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
1309 		unsigned num_fields UNUSED)
1310 {
1311     return;
1312 }
1313 
1314 static int
xo_retain_find(const char * fmt UNUSED,xo_field_info_t ** valp UNUSED,unsigned * nump UNUSED)1315 xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
1316 		 unsigned *nump UNUSED)
1317 {
1318     return -1;
1319 }
1320 
1321 #else /* !LIBXO_NO_RETAIN */
1322 /*
1323  * Retain: We retain parsed field definitions to enhance performance,
1324  * especially inside loops.  We depend on the caller treating the format
1325  * strings as immutable, so that we can retain pointers into them.  We
1326  * hold the pointers in a hash table, so allow quick access.  Retained
1327  * information is retained until xo_retain_clear is called.
1328  */
1329 
1330 /*
1331  * xo_retain_entry_t holds information about one retained set of
1332  * parsed fields.
1333  */
1334 typedef struct xo_retain_entry_s {
1335     struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
1336     unsigned long xre_hits;		 /* Number of times we've hit */
1337     const char *xre_format;		 /* Pointer to format string */
1338     unsigned xre_num_fields;		 /* Number of fields saved */
1339     xo_field_info_t *xre_fields;	 /* Pointer to fields */
1340 } xo_retain_entry_t;
1341 
1342 /*
1343  * xo_retain_t holds a complete set of parsed fields as a hash table.
1344  */
1345 #ifndef XO_RETAIN_SIZE
1346 #define XO_RETAIN_SIZE 6
1347 #endif /* XO_RETAIN_SIZE */
1348 #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
1349 
1350 typedef struct xo_retain_s {
1351     xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
1352 } xo_retain_t;
1353 
1354 static THREAD_LOCAL(xo_retain_t) xo_retain;
1355 static THREAD_LOCAL(unsigned) xo_retain_count;
1356 
1357 /*
1358  * Simple hash function based on Thomas Wang's paper.  The original is
1359  * gone, but an archive is available on the Way Back Machine:
1360  *
1361  * http://web.archive.org/web/20071223173210/\
1362  *     http://www.concentric.net/~Ttwang/tech/inthash.htm
1363  *
1364  * For our purposes, we can assume the low four bits are uninteresting
1365  * since any string less that 16 bytes wouldn't be worthy of
1366  * retaining.  We toss the high bits also, since these bits are likely
1367  * to be common among constant format strings.  We then run Wang's
1368  * algorithm, and cap the result at RETAIN_HASH_SIZE.
1369  */
1370 static unsigned
xo_retain_hash(const char * fmt)1371 xo_retain_hash (const char *fmt)
1372 {
1373     volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
1374 
1375     /* Discard low four bits and high bits; they aren't interesting */
1376     uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
1377 
1378     val = (val ^ 61) ^ (val >> 16);
1379     val = val + (val << 3);
1380     val = val ^ (val >> 4);
1381     val = val * 0x3a8f05c5;	/* My large prime number */
1382     val = val ^ (val >> 15);
1383     val &= RETAIN_HASH_SIZE - 1;
1384 
1385     return val;
1386 }
1387 
1388 /*
1389  * Walk all buckets, clearing all retained entries
1390  */
1391 void
xo_retain_clear_all(void)1392 xo_retain_clear_all (void)
1393 {
1394     int i;
1395     xo_retain_entry_t *xrep, *next;
1396 
1397     for (i = 0; i < RETAIN_HASH_SIZE; i++) {
1398 	for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
1399 	    next = xrep->xre_next;
1400 	    xo_free(xrep);
1401 	}
1402 	xo_retain.xr_bucket[i] = NULL;
1403     }
1404     xo_retain_count = 0;
1405 }
1406 
1407 /*
1408  * Walk all buckets, clearing all retained entries
1409  */
1410 void
xo_retain_clear(const char * fmt)1411 xo_retain_clear (const char *fmt)
1412 {
1413     xo_retain_entry_t **xrepp;
1414     unsigned hash = xo_retain_hash(fmt);
1415 
1416     for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
1417 	 xrepp = &(*xrepp)->xre_next) {
1418 	if ((*xrepp)->xre_format == fmt) {
1419 	    *xrepp = (*xrepp)->xre_next;
1420 	    xo_retain_count -= 1;
1421 	    return;
1422 	}
1423     }
1424 }
1425 
1426 /*
1427  * Search the hash for an entry matching 'fmt'; return it's fields.
1428  */
1429 static int
xo_retain_find(const char * fmt,xo_field_info_t ** valp,unsigned * nump)1430 xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
1431 {
1432     if (xo_retain_count == 0)
1433 	return -1;
1434 
1435     unsigned hash = xo_retain_hash(fmt);
1436     xo_retain_entry_t *xrep;
1437 
1438     for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
1439 	 xrep = xrep->xre_next) {
1440 	if (xrep->xre_format == fmt) {
1441 	    *valp = xrep->xre_fields;
1442 	    *nump = xrep->xre_num_fields;
1443 	    xrep->xre_hits += 1;
1444 	    return 0;
1445 	}
1446     }
1447 
1448     return -1;
1449 }
1450 
1451 static void
xo_retain_add(const char * fmt,xo_field_info_t * fields,unsigned num_fields)1452 xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
1453 {
1454     unsigned hash = xo_retain_hash(fmt);
1455     xo_retain_entry_t *xrep;
1456     unsigned sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
1457     xo_field_info_t *xfip;
1458 
1459     xrep = xo_realloc(NULL, sz);
1460     if (xrep == NULL)
1461 	return;
1462 
1463     xfip = (xo_field_info_t *) &xrep[1];
1464     memcpy(xfip, fields, num_fields * sizeof(*fields));
1465 
1466     bzero(xrep, sizeof(*xrep));
1467 
1468     xrep->xre_format = fmt;
1469     xrep->xre_fields = xfip;
1470     xrep->xre_num_fields = num_fields;
1471 
1472     /* Record the field info in the retain bucket */
1473     xrep->xre_next = xo_retain.xr_bucket[hash];
1474     xo_retain.xr_bucket[hash] = xrep;
1475     xo_retain_count += 1;
1476 }
1477 
1478 #endif /* !LIBXO_NO_RETAIN */
1479 
1480 /*
1481  * Generate a warning.  Normally, this is a text message written to
1482  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1483  * XMLified content on standard output.
1484  */
1485 static void
xo_warn_hcv(xo_handle_t * xop,int code,int check_warn,const char * fmt,va_list vap)1486 xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
1487 	     const char *fmt, va_list vap)
1488 {
1489     xop = xo_default(xop);
1490     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
1491 	return;
1492 
1493     if (fmt == NULL)
1494 	return;
1495 
1496     int len = strlen(fmt);
1497     int plen = xo_program ? strlen(xo_program) : 0;
1498     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
1499 
1500     if (plen) {
1501 	memcpy(newfmt, xo_program, plen);
1502 	newfmt[plen++] = ':';
1503 	newfmt[plen++] = ' ';
1504     }
1505     memcpy(newfmt + plen, fmt, len);
1506     newfmt[len + plen] = '\0';
1507 
1508     if (XOF_ISSET(xop, XOF_WARN_XML)) {
1509 	static char err_open[] = "<error>";
1510 	static char err_close[] = "</error>";
1511 	static char msg_open[] = "<message>";
1512 	static char msg_close[] = "</message>";
1513 
1514 	xo_buffer_t *xbp = &xop->xo_data;
1515 
1516 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
1517 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1518 
1519 	va_list va_local;
1520 	va_copy(va_local, vap);
1521 
1522 	int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1523 	int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1524 	if (rc >= left) {
1525 	    if (!xo_buf_has_room(xbp, rc)) {
1526 		va_end(va_local);
1527 		return;
1528 	    }
1529 
1530 	    va_end(vap);	/* Reset vap to the start */
1531 	    va_copy(vap, va_local);
1532 
1533 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1534 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1535 	}
1536 	va_end(va_local);
1537 
1538 	rc = xo_escape_xml(xbp, rc, 1);
1539 	xbp->xb_curp += rc;
1540 
1541 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1542 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
1543 
1544 	if (code >= 0) {
1545 	    const char *msg = strerror(code);
1546 	    if (msg) {
1547 		xo_buf_append(xbp, ": ", 2);
1548 		xo_buf_append(xbp, msg, strlen(msg));
1549 	    }
1550 	}
1551 
1552 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1553 	(void) xo_write(xop);
1554 
1555     } else {
1556 	vfprintf(stderr, newfmt, vap);
1557 	if (code >= 0) {
1558 	    const char *msg = strerror(code);
1559 	    if (msg)
1560 		fprintf(stderr, ": %s", msg);
1561 	}
1562 	fprintf(stderr, "\n");
1563     }
1564 }
1565 
1566 void
xo_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)1567 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1568 {
1569     va_list vap;
1570 
1571     va_start(vap, fmt);
1572     xo_warn_hcv(xop, code, 0, fmt, vap);
1573     va_end(vap);
1574 }
1575 
1576 void
xo_warn_c(int code,const char * fmt,...)1577 xo_warn_c (int code, const char *fmt, ...)
1578 {
1579     va_list vap;
1580 
1581     va_start(vap, fmt);
1582     xo_warn_hcv(NULL, code, 0, fmt, vap);
1583     va_end(vap);
1584 }
1585 
1586 void
xo_warn(const char * fmt,...)1587 xo_warn (const char *fmt, ...)
1588 {
1589     int code = errno;
1590     va_list vap;
1591 
1592     va_start(vap, fmt);
1593     xo_warn_hcv(NULL, code, 0, fmt, vap);
1594     va_end(vap);
1595 }
1596 
1597 void
xo_warnx(const char * fmt,...)1598 xo_warnx (const char *fmt, ...)
1599 {
1600     va_list vap;
1601 
1602     va_start(vap, fmt);
1603     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1604     va_end(vap);
1605 }
1606 
1607 void
xo_err(int eval,const char * fmt,...)1608 xo_err (int eval, const char *fmt, ...)
1609 {
1610     int code = errno;
1611     va_list vap;
1612 
1613     va_start(vap, fmt);
1614     xo_warn_hcv(NULL, code, 0, fmt, vap);
1615     va_end(vap);
1616     xo_finish();
1617     exit(eval);
1618 }
1619 
1620 void
xo_errx(int eval,const char * fmt,...)1621 xo_errx (int eval, const char *fmt, ...)
1622 {
1623     va_list vap;
1624 
1625     va_start(vap, fmt);
1626     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1627     va_end(vap);
1628     xo_finish();
1629     exit(eval);
1630 }
1631 
1632 void
xo_errc(int eval,int code,const char * fmt,...)1633 xo_errc (int eval, int code, const char *fmt, ...)
1634 {
1635     va_list vap;
1636 
1637     va_start(vap, fmt);
1638     xo_warn_hcv(NULL, code, 0, fmt, vap);
1639     va_end(vap);
1640     xo_finish();
1641     exit(eval);
1642 }
1643 
1644 /*
1645  * Generate a warning.  Normally, this is a text message written to
1646  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1647  * XMLified content on standard output.
1648  */
1649 void
xo_message_hcv(xo_handle_t * xop,int code,const char * fmt,va_list vap)1650 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1651 {
1652     static char msg_open[] = "<message>";
1653     static char msg_close[] = "</message>";
1654     xo_buffer_t *xbp;
1655     int rc;
1656     va_list va_local;
1657 
1658     xop = xo_default(xop);
1659 
1660     if (fmt == NULL || *fmt == '\0')
1661 	return;
1662 
1663     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1664 
1665     switch (xo_style(xop)) {
1666     case XO_STYLE_XML:
1667 	xbp = &xop->xo_data;
1668 	if (XOF_ISSET(xop, XOF_PRETTY))
1669 	    xo_buf_indent(xop, xop->xo_indent_by);
1670 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1671 
1672 	va_copy(va_local, vap);
1673 
1674 	int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1675 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1676 	if (rc >= left) {
1677 	    if (!xo_buf_has_room(xbp, rc)) {
1678 		va_end(va_local);
1679 		return;
1680 	    }
1681 
1682 	    va_end(vap);	/* Reset vap to the start */
1683 	    va_copy(vap, va_local);
1684 
1685 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1686 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1687 	}
1688 	va_end(va_local);
1689 
1690 	rc = xo_escape_xml(xbp, rc, 0);
1691 	xbp->xb_curp += rc;
1692 
1693 	if (need_nl && code > 0) {
1694 	    const char *msg = strerror(code);
1695 	    if (msg) {
1696 		xo_buf_append(xbp, ": ", 2);
1697 		xo_buf_append(xbp, msg, strlen(msg));
1698 	    }
1699 	}
1700 
1701 	if (need_nl)
1702 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1703 
1704 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1705 
1706 	if (XOF_ISSET(xop, XOF_PRETTY))
1707 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1708 
1709 	(void) xo_write(xop);
1710 	break;
1711 
1712     case XO_STYLE_HTML:
1713 	{
1714 	    char buf[BUFSIZ], *bp = buf, *cp;
1715 	    int bufsiz = sizeof(buf);
1716 	    int rc2;
1717 
1718 	    va_copy(va_local, vap);
1719 
1720 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
1721 	    if (rc > bufsiz) {
1722 		bufsiz = rc + BUFSIZ;
1723 		bp = alloca(bufsiz);
1724 		va_end(va_local);
1725 		va_copy(va_local, vap);
1726 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
1727 	    }
1728 	    va_end(va_local);
1729 	    cp = bp + rc;
1730 
1731 	    if (need_nl) {
1732 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1733 			       (code > 0) ? ": " : "",
1734 			       (code > 0) ? strerror(code) : "");
1735 		if (rc2 > 0)
1736 		    rc += rc2;
1737 	    }
1738 
1739 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
1740 	}
1741 	break;
1742 
1743     case XO_STYLE_JSON:
1744     case XO_STYLE_SDPARAMS:
1745     case XO_STYLE_ENCODER:
1746 	/* No means of representing messages */
1747 	return;
1748 
1749     case XO_STYLE_TEXT:
1750 	rc = xo_printf_v(xop, fmt, vap);
1751 	/*
1752 	 * XXX need to handle UTF-8 widths
1753 	 */
1754 	if (rc > 0) {
1755 	    if (XOF_ISSET(xop, XOF_COLUMNS))
1756 		xop->xo_columns += rc;
1757 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
1758 		xop->xo_anchor_columns += rc;
1759 	}
1760 
1761 	if (need_nl && code > 0) {
1762 	    const char *msg = strerror(code);
1763 	    if (msg) {
1764 		xo_printf(xop, ": %s", msg);
1765 	    }
1766 	}
1767 	if (need_nl)
1768 	    xo_printf(xop, "\n");
1769 
1770 	break;
1771     }
1772 
1773     switch (xo_style(xop)) {
1774     case XO_STYLE_HTML:
1775 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
1776 	    static char div_close[] = "</div>";
1777 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
1778 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
1779 
1780 	    if (XOF_ISSET(xop, XOF_PRETTY))
1781 		xo_data_append(xop, "\n", 1);
1782 	}
1783 	break;
1784     }
1785 
1786     (void) xo_flush_h(xop);
1787 }
1788 
1789 void
xo_message_hc(xo_handle_t * xop,int code,const char * fmt,...)1790 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1791 {
1792     va_list vap;
1793 
1794     va_start(vap, fmt);
1795     xo_message_hcv(xop, code, fmt, vap);
1796     va_end(vap);
1797 }
1798 
1799 void
xo_message_c(int code,const char * fmt,...)1800 xo_message_c (int code, const char *fmt, ...)
1801 {
1802     va_list vap;
1803 
1804     va_start(vap, fmt);
1805     xo_message_hcv(NULL, code, fmt, vap);
1806     va_end(vap);
1807 }
1808 
1809 void
xo_message_e(const char * fmt,...)1810 xo_message_e (const char *fmt, ...)
1811 {
1812     int code = errno;
1813     va_list vap;
1814 
1815     va_start(vap, fmt);
1816     xo_message_hcv(NULL, code, fmt, vap);
1817     va_end(vap);
1818 }
1819 
1820 void
xo_message(const char * fmt,...)1821 xo_message (const char *fmt, ...)
1822 {
1823     va_list vap;
1824 
1825     va_start(vap, fmt);
1826     xo_message_hcv(NULL, 0, fmt, vap);
1827     va_end(vap);
1828 }
1829 
1830 static void
xo_failure(xo_handle_t * xop,const char * fmt,...)1831 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1832 {
1833     if (!XOF_ISSET(xop, XOF_WARN))
1834 	return;
1835 
1836     va_list vap;
1837 
1838     va_start(vap, fmt);
1839     xo_warn_hcv(xop, -1, 1, fmt, vap);
1840     va_end(vap);
1841 }
1842 
1843 /**
1844  * Create a handle for use by later libxo functions.
1845  *
1846  * Note: normal use of libxo does not require a distinct handle, since
1847  * the default handle (used when NULL is passed) generates text on stdout.
1848  *
1849  * @style Style of output desired (XO_STYLE_* value)
1850  * @flags Set of XOF_* flags in use with this handle
1851  */
1852 xo_handle_t *
xo_create(xo_style_t style,xo_xof_flags_t flags)1853 xo_create (xo_style_t style, xo_xof_flags_t flags)
1854 {
1855     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1856 
1857     if (xop) {
1858 	bzero(xop, sizeof(*xop));
1859 
1860 	xop->xo_style = style;
1861 	XOF_SET(xop, flags);
1862 	xo_init_handle(xop);
1863 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
1864     }
1865 
1866     return xop;
1867 }
1868 
1869 /**
1870  * Create a handle that will write to the given file.  Use
1871  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1872  * @fp FILE pointer to use
1873  * @style Style of output desired (XO_STYLE_* value)
1874  * @flags Set of XOF_* flags to use with this handle
1875  */
1876 xo_handle_t *
xo_create_to_file(FILE * fp,xo_style_t style,xo_xof_flags_t flags)1877 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1878 {
1879     xo_handle_t *xop = xo_create(style, flags);
1880 
1881     if (xop) {
1882 	xop->xo_opaque = fp;
1883 	xop->xo_write = xo_write_to_file;
1884 	xop->xo_close = xo_close_file;
1885 	xop->xo_flush = xo_flush_file;
1886     }
1887 
1888     return xop;
1889 }
1890 
1891 /**
1892  * Set the default handler to output to a file.
1893  * @xop libxo handle
1894  * @fp FILE pointer to use
1895  */
1896 int
xo_set_file_h(xo_handle_t * xop,FILE * fp)1897 xo_set_file_h (xo_handle_t *xop, FILE *fp)
1898 {
1899     xop = xo_default(xop);
1900 
1901     if (fp == NULL) {
1902 	xo_failure(xop, "xo_set_file: NULL fp");
1903 	return -1;
1904     }
1905 
1906     xop->xo_opaque = fp;
1907     xop->xo_write = xo_write_to_file;
1908     xop->xo_close = xo_close_file;
1909     xop->xo_flush = xo_flush_file;
1910 
1911     return 0;
1912 }
1913 
1914 /**
1915  * Set the default handler to output to a file.
1916  * @fp FILE pointer to use
1917  */
1918 int
xo_set_file(FILE * fp)1919 xo_set_file (FILE *fp)
1920 {
1921     return xo_set_file_h(NULL, fp);
1922 }
1923 
1924 /**
1925  * Release any resources held by the handle.
1926  * @xop XO handle to alter (or NULL for default handle)
1927  */
1928 void
xo_destroy(xo_handle_t * xop_arg)1929 xo_destroy (xo_handle_t *xop_arg)
1930 {
1931     xo_handle_t *xop = xo_default(xop_arg);
1932 
1933     xo_flush_h(xop);
1934 
1935     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
1936 	xop->xo_close(xop->xo_opaque);
1937 
1938     xo_free(xop->xo_stack);
1939     xo_buf_cleanup(&xop->xo_data);
1940     xo_buf_cleanup(&xop->xo_fmt);
1941     xo_buf_cleanup(&xop->xo_predicate);
1942     xo_buf_cleanup(&xop->xo_attrs);
1943     xo_buf_cleanup(&xop->xo_color_buf);
1944 
1945     if (xop->xo_version)
1946 	xo_free(xop->xo_version);
1947 
1948     if (xop_arg == NULL) {
1949 	bzero(&xo_default_handle, sizeof(xo_default_handle));
1950 	xo_default_inited = 0;
1951     } else
1952 	xo_free(xop);
1953 }
1954 
1955 /**
1956  * Record a new output style to use for the given handle (or default if
1957  * handle is NULL).  This output style will be used for any future output.
1958  *
1959  * @xop XO handle to alter (or NULL for default handle)
1960  * @style new output style (XO_STYLE_*)
1961  */
1962 void
xo_set_style(xo_handle_t * xop,xo_style_t style)1963 xo_set_style (xo_handle_t *xop, xo_style_t style)
1964 {
1965     xop = xo_default(xop);
1966     xop->xo_style = style;
1967 }
1968 
1969 xo_style_t
xo_get_style(xo_handle_t * xop)1970 xo_get_style (xo_handle_t *xop)
1971 {
1972     xop = xo_default(xop);
1973     return xo_style(xop);
1974 }
1975 
1976 static int
xo_name_to_style(const char * name)1977 xo_name_to_style (const char *name)
1978 {
1979     if (strcmp(name, "xml") == 0)
1980 	return XO_STYLE_XML;
1981     else if (strcmp(name, "json") == 0)
1982 	return XO_STYLE_JSON;
1983     else if (strcmp(name, "encoder") == 0)
1984 	return XO_STYLE_ENCODER;
1985     else if (strcmp(name, "text") == 0)
1986 	return XO_STYLE_TEXT;
1987     else if (strcmp(name, "html") == 0)
1988 	return XO_STYLE_HTML;
1989     else if (strcmp(name, "sdparams") == 0)
1990 	return XO_STYLE_SDPARAMS;
1991 
1992     return -1;
1993 }
1994 
1995 /*
1996  * Indicate if the style is an "encoding" one as opposed to a "display" one.
1997  */
1998 static int
xo_style_is_encoding(xo_handle_t * xop)1999 xo_style_is_encoding (xo_handle_t *xop)
2000 {
2001     if (xo_style(xop) == XO_STYLE_JSON
2002 	|| xo_style(xop) == XO_STYLE_XML
2003 	|| xo_style(xop) == XO_STYLE_SDPARAMS
2004 	|| xo_style(xop) == XO_STYLE_ENCODER)
2005 	return 1;
2006     return 0;
2007 }
2008 
2009 /* Simple name-value mapping */
2010 typedef struct xo_mapping_s {
2011     xo_xff_flags_t xm_value;
2012     const char *xm_name;
2013 } xo_mapping_t;
2014 
2015 static xo_xff_flags_t
xo_name_lookup(xo_mapping_t * map,const char * value,int len)2016 xo_name_lookup (xo_mapping_t *map, const char *value, int len)
2017 {
2018     if (len == 0)
2019 	return 0;
2020 
2021     if (len < 0)
2022 	len = strlen(value);
2023 
2024     while (isspace((int) *value)) {
2025 	value += 1;
2026 	len -= 1;
2027     }
2028 
2029     while (isspace((int) value[len]))
2030 	len -= 1;
2031 
2032     if (*value == '\0')
2033 	return 0;
2034 
2035     for ( ; map->xm_name; map++)
2036 	if (strncmp(map->xm_name, value, len) == 0)
2037 	    return map->xm_value;
2038 
2039     return 0;
2040 }
2041 
2042 #ifdef NOT_NEEDED_YET
2043 static const char *
xo_value_lookup(xo_mapping_t * map,xo_xff_flags_t value)2044 xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2045 {
2046     if (value == 0)
2047 	return NULL;
2048 
2049     for ( ; map->xm_name; map++)
2050 	if (map->xm_value == value)
2051 	    return map->xm_name;
2052 
2053     return NULL;
2054 }
2055 #endif /* NOT_NEEDED_YET */
2056 
2057 static xo_mapping_t xo_xof_names[] = {
2058     { XOF_COLOR_ALLOWED, "color" },
2059     { XOF_COLUMNS, "columns" },
2060     { XOF_DTRT, "dtrt" },
2061     { XOF_FLUSH, "flush" },
2062     { XOF_IGNORE_CLOSE, "ignore-close" },
2063     { XOF_INFO, "info" },
2064     { XOF_KEYS, "keys" },
2065     { XOF_LOG_GETTEXT, "log-gettext" },
2066     { XOF_LOG_SYSLOG, "log-syslog" },
2067     { XOF_NO_HUMANIZE, "no-humanize" },
2068     { XOF_NO_LOCALE, "no-locale" },
2069     { XOF_RETAIN_NONE, "no-retain" },
2070     { XOF_NO_TOP, "no-top" },
2071     { XOF_NOT_FIRST, "not-first" },
2072     { XOF_PRETTY, "pretty" },
2073     { XOF_RETAIN_ALL, "retain" },
2074     { XOF_UNDERSCORES, "underscores" },
2075     { XOF_UNITS, "units" },
2076     { XOF_WARN, "warn" },
2077     { XOF_WARN_XML, "warn-xml" },
2078     { XOF_XPATH, "xpath" },
2079     { 0, NULL }
2080 };
2081 
2082 /*
2083  * Convert string name to XOF_* flag value.
2084  * Not all are useful.  Or safe.  Or sane.
2085  */
2086 static unsigned
xo_name_to_flag(const char * name)2087 xo_name_to_flag (const char *name)
2088 {
2089     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
2090 }
2091 
2092 int
xo_set_style_name(xo_handle_t * xop,const char * name)2093 xo_set_style_name (xo_handle_t *xop, const char *name)
2094 {
2095     if (name == NULL)
2096 	return -1;
2097 
2098     int style = xo_name_to_style(name);
2099     if (style < 0)
2100 	return -1;
2101 
2102     xo_set_style(xop, style);
2103     return 0;
2104 }
2105 
2106 /*
2107  * Set the options for a handle using a string of options
2108  * passed in.  The input is a comma-separated set of names
2109  * and optional values: "xml,pretty,indent=4"
2110  */
2111 int
xo_set_options(xo_handle_t * xop,const char * input)2112 xo_set_options (xo_handle_t *xop, const char *input)
2113 {
2114     char *cp, *ep, *vp, *np, *bp;
2115     int style = -1, new_style, len, rc = 0;
2116     xo_xof_flags_t new_flag;
2117 
2118     if (input == NULL)
2119 	return 0;
2120 
2121     xop = xo_default(xop);
2122 
2123 #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2124     /* If the installer used --enable-color-on-by-default, then we allow it */
2125     XOF_SET(xop, XOF_COLOR_ALLOWED);
2126 #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2127 
2128     /*
2129      * We support a simpler, old-school style of giving option
2130      * also, using a single character for each option.  It's
2131      * ideal for lazy people, such as myself.
2132      */
2133     if (*input == ':') {
2134 	int sz;
2135 
2136 	for (input++ ; *input; input++) {
2137 	    switch (*input) {
2138 	    case 'c':
2139 		XOF_SET(xop, XOF_COLOR_ALLOWED);
2140 		break;
2141 
2142 	    case 'f':
2143 		XOF_SET(xop, XOF_FLUSH);
2144 		break;
2145 
2146 	    case 'F':
2147 		XOF_SET(xop, XOF_FLUSH_LINE);
2148 		break;
2149 
2150 	    case 'g':
2151 		XOF_SET(xop, XOF_LOG_GETTEXT);
2152 		break;
2153 
2154 	    case 'H':
2155 		xop->xo_style = XO_STYLE_HTML;
2156 		break;
2157 
2158 	    case 'I':
2159 		XOF_SET(xop, XOF_INFO);
2160 		break;
2161 
2162 	    case 'i':
2163 		sz = strspn(input + 1, "0123456789");
2164 		if (sz > 0) {
2165 		    xop->xo_indent_by = atoi(input + 1);
2166 		    input += sz - 1;	/* Skip value */
2167 		}
2168 		break;
2169 
2170 	    case 'J':
2171 		xop->xo_style = XO_STYLE_JSON;
2172 		break;
2173 
2174 	    case 'k':
2175 		XOF_SET(xop, XOF_KEYS);
2176 		break;
2177 
2178 	    case 'n':
2179 		XOF_SET(xop, XOF_NO_HUMANIZE);
2180 		break;
2181 
2182 	    case 'P':
2183 		XOF_SET(xop, XOF_PRETTY);
2184 		break;
2185 
2186 	    case 'T':
2187 		xop->xo_style = XO_STYLE_TEXT;
2188 		break;
2189 
2190 	    case 'U':
2191 		XOF_SET(xop, XOF_UNITS);
2192 		break;
2193 
2194 	    case 'u':
2195 		XOF_SET(xop, XOF_UNDERSCORES);
2196 		break;
2197 
2198 	    case 'W':
2199 		XOF_SET(xop, XOF_WARN);
2200 		break;
2201 
2202 	    case 'X':
2203 		xop->xo_style = XO_STYLE_XML;
2204 		break;
2205 
2206 	    case 'x':
2207 		XOF_SET(xop, XOF_XPATH);
2208 		break;
2209 	    }
2210 	}
2211 	return 0;
2212     }
2213 
2214     len = strlen(input) + 1;
2215     bp = alloca(len);
2216     memcpy(bp, input, len);
2217 
2218     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2219 	np = strchr(cp, ',');
2220 	if (np)
2221 	    *np++ = '\0';
2222 
2223 	vp = strchr(cp, '=');
2224 	if (vp)
2225 	    *vp++ = '\0';
2226 
2227 	if (strcmp("colors", cp) == 0) {
2228 	    /* XXX Look for colors=red-blue+green-yellow */
2229 	    continue;
2230 	}
2231 
2232 	/*
2233 	 * For options, we don't allow "encoder" since we want to
2234 	 * handle it explicitly below as "encoder=xxx".
2235 	 */
2236 	new_style = xo_name_to_style(cp);
2237 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
2238 	    if (style >= 0)
2239 		xo_warnx("ignoring multiple styles: '%s'", cp);
2240 	    else
2241 		style = new_style;
2242 	} else {
2243 	    new_flag = xo_name_to_flag(cp);
2244 	    if (new_flag != 0)
2245 		XOF_SET(xop, new_flag);
2246 	    else {
2247 		if (strcmp(cp, "no-color") == 0) {
2248 		    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2249 		} else if (strcmp(cp, "indent") == 0) {
2250 		    if (vp)
2251 			xop->xo_indent_by = atoi(vp);
2252 		    else
2253 			xo_failure(xop, "missing value for indent option");
2254 		} else if (strcmp(cp, "encoder") == 0) {
2255 		    if (vp == NULL)
2256 			xo_failure(xop, "missing value for encoder option");
2257 		    else {
2258 			if (xo_encoder_init(xop, vp)) {
2259 			    xo_failure(xop, "encoder not found: %s", vp);
2260 			    rc = -1;
2261 			}
2262 		    }
2263 
2264 		} else {
2265 		    xo_warnx("unknown libxo option value: '%s'", cp);
2266 		    rc = -1;
2267 		}
2268 	    }
2269 	}
2270     }
2271 
2272     if (style > 0)
2273 	xop->xo_style= style;
2274 
2275     return rc;
2276 }
2277 
2278 /**
2279  * Set one or more flags for a given handle (or default if handle is NULL).
2280  * These flags will affect future output.
2281  *
2282  * @xop XO handle to alter (or NULL for default handle)
2283  * @flags Flags to be set (XOF_*)
2284  */
2285 void
xo_set_flags(xo_handle_t * xop,xo_xof_flags_t flags)2286 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2287 {
2288     xop = xo_default(xop);
2289 
2290     XOF_SET(xop, flags);
2291 }
2292 
2293 xo_xof_flags_t
xo_get_flags(xo_handle_t * xop)2294 xo_get_flags (xo_handle_t *xop)
2295 {
2296     xop = xo_default(xop);
2297 
2298     return xop->xo_flags;
2299 }
2300 
2301 /*
2302  * strndup with a twist: len < 0 means strlen
2303  */
2304 static char *
xo_strndup(const char * str,int len)2305 xo_strndup (const char *str, int len)
2306 {
2307     if (len < 0)
2308 	len = strlen(str);
2309 
2310     char *cp = xo_realloc(NULL, len + 1);
2311     if (cp) {
2312 	memcpy(cp, str, len);
2313 	cp[len] = '\0';
2314     }
2315 
2316     return cp;
2317 }
2318 
2319 /**
2320  * Record a leading prefix for the XPath we generate.  This allows the
2321  * generated data to be placed within an XML hierarchy but still have
2322  * accurate XPath expressions.
2323  *
2324  * @xop XO handle to alter (or NULL for default handle)
2325  * @path The XPath expression
2326  */
2327 void
xo_set_leading_xpath(xo_handle_t * xop,const char * path)2328 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
2329 {
2330     xop = xo_default(xop);
2331 
2332     if (xop->xo_leading_xpath) {
2333 	xo_free(xop->xo_leading_xpath);
2334 	xop->xo_leading_xpath = NULL;
2335     }
2336 
2337     if (path == NULL)
2338 	return;
2339 
2340     xop->xo_leading_xpath = xo_strndup(path, -1);
2341 }
2342 
2343 /**
2344  * Record the info data for a set of tags
2345  *
2346  * @xop XO handle to alter (or NULL for default handle)
2347  * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
2348  * @count Number of entries in info (or -1 to count them ourselves)
2349  */
2350 void
xo_set_info(xo_handle_t * xop,xo_info_t * infop,int count)2351 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
2352 {
2353     xop = xo_default(xop);
2354 
2355     if (count < 0 && infop) {
2356 	xo_info_t *xip;
2357 
2358 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
2359 	    continue;
2360     }
2361 
2362     xop->xo_info = infop;
2363     xop->xo_info_count = count;
2364 }
2365 
2366 /**
2367  * Set the formatter callback for a handle.  The callback should
2368  * return a newly formatting contents of a formatting instruction,
2369  * meaning the bits inside the braces.
2370  */
2371 void
xo_set_formatter(xo_handle_t * xop,xo_formatter_t func,xo_checkpointer_t cfunc)2372 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
2373 		  xo_checkpointer_t cfunc)
2374 {
2375     xop = xo_default(xop);
2376 
2377     xop->xo_formatter = func;
2378     xop->xo_checkpointer = cfunc;
2379 }
2380 
2381 /**
2382  * Clear one or more flags for a given handle (or default if handle is NULL).
2383  * These flags will affect future output.
2384  *
2385  * @xop XO handle to alter (or NULL for default handle)
2386  * @flags Flags to be cleared (XOF_*)
2387  */
2388 void
xo_clear_flags(xo_handle_t * xop,xo_xof_flags_t flags)2389 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2390 {
2391     xop = xo_default(xop);
2392 
2393     XOF_CLEAR(xop, flags);
2394 }
2395 
2396 static const char *
xo_state_name(xo_state_t state)2397 xo_state_name (xo_state_t state)
2398 {
2399     static const char *names[] = {
2400 	"init",
2401 	"open_container",
2402 	"close_container",
2403 	"open_list",
2404 	"close_list",
2405 	"open_instance",
2406 	"close_instance",
2407 	"open_leaf_list",
2408 	"close_leaf_list",
2409 	"discarding",
2410 	"marker",
2411 	"emit",
2412 	"emit_leaf_list",
2413 	"finish",
2414 	NULL
2415     };
2416 
2417     if (state < (sizeof(names) / sizeof(names[0])))
2418 	return names[state];
2419 
2420     return "unknown";
2421 }
2422 
2423 static void
xo_line_ensure_open(xo_handle_t * xop,xo_xff_flags_t flags UNUSED)2424 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
2425 {
2426     static char div_open[] = "<div class=\"line\">";
2427     static char div_open_blank[] = "<div class=\"blank-line\">";
2428 
2429     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
2430 	return;
2431 
2432     if (xo_style(xop) != XO_STYLE_HTML)
2433 	return;
2434 
2435     XOIF_SET(xop, XOIF_DIV_OPEN);
2436     if (flags & XFF_BLANK_LINE)
2437 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
2438     else
2439 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
2440 
2441     if (XOF_ISSET(xop, XOF_PRETTY))
2442 	xo_data_append(xop, "\n", 1);
2443 }
2444 
2445 static void
xo_line_close(xo_handle_t * xop)2446 xo_line_close (xo_handle_t *xop)
2447 {
2448     static char div_close[] = "</div>";
2449 
2450     switch (xo_style(xop)) {
2451     case XO_STYLE_HTML:
2452 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
2453 	    xo_line_ensure_open(xop, 0);
2454 
2455 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
2456 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
2457 
2458 	if (XOF_ISSET(xop, XOF_PRETTY))
2459 	    xo_data_append(xop, "\n", 1);
2460 	break;
2461 
2462     case XO_STYLE_TEXT:
2463 	xo_data_append(xop, "\n", 1);
2464 	break;
2465     }
2466 }
2467 
2468 static int
xo_info_compare(const void * key,const void * data)2469 xo_info_compare (const void *key, const void *data)
2470 {
2471     const char *name = key;
2472     const xo_info_t *xip = data;
2473 
2474     return strcmp(name, xip->xi_name);
2475 }
2476 
2477 
2478 static xo_info_t *
xo_info_find(xo_handle_t * xop,const char * name,int nlen)2479 xo_info_find (xo_handle_t *xop, const char *name, int nlen)
2480 {
2481     xo_info_t *xip;
2482     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
2483 
2484     memcpy(cp, name, nlen);
2485     cp[nlen] = '\0';
2486 
2487     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
2488 		  sizeof(xop->xo_info[0]), xo_info_compare);
2489     return xip;
2490 }
2491 
2492 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
2493 
2494 /*
2495  * Check to see that the conversion is safe and sane.
2496  */
2497 static int
xo_check_conversion(xo_handle_t * xop,int have_enc,int need_enc)2498 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
2499 {
2500     switch (CONVERT(have_enc, need_enc)) {
2501     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
2502     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
2503     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
2504     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
2505     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
2506     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
2507 	return 0;
2508 
2509     default:
2510 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
2511 	return 1;
2512     }
2513 }
2514 
2515 static int
xo_format_string_direct(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,const wchar_t * wcp,const char * cp,int len,int max,int need_enc,int have_enc)2516 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
2517 			 xo_xff_flags_t flags,
2518 			 const wchar_t *wcp, const char *cp, int len, int max,
2519 			 int need_enc, int have_enc)
2520 {
2521     int cols = 0;
2522     wchar_t wc = 0;
2523     int ilen, olen, width;
2524     int attr = (flags & XFF_ATTR);
2525     const char *sp;
2526 
2527     if (len > 0 && !xo_buf_has_room(xbp, len))
2528 	return 0;
2529 
2530     for (;;) {
2531 	if (len == 0)
2532 	    break;
2533 
2534 	if (cp) {
2535 	    if (*cp == '\0')
2536 		break;
2537 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
2538 		cp += 1;
2539 		len -= 1;
2540 	    }
2541 	}
2542 
2543 	if (wcp && *wcp == L'\0')
2544 	    break;
2545 
2546 	ilen = 0;
2547 
2548 	switch (have_enc) {
2549 	case XF_ENC_WIDE:		/* Wide character */
2550 	    wc = *wcp++;
2551 	    ilen = 1;
2552 	    break;
2553 
2554 	case XF_ENC_UTF8:		/* UTF-8 */
2555 	    ilen = xo_utf8_to_wc_len(cp);
2556 	    if (ilen < 0) {
2557 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2558 		return -1;	/* Can't continue; we can't find the end */
2559 	    }
2560 
2561 	    if (len > 0 && len < ilen) {
2562 		len = 0;	/* Break out of the loop */
2563 		continue;
2564 	    }
2565 
2566 	    wc = xo_utf8_char(cp, ilen);
2567 	    if (wc == (wchar_t) -1) {
2568 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
2569 			   *cp, ilen);
2570 		return -1;	/* Can't continue; we can't find the end */
2571 	    }
2572 	    cp += ilen;
2573 	    break;
2574 
2575 	case XF_ENC_LOCALE:		/* Native locale */
2576 	    ilen = (len > 0) ? len : MB_LEN_MAX;
2577 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
2578 	    if (ilen < 0) {		/* Invalid data; skip */
2579 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2580 		wc = L'?';
2581 		ilen = 1;
2582 	    }
2583 
2584 	    if (ilen == 0) {		/* Hit a wide NUL character */
2585 		len = 0;
2586 		continue;
2587 	    }
2588 
2589 	    cp += ilen;
2590 	    break;
2591 	}
2592 
2593 	/* Reduce len, but not below zero */
2594 	if (len > 0) {
2595 	    len -= ilen;
2596 	    if (len < 0)
2597 		len = 0;
2598 	}
2599 
2600 	/*
2601 	 * Find the width-in-columns of this character, which must be done
2602 	 * in wide characters, since we lack a mbswidth() function.  If
2603 	 * it doesn't fit
2604 	 */
2605 	width = xo_wcwidth(wc);
2606 	if (width < 0)
2607 	    width = iswcntrl(wc) ? 0 : 1;
2608 
2609 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
2610 	    if (max > 0 && cols + width > max)
2611 		break;
2612 	}
2613 
2614 	switch (need_enc) {
2615 	case XF_ENC_UTF8:
2616 
2617 	    /* Output in UTF-8 needs to be escaped, based on the style */
2618 	    switch (xo_style(xop)) {
2619 	    case XO_STYLE_XML:
2620 	    case XO_STYLE_HTML:
2621 		if (wc == '<')
2622 		    sp = xo_xml_lt;
2623 		else if (wc == '>')
2624 		    sp = xo_xml_gt;
2625 		else if (wc == '&')
2626 		    sp = xo_xml_amp;
2627 		else if (attr && wc == '"')
2628 		    sp = xo_xml_quot;
2629 		else
2630 		    break;
2631 
2632 		int slen = strlen(sp);
2633 		if (!xo_buf_has_room(xbp, slen - 1))
2634 		    return -1;
2635 
2636 		memcpy(xbp->xb_curp, sp, slen);
2637 		xbp->xb_curp += slen;
2638 		goto done_with_encoding; /* Need multi-level 'break' */
2639 
2640 	    case XO_STYLE_JSON:
2641 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
2642 		    break;
2643 
2644 		if (!xo_buf_has_room(xbp, 2))
2645 		    return -1;
2646 
2647 		*xbp->xb_curp++ = '\\';
2648 		if (wc == '\n')
2649 		    wc = 'n';
2650 		else if (wc == '\r')
2651 		    wc = 'r';
2652 		else wc = wc & 0x7f;
2653 
2654 		*xbp->xb_curp++ = wc;
2655 		goto done_with_encoding;
2656 
2657 	    case XO_STYLE_SDPARAMS:
2658 		if (wc != '\\' && wc != '"' && wc != ']')
2659 		    break;
2660 
2661 		if (!xo_buf_has_room(xbp, 2))
2662 		    return -1;
2663 
2664 		*xbp->xb_curp++ = '\\';
2665 		wc = wc & 0x7f;
2666 		*xbp->xb_curp++ = wc;
2667 		goto done_with_encoding;
2668 	    }
2669 
2670 	    olen = xo_utf8_emit_len(wc);
2671 	    if (olen < 0) {
2672 		xo_failure(xop, "ignoring bad length");
2673 		continue;
2674 	    }
2675 
2676 	    if (!xo_buf_has_room(xbp, olen))
2677 		return -1;
2678 
2679 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
2680 	    xbp->xb_curp += olen;
2681 	    break;
2682 
2683 	case XF_ENC_LOCALE:
2684 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
2685 		return -1;
2686 
2687 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
2688 	    if (olen <= 0) {
2689 		xo_failure(xop, "could not convert wide char: %lx",
2690 			   (unsigned long) wc);
2691 		width = 1;
2692 		*xbp->xb_curp++ = '?';
2693 	    } else
2694 		xbp->xb_curp += olen;
2695 	    break;
2696 	}
2697 
2698     done_with_encoding:
2699 	cols += width;
2700     }
2701 
2702     return cols;
2703 }
2704 
2705 static int
xo_needed_encoding(xo_handle_t * xop)2706 xo_needed_encoding (xo_handle_t *xop)
2707 {
2708     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2709 	return XF_ENC_UTF8;
2710 
2711     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2712 	return XF_ENC_LOCALE;
2713 
2714     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2715 }
2716 
2717 static int
xo_format_string(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,xo_format_t * xfp)2718 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
2719 		  xo_format_t *xfp)
2720 {
2721     static char null[] = "(null)";
2722     static char null_no_quotes[] = "null";
2723 
2724     char *cp = NULL;
2725     wchar_t *wcp = NULL;
2726     int len, cols = 0, rc = 0;
2727     int off = xbp->xb_curp - xbp->xb_bufp, off2;
2728     int need_enc = xo_needed_encoding(xop);
2729 
2730     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
2731 	return 0;
2732 
2733     len = xfp->xf_width[XF_WIDTH_SIZE];
2734 
2735     if (xfp->xf_fc == 'm') {
2736 	cp = strerror(xop->xo_errno);
2737 	if (len < 0)
2738 	    len = cp ? strlen(cp) : 0;
2739 	goto normal_string;
2740 
2741     } else if (xfp->xf_enc == XF_ENC_WIDE) {
2742 	wcp = va_arg(xop->xo_vap, wchar_t *);
2743 	if (xfp->xf_skip)
2744 	    return 0;
2745 
2746 	/*
2747 	 * Dont' deref NULL; use the traditional "(null)" instead
2748 	 * of the more accurate "who's been a naughty boy, then?".
2749 	 */
2750 	if (wcp == NULL) {
2751 	    cp = null;
2752 	    len = sizeof(null) - 1;
2753 	}
2754 
2755     } else {
2756 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2757 
2758     normal_string:
2759 	if (xfp->xf_skip)
2760 	    return 0;
2761 
2762 	/* Echo "Dont' deref NULL" logic */
2763 	if (cp == NULL) {
2764 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2765 		cp = null_no_quotes;
2766 		len = sizeof(null_no_quotes) - 1;
2767 	    } else {
2768 		cp = null;
2769 		len = sizeof(null) - 1;
2770 	    }
2771 	}
2772 
2773 	/*
2774 	 * Optimize the most common case, which is "%s".  We just
2775 	 * need to copy the complete string to the output buffer.
2776 	 */
2777 	if (xfp->xf_enc == need_enc
2778 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
2779 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
2780 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2781 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2782 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
2783 	    len = strlen(cp);
2784 	    xo_buf_escape(xop, xbp, cp, len, flags);
2785 
2786 	    /*
2787 	     * Our caller expects xb_curp left untouched, so we have
2788 	     * to reset it and return the number of bytes written to
2789 	     * the buffer.
2790 	     */
2791 	    off2 = xbp->xb_curp - xbp->xb_bufp;
2792 	    rc = off2 - off;
2793 	    xbp->xb_curp = xbp->xb_bufp + off;
2794 
2795 	    return rc;
2796 	}
2797     }
2798 
2799     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
2800 				   xfp->xf_width[XF_WIDTH_MAX],
2801 				   need_enc, xfp->xf_enc);
2802     if (cols < 0)
2803 	goto bail;
2804 
2805     /*
2806      * xo_buf_append* will move xb_curp, so we save/restore it.
2807      */
2808     off2 = xbp->xb_curp - xbp->xb_bufp;
2809     rc = off2 - off;
2810     xbp->xb_curp = xbp->xb_bufp + off;
2811 
2812     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
2813 	/*
2814 	 * Find the number of columns needed to display the string.
2815 	 * If we have the original wide string, we just call wcswidth,
2816 	 * but if we did the work ourselves, then we need to do it.
2817 	 */
2818 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
2819 	if (!xo_buf_has_room(xbp, delta))
2820 	    goto bail;
2821 
2822 	/*
2823 	 * If seen_minus, then pad on the right; otherwise move it so
2824 	 * we can pad on the left.
2825 	 */
2826 	if (xfp->xf_seen_minus) {
2827 	    cp = xbp->xb_curp + rc;
2828 	} else {
2829 	    cp = xbp->xb_curp;
2830 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
2831 	}
2832 
2833 	/* Set the padding */
2834 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
2835 	rc += delta;
2836 	cols += delta;
2837     }
2838 
2839     if (XOF_ISSET(xop, XOF_COLUMNS))
2840 	xop->xo_columns += cols;
2841     if (XOIF_ISSET(xop, XOIF_ANCHOR))
2842 	xop->xo_anchor_columns += cols;
2843 
2844     return rc;
2845 
2846  bail:
2847     xbp->xb_curp = xbp->xb_bufp + off;
2848     return 0;
2849 }
2850 
2851 /*
2852  * Look backwards in a buffer to find a numeric value
2853  */
2854 static int
xo_buf_find_last_number(xo_buffer_t * xbp,int start_offset)2855 xo_buf_find_last_number (xo_buffer_t *xbp, int start_offset)
2856 {
2857     int rc = 0;			/* Fail with zero */
2858     int digit = 1;
2859     char *sp = xbp->xb_bufp;
2860     char *cp = sp + start_offset;
2861 
2862     while (--cp >= sp)
2863 	if (isdigit((int) *cp))
2864 	    break;
2865 
2866     for ( ; cp >= sp; cp--) {
2867 	if (!isdigit((int) *cp))
2868 	    break;
2869 	rc += (*cp - '0') * digit;
2870 	digit *= 10;
2871     }
2872 
2873     return rc;
2874 }
2875 
2876 static int
xo_count_utf8_cols(const char * str,int len)2877 xo_count_utf8_cols (const char *str, int len)
2878 {
2879     int tlen;
2880     wchar_t wc;
2881     int cols = 0;
2882     const char *ep = str + len;
2883 
2884     while (str < ep) {
2885 	tlen = xo_utf8_to_wc_len(str);
2886 	if (tlen < 0)		/* Broken input is very bad */
2887 	    return cols;
2888 
2889 	wc = xo_utf8_char(str, tlen);
2890 	if (wc == (wchar_t) -1)
2891 	    return cols;
2892 
2893 	/* We only print printable characters */
2894 	if (iswprint((wint_t) wc)) {
2895 	    /*
2896 	     * Find the width-in-columns of this character, which must be done
2897 	     * in wide characters, since we lack a mbswidth() function.
2898 	     */
2899 	    int width = xo_wcwidth(wc);
2900 	    if (width < 0)
2901 		width = iswcntrl(wc) ? 0 : 1;
2902 
2903 	    cols += width;
2904 	}
2905 
2906 	str += tlen;
2907     }
2908 
2909     return cols;
2910 }
2911 
2912 #ifdef HAVE_GETTEXT
2913 static inline const char *
xo_dgettext(xo_handle_t * xop,const char * str)2914 xo_dgettext (xo_handle_t *xop, const char *str)
2915 {
2916     const char *domainname = xop->xo_gt_domain;
2917     const char *res;
2918 
2919     res = dgettext(domainname, str);
2920 
2921     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2922 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
2923 		domainname ? "domain \"" : "", xo_printable(domainname),
2924 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
2925 
2926     return res;
2927 }
2928 
2929 static inline const char *
xo_dngettext(xo_handle_t * xop,const char * sing,const char * plural,unsigned long int n)2930 xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
2931 	      unsigned long int n)
2932 {
2933     const char *domainname = xop->xo_gt_domain;
2934     const char *res;
2935 
2936     res = dngettext(domainname, sing, plural, n);
2937     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
2938 	fprintf(stderr, "xo: gettext: %s%s%s"
2939 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
2940 		domainname ? "domain \"" : "",
2941 		xo_printable(domainname), domainname ? "\", " : "",
2942 		xo_printable(sing),
2943 		xo_printable(plural), n, xo_printable(res));
2944 
2945     return res;
2946 }
2947 #else /* HAVE_GETTEXT */
2948 static inline const char *
xo_dgettext(xo_handle_t * xop UNUSED,const char * str)2949 xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
2950 {
2951     return str;
2952 }
2953 
2954 static inline const char *
xo_dngettext(xo_handle_t * xop UNUSED,const char * singular,const char * plural,unsigned long int n)2955 xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
2956 	      const char *plural, unsigned long int n)
2957 {
2958     return (n == 1) ? singular : plural;
2959 }
2960 #endif /* HAVE_GETTEXT */
2961 
2962 /*
2963  * This is really _re_formatting, since the normal format code has
2964  * generated a beautiful string into xo_data, starting at
2965  * start_offset.  We need to see if it's plural, which means
2966  * comma-separated options, or singular.  Then we make the appropriate
2967  * call to d[n]gettext() to get the locale-based version.  Note that
2968  * both input and output of gettext() this should be UTF-8.
2969  */
2970 static int
xo_format_gettext(xo_handle_t * xop,xo_xff_flags_t flags,int start_offset,int cols,int need_enc)2971 xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
2972 		   int start_offset, int cols, int need_enc)
2973 {
2974     xo_buffer_t *xbp = &xop->xo_data;
2975 
2976     if (!xo_buf_has_room(xbp, 1))
2977 	return cols;
2978 
2979     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
2980 
2981     char *cp = xbp->xb_bufp + start_offset;
2982     int len = xbp->xb_curp - cp;
2983     const char *newstr = NULL;
2984 
2985     /*
2986      * The plural flag asks us to look backwards at the last numeric
2987      * value rendered and disect the string into two pieces.
2988      */
2989     if (flags & XFF_GT_PLURAL) {
2990 	int n = xo_buf_find_last_number(xbp, start_offset);
2991 	char *two = memchr(cp, (int) ',', len);
2992 	if (two == NULL) {
2993 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
2994 	    return cols;
2995 	}
2996 
2997 	if (two == cp) {
2998 	    xo_failure(xop, "nothing before comma in plural gettext "
2999 		       "field: '%s'", cp);
3000 	    return cols;
3001 	}
3002 
3003 	if (two == xbp->xb_curp) {
3004 	    xo_failure(xop, "nothing after comma in plural gettext "
3005 		       "field: '%s'", cp);
3006 	    return cols;
3007 	}
3008 
3009 	*two++ = '\0';
3010 	if (flags & XFF_GT_FIELD) {
3011 	    newstr = xo_dngettext(xop, cp, two, n);
3012 	} else {
3013 	    /* Don't do a gettext() look up, just get the plural form */
3014 	    newstr = (n == 1) ? cp : two;
3015 	}
3016 
3017 	/*
3018 	 * If we returned the first string, optimize a bit by
3019 	 * backing up over comma
3020 	 */
3021 	if (newstr == cp) {
3022 	    xbp->xb_curp = two - 1; /* One for comma */
3023 	    /*
3024 	     * If the caller wanted UTF8, we're done; nothing changed,
3025 	     * but we need to count the columns used.
3026 	     */
3027 	    if (need_enc == XF_ENC_UTF8)
3028 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3029 	}
3030 
3031     } else {
3032 	/* The simple case (singular) */
3033 	newstr = xo_dgettext(xop, cp);
3034 
3035 	if (newstr == cp) {
3036 	    /* If the caller wanted UTF8, we're done; nothing changed */
3037 	    if (need_enc == XF_ENC_UTF8)
3038 		return cols;
3039 	}
3040     }
3041 
3042     /*
3043      * Since the new string string might be in gettext's buffer or
3044      * in the buffer (as the plural form), we make a copy.
3045      */
3046     int nlen = strlen(newstr);
3047     char *newcopy = alloca(nlen + 1);
3048     memcpy(newcopy, newstr, nlen + 1);
3049 
3050     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3051     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3052 				   need_enc, XF_ENC_UTF8);
3053 }
3054 
3055 static void
xo_data_append_content(xo_handle_t * xop,const char * str,int len,xo_xff_flags_t flags)3056 xo_data_append_content (xo_handle_t *xop, const char *str, int len,
3057 			xo_xff_flags_t flags)
3058 {
3059     int cols;
3060     int need_enc = xo_needed_encoding(xop);
3061     int start_offset = xo_buf_offset(&xop->xo_data);
3062 
3063     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
3064 				   NULL, str, len, -1,
3065 				   need_enc, XF_ENC_UTF8);
3066     if (flags & XFF_GT_FLAGS)
3067 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
3068 
3069     if (XOF_ISSET(xop, XOF_COLUMNS))
3070 	xop->xo_columns += cols;
3071     if (XOIF_ISSET(xop, XOIF_ANCHOR))
3072 	xop->xo_anchor_columns += cols;
3073 }
3074 
3075 static void
xo_bump_width(xo_format_t * xfp,int digit)3076 xo_bump_width (xo_format_t *xfp, int digit)
3077 {
3078     int *ip = &xfp->xf_width[xfp->xf_dots];
3079 
3080     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
3081 }
3082 
3083 static int
xo_trim_ws(xo_buffer_t * xbp,int len)3084 xo_trim_ws (xo_buffer_t *xbp, int len)
3085 {
3086     char *cp, *sp, *ep;
3087     int delta;
3088 
3089     /* First trim leading space */
3090     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
3091 	if (*cp != ' ')
3092 	    break;
3093     }
3094 
3095     delta = cp - sp;
3096     if (delta) {
3097 	len -= delta;
3098 	memmove(sp, cp, len);
3099     }
3100 
3101     /* Then trim off the end */
3102     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
3103 	if (ep[-1] != ' ')
3104 	    break;
3105     }
3106 
3107     delta = sp - ep;
3108     if (delta) {
3109 	len -= delta;
3110 	cp[len] = '\0';
3111     }
3112 
3113     return len;
3114 }
3115 
3116 /*
3117  * Interface to format a single field.  The arguments are in xo_vap,
3118  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
3119  * this is the most common case.
3120  */
3121 static int
xo_do_format_field(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,int flen,xo_xff_flags_t flags)3122 xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
3123 		const char *fmt, int flen, xo_xff_flags_t flags)
3124 {
3125     xo_format_t xf;
3126     const char *cp, *ep, *sp, *xp = NULL;
3127     int rc, cols;
3128     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
3129     unsigned make_output = !(flags & XFF_NO_OUTPUT);
3130     int need_enc = xo_needed_encoding(xop);
3131     int real_need_enc = need_enc;
3132     int old_cols = xop->xo_columns;
3133 
3134     /* The gettext interface is UTF-8, so we'll need that for now */
3135     if (flags & XFF_GT_FIELD)
3136 	need_enc = XF_ENC_UTF8;
3137 
3138     if (xbp == NULL)
3139 	xbp = &xop->xo_data;
3140 
3141     unsigned start_offset = xo_buf_offset(xbp);
3142 
3143     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3144 	/*
3145 	 * Since we're starting a new field, save the starting offset.
3146 	 * We'll need this later for field-related operations.
3147 	 */
3148 
3149 	if (*cp != '%') {
3150 	add_one:
3151 	    if (xp == NULL)
3152 		xp = cp;
3153 
3154 	    if (*cp == '\\' && cp[1] != '\0')
3155 		cp += 1;
3156 	    continue;
3157 
3158 	} if (cp + 1 < ep && cp[1] == '%') {
3159 	    cp += 1;
3160 	    goto add_one;
3161 	}
3162 
3163 	if (xp) {
3164 	    if (make_output) {
3165 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3166 					       NULL, xp, cp - xp, -1,
3167 					       need_enc, XF_ENC_UTF8);
3168 		if (XOF_ISSET(xop, XOF_COLUMNS))
3169 		    xop->xo_columns += cols;
3170 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
3171 		    xop->xo_anchor_columns += cols;
3172 	    }
3173 
3174 	    xp = NULL;
3175 	}
3176 
3177 	bzero(&xf, sizeof(xf));
3178 	xf.xf_leading_zero = -1;
3179 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
3180 
3181 	/*
3182 	 * "%@" starts an XO-specific set of flags:
3183 	 *   @X@ - XML-only field; ignored if style isn't XML
3184 	 */
3185 	if (cp[1] == '@') {
3186 	    for (cp += 2; cp < ep; cp++) {
3187 		if (*cp == '@') {
3188 		    break;
3189 		}
3190 		if (*cp == '*') {
3191 		    /*
3192 		     * '*' means there's a "%*.*s" value in vap that
3193 		     * we want to ignore
3194 		     */
3195 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
3196 			va_arg(xop->xo_vap, int);
3197 		}
3198 	    }
3199 	}
3200 
3201 	/* Hidden fields are only visible to JSON and XML */
3202 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
3203 	    if (style != XO_STYLE_XML
3204 		    && !xo_style_is_encoding(xop))
3205 		xf.xf_skip = 1;
3206 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
3207 	    if (style != XO_STYLE_TEXT
3208 		    && xo_style(xop) != XO_STYLE_HTML)
3209 		xf.xf_skip = 1;
3210 	}
3211 
3212 	if (!make_output)
3213 	    xf.xf_skip = 1;
3214 
3215 	/*
3216 	 * Looking at one piece of a format; find the end and
3217 	 * call snprintf.  Then advance xo_vap on our own.
3218 	 *
3219 	 * Note that 'n', 'v', and '$' are not supported.
3220 	 */
3221 	sp = cp;		/* Save start pointer */
3222 	for (cp += 1; cp < ep; cp++) {
3223 	    if (*cp == 'l')
3224 		xf.xf_lflag += 1;
3225 	    else if (*cp == 'h')
3226 		xf.xf_hflag += 1;
3227 	    else if (*cp == 'j')
3228 		xf.xf_jflag += 1;
3229 	    else if (*cp == 't')
3230 		xf.xf_tflag += 1;
3231 	    else if (*cp == 'z')
3232 		xf.xf_zflag += 1;
3233 	    else if (*cp == 'q')
3234 		xf.xf_qflag += 1;
3235 	    else if (*cp == '.') {
3236 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
3237 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
3238 		    return -1;
3239 		}
3240 	    } else if (*cp == '-')
3241 		xf.xf_seen_minus = 1;
3242 	    else if (isdigit((int) *cp)) {
3243 		if (xf.xf_leading_zero < 0)
3244 		    xf.xf_leading_zero = (*cp == '0');
3245 		xo_bump_width(&xf, *cp - '0');
3246 	    } else if (*cp == '*') {
3247 		xf.xf_stars += 1;
3248 		xf.xf_star[xf.xf_dots] = 1;
3249 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
3250 		break;
3251 	    else if (*cp == 'n' || *cp == 'v') {
3252 		xo_failure(xop, "unsupported format: '%s'", fmt);
3253 		return -1;
3254 	    }
3255 	}
3256 
3257 	if (cp == ep)
3258 	    xo_failure(xop, "field format missing format character: %s",
3259 			  fmt);
3260 
3261 	xf.xf_fc = *cp;
3262 
3263 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3264 	    if (*cp == 's' || *cp == 'S') {
3265 		/* Handle "%*.*.*s" */
3266 		int s;
3267 		for (s = 0; s < XF_WIDTH_NUM; s++) {
3268 		    if (xf.xf_star[s]) {
3269 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
3270 
3271 			/* Normalize a negative width value */
3272 			if (xf.xf_width[s] < 0) {
3273 			    if (s == 0) {
3274 				xf.xf_width[0] = -xf.xf_width[0];
3275 				xf.xf_seen_minus = 1;
3276 			    } else
3277 				xf.xf_width[s] = -1; /* Ignore negative values */
3278 			}
3279 		    }
3280 		}
3281 	    }
3282 	}
3283 
3284 	/* If no max is given, it defaults to size */
3285 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
3286 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
3287 
3288 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
3289 	    xf.xf_lflag = 1;
3290 
3291 	if (!xf.xf_skip) {
3292 	    xo_buffer_t *fbp = &xop->xo_fmt;
3293 	    int len = cp - sp + 1;
3294 	    if (!xo_buf_has_room(fbp, len + 1))
3295 		return -1;
3296 
3297 	    char *newfmt = fbp->xb_curp;
3298 	    memcpy(newfmt, sp, len);
3299 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
3300 	    newfmt[len] = '\0';
3301 
3302 	    /*
3303 	     * Bad news: our strings are UTF-8, but the stock printf
3304 	     * functions won't handle field widths for wide characters
3305 	     * correctly.  So we have to handle this ourselves.
3306 	     */
3307 	    if (xop->xo_formatter == NULL
3308 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3309 			|| xf.xf_fc == 'm')) {
3310 
3311 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3312 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3313 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3314 
3315 		rc = xo_format_string(xop, xbp, flags, &xf);
3316 
3317 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
3318 		    rc = xo_trim_ws(xbp, rc);
3319 
3320 	    } else {
3321 		int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
3322 
3323 		/*
3324 		 * For XML and HTML, we need "&<>" processing; for JSON,
3325 		 * it's quotes.  Text gets nothing.
3326 		 */
3327 		switch (style) {
3328 		case XO_STYLE_XML:
3329 		    if (flags & XFF_TRIM_WS)
3330 			columns = rc = xo_trim_ws(xbp, rc);
3331 		    /* fall thru */
3332 		case XO_STYLE_HTML:
3333 		    rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
3334 		    break;
3335 
3336 		case XO_STYLE_JSON:
3337 		    if (flags & XFF_TRIM_WS)
3338 			columns = rc = xo_trim_ws(xbp, rc);
3339 		    rc = xo_escape_json(xbp, rc, 0);
3340 		    break;
3341 
3342 		case XO_STYLE_SDPARAMS:
3343 		    if (flags & XFF_TRIM_WS)
3344 			columns = rc = xo_trim_ws(xbp, rc);
3345 		    rc = xo_escape_sdparams(xbp, rc, 0);
3346 		    break;
3347 
3348 		case XO_STYLE_ENCODER:
3349 		    if (flags & XFF_TRIM_WS)
3350 			columns = rc = xo_trim_ws(xbp, rc);
3351 		    break;
3352 		}
3353 
3354 		/*
3355 		 * We can assume all the non-%s data we've
3356 		 * added is ASCII, so the columns and bytes are the
3357 		 * same.  xo_format_string handles all the fancy
3358 		 * string conversions and updates xo_anchor_columns
3359 		 * accordingly.
3360 		 */
3361 		if (XOF_ISSET(xop, XOF_COLUMNS))
3362 		    xop->xo_columns += columns;
3363 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
3364 		    xop->xo_anchor_columns += columns;
3365 	    }
3366 
3367 	    xbp->xb_curp += rc;
3368 	}
3369 
3370 	/*
3371 	 * Now for the tricky part: we need to move the argument pointer
3372 	 * along by the amount needed.
3373 	 */
3374 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3375 
3376 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
3377 		/*
3378 		 * The 'S' and 's' formats are normally handled in
3379 		 * xo_format_string, but if we skipped it, then we
3380 		 * need to pop it.
3381 		 */
3382 		if (xf.xf_skip)
3383 		    va_arg(xop->xo_vap, char *);
3384 
3385 	    } else if (xf.xf_fc == 'm') {
3386 		/* Nothing on the stack for "%m" */
3387 
3388 	    } else {
3389 		int s;
3390 		for (s = 0; s < XF_WIDTH_NUM; s++) {
3391 		    if (xf.xf_star[s])
3392 			va_arg(xop->xo_vap, int);
3393 		}
3394 
3395 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
3396 		    if (xf.xf_hflag > 1) {
3397 			va_arg(xop->xo_vap, int);
3398 
3399 		    } else if (xf.xf_hflag > 0) {
3400 			va_arg(xop->xo_vap, int);
3401 
3402 		    } else if (xf.xf_lflag > 1) {
3403 			va_arg(xop->xo_vap, unsigned long long);
3404 
3405 		    } else if (xf.xf_lflag > 0) {
3406 			va_arg(xop->xo_vap, unsigned long);
3407 
3408 		    } else if (xf.xf_jflag > 0) {
3409 			va_arg(xop->xo_vap, intmax_t);
3410 
3411 		    } else if (xf.xf_tflag > 0) {
3412 			va_arg(xop->xo_vap, ptrdiff_t);
3413 
3414 		    } else if (xf.xf_zflag > 0) {
3415 			va_arg(xop->xo_vap, size_t);
3416 
3417 		    } else if (xf.xf_qflag > 0) {
3418 			va_arg(xop->xo_vap, quad_t);
3419 
3420 		    } else {
3421 			va_arg(xop->xo_vap, int);
3422 		    }
3423 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
3424 		    if (xf.xf_lflag)
3425 			va_arg(xop->xo_vap, long double);
3426 		    else
3427 			va_arg(xop->xo_vap, double);
3428 
3429 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
3430 		    va_arg(xop->xo_vap, wint_t);
3431 
3432 		else if (xf.xf_fc == 'c')
3433 		    va_arg(xop->xo_vap, int);
3434 
3435 		else if (xf.xf_fc == 'p')
3436 		    va_arg(xop->xo_vap, void *);
3437 	    }
3438 	}
3439     }
3440 
3441     if (xp) {
3442 	if (make_output) {
3443 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3444 					   NULL, xp, cp - xp, -1,
3445 					   need_enc, XF_ENC_UTF8);
3446 
3447 	    if (XOF_ISSET(xop, XOF_COLUMNS))
3448 		xop->xo_columns += cols;
3449 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
3450 		xop->xo_anchor_columns += cols;
3451 	}
3452 
3453 	xp = NULL;
3454     }
3455 
3456     if (flags & XFF_GT_FLAGS) {
3457 	/*
3458 	 * Handle gettext()ing the field by looking up the value
3459 	 * and then copying it in, while converting to locale, if
3460 	 * needed.
3461 	 */
3462 	int new_cols = xo_format_gettext(xop, flags, start_offset,
3463 					 old_cols, real_need_enc);
3464 
3465 	if (XOF_ISSET(xop, XOF_COLUMNS))
3466 	    xop->xo_columns += new_cols - old_cols;
3467 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3468 	    xop->xo_anchor_columns += new_cols - old_cols;
3469     }
3470 
3471     return 0;
3472 }
3473 
3474 static char *
xo_fix_encoding(xo_handle_t * xop UNUSED,char * encoding)3475 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
3476 {
3477     char *cp = encoding;
3478 
3479     if (cp[0] != '%' || !isdigit((int) cp[1]))
3480 	return encoding;
3481 
3482     for (cp += 2; *cp; cp++) {
3483 	if (!isdigit((int) *cp))
3484 	    break;
3485     }
3486 
3487     cp -= 1;
3488     *cp = '%';
3489 
3490     return cp;
3491 }
3492 
3493 static void
xo_color_append_html(xo_handle_t * xop)3494 xo_color_append_html (xo_handle_t *xop)
3495 {
3496     /*
3497      * If the color buffer has content, we add it now.  It's already
3498      * prebuilt and ready, since we want to add it to every <div>.
3499      */
3500     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3501 	xo_buffer_t *xbp = &xop->xo_color_buf;
3502 
3503 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3504     }
3505 }
3506 
3507 /*
3508  * A wrapper for humanize_number that autoscales, since the
3509  * HN_AUTOSCALE flag scales as needed based on the size of
3510  * the output buffer, not the size of the value.  I also
3511  * wish HN_DECIMAL was more imperative, without the <10
3512  * test.  But the boat only goes where we want when we hold
3513  * the rudder, so xo_humanize fixes part of the problem.
3514  */
3515 static int
xo_humanize(char * buf,int len,uint64_t value,int flags)3516 xo_humanize (char *buf, int len, uint64_t value, int flags)
3517 {
3518     int scale = 0;
3519 
3520     if (value) {
3521 	uint64_t left = value;
3522 
3523 	if (flags & HN_DIVISOR_1000) {
3524 	    for ( ; left; scale++)
3525 		left /= 1000;
3526 	} else {
3527 	    for ( ; left; scale++)
3528 		left /= 1024;
3529 	}
3530 	scale -= 1;
3531     }
3532 
3533     return xo_humanize_number(buf, len, value, "", scale, flags);
3534 }
3535 
3536 /*
3537  * This is an area where we can save information from the handle for
3538  * later restoration.  We need to know what data was rendered to know
3539  * what needs cleaned up.
3540  */
3541 typedef struct xo_humanize_save_s {
3542     unsigned xhs_offset;	/* Saved xo_offset */
3543     unsigned xhs_columns;	/* Saved xo_columns */
3544     unsigned xhs_anchor_columns; /* Saved xo_anchor_columns */
3545 } xo_humanize_save_t;
3546 
3547 /*
3548  * Format a "humanized" value for a numeric, meaning something nice
3549  * like "44M" instead of "44470272".  We autoscale, choosing the
3550  * most appropriate value for K/M/G/T/P/E based on the value given.
3551  */
3552 static void
xo_format_humanize(xo_handle_t * xop,xo_buffer_t * xbp,xo_humanize_save_t * savep,xo_xff_flags_t flags)3553 xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3554 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3555 {
3556     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3557 	return;
3558 
3559     unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
3560     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3561 	return;
3562 
3563     /*
3564      * We have a string that's allegedly a number. We want to
3565      * humanize it, which means turning it back into a number
3566      * and calling xo_humanize_number on it.
3567      */
3568     uint64_t value;
3569     char *ep;
3570 
3571     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3572 
3573     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3574     if (!(value == ULLONG_MAX && errno == ERANGE)
3575 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3576 	/*
3577 	 * There are few values where humanize_number needs
3578 	 * more bytes than the original value.  I've used
3579 	 * 10 as a rectal number to cover those scenarios.
3580 	 */
3581 	if (xo_buf_has_room(xbp, 10)) {
3582 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3583 
3584 	    int rc;
3585 	    int left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3586 	    int hn_flags = HN_NOSPACE; /* On by default */
3587 
3588 	    if (flags & XFF_HN_SPACE)
3589 		hn_flags &= ~HN_NOSPACE;
3590 
3591 	    if (flags & XFF_HN_DECIMAL)
3592 		hn_flags |= HN_DECIMAL;
3593 
3594 	    if (flags & XFF_HN_1000)
3595 		hn_flags |= HN_DIVISOR_1000;
3596 
3597 	    rc = xo_humanize(xbp->xb_curp,
3598 			     left, value, hn_flags);
3599 	    if (rc > 0) {
3600 		xbp->xb_curp += rc;
3601 		xop->xo_columns = savep->xhs_columns + rc;
3602 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3603 	    }
3604 	}
3605     }
3606 }
3607 
3608 static void
xo_buf_append_div(xo_handle_t * xop,const char * class,xo_xff_flags_t flags,const char * name,int nlen,const char * value,int vlen,const char * encoding,int elen)3609 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
3610 		   const char *name, int nlen,
3611 		   const char *value, int vlen,
3612 		   const char *encoding, int elen)
3613 {
3614     static char div_start[] = "<div class=\"";
3615     static char div_tag[] = "\" data-tag=\"";
3616     static char div_xpath[] = "\" data-xpath=\"";
3617     static char div_key[] = "\" data-key=\"key";
3618     static char div_end[] = "\">";
3619     static char div_close[] = "</div>";
3620 
3621     /* The encoding format defaults to the normal format */
3622     if (encoding == NULL) {
3623 	char *enc  = alloca(vlen + 1);
3624 	memcpy(enc, value, vlen);
3625 	enc[vlen] = '\0';
3626 	encoding = xo_fix_encoding(xop, enc);
3627 	elen = strlen(encoding);
3628     }
3629 
3630     /*
3631      * To build our XPath predicate, we need to save the va_list before
3632      * we format our data, and then restore it before we format the
3633      * xpath expression.
3634      * Display-only keys implies that we've got an encode-only key
3635      * elsewhere, so we don't use them from making predicates.
3636      */
3637     int need_predidate =
3638 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
3639 	     && XOF_ISSET(xop, XOF_XPATH));
3640 
3641     if (need_predidate) {
3642 	va_list va_local;
3643 
3644 	va_copy(va_local, xop->xo_vap);
3645 	if (xop->xo_checkpointer)
3646 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
3647 
3648 	/*
3649 	 * Build an XPath predicate expression to match this key.
3650 	 * We use the format buffer.
3651 	 */
3652 	xo_buffer_t *pbp = &xop->xo_predicate;
3653 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
3654 
3655 	xo_buf_append(pbp, "[", 1);
3656 	xo_buf_escape(xop, pbp, name, nlen, 0);
3657 	if (XOF_ISSET(xop, XOF_PRETTY))
3658 	    xo_buf_append(pbp, " = '", 4);
3659 	else
3660 	    xo_buf_append(pbp, "='", 2);
3661 
3662 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3663 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3664 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
3665 
3666 	xo_buf_append(pbp, "']", 2);
3667 
3668 	/* Now we record this predicate expression in the stack */
3669 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3670 	int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
3671 	int dlen = pbp->xb_curp - pbp->xb_bufp;
3672 
3673 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
3674 	if (cp) {
3675 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
3676 	    cp[olen + dlen] = '\0';
3677 	    xsp->xs_keys = cp;
3678 	}
3679 
3680 	/* Now we reset the xo_vap as if we were never here */
3681 	va_end(xop->xo_vap);
3682 	va_copy(xop->xo_vap, va_local);
3683 	va_end(va_local);
3684 	if (xop->xo_checkpointer)
3685 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
3686     }
3687 
3688     if (flags & XFF_ENCODE_ONLY) {
3689 	/*
3690 	 * Even if this is encode-only, we need to go thru the
3691 	 * work of formatting it to make sure the args are cleared
3692 	 * from xo_vap.
3693 	 */
3694 	xo_do_format_field(xop, NULL, encoding, elen,
3695 		       flags | XFF_NO_OUTPUT);
3696 	return;
3697     }
3698 
3699     xo_line_ensure_open(xop, 0);
3700 
3701     if (XOF_ISSET(xop, XOF_PRETTY))
3702 	xo_buf_indent(xop, xop->xo_indent_by);
3703 
3704     xo_data_append(xop, div_start, sizeof(div_start) - 1);
3705     xo_data_append(xop, class, strlen(class));
3706 
3707     /*
3708      * If the color buffer has content, we add it now.  It's already
3709      * prebuilt and ready, since we want to add it to every <div>.
3710      */
3711     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3712 	xo_buffer_t *xbp = &xop->xo_color_buf;
3713 
3714 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3715     }
3716 
3717     if (name) {
3718 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
3719 	xo_data_escape(xop, name, nlen);
3720 
3721 	/*
3722 	 * Save the offset at which we'd place units.  See xo_format_units.
3723 	 */
3724 	if (XOF_ISSET(xop, XOF_UNITS)) {
3725 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
3726 	    /*
3727 	     * Note: We need the '+1' here because we know we've not
3728 	     * added the closing quote.  We add one, knowing the quote
3729 	     * will be added shortly.
3730 	     */
3731 	    xop->xo_units_offset =
3732 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
3733 	}
3734 
3735 	if (XOF_ISSET(xop, XOF_XPATH)) {
3736 	    int i;
3737 	    xo_stack_t *xsp;
3738 
3739 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
3740 	    if (xop->xo_leading_xpath)
3741 		xo_data_append(xop, xop->xo_leading_xpath,
3742 			       strlen(xop->xo_leading_xpath));
3743 
3744 	    for (i = 0; i <= xop->xo_depth; i++) {
3745 		xsp = &xop->xo_stack[i];
3746 		if (xsp->xs_name == NULL)
3747 		    continue;
3748 
3749 		/*
3750 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3751 		 * are directly under XSS_OPEN_INSTANCE frames so we
3752 		 * don't need to put these in our XPath expressions.
3753 		 */
3754 		if (xsp->xs_state == XSS_OPEN_LIST
3755 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3756 		    continue;
3757 
3758 		xo_data_append(xop, "/", 1);
3759 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
3760 		if (xsp->xs_keys) {
3761 		    /* Don't show keys for the key field */
3762 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
3763 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
3764 		}
3765 	    }
3766 
3767 	    xo_data_append(xop, "/", 1);
3768 	    xo_data_escape(xop, name, nlen);
3769 	}
3770 
3771 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
3772 	    static char in_type[] = "\" data-type=\"";
3773 	    static char in_help[] = "\" data-help=\"";
3774 
3775 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
3776 	    if (xip) {
3777 		if (xip->xi_type) {
3778 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
3779 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
3780 		}
3781 		if (xip->xi_help) {
3782 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
3783 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
3784 		}
3785 	    }
3786 	}
3787 
3788 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
3789 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
3790     }
3791 
3792     xo_buffer_t *xbp = &xop->xo_data;
3793     unsigned base_offset = xbp->xb_curp - xbp->xb_bufp;
3794 
3795     xo_data_append(xop, div_end, sizeof(div_end) - 1);
3796 
3797     xo_humanize_save_t save;	/* Save values for humanizing logic */
3798 
3799     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
3800     save.xhs_columns = xop->xo_columns;
3801     save.xhs_anchor_columns = xop->xo_anchor_columns;
3802 
3803     xo_do_format_field(xop, NULL, value, vlen, flags);
3804 
3805     if (flags & XFF_HUMANIZE) {
3806 	/*
3807 	 * Unlike text style, we want to retain the original value and
3808 	 * stuff it into the "data-number" attribute.
3809 	 */
3810 	static const char div_number[] = "\" data-number=\"";
3811 	int div_len = sizeof(div_number) - 1;
3812 
3813 	unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
3814 	int olen = end_offset - save.xhs_offset;
3815 
3816 	char *cp = alloca(olen + 1);
3817 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
3818 	cp[olen] = '\0';
3819 
3820 	xo_format_humanize(xop, xbp, &save, flags);
3821 
3822 	if (xo_buf_has_room(xbp, div_len + olen)) {
3823 	    unsigned new_offset = xbp->xb_curp - xbp->xb_bufp;
3824 
3825 
3826 	    /* Move the humanized string off to the left */
3827 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
3828 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
3829 
3830 	    /* Copy the data_number attribute name */
3831 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
3832 
3833 	    /* Copy the original long value */
3834 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
3835 	    xbp->xb_curp += div_len + olen;
3836 	}
3837     }
3838 
3839     xo_data_append(xop, div_close, sizeof(div_close) - 1);
3840 
3841     if (XOF_ISSET(xop, XOF_PRETTY))
3842 	xo_data_append(xop, "\n", 1);
3843 }
3844 
3845 static void
xo_format_text(xo_handle_t * xop,const char * str,int len)3846 xo_format_text (xo_handle_t *xop, const char *str, int len)
3847 {
3848     switch (xo_style(xop)) {
3849     case XO_STYLE_TEXT:
3850 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
3851 	break;
3852 
3853     case XO_STYLE_HTML:
3854 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
3855 	break;
3856     }
3857 }
3858 
3859 static void
xo_format_title(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)3860 xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
3861 		 const char *str, unsigned len)
3862 {
3863     const char *fmt = xfip->xfi_format;
3864     unsigned flen = xfip->xfi_flen;
3865     xo_xff_flags_t flags = xfip->xfi_flags;
3866 
3867     static char div_open[] = "<div class=\"title";
3868     static char div_middle[] = "\">";
3869     static char div_close[] = "</div>";
3870 
3871     if (flen == 0) {
3872 	fmt = "%s";
3873 	flen = 2;
3874     }
3875 
3876     switch (xo_style(xop)) {
3877     case XO_STYLE_XML:
3878     case XO_STYLE_JSON:
3879     case XO_STYLE_SDPARAMS:
3880     case XO_STYLE_ENCODER:
3881 	/*
3882 	 * Even though we don't care about text, we need to do
3883 	 * enough parsing work to skip over the right bits of xo_vap.
3884 	 */
3885 	if (len == 0)
3886 	    xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
3887 	return;
3888     }
3889 
3890     xo_buffer_t *xbp = &xop->xo_data;
3891     int start = xbp->xb_curp - xbp->xb_bufp;
3892     int left = xbp->xb_size - start;
3893     int rc;
3894 
3895     if (xo_style(xop) == XO_STYLE_HTML) {
3896 	xo_line_ensure_open(xop, 0);
3897 	if (XOF_ISSET(xop, XOF_PRETTY))
3898 	    xo_buf_indent(xop, xop->xo_indent_by);
3899 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
3900 	xo_color_append_html(xop);
3901 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
3902     }
3903 
3904     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
3905     if (len) {
3906 	char *newfmt = alloca(flen + 1);
3907 	memcpy(newfmt, fmt, flen);
3908 	newfmt[flen] = '\0';
3909 
3910 	/* If len is non-zero, the format string apply to the name */
3911 	char *newstr = alloca(len + 1);
3912 	memcpy(newstr, str, len);
3913 	newstr[len] = '\0';
3914 
3915 	if (newstr[len - 1] == 's') {
3916 	    char *bp;
3917 
3918 	    rc = snprintf(NULL, 0, newfmt, newstr);
3919 	    if (rc > 0) {
3920 		/*
3921 		 * We have to do this the hard way, since we might need
3922 		 * the columns.
3923 		 */
3924 		bp = alloca(rc + 1);
3925 		rc = snprintf(bp, rc + 1, newfmt, newstr);
3926 
3927 		xo_data_append_content(xop, bp, rc, flags);
3928 	    }
3929 	    goto move_along;
3930 
3931 	} else {
3932 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3933 	    if (rc >= left) {
3934 		if (!xo_buf_has_room(xbp, rc))
3935 		    return;
3936 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
3937 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
3938 	    }
3939 
3940 	    if (rc > 0) {
3941 		if (XOF_ISSET(xop, XOF_COLUMNS))
3942 		    xop->xo_columns += rc;
3943 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
3944 		    xop->xo_anchor_columns += rc;
3945 	    }
3946 	}
3947 
3948     } else {
3949 	xo_do_format_field(xop, NULL, fmt, flen, flags);
3950 
3951 	/* xo_do_format_field moved curp, so we need to reset it */
3952 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
3953 	xbp->xb_curp = xbp->xb_bufp + start;
3954     }
3955 
3956     /* If we're styling HTML, then we need to escape it */
3957     if (xo_style(xop) == XO_STYLE_HTML) {
3958 	rc = xo_escape_xml(xbp, rc, 0);
3959     }
3960 
3961     if (rc > 0)
3962 	xbp->xb_curp += rc;
3963 
3964  move_along:
3965     if (xo_style(xop) == XO_STYLE_HTML) {
3966 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
3967 	if (XOF_ISSET(xop, XOF_PRETTY))
3968 	    xo_data_append(xop, "\n", 1);
3969     }
3970 }
3971 
3972 static void
xo_format_prep(xo_handle_t * xop,xo_xff_flags_t flags)3973 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
3974 {
3975     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
3976 	xo_data_append(xop, ",", 1);
3977 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
3978 	    xo_data_append(xop, "\n", 1);
3979     } else
3980 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
3981 }
3982 
3983 #if 0
3984 /* Useful debugging function */
3985 void
3986 xo_arg (xo_handle_t *xop);
3987 void
3988 xo_arg (xo_handle_t *xop)
3989 {
3990     xop = xo_default(xop);
3991     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
3992 }
3993 #endif /* 0 */
3994 
3995 static void
xo_format_value(xo_handle_t * xop,const char * name,int nlen,const char * format,int flen,const char * encoding,int elen,xo_xff_flags_t flags)3996 xo_format_value (xo_handle_t *xop, const char *name, int nlen,
3997                 const char *format, int flen,
3998                 const char *encoding, int elen, xo_xff_flags_t flags)
3999 {
4000     int pretty = XOF_ISSET(xop, XOF_PRETTY);
4001     int quote;
4002 
4003     /*
4004      * Before we emit a value, we need to know that the frame is ready.
4005      */
4006     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4007 
4008     if (flags & XFF_LEAF_LIST) {
4009 	/*
4010 	 * Check if we've already started to emit normal leafs
4011 	 * or if we're not in a leaf list.
4012 	 */
4013 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4014 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4015 	    char nbuf[nlen + 1];
4016 	    memcpy(nbuf, name, nlen);
4017 	    nbuf[nlen] = '\0';
4018 
4019 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4020 	    if (rc < 0)
4021 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4022 	    else
4023 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4024 	}
4025 
4026 	xsp = &xop->xo_stack[xop->xo_depth];
4027 	if (xsp->xs_name) {
4028 	    name = xsp->xs_name;
4029 	    nlen = strlen(name);
4030 	}
4031 
4032     } else if (flags & XFF_KEY) {
4033 	/* Emitting a 'k' (key) field */
4034 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4035 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4036 		       nlen, name);
4037 
4038 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4039 	    char nbuf[nlen + 1];
4040 	    memcpy(nbuf, name, nlen);
4041 	    nbuf[nlen] = '\0';
4042 
4043 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4044 	    if (rc < 0)
4045 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4046 	    else
4047 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4048 
4049 	    xsp = &xop->xo_stack[xop->xo_depth];
4050 	    xsp->xs_flags |= XSF_EMIT_KEY;
4051 	}
4052 
4053     } else {
4054 	/* Emitting a normal value field */
4055 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4056 	    || !(xsp->xs_flags & XSF_EMIT)) {
4057 	    char nbuf[nlen + 1];
4058 	    memcpy(nbuf, name, nlen);
4059 	    nbuf[nlen] = '\0';
4060 
4061 	    int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4062 	    if (rc < 0)
4063 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4064 	    else
4065 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4066 
4067 	    xsp = &xop->xo_stack[xop->xo_depth];
4068 	    xsp->xs_flags |= XSF_EMIT;
4069 	}
4070     }
4071 
4072     xo_buffer_t *xbp = &xop->xo_data;
4073     xo_humanize_save_t save;	/* Save values for humanizing logic */
4074 
4075     switch (xo_style(xop)) {
4076     case XO_STYLE_TEXT:
4077 	if (flags & XFF_ENCODE_ONLY)
4078 	    flags |= XFF_NO_OUTPUT;
4079 
4080 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4081 	save.xhs_columns = xop->xo_columns;
4082 	save.xhs_anchor_columns = xop->xo_anchor_columns;
4083 
4084 	xo_do_format_field(xop, NULL, format, flen, flags);
4085 
4086 	if (flags & XFF_HUMANIZE)
4087 	    xo_format_humanize(xop, xbp, &save, flags);
4088 	break;
4089 
4090     case XO_STYLE_HTML:
4091 	if (flags & XFF_ENCODE_ONLY)
4092 	    flags |= XFF_NO_OUTPUT;
4093 
4094 	xo_buf_append_div(xop, "data", flags, name, nlen,
4095 			  format, flen, encoding, elen);
4096 	break;
4097 
4098     case XO_STYLE_XML:
4099 	/*
4100 	 * Even though we're not making output, we still need to
4101 	 * let the formatting code handle the va_arg popping.
4102 	 */
4103 	if (flags & XFF_DISPLAY_ONLY) {
4104 	    flags |= XFF_NO_OUTPUT;
4105 	    xo_do_format_field(xop, NULL, format, flen, flags);
4106 	    break;
4107 	}
4108 
4109 	if (encoding) {
4110    	    format = encoding;
4111 	    flen = elen;
4112 	} else {
4113 	    char *enc  = alloca(flen + 1);
4114 	    memcpy(enc, format, flen);
4115 	    enc[flen] = '\0';
4116 	    format = xo_fix_encoding(xop, enc);
4117 	    flen = strlen(format);
4118 	}
4119 
4120 	if (nlen == 0) {
4121 	    static char missing[] = "missing-field-name";
4122 	    xo_failure(xop, "missing field name: %s", format);
4123 	    name = missing;
4124 	    nlen = sizeof(missing) - 1;
4125 	}
4126 
4127 	if (pretty)
4128 	    xo_buf_indent(xop, -1);
4129 	xo_data_append(xop, "<", 1);
4130 	xo_data_escape(xop, name, nlen);
4131 
4132 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4133 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
4134 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4135 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4136 	}
4137 
4138 	/*
4139 	 * We indicate 'key' fields using the 'key' attribute.  While
4140 	 * this is really committing the crime of mixing meta-data with
4141 	 * data, it's often useful.  Especially when format meta-data is
4142 	 * difficult to come by.
4143 	 */
4144 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
4145 	    static char attr[] = " key=\"key\"";
4146 	    xo_data_append(xop, attr, sizeof(attr) - 1);
4147 	}
4148 
4149 	/*
4150 	 * Save the offset at which we'd place units.  See xo_format_units.
4151 	 */
4152 	if (XOF_ISSET(xop, XOF_UNITS)) {
4153 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
4154 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
4155 	}
4156 
4157 	xo_data_append(xop, ">", 1);
4158 	xo_do_format_field(xop, NULL, format, flen, flags);
4159 	xo_data_append(xop, "</", 2);
4160 	xo_data_escape(xop, name, nlen);
4161 	xo_data_append(xop, ">", 1);
4162 	if (pretty)
4163 	    xo_data_append(xop, "\n", 1);
4164 	break;
4165 
4166     case XO_STYLE_JSON:
4167 	if (flags & XFF_DISPLAY_ONLY) {
4168 	    flags |= XFF_NO_OUTPUT;
4169 	    xo_do_format_field(xop, NULL, format, flen, flags);
4170 	    break;
4171 	}
4172 
4173 	if (encoding) {
4174 	    format = encoding;
4175 	    flen = elen;
4176 	} else {
4177 	    char *enc  = alloca(flen + 1);
4178 	    memcpy(enc, format, flen);
4179 	    enc[flen] = '\0';
4180 	    format = xo_fix_encoding(xop, enc);
4181 	    flen = strlen(format);
4182 	}
4183 
4184 	int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
4185 
4186 	xo_format_prep(xop, flags);
4187 
4188 	if (flags & XFF_QUOTE)
4189 	    quote = 1;
4190 	else if (flags & XFF_NOQUOTE)
4191 	    quote = 0;
4192 	else if (flen == 0) {
4193 	    quote = 0;
4194 	    format = "true";	/* JSON encodes empty tags as a boolean true */
4195 	    flen = 4;
4196 	} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
4197 	    quote = 1;
4198 	else
4199 	    quote = 0;
4200 
4201 	if (nlen == 0) {
4202 	    static char missing[] = "missing-field-name";
4203 	    xo_failure(xop, "missing field name: %s", format);
4204 	    name = missing;
4205 	    nlen = sizeof(missing) - 1;
4206 	}
4207 
4208 	if (flags & XFF_LEAF_LIST) {
4209 	    if (!first && pretty)
4210 		xo_data_append(xop, "\n", 1);
4211 	    if (pretty)
4212 		xo_buf_indent(xop, -1);
4213 	} else {
4214 	    if (pretty)
4215 		xo_buf_indent(xop, -1);
4216 	    xo_data_append(xop, "\"", 1);
4217 
4218 	    xbp = &xop->xo_data;
4219 	    int off = xbp->xb_curp - xbp->xb_bufp;
4220 
4221 	    xo_data_escape(xop, name, nlen);
4222 
4223 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
4224 		int now = xbp->xb_curp - xbp->xb_bufp;
4225 		for ( ; off < now; off++)
4226 		    if (xbp->xb_bufp[off] == '-')
4227 			xbp->xb_bufp[off] = '_';
4228 	    }
4229 	    xo_data_append(xop, "\":", 2);
4230 	    if (pretty)
4231 	        xo_data_append(xop, " ", 1);
4232 	}
4233 
4234 	if (quote)
4235 	    xo_data_append(xop, "\"", 1);
4236 
4237 	xo_do_format_field(xop, NULL, format, flen, flags);
4238 
4239 	if (quote)
4240 	    xo_data_append(xop, "\"", 1);
4241 	break;
4242 
4243     case XO_STYLE_SDPARAMS:
4244 	if (flags & XFF_DISPLAY_ONLY) {
4245 	    flags |= XFF_NO_OUTPUT;
4246 	    xo_do_format_field(xop, NULL, format, flen, flags);
4247 	    break;
4248 	}
4249 
4250 	if (encoding) {
4251 	    format = encoding;
4252 	    flen = elen;
4253 	} else {
4254 	    char *enc  = alloca(flen + 1);
4255 	    memcpy(enc, format, flen);
4256 	    enc[flen] = '\0';
4257 	    format = xo_fix_encoding(xop, enc);
4258 	    flen = strlen(format);
4259 	}
4260 
4261 	if (nlen == 0) {
4262 	    static char missing[] = "missing-field-name";
4263 	    xo_failure(xop, "missing field name: %s", format);
4264 	    name = missing;
4265 	    nlen = sizeof(missing) - 1;
4266 	}
4267 
4268 	xo_data_escape(xop, name, nlen);
4269 	xo_data_append(xop, "=\"", 2);
4270 	xo_do_format_field(xop, NULL, format, flen, flags);
4271 	xo_data_append(xop, "\" ", 2);
4272 	break;
4273 
4274     case XO_STYLE_ENCODER:
4275 	if (flags & XFF_DISPLAY_ONLY) {
4276 	    flags |= XFF_NO_OUTPUT;
4277 	    xo_do_format_field(xop, NULL, format, flen, flags);
4278 	    break;
4279 	}
4280 
4281 	if (flags & XFF_QUOTE)
4282 	    quote = 1;
4283 	else if (flags & XFF_NOQUOTE)
4284 	    quote = 0;
4285 	else if (flen == 0) {
4286 	    quote = 0;
4287 	    format = "true";	/* JSON encodes empty tags as a boolean true */
4288 	    flen = 4;
4289 	} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
4290 	    quote = 1;
4291 	else
4292 	    quote = 0;
4293 
4294 	if (encoding) {
4295 	    format = encoding;
4296 	    flen = elen;
4297 	} else {
4298 	    char *enc  = alloca(flen + 1);
4299 	    memcpy(enc, format, flen);
4300 	    enc[flen] = '\0';
4301 	    format = xo_fix_encoding(xop, enc);
4302 	    flen = strlen(format);
4303 	}
4304 
4305 	if (nlen == 0) {
4306 	    static char missing[] = "missing-field-name";
4307 	    xo_failure(xop, "missing field name: %s", format);
4308 	    name = missing;
4309 	    nlen = sizeof(missing) - 1;
4310 	}
4311 
4312 	unsigned name_offset = xo_buf_offset(&xop->xo_data);
4313 	xo_data_append(xop, name, nlen);
4314 	xo_data_append(xop, "", 1);
4315 
4316 	unsigned value_offset = xo_buf_offset(&xop->xo_data);
4317 	xo_do_format_field(xop, NULL, format, flen, flags);
4318 	xo_data_append(xop, "", 1);
4319 
4320 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4321 			  xo_buf_data(&xop->xo_data, name_offset),
4322 			  xo_buf_data(&xop->xo_data, value_offset));
4323 	xo_buf_reset(&xop->xo_data);
4324 	break;
4325     }
4326 }
4327 
4328 static void
xo_set_gettext_domain(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4329 xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
4330 		       const char *str, unsigned len)
4331 {
4332     const char *fmt = xfip->xfi_format;
4333     unsigned flen = xfip->xfi_flen;
4334 
4335     /* Start by discarding previous domain */
4336     if (xop->xo_gt_domain) {
4337 	xo_free(xop->xo_gt_domain);
4338 	xop->xo_gt_domain = NULL;
4339     }
4340 
4341     /* An empty {G:} means no domainname */
4342     if (len == 0 && flen == 0)
4343 	return;
4344 
4345     int start_offset = -1;
4346     if (len == 0 && flen != 0) {
4347 	/* Need to do format the data to get the domainname from args */
4348 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4349 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4350 
4351 	int end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4352 	len = end_offset - start_offset;
4353 	str = xop->xo_data.xb_bufp + start_offset;
4354     }
4355 
4356     xop->xo_gt_domain = xo_strndup(str, len);
4357 
4358     /* Reset the current buffer point to avoid emitting the name as output */
4359     if (start_offset >= 0)
4360 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4361 }
4362 
4363 static void
xo_format_content(xo_handle_t * xop,const char * class_name,const char * tag_name,const char * str,int len,const char * fmt,int flen,xo_xff_flags_t flags)4364 xo_format_content (xo_handle_t *xop, const char *class_name,
4365 		   const char *tag_name,
4366 		   const char *str, int len, const char *fmt, int flen,
4367 		   xo_xff_flags_t flags)
4368 {
4369     switch (xo_style(xop)) {
4370     case XO_STYLE_TEXT:
4371 	if (len)
4372 	    xo_data_append_content(xop, str, len, flags);
4373 	else
4374 	    xo_do_format_field(xop, NULL, fmt, flen, flags);
4375 	break;
4376 
4377     case XO_STYLE_HTML:
4378 	if (len == 0) {
4379 	    str = fmt;
4380 	    len = flen;
4381 	}
4382 
4383 	xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
4384 	break;
4385 
4386     case XO_STYLE_XML:
4387     case XO_STYLE_JSON:
4388     case XO_STYLE_SDPARAMS:
4389 	if (tag_name) {
4390 	    if (len == 0) {
4391 		str = fmt;
4392 		len = flen;
4393 	    }
4394 
4395 	    xo_open_container_h(xop, tag_name);
4396 	    xo_format_value(xop, "message", 7, str, len, NULL, 0, flags);
4397 	    xo_close_container_h(xop, tag_name);
4398 
4399 	} else {
4400 	    /*
4401 	     * Even though we don't care about labels, we need to do
4402 	     * enough parsing work to skip over the right bits of xo_vap.
4403 	     */
4404 	    if (len == 0)
4405 		xo_do_format_field(xop, NULL, fmt, flen,
4406 				   flags | XFF_NO_OUTPUT);
4407 	}
4408 	break;
4409 
4410     case XO_STYLE_ENCODER:
4411 	if (len == 0)
4412 	    xo_do_format_field(xop, NULL, fmt, flen,
4413 			       flags | XFF_NO_OUTPUT);
4414 	break;
4415     }
4416 }
4417 
4418 static const char *xo_color_names[] = {
4419     "default",	/* XO_COL_DEFAULT */
4420     "black",	/* XO_COL_BLACK */
4421     "red",	/* XO_CLOR_RED */
4422     "green",	/* XO_COL_GREEN */
4423     "yellow",	/* XO_COL_YELLOW */
4424     "blue",	/* XO_COL_BLUE */
4425     "magenta",	/* XO_COL_MAGENTA */
4426     "cyan",	/* XO_COL_CYAN */
4427     "white",	/* XO_COL_WHITE */
4428     NULL
4429 };
4430 
4431 static int
xo_color_find(const char * str)4432 xo_color_find (const char *str)
4433 {
4434     int i;
4435 
4436     for (i = 0; xo_color_names[i]; i++) {
4437 	if (strcmp(xo_color_names[i], str) == 0)
4438 	    return i;
4439     }
4440 
4441     return -1;
4442 }
4443 
4444 static const char *xo_effect_names[] = {
4445     "reset",			/* XO_EFF_RESET */
4446     "normal",			/* XO_EFF_NORMAL */
4447     "bold",			/* XO_EFF_BOLD */
4448     "underline",		/* XO_EFF_UNDERLINE */
4449     "inverse",			/* XO_EFF_INVERSE */
4450     NULL
4451 };
4452 
4453 static const char *xo_effect_on_codes[] = {
4454     "0",			/* XO_EFF_RESET */
4455     "0",			/* XO_EFF_NORMAL */
4456     "1",			/* XO_EFF_BOLD */
4457     "4",			/* XO_EFF_UNDERLINE */
4458     "7",			/* XO_EFF_INVERSE */
4459     NULL
4460 };
4461 
4462 #if 0
4463 /*
4464  * See comment below re: joy of terminal standards.  These can
4465  * be use by just adding:
4466  * +	if (newp->xoc_effects & bit)
4467  *	    code = xo_effect_on_codes[i];
4468  * +	else
4469  * +	    code = xo_effect_off_codes[i];
4470  * in xo_color_handle_text.
4471  */
4472 static const char *xo_effect_off_codes[] = {
4473     "0",			/* XO_EFF_RESET */
4474     "0",			/* XO_EFF_NORMAL */
4475     "21",			/* XO_EFF_BOLD */
4476     "24",			/* XO_EFF_UNDERLINE */
4477     "27",			/* XO_EFF_INVERSE */
4478     NULL
4479 };
4480 #endif /* 0 */
4481 
4482 static int
xo_effect_find(const char * str)4483 xo_effect_find (const char *str)
4484 {
4485     int i;
4486 
4487     for (i = 0; xo_effect_names[i]; i++) {
4488 	if (strcmp(xo_effect_names[i], str) == 0)
4489 	    return i;
4490     }
4491 
4492     return -1;
4493 }
4494 
4495 static void
xo_colors_parse(xo_handle_t * xop,xo_colors_t * xocp,char * str)4496 xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4497 {
4498 #ifdef LIBXO_TEXT_ONLY
4499     return;
4500 #endif /* LIBXO_TEXT_ONLY */
4501 
4502     char *cp, *ep, *np, *xp;
4503     int len = strlen(str);
4504     int rc;
4505 
4506     /*
4507      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4508      */
4509     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4510 	/* Trim leading whitespace */
4511 	while (isspace((int) *cp))
4512 	    cp += 1;
4513 
4514 	np = strchr(cp, ',');
4515 	if (np)
4516 	    *np++ = '\0';
4517 
4518 	/* Trim trailing whitespace */
4519 	xp = cp + strlen(cp) - 1;
4520 	while (isspace(*xp) && xp > cp)
4521 	    *xp-- = '\0';
4522 
4523 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4524 	    rc = xo_color_find(cp + 3);
4525 	    if (rc < 0)
4526 		goto unknown;
4527 
4528 	    xocp->xoc_col_fg = rc;
4529 
4530 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4531 	    rc = xo_color_find(cp + 3);
4532 	    if (rc < 0)
4533 		goto unknown;
4534 	    xocp->xoc_col_bg = rc;
4535 
4536 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4537 	    rc = xo_effect_find(cp + 3);
4538 	    if (rc < 0)
4539 		goto unknown;
4540 	    xocp->xoc_effects &= ~(1 << rc);
4541 
4542 	} else {
4543 	    rc = xo_effect_find(cp);
4544 	    if (rc < 0)
4545 		goto unknown;
4546 	    xocp->xoc_effects |= 1 << rc;
4547 
4548 	    switch (1 << rc) {
4549 	    case XO_EFF_RESET:
4550 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4551 		/* Note: not "|=" since we want to wipe out the old value */
4552 		xocp->xoc_effects = XO_EFF_RESET;
4553 		break;
4554 
4555 	    case XO_EFF_NORMAL:
4556 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4557 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4558 		break;
4559 	    }
4560 	}
4561 	continue;
4562 
4563     unknown:
4564 	if (XOF_ISSET(xop, XOF_WARN))
4565 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4566     }
4567 }
4568 
4569 static inline int
xo_colors_enabled(xo_handle_t * xop UNUSED)4570 xo_colors_enabled (xo_handle_t *xop UNUSED)
4571 {
4572 #ifdef LIBXO_TEXT_ONLY
4573     return 0;
4574 #else /* LIBXO_TEXT_ONLY */
4575     return XOF_ISSET(xop, XOF_COLOR);
4576 #endif /* LIBXO_TEXT_ONLY */
4577 }
4578 
4579 static void
xo_colors_handle_text(xo_handle_t * xop,xo_colors_t * newp)4580 xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4581 {
4582     char buf[BUFSIZ];
4583     char *cp = buf, *ep = buf + sizeof(buf);
4584     unsigned i, bit;
4585     xo_colors_t *oldp = &xop->xo_colors;
4586     const char *code = NULL;
4587 
4588     /*
4589      * Start the buffer with an escape.  We don't want to add the '['
4590      * now, since we let xo_effect_text_add unconditionally add the ';'.
4591      * We'll replace the first ';' with a '[' when we're done.
4592      */
4593     *cp++ = 0x1b;		/* Escape */
4594 
4595     /*
4596      * Terminals were designed back in the age before "certainty" was
4597      * invented, when standards were more what you'd call "guidelines"
4598      * than actual rules.  Anyway we can't depend on them to operate
4599      * correctly.  So when display attributes are changed, we punt,
4600      * reseting them all and turning back on the ones we want to keep.
4601      * Longer, but should be completely reliable.  Savvy?
4602      */
4603     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4604 	newp->xoc_effects |= XO_EFF_RESET;
4605 	oldp->xoc_effects = 0;
4606     }
4607 
4608     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4609 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4610 	    continue;
4611 
4612 	code = xo_effect_on_codes[i];
4613 
4614 	cp += snprintf(cp, ep - cp, ";%s", code);
4615 	if (cp >= ep)
4616 	    return;		/* Should not occur */
4617 
4618 	if (bit == XO_EFF_RESET) {
4619 	    /* Mark up the old value so we can detect current values as new */
4620 	    oldp->xoc_effects = 0;
4621 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4622 	}
4623     }
4624 
4625     if (newp->xoc_col_fg != oldp->xoc_col_fg) {
4626 	cp += snprintf(cp, ep - cp, ";3%u",
4627 		       (newp->xoc_col_fg != XO_COL_DEFAULT)
4628 		       ? newp->xoc_col_fg - 1 : 9);
4629     }
4630 
4631     if (newp->xoc_col_bg != oldp->xoc_col_bg) {
4632 	cp += snprintf(cp, ep - cp, ";4%u",
4633 		       (newp->xoc_col_bg != XO_COL_DEFAULT)
4634 		       ? newp->xoc_col_bg - 1 : 9);
4635     }
4636 
4637     if (cp - buf != 1 && cp < ep - 3) {
4638 	buf[1] = '[';		/* Overwrite leading ';' */
4639 	*cp++ = 'm';
4640 	*cp = '\0';
4641 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4642     }
4643 }
4644 
4645 static void
xo_colors_handle_html(xo_handle_t * xop,xo_colors_t * newp)4646 xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4647 {
4648     xo_colors_t *oldp = &xop->xo_colors;
4649 
4650     /*
4651      * HTML colors are mostly trivial: fill in xo_color_buf with
4652      * a set of class tags representing the colors and effects.
4653      */
4654 
4655     /* If nothing changed, then do nothing */
4656     if (oldp->xoc_effects == newp->xoc_effects
4657 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4658 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4659 	return;
4660 
4661     unsigned i, bit;
4662     xo_buffer_t *xbp = &xop->xo_color_buf;
4663 
4664     xo_buf_reset(xbp);		/* We rebuild content after each change */
4665 
4666     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4667 	if (!(newp->xoc_effects & bit))
4668 	    continue;
4669 
4670 	xo_buf_append_str(xbp, " effect-");
4671 	xo_buf_append_str(xbp, xo_effect_names[i]);
4672     }
4673 
4674     const char *fg = NULL;
4675     const char *bg = NULL;
4676 
4677     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4678 	fg = xo_color_names[newp->xoc_col_fg];
4679     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4680 	bg = xo_color_names[newp->xoc_col_bg];
4681 
4682     if (newp->xoc_effects & XO_EFF_INVERSE) {
4683 	const char *tmp = fg;
4684 	fg = bg;
4685 	bg = tmp;
4686 	if (fg == NULL)
4687 	    fg = "inverse";
4688 	if (bg == NULL)
4689 	    bg = "inverse";
4690 
4691     }
4692 
4693     if (fg) {
4694 	xo_buf_append_str(xbp, " color-fg-");
4695 	xo_buf_append_str(xbp, fg);
4696     }
4697 
4698     if (bg) {
4699 	xo_buf_append_str(xbp, " color-bg-");
4700 	xo_buf_append_str(xbp, bg);
4701     }
4702 }
4703 
4704 static void
xo_format_colors(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4705 xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
4706 		  const char *str, unsigned len)
4707 {
4708     const char *fmt = xfip->xfi_format;
4709     unsigned flen = xfip->xfi_flen;
4710 
4711     xo_buffer_t xb;
4712 
4713     /* If the string is static and we've in an encoding style, bail */
4714     if (len != 0 && xo_style_is_encoding(xop))
4715 	return;
4716 
4717     xo_buf_init(&xb);
4718 
4719     if (len)
4720 	xo_buf_append(&xb, str, len);
4721     else if (flen)
4722 	xo_do_format_field(xop, &xb, fmt, flen, 0);
4723     else
4724 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
4725 
4726     if (xo_colors_enabled(xop)) {
4727 	switch (xo_style(xop)) {
4728 	case XO_STYLE_TEXT:
4729 	case XO_STYLE_HTML:
4730 	    xo_buf_append(&xb, "", 1);
4731 
4732 	    xo_colors_t xoc = xop->xo_colors;
4733 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
4734 
4735 	    if (xo_style(xop) == XO_STYLE_TEXT) {
4736 		/*
4737 		 * Text mode means emitting the colors as ANSI character
4738 		 * codes.  This will allow people who like colors to have
4739 		 * colors.  The issue is, of course conflicting with the
4740 		 * user's perfectly reasonable color scheme.  Which leads
4741 		 * to the hell of LSCOLORS, where even app need to have
4742 		 * customization hooks for adjusting colors.  Instead we
4743 		 * provide a simpler-but-still-annoying answer where one
4744 		 * can map colors to other colors.
4745 		 */
4746 		xo_colors_handle_text(xop, &xoc);
4747 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
4748 
4749 	    } else {
4750 		/*
4751 		 * HTML output is wrapped in divs, so the color information
4752 		 * must appear in every div until cleared.  Most pathetic.
4753 		 * Most unavoidable.
4754 		 */
4755 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
4756 		xo_colors_handle_html(xop, &xoc);
4757 	    }
4758 
4759 	    xop->xo_colors = xoc;
4760 	    break;
4761 
4762 	case XO_STYLE_XML:
4763 	case XO_STYLE_JSON:
4764 	case XO_STYLE_SDPARAMS:
4765 	case XO_STYLE_ENCODER:
4766 	    /*
4767 	     * Nothing to do; we did all that work just to clear the stack of
4768 	     * formatting arguments.
4769 	     */
4770 	    break;
4771 	}
4772     }
4773 
4774     xo_buf_cleanup(&xb);
4775 }
4776 
4777 static void
xo_format_units(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4778 xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
4779 		 const char *str, unsigned len)
4780 {
4781     const char *fmt = xfip->xfi_format;
4782     unsigned flen = xfip->xfi_flen;
4783     xo_xff_flags_t flags = xfip->xfi_flags;
4784 
4785     static char units_start_xml[] = " units=\"";
4786     static char units_start_html[] = " data-units=\"";
4787 
4788     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
4789 	xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
4790 	return;
4791     }
4792 
4793     xo_buffer_t *xbp = &xop->xo_data;
4794     int start = xop->xo_units_offset;
4795     int stop = xbp->xb_curp - xbp->xb_bufp;
4796 
4797     if (xo_style(xop) == XO_STYLE_XML)
4798 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
4799     else if (xo_style(xop) == XO_STYLE_HTML)
4800 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
4801     else
4802 	return;
4803 
4804     if (len)
4805 	xo_data_escape(xop, str, len);
4806     else
4807 	xo_do_format_field(xop, NULL, fmt, flen, flags);
4808 
4809     xo_buf_append(xbp, "\"", 1);
4810 
4811     int now = xbp->xb_curp - xbp->xb_bufp;
4812     int delta = now - stop;
4813     if (delta <= 0) {		/* Strange; no output to move */
4814 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
4815 	return;
4816     }
4817 
4818     /*
4819      * Now we're in it alright.  We've need to insert the unit value
4820      * we just created into the right spot.  We make a local copy,
4821      * move it and then insert our copy.  We know there's room in the
4822      * buffer, since we're just moving this around.
4823      */
4824     char *buf = alloca(delta);
4825 
4826     memcpy(buf, xbp->xb_bufp + stop, delta);
4827     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
4828     memmove(xbp->xb_bufp + start, buf, delta);
4829 }
4830 
4831 static int
xo_find_width(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4832 xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
4833 	       const char *str, unsigned len)
4834 {
4835     const char *fmt = xfip->xfi_format;
4836     unsigned flen = xfip->xfi_flen;
4837 
4838     long width = 0;
4839     char *bp;
4840     char *cp;
4841 
4842     if (len) {
4843 	bp = alloca(len + 1);	/* Make local NUL-terminated copy of str */
4844 	memcpy(bp, str, len);
4845 	bp[len] = '\0';
4846 
4847 	width = strtol(bp, &cp, 0);
4848 	if (width == LONG_MIN || width == LONG_MAX
4849 	    || bp == cp || *cp != '\0' ) {
4850 	    width = 0;
4851 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
4852 	}
4853     } else if (flen) {
4854 	if (flen != 2 || strncmp("%d", fmt, flen) != 0)
4855 	    xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
4856 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
4857 	    width = va_arg(xop->xo_vap, int);
4858     }
4859 
4860     return width;
4861 }
4862 
4863 static void
xo_anchor_clear(xo_handle_t * xop)4864 xo_anchor_clear (xo_handle_t *xop)
4865 {
4866     XOIF_CLEAR(xop, XOIF_ANCHOR);
4867     xop->xo_anchor_offset = 0;
4868     xop->xo_anchor_columns = 0;
4869     xop->xo_anchor_min_width = 0;
4870 }
4871 
4872 /*
4873  * An anchor is a marker used to delay field width implications.
4874  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
4875  * We are looking for output like "     1/4/5"
4876  *
4877  * To make this work, we record the anchor and then return to
4878  * format it when the end anchor tag is seen.
4879  */
4880 static void
xo_anchor_start(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4881 xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
4882 		 const char *str, unsigned len)
4883 {
4884     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
4885 	return;
4886 
4887     if (XOIF_ISSET(xop, XOIF_ANCHOR))
4888 	xo_failure(xop, "the anchor already recording is discarded");
4889 
4890     XOIF_SET(xop, XOIF_ANCHOR);
4891     xo_buffer_t *xbp = &xop->xo_data;
4892     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
4893     xop->xo_anchor_columns = 0;
4894 
4895     /*
4896      * Now we find the width, if possible.  If it's not there,
4897      * we'll get it on the end anchor.
4898      */
4899     xop->xo_anchor_min_width = xo_find_width(xop, xfip, str, len);
4900 }
4901 
4902 static void
xo_anchor_stop(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,unsigned len)4903 xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
4904 		 const char *str, unsigned len)
4905 {
4906     if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
4907 	return;
4908 
4909     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
4910 	xo_failure(xop, "no start anchor");
4911 	return;
4912     }
4913 
4914     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
4915 
4916     int width = xo_find_width(xop, xfip, str, len);
4917     if (width == 0)
4918 	width = xop->xo_anchor_min_width;
4919 
4920     if (width == 0)		/* No width given; nothing to do */
4921 	goto done;
4922 
4923     xo_buffer_t *xbp = &xop->xo_data;
4924     int start = xop->xo_anchor_offset;
4925     int stop = xbp->xb_curp - xbp->xb_bufp;
4926     int abswidth = (width > 0) ? width : -width;
4927     int blen = abswidth - xop->xo_anchor_columns;
4928 
4929     if (blen <= 0)		/* Already over width */
4930 	goto done;
4931 
4932     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
4933 	xo_failure(xop, "width over %u are not supported",
4934 		   XO_MAX_ANCHOR_WIDTH);
4935 	goto done;
4936     }
4937 
4938     /* Make a suitable padding field and emit it */
4939     char *buf = alloca(blen);
4940     memset(buf, ' ', blen);
4941     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
4942 
4943     if (width < 0)		/* Already left justified */
4944 	goto done;
4945 
4946     int now = xbp->xb_curp - xbp->xb_bufp;
4947     int delta = now - stop;
4948     if (delta <= 0)		/* Strange; no output to move */
4949 	goto done;
4950 
4951     /*
4952      * Now we're in it alright.  We've need to insert the padding data
4953      * we just created (which might be an HTML <div> or text) before
4954      * the formatted data.  We make a local copy, move it and then
4955      * insert our copy.  We know there's room in the buffer, since
4956      * we're just moving this around.
4957      */
4958     if (delta > blen)
4959 	buf = alloca(delta);	/* Expand buffer if needed */
4960 
4961     memcpy(buf, xbp->xb_bufp + stop, delta);
4962     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
4963     memmove(xbp->xb_bufp + start, buf, delta);
4964 
4965  done:
4966     xo_anchor_clear(xop);
4967 }
4968 
4969 static const char *
xo_class_name(int ftype)4970 xo_class_name (int ftype)
4971 {
4972     switch (ftype) {
4973     case 'D': return "decoration";
4974     case 'E': return "error";
4975     case 'L': return "label";
4976     case 'N': return "note";
4977     case 'P': return "padding";
4978     case 'W': return "warning";
4979     }
4980 
4981     return NULL;
4982 }
4983 
4984 static const char *
xo_tag_name(int ftype)4985 xo_tag_name (int ftype)
4986 {
4987     switch (ftype) {
4988     case 'E': return "__error";
4989     case 'W': return "__warning";
4990     }
4991 
4992     return NULL;
4993 }
4994 
4995 static int
xo_role_wants_default_format(int ftype)4996 xo_role_wants_default_format (int ftype)
4997 {
4998     switch (ftype) {
4999 	/* These roles can be completely empty and/or without formatting */
5000     case 'C':
5001     case 'G':
5002     case '[':
5003     case ']':
5004 	return 0;
5005     }
5006 
5007     return 1;
5008 }
5009 
5010 static xo_mapping_t xo_role_names[] = {
5011     { 'C', "color" },
5012     { 'D', "decoration" },
5013     { 'E', "error" },
5014     { 'L', "label" },
5015     { 'N', "note" },
5016     { 'P', "padding" },
5017     { 'T', "title" },
5018     { 'U', "units" },
5019     { 'V', "value" },
5020     { 'W', "warning" },
5021     { '[', "start-anchor" },
5022     { ']', "stop-anchor" },
5023     { 0, NULL }
5024 };
5025 
5026 #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
5027 #define XO_ROLE_TEXT	'+'
5028 #define XO_ROLE_NEWLINE	'\n'
5029 
5030 static xo_mapping_t xo_modifier_names[] = {
5031     { XFF_ARGUMENT, "argument" },
5032     { XFF_COLON, "colon" },
5033     { XFF_COMMA, "comma" },
5034     { XFF_DISPLAY_ONLY, "display" },
5035     { XFF_ENCODE_ONLY, "encoding" },
5036     { XFF_GT_FIELD, "gettext" },
5037     { XFF_HUMANIZE, "humanize" },
5038     { XFF_HUMANIZE, "hn" },
5039     { XFF_HN_SPACE, "hn-space" },
5040     { XFF_HN_DECIMAL, "hn-decimal" },
5041     { XFF_HN_1000, "hn-1000" },
5042     { XFF_KEY, "key" },
5043     { XFF_LEAF_LIST, "leaf-list" },
5044     { XFF_LEAF_LIST, "list" },
5045     { XFF_NOQUOTE, "no-quotes" },
5046     { XFF_NOQUOTE, "no-quote" },
5047     { XFF_GT_PLURAL, "plural" },
5048     { XFF_QUOTE, "quotes" },
5049     { XFF_QUOTE, "quote" },
5050     { XFF_TRIM_WS, "trim" },
5051     { XFF_WS, "white" },
5052     { 0, NULL }
5053 };
5054 
5055 #ifdef NOT_NEEDED_YET
5056 static xo_mapping_t xo_modifier_short_names[] = {
5057     { XFF_COLON, "c" },
5058     { XFF_DISPLAY_ONLY, "d" },
5059     { XFF_ENCODE_ONLY, "e" },
5060     { XFF_GT_FIELD, "g" },
5061     { XFF_HUMANIZE, "h" },
5062     { XFF_KEY, "k" },
5063     { XFF_LEAF_LIST, "l" },
5064     { XFF_NOQUOTE, "n" },
5065     { XFF_GT_PLURAL, "p" },
5066     { XFF_QUOTE, "q" },
5067     { XFF_TRIM_WS, "t" },
5068     { XFF_WS, "w" },
5069     { 0, NULL }
5070 };
5071 #endif /* NOT_NEEDED_YET */
5072 
5073 static int
xo_count_fields(xo_handle_t * xop UNUSED,const char * fmt)5074 xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5075 {
5076     int rc = 1;
5077     const char *cp;
5078 
5079     for (cp = fmt; *cp; cp++)
5080 	if (*cp == '{' || *cp == '\n')
5081 	    rc += 1;
5082 
5083     return rc * 2 + 1;
5084 }
5085 
5086 /*
5087  * The field format is:
5088  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5089  * Roles are optional and include the following field types:
5090  *   'D': decoration; something non-text and non-data (colons, commmas)
5091  *   'E': error message
5092  *   'G': gettext() the entire string; optional domainname as content
5093  *   'L': label; text preceding data
5094  *   'N': note; text following data
5095  *   'P': padding; whitespace
5096  *   'T': Title, where 'content' is a column title
5097  *   'U': Units, where 'content' is the unit label
5098  *   'V': value, where 'content' is the name of the field (the default)
5099  *   'W': warning message
5100  *   '[': start a section of anchored text
5101  *   ']': end a section of anchored text
5102  * The following modifiers are also supported:
5103  *   'a': content is provided via argument (const char *), not descriptor
5104  *   'c': flag: emit a colon after the label
5105  *   'd': field is only emitted for display styles (text and html)
5106  *   'e': field is only emitted for encoding styles (xml and json)
5107  *   'g': gettext() the field
5108  *   'h': humanize a numeric value (only for display styles)
5109  *   'k': this field is a key, suitable for XPath predicates
5110  *   'l': a leaf-list, a simple list of values
5111  *   'n': no quotes around this field
5112  *   'p': the field has plural gettext semantics (ngettext)
5113  *   'q': add quotes around this field
5114  *   't': trim whitespace around the value
5115  *   'w': emit a blank after the label
5116  * The print-fmt and encode-fmt strings is the printf-style formating
5117  * for this data.  JSON and XML will use the encoding-fmt, if present.
5118  * If the encode-fmt is not provided, it defaults to the print-fmt.
5119  * If the print-fmt is not provided, it defaults to 's'.
5120  */
5121 static const char *
xo_parse_roles(xo_handle_t * xop,const char * fmt,const char * basep,xo_field_info_t * xfip)5122 xo_parse_roles (xo_handle_t *xop, const char *fmt,
5123 		const char *basep, xo_field_info_t *xfip)
5124 {
5125     const char *sp;
5126     unsigned ftype = 0;
5127     xo_xff_flags_t flags = 0;
5128     uint8_t fnum = 0;
5129 
5130     for (sp = basep; sp && *sp; sp++) {
5131 	if (*sp == ':' || *sp == '/' || *sp == '}')
5132 	    break;
5133 
5134 	if (*sp == '\\') {
5135 	    if (sp[1] == '\0') {
5136 		xo_failure(xop, "backslash at the end of string");
5137 		return NULL;
5138 	    }
5139 
5140 	    /* Anything backslashed is ignored */
5141 	    sp += 1;
5142 	    continue;
5143 	}
5144 
5145 	if (*sp == ',') {
5146 	    const char *np;
5147 	    for (np = ++sp; *np; np++)
5148 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5149 		    break;
5150 
5151 	    int slen = np - sp;
5152 	    if (slen > 0) {
5153 		xo_xff_flags_t value;
5154 
5155 		value = xo_name_lookup(xo_role_names, sp, slen);
5156 		if (value)
5157 		    ftype = value;
5158 		else {
5159 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
5160 		    if (value)
5161 			flags |= value;
5162 		    else
5163 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
5164 				   slen, sp);
5165 		}
5166 	    }
5167 
5168 	    sp = np - 1;
5169 	    continue;
5170 	}
5171 
5172 	switch (*sp) {
5173 	case 'C':
5174 	case 'D':
5175 	case 'E':
5176 	case 'G':
5177 	case 'L':
5178 	case 'N':
5179 	case 'P':
5180 	case 'T':
5181 	case 'U':
5182 	case 'V':
5183 	case 'W':
5184 	case '[':
5185 	case ']':
5186 	    if (ftype != 0) {
5187 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
5188 			   xo_printable(fmt));
5189 		return NULL;
5190 	    }
5191 	    ftype = *sp;
5192 	    break;
5193 
5194 	case '0':
5195 	case '1':
5196 	case '2':
5197 	case '3':
5198 	case '4':
5199 	case '5':
5200 	case '6':
5201 	case '7':
5202 	case '8':
5203 	case '9':
5204 	    fnum = (fnum * 10) + (*sp - '0');
5205 	    break;
5206 
5207 	case 'a':
5208 	    flags |= XFF_ARGUMENT;
5209 	    break;
5210 
5211 	case 'c':
5212 	    flags |= XFF_COLON;
5213 	    break;
5214 
5215 	case 'd':
5216 	    flags |= XFF_DISPLAY_ONLY;
5217 	    break;
5218 
5219 	case 'e':
5220 	    flags |= XFF_ENCODE_ONLY;
5221 	    break;
5222 
5223 	case 'g':
5224 	    flags |= XFF_GT_FIELD;
5225 	    break;
5226 
5227 	case 'h':
5228 	    flags |= XFF_HUMANIZE;
5229 	    break;
5230 
5231 	case 'k':
5232 	    flags |= XFF_KEY;
5233 	    break;
5234 
5235 	case 'l':
5236 	    flags |= XFF_LEAF_LIST;
5237 	    break;
5238 
5239 	case 'n':
5240 	    flags |= XFF_NOQUOTE;
5241 	    break;
5242 
5243 	case 'p':
5244 	    flags |= XFF_GT_PLURAL;
5245 	    break;
5246 
5247 	case 'q':
5248 	    flags |= XFF_QUOTE;
5249 	    break;
5250 
5251 	case 't':
5252 	    flags |= XFF_TRIM_WS;
5253 	    break;
5254 
5255 	case 'w':
5256 	    flags |= XFF_WS;
5257 	    break;
5258 
5259 	default:
5260 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5261 		       xo_printable(fmt));
5262 	    /*
5263 	     * No good answer here; a bad format will likely
5264 	     * mean a core file.  We just return and hope
5265 	     * the caller notices there's no output, and while
5266 	     * that seems, well, bad, there's nothing better.
5267 	     */
5268 	    return NULL;
5269 	}
5270 
5271 	if (ftype == 'N' || ftype == 'U') {
5272 	    if (flags & XFF_COLON) {
5273 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5274 			   "'%s'", xo_printable(fmt));
5275 		flags &= ~XFF_COLON;
5276 	    }
5277 	}
5278     }
5279 
5280     xfip->xfi_flags = flags;
5281     xfip->xfi_ftype = ftype ?: 'V';
5282     xfip->xfi_fnum = fnum;
5283 
5284     return sp;
5285 }
5286 
5287 /*
5288  * Number any remaining fields that need numbers.  Note that some
5289  * field types (text, newline, escaped braces) never get numbers.
5290  */
5291 static void
xo_gettext_finish_numbering_fields(xo_handle_t * xop UNUSED,const char * fmt UNUSED,xo_field_info_t * fields)5292 xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5293 				    const char *fmt UNUSED,
5294 				    xo_field_info_t *fields)
5295 {
5296     xo_field_info_t *xfip;
5297     unsigned fnum, max_fields;
5298     uint64_t bits = 0;
5299 
5300     /* First make a list of add the explicitly used bits */
5301     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5302 	switch (xfip->xfi_ftype) {
5303 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5304 	case XO_ROLE_TEXT:
5305 	case XO_ROLE_EBRACE:
5306 	case 'G':
5307 	    continue;
5308 	}
5309 
5310 	fnum += 1;
5311 	if (fnum >= 63)
5312 	    break;
5313 
5314 	if (xfip->xfi_fnum)
5315 	    bits |= 1 << xfip->xfi_fnum;
5316     }
5317 
5318     max_fields = fnum;
5319 
5320     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5321 	switch (xfip->xfi_ftype) {
5322 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5323 	case XO_ROLE_TEXT:
5324 	case XO_ROLE_EBRACE:
5325 	case 'G':
5326 	    continue;
5327 	}
5328 
5329 	if (xfip->xfi_fnum != 0)
5330 	    continue;
5331 
5332 	/* Find the next unassigned field */
5333 	for (fnum++; bits & (1 << fnum); fnum++)
5334 	    continue;
5335 
5336 	if (fnum > max_fields)
5337 	    break;
5338 
5339 	xfip->xfi_fnum = fnum;	/* Mark the field number */
5340 	bits |= 1 << fnum;	/* Mark it used */
5341     }
5342 }
5343 
5344 /*
5345  * The format string uses field numbers, so we need to whiffle thru it
5346  * and make sure everything's sane and lovely.
5347  */
5348 static int
xo_parse_field_numbers(xo_handle_t * xop,const char * fmt,xo_field_info_t * fields,unsigned num_fields)5349 xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5350 			xo_field_info_t *fields, unsigned num_fields)
5351 {
5352     xo_field_info_t *xfip;
5353     unsigned field, fnum;
5354     uint64_t bits = 0;
5355 
5356     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5357 	/* Fields default to 1:1 with natural position */
5358 	if (xfip->xfi_fnum == 0)
5359 	    xfip->xfi_fnum = field + 1;
5360 	else if (xfip->xfi_fnum > num_fields) {
5361 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5362 	    return -1;
5363 	}
5364 
5365 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5366 	if (fnum < 64) {	/* Only test what fits */
5367 	    if (bits & (1 << fnum)) {
5368 		xo_failure(xop, "field number %u reused: '%s'",
5369 			   xfip->xfi_fnum, fmt);
5370 		return -1;
5371 	    }
5372 	    bits |= 1 << fnum;
5373 	}
5374     }
5375 
5376     return 0;
5377 }
5378 
5379 static int
xo_parse_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned num_fields,const char * fmt)5380 xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5381 		 unsigned num_fields, const char *fmt)
5382 {
5383     const char *cp, *sp, *ep, *basep;
5384     unsigned field = 0;
5385     xo_field_info_t *xfip = fields;
5386     unsigned seen_fnum = 0;
5387 
5388     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5389 	xfip->xfi_start = cp;
5390 
5391 	if (*cp == '\n') {
5392 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5393 	    xfip->xfi_len = 1;
5394 	    cp += 1;
5395 	    continue;
5396 	}
5397 
5398 	if (*cp != '{') {
5399 	    /* Normal text */
5400 	    for (sp = cp; *sp; sp++) {
5401 		if (*sp == '{' || *sp == '\n')
5402 		    break;
5403 	    }
5404 
5405 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5406 	    xfip->xfi_content = cp;
5407 	    xfip->xfi_clen = sp - cp;
5408 	    xfip->xfi_next = sp;
5409 
5410 	    cp = sp;
5411 	    continue;
5412 	}
5413 
5414 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5415 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5416 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5417 
5418 	    cp += 2;	/* Skip over _both_ characters */
5419 	    for (sp = cp; *sp; sp++) {
5420 		if (*sp == '}' && sp[1] == '}')
5421 		    break;
5422 	    }
5423 	    if (*sp == '\0') {
5424 		xo_failure(xop, "missing closing '}}': '%s'",
5425 			   xo_printable(fmt));
5426 		return -1;
5427 	    }
5428 
5429 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5430 
5431 	    /* Move along the string, but don't run off the end */
5432 	    if (*sp == '}' && sp[1] == '}')
5433 		sp += 2;
5434 	    cp = *sp ? sp : sp;
5435 	    xfip->xfi_next = cp;
5436 	    continue;
5437 	}
5438 
5439 	/* We are looking at the start of a field definition */
5440 	xfip->xfi_start = basep = cp + 1;
5441 
5442 	const char *format = NULL;
5443 	int flen = 0;
5444 
5445 	/* Looking at roles and modifiers */
5446 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5447 	if (sp == NULL) {
5448 	    /* xo_failure has already been called */
5449 	    return -1;
5450 	}
5451 
5452 	if (xfip->xfi_fnum)
5453 	    seen_fnum = 1;
5454 
5455 	/* Looking at content */
5456 	if (*sp == ':') {
5457 	    for (ep = ++sp; *sp; sp++) {
5458 		if (*sp == '}' || *sp == '/')
5459 		    break;
5460 		if (*sp == '\\') {
5461 		    if (sp[1] == '\0') {
5462 			xo_failure(xop, "backslash at the end of string");
5463 			return -1;
5464 		    }
5465 		    sp += 1;
5466 		    continue;
5467 		}
5468 	    }
5469 	    if (ep != sp) {
5470 		xfip->xfi_clen = sp - ep;
5471 		xfip->xfi_content = ep;
5472 	    }
5473 	} else {
5474 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
5475 	    return -1;
5476 	}
5477 
5478 	/* Looking at main (display) format */
5479 	if (*sp == '/') {
5480 	    for (ep = ++sp; *sp; sp++) {
5481 		if (*sp == '}' || *sp == '/')
5482 		    break;
5483 		if (*sp == '\\') {
5484 		    if (sp[1] == '\0') {
5485 			xo_failure(xop, "backslash at the end of string");
5486 			return -1;
5487 		    }
5488 		    sp += 1;
5489 		    continue;
5490 		}
5491 	    }
5492 	    flen = sp - ep;
5493 	    format = ep;
5494 	}
5495 
5496 	/* Looking at encoding format */
5497 	if (*sp == '/') {
5498 	    for (ep = ++sp; *sp; sp++) {
5499 		if (*sp == '}')
5500 		    break;
5501 	    }
5502 
5503 	    xfip->xfi_encoding = ep;
5504 	    xfip->xfi_elen = sp - ep;
5505 	}
5506 
5507 	if (*sp != '}') {
5508 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
5509 	    return -1;
5510 	}
5511 
5512 	xfip->xfi_len = sp - xfip->xfi_start;
5513 	xfip->xfi_next = ++sp;
5514 
5515 	/* If we have content, then we have a default format */
5516 	if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5517 	    if (format) {
5518 		xfip->xfi_format = format;
5519 		xfip->xfi_flen = flen;
5520 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
5521 		xfip->xfi_format = xo_default_format;
5522 		xfip->xfi_flen = 2;
5523 	    }
5524 	}
5525 
5526 	cp = sp;
5527     }
5528 
5529     int rc = 0;
5530 
5531     /*
5532      * If we saw a field number on at least one field, then we need
5533      * to enforce some rules and/or guidelines.
5534      */
5535     if (seen_fnum)
5536 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5537 
5538     return rc;
5539 }
5540 
5541 /*
5542  * We are passed a pointer to a format string just past the "{G:}"
5543  * field.  We build a simplified version of the format string.
5544  */
5545 static int
xo_gettext_simplify_format(xo_handle_t * xop UNUSED,xo_buffer_t * xbp,xo_field_info_t * fields,int this_field,const char * fmt UNUSED,xo_simplify_field_func_t field_cb)5546 xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5547 		       xo_buffer_t *xbp,
5548 		       xo_field_info_t *fields,
5549 		       int this_field,
5550 		       const char *fmt UNUSED,
5551 		       xo_simplify_field_func_t field_cb)
5552 {
5553     unsigned ftype;
5554     xo_xff_flags_t flags;
5555     int field = this_field + 1;
5556     xo_field_info_t *xfip;
5557     char ch;
5558 
5559     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5560 	ftype = xfip->xfi_ftype;
5561 	flags = xfip->xfi_flags;
5562 
5563 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5564 	    if (field_cb)
5565 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5566 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5567 	}
5568 
5569 	switch (ftype) {
5570 	case 'G':
5571 	    /* Ignore gettext roles */
5572 	    break;
5573 
5574 	case XO_ROLE_NEWLINE:
5575 	    xo_buf_append(xbp, "\n", 1);
5576 	    break;
5577 
5578 	case XO_ROLE_EBRACE:
5579 	    xo_buf_append(xbp, "{", 1);
5580 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5581 	    xo_buf_append(xbp, "}", 1);
5582 	    break;
5583 
5584 	case XO_ROLE_TEXT:
5585 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5586 	    break;
5587 
5588 	default:
5589 	    xo_buf_append(xbp, "{", 1);
5590 	    if (ftype != 'V') {
5591 		ch = ftype;
5592 		xo_buf_append(xbp, &ch, 1);
5593 	    }
5594 
5595 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5596 	    if (fnum) {
5597 		char num[12];
5598 		/* Field numbers are origin 1, not 0, following printf(3) */
5599 		snprintf(num, sizeof(num), "%u", fnum);
5600 		xo_buf_append(xbp, num, strlen(num));
5601 	    }
5602 
5603 	    xo_buf_append(xbp, ":", 1);
5604 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5605 	    xo_buf_append(xbp, "}", 1);
5606 	}
5607     }
5608 
5609     xo_buf_append(xbp, "", 1);
5610     return 0;
5611 }
5612 
5613 void
5614 xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5615 void
xo_dump_fields(xo_field_info_t * fields)5616 xo_dump_fields (xo_field_info_t *fields)
5617 {
5618     xo_field_info_t *xfip;
5619 
5620     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5621 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5622 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5623 	       (unsigned long) xfip->xfi_flags,
5624 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5625 	       xfip->xfi_ftype,
5626 	       xfip->xfi_clen, xfip->xfi_content ?: "",
5627 	       xfip->xfi_flen, xfip->xfi_format ?: "",
5628 	       xfip->xfi_elen, xfip->xfi_encoding ?: "");
5629     }
5630 }
5631 
5632 #ifdef HAVE_GETTEXT
5633 /*
5634  * Find the field that matches the given field number
5635  */
5636 static xo_field_info_t *
xo_gettext_find_field(xo_field_info_t * fields,unsigned fnum)5637 xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5638 {
5639     xo_field_info_t *xfip;
5640 
5641     for (xfip = fields; xfip->xfi_ftype; xfip++)
5642 	if (xfip->xfi_fnum == fnum)
5643 	    return xfip;
5644 
5645     return NULL;
5646 }
5647 
5648 /*
5649  * At this point, we need to consider if the fields have been reordered,
5650  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5651  *
5652  * We need to rewrite the new_fields using the old fields order,
5653  * so that we can render the message using the arguments as they
5654  * appear on the stack.  It's a lot of work, but we don't really
5655  * want to (eventually) fall into the standard printf code which
5656  * means using the arguments straight (and in order) from the
5657  * varargs we were originally passed.
5658  */
5659 static void
xo_gettext_rewrite_fields(xo_handle_t * xop UNUSED,xo_field_info_t * fields,unsigned max_fields)5660 xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
5661 			   xo_field_info_t *fields, unsigned max_fields)
5662 {
5663     xo_field_info_t tmp[max_fields];
5664     bzero(tmp, max_fields * sizeof(tmp[0]));
5665 
5666     unsigned fnum = 0;
5667     xo_field_info_t *newp, *outp, *zp;
5668     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
5669 	switch (newp->xfi_ftype) {
5670 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5671 	case XO_ROLE_TEXT:
5672 	case XO_ROLE_EBRACE:
5673 	case 'G':
5674 	    *outp = *newp;
5675 	    outp->xfi_renum = 0;
5676 	    continue;
5677 	}
5678 
5679 	zp = xo_gettext_find_field(fields, ++fnum);
5680 	if (zp == NULL) { 	/* Should not occur */
5681 	    *outp = *newp;
5682 	    outp->xfi_renum = 0;
5683 	    continue;
5684 	}
5685 
5686 	*outp = *zp;
5687 	outp->xfi_renum = newp->xfi_fnum;
5688     }
5689 
5690     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
5691 }
5692 
5693 /*
5694  * We've got two lists of fields, the old list from the original
5695  * format string and the new one from the parsed gettext reply.  The
5696  * new list has the localized words, where the old list has the
5697  * formatting information.  We need to combine them into a single list
5698  * (the new list).
5699  *
5700  * If the list needs to be reordered, then we've got more serious work
5701  * to do.
5702  */
5703 static int
xo_gettext_combine_formats(xo_handle_t * xop,const char * fmt UNUSED,const char * gtfmt,xo_field_info_t * old_fields,xo_field_info_t * new_fields,unsigned new_max_fields,int * reorderedp)5704 xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
5705 		    const char *gtfmt, xo_field_info_t *old_fields,
5706 		    xo_field_info_t *new_fields, unsigned new_max_fields,
5707 		    int *reorderedp)
5708 {
5709     int reordered = 0;
5710     xo_field_info_t *newp, *oldp, *startp = old_fields;
5711 
5712     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
5713 
5714     for (newp = new_fields; newp->xfi_ftype; newp++) {
5715 	switch (newp->xfi_ftype) {
5716 	case XO_ROLE_NEWLINE:
5717 	case XO_ROLE_TEXT:
5718 	case XO_ROLE_EBRACE:
5719 	    continue;
5720 
5721 	case 'V':
5722 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5723 		if (oldp->xfi_ftype != 'V')
5724 		    continue;
5725 		if (newp->xfi_clen != oldp->xfi_clen
5726 		    || strncmp(newp->xfi_content, oldp->xfi_content,
5727 			       oldp->xfi_clen) != 0) {
5728 		    reordered = 1;
5729 		    continue;
5730 		}
5731 		startp = oldp + 1;
5732 		break;
5733 	    }
5734 
5735 	    /* Didn't find it on the first pass (starting from start) */
5736 	    if (oldp->xfi_ftype == 0) {
5737 		for (oldp = old_fields; oldp < startp; oldp++) {
5738 		    if (oldp->xfi_ftype != 'V')
5739 			continue;
5740 		    if (newp->xfi_clen != oldp->xfi_clen)
5741 			continue;
5742 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
5743 				oldp->xfi_clen) != 0)
5744 			continue;
5745 		    reordered = 1;
5746 		    break;
5747 		}
5748 		if (oldp == startp) {
5749 		    /* Field not found */
5750 		    xo_failure(xop, "post-gettext format can't find field "
5751 			       "'%.*s' in format '%s'",
5752 			       newp->xfi_clen, newp->xfi_content,
5753 			       xo_printable(gtfmt));
5754 		    return -1;
5755 		}
5756 	    }
5757 	    break;
5758 
5759 	default:
5760 	    /*
5761 	     * Other fields don't have names for us to use, so if
5762 	     * the types aren't the same, then we'll have to assume
5763 	     * the original field is a match.
5764 	     */
5765 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
5766 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
5767 		    break;
5768 		if (oldp->xfi_ftype == newp->xfi_ftype)
5769 		    goto copy_it; /* Assumably we have a match */
5770 	    }
5771 	    continue;
5772 	}
5773 
5774 	/*
5775 	 * Found a match; copy over appropriate fields
5776 	 */
5777     copy_it:
5778 	newp->xfi_flags = oldp->xfi_flags;
5779 	newp->xfi_fnum = oldp->xfi_fnum;
5780 	newp->xfi_format = oldp->xfi_format;
5781 	newp->xfi_flen = oldp->xfi_flen;
5782 	newp->xfi_encoding = oldp->xfi_encoding;
5783 	newp->xfi_elen = oldp->xfi_elen;
5784     }
5785 
5786     *reorderedp = reordered;
5787     if (reordered) {
5788 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
5789 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
5790     }
5791 
5792     return 0;
5793 }
5794 
5795 /*
5796  * We don't want to make gettext() calls here with a complete format
5797  * string, since that means changing a flag would mean a
5798  * labor-intensive re-translation expense.  Instead we build a
5799  * simplified form with a reduced level of detail, perform a lookup on
5800  * that string and then re-insert the formating info.
5801  *
5802  * So something like:
5803  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
5804  * would have a lookup string of:
5805  *   "close {:fd} returned {:error} {:test}\n"
5806  *
5807  * We also need to handling reordering of fields, where the gettext()
5808  * reply string uses fields in a different order than the original
5809  * format string:
5810  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
5811  * If we have to reorder fields within the message, then things get
5812  * complicated.  See xo_gettext_rewrite_fields.
5813  *
5814  * Summary: i18n aighn't cheap.
5815  */
5816 static const char *
xo_gettext_build_format(xo_handle_t * xop,xo_field_info_t * fields,int this_field,const char * fmt,char ** new_fmtp)5817 xo_gettext_build_format (xo_handle_t *xop,
5818 			 xo_field_info_t *fields, int this_field,
5819 			 const char *fmt, char **new_fmtp)
5820 {
5821     if (xo_style_is_encoding(xop))
5822 	goto bail;
5823 
5824     xo_buffer_t xb;
5825     xo_buf_init(&xb);
5826 
5827     if (xo_gettext_simplify_format(xop, &xb, fields,
5828 				   this_field, fmt, NULL))
5829 	goto bail2;
5830 
5831     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
5832     if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
5833 	goto bail2;
5834 
5835     xo_buf_cleanup(&xb);
5836 
5837     char *new_fmt = xo_strndup(gtfmt, -1);
5838     if (new_fmt == NULL)
5839 	goto bail2;
5840 
5841     *new_fmtp = new_fmt;
5842     return new_fmt;
5843 
5844  bail2:
5845 	xo_buf_cleanup(&xb);
5846  bail:
5847     *new_fmtp = NULL;
5848     return fmt;
5849 }
5850 
5851 static void
xo_gettext_rebuild_content(xo_handle_t * xop,xo_field_info_t * fields,unsigned * fstart,unsigned min_fstart,unsigned * fend,unsigned max_fend)5852 xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
5853 			    unsigned *fstart, unsigned min_fstart,
5854 			    unsigned *fend, unsigned max_fend)
5855 {
5856     xo_field_info_t *xfip;
5857     char *buf;
5858     unsigned base = fstart[min_fstart];
5859     unsigned blen = fend[max_fend] - base;
5860     xo_buffer_t *xbp = &xop->xo_data;
5861 
5862     if (blen == 0)
5863 	return;
5864 
5865     buf = xo_realloc(NULL, blen);
5866     if (buf == NULL)
5867 	return;
5868 
5869     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
5870 
5871     unsigned field = min_fstart, soff, doff = base, len, fnum;
5872     xo_field_info_t *zp;
5873 
5874     /*
5875      * Be aware there are two competing views of "field number": we
5876      * want the user to thing in terms of "The {1:size}" where {G:},
5877      * newlines, escaped braces, and text don't have numbers.  But is
5878      * also the internal view, where we have an array of
5879      * xo_field_info_t and every field have an index.  fnum, fstart[]
5880      * and fend[] are the latter, but xfi_renum is the former.
5881      */
5882     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
5883 	fnum = field;
5884 	if (xfip->xfi_renum) {
5885 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
5886 	    fnum = zp ? zp - fields : field;
5887 	}
5888 
5889 	soff = fstart[fnum];
5890 	len = fend[fnum] - soff;
5891 
5892 	if (len > 0) {
5893 	    soff -= base;
5894 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
5895 	    doff += len;
5896 	}
5897     }
5898 
5899     xo_free(buf);
5900 }
5901 #else  /* HAVE_GETTEXT */
5902 static const char *
xo_gettext_build_format(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,int this_field UNUSED,const char * fmt UNUSED,char ** new_fmtp)5903 xo_gettext_build_format (xo_handle_t *xop UNUSED,
5904 			 xo_field_info_t *fields UNUSED,
5905 			 int this_field UNUSED,
5906 			 const char *fmt UNUSED, char **new_fmtp)
5907 {
5908     *new_fmtp = NULL;
5909     return fmt;
5910 }
5911 
5912 static int
xo_gettext_combine_formats(xo_handle_t * xop UNUSED,const char * fmt UNUSED,const char * gtfmt UNUSED,xo_field_info_t * old_fields UNUSED,xo_field_info_t * new_fields UNUSED,unsigned new_max_fields UNUSED,int * reorderedp UNUSED)5913 xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
5914 		    const char *gtfmt UNUSED,
5915 		    xo_field_info_t *old_fields UNUSED,
5916 		    xo_field_info_t *new_fields UNUSED,
5917 		    unsigned new_max_fields UNUSED,
5918 		    int *reorderedp UNUSED)
5919 {
5920     return -1;
5921 }
5922 
5923 static void
xo_gettext_rebuild_content(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,unsigned * fstart UNUSED,unsigned min_fstart UNUSED,unsigned * fend UNUSED,unsigned max_fend UNUSED)5924 xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
5925 		    xo_field_info_t *fields UNUSED,
5926 		    unsigned *fstart UNUSED, unsigned min_fstart UNUSED,
5927 		    unsigned *fend UNUSED, unsigned max_fend UNUSED)
5928 {
5929     return;
5930 }
5931 #endif /* HAVE_GETTEXT */
5932 
5933 /*
5934  * Emit a set of fields.  This is really the core of libxo.
5935  */
5936 static int
xo_do_emit_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned max_fields,const char * fmt)5937 xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
5938 		   unsigned max_fields, const char *fmt)
5939 {
5940     int gettext_inuse = 0;
5941     int gettext_changed = 0;
5942     int gettext_reordered = 0;
5943     unsigned ftype;
5944     xo_xff_flags_t flags;
5945     xo_field_info_t *new_fields = NULL;
5946     xo_field_info_t *xfip;
5947     unsigned field;
5948     int rc = 0;
5949 
5950     int flush = XOF_ISSET(xop, XOF_FLUSH);
5951     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
5952     char *new_fmt = NULL;
5953 
5954     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
5955 	flush_line = 0;
5956 
5957     /*
5958      * Some overhead for gettext; if the fields in the msgstr returned
5959      * by gettext are reordered, then we need to record start and end
5960      * for each field.  We'll go ahead and render the fields in the
5961      * normal order, but later we can then reconstruct the reordered
5962      * fields using these fstart/fend values.
5963      */
5964     unsigned flimit = max_fields * 2; /* Pessimistic limit */
5965     unsigned min_fstart = flimit - 1;
5966     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
5967     unsigned fstart[flimit];
5968     bzero(fstart, flimit * sizeof(fstart[0]));
5969     unsigned fend[flimit];
5970     bzero(fend, flimit * sizeof(fend[0]));
5971 
5972     for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields;
5973 	 xfip++, field++) {
5974 	ftype = xfip->xfi_ftype;
5975 	flags = xfip->xfi_flags;
5976 
5977 	/* Record field start offset */
5978 	if (gettext_reordered) {
5979 	    fstart[field] = xo_buf_offset(&xop->xo_data);
5980 	    if (min_fstart > field)
5981 		min_fstart = field;
5982 	}
5983 
5984 	const char *content = xfip->xfi_content;
5985 	int clen = xfip->xfi_clen;
5986 
5987 	if (flags & XFF_ARGUMENT) {
5988 	    /*
5989 	     * Argument flag means the content isn't given in the descriptor,
5990 	     * but as a UTF-8 string ('const char *') argument in xo_vap.
5991 	     */
5992 	    content = va_arg(xop->xo_vap, char *);
5993 	    clen = content ? strlen(content) : 0;
5994 	}
5995 
5996 	if (ftype == XO_ROLE_NEWLINE) {
5997 	    xo_line_close(xop);
5998 	    if (flush_line && xo_flush_h(xop) < 0)
5999 		return -1;
6000 	    goto bottom;
6001 
6002 	} else if (ftype == XO_ROLE_EBRACE) {
6003 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6004 	    goto bottom;
6005 
6006 	} else if (ftype == XO_ROLE_TEXT) {
6007 	    /* Normal text */
6008 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6009 	    goto bottom;
6010 	}
6011 
6012 	/*
6013 	 * Notes and units need the 'w' flag handled before the content.
6014 	 */
6015 	if (ftype == 'N' || ftype == 'U') {
6016 	    if (flags & XFF_WS) {
6017 		xo_format_content(xop, "padding", NULL, " ", 1,
6018 				  NULL, 0, flags);
6019 		flags &= ~XFF_WS; /* Block later handling of this */
6020 	    }
6021 	}
6022 
6023 	if (ftype == 'V')
6024 	    xo_format_value(xop, content, clen,
6025 			    xfip->xfi_format, xfip->xfi_flen,
6026 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
6027 	else if (ftype == '[')
6028 	    xo_anchor_start(xop, xfip, content, clen);
6029 	else if (ftype == ']')
6030 	    xo_anchor_stop(xop, xfip, content, clen);
6031 	else if (ftype == 'C')
6032 	    xo_format_colors(xop, xfip, content, clen);
6033 
6034 	else if (ftype == 'G') {
6035 	    /*
6036 	     * A {G:domain} field; disect the domain name and translate
6037 	     * the remaining portion of the input string.  If the user
6038 	     * didn't put the {G:} at the start of the format string, then
6039 	     * assumably they just want us to translate the rest of it.
6040 	     * Since gettext returns strings in a static buffer, we make
6041 	     * a copy in new_fmt.
6042 	     */
6043 	    xo_set_gettext_domain(xop, xfip, content, clen);
6044 
6045 	    if (!gettext_inuse) { /* Only translate once */
6046 		gettext_inuse = 1;
6047 		if (new_fmt) {
6048 		    xo_free(new_fmt);
6049 		    new_fmt = NULL;
6050 		}
6051 
6052 		xo_gettext_build_format(xop, fields, field,
6053 					xfip->xfi_next, &new_fmt);
6054 		if (new_fmt) {
6055 		    gettext_changed = 1;
6056 
6057 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6058 
6059 		    if (++new_max_fields < max_fields)
6060 			new_max_fields = max_fields;
6061 
6062 		    /* Leave a blank slot at the beginning */
6063 		    int sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6064 		    new_fields = alloca(sz);
6065 		    bzero(new_fields, sz);
6066 
6067 		    if (!xo_parse_fields(xop, new_fields + 1,
6068 					 new_max_fields, new_fmt)) {
6069 			gettext_reordered = 0;
6070 
6071 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6072 					fields, new_fields + 1,
6073 					new_max_fields, &gettext_reordered)) {
6074 
6075 			    if (gettext_reordered) {
6076 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6077 				    xo_failure(xop, "gettext finds reordered "
6078 					       "fields in '%s' and '%s'",
6079 					       xo_printable(fmt),
6080 					       xo_printable(new_fmt));
6081 				flush_line = 0; /* Must keep at content */
6082 				XOIF_SET(xop, XOIF_REORDER);
6083 			    }
6084 
6085 			    field = -1; /* Will be incremented at top of loop */
6086 			    xfip = new_fields;
6087 			    max_fields = new_max_fields;
6088 			}
6089 		    }
6090 		}
6091 	    }
6092 	    continue;
6093 
6094 	} else  if (clen || xfip->xfi_format) {
6095 
6096 	    const char *class_name = xo_class_name(ftype);
6097 	    if (class_name)
6098 		xo_format_content(xop, class_name, xo_tag_name(ftype),
6099 				  content, clen,
6100 				  xfip->xfi_format, xfip->xfi_flen, flags);
6101 	    else if (ftype == 'T')
6102 		xo_format_title(xop, xfip, content, clen);
6103 	    else if (ftype == 'U')
6104 		xo_format_units(xop, xfip, content, clen);
6105 	    else
6106 		xo_failure(xop, "unknown field type: '%c'", ftype);
6107 	}
6108 
6109 	if (flags & XFF_COLON)
6110 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
6111 
6112 	if (flags & XFF_WS)
6113 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6114 
6115     bottom:
6116 	/* Record the end-of-field offset */
6117 	if (gettext_reordered) {
6118 	    fend[field] = xo_buf_offset(&xop->xo_data);
6119 	    max_fend = field;
6120 	}
6121     }
6122 
6123     if (gettext_changed && gettext_reordered) {
6124 	/* Final step: rebuild the content using the rendered fields */
6125 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6126 				   fend, max_fend);
6127     }
6128 
6129     XOIF_CLEAR(xop, XOIF_REORDER);
6130 
6131     /* If we don't have an anchor, write the text out */
6132     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6133 	if (xo_write(xop) < 0)
6134 	    rc = -1;		/* Report failure */
6135 	else if (xo_flush_h(xop) < 0)
6136 	    rc = -1;
6137     }
6138 
6139     if (new_fmt)
6140 	xo_free(new_fmt);
6141 
6142     /*
6143      * We've carried the gettext domainname inside our handle just for
6144      * convenience, but we need to ensure it doesn't survive across
6145      * xo_emit calls.
6146      */
6147     if (xop->xo_gt_domain) {
6148 	xo_free(xop->xo_gt_domain);
6149 	xop->xo_gt_domain = NULL;
6150     }
6151 
6152     return (rc < 0) ? rc : (int) xop->xo_columns;
6153 }
6154 
6155 /*
6156  * Parse and emit a set of fields
6157  */
6158 static int
xo_do_emit(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt)6159 xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
6160 {
6161     xop->xo_columns = 0;	/* Always reset it */
6162     xop->xo_errno = errno;	/* Save for "%m" */
6163 
6164     if (fmt == NULL)
6165 	return 0;
6166 
6167     unsigned max_fields;
6168     xo_field_info_t *fields = NULL;
6169 
6170     /* Adjust XOEF_RETAIN based on global flags */
6171     if (XOF_ISSET(xop, XOF_RETAIN_ALL))
6172 	flags |= XOEF_RETAIN;
6173     if (XOF_ISSET(xop, XOF_RETAIN_NONE))
6174 	flags &= ~XOEF_RETAIN;
6175 
6176     /*
6177      * Check for 'retain' flag, telling us to retain the field
6178      * information.  If we've already saved it, then we can avoid
6179      * re-parsing the format string.
6180      */
6181     if (!(flags & XOEF_RETAIN)
6182 	|| xo_retain_find(fmt, &fields, &max_fields) != 0
6183 	|| fields == NULL) {
6184 
6185 	/* Nothing retained; parse the format string */
6186 	max_fields = xo_count_fields(xop, fmt);
6187 	fields = alloca(max_fields * sizeof(fields[0]));
6188 	bzero(fields, max_fields * sizeof(fields[0]));
6189 
6190 	if (xo_parse_fields(xop, fields, max_fields, fmt))
6191 	    return -1;		/* Warning already displayed */
6192 
6193 	if (flags & XOEF_RETAIN) {
6194 	    /* Retain the info */
6195 	    xo_retain_add(fmt, fields, max_fields);
6196 	}
6197     }
6198 
6199     return xo_do_emit_fields(xop, fields, max_fields, fmt);
6200 }
6201 
6202 /*
6203  * Rebuild a format string in a gettext-friendly format.  This function
6204  * is exposed to tools can perform this function.  See xo(1).
6205  */
6206 char *
xo_simplify_format(xo_handle_t * xop,const char * fmt,int with_numbers,xo_simplify_field_func_t field_cb)6207 xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6208 		    xo_simplify_field_func_t field_cb)
6209 {
6210     xop = xo_default(xop);
6211 
6212     xop->xo_columns = 0;	/* Always reset it */
6213     xop->xo_errno = errno;	/* Save for "%m" */
6214 
6215     unsigned max_fields = xo_count_fields(xop, fmt);
6216     xo_field_info_t fields[max_fields];
6217 
6218     bzero(fields, max_fields * sizeof(fields[0]));
6219 
6220     if (xo_parse_fields(xop, fields, max_fields, fmt))
6221 	return NULL;		/* Warning already displayed */
6222 
6223     xo_buffer_t xb;
6224     xo_buf_init(&xb);
6225 
6226     if (with_numbers)
6227 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
6228 
6229     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6230 	return NULL;
6231 
6232     return xb.xb_bufp;
6233 }
6234 
6235 int
xo_emit_hv(xo_handle_t * xop,const char * fmt,va_list vap)6236 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
6237 {
6238     int rc;
6239 
6240     xop = xo_default(xop);
6241     va_copy(xop->xo_vap, vap);
6242     rc = xo_do_emit(xop, 0, fmt);
6243     va_end(xop->xo_vap);
6244     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6245 
6246     return rc;
6247 }
6248 
6249 int
xo_emit_h(xo_handle_t * xop,const char * fmt,...)6250 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
6251 {
6252     int rc;
6253 
6254     xop = xo_default(xop);
6255     va_start(xop->xo_vap, fmt);
6256     rc = xo_do_emit(xop, 0, fmt);
6257     va_end(xop->xo_vap);
6258     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6259 
6260     return rc;
6261 }
6262 
6263 int
xo_emit(const char * fmt,...)6264 xo_emit (const char *fmt, ...)
6265 {
6266     xo_handle_t *xop = xo_default(NULL);
6267     int rc;
6268 
6269     va_start(xop->xo_vap, fmt);
6270     rc = xo_do_emit(xop, 0, fmt);
6271     va_end(xop->xo_vap);
6272     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6273 
6274     return rc;
6275 }
6276 
6277 int
xo_emit_hvf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,va_list vap)6278 xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
6279 	     const char *fmt, va_list vap)
6280 {
6281     int rc;
6282 
6283     xop = xo_default(xop);
6284     va_copy(xop->xo_vap, vap);
6285     rc = xo_do_emit(xop, flags, fmt);
6286     va_end(xop->xo_vap);
6287     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6288 
6289     return rc;
6290 }
6291 
6292 int
xo_emit_hf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,...)6293 xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
6294 {
6295     int rc;
6296 
6297     xop = xo_default(xop);
6298     va_start(xop->xo_vap, fmt);
6299     rc = xo_do_emit(xop, flags, fmt);
6300     va_end(xop->xo_vap);
6301     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6302 
6303     return rc;
6304 }
6305 
6306 int
xo_emit_f(xo_emit_flags_t flags,const char * fmt,...)6307 xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
6308 {
6309     xo_handle_t *xop = xo_default(NULL);
6310     int rc;
6311 
6312     va_start(xop->xo_vap, fmt);
6313     rc = xo_do_emit(xop, flags, fmt);
6314     va_end(xop->xo_vap);
6315     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6316 
6317     return rc;
6318 }
6319 
6320 /*
6321  * Emit a single field by providing the info information typically provided
6322  * inside the field description (role, modifiers, and formats).  This is
6323  * a convenience function to avoid callers using snprintf to build field
6324  * descriptions.
6325  */
6326 int
xo_emit_field_hv(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,va_list vap)6327 xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
6328 		  const char *fmt, const char *efmt,
6329 		  va_list vap)
6330 {
6331     int rc;
6332 
6333     xop = xo_default(xop);
6334 
6335     if (rolmod == NULL)
6336 	rolmod = "V";
6337 
6338     xo_field_info_t xfi;
6339 
6340     bzero(&xfi, sizeof(xfi));
6341 
6342     const char *cp;
6343     cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
6344     if (cp == NULL)
6345 	return -1;
6346 
6347     xfi.xfi_start = fmt;
6348     xfi.xfi_content = contents;
6349     xfi.xfi_format = fmt;
6350     xfi.xfi_encoding = efmt;
6351     xfi.xfi_clen = contents ? strlen(contents) : 0;
6352     xfi.xfi_flen = fmt ? strlen(fmt) : 0;
6353     xfi.xfi_elen = efmt ? strlen(efmt) : 0;
6354 
6355     /* If we have content, then we have a default format */
6356     if (contents && fmt == NULL
6357 		&& xo_role_wants_default_format(xfi.xfi_ftype)) {
6358 	xfi.xfi_format = xo_default_format;
6359 	xfi.xfi_flen = 2;
6360     }
6361 
6362 
6363 
6364     va_copy(xop->xo_vap, vap);
6365 
6366     rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
6367 
6368     va_end(xop->xo_vap);
6369 
6370     return rc;
6371 }
6372 
6373 int
xo_emit_field_h(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)6374 xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
6375 		 const char *fmt, const char *efmt, ...)
6376 {
6377     int rc;
6378     va_list vap;
6379 
6380     va_start(vap, efmt);
6381     rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
6382     va_end(vap);
6383 
6384     return rc;
6385 }
6386 
6387 int
xo_emit_field(const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)6388 xo_emit_field (const char *rolmod, const char *contents,
6389 	       const char *fmt, const char *efmt, ...)
6390 {
6391     int rc;
6392     va_list vap;
6393 
6394     va_start(vap, efmt);
6395     rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
6396     va_end(vap);
6397 
6398     return rc;
6399 }
6400 
6401 int
xo_attr_hv(xo_handle_t * xop,const char * name,const char * fmt,va_list vap)6402 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
6403 {
6404     const int extra = 5; 	/* space, equals, quote, quote, and nul */
6405     xop = xo_default(xop);
6406 
6407     int rc = 0;
6408     int nlen = strlen(name);
6409     xo_buffer_t *xbp = &xop->xo_attrs;
6410     unsigned name_offset, value_offset;
6411 
6412     switch (xo_style(xop)) {
6413     case XO_STYLE_XML:
6414 	if (!xo_buf_has_room(xbp, nlen + extra))
6415 	    return -1;
6416 
6417 	*xbp->xb_curp++ = ' ';
6418 	memcpy(xbp->xb_curp, name, nlen);
6419 	xbp->xb_curp += nlen;
6420 	*xbp->xb_curp++ = '=';
6421 	*xbp->xb_curp++ = '"';
6422 
6423 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6424 
6425 	if (rc >= 0) {
6426 	    rc = xo_escape_xml(xbp, rc, 1);
6427 	    xbp->xb_curp += rc;
6428 	}
6429 
6430 	if (!xo_buf_has_room(xbp, 2))
6431 	    return -1;
6432 
6433 	*xbp->xb_curp++ = '"';
6434 	*xbp->xb_curp = '\0';
6435 
6436 	rc += nlen + extra;
6437 	break;
6438 
6439     case XO_STYLE_ENCODER:
6440 	name_offset = xo_buf_offset(xbp);
6441 	xo_buf_append(xbp, name, nlen);
6442 	xo_buf_append(xbp, "", 1);
6443 
6444 	value_offset = xo_buf_offset(xbp);
6445 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6446 	if (rc >= 0) {
6447 	    xbp->xb_curp += rc;
6448 	    *xbp->xb_curp = '\0';
6449 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6450 				   xo_buf_data(xbp, name_offset),
6451 				   xo_buf_data(xbp, value_offset));
6452 	}
6453     }
6454 
6455     return rc;
6456 }
6457 
6458 int
xo_attr_h(xo_handle_t * xop,const char * name,const char * fmt,...)6459 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
6460 {
6461     int rc;
6462     va_list vap;
6463 
6464     va_start(vap, fmt);
6465     rc = xo_attr_hv(xop, name, fmt, vap);
6466     va_end(vap);
6467 
6468     return rc;
6469 }
6470 
6471 int
xo_attr(const char * name,const char * fmt,...)6472 xo_attr (const char *name, const char *fmt, ...)
6473 {
6474     int rc;
6475     va_list vap;
6476 
6477     va_start(vap, fmt);
6478     rc = xo_attr_hv(NULL, name, fmt, vap);
6479     va_end(vap);
6480 
6481     return rc;
6482 }
6483 
6484 static void
xo_stack_set_flags(xo_handle_t * xop)6485 xo_stack_set_flags (xo_handle_t *xop)
6486 {
6487     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
6488 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6489 
6490 	xsp->xs_flags |= XSF_NOT_FIRST;
6491 	XOF_CLEAR(xop, XOF_NOT_FIRST);
6492     }
6493 }
6494 
6495 static void
xo_depth_change(xo_handle_t * xop,const char * name,int delta,int indent,xo_state_t state,xo_xsf_flags_t flags)6496 xo_depth_change (xo_handle_t *xop, const char *name,
6497 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
6498 {
6499     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6500 	indent = 0;
6501 
6502     if (XOF_ISSET(xop, XOF_DTRT))
6503 	flags |= XSF_DTRT;
6504 
6505     if (delta >= 0) {			/* Push operation */
6506 	if (xo_depth_check(xop, xop->xo_depth + delta))
6507 	    return;
6508 
6509 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
6510 	xsp->xs_flags = flags;
6511 	xsp->xs_state = state;
6512 	xo_stack_set_flags(xop);
6513 
6514 	if (name == NULL)
6515 	    name = XO_FAILURE_NAME;
6516 
6517 	xsp->xs_name = xo_strndup(name, -1);
6518 
6519     } else {			/* Pop operation */
6520 	if (xop->xo_depth == 0) {
6521 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
6522 		xo_failure(xop, "close with empty stack: '%s'", name);
6523 	    return;
6524 	}
6525 
6526 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6527 	if (XOF_ISSET(xop, XOF_WARN)) {
6528 	    const char *top = xsp->xs_name;
6529 	    if (top && strcmp(name, top) != 0) {
6530 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
6531 			      name, top);
6532 		return;
6533 	    }
6534 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
6535 		xo_failure(xop, "list close on list confict: '%s'",
6536 			      name);
6537 		return;
6538 	    }
6539 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
6540 		xo_failure(xop, "list close on instance confict: '%s'",
6541 			      name);
6542 		return;
6543 	    }
6544 	}
6545 
6546 	if (xsp->xs_name) {
6547 	    xo_free(xsp->xs_name);
6548 	    xsp->xs_name = NULL;
6549 	}
6550 	if (xsp->xs_keys) {
6551 	    xo_free(xsp->xs_keys);
6552 	    xsp->xs_keys = NULL;
6553 	}
6554     }
6555 
6556     xop->xo_depth += delta;	/* Record new depth */
6557     xop->xo_indent += indent;
6558 }
6559 
6560 void
xo_set_depth(xo_handle_t * xop,int depth)6561 xo_set_depth (xo_handle_t *xop, int depth)
6562 {
6563     xop = xo_default(xop);
6564 
6565     if (xo_depth_check(xop, depth))
6566 	return;
6567 
6568     xop->xo_depth += depth;
6569     xop->xo_indent += depth;
6570 }
6571 
6572 static xo_xsf_flags_t
xo_stack_flags(unsigned xflags)6573 xo_stack_flags (unsigned xflags)
6574 {
6575     if (xflags & XOF_DTRT)
6576 	return XSF_DTRT;
6577     return 0;
6578 }
6579 
6580 static void
xo_emit_top(xo_handle_t * xop,const char * ppn)6581 xo_emit_top (xo_handle_t *xop, const char *ppn)
6582 {
6583     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6584     XOIF_SET(xop, XOIF_TOP_EMITTED);
6585 
6586     if (xop->xo_version) {
6587 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6588 		  xo_indent(xop), "", xop->xo_version, ppn);
6589 	xo_free(xop->xo_version);
6590 	xop->xo_version = NULL;
6591     }
6592 }
6593 
6594 static int
xo_do_open_container(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)6595 xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6596 {
6597     int rc = 0;
6598     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6599     const char *pre_nl = "";
6600 
6601     if (name == NULL) {
6602 	xo_failure(xop, "NULL passed for container name");
6603 	name = XO_FAILURE_NAME;
6604     }
6605 
6606     flags |= xop->xo_flags;	/* Pick up handle flags */
6607 
6608     switch (xo_style(xop)) {
6609     case XO_STYLE_XML:
6610 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
6611 
6612 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6613 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6614 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6615 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6616 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6617 	}
6618 
6619 	rc += xo_printf(xop, ">%s", ppn);
6620 	break;
6621 
6622     case XO_STYLE_JSON:
6623 	xo_stack_set_flags(xop);
6624 
6625 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6626 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6627 	    xo_emit_top(xop, ppn);
6628 
6629 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6630 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6631 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6632 
6633 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
6634 		       pre_nl, xo_indent(xop), "", name, ppn);
6635 	break;
6636 
6637     case XO_STYLE_SDPARAMS:
6638 	break;
6639 
6640     case XO_STYLE_ENCODER:
6641 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL);
6642 	break;
6643     }
6644 
6645     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
6646 		    xo_stack_flags(flags));
6647 
6648     return rc;
6649 }
6650 
6651 static int
xo_open_container_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)6652 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6653 {
6654     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
6655 }
6656 
6657 int
xo_open_container_h(xo_handle_t * xop,const char * name)6658 xo_open_container_h (xo_handle_t *xop, const char *name)
6659 {
6660     return xo_open_container_hf(xop, 0, name);
6661 }
6662 
6663 int
xo_open_container(const char * name)6664 xo_open_container (const char *name)
6665 {
6666     return xo_open_container_hf(NULL, 0, name);
6667 }
6668 
6669 int
xo_open_container_hd(xo_handle_t * xop,const char * name)6670 xo_open_container_hd (xo_handle_t *xop, const char *name)
6671 {
6672     return xo_open_container_hf(xop, XOF_DTRT, name);
6673 }
6674 
6675 int
xo_open_container_d(const char * name)6676 xo_open_container_d (const char *name)
6677 {
6678     return xo_open_container_hf(NULL, XOF_DTRT, name);
6679 }
6680 
6681 static int
xo_do_close_container(xo_handle_t * xop,const char * name)6682 xo_do_close_container (xo_handle_t *xop, const char *name)
6683 {
6684     xop = xo_default(xop);
6685 
6686     int rc = 0;
6687     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6688     const char *pre_nl = "";
6689 
6690     if (name == NULL) {
6691 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6692 
6693 	name = xsp->xs_name;
6694 	if (name) {
6695 	    int len = strlen(name) + 1;
6696 	    /* We need to make a local copy; xo_depth_change will free it */
6697 	    char *cp = alloca(len);
6698 	    memcpy(cp, name, len);
6699 	    name = cp;
6700 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6701 	    xo_failure(xop, "missing name without 'dtrt' mode");
6702 	    name = XO_FAILURE_NAME;
6703 	}
6704     }
6705 
6706     switch (xo_style(xop)) {
6707     case XO_STYLE_XML:
6708 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
6709 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
6710 	break;
6711 
6712     case XO_STYLE_JSON:
6713 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6714 	ppn = (xop->xo_depth <= 1) ? "\n" : "";
6715 
6716 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
6717 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
6718 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6719 	break;
6720 
6721     case XO_STYLE_HTML:
6722     case XO_STYLE_TEXT:
6723 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
6724 	break;
6725 
6726     case XO_STYLE_SDPARAMS:
6727 	break;
6728 
6729     case XO_STYLE_ENCODER:
6730 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
6731 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL);
6732 	break;
6733     }
6734 
6735     return rc;
6736 }
6737 
6738 int
xo_close_container_h(xo_handle_t * xop,const char * name)6739 xo_close_container_h (xo_handle_t *xop, const char *name)
6740 {
6741     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
6742 }
6743 
6744 int
xo_close_container(const char * name)6745 xo_close_container (const char *name)
6746 {
6747     return xo_close_container_h(NULL, name);
6748 }
6749 
6750 int
xo_close_container_hd(xo_handle_t * xop)6751 xo_close_container_hd (xo_handle_t *xop)
6752 {
6753     return xo_close_container_h(xop, NULL);
6754 }
6755 
6756 int
xo_close_container_d(void)6757 xo_close_container_d (void)
6758 {
6759     return xo_close_container_h(NULL, NULL);
6760 }
6761 
6762 static int
xo_do_open_list(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name)6763 xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6764 {
6765     int rc = 0;
6766     int indent = 0;
6767 
6768     xop = xo_default(xop);
6769 
6770     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6771     const char *pre_nl = "";
6772 
6773     switch (xo_style(xop)) {
6774     case XO_STYLE_JSON:
6775 
6776 	indent = 1;
6777 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6778 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6779 	    xo_emit_top(xop, ppn);
6780 
6781 	if (name == NULL) {
6782 	    xo_failure(xop, "NULL passed for list name");
6783 	    name = XO_FAILURE_NAME;
6784 	}
6785 
6786 	xo_stack_set_flags(xop);
6787 
6788 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6789 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6790 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6791 
6792 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
6793 		       pre_nl, xo_indent(xop), "", name, ppn);
6794 	break;
6795 
6796     case XO_STYLE_ENCODER:
6797 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL);
6798 	break;
6799     }
6800 
6801     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
6802 		    XSF_LIST | xo_stack_flags(flags));
6803 
6804     return rc;
6805 }
6806 
6807 static int
xo_open_list_hf(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name)6808 xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6809 {
6810     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
6811 }
6812 
6813 int
xo_open_list_h(xo_handle_t * xop,const char * name)6814 xo_open_list_h (xo_handle_t *xop, const char *name)
6815 {
6816     return xo_open_list_hf(xop, 0, name);
6817 }
6818 
6819 int
xo_open_list(const char * name)6820 xo_open_list (const char *name)
6821 {
6822     return xo_open_list_hf(NULL, 0, name);
6823 }
6824 
6825 int
xo_open_list_hd(xo_handle_t * xop,const char * name)6826 xo_open_list_hd (xo_handle_t *xop, const char *name)
6827 {
6828     return xo_open_list_hf(xop, XOF_DTRT, name);
6829 }
6830 
6831 int
xo_open_list_d(const char * name)6832 xo_open_list_d (const char *name)
6833 {
6834     return xo_open_list_hf(NULL, XOF_DTRT, name);
6835 }
6836 
6837 static int
xo_do_close_list(xo_handle_t * xop,const char * name)6838 xo_do_close_list (xo_handle_t *xop, const char *name)
6839 {
6840     int rc = 0;
6841     const char *pre_nl = "";
6842 
6843     if (name == NULL) {
6844 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6845 
6846 	name = xsp->xs_name;
6847 	if (name) {
6848 	    int len = strlen(name) + 1;
6849 	    /* We need to make a local copy; xo_depth_change will free it */
6850 	    char *cp = alloca(len);
6851 	    memcpy(cp, name, len);
6852 	    name = cp;
6853 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6854 	    xo_failure(xop, "missing name without 'dtrt' mode");
6855 	    name = XO_FAILURE_NAME;
6856 	}
6857     }
6858 
6859     switch (xo_style(xop)) {
6860     case XO_STYLE_JSON:
6861 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6862 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6863 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6864 
6865 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
6866 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
6867 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6868 	break;
6869 
6870     case XO_STYLE_ENCODER:
6871 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6872 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL);
6873 	break;
6874 
6875     default:
6876 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
6877 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6878 	break;
6879     }
6880 
6881     return rc;
6882 }
6883 
6884 int
xo_close_list_h(xo_handle_t * xop,const char * name)6885 xo_close_list_h (xo_handle_t *xop, const char *name)
6886 {
6887     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
6888 }
6889 
6890 int
xo_close_list(const char * name)6891 xo_close_list (const char *name)
6892 {
6893     return xo_close_list_h(NULL, name);
6894 }
6895 
6896 int
xo_close_list_hd(xo_handle_t * xop)6897 xo_close_list_hd (xo_handle_t *xop)
6898 {
6899     return xo_close_list_h(xop, NULL);
6900 }
6901 
6902 int
xo_close_list_d(void)6903 xo_close_list_d (void)
6904 {
6905     return xo_close_list_h(NULL, NULL);
6906 }
6907 
6908 static int
xo_do_open_leaf_list(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name)6909 xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
6910 {
6911     int rc = 0;
6912     int indent = 0;
6913 
6914     xop = xo_default(xop);
6915 
6916     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6917     const char *pre_nl = "";
6918 
6919     switch (xo_style(xop)) {
6920     case XO_STYLE_JSON:
6921 	indent = 1;
6922 
6923 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
6924 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
6925 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6926 		XOIF_SET(xop, XOIF_TOP_EMITTED);
6927 	    }
6928 	}
6929 
6930 	if (name == NULL) {
6931 	    xo_failure(xop, "NULL passed for list name");
6932 	    name = XO_FAILURE_NAME;
6933 	}
6934 
6935 	xo_stack_set_flags(xop);
6936 
6937 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6938 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6939 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6940 
6941 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
6942 		       pre_nl, xo_indent(xop), "", name, ppn);
6943 	break;
6944 
6945     case XO_STYLE_ENCODER:
6946 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL);
6947 	break;
6948     }
6949 
6950     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
6951 		    XSF_LIST | xo_stack_flags(flags));
6952 
6953     return rc;
6954 }
6955 
6956 static int
xo_do_close_leaf_list(xo_handle_t * xop,const char * name)6957 xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
6958 {
6959     int rc = 0;
6960     const char *pre_nl = "";
6961 
6962     if (name == NULL) {
6963 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6964 
6965 	name = xsp->xs_name;
6966 	if (name) {
6967 	    int len = strlen(name) + 1;
6968 	    /* We need to make a local copy; xo_depth_change will free it */
6969 	    char *cp = alloca(len);
6970 	    memcpy(cp, name, len);
6971 	    name = cp;
6972 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
6973 	    xo_failure(xop, "missing name without 'dtrt' mode");
6974 	    name = XO_FAILURE_NAME;
6975 	}
6976     }
6977 
6978     switch (xo_style(xop)) {
6979     case XO_STYLE_JSON:
6980 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6981 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6982 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6983 
6984 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
6985 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
6986 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6987 	break;
6988 
6989     case XO_STYLE_ENCODER:
6990 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL);
6991 	/*fallthru*/
6992 
6993     default:
6994 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
6995 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6996 	break;
6997     }
6998 
6999     return rc;
7000 }
7001 
7002 static int
xo_do_open_instance(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name)7003 xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7004 {
7005     xop = xo_default(xop);
7006 
7007     int rc = 0;
7008     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7009     const char *pre_nl = "";
7010 
7011     flags |= xop->xo_flags;
7012 
7013     if (name == NULL) {
7014 	xo_failure(xop, "NULL passed for instance name");
7015 	name = XO_FAILURE_NAME;
7016     }
7017 
7018     switch (xo_style(xop)) {
7019     case XO_STYLE_XML:
7020 	rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
7021 
7022 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7023 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7024 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
7025 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7026 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7027 	}
7028 
7029 	rc += xo_printf(xop, ">%s", ppn);
7030 	break;
7031 
7032     case XO_STYLE_JSON:
7033 	xo_stack_set_flags(xop);
7034 
7035 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7036 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7037 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7038 
7039 	rc = xo_printf(xop, "%s%*s{%s",
7040 		       pre_nl, xo_indent(xop), "", ppn);
7041 	break;
7042 
7043     case XO_STYLE_SDPARAMS:
7044 	break;
7045 
7046     case XO_STYLE_ENCODER:
7047 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL);
7048 	break;
7049     }
7050 
7051     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7052 
7053     return rc;
7054 }
7055 
7056 static int
xo_open_instance_hf(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name)7057 xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
7058 {
7059     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7060 }
7061 
7062 int
xo_open_instance_h(xo_handle_t * xop,const char * name)7063 xo_open_instance_h (xo_handle_t *xop, const char *name)
7064 {
7065     return xo_open_instance_hf(xop, 0, name);
7066 }
7067 
7068 int
xo_open_instance(const char * name)7069 xo_open_instance (const char *name)
7070 {
7071     return xo_open_instance_hf(NULL, 0, name);
7072 }
7073 
7074 int
xo_open_instance_hd(xo_handle_t * xop,const char * name)7075 xo_open_instance_hd (xo_handle_t *xop, const char *name)
7076 {
7077     return xo_open_instance_hf(xop, XOF_DTRT, name);
7078 }
7079 
7080 int
xo_open_instance_d(const char * name)7081 xo_open_instance_d (const char *name)
7082 {
7083     return xo_open_instance_hf(NULL, XOF_DTRT, name);
7084 }
7085 
7086 static int
xo_do_close_instance(xo_handle_t * xop,const char * name)7087 xo_do_close_instance (xo_handle_t *xop, const char *name)
7088 {
7089     xop = xo_default(xop);
7090 
7091     int rc = 0;
7092     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7093     const char *pre_nl = "";
7094 
7095     if (name == NULL) {
7096 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7097 
7098 	name = xsp->xs_name;
7099 	if (name) {
7100 	    int len = strlen(name) + 1;
7101 	    /* We need to make a local copy; xo_depth_change will free it */
7102 	    char *cp = alloca(len);
7103 	    memcpy(cp, name, len);
7104 	    name = cp;
7105 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7106 	    xo_failure(xop, "missing name without 'dtrt' mode");
7107 	    name = XO_FAILURE_NAME;
7108 	}
7109     }
7110 
7111     switch (xo_style(xop)) {
7112     case XO_STYLE_XML:
7113 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7114 	rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
7115 	break;
7116 
7117     case XO_STYLE_JSON:
7118 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7119 
7120 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7121 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
7122 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7123 	break;
7124 
7125     case XO_STYLE_HTML:
7126     case XO_STYLE_TEXT:
7127 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7128 	break;
7129 
7130     case XO_STYLE_SDPARAMS:
7131 	break;
7132 
7133     case XO_STYLE_ENCODER:
7134 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7135 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL);
7136 	break;
7137     }
7138 
7139     return rc;
7140 }
7141 
7142 int
xo_close_instance_h(xo_handle_t * xop,const char * name)7143 xo_close_instance_h (xo_handle_t *xop, const char *name)
7144 {
7145     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7146 }
7147 
7148 int
xo_close_instance(const char * name)7149 xo_close_instance (const char *name)
7150 {
7151     return xo_close_instance_h(NULL, name);
7152 }
7153 
7154 int
xo_close_instance_hd(xo_handle_t * xop)7155 xo_close_instance_hd (xo_handle_t *xop)
7156 {
7157     return xo_close_instance_h(xop, NULL);
7158 }
7159 
7160 int
xo_close_instance_d(void)7161 xo_close_instance_d (void)
7162 {
7163     return xo_close_instance_h(NULL, NULL);
7164 }
7165 
7166 static int
xo_do_close_all(xo_handle_t * xop,xo_stack_t * limit)7167 xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7168 {
7169     xo_stack_t *xsp;
7170     int rc = 0;
7171     xo_xsf_flags_t flags;
7172 
7173     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7174 	switch (xsp->xs_state) {
7175 	case XSS_INIT:
7176 	    /* Nothing */
7177 	    rc = 0;
7178 	    break;
7179 
7180 	case XSS_OPEN_CONTAINER:
7181 	    rc = xo_do_close_container(xop, NULL);
7182 	    break;
7183 
7184 	case XSS_OPEN_LIST:
7185 	    rc = xo_do_close_list(xop, NULL);
7186 	    break;
7187 
7188 	case XSS_OPEN_INSTANCE:
7189 	    rc = xo_do_close_instance(xop, NULL);
7190 	    break;
7191 
7192 	case XSS_OPEN_LEAF_LIST:
7193 	    rc = xo_do_close_leaf_list(xop, NULL);
7194 	    break;
7195 
7196 	case XSS_MARKER:
7197 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7198 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7199 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7200 	    rc = 0;
7201 	    break;
7202 	}
7203 
7204 	if (rc < 0)
7205 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7206     }
7207 
7208     return 0;
7209 }
7210 
7211 /*
7212  * This function is responsible for clearing out whatever is needed
7213  * to get to the desired state, if possible.
7214  */
7215 static int
xo_do_close(xo_handle_t * xop,const char * name,xo_state_t new_state)7216 xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7217 {
7218     xo_stack_t *xsp, *limit = NULL;
7219     int rc;
7220     xo_state_t need_state = new_state;
7221 
7222     if (new_state == XSS_CLOSE_CONTAINER)
7223 	need_state = XSS_OPEN_CONTAINER;
7224     else if (new_state == XSS_CLOSE_LIST)
7225 	need_state = XSS_OPEN_LIST;
7226     else if (new_state == XSS_CLOSE_INSTANCE)
7227 	need_state = XSS_OPEN_INSTANCE;
7228     else if (new_state == XSS_CLOSE_LEAF_LIST)
7229 	need_state = XSS_OPEN_LEAF_LIST;
7230     else if (new_state == XSS_MARKER)
7231 	need_state = XSS_MARKER;
7232     else
7233 	return 0; /* Unknown or useless new states are ignored */
7234 
7235     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7236 	/*
7237 	 * Marker's normally stop us from going any further, unless
7238 	 * we are popping a marker (new_state == XSS_MARKER).
7239 	 */
7240 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7241 	    if (name) {
7242 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7243 			   "not found '%s'",
7244 			   xo_state_name(new_state),
7245 			   xsp->xs_name, name);
7246 		return 0;
7247 
7248 	    } else {
7249 		limit = xsp;
7250 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7251 	    }
7252 	    break;
7253 	}
7254 
7255 	if (xsp->xs_state != need_state)
7256 	    continue;
7257 
7258 	if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
7259 	    continue;
7260 
7261 	limit = xsp;
7262 	break;
7263     }
7264 
7265     if (limit == NULL) {
7266 	xo_failure(xop, "xo_%s can't find match for '%s'",
7267 		   xo_state_name(new_state), name);
7268 	return 0;
7269     }
7270 
7271     rc = xo_do_close_all(xop, limit);
7272 
7273     return rc;
7274 }
7275 
7276 /*
7277  * We are in a given state and need to transition to the new state.
7278  */
7279 static int
xo_transition(xo_handle_t * xop,xo_xsf_flags_t flags,const char * name,xo_state_t new_state)7280 xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
7281 	       xo_state_t new_state)
7282 {
7283     xo_stack_t *xsp;
7284     int rc;
7285     int old_state, on_marker;
7286 
7287     xop = xo_default(xop);
7288 
7289     rc = 0;
7290     xsp = &xop->xo_stack[xop->xo_depth];
7291     old_state = xsp->xs_state;
7292     on_marker = (old_state == XSS_MARKER);
7293 
7294     /* If there's a marker on top of the stack, we need to find a real state */
7295     while (old_state == XSS_MARKER) {
7296 	if (xsp == xop->xo_stack)
7297 	    break;
7298 	xsp -= 1;
7299 	old_state = xsp->xs_state;
7300     }
7301 
7302     /*
7303      * At this point, the list of possible states are:
7304      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7305      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7306      */
7307     switch (XSS_TRANSITION(old_state, new_state)) {
7308 
7309     open_container:
7310     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7311     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7312     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7313        rc = xo_do_open_container(xop, flags, name);
7314        break;
7315 
7316     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7317 	if (on_marker)
7318 	    goto marker_prevents_close;
7319 	rc = xo_do_close_list(xop, NULL);
7320 	if (rc >= 0)
7321 	    goto open_container;
7322 	break;
7323 
7324     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7325 	if (on_marker)
7326 	    goto marker_prevents_close;
7327 	rc = xo_do_close_leaf_list(xop, NULL);
7328 	if (rc >= 0)
7329 	    goto open_container;
7330 	break;
7331 
7332     /*close_container:*/
7333     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7334 	if (on_marker)
7335 	    goto marker_prevents_close;
7336 	rc = xo_do_close(xop, name, new_state);
7337 	break;
7338 
7339     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7340 	/* This is an exception for "xo --close" */
7341 	rc = xo_do_close_container(xop, name);
7342 	break;
7343 
7344     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7345     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7346 	if (on_marker)
7347 	    goto marker_prevents_close;
7348 	rc = xo_do_close(xop, name, new_state);
7349 	break;
7350 
7351     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7352 	if (on_marker)
7353 	    goto marker_prevents_close;
7354 	rc = xo_do_close_leaf_list(xop, NULL);
7355 	if (rc >= 0)
7356 	    rc = xo_do_close(xop, name, new_state);
7357 	break;
7358 
7359     open_list:
7360     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7361     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7362     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7363 	rc = xo_do_open_list(xop, flags, name);
7364 	break;
7365 
7366     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7367 	if (on_marker)
7368 	    goto marker_prevents_close;
7369 	rc = xo_do_close_list(xop, NULL);
7370 	if (rc >= 0)
7371 	    goto open_list;
7372 	break;
7373 
7374     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7375 	if (on_marker)
7376 	    goto marker_prevents_close;
7377 	rc = xo_do_close_leaf_list(xop, NULL);
7378 	if (rc >= 0)
7379 	    goto open_list;
7380 	break;
7381 
7382     /*close_list:*/
7383     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7384 	if (on_marker)
7385 	    goto marker_prevents_close;
7386 	rc = xo_do_close(xop, name, new_state);
7387 	break;
7388 
7389     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7390     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7391     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7392     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7393 	rc = xo_do_close(xop, name, new_state);
7394 	break;
7395 
7396     open_instance:
7397     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7398 	rc = xo_do_open_instance(xop, flags, name);
7399 	break;
7400 
7401     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7402     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7403 	rc = xo_do_open_list(xop, flags, name);
7404 	if (rc >= 0)
7405 	    goto open_instance;
7406 	break;
7407 
7408     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7409 	if (on_marker) {
7410 	    rc = xo_do_open_list(xop, flags, name);
7411 	} else {
7412 	    rc = xo_do_close_instance(xop, NULL);
7413 	}
7414 	if (rc >= 0)
7415 	    goto open_instance;
7416 	break;
7417 
7418     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7419 	if (on_marker)
7420 	    goto marker_prevents_close;
7421 	rc = xo_do_close_leaf_list(xop, NULL);
7422 	if (rc >= 0)
7423 	    goto open_instance;
7424 	break;
7425 
7426     /*close_instance:*/
7427     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7428 	if (on_marker)
7429 	    goto marker_prevents_close;
7430 	rc = xo_do_close_instance(xop, name);
7431 	break;
7432 
7433     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7434 	/* This one makes no sense; ignore it */
7435 	xo_failure(xop, "xo_close_instance ignored when called from "
7436 		   "initial state ('%s')", name ?: "(unknown)");
7437 	break;
7438 
7439     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7440     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7441 	if (on_marker)
7442 	    goto marker_prevents_close;
7443 	rc = xo_do_close(xop, name, new_state);
7444 	break;
7445 
7446     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7447 	if (on_marker)
7448 	    goto marker_prevents_close;
7449 	rc = xo_do_close_leaf_list(xop, NULL);
7450 	if (rc >= 0)
7451 	    rc = xo_do_close(xop, name, new_state);
7452 	break;
7453 
7454     open_leaf_list:
7455     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7456     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7457     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7458 	rc = xo_do_open_leaf_list(xop, flags, name);
7459 	break;
7460 
7461     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7462     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7463 	if (on_marker)
7464 	    goto marker_prevents_close;
7465 	rc = xo_do_close_list(xop, NULL);
7466 	if (rc >= 0)
7467 	    goto open_leaf_list;
7468 	break;
7469 
7470     /*close_leaf_list:*/
7471     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7472 	if (on_marker)
7473 	    goto marker_prevents_close;
7474 	rc = xo_do_close_leaf_list(xop, name);
7475 	break;
7476 
7477     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7478 	/* Makes no sense; ignore */
7479 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7480 		   "initial state ('%s')", name ?: "(unknown)");
7481 	break;
7482 
7483     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7484     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7485     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7486 	if (on_marker)
7487 	    goto marker_prevents_close;
7488 	rc = xo_do_close(xop, name, new_state);
7489 	break;
7490 
7491     /*emit:*/
7492     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7493     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7494 	break;
7495 
7496     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7497 	if (on_marker)
7498 	    goto marker_prevents_close;
7499 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7500 	break;
7501 
7502     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7503 	break;
7504 
7505     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7506 	if (on_marker)
7507 	    goto marker_prevents_close;
7508 	rc = xo_do_close_leaf_list(xop, NULL);
7509 	break;
7510 
7511     /*emit_leaf_list:*/
7512     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7513     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7514     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7515 	rc = xo_do_open_leaf_list(xop, flags, name);
7516 	break;
7517 
7518     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7519 	break;
7520 
7521     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7522 	/*
7523 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7524 	 * API, where both lists and leaf-lists were opened as lists.  So
7525 	 * if we find an open list that hasn't had anything written to it,
7526 	 * we'll accept it.
7527 	 */
7528 	break;
7529 
7530     default:
7531 	xo_failure(xop, "unknown transition: (%u -> %u)",
7532 		   xsp->xs_state, new_state);
7533     }
7534 
7535     /* Handle the flush flag */
7536     if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
7537 	if (xo_flush_h(xop))
7538 	    rc = -1;
7539 
7540     return rc;
7541 
7542  marker_prevents_close:
7543     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7544 	       xop->xo_stack[xop->xo_depth].xs_name,
7545 	       xo_state_name(old_state), xo_state_name(new_state));
7546     return -1;
7547 }
7548 
7549 int
xo_open_marker_h(xo_handle_t * xop,const char * name)7550 xo_open_marker_h (xo_handle_t *xop, const char *name)
7551 {
7552     xop = xo_default(xop);
7553 
7554     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7555 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7556 
7557     return 0;
7558 }
7559 
7560 int
xo_open_marker(const char * name)7561 xo_open_marker (const char *name)
7562 {
7563     return xo_open_marker_h(NULL, name);
7564 }
7565 
7566 int
xo_close_marker_h(xo_handle_t * xop,const char * name)7567 xo_close_marker_h (xo_handle_t *xop, const char *name)
7568 {
7569     xop = xo_default(xop);
7570 
7571     return xo_do_close(xop, name, XSS_MARKER);
7572 }
7573 
7574 int
xo_close_marker(const char * name)7575 xo_close_marker (const char *name)
7576 {
7577     return xo_close_marker_h(NULL, name);
7578 }
7579 
7580 /*
7581  * Record custom output functions into the xo handle, allowing
7582  * integration with a variety of output frameworks.
7583  */
7584 void
xo_set_writer(xo_handle_t * xop,void * opaque,xo_write_func_t write_func,xo_close_func_t close_func,xo_flush_func_t flush_func)7585 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7586 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
7587 {
7588     xop = xo_default(xop);
7589 
7590     xop->xo_opaque = opaque;
7591     xop->xo_write = write_func;
7592     xop->xo_close = close_func;
7593     xop->xo_flush = flush_func;
7594 }
7595 
7596 void
xo_set_allocator(xo_realloc_func_t realloc_func,xo_free_func_t free_func)7597 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
7598 {
7599     xo_realloc = realloc_func;
7600     xo_free = free_func;
7601 }
7602 
7603 int
xo_flush_h(xo_handle_t * xop)7604 xo_flush_h (xo_handle_t *xop)
7605 {
7606     int rc;
7607 
7608     xop = xo_default(xop);
7609 
7610     switch (xo_style(xop)) {
7611     case XO_STYLE_ENCODER:
7612 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL);
7613     }
7614 
7615     rc = xo_write(xop);
7616     if (rc >= 0 && xop->xo_flush)
7617 	if (xop->xo_flush(xop->xo_opaque) < 0)
7618 	    return -1;
7619 
7620     return rc;
7621 }
7622 
7623 int
xo_flush(void)7624 xo_flush (void)
7625 {
7626     return xo_flush_h(NULL);
7627 }
7628 
7629 int
xo_finish_h(xo_handle_t * xop)7630 xo_finish_h (xo_handle_t *xop)
7631 {
7632     const char *cp = "";
7633     xop = xo_default(xop);
7634 
7635     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
7636 	xo_do_close_all(xop, xop->xo_stack);
7637 
7638     switch (xo_style(xop)) {
7639     case XO_STYLE_JSON:
7640 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7641 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7642 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
7643 	    else
7644 		cp = "{ ";
7645 	    xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
7646 	}
7647 	break;
7648 
7649     case XO_STYLE_ENCODER:
7650 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL);
7651 	break;
7652     }
7653 
7654     return xo_flush_h(xop);
7655 }
7656 
7657 int
xo_finish(void)7658 xo_finish (void)
7659 {
7660     return xo_finish_h(NULL);
7661 }
7662 
7663 /*
7664  * xo_finish_atexit is suitable for atexit() calls, to force clear up
7665  * and finalizing output.
7666  */
7667 void
xo_finish_atexit(void)7668 xo_finish_atexit (void)
7669 {
7670     (void) xo_finish_h(NULL);
7671 }
7672 
7673 /*
7674  * Generate an error message, such as would be displayed on stderr
7675  */
7676 void
xo_error_hv(xo_handle_t * xop,const char * fmt,va_list vap)7677 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
7678 {
7679     xop = xo_default(xop);
7680 
7681     /*
7682      * If the format string doesn't end with a newline, we pop
7683      * one on ourselves.
7684      */
7685     int len = strlen(fmt);
7686     if (len > 0 && fmt[len - 1] != '\n') {
7687 	char *newfmt = alloca(len + 2);
7688 	memcpy(newfmt, fmt, len);
7689 	newfmt[len] = '\n';
7690 	newfmt[len] = '\0';
7691 	fmt = newfmt;
7692     }
7693 
7694     switch (xo_style(xop)) {
7695     case XO_STYLE_TEXT:
7696 	vfprintf(stderr, fmt, vap);
7697 	break;
7698 
7699     case XO_STYLE_HTML:
7700 	va_copy(xop->xo_vap, vap);
7701 
7702 	xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
7703 
7704 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
7705 	    xo_line_close(xop);
7706 
7707 	xo_write(xop);
7708 
7709 	va_end(xop->xo_vap);
7710 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
7711 	break;
7712 
7713     case XO_STYLE_XML:
7714     case XO_STYLE_JSON:
7715 	va_copy(xop->xo_vap, vap);
7716 
7717 	xo_open_container_h(xop, "error");
7718 	xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
7719 	xo_close_container_h(xop, "error");
7720 
7721 	va_end(xop->xo_vap);
7722 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
7723 	break;
7724 
7725     case XO_STYLE_SDPARAMS:
7726     case XO_STYLE_ENCODER:
7727 	break;
7728     }
7729 }
7730 
7731 void
xo_error_h(xo_handle_t * xop,const char * fmt,...)7732 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
7733 {
7734     va_list vap;
7735 
7736     va_start(vap, fmt);
7737     xo_error_hv(xop, fmt, vap);
7738     va_end(vap);
7739 }
7740 
7741 /*
7742  * Generate an error message, such as would be displayed on stderr
7743  */
7744 void
xo_error(const char * fmt,...)7745 xo_error (const char *fmt, ...)
7746 {
7747     va_list vap;
7748 
7749     va_start(vap, fmt);
7750     xo_error_hv(NULL, fmt, vap);
7751     va_end(vap);
7752 }
7753 
7754 /*
7755  * Parse any libxo-specific options from the command line, removing them
7756  * so the main() argument parsing won't see them.  We return the new value
7757  * for argc or -1 for error.  If an error occurred, the program should
7758  * exit.  A suitable error message has already been displayed.
7759  */
7760 int
xo_parse_args(int argc,char ** argv)7761 xo_parse_args (int argc, char **argv)
7762 {
7763     static char libxo_opt[] = "--libxo";
7764     char *cp;
7765     int i, save;
7766 
7767     /* Save our program name for xo_err and friends */
7768     xo_program = argv[0];
7769     cp = strrchr(xo_program, '/');
7770     if (cp)
7771 	xo_program = cp + 1;
7772 
7773     for (save = i = 1; i < argc; i++) {
7774 	if (argv[i] == NULL
7775 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
7776 	    if (save != i)
7777 		argv[save] = argv[i];
7778 	    save += 1;
7779 	    continue;
7780 	}
7781 
7782 	cp = argv[i] + sizeof(libxo_opt) - 1;
7783 	if (*cp == 0) {
7784 	    cp = argv[++i];
7785 	    if (cp == 0) {
7786 		xo_warnx("missing libxo option");
7787 		return -1;
7788 	    }
7789 
7790 	    if (xo_set_options(NULL, cp) < 0)
7791 		return -1;
7792 	} else if (*cp == ':') {
7793 	    if (xo_set_options(NULL, cp) < 0)
7794 		return -1;
7795 
7796 	} else if (*cp == '=') {
7797 	    if (xo_set_options(NULL, ++cp) < 0)
7798 		return -1;
7799 
7800 	} else if (*cp == '-') {
7801 	    cp += 1;
7802 	    if (strcmp(cp, "check") == 0) {
7803 		exit(XO_HAS_LIBXO);
7804 
7805 	    } else {
7806 		xo_warnx("unknown libxo option: '%s'", argv[i]);
7807 		return -1;
7808 	    }
7809 	} else {
7810 		xo_warnx("unknown libxo option: '%s'", argv[i]);
7811 	    return -1;
7812 	}
7813     }
7814 
7815     argv[save] = NULL;
7816     return save;
7817 }
7818 
7819 /*
7820  * Debugging function that dumps the current stack of open libxo constructs,
7821  * suitable for calling from the debugger.
7822  */
7823 void
xo_dump_stack(xo_handle_t * xop)7824 xo_dump_stack (xo_handle_t *xop)
7825 {
7826     int i;
7827     xo_stack_t *xsp;
7828 
7829     xop = xo_default(xop);
7830 
7831     fprintf(stderr, "Stack dump:\n");
7832 
7833     xsp = xop->xo_stack;
7834     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
7835 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
7836 		i, xo_state_name(xsp->xs_state),
7837 		xsp->xs_name ?: "--", xsp->xs_flags);
7838     }
7839 }
7840 
7841 /*
7842  * Record the program name used for error messages
7843  */
7844 void
xo_set_program(const char * name)7845 xo_set_program (const char *name)
7846 {
7847     xo_program = name;
7848 }
7849 
7850 void
xo_set_version_h(xo_handle_t * xop,const char * version)7851 xo_set_version_h (xo_handle_t *xop, const char *version)
7852 {
7853     xop = xo_default(xop);
7854 
7855     if (version == NULL || strchr(version, '"') != NULL)
7856 	return;
7857 
7858     if (!xo_style_is_encoding(xop))
7859 	return;
7860 
7861     switch (xo_style(xop)) {
7862     case XO_STYLE_XML:
7863 	/* For XML, we record this as an attribute for the first tag */
7864 	xo_attr_h(xop, "__version", "%s", version);
7865 	break;
7866 
7867     case XO_STYLE_JSON:
7868 	/*
7869 	 * For JSON, we record the version string in our handle, and emit
7870 	 * it in xo_emit_top.
7871 	 */
7872 	xop->xo_version = xo_strndup(version, -1);
7873 	break;
7874 
7875     case XO_STYLE_ENCODER:
7876 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version);
7877 	break;
7878     }
7879 }
7880 
7881 /*
7882  * Set the version number for the API content being carried thru
7883  * the xo handle.
7884  */
7885 void
xo_set_version(const char * version)7886 xo_set_version (const char *version)
7887 {
7888     xo_set_version_h(NULL, version);
7889 }
7890 
7891 /*
7892  * Generate a warning.  Normally, this is a text message written to
7893  * standard error.  If the XOF_WARN_XML flag is set, then we generate
7894  * XMLified content on standard output.
7895  */
7896 void
xo_emit_warn_hcv(xo_handle_t * xop,int as_warning,int code,const char * fmt,va_list vap)7897 xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
7898 		  const char *fmt, va_list vap)
7899 {
7900     xop = xo_default(xop);
7901 
7902     if (fmt == NULL)
7903 	return;
7904 
7905     xo_open_marker_h(xop, "xo_emit_warn_hcv");
7906     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
7907 
7908     if (xo_program)
7909 	xo_emit("{wc:program}", xo_program);
7910 
7911     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
7912 	va_list ap;
7913 	xo_handle_t temp;
7914 
7915 	bzero(&temp, sizeof(temp));
7916 	temp.xo_style = XO_STYLE_TEXT;
7917 	xo_buf_init(&temp.xo_data);
7918 	xo_depth_check(&temp, XO_DEPTH);
7919 
7920 	va_copy(ap, vap);
7921 	(void) xo_emit_hv(&temp, fmt, ap);
7922 	va_end(ap);
7923 
7924 	xo_buffer_t *src = &temp.xo_data;
7925 	xo_format_value(xop, "message", 7, src->xb_bufp,
7926 			src->xb_curp - src->xb_bufp, NULL, 0, 0);
7927 
7928 	xo_free(temp.xo_stack);
7929 	xo_buf_cleanup(src);
7930     }
7931 
7932     (void) xo_emit_hv(xop, fmt, vap);
7933 
7934     int len = strlen(fmt);
7935     if (len > 0 && fmt[len - 1] != '\n') {
7936 	if (code > 0) {
7937 	    const char *msg = strerror(code);
7938 	    if (msg)
7939 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
7940 	}
7941 	xo_emit("\n");
7942     }
7943 
7944     xo_close_marker_h(xop, "xo_emit_warn_hcv");
7945     xo_flush_h(xop);
7946 }
7947 
7948 void
xo_emit_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)7949 xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
7950 {
7951     va_list vap;
7952 
7953     va_start(vap, fmt);
7954     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
7955     va_end(vap);
7956 }
7957 
7958 void
xo_emit_warn_c(int code,const char * fmt,...)7959 xo_emit_warn_c (int code, const char *fmt, ...)
7960 {
7961     va_list vap;
7962 
7963     va_start(vap, fmt);
7964     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7965     va_end(vap);
7966 }
7967 
7968 void
xo_emit_warn(const char * fmt,...)7969 xo_emit_warn (const char *fmt, ...)
7970 {
7971     int code = errno;
7972     va_list vap;
7973 
7974     va_start(vap, fmt);
7975     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
7976     va_end(vap);
7977 }
7978 
7979 void
xo_emit_warnx(const char * fmt,...)7980 xo_emit_warnx (const char *fmt, ...)
7981 {
7982     va_list vap;
7983 
7984     va_start(vap, fmt);
7985     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
7986     va_end(vap);
7987 }
7988 
7989 void
xo_emit_err_v(int eval,int code,const char * fmt,va_list vap)7990 xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
7991 {
7992     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
7993     xo_finish();
7994     exit(eval);
7995 }
7996 
7997 void
xo_emit_err(int eval,const char * fmt,...)7998 xo_emit_err (int eval, const char *fmt, ...)
7999 {
8000     int code = errno;
8001     va_list vap;
8002     va_start(vap, fmt);
8003     xo_emit_err_v(0, code, fmt, vap);
8004     va_end(vap);
8005     exit(eval);
8006 }
8007 
8008 void
xo_emit_errx(int eval,const char * fmt,...)8009 xo_emit_errx (int eval, const char *fmt, ...)
8010 {
8011     va_list vap;
8012 
8013     va_start(vap, fmt);
8014     xo_emit_err_v(0, -1, fmt, vap);
8015     va_end(vap);
8016     xo_finish();
8017     exit(eval);
8018 }
8019 
8020 void
xo_emit_errc(int eval,int code,const char * fmt,...)8021 xo_emit_errc (int eval, int code, const char *fmt, ...)
8022 {
8023     va_list vap;
8024 
8025     va_start(vap, fmt);
8026     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8027     va_end(vap);
8028     xo_finish();
8029     exit(eval);
8030 }
8031 
8032 /*
8033  * Get the opaque private pointer for an xo handle
8034  */
8035 void *
xo_get_private(xo_handle_t * xop)8036 xo_get_private (xo_handle_t *xop)
8037 {
8038     xop = xo_default(xop);
8039     return xop->xo_private;
8040 }
8041 
8042 /*
8043  * Set the opaque private pointer for an xo handle.
8044  */
8045 void
xo_set_private(xo_handle_t * xop,void * opaque)8046 xo_set_private (xo_handle_t *xop, void *opaque)
8047 {
8048     xop = xo_default(xop);
8049     xop->xo_private = opaque;
8050 }
8051 
8052 /*
8053  * Get the encoder function
8054  */
8055 xo_encoder_func_t
xo_get_encoder(xo_handle_t * xop)8056 xo_get_encoder (xo_handle_t *xop)
8057 {
8058     xop = xo_default(xop);
8059     return xop->xo_encoder;
8060 }
8061 
8062 /*
8063  * Record an encoder callback function in an xo handle.
8064  */
8065 void
xo_set_encoder(xo_handle_t * xop,xo_encoder_func_t encoder)8066 xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8067 {
8068     xop = xo_default(xop);
8069 
8070     xop->xo_style = XO_STYLE_ENCODER;
8071     xop->xo_encoder = encoder;
8072 }
8073