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