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