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