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