2 * Copyright (c) 2014-2019, Juniper Networks, Inc.
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
8 * Phil Shafer, July 2014
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.
18 * Full documentation is available in ./doc/libxo.txt or online at:
19 * http://juniper.github.io/libxo/libxo-manual.html
21 * For first time readers, the core bits of code to start looking at are:
22 * - xo_do_emit() -- parse and emit a set of fields
23 * - xo_do_emit_fields -- the central function of the library
24 * - xo_do_format_field() -- handles formatting a single field
25 * - xo_transiton() -- the state machine that keeps things sane
26 * and of course the "xo_handle_t" data structure, which carries all
27 * configuration and state.
37 #include <sys/types.h>
46 #include "xo_config.h"
48 #include "xo_encoder.h"
50 #include "xo_explicit.h"
53 * We ask wcwidth() to do an impossible job, really. It's supposed to
54 * need to tell us the number of columns consumed to display a unicode
55 * character. It returns that number without any sort of context, but
56 * we know they are characters whose glyph differs based on placement
57 * (end of word, middle of word, etc) and many that affect characters
58 * previously emitted. Without content, it can't hope to tell us.
59 * But it's the only standard tool we've got, so we use it. We would
60 * use wcswidth() but it typically just loops through adding the results
61 * of wcwidth() calls in an entirely unhelpful way.
63 * Even then, there are many poor implementations (macosx), so we have
64 * to carry our own. We could have configure.ac test this (with
65 * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
66 * to run a binary, which breaks cross-compilation. Hmm... I could
67 * run this test at init time and make a warning for our dear user.
69 * Anyhow, it remains a best-effort sort of thing. And it's all made
70 * more hopeless because we assume the display code doing the rendering is
71 * playing by the same rules we are. If it display 0x200d as a square
72 * box or a funky question mark, the output will be hosed.
75 #include "xo_wcwidth.h"
76 #else /* LIBXO_WCWIDTH */
77 #define xo_wcwidth(_x) wcwidth(_x)
78 #endif /* LIBXO_WCWIDTH */
80 #ifdef HAVE_STDIO_EXT_H
81 #include <stdio_ext.h>
82 #endif /* HAVE_STDIO_EXT_H */
85 * humanize_number is a great function, unless you don't have it. So
86 * we carry one in our pocket.
88 #ifdef HAVE_HUMANIZE_NUMBER
90 #define xo_humanize_number humanize_number
91 #else /* HAVE_HUMANIZE_NUMBER */
92 #include "xo_humanize.h"
93 #endif /* HAVE_HUMANIZE_NUMBER */
97 #endif /* HAVE_GETTEXT */
99 /* Rather lame that we can't count on these... */
108 * Three styles of specifying thread-local variables are supported.
109 * configure.ac has the brains to run each possibility through the
110 * compiler and see what works; we are left to define the THREAD_LOCAL
111 * macro to the right value. Most toolchains (clang, gcc) use
112 * "before", but some (borland) use "after" and I've heard of some
113 * (ms) that use __declspec. Any others out there?
115 #define THREAD_LOCAL_before 1
116 #define THREAD_LOCAL_after 2
117 #define THREAD_LOCAL_declspec 3
119 #ifndef HAVE_THREAD_LOCAL
120 #define THREAD_LOCAL(_x) _x
121 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
122 #define THREAD_LOCAL(_x) __thread _x
123 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
124 #define THREAD_LOCAL(_x) _x __thread
125 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
126 #define THREAD_LOCAL(_x) __declspec(_x)
128 #error unknown thread-local setting
129 #endif /* HAVE_THREADS_H */
131 const char xo_version[] = LIBXO_VERSION;
132 const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
133 static const char xo_default_format[] = "%s";
136 #define UNUSED __attribute__ ((__unused__))
139 #define XO_INDENT_BY 2 /* Amount to indent when pretty printing */
140 #define XO_DEPTH 128 /* Default stack depth */
141 #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just silly */
143 #define XO_FAILURE_NAME "failure"
145 /* Flags for the stack frame */
146 typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
147 #define XSF_NOT_FIRST (1<<0) /* Not the first element */
148 #define XSF_LIST (1<<1) /* Frame is a list */
149 #define XSF_INSTANCE (1<<2) /* Frame is an instance */
150 #define XSF_DTRT (1<<3) /* Save the name for DTRT mode */
152 #define XSF_CONTENT (1<<4) /* Some content has been emitted */
153 #define XSF_EMIT (1<<5) /* Some field has been emitted */
154 #define XSF_EMIT_KEY (1<<6) /* A key has been emitted */
155 #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
157 /* These are the flags we propagate between markers and their parents */
158 #define XSF_MARKER_FLAGS \
159 (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
162 * Turn the transition between two states into a number suitable for
163 * a "switch" statement.
165 #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
168 * xo_stack_t: As we open and close containers and levels, we
169 * create a stack of frames to track them. This is needed for
170 * XOF_WARN and XOF_XPATH.
172 typedef struct xo_stack_s {
173 xo_xsf_flags_t xs_flags; /* Flags for this frame */
174 xo_state_t xs_state; /* State for this stack frame */
175 char *xs_name; /* Name (for XPath value) */
176 char *xs_keys; /* XPath predicate for any key fields */
180 * libxo supports colors and effects, for those who like them.
181 * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
182 * ("effects") are bits since we need to maintain state.
184 typedef uint8_t xo_color_t;
185 #define XO_COL_DEFAULT 0
186 #define XO_COL_BLACK 1
188 #define XO_COL_GREEN 3
189 #define XO_COL_YELLOW 4
190 #define XO_COL_BLUE 5
191 #define XO_COL_MAGENTA 6
192 #define XO_COL_CYAN 7
193 #define XO_COL_WHITE 8
195 #define XO_NUM_COLORS 9
198 * Yes, there's no blink. We're civilized. We like users. Blink
199 * isn't something one does to someone you like. Friends don't let
200 * friends use blink. On friends. You know what I mean. Blink is
201 * like, well, it's like bursting into show tunes at a funeral. It's
202 * just not done. Not something anyone wants. And on those rare
203 * instances where it might actually be appropriate, it's still wrong,
204 * since it's likely done by the wrong person for the wrong reason.
205 * Just like blink. And if I implemented blink, I'd be like a funeral
206 * director who adds "Would you like us to burst into show tunes?" on
207 * the list of questions asked while making funeral arrangements.
208 * It's formalizing wrongness in the wrong way. And we're just too
209 * civilized to do that. Hhhmph!
211 #define XO_EFF_RESET (1<<0)
212 #define XO_EFF_NORMAL (1<<1)
213 #define XO_EFF_BOLD (1<<2)
214 #define XO_EFF_UNDERLINE (1<<3)
215 #define XO_EFF_INVERSE (1<<4)
217 #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
219 typedef uint8_t xo_effect_t;
220 typedef struct xo_colors_s {
221 xo_effect_t xoc_effects; /* Current effect set */
222 xo_color_t xoc_col_fg; /* Foreground color */
223 xo_color_t xoc_col_bg; /* Background color */
227 * xo_handle_t: this is the principle data structure for libxo.
228 * It's used as a store for state, options, content, and all manor
229 * of other information.
232 xo_xof_flags_t xo_flags; /* Flags (XOF_*) from the user*/
233 xo_xof_flags_t xo_iflags; /* Internal flags (XOIF_*) */
234 xo_style_t xo_style; /* XO_STYLE_* value */
235 unsigned short xo_indent; /* Indent level (if pretty) */
236 unsigned short xo_indent_by; /* Indent amount (tab stop) */
237 xo_write_func_t xo_write; /* Write callback */
238 xo_close_func_t xo_close; /* Close callback */
239 xo_flush_func_t xo_flush; /* Flush callback */
240 xo_formatter_t xo_formatter; /* Custom formating function */
241 xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
242 void *xo_opaque; /* Opaque data for write function */
243 xo_buffer_t xo_data; /* Output data */
244 xo_buffer_t xo_fmt; /* Work area for building format strings */
245 xo_buffer_t xo_attrs; /* Work area for building XML attributes */
246 xo_buffer_t xo_predicate; /* Work area for building XPath predicates */
247 xo_stack_t *xo_stack; /* Stack pointer */
248 int xo_depth; /* Depth of stack */
249 int xo_stack_size; /* Size of the stack */
250 xo_info_t *xo_info; /* Info fields for all elements */
251 int xo_info_count; /* Number of info entries */
252 va_list xo_vap; /* Variable arguments (stdargs) */
253 char *xo_leading_xpath; /* A leading XPath expression */
254 mbstate_t xo_mbstate; /* Multi-byte character conversion state */
255 ssize_t xo_anchor_offset; /* Start of anchored text */
256 ssize_t xo_anchor_columns; /* Number of columns since the start anchor */
257 ssize_t xo_anchor_min_width; /* Desired width of anchored text */
258 ssize_t xo_units_offset; /* Start of units insertion point */
259 ssize_t xo_columns; /* Columns emitted during this xo_emit call */
260 #ifndef LIBXO_TEXT_ONLY
261 xo_color_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
262 xo_color_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
263 #endif /* LIBXO_TEXT_ONLY */
264 xo_colors_t xo_colors; /* Current color and effect values */
265 xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */
266 char *xo_version; /* Version string */
267 int xo_errno; /* Saved errno for "%m" */
268 char *xo_gt_domain; /* Gettext domain, suitable for dgettext(3) */
269 xo_encoder_func_t xo_encoder; /* Encoding function */
270 void *xo_private; /* Private data for external encoders */
273 /* Flag operations */
274 #define XOF_BIT_ISSET(_flag, _bit) (((_flag) & (_bit)) ? 1 : 0)
275 #define XOF_BIT_SET(_flag, _bit) do { (_flag) |= (_bit); } while (0)
276 #define XOF_BIT_CLEAR(_flag, _bit) do { (_flag) &= ~(_bit); } while (0)
278 #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
279 #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
280 #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
282 #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
283 #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
284 #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
287 #define XOIF_REORDER XOF_BIT(0) /* Reordering fields; record field info */
288 #define XOIF_DIV_OPEN XOF_BIT(1) /* A <div> is open */
289 #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
290 #define XOIF_ANCHOR XOF_BIT(3) /* An anchor is in place */
292 #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
293 #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
294 #define XOIF_MADE_OUTPUT XOF_BIT(6) /* Have already made output */
296 /* Flags for formatting functions */
297 typedef unsigned long xo_xff_flags_t;
298 #define XFF_COLON (1<<0) /* Append a ":" */
299 #define XFF_COMMA (1<<1) /* Append a "," iff there's more output */
300 #define XFF_WS (1<<2) /* Append a blank */
301 #define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding styles (XML, JSON) */
303 #define XFF_QUOTE (1<<4) /* Force quotes */
304 #define XFF_NOQUOTE (1<<5) /* Force no quotes */
305 #define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display styles (text, html) */
306 #define XFF_KEY (1<<7) /* Field is a key (for XPath) */
308 #define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */
309 #define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */
310 #define XFF_BLANK_LINE (1<<10) /* Emit a blank line */
311 #define XFF_NO_OUTPUT (1<<11) /* Do not make any output */
313 #define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */
314 #define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */
315 #define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */
316 #define XFF_HUMANIZE (1<<15) /* Humanize the value (for display styles) */
318 #define XFF_HN_SPACE (1<<16) /* Humanize: put space before suffix */
319 #define XFF_HN_DECIMAL (1<<17) /* Humanize: add one decimal place if <10 */
320 #define XFF_HN_1000 (1<<18) /* Humanize: use 1000, not 1024 */
321 #define XFF_GT_FIELD (1<<19) /* Call gettext() on a field */
323 #define XFF_GT_PLURAL (1<<20) /* Call dngettext to find plural form */
324 #define XFF_ARGUMENT (1<<21) /* Content provided via argument */
326 /* Flags to turn off when we don't want i18n processing */
327 #define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
330 * Normal printf has width and precision, which for strings operate as
331 * min and max number of columns. But this depends on the idea that
332 * one byte means one column, which UTF-8 and multi-byte characters
333 * pitches on its ear. It may take 40 bytes of data to populate 14
334 * columns, but we can't go off looking at 40 bytes of data without the
335 * caller's permission for fear/knowledge that we'll generate core files.
337 * So we make three values, distinguishing between "max column" and
338 * "number of bytes that we will inspect inspect safely" We call the
339 * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
341 * Under the "first do no harm" theory, we default "max" to "size".
342 * This is a reasonable assumption for folks that don't grok the
343 * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
346 * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
347 * columns of output, but will never look at more than 14 bytes of the
348 * input buffer. This is mostly compatible with printf and caller's
351 * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
352 * many bytes (or until a NUL is seen) are needed to fill 14 columns
353 * of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
354 * to xx bytes (or until a NUL is seen) in order to fill 14 columns
357 * It's fairly amazing how a good idea (handle all languages of the
358 * world) blows such a big hole in the bottom of the fairly weak boat
359 * that is C string handling. The simplicity and completenesss are
360 * sunk in ways we haven't even begun to understand.
362 #define XF_WIDTH_MIN 0 /* Minimal width */
363 #define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */
364 #define XF_WIDTH_MAX 2 /* Maximum width */
365 #define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */
367 /* Input and output string encodings */
368 #define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */
369 #define XF_ENC_UTF8 2 /* UTF-8 */
370 #define XF_ENC_LOCALE 3 /* Current locale */
373 * A place to parse printf-style format flags for each field
375 typedef struct xo_format_s {
376 unsigned char xf_fc; /* Format character */
377 unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */
378 unsigned char xf_skip; /* Skip this field */
379 unsigned char xf_lflag; /* 'l' (long) */
380 unsigned char xf_hflag;; /* 'h' (half) */
381 unsigned char xf_jflag; /* 'j' (intmax_t) */
382 unsigned char xf_tflag; /* 't' (ptrdiff_t) */
383 unsigned char xf_zflag; /* 'z' (size_t) */
384 unsigned char xf_qflag; /* 'q' (quad_t) */
385 unsigned char xf_seen_minus; /* Seen a minus */
386 int xf_leading_zero; /* Seen a leading zero (zero fill) */
387 unsigned xf_dots; /* Seen one or more '.'s */
388 int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
389 unsigned xf_stars; /* Seen one or more '*'s */
390 unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
394 * This structure represents the parsed field information, suitable for
395 * processing by xo_do_emit and anything else that needs to parse fields.
396 * Note that all pointers point to the main format string.
398 * XXX This is a first step toward compilable or cachable format
399 * strings. We can also cache the results of dgettext when no format
400 * is used, assuming the 'p' modifier has _not_ been set.
402 typedef struct xo_field_info_s {
403 xo_xff_flags_t xfi_flags; /* Flags for this field */
404 unsigned xfi_ftype; /* Field type, as character (e.g. 'V') */
405 const char *xfi_start; /* Start of field in the format string */
406 const char *xfi_content; /* Field's content */
407 const char *xfi_format; /* Field's Format */
408 const char *xfi_encoding; /* Field's encoding format */
409 const char *xfi_next; /* Next character in format string */
410 ssize_t xfi_len; /* Length of field */
411 ssize_t xfi_clen; /* Content length */
412 ssize_t xfi_flen; /* Format length */
413 ssize_t xfi_elen; /* Encoding length */
414 unsigned xfi_fnum; /* Field number (if used; 0 otherwise) */
415 unsigned xfi_renum; /* Reordered number (0 == no renumbering) */
419 * We keep a 'default' handle to allow callers to avoid having to
420 * allocate one. Passing NULL to any of our functions will use
421 * this default handle. Most functions have a variant that doesn't
422 * require a handle at all, since most output is to stdout, which
423 * the default handle handles handily.
425 static THREAD_LOCAL(xo_handle_t) xo_default_handle;
426 static THREAD_LOCAL(int) xo_default_inited;
427 static int xo_locale_inited;
428 static const char *xo_program;
431 * To allow libxo to be used in diverse environment, we allow the
432 * caller to give callbacks for memory allocation.
434 xo_realloc_func_t xo_realloc = realloc;
435 xo_free_func_t xo_free = free;
437 /* Forward declarations */
439 xo_failure (xo_handle_t *xop, const char *fmt, ...);
442 xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
443 xo_state_t new_state);
446 xo_set_options_simple (xo_handle_t *xop, const char *input);
449 xo_color_find (const char *str);
452 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
453 const char *name, ssize_t nlen,
454 const char *value, ssize_t vlen,
455 const char *fmt, ssize_t flen,
456 const char *encoding, ssize_t elen);
459 xo_anchor_clear (xo_handle_t *xop);
462 * xo_style is used to retrieve the current style. When we're built
463 * for "text only" mode, we use this function to drive the removal
464 * of most of the code in libxo. We return a constant and the compiler
465 * happily removes the non-text code that is not longer executed. This
466 * trims our code nicely without needing to trampel perfectly readable
469 static inline xo_style_t
470 xo_style (xo_handle_t *xop UNUSED)
472 #ifdef LIBXO_TEXT_ONLY
473 return XO_STYLE_TEXT;
474 #else /* LIBXO_TEXT_ONLY */
475 return xop->xo_style;
476 #endif /* LIBXO_TEXT_ONLY */
480 * Allow the compiler to optimize out non-text-only code while
481 * still compiling it.
486 #ifdef LIBXO_TEXT_ONLY
488 #else /* LIBXO_TEXT_ONLY */
490 #endif /* LIBXO_TEXT_ONLY */
494 * Callback to write data to a FILE pointer
497 xo_write_to_file (void *opaque, const char *data)
499 FILE *fp = (FILE *) opaque;
501 return fprintf(fp, "%s", data);
505 * Callback to close a file
508 xo_close_file (void *opaque)
510 FILE *fp = (FILE *) opaque;
516 * Callback to flush a FILE pointer
519 xo_flush_file (void *opaque)
521 FILE *fp = (FILE *) opaque;
527 * Use a rotating stock of buffers to make a printable string
530 #define XO_SMBUFSZ 128
533 xo_printable (const char *str)
535 static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
536 static THREAD_LOCAL(int) bufnum = 0;
541 if (++bufnum == XO_NUMBUFS)
544 char *res = bufset[bufnum], *cp, *ep;
546 for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
550 } else if (*str == '\r') {
553 } else if (*str == '\"') {
565 xo_depth_check (xo_handle_t *xop, int depth)
569 if (depth >= xop->xo_stack_size) {
570 depth += XO_DEPTH; /* Extra room */
572 xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
574 xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
578 int count = depth - xop->xo_stack_size;
580 bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
581 xop->xo_stack_size = depth;
589 xo_no_setlocale (void)
591 xo_locale_inited = 1; /* Skip initialization */
595 * For XML, the first character of a tag cannot be numeric, but people
596 * will likely not notice. So we people-proof them by forcing a leading
597 * underscore if they use invalid tags. Note that this doesn't cover
598 * all broken tags, just this fairly specific case.
601 xo_xml_leader_len (xo_handle_t *xop, const char *name, xo_ssize_t nlen)
603 if (name == NULL || isalpha(name[0]) || name[0] == '_')
606 xo_failure(xop, "invalid XML tag name: '%.*s'", nlen, name);
611 xo_xml_leader (xo_handle_t *xop, const char *name)
613 return xo_xml_leader_len(xop, name, strlen(name));
617 * We need to decide if stdout is line buffered (_IOLBF). Lacking a
618 * standard way to decide this (e.g. getlinebuf()), we have configure
619 * look to find __flbf, which glibc supported. If not, we'll rely on
620 * isatty, with the assumption that terminals are the only thing
621 * that's line buffered. We _could_ test for "steam._flags & _IOLBF",
622 * which is all __flbf does, but that's even tackier. Like a
623 * bedazzled Elvis outfit on an ugly lap dog sort of tacky. Not
624 * something we're willing to do.
627 xo_is_line_buffered (FILE *stream)
632 #else /* HAVE___FLBF */
633 if (isatty(fileno(stream)))
635 #endif /* HAVE___FLBF */
640 * Initialize an xo_handle_t, using both static defaults and
641 * the global settings from the LIBXO_OPTIONS environment
645 xo_init_handle (xo_handle_t *xop)
647 xop->xo_opaque = stdout;
648 xop->xo_write = xo_write_to_file;
649 xop->xo_flush = xo_flush_file;
651 if (xo_is_line_buffered(stdout))
652 XOF_SET(xop, XOF_FLUSH_LINE);
655 * We need to initialize the locale, which isn't really pretty.
656 * Libraries should depend on their caller to set up the
657 * environment. But we really can't count on the caller to do
658 * this, because well, they won't. Trust me.
660 if (!xo_locale_inited) {
661 xo_locale_inited = 1; /* Only do this once */
663 const char *cp = getenv("LC_CTYPE");
667 cp = getenv("LC_ALL");
669 cp = "C"; /* Default for C programs */
670 (void) setlocale(LC_CTYPE, cp);
674 * Initialize only the xo_buffers we know we'll need; the others
675 * can be allocated as needed.
677 xo_buf_init(&xop->xo_data);
678 xo_buf_init(&xop->xo_fmt);
680 if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
682 XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
684 xop->xo_indent_by = XO_INDENT_BY;
685 xo_depth_check(xop, XO_DEPTH);
687 XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
691 * Initialize the default handle.
694 xo_default_init (void)
696 xo_handle_t *xop = &xo_default_handle;
700 #if !defined(NO_LIBXO_OPTIONS)
701 if (!XOF_ISSET(xop, XOF_NO_ENV)) {
702 char *env = getenv("LIBXO_OPTIONS");
705 xo_set_options_simple(xop, env);
708 #endif /* NO_LIBXO_OPTIONS */
710 xo_default_inited = 1;
714 * Cheap convenience function to return either the argument, or
715 * the internal handle, after it has been initialized. The usage
717 * xop = xo_default(xop);
720 xo_default (xo_handle_t *xop)
723 if (xo_default_inited == 0)
725 xop = &xo_default_handle;
732 * Return the number of spaces we should be indenting. If
733 * we are pretty-printing, this is indent * indent_by.
736 xo_indent (xo_handle_t *xop)
740 xop = xo_default(xop);
742 if (XOF_ISSET(xop, XOF_PRETTY)) {
743 rc = xop->xo_indent * xop->xo_indent_by;
744 if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
745 rc += xop->xo_indent_by;
748 return (rc > 0) ? rc : 0;
752 xo_buf_indent (xo_handle_t *xop, int indent)
754 xo_buffer_t *xbp = &xop->xo_data;
757 indent = xo_indent(xop);
759 if (!xo_buf_has_room(xbp, indent))
762 memset(xbp->xb_curp, ' ', indent);
763 xbp->xb_curp += indent;
766 static char xo_xml_amp[] = "&";
767 static char xo_xml_lt[] = "<";
768 static char xo_xml_gt[] = ">";
769 static char xo_xml_quot[] = """;
772 xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags)
778 int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
780 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
781 /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
783 delta += sizeof(xo_xml_lt) - 2;
785 delta += sizeof(xo_xml_gt) - 2;
787 delta += sizeof(xo_xml_amp) - 2;
788 else if (attr && *cp == '"')
789 delta += sizeof(xo_xml_quot) - 2;
792 if (delta == 0) /* Nothing to escape; bail */
795 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
811 else if (attr && *cp == '"')
820 memcpy(ip, sp, slen);
822 } while (cp > ep && cp != ip);
828 xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
833 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
834 if (*cp == '\\' || *cp == '"')
836 else if (*cp == '\n' || *cp == '\r')
840 if (delta == 0) /* Nothing to escape; bail */
843 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
853 if (*cp == '\\' || *cp == '"') {
856 } else if (*cp == '\n') {
859 } else if (*cp == '\r') {
866 } while (cp > ep && cp != ip);
872 * PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
873 * ; ']' MUST be escaped.
876 xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
881 for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
882 if (*cp == '\\' || *cp == '"' || *cp == ']')
886 if (delta == 0) /* Nothing to escape; bail */
889 if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
899 if (*cp == '\\' || *cp == '"' || *cp == ']') {
906 } while (cp > ep && cp != ip);
912 xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
913 const char *str, ssize_t len, xo_xff_flags_t flags)
915 if (!xo_buf_has_room(xbp, len))
918 memcpy(xbp->xb_curp, str, len);
920 switch (xo_style(xop)) {
923 len = xo_escape_xml(xbp, len, flags);
927 len = xo_escape_json(xbp, len, flags);
930 case XO_STYLE_SDPARAMS:
931 len = xo_escape_sdparams(xbp, len, flags);
939 * Write the current contents of the data buffer using the handle's
943 xo_write (xo_handle_t *xop)
946 xo_buffer_t *xbp = &xop->xo_data;
948 if (xbp->xb_curp != xbp->xb_bufp) {
949 xo_buf_append(xbp, "", 1); /* Append ending NUL */
950 xo_anchor_clear(xop);
952 rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
953 xbp->xb_curp = xbp->xb_bufp;
956 /* Turn off the flags that don't survive across writes */
957 XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
963 * Format arguments into our buffer. If a custom formatter has been set,
964 * we use that to do the work; otherwise we vsnprintf().
967 xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
971 ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
973 va_copy(va_local, vap);
975 if (xop->xo_formatter)
976 rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
978 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
981 if (!xo_buf_has_room(xbp, rc)) {
987 * After we call vsnprintf(), the stage of vap is not defined.
988 * We need to copy it before we pass. Then we have to do our
989 * own logic below to move it along. This is because the
990 * implementation can have va_list be a pointer (bsd) or a
991 * structure (macosx) or anything in between.
994 va_end(va_local); /* Reset vap to the start */
995 va_copy(va_local, vap);
997 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
998 if (xop->xo_formatter)
999 rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
1001 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1009 * Print some data through the handle.
1012 xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
1014 xo_buffer_t *xbp = &xop->xo_data;
1015 ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1019 va_copy(va_local, vap);
1021 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1024 if (!xo_buf_has_room(xbp, rc)) {
1029 va_end(va_local); /* Reset vap to the start */
1030 va_copy(va_local, vap);
1032 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1033 rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1045 xo_printf (xo_handle_t *xop, const char *fmt, ...)
1052 rc = xo_printf_v(xop, fmt, vap);
1059 * These next few function are make The Essential UTF-8 Ginsu Knife.
1060 * Identify an input and output character, and convert it.
1062 static uint8_t xo_utf8_data_bits[5] = { 0, 0x7f, 0x1f, 0x0f, 0x07 };
1063 static uint8_t xo_utf8_len_bits[5] = { 0, 0x00, 0xc0, 0xe0, 0xf0 };
1066 * If the byte has a high-bit set, it's UTF-8, not ASCII.
1069 xo_is_utf8 (char ch)
1075 * Look at the high bits of the first byte to determine the length
1076 * of the UTF-8 character.
1078 static inline ssize_t
1079 xo_utf8_to_wc_len (const char *buf)
1081 uint8_t bval = (uint8_t) *buf;
1084 if ((bval & 0x80) == 0x0)
1086 else if ((bval & 0xe0) == 0xc0)
1088 else if ((bval & 0xf0) == 0xe0)
1090 else if ((bval & 0xf8) == 0xf0)
1099 xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz)
1101 unsigned b = (unsigned char) *buf;
1104 len = xo_utf8_to_wc_len(buf);
1106 xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
1111 xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
1116 for (i = 2; i < len; i++) {
1117 b = (unsigned char ) buf[i];
1118 if ((b & 0xc0) != 0x80) {
1119 xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
1128 * Build a wide character from the input buffer; the number of
1129 * bits we pull off the first character is dependent on the length,
1130 * but we put 6 bits off all other bytes.
1132 static inline wchar_t
1133 xo_utf8_char (const char *buf, ssize_t len)
1135 /* Most common case: singleton byte */
1137 return (unsigned char) buf[0];
1141 const unsigned char *cp = (const unsigned char *) buf;
1143 wc = *cp & xo_utf8_data_bits[len];
1144 for (i = 1; i < len; i++) {
1145 wc <<= 6; /* Low six bits have data */
1147 if ((cp[i] & 0xc0) != 0x80)
1148 return (wchar_t) -1;
1155 * Determine the number of bytes needed to encode a wide character.
1158 xo_utf8_emit_len (wchar_t wc)
1162 if ((wc & ((1 << 7) - 1)) == wc) /* Simple case */
1164 else if ((wc & ((1 << 11) - 1)) == wc)
1166 else if ((wc & ((1 << 16) - 1)) == wc)
1168 else if ((wc & ((1 << 21) - 1)) == wc)
1171 len = -1; /* Invalid */
1177 * Emit one wide character into the given buffer
1180 xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc)
1184 if (len == 1) { /* Simple case */
1189 /* Start with the low bits and insert them, six bits at a time */
1190 for (i = len - 1; i >= 0; i--) {
1191 buf[i] = 0x80 | (wc & 0x3f);
1192 wc >>= 6; /* Drop the low six bits */
1195 /* Finish off the first byte with the length bits */
1196 buf[0] &= xo_utf8_data_bits[len]; /* Clear out the length bits */
1197 buf[0] |= xo_utf8_len_bits[len]; /* Drop in new length bits */
1201 * Append a single UTF-8 character to a buffer, converting it to locale
1202 * encoding. Returns the number of columns consumed by that character,
1203 * as best we can determine it.
1206 xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
1207 const char *ibuf, ssize_t ilen)
1213 * Build our wide character from the input buffer; the number of
1214 * bits we pull off the first character is dependent on the length,
1215 * but we put 6 bits off all other bytes.
1217 wc = xo_utf8_char(ibuf, ilen);
1218 if (wc == (wchar_t) -1) {
1219 xo_failure(xop, "invalid UTF-8 byte sequence");
1223 if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
1224 if (!xo_buf_has_room(xbp, ilen))
1227 memcpy(xbp->xb_curp, ibuf, ilen);
1228 xbp->xb_curp += ilen;
1231 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1234 bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
1235 len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1238 xo_failure(xop, "could not convert wide char: %lx",
1239 (unsigned long) wc);
1242 xbp->xb_curp += len;
1245 return xo_wcwidth(wc);
1249 * Append a UTF-8 string to a buffer, converting it into locale encoding
1252 xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
1253 const char *cp, ssize_t len)
1255 const char *sp = cp, *ep = cp + len;
1256 ssize_t save_off = xbp->xb_bufp - xbp->xb_curp;
1260 for ( ; cp < ep; cp++) {
1261 if (!xo_is_utf8(*cp)) {
1267 * We're looking at a non-ascii UTF-8 character.
1268 * First we copy the previous data.
1269 * Then we need find the length and validate it.
1270 * Then we turn it into a wide string.
1271 * Then we turn it into a localized string.
1272 * Then we repeat. Isn't i18n fun?
1275 xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
1277 slen = xo_buf_utf8_len(xop, cp, ep - cp);
1279 /* Bad data; back it all out */
1280 xbp->xb_curp = xbp->xb_bufp + save_off;
1284 cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
1286 /* Next time through, we'll start at the next character */
1291 /* Update column values */
1292 if (XOF_ISSET(xop, XOF_COLUMNS))
1293 xop->xo_columns += cols;
1294 if (XOIF_ISSET(xop, XOIF_ANCHOR))
1295 xop->xo_anchor_columns += cols;
1297 /* Before we fall into the basic logic below, we need reset len */
1299 if (len != 0) /* Append trailing data */
1300 xo_buf_append(xbp, sp, len);
1304 * Append the given string to the given buffer, without escaping or
1305 * character set conversion. This is the straight copy to the data
1306 * buffer with no fanciness.
1309 xo_data_append (xo_handle_t *xop, const char *str, ssize_t len)
1311 xo_buf_append(&xop->xo_data, str, len);
1315 * Append the given string to the given buffer
1318 xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len)
1320 xo_buf_escape(xop, &xop->xo_data, str, len, 0);
1323 #ifdef LIBXO_NO_RETAIN
1325 * Empty implementations of the retain logic
1329 xo_retain_clear_all (void)
1335 xo_retain_clear (const char *fmt UNUSED)
1340 xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
1341 unsigned num_fields UNUSED)
1347 xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
1348 unsigned *nump UNUSED)
1353 #else /* !LIBXO_NO_RETAIN */
1355 * Retain: We retain parsed field definitions to enhance performance,
1356 * especially inside loops. We depend on the caller treating the format
1357 * strings as immutable, so that we can retain pointers into them. We
1358 * hold the pointers in a hash table, so allow quick access. Retained
1359 * information is retained until xo_retain_clear is called.
1363 * xo_retain_entry_t holds information about one retained set of
1366 typedef struct xo_retain_entry_s {
1367 struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
1368 unsigned long xre_hits; /* Number of times we've hit */
1369 const char *xre_format; /* Pointer to format string */
1370 unsigned xre_num_fields; /* Number of fields saved */
1371 xo_field_info_t *xre_fields; /* Pointer to fields */
1372 } xo_retain_entry_t;
1375 * xo_retain_t holds a complete set of parsed fields as a hash table.
1377 #ifndef XO_RETAIN_SIZE
1378 #define XO_RETAIN_SIZE 6
1379 #endif /* XO_RETAIN_SIZE */
1380 #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
1382 typedef struct xo_retain_s {
1383 xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
1386 static THREAD_LOCAL(xo_retain_t) xo_retain;
1387 static THREAD_LOCAL(unsigned) xo_retain_count;
1390 * Simple hash function based on Thomas Wang's paper. The original is
1391 * gone, but an archive is available on the Way Back Machine:
1393 * http://web.archive.org/web/20071223173210/\
1394 * http://www.concentric.net/~Ttwang/tech/inthash.htm
1396 * For our purposes, we can assume the low four bits are uninteresting
1397 * since any string less that 16 bytes wouldn't be worthy of
1398 * retaining. We toss the high bits also, since these bits are likely
1399 * to be common among constant format strings. We then run Wang's
1400 * algorithm, and cap the result at RETAIN_HASH_SIZE.
1403 xo_retain_hash (const char *fmt)
1405 volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
1407 /* Discard low four bits and high bits; they aren't interesting */
1408 uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
1410 val = (val ^ 61) ^ (val >> 16);
1411 val = val + (val << 3);
1412 val = val ^ (val >> 4);
1413 val = val * 0x3a8f05c5; /* My large prime number */
1414 val = val ^ (val >> 15);
1415 val &= RETAIN_HASH_SIZE - 1;
1421 * Walk all buckets, clearing all retained entries
1424 xo_retain_clear_all (void)
1427 xo_retain_entry_t *xrep, *next;
1429 for (i = 0; i < RETAIN_HASH_SIZE; i++) {
1430 for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
1431 next = xrep->xre_next;
1434 xo_retain.xr_bucket[i] = NULL;
1436 xo_retain_count = 0;
1440 * Walk all buckets, clearing all retained entries
1443 xo_retain_clear (const char *fmt)
1445 xo_retain_entry_t **xrepp;
1446 unsigned hash = xo_retain_hash(fmt);
1448 for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
1449 xrepp = &(*xrepp)->xre_next) {
1450 if ((*xrepp)->xre_format == fmt) {
1451 *xrepp = (*xrepp)->xre_next;
1452 xo_retain_count -= 1;
1459 * Search the hash for an entry matching 'fmt'; return it's fields.
1462 xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
1464 if (xo_retain_count == 0)
1467 unsigned hash = xo_retain_hash(fmt);
1468 xo_retain_entry_t *xrep;
1470 for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
1471 xrep = xrep->xre_next) {
1472 if (xrep->xre_format == fmt) {
1473 *valp = xrep->xre_fields;
1474 *nump = xrep->xre_num_fields;
1475 xrep->xre_hits += 1;
1484 xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
1486 unsigned hash = xo_retain_hash(fmt);
1487 xo_retain_entry_t *xrep;
1488 ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
1489 xo_field_info_t *xfip;
1491 xrep = xo_realloc(NULL, sz);
1495 xfip = (xo_field_info_t *) &xrep[1];
1496 memcpy(xfip, fields, num_fields * sizeof(*fields));
1498 bzero(xrep, sizeof(*xrep));
1500 xrep->xre_format = fmt;
1501 xrep->xre_fields = xfip;
1502 xrep->xre_num_fields = num_fields;
1504 /* Record the field info in the retain bucket */
1505 xrep->xre_next = xo_retain.xr_bucket[hash];
1506 xo_retain.xr_bucket[hash] = xrep;
1507 xo_retain_count += 1;
1510 #endif /* !LIBXO_NO_RETAIN */
1513 * Generate a warning. Normally, this is a text message written to
1514 * standard error. If the XOF_WARN_XML flag is set, then we generate
1515 * XMLified content on standard output.
1518 xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
1519 const char *fmt, va_list vap)
1521 xop = xo_default(xop);
1522 if (check_warn && !XOF_ISSET(xop, XOF_WARN))
1528 ssize_t len = strlen(fmt);
1529 ssize_t plen = xo_program ? strlen(xo_program) : 0;
1530 char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
1533 memcpy(newfmt, xo_program, plen);
1534 newfmt[plen++] = ':';
1535 newfmt[plen++] = ' ';
1538 memcpy(newfmt + plen, fmt, len);
1539 newfmt[len + plen] = '\0';
1541 if (XOF_ISSET(xop, XOF_WARN_XML)) {
1542 static char err_open[] = "<error>";
1543 static char err_close[] = "</error>";
1544 static char msg_open[] = "<message>";
1545 static char msg_close[] = "</message>";
1547 xo_buffer_t *xbp = &xop->xo_data;
1549 xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
1550 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1553 va_copy(va_local, vap);
1555 ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1556 ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1559 if (!xo_buf_has_room(xbp, rc)) {
1564 va_end(vap); /* Reset vap to the start */
1565 va_copy(vap, va_local);
1567 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1568 rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1573 rc = xo_escape_xml(xbp, rc, 1);
1576 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1577 xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
1580 const char *msg = strerror(code);
1583 xo_buf_append(xbp, ": ", 2);
1584 xo_buf_append(xbp, msg, strlen(msg));
1588 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1589 (void) xo_write(xop);
1592 vfprintf(stderr, newfmt, vap);
1594 const char *msg = strerror(code);
1597 fprintf(stderr, ": %s", msg);
1599 fprintf(stderr, "\n");
1604 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1609 xo_warn_hcv(xop, code, 0, fmt, vap);
1614 xo_warn_c (int code, const char *fmt, ...)
1619 xo_warn_hcv(NULL, code, 0, fmt, vap);
1624 xo_warn (const char *fmt, ...)
1630 xo_warn_hcv(NULL, code, 0, fmt, vap);
1635 xo_warnx (const char *fmt, ...)
1640 xo_warn_hcv(NULL, -1, 0, fmt, vap);
1645 xo_err (int eval, const char *fmt, ...)
1651 xo_warn_hcv(NULL, code, 0, fmt, vap);
1658 xo_errx (int eval, const char *fmt, ...)
1663 xo_warn_hcv(NULL, -1, 0, fmt, vap);
1670 xo_errc (int eval, int code, const char *fmt, ...)
1675 xo_warn_hcv(NULL, code, 0, fmt, vap);
1682 * Generate a warning. Normally, this is a text message written to
1683 * standard error. If the XOF_WARN_XML flag is set, then we generate
1684 * XMLified content on standard output.
1687 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1689 static char msg_open[] = "<message>";
1690 static char msg_close[] = "</message>";
1695 xop = xo_default(xop);
1697 if (fmt == NULL || *fmt == '\0')
1700 int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1702 switch (xo_style(xop)) {
1704 xbp = &xop->xo_data;
1705 if (XOF_ISSET(xop, XOF_PRETTY))
1706 xo_buf_indent(xop, xop->xo_indent_by);
1707 xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1709 va_copy(va_local, vap);
1711 ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1713 rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1715 if (!xo_buf_has_room(xbp, rc)) {
1720 va_end(vap); /* Reset vap to the start */
1721 va_copy(vap, va_local);
1723 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1724 rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1729 rc = xo_escape_xml(xbp, rc, 0);
1732 if (need_nl && code > 0) {
1733 const char *msg = strerror(code);
1736 xo_buf_append(xbp, ": ", 2);
1737 xo_buf_append(xbp, msg, strlen(msg));
1742 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1744 xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1746 if (XOF_ISSET(xop, XOF_PRETTY))
1747 xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1749 (void) xo_write(xop);
1754 char buf[BUFSIZ], *bp = buf, *cp;
1755 ssize_t bufsiz = sizeof(buf);
1758 va_copy(va_local, vap);
1760 rc = vsnprintf(bp, bufsiz, fmt, va_local);
1762 bufsiz = rc + BUFSIZ;
1763 bp = alloca(bufsiz);
1765 va_copy(va_local, vap);
1766 rc = vsnprintf(bp, bufsiz, fmt, va_local);
1773 rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1774 (code > 0) ? ": " : "",
1775 (code > 0) ? strerror(code) : "");
1780 xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc,
1786 case XO_STYLE_SDPARAMS:
1787 case XO_STYLE_ENCODER:
1788 /* No means of representing messages */
1792 rc = xo_printf_v(xop, fmt, vap);
1794 * XXX need to handle UTF-8 widths
1797 if (XOF_ISSET(xop, XOF_COLUMNS))
1798 xop->xo_columns += rc;
1799 if (XOIF_ISSET(xop, XOIF_ANCHOR))
1800 xop->xo_anchor_columns += rc;
1803 if (need_nl && code > 0) {
1804 const char *msg = strerror(code);
1807 xo_printf(xop, ": %s", msg);
1811 xo_printf(xop, "\n");
1816 switch (xo_style(xop)) {
1818 if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
1819 static char div_close[] = "</div>";
1821 XOIF_CLEAR(xop, XOIF_DIV_OPEN);
1822 xo_data_append(xop, div_close, sizeof(div_close) - 1);
1824 if (XOF_ISSET(xop, XOF_PRETTY))
1825 xo_data_append(xop, "\n", 1);
1830 (void) xo_flush_h(xop);
1834 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1839 xo_message_hcv(xop, code, fmt, vap);
1844 xo_message_c (int code, const char *fmt, ...)
1849 xo_message_hcv(NULL, code, fmt, vap);
1854 xo_message_e (const char *fmt, ...)
1860 xo_message_hcv(NULL, code, fmt, vap);
1865 xo_message (const char *fmt, ...)
1870 xo_message_hcv(NULL, 0, fmt, vap);
1875 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1877 if (!XOF_ISSET(xop, XOF_WARN))
1883 xo_warn_hcv(xop, -1, 1, fmt, vap);
1888 * Create a handle for use by later libxo functions.
1890 * Note: normal use of libxo does not require a distinct handle, since
1891 * the default handle (used when NULL is passed) generates text on stdout.
1893 * @param style Style of output desired (XO_STYLE_* value)
1894 * @param flags Set of XOF_* flags in use with this handle
1895 * @return Newly allocated handle
1899 xo_create (xo_style_t style, xo_xof_flags_t flags)
1901 xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1904 bzero(xop, sizeof(*xop));
1906 xop->xo_style = style;
1907 XOF_SET(xop, flags);
1908 xo_init_handle(xop);
1909 xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */
1916 * Create a handle that will write to the given file. Use
1917 * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1919 * @param fp FILE pointer to use
1920 * @param style Style of output desired (XO_STYLE_* value)
1921 * @param flags Set of XOF_* flags to use with this handle
1922 * @return Newly allocated handle
1926 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1928 xo_handle_t *xop = xo_create(style, flags);
1931 xop->xo_opaque = fp;
1932 xop->xo_write = xo_write_to_file;
1933 xop->xo_close = xo_close_file;
1934 xop->xo_flush = xo_flush_file;
1941 * Set the default handler to output to a file.
1943 * @param xop libxo handle
1944 * @param fp FILE pointer to use
1945 * @return 0 on success, non-zero on failure
1948 xo_set_file_h (xo_handle_t *xop, FILE *fp)
1950 xop = xo_default(xop);
1953 xo_failure(xop, "xo_set_file: NULL fp");
1957 xop->xo_opaque = fp;
1958 xop->xo_write = xo_write_to_file;
1959 xop->xo_close = xo_close_file;
1960 xop->xo_flush = xo_flush_file;
1966 * Set the default handler to output to a file.
1968 * @param fp FILE pointer to use
1969 * @return 0 on success, non-zero on failure
1972 xo_set_file (FILE *fp)
1974 return xo_set_file_h(NULL, fp);
1978 * Release any resources held by the handle.
1980 * @param xop XO handle to alter (or NULL for default handle)
1983 xo_destroy (xo_handle_t *xop_arg)
1985 xo_handle_t *xop = xo_default(xop_arg);
1989 if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
1990 xop->xo_close(xop->xo_opaque);
1992 xo_free(xop->xo_stack);
1993 xo_buf_cleanup(&xop->xo_data);
1994 xo_buf_cleanup(&xop->xo_fmt);
1995 xo_buf_cleanup(&xop->xo_predicate);
1996 xo_buf_cleanup(&xop->xo_attrs);
1997 xo_buf_cleanup(&xop->xo_color_buf);
1999 if (xop->xo_version)
2000 xo_free(xop->xo_version);
2002 if (xop_arg == NULL) {
2003 bzero(&xo_default_handle, sizeof(xo_default_handle));
2004 xo_default_inited = 0;
2010 * Record a new output style to use for the given handle (or default if
2011 * handle is NULL). This output style will be used for any future output.
2013 * @param xop XO handle to alter (or NULL for default handle)
2014 * @param style new output style (XO_STYLE_*)
2017 xo_set_style (xo_handle_t *xop, xo_style_t style)
2019 xop = xo_default(xop);
2020 xop->xo_style = style;
2024 * Return the current style of a handle
2026 * @param xop XO handle to access
2027 * @return The handle's current style
2030 xo_get_style (xo_handle_t *xop)
2032 xop = xo_default(xop);
2033 return xo_style(xop);
2037 * Return the XO_STYLE_* value matching a given name
2039 * @param name String name of a style
2040 * @return XO_STYLE_* value
2043 xo_name_to_style (const char *name)
2045 if (strcmp(name, "xml") == 0)
2046 return XO_STYLE_XML;
2047 else if (strcmp(name, "json") == 0)
2048 return XO_STYLE_JSON;
2049 else if (strcmp(name, "encoder") == 0)
2050 return XO_STYLE_ENCODER;
2051 else if (strcmp(name, "text") == 0)
2052 return XO_STYLE_TEXT;
2053 else if (strcmp(name, "html") == 0)
2054 return XO_STYLE_HTML;
2055 else if (strcmp(name, "sdparams") == 0)
2056 return XO_STYLE_SDPARAMS;
2062 * Indicate if the style is an "encoding" one as opposed to a "display" one.
2065 xo_style_is_encoding (xo_handle_t *xop)
2067 if (xo_style(xop) == XO_STYLE_JSON
2068 || xo_style(xop) == XO_STYLE_XML
2069 || xo_style(xop) == XO_STYLE_SDPARAMS
2070 || xo_style(xop) == XO_STYLE_ENCODER)
2075 /* Simple name-value mapping */
2076 typedef struct xo_mapping_s {
2077 xo_xff_flags_t xm_value; /* Flag value */
2078 const char *xm_name; /* String name */
2081 static xo_xff_flags_t
2082 xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len)
2088 len = strlen(value);
2090 while (isspace((int) *value)) {
2095 while (isspace((int) value[len]))
2101 for ( ; map->xm_name; map++)
2102 if (strncmp(map->xm_name, value, len) == 0)
2103 return map->xm_value;
2108 #ifdef NOT_NEEDED_YET
2110 xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2115 for ( ; map->xm_name; map++)
2116 if (map->xm_value == value)
2117 return map->xm_name;
2121 #endif /* NOT_NEEDED_YET */
2123 static xo_mapping_t xo_xof_names[] = {
2124 { XOF_COLOR_ALLOWED, "color" },
2125 { XOF_COLOR, "color-force" },
2126 { XOF_COLUMNS, "columns" },
2127 { XOF_DTRT, "dtrt" },
2128 { XOF_FLUSH, "flush" },
2129 { XOF_FLUSH_LINE, "flush-line" },
2130 { XOF_IGNORE_CLOSE, "ignore-close" },
2131 { XOF_INFO, "info" },
2132 { XOF_KEYS, "keys" },
2133 { XOF_LOG_GETTEXT, "log-gettext" },
2134 { XOF_LOG_SYSLOG, "log-syslog" },
2135 { XOF_NO_HUMANIZE, "no-humanize" },
2136 { XOF_NO_LOCALE, "no-locale" },
2137 { XOF_RETAIN_NONE, "no-retain" },
2138 { XOF_NO_TOP, "no-top" },
2139 { XOF_NOT_FIRST, "not-first" },
2140 { XOF_PRETTY, "pretty" },
2141 { XOF_RETAIN_ALL, "retain" },
2142 { XOF_UNDERSCORES, "underscores" },
2143 { XOF_UNITS, "units" },
2144 { XOF_WARN, "warn" },
2145 { XOF_WARN_XML, "warn-xml" },
2146 { XOF_XPATH, "xpath" },
2150 /* Options available via the environment variable ($LIBXO_OPTIONS) */
2151 static xo_mapping_t xo_xof_simple_names[] = {
2152 { XOF_COLOR_ALLOWED, "color" },
2153 { XOF_FLUSH, "flush" },
2154 { XOF_FLUSH_LINE, "flush-line" },
2155 { XOF_NO_HUMANIZE, "no-humanize" },
2156 { XOF_NO_LOCALE, "no-locale" },
2157 { XOF_RETAIN_NONE, "no-retain" },
2158 { XOF_PRETTY, "pretty" },
2159 { XOF_RETAIN_ALL, "retain" },
2160 { XOF_UNDERSCORES, "underscores" },
2161 { XOF_WARN, "warn" },
2166 * Convert string name to XOF_* flag value.
2167 * Not all are useful. Or safe. Or sane.
2170 xo_name_to_flag (const char *name)
2172 return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
2176 * Set the style of an libxo handle based on a string name
2178 * @param xop XO handle
2179 * @param name String value of name
2180 * @return 0 on success, non-zero on failure
2183 xo_set_style_name (xo_handle_t *xop, const char *name)
2188 int style = xo_name_to_style(name);
2193 xo_set_style(xop, style);
2198 * Fill in the color map, based on the input string; currently unimplemented
2199 * Look for something like "colors=red/blue+green/yellow" as fg/bg pairs.
2202 xo_set_color_map (xo_handle_t *xop, char *value)
2207 char *cp, *ep, *vp, *np;
2208 ssize_t len = value ? strlen(value) + 1 : 0;
2209 int num = 1, fg, bg;
2211 for (cp = value, ep = cp + len - 1; cp && *cp && cp < ep; cp = np) {
2212 np = strchr(cp, '+');
2216 vp = strchr(cp, '/');
2220 fg = *cp ? xo_color_find(cp) : -1;
2221 bg = (vp && *vp) ? xo_color_find(vp) : -1;
2223 #ifndef LIBXO_TEXT_ONLY
2224 xop->xo_color_map_fg[num] = (fg < 0) ? num : fg;
2225 xop->xo_color_map_bg[num] = (bg < 0) ? num : bg;
2226 #endif /* LIBXO_TEXT_ONLY */
2228 if (++num > XO_NUM_COLORS)
2232 /* If no color initialization happened, then we don't need the map */
2234 XOF_SET(xop, XOF_COLOR_MAP);
2236 XOF_CLEAR(xop, XOF_COLOR_MAP);
2238 #ifndef LIBXO_TEXT_ONLY
2239 /* Fill in the rest of the colors with the defaults */
2240 for ( ; num < XO_NUM_COLORS; num++)
2241 xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num;
2242 #endif /* LIBXO_TEXT_ONLY */
2246 xo_set_options_simple (xo_handle_t *xop, const char *input)
2248 xo_xof_flags_t new_flag;
2249 char *cp, *ep, *vp, *np, *bp;
2250 ssize_t len = strlen(input) + 1;
2253 memcpy(bp, input, len);
2255 for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2256 np = strchr(cp, ',');
2260 vp = strchr(cp, '=');
2264 if (strcmp("colors", cp) == 0) {
2265 xo_set_color_map(xop, vp);
2269 new_flag = xo_name_lookup(xo_xof_simple_names, cp, -1);
2270 if (new_flag != 0) {
2271 XOF_SET(xop, new_flag);
2272 } else if (strcmp(cp, "no-color") == 0) {
2273 XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2275 xo_failure(xop, "unknown simple option: %s", cp);
2284 * Set the options for a handle using a string of options
2285 * passed in. The input is a comma-separated set of names
2286 * and optional values: "xml,pretty,indent=4"
2288 * @param xop XO handle
2289 * @param input Comma-separated set of option values
2290 * @return 0 on success, non-zero on failure
2293 xo_set_options (xo_handle_t *xop, const char *input)
2295 char *cp, *ep, *vp, *np, *bp;
2296 int style = -1, new_style, rc = 0;
2298 xo_xof_flags_t new_flag;
2303 xop = xo_default(xop);
2305 #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2306 /* If the installer used --enable-color-on-by-default, then we allow it */
2307 XOF_SET(xop, XOF_COLOR_ALLOWED);
2308 #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2311 * We support a simpler, old-school style of giving option
2312 * also, using a single character for each option. It's
2313 * ideal for lazy people, such as myself.
2315 if (*input == ':') {
2318 for (input++ ; *input; input++) {
2321 XOF_SET(xop, XOF_COLOR_ALLOWED);
2325 XOF_SET(xop, XOF_FLUSH);
2329 XOF_SET(xop, XOF_FLUSH_LINE);
2333 XOF_SET(xop, XOF_LOG_GETTEXT);
2337 xop->xo_style = XO_STYLE_HTML;
2341 XOF_SET(xop, XOF_INFO);
2345 sz = strspn(input + 1, "0123456789");
2347 xop->xo_indent_by = atoi(input + 1);
2348 input += sz - 1; /* Skip value */
2353 xop->xo_style = XO_STYLE_JSON;
2357 XOF_SET(xop, XOF_KEYS);
2361 XOF_SET(xop, XOF_NO_HUMANIZE);
2365 XOF_SET(xop, XOF_PRETTY);
2369 xop->xo_style = XO_STYLE_TEXT;
2373 XOF_SET(xop, XOF_UNITS);
2377 XOF_SET(xop, XOF_UNDERSCORES);
2381 XOF_SET(xop, XOF_WARN);
2385 xop->xo_style = XO_STYLE_XML;
2389 XOF_SET(xop, XOF_XPATH);
2396 len = strlen(input) + 1;
2398 memcpy(bp, input, len);
2400 for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2401 np = strchr(cp, ',');
2405 vp = strchr(cp, '=');
2409 if (strcmp("colors", cp) == 0) {
2410 xo_set_color_map(xop, vp);
2415 * For options, we don't allow "encoder" since we want to
2416 * handle it explicitly below as "encoder=xxx".
2418 new_style = xo_name_to_style(cp);
2419 if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
2421 xo_warnx("ignoring multiple styles: '%s'", cp);
2425 new_flag = xo_name_to_flag(cp);
2427 XOF_SET(xop, new_flag);
2428 else if (strcmp(cp, "no-color") == 0)
2429 XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2430 else if (strcmp(cp, "indent") == 0) {
2432 xop->xo_indent_by = atoi(vp);
2434 xo_failure(xop, "missing value for indent option");
2435 } else if (strcmp(cp, "encoder") == 0) {
2437 xo_failure(xop, "missing value for encoder option");
2439 if (xo_encoder_init(xop, vp)) {
2440 xo_failure(xop, "encoder not found: %s", vp);
2446 xo_warnx("unknown libxo option value: '%s'", cp);
2453 xop->xo_style= style;
2459 * Set one or more flags for a given handle (or default if handle is NULL).
2460 * These flags will affect future output.
2462 * @param xop XO handle to alter (or NULL for default handle)
2463 * @param flags Flags to be set (XOF_*)
2466 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2468 xop = xo_default(xop);
2470 XOF_SET(xop, flags);
2474 * Accessor to return the current set of flags for a handle
2475 * @param xop XO handle
2476 * @return Current set of flags
2479 xo_get_flags (xo_handle_t *xop)
2481 xop = xo_default(xop);
2483 return xop->xo_flags;
2487 * strndup with a twist: len < 0 means len = strlen(str)
2490 xo_strndup (const char *str, ssize_t len)
2495 char *cp = xo_realloc(NULL, len + 1);
2497 memcpy(cp, str, len);
2505 * Record a leading prefix for the XPath we generate. This allows the
2506 * generated data to be placed within an XML hierarchy but still have
2507 * accurate XPath expressions.
2509 * @param xop XO handle to alter (or NULL for default handle)
2510 * @param path The XPath expression
2513 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
2515 xop = xo_default(xop);
2517 if (xop->xo_leading_xpath) {
2518 xo_free(xop->xo_leading_xpath);
2519 xop->xo_leading_xpath = NULL;
2525 xop->xo_leading_xpath = xo_strndup(path, -1);
2529 * Record the info data for a set of tags
2531 * @param xop XO handle to alter (or NULL for default handle)
2532 * @param info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
2533 * @pararm count Number of entries in info (or -1 to count them ourselves)
2536 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
2538 xop = xo_default(xop);
2540 if (count < 0 && infop) {
2543 for (xip = infop, count = 0; xip->xi_name; xip++, count++)
2547 xop->xo_info = infop;
2548 xop->xo_info_count = count;
2552 * Set the formatter callback for a handle. The callback should
2553 * return a newly formatting contents of a formatting instruction,
2554 * meaning the bits inside the braces.
2557 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
2558 xo_checkpointer_t cfunc)
2560 xop = xo_default(xop);
2562 xop->xo_formatter = func;
2563 xop->xo_checkpointer = cfunc;
2567 * Clear one or more flags for a given handle (or default if handle is NULL).
2568 * These flags will affect future output.
2570 * @param xop XO handle to alter (or NULL for default handle)
2571 * @param flags Flags to be cleared (XOF_*)
2574 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2576 xop = xo_default(xop);
2578 XOF_CLEAR(xop, flags);
2582 xo_state_name (xo_state_t state)
2584 static const char *names[] = {
2602 if (state < (sizeof(names) / sizeof(names[0])))
2603 return names[state];
2609 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
2611 static char div_open[] = "<div class=\"line\">";
2612 static char div_open_blank[] = "<div class=\"blank-line\">";
2614 if (XOF_ISSET(xop, XOF_CONTINUATION)) {
2615 XOF_CLEAR(xop, XOF_CONTINUATION);
2616 XOIF_SET(xop, XOIF_DIV_OPEN);
2620 if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
2623 if (xo_style(xop) != XO_STYLE_HTML)
2626 XOIF_SET(xop, XOIF_DIV_OPEN);
2627 if (flags & XFF_BLANK_LINE)
2628 xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
2630 xo_data_append(xop, div_open, sizeof(div_open) - 1);
2632 if (XOF_ISSET(xop, XOF_PRETTY))
2633 xo_data_append(xop, "\n", 1);
2637 xo_line_close (xo_handle_t *xop)
2639 static char div_close[] = "</div>";
2641 switch (xo_style(xop)) {
2643 if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
2644 xo_line_ensure_open(xop, 0);
2646 XOIF_CLEAR(xop, XOIF_DIV_OPEN);
2647 xo_data_append(xop, div_close, sizeof(div_close) - 1);
2649 if (XOF_ISSET(xop, XOF_PRETTY))
2650 xo_data_append(xop, "\n", 1);
2654 xo_data_append(xop, "\n", 1);
2660 xo_info_compare (const void *key, const void *data)
2662 const char *name = key;
2663 const xo_info_t *xip = data;
2665 return strcmp(name, xip->xi_name);
2670 xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen)
2673 char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
2675 memcpy(cp, name, nlen);
2678 xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
2679 sizeof(xop->xo_info[0]), xo_info_compare);
2683 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
2686 * Check to see that the conversion is safe and sane.
2689 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
2691 switch (CONVERT(have_enc, need_enc)) {
2692 case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
2693 case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
2694 case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
2695 case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
2696 case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
2697 case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
2701 xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
2707 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
2708 xo_xff_flags_t flags,
2709 const wchar_t *wcp, const char *cp,
2710 ssize_t len, int max,
2711 int need_enc, int have_enc)
2717 int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
2720 if (len > 0 && !xo_buf_has_room(xbp, len))
2730 if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
2733 if (len == 0 || *cp == '\0')
2738 if (wcp && *wcp == L'\0')
2744 case XF_ENC_WIDE: /* Wide character */
2749 case XF_ENC_UTF8: /* UTF-8 */
2750 ilen = xo_utf8_to_wc_len(cp);
2752 xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2753 return -1; /* Can't continue; we can't find the end */
2756 if (len > 0 && len < ilen) {
2757 len = 0; /* Break out of the loop */
2761 wc = xo_utf8_char(cp, ilen);
2762 if (wc == (wchar_t) -1) {
2763 xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
2765 return -1; /* Can't continue; we can't find the end */
2770 case XF_ENC_LOCALE: /* Native locale */
2771 ilen = (len > 0) ? len : MB_LEN_MAX;
2772 ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
2773 if (ilen < 0) { /* Invalid data; skip */
2774 xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2779 if (ilen == 0) { /* Hit a wide NUL character */
2788 /* Reduce len, but not below zero */
2796 * Find the width-in-columns of this character, which must be done
2797 * in wide characters, since we lack a mbswidth() function. If
2800 width = xo_wcwidth(wc);
2802 width = iswcntrl(wc) ? 0 : 1;
2804 if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
2805 if (max > 0 && cols + width > max)
2812 /* Output in UTF-8 needs to be escaped, based on the style */
2813 switch (xo_style(xop)) {
2822 else if (attr && wc == '"')
2827 ssize_t slen = strlen(sp);
2828 if (!xo_buf_has_room(xbp, slen - 1))
2831 memcpy(xbp->xb_curp, sp, slen);
2832 xbp->xb_curp += slen;
2833 goto done_with_encoding; /* Need multi-level 'break' */
2836 if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
2839 if (!xo_buf_has_room(xbp, 2))
2842 *xbp->xb_curp++ = '\\';
2845 else if (wc == '\r')
2847 else wc = wc & 0x7f;
2849 *xbp->xb_curp++ = wc;
2850 goto done_with_encoding;
2852 case XO_STYLE_SDPARAMS:
2853 if (wc != '\\' && wc != '"' && wc != ']')
2856 if (!xo_buf_has_room(xbp, 2))
2859 *xbp->xb_curp++ = '\\';
2861 *xbp->xb_curp++ = wc;
2862 goto done_with_encoding;
2865 olen = xo_utf8_emit_len(wc);
2867 xo_failure(xop, "ignoring bad length");
2871 if (!xo_buf_has_room(xbp, olen))
2874 xo_utf8_emit_char(xbp->xb_curp, olen, wc);
2875 xbp->xb_curp += olen;
2879 if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
2882 olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
2884 xo_failure(xop, "could not convert wide char: %lx",
2885 (unsigned long) wc);
2887 *xbp->xb_curp++ = '?';
2889 xbp->xb_curp += olen;
2901 xo_needed_encoding (xo_handle_t *xop)
2903 if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2906 if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2907 return XF_ENC_LOCALE;
2909 return XF_ENC_UTF8; /* Otherwise, we love UTF-8 */
2913 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
2916 static char null[] = "(null)";
2917 static char null_no_quotes[] = "null";
2920 wchar_t *wcp = NULL;
2922 ssize_t cols = 0, rc = 0;
2923 ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2;
2924 int need_enc = xo_needed_encoding(xop);
2926 if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
2929 len = xfp->xf_width[XF_WIDTH_SIZE];
2931 if (xfp->xf_fc == 'm') {
2932 cp = strerror(xop->xo_errno);
2934 len = cp ? strlen(cp) : 0;
2937 } else if (xfp->xf_enc == XF_ENC_WIDE) {
2938 wcp = va_arg(xop->xo_vap, wchar_t *);
2943 * Dont' deref NULL; use the traditional "(null)" instead
2944 * of the more accurate "who's been a naughty boy, then?".
2948 len = sizeof(null) - 1;
2952 cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2958 /* Echo "Dont' deref NULL" logic */
2960 if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2961 cp = null_no_quotes;
2962 len = sizeof(null_no_quotes) - 1;
2965 len = sizeof(null) - 1;
2970 * Optimize the most common case, which is "%s". We just
2971 * need to copy the complete string to the output buffer.
2973 if (xfp->xf_enc == need_enc
2974 && xfp->xf_width[XF_WIDTH_MIN] < 0
2975 && xfp->xf_width[XF_WIDTH_SIZE] < 0
2976 && xfp->xf_width[XF_WIDTH_MAX] < 0
2977 && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2978 || XOF_ISSET(xop, XOF_COLUMNS))) {
2980 xo_buf_escape(xop, xbp, cp, len, flags);
2983 * Our caller expects xb_curp left untouched, so we have
2984 * to reset it and return the number of bytes written to
2987 off2 = xbp->xb_curp - xbp->xb_bufp;
2989 xbp->xb_curp = xbp->xb_bufp + off;
2995 cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
2996 xfp->xf_width[XF_WIDTH_MAX],
2997 need_enc, xfp->xf_enc);
3002 * xo_buf_append* will move xb_curp, so we save/restore it.
3004 off2 = xbp->xb_curp - xbp->xb_bufp;
3006 xbp->xb_curp = xbp->xb_bufp + off;
3008 if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
3010 * Find the number of columns needed to display the string.
3011 * If we have the original wide string, we just call wcswidth,
3012 * but if we did the work ourselves, then we need to do it.
3014 int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
3015 if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN]))
3019 * If seen_minus, then pad on the right; otherwise move it so
3020 * we can pad on the left.
3022 if (xfp->xf_seen_minus) {
3023 cp = xbp->xb_curp + rc;
3026 memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
3029 /* Set the padding */
3030 memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
3035 if (XOF_ISSET(xop, XOF_COLUMNS))
3036 xop->xo_columns += cols;
3037 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3038 xop->xo_anchor_columns += cols;
3043 xbp->xb_curp = xbp->xb_bufp + off;
3048 * Look backwards in a buffer to find a numeric value
3051 xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset)
3053 int rc = 0; /* Fail with zero */
3055 char *sp = xbp->xb_bufp;
3056 char *cp = sp + start_offset;
3059 if (isdigit((int) *cp))
3062 for ( ; cp >= sp; cp--) {
3063 if (!isdigit((int) *cp))
3065 rc += (*cp - '0') * digit;
3073 xo_count_utf8_cols (const char *str, ssize_t len)
3078 const char *ep = str + len;
3081 tlen = xo_utf8_to_wc_len(str);
3082 if (tlen < 0) /* Broken input is very bad */
3085 wc = xo_utf8_char(str, tlen);
3086 if (wc == (wchar_t) -1)
3089 /* We only print printable characters */
3090 if (iswprint((wint_t) wc)) {
3092 * Find the width-in-columns of this character, which must be done
3093 * in wide characters, since we lack a mbswidth() function.
3095 ssize_t width = xo_wcwidth(wc);
3097 width = iswcntrl(wc) ? 0 : 1;
3109 static inline const char *
3110 xo_dgettext (xo_handle_t *xop, const char *str)
3112 const char *domainname = xop->xo_gt_domain;
3115 res = dgettext(domainname, str);
3117 if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3118 fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
3119 domainname ? "domain \"" : "", xo_printable(domainname),
3120 domainname ? "\", " : "", xo_printable(str), xo_printable(res));
3125 static inline const char *
3126 xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
3127 unsigned long int n)
3129 const char *domainname = xop->xo_gt_domain;
3132 res = dngettext(domainname, sing, plural, n);
3133 if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3134 fprintf(stderr, "xo: gettext: %s%s%s"
3135 "msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
3136 domainname ? "domain \"" : "",
3137 xo_printable(domainname), domainname ? "\", " : "",
3139 xo_printable(plural), n, xo_printable(res));
3143 #else /* HAVE_GETTEXT */
3144 static inline const char *
3145 xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
3150 static inline const char *
3151 xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
3152 const char *plural, unsigned long int n)
3154 return (n == 1) ? singular : plural;
3156 #endif /* HAVE_GETTEXT */
3159 * This is really _re_formatting, since the normal format code has
3160 * generated a beautiful string into xo_data, starting at
3161 * start_offset. We need to see if it's plural, which means
3162 * comma-separated options, or singular. Then we make the appropriate
3163 * call to d[n]gettext() to get the locale-based version. Note that
3164 * both input and output of gettext() this should be UTF-8.
3167 xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
3168 ssize_t start_offset, ssize_t cols, int need_enc)
3170 xo_buffer_t *xbp = &xop->xo_data;
3172 if (!xo_buf_has_room(xbp, 1))
3175 xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
3177 char *cp = xbp->xb_bufp + start_offset;
3178 ssize_t len = xbp->xb_curp - cp;
3179 const char *newstr = NULL;
3182 * The plural flag asks us to look backwards at the last numeric
3183 * value rendered and disect the string into two pieces.
3185 if (flags & XFF_GT_PLURAL) {
3186 int n = xo_buf_find_last_number(xbp, start_offset);
3187 char *two = memchr(cp, (int) ',', len);
3189 xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
3194 xo_failure(xop, "nothing before comma in plural gettext "
3199 if (two == xbp->xb_curp) {
3200 xo_failure(xop, "nothing after comma in plural gettext "
3206 if (flags & XFF_GT_FIELD) {
3207 newstr = xo_dngettext(xop, cp, two, n);
3209 /* Don't do a gettext() look up, just get the plural form */
3210 newstr = (n == 1) ? cp : two;
3214 * If we returned the first string, optimize a bit by
3215 * backing up over comma
3218 xbp->xb_curp = two - 1; /* One for comma */
3220 * If the caller wanted UTF8, we're done; nothing changed,
3221 * but we need to count the columns used.
3223 if (need_enc == XF_ENC_UTF8)
3224 return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3228 /* The simple case (singular) */
3229 newstr = xo_dgettext(xop, cp);
3232 /* If the caller wanted UTF8, we're done; nothing changed */
3233 if (need_enc == XF_ENC_UTF8)
3239 * Since the new string string might be in gettext's buffer or
3240 * in the buffer (as the plural form), we make a copy.
3242 ssize_t nlen = strlen(newstr);
3243 char *newcopy = alloca(nlen + 1);
3244 memcpy(newcopy, newstr, nlen + 1);
3246 xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3247 return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3248 need_enc, XF_ENC_UTF8);
3252 xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len,
3253 xo_xff_flags_t flags)
3256 int need_enc = xo_needed_encoding(xop);
3257 ssize_t start_offset = xo_buf_offset(&xop->xo_data);
3259 cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
3261 need_enc, XF_ENC_UTF8);
3262 if (flags & XFF_GT_FLAGS)
3263 cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
3265 if (XOF_ISSET(xop, XOF_COLUMNS))
3266 xop->xo_columns += cols;
3267 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3268 xop->xo_anchor_columns += cols;
3272 * Bump one of the 'width' values in a format strings (e.g. "%40.50.60s").
3273 * @param xfp Formatting instructions
3274 * @param digit Single digit (0-9) of input
3277 xo_bump_width (xo_format_t *xfp, int digit)
3279 int *ip = &xfp->xf_width[xfp->xf_dots];
3281 *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
3285 xo_trim_ws (xo_buffer_t *xbp, ssize_t len)
3290 /* First trim leading space */
3291 for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
3299 memmove(sp, cp, len);
3302 /* Then trim off the end */
3303 for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
3318 * Interface to format a single field. The arguments are in xo_vap,
3319 * and the format is in 'fmt'. If 'xbp' is null, we use xop->xo_data;
3320 * this is the most common case.
3323 xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
3324 const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3327 const char *cp, *ep, *sp, *xp = NULL;
3329 int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
3330 unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0;
3331 int need_enc = xo_needed_encoding(xop);
3332 int real_need_enc = need_enc;
3333 ssize_t old_cols = xop->xo_columns;
3335 /* The gettext interface is UTF-8, so we'll need that for now */
3336 if (flags & XFF_GT_FIELD)
3337 need_enc = XF_ENC_UTF8;
3340 xbp = &xop->xo_data;
3342 ssize_t start_offset = xo_buf_offset(xbp);
3344 for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3346 * Since we're starting a new field, save the starting offset.
3347 * We'll need this later for field-related operations.
3355 if (*cp == '\\' && cp[1] != '\0')
3359 } if (cp + 1 < ep && cp[1] == '%') {
3366 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3367 NULL, xp, cp - xp, -1,
3368 need_enc, XF_ENC_UTF8);
3369 if (XOF_ISSET(xop, XOF_COLUMNS))
3370 xop->xo_columns += cols;
3371 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3372 xop->xo_anchor_columns += cols;
3378 bzero(&xf, sizeof(xf));
3379 xf.xf_leading_zero = -1;
3380 xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
3383 * "%@" starts an XO-specific set of flags:
3384 * @X@ - XML-only field; ignored if style isn't XML
3387 for (cp += 2; cp < ep; cp++) {
3393 * '*' means there's a "%*.*s" value in vap that
3396 if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
3397 va_arg(xop->xo_vap, int);
3402 /* Hidden fields are only visible to JSON and XML */
3403 if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
3404 if (style != XO_STYLE_XML
3405 && !xo_style_is_encoding(xop))
3407 } else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
3408 if (style != XO_STYLE_TEXT
3409 && xo_style(xop) != XO_STYLE_HTML)
3417 * Looking at one piece of a format; find the end and
3418 * call snprintf. Then advance xo_vap on our own.
3420 * Note that 'n', 'v', and '$' are not supported.
3422 sp = cp; /* Save start pointer */
3423 for (cp += 1; cp < ep; cp++) {
3426 else if (*cp == 'h')
3428 else if (*cp == 'j')
3430 else if (*cp == 't')
3432 else if (*cp == 'z')
3434 else if (*cp == 'q')
3436 else if (*cp == '.') {
3437 if (++xf.xf_dots >= XF_WIDTH_NUM) {
3438 xo_failure(xop, "Too many dots in format: '%s'", fmt);
3441 } else if (*cp == '-')
3442 xf.xf_seen_minus = 1;
3443 else if (isdigit((int) *cp)) {
3444 if (xf.xf_leading_zero < 0)
3445 xf.xf_leading_zero = (*cp == '0');
3446 xo_bump_width(&xf, *cp - '0');
3447 } else if (*cp == '*') {
3449 xf.xf_star[xf.xf_dots] = 1;
3450 } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
3452 else if (*cp == 'n' || *cp == 'v') {
3453 xo_failure(xop, "unsupported format: '%s'", fmt);
3459 xo_failure(xop, "field format missing format character: %s",
3464 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3465 if (*cp == 's' || *cp == 'S') {
3466 /* Handle "%*.*.*s" */
3468 for (s = 0; s < XF_WIDTH_NUM; s++) {
3469 if (xf.xf_star[s]) {
3470 xf.xf_width[s] = va_arg(xop->xo_vap, int);
3472 /* Normalize a negative width value */
3473 if (xf.xf_width[s] < 0) {
3475 xf.xf_width[0] = -xf.xf_width[0];
3476 xf.xf_seen_minus = 1;
3478 xf.xf_width[s] = -1; /* Ignore negative values */
3485 /* If no max is given, it defaults to size */
3486 if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
3487 xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
3489 if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
3493 xo_buffer_t *fbp = &xop->xo_fmt;
3494 ssize_t len = cp - sp + 1;
3495 if (!xo_buf_has_room(fbp, len + 1))
3498 char *newfmt = fbp->xb_curp;
3499 memcpy(newfmt, sp, len);
3500 newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */
3504 * Bad news: our strings are UTF-8, but the stock printf
3505 * functions won't handle field widths for wide characters
3506 * correctly. So we have to handle this ourselves.
3508 if (xop->xo_formatter == NULL
3509 && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3510 || xf.xf_fc == 'm')) {
3512 xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3513 : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3514 : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3516 rc = xo_format_string(xop, xbp, flags, &xf);
3518 if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
3519 rc = xo_trim_ws(xbp, rc);
3522 ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt,
3527 * For XML and HTML, we need "&<>" processing; for JSON,
3528 * it's quotes. Text gets nothing.
3532 if (flags & XFF_TRIM_WS)
3533 columns = rc = xo_trim_ws(xbp, rc);
3536 rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
3540 if (flags & XFF_TRIM_WS)
3541 columns = rc = xo_trim_ws(xbp, rc);
3542 rc = xo_escape_json(xbp, rc, 0);
3545 case XO_STYLE_SDPARAMS:
3546 if (flags & XFF_TRIM_WS)
3547 columns = rc = xo_trim_ws(xbp, rc);
3548 rc = xo_escape_sdparams(xbp, rc, 0);
3551 case XO_STYLE_ENCODER:
3552 if (flags & XFF_TRIM_WS)
3553 columns = rc = xo_trim_ws(xbp, rc);
3558 * We can assume all the non-%s data we've
3559 * added is ASCII, so the columns and bytes are the
3560 * same. xo_format_string handles all the fancy
3561 * string conversions and updates xo_anchor_columns
3564 if (XOF_ISSET(xop, XOF_COLUMNS))
3565 xop->xo_columns += columns;
3566 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3567 xop->xo_anchor_columns += columns;
3576 * Now for the tricky part: we need to move the argument pointer
3577 * along by the amount needed.
3579 if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3581 if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
3583 * The 'S' and 's' formats are normally handled in
3584 * xo_format_string, but if we skipped it, then we
3588 va_arg(xop->xo_vap, char *);
3590 } else if (xf.xf_fc == 'm') {
3591 /* Nothing on the stack for "%m" */
3595 for (s = 0; s < XF_WIDTH_NUM; s++) {
3597 va_arg(xop->xo_vap, int);
3600 if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
3601 if (xf.xf_hflag > 1) {
3602 va_arg(xop->xo_vap, int);
3604 } else if (xf.xf_hflag > 0) {
3605 va_arg(xop->xo_vap, int);
3607 } else if (xf.xf_lflag > 1) {
3608 va_arg(xop->xo_vap, unsigned long long);
3610 } else if (xf.xf_lflag > 0) {
3611 va_arg(xop->xo_vap, unsigned long);
3613 } else if (xf.xf_jflag > 0) {
3614 va_arg(xop->xo_vap, intmax_t);
3616 } else if (xf.xf_tflag > 0) {
3617 va_arg(xop->xo_vap, ptrdiff_t);
3619 } else if (xf.xf_zflag > 0) {
3620 va_arg(xop->xo_vap, size_t);
3622 } else if (xf.xf_qflag > 0) {
3623 va_arg(xop->xo_vap, quad_t);
3626 va_arg(xop->xo_vap, int);
3628 } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
3630 va_arg(xop->xo_vap, long double);
3632 va_arg(xop->xo_vap, double);
3634 else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
3635 va_arg(xop->xo_vap, wint_t);
3637 else if (xf.xf_fc == 'c')
3638 va_arg(xop->xo_vap, int);
3640 else if (xf.xf_fc == 'p')
3641 va_arg(xop->xo_vap, void *);
3648 cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3649 NULL, xp, cp - xp, -1,
3650 need_enc, XF_ENC_UTF8);
3652 if (XOF_ISSET(xop, XOF_COLUMNS))
3653 xop->xo_columns += cols;
3654 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3655 xop->xo_anchor_columns += cols;
3661 if (flags & XFF_GT_FLAGS) {
3663 * Handle gettext()ing the field by looking up the value
3664 * and then copying it in, while converting to locale, if
3667 ssize_t new_cols = xo_format_gettext(xop, flags, start_offset,
3668 old_cols, real_need_enc);
3670 if (XOF_ISSET(xop, XOF_COLUMNS))
3671 xop->xo_columns += new_cols - old_cols;
3672 if (XOIF_ISSET(xop, XOIF_ANCHOR))
3673 xop->xo_anchor_columns += new_cols - old_cols;
3680 * Remove any numeric precision/width format from the format string by
3681 * inserting the "%" after the [0-9]+, returning the substring.
3684 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
3686 char *cp = encoding;
3688 if (cp[0] != '%' || !isdigit((int) cp[1]))
3691 for (cp += 2; *cp; cp++) {
3692 if (!isdigit((int) *cp))
3696 *--cp = '%'; /* Back off and insert the '%' */
3702 xo_color_append_html (xo_handle_t *xop)
3705 * If the color buffer has content, we add it now. It's already
3706 * prebuilt and ready, since we want to add it to every <div>.
3708 if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3709 xo_buffer_t *xbp = &xop->xo_color_buf;
3711 xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3716 * A wrapper for humanize_number that autoscales, since the
3717 * HN_AUTOSCALE flag scales as needed based on the size of
3718 * the output buffer, not the size of the value. I also
3719 * wish HN_DECIMAL was more imperative, without the <10
3720 * test. But the boat only goes where we want when we hold
3721 * the rudder, so xo_humanize fixes part of the problem.
3724 xo_humanize (char *buf, ssize_t len, uint64_t value, int flags)
3729 uint64_t left = value;
3731 if (flags & HN_DIVISOR_1000) {
3732 for ( ; left; scale++)
3735 for ( ; left; scale++)
3741 return xo_humanize_number(buf, len, value, "", scale, flags);
3745 * This is an area where we can save information from the handle for
3746 * later restoration. We need to know what data was rendered to know
3747 * what needs cleaned up.
3749 typedef struct xo_humanize_save_s {
3750 ssize_t xhs_offset; /* Saved xo_offset */
3751 ssize_t xhs_columns; /* Saved xo_columns */
3752 ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */
3753 } xo_humanize_save_t;
3756 * Format a "humanized" value for a numeric, meaning something nice
3757 * like "44M" instead of "44470272". We autoscale, choosing the
3758 * most appropriate value for K/M/G/T/P/E based on the value given.
3761 xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3762 xo_humanize_save_t *savep, xo_xff_flags_t flags)
3764 if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3767 ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
3768 if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3772 * We have a string that's allegedly a number. We want to
3773 * humanize it, which means turning it back into a number
3774 * and calling xo_humanize_number on it.
3779 xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3781 value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3782 if (!(value == ULLONG_MAX && errno == ERANGE)
3783 && (ep != xbp->xb_bufp + savep->xhs_offset)) {
3785 * There are few values where humanize_number needs
3786 * more bytes than the original value. I've used
3787 * 10 as a rectal number to cover those scenarios.
3789 if (xo_buf_has_room(xbp, 10)) {
3790 xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3793 ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3794 int hn_flags = HN_NOSPACE; /* On by default */
3796 if (flags & XFF_HN_SPACE)
3797 hn_flags &= ~HN_NOSPACE;
3799 if (flags & XFF_HN_DECIMAL)
3800 hn_flags |= HN_DECIMAL;
3802 if (flags & XFF_HN_1000)
3803 hn_flags |= HN_DIVISOR_1000;
3805 rc = xo_humanize(xbp->xb_curp, left, value, hn_flags);
3808 xop->xo_columns = savep->xhs_columns + rc;
3809 xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3816 * Convenience function that either append a fixed value (if one is
3817 * given) or formats a field using a format string. If it's
3818 * encode_only, then we can't skip formatting the field, since it may
3819 * be pulling arguments off the stack.
3822 xo_simple_field (xo_handle_t *xop, unsigned encode_only,
3823 const char *value, ssize_t vlen,
3824 const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3827 flags |= XFF_NO_OUTPUT;
3830 xo_do_format_field(xop, NULL, fmt, flen, flags);
3831 else if (!encode_only)
3832 xo_data_append_content(xop, value, vlen, flags);
3836 * Html mode: append a <div> to the output buffer contain a field
3837 * along with all the supporting information indicated by the flags.
3840 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
3841 const char *name, ssize_t nlen,
3842 const char *value, ssize_t vlen,
3843 const char *fmt, ssize_t flen,
3844 const char *encoding, ssize_t elen)
3846 static char div_start[] = "<div class=\"";
3847 static char div_tag[] = "\" data-tag=\"";
3848 static char div_xpath[] = "\" data-xpath=\"";
3849 static char div_key[] = "\" data-key=\"key";
3850 static char div_end[] = "\">";
3851 static char div_close[] = "</div>";
3853 /* The encoding format defaults to the normal format */
3854 if (encoding == NULL && fmt != NULL) {
3855 char *enc = alloca(flen + 1);
3856 memcpy(enc, fmt, flen);
3858 encoding = xo_fix_encoding(xop, enc);
3859 elen = strlen(encoding);
3863 * To build our XPath predicate, we need to save the va_list before
3864 * we format our data, and then restore it before we format the
3866 * Display-only keys implies that we've got an encode-only key
3867 * elsewhere, so we don't use them from making predicates.
3869 int need_predidate =
3870 (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
3871 && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0;
3873 if (need_predidate) {
3876 va_copy(va_local, xop->xo_vap);
3877 if (xop->xo_checkpointer)
3878 xop->xo_checkpointer(xop, xop->xo_vap, 0);
3881 * Build an XPath predicate expression to match this key.
3882 * We use the format buffer.
3884 xo_buffer_t *pbp = &xop->xo_predicate;
3885 pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
3887 xo_buf_append(pbp, "[", 1);
3888 xo_buf_escape(xop, pbp, name, nlen, 0);
3889 if (XOF_ISSET(xop, XOF_PRETTY))
3890 xo_buf_append(pbp, " = '", 4);
3892 xo_buf_append(pbp, "='", 2);
3894 xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3895 pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3896 xo_do_format_field(xop, pbp, encoding, elen, pflags);
3898 xo_buf_append(pbp, "']", 2);
3900 /* Now we record this predicate expression in the stack */
3901 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3902 ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
3903 ssize_t dlen = pbp->xb_curp - pbp->xb_bufp;
3905 char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
3907 memcpy(cp + olen, pbp->xb_bufp, dlen);
3908 cp[olen + dlen] = '\0';
3912 /* Now we reset the xo_vap as if we were never here */
3913 va_end(xop->xo_vap);
3914 va_copy(xop->xo_vap, va_local);
3916 if (xop->xo_checkpointer)
3917 xop->xo_checkpointer(xop, xop->xo_vap, 1);
3920 if (flags & XFF_ENCODE_ONLY) {
3922 * Even if this is encode-only, we need to go through the
3923 * work of formatting it to make sure the args are cleared
3924 * from xo_vap. This is not true when vlen is zero, since
3925 * that means our "value" isn't on the stack.
3927 xo_simple_field(xop, TRUE, NULL, 0, encoding, elen, flags);
3931 xo_line_ensure_open(xop, 0);
3933 if (XOF_ISSET(xop, XOF_PRETTY))
3934 xo_buf_indent(xop, xop->xo_indent_by);
3936 xo_data_append(xop, div_start, sizeof(div_start) - 1);
3937 xo_data_append(xop, class, strlen(class));
3940 * If the color buffer has content, we add it now. It's already
3941 * prebuilt and ready, since we want to add it to every <div>.
3943 if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3944 xo_buffer_t *xbp = &xop->xo_color_buf;
3946 xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3950 xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
3951 xo_data_escape(xop, name, nlen);
3954 * Save the offset at which we'd place units. See xo_format_units.
3956 if (XOF_ISSET(xop, XOF_UNITS)) {
3957 XOIF_SET(xop, XOIF_UNITS_PENDING);
3959 * Note: We need the '+1' here because we know we've not
3960 * added the closing quote. We add one, knowing the quote
3961 * will be added shortly.
3963 xop->xo_units_offset =
3964 xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
3967 if (XOF_ISSET(xop, XOF_XPATH)) {
3971 xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
3972 if (xop->xo_leading_xpath)
3973 xo_data_append(xop, xop->xo_leading_xpath,
3974 strlen(xop->xo_leading_xpath));
3976 for (i = 0; i <= xop->xo_depth; i++) {
3977 xsp = &xop->xo_stack[i];
3978 if (xsp->xs_name == NULL)
3982 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3983 * are directly under XSS_OPEN_INSTANCE frames so we
3984 * don't need to put these in our XPath expressions.
3986 if (xsp->xs_state == XSS_OPEN_LIST
3987 || xsp->xs_state == XSS_OPEN_LEAF_LIST)
3990 xo_data_append(xop, "/", 1);
3991 xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
3993 /* Don't show keys for the key field */
3994 if (i != xop->xo_depth || !(flags & XFF_KEY))
3995 xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
3999 xo_data_append(xop, "/", 1);
4000 xo_data_escape(xop, name, nlen);
4003 if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
4004 static char in_type[] = "\" data-type=\"";
4005 static char in_help[] = "\" data-help=\"";
4007 xo_info_t *xip = xo_info_find(xop, name, nlen);
4010 xo_data_append(xop, in_type, sizeof(in_type) - 1);
4011 xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
4014 xo_data_append(xop, in_help, sizeof(in_help) - 1);
4015 xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
4020 if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
4021 xo_data_append(xop, div_key, sizeof(div_key) - 1);
4024 xo_buffer_t *xbp = &xop->xo_data;
4025 ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp;
4027 xo_data_append(xop, div_end, sizeof(div_end) - 1);
4029 xo_humanize_save_t save; /* Save values for humanizing logic */
4031 save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4032 save.xhs_columns = xop->xo_columns;
4033 save.xhs_anchor_columns = xop->xo_anchor_columns;
4035 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4037 if (flags & XFF_HUMANIZE) {
4039 * Unlike text style, we want to retain the original value and
4040 * stuff it into the "data-number" attribute.
4042 static const char div_number[] = "\" data-number=\"";
4043 ssize_t div_len = sizeof(div_number) - 1;
4045 ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
4046 ssize_t olen = end_offset - save.xhs_offset;
4048 char *cp = alloca(olen + 1);
4049 memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
4052 xo_format_humanize(xop, xbp, &save, flags);
4054 if (xo_buf_has_room(xbp, div_len + olen)) {
4055 ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp;
4058 /* Move the humanized string off to the left */
4059 memmove(xbp->xb_bufp + base_offset + div_len + olen,
4060 xbp->xb_bufp + base_offset, new_offset - base_offset);
4062 /* Copy the data_number attribute name */
4063 memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
4065 /* Copy the original long value */
4066 memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
4067 xbp->xb_curp += div_len + olen;
4071 xo_data_append(xop, div_close, sizeof(div_close) - 1);
4073 if (XOF_ISSET(xop, XOF_PRETTY))
4074 xo_data_append(xop, "\n", 1);
4078 xo_format_text (xo_handle_t *xop, const char *str, ssize_t len)
4080 switch (xo_style(xop)) {
4082 xo_buf_append_locale(xop, &xop->xo_data, str, len);
4086 xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0, NULL, 0);
4092 xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
4093 const char *value, ssize_t vlen)
4095 const char *fmt = xfip->xfi_format;
4096 ssize_t flen = xfip->xfi_flen;
4097 xo_xff_flags_t flags = xfip->xfi_flags;
4099 static char div_open[] = "<div class=\"title";
4100 static char div_middle[] = "\">";
4101 static char div_close[] = "</div>";
4108 switch (xo_style(xop)) {
4111 case XO_STYLE_SDPARAMS:
4112 case XO_STYLE_ENCODER:
4114 * Even though we don't care about text, we need to do
4115 * enough parsing work to skip over the right bits of xo_vap.
4117 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4121 xo_buffer_t *xbp = &xop->xo_data;
4122 ssize_t start = xbp->xb_curp - xbp->xb_bufp;
4123 ssize_t left = xbp->xb_size - start;
4126 if (xo_style(xop) == XO_STYLE_HTML) {
4127 xo_line_ensure_open(xop, 0);
4128 if (XOF_ISSET(xop, XOF_PRETTY))
4129 xo_buf_indent(xop, xop->xo_indent_by);
4130 xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
4131 xo_color_append_html(xop);
4132 xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
4135 start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
4137 char *newfmt = alloca(flen + 1);
4138 memcpy(newfmt, fmt, flen);
4139 newfmt[flen] = '\0';
4141 /* If len is non-zero, the format string apply to the name */
4142 char *newstr = alloca(vlen + 1);
4143 memcpy(newstr, value, vlen);
4144 newstr[vlen] = '\0';
4146 if (newstr[vlen - 1] == 's') {
4149 rc = snprintf(NULL, 0, newfmt, newstr);
4152 * We have to do this the hard way, since we might need
4155 bp = alloca(rc + 1);
4156 rc = snprintf(bp, rc + 1, newfmt, newstr);
4158 xo_data_append_content(xop, bp, rc, flags);
4163 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4165 if (!xo_buf_has_room(xbp, rc))
4167 left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
4168 rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4172 if (XOF_ISSET(xop, XOF_COLUMNS))
4173 xop->xo_columns += rc;
4174 if (XOIF_ISSET(xop, XOIF_ANCHOR))
4175 xop->xo_anchor_columns += rc;
4180 xo_do_format_field(xop, NULL, fmt, flen, flags);
4182 /* xo_do_format_field moved curp, so we need to reset it */
4183 rc = xbp->xb_curp - (xbp->xb_bufp + start);
4184 xbp->xb_curp = xbp->xb_bufp + start;
4187 /* If we're styling HTML, then we need to escape it */
4188 if (xo_style(xop) == XO_STYLE_HTML) {
4189 rc = xo_escape_xml(xbp, rc, 0);
4196 if (xo_style(xop) == XO_STYLE_HTML) {
4197 xo_data_append(xop, div_close, sizeof(div_close) - 1);
4198 if (XOF_ISSET(xop, XOF_PRETTY))
4199 xo_data_append(xop, "\n", 1);
4204 * strspn() with a string length
4207 xo_strnspn (const char *str, size_t len, const char *accept)
4210 const char *cp, *ep;
4212 for (i = 0, cp = str, ep = str + len; cp < ep && *cp != '\0'; i++, cp++) {
4213 if (strchr(accept, *cp) == NULL)
4221 * Decide if a format string should be considered "numeric",
4222 * in the sense that the number does not need to be quoted.
4223 * This means that it consists only of a single numeric field
4224 * with nothing exotic or "interesting". This means that
4225 * static values are never considered numeric.
4228 xo_format_is_numeric (const char *fmt, ssize_t flen)
4230 if (flen <= 0 || *fmt++ != '%') /* Must start with '%' */
4234 /* Handle leading flags; don't want "#" since JSON can't handle hex */
4235 ssize_t spn = xo_strnspn(fmt, flen, "0123456789.*+ -");
4239 fmt += spn; /* Move along the input string */
4242 /* Handle the length modifiers */
4243 spn = xo_strnspn(fmt, flen, "hljtqz");
4247 fmt += spn; /* Move along the input string */
4250 if (flen != 1) /* Should only be one character left */
4253 return (strchr("diouDOUeEfFgG", *fmt) == NULL) ? FALSE : TRUE;
4257 * Update the stack flags using the object flags, allowing callers
4258 * to monkey with the stack flags without even knowing they exist.
4261 xo_stack_set_flags (xo_handle_t *xop)
4263 if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
4264 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4266 xsp->xs_flags |= XSF_NOT_FIRST;
4267 XOF_CLEAR(xop, XOF_NOT_FIRST);
4272 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
4274 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
4275 xo_data_append(xop, ",", 1);
4276 if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
4277 xo_data_append(xop, "\n", 1);
4279 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4283 /* Useful debugging function */
4285 xo_arg (xo_handle_t *xop);
4287 xo_arg (xo_handle_t *xop)
4289 xop = xo_default(xop);
4290 fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
4295 xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
4296 const char *value, ssize_t vlen,
4297 const char *fmt, ssize_t flen,
4298 const char *encoding, ssize_t elen, xo_xff_flags_t flags)
4300 int pretty = XOF_ISSET(xop, XOF_PRETTY);
4304 * Before we emit a value, we need to know that the frame is ready.
4306 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4308 if (flags & XFF_LEAF_LIST) {
4310 * Check if we've already started to emit normal leafs
4311 * or if we're not in a leaf list.
4313 if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4314 || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4315 char nbuf[nlen + 1];
4316 memcpy(nbuf, name, nlen);
4319 ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4321 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4323 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4326 xsp = &xop->xo_stack[xop->xo_depth];
4328 name = xsp->xs_name;
4329 nlen = strlen(name);
4332 } else if (flags & XFF_KEY) {
4333 /* Emitting a 'k' (key) field */
4334 if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4335 xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4338 } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4339 char nbuf[nlen + 1];
4340 memcpy(nbuf, name, nlen);
4343 ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4345 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4347 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4349 xsp = &xop->xo_stack[xop->xo_depth];
4350 xsp->xs_flags |= XSF_EMIT_KEY;
4354 /* Emitting a normal value field */
4355 if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4356 || !(xsp->xs_flags & XSF_EMIT)) {
4357 char nbuf[nlen + 1];
4358 memcpy(nbuf, name, nlen);
4361 ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4363 flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4365 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4367 xsp = &xop->xo_stack[xop->xo_depth];
4368 xsp->xs_flags |= XSF_EMIT;
4372 xo_buffer_t *xbp = &xop->xo_data;
4373 xo_humanize_save_t save; /* Save values for humanizing logic */
4375 const char *leader = xo_xml_leader_len(xop, name, nlen);
4377 switch (xo_style(xop)) {
4379 if (flags & XFF_ENCODE_ONLY)
4380 flags |= XFF_NO_OUTPUT;
4382 save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4383 save.xhs_columns = xop->xo_columns;
4384 save.xhs_anchor_columns = xop->xo_anchor_columns;
4386 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4388 if (flags & XFF_HUMANIZE)
4389 xo_format_humanize(xop, xbp, &save, flags);
4393 if (flags & XFF_ENCODE_ONLY)
4394 flags |= XFF_NO_OUTPUT;
4396 xo_buf_append_div(xop, "data", flags, name, nlen, value, vlen,
4397 fmt, flen, encoding, elen);
4402 * Even though we're not making output, we still need to
4403 * let the formatting code handle the va_arg popping.
4405 if (flags & XFF_DISPLAY_ONLY) {
4406 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4414 char *enc = alloca(flen + 1);
4415 memcpy(enc, fmt, flen);
4417 fmt = xo_fix_encoding(xop, enc);
4422 static char missing[] = "missing-field-name";
4423 xo_failure(xop, "missing field name: %s", fmt);
4425 nlen = sizeof(missing) - 1;
4429 xo_buf_indent(xop, -1);
4430 xo_data_append(xop, "<", 1);
4432 xo_data_append(xop, leader, 1);
4433 xo_data_escape(xop, name, nlen);
4435 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4436 xo_data_append(xop, xop->xo_attrs.xb_bufp,
4437 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4438 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4442 * We indicate 'key' fields using the 'key' attribute. While
4443 * this is really committing the crime of mixing meta-data with
4444 * data, it's often useful. Especially when format meta-data is
4445 * difficult to come by.
4447 if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
4448 static char attr[] = " key=\"key\"";
4449 xo_data_append(xop, attr, sizeof(attr) - 1);
4453 * Save the offset at which we'd place units. See xo_format_units.
4455 if (XOF_ISSET(xop, XOF_UNITS)) {
4456 XOIF_SET(xop, XOIF_UNITS_PENDING);
4457 xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
4460 xo_data_append(xop, ">", 1);
4462 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4464 xo_data_append(xop, "</", 2);
4466 xo_data_append(xop, leader, 1);
4467 xo_data_escape(xop, name, nlen);
4468 xo_data_append(xop, ">", 1);
4470 xo_data_append(xop, "\n", 1);
4474 if (flags & XFF_DISPLAY_ONLY) {
4475 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4483 char *enc = alloca(flen + 1);
4484 memcpy(enc, fmt, flen);
4486 fmt = xo_fix_encoding(xop, enc);
4490 xo_stack_set_flags(xop);
4492 int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4495 xo_format_prep(xop, flags);
4497 if (flags & XFF_QUOTE)
4499 else if (flags & XFF_NOQUOTE)
4503 else if (flen == 0) {
4505 fmt = "true"; /* JSON encodes empty tags as a boolean true */
4507 } else if (xo_format_is_numeric(fmt, flen))
4513 static char missing[] = "missing-field-name";
4514 xo_failure(xop, "missing field name: %s", fmt);
4516 nlen = sizeof(missing) - 1;
4519 if (flags & XFF_LEAF_LIST) {
4520 if (!first && pretty)
4521 xo_data_append(xop, "\n", 1);
4523 xo_buf_indent(xop, -1);
4526 xo_buf_indent(xop, -1);
4527 xo_data_append(xop, "\"", 1);
4529 xbp = &xop->xo_data;
4530 ssize_t off = xbp->xb_curp - xbp->xb_bufp;
4532 xo_data_escape(xop, name, nlen);
4534 if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
4535 ssize_t coff = xbp->xb_curp - xbp->xb_bufp;
4536 for ( ; off < coff; off++)
4537 if (xbp->xb_bufp[off] == '-')
4538 xbp->xb_bufp[off] = '_';
4540 xo_data_append(xop, "\":", 2);
4542 xo_data_append(xop, " ", 1);
4546 xo_data_append(xop, "\"", 1);
4548 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4551 xo_data_append(xop, "\"", 1);
4554 case XO_STYLE_SDPARAMS:
4555 if (flags & XFF_DISPLAY_ONLY) {
4556 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4564 char *enc = alloca(flen + 1);
4565 memcpy(enc, fmt, flen);
4567 fmt = xo_fix_encoding(xop, enc);
4572 static char missing[] = "missing-field-name";
4573 xo_failure(xop, "missing field name: %s", fmt);
4575 nlen = sizeof(missing) - 1;
4578 xo_data_escape(xop, name, nlen);
4579 xo_data_append(xop, "=\"", 2);
4581 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4583 xo_data_append(xop, "\" ", 2);
4586 case XO_STYLE_ENCODER:
4587 if (flags & XFF_DISPLAY_ONLY) {
4588 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4592 if (flags & XFF_QUOTE)
4594 else if (flags & XFF_NOQUOTE)
4596 else if (flen == 0) {
4598 fmt = "true"; /* JSON encodes empty tags as a boolean true */
4600 } else if (strchr("diouxXDOUeEfFgGaAcCp", fmt[flen - 1]) == NULL)
4609 char *enc = alloca(flen + 1);
4610 memcpy(enc, fmt, flen);
4612 fmt = xo_fix_encoding(xop, enc);
4617 static char missing[] = "missing-field-name";
4618 xo_failure(xop, "missing field name: %s", fmt);
4620 nlen = sizeof(missing) - 1;
4623 ssize_t name_offset = xo_buf_offset(&xop->xo_data);
4624 xo_data_append(xop, name, nlen);
4625 xo_data_append(xop, "", 1);
4627 ssize_t value_offset = xo_buf_offset(&xop->xo_data);
4629 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4631 xo_data_append(xop, "", 1);
4633 xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4634 xo_buf_data(&xop->xo_data, name_offset),
4635 xo_buf_data(&xop->xo_data, value_offset), flags);
4636 xo_buf_reset(&xop->xo_data);
4642 xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
4643 const char *str, ssize_t len)
4645 const char *fmt = xfip->xfi_format;
4646 ssize_t flen = xfip->xfi_flen;
4648 /* Start by discarding previous domain */
4649 if (xop->xo_gt_domain) {
4650 xo_free(xop->xo_gt_domain);
4651 xop->xo_gt_domain = NULL;
4654 /* An empty {G:} means no domainname */
4655 if (len == 0 && flen == 0)
4658 ssize_t start_offset = -1;
4659 if (len == 0 && flen != 0) {
4660 /* Need to do format the data to get the domainname from args */
4661 start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4662 xo_do_format_field(xop, NULL, fmt, flen, 0);
4664 ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4665 len = end_offset - start_offset;
4666 str = xop->xo_data.xb_bufp + start_offset;
4669 xop->xo_gt_domain = xo_strndup(str, len);
4671 /* Reset the current buffer point to avoid emitting the name as output */
4672 if (start_offset >= 0)
4673 xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4677 xo_format_content (xo_handle_t *xop, const char *class_name,
4678 const char *tag_name,
4679 const char *value, ssize_t vlen,
4680 const char *fmt, ssize_t flen,
4681 xo_xff_flags_t flags)
4683 switch (xo_style(xop)) {
4685 xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4689 xo_buf_append_div(xop, class_name, flags, NULL, 0,
4690 value, vlen, fmt, flen, NULL, 0);
4695 case XO_STYLE_SDPARAMS:
4697 xo_open_container_h(xop, tag_name);
4698 xo_format_value(xop, "message", 7, value, vlen,
4699 fmt, flen, NULL, 0, flags);
4700 xo_close_container_h(xop, tag_name);
4704 * Even though we don't care about labels, we need to do
4705 * enough parsing work to skip over the right bits of xo_vap.
4707 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4711 case XO_STYLE_ENCODER:
4712 xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4717 static const char *xo_color_names[] = {
4718 "default", /* XO_COL_DEFAULT */
4719 "black", /* XO_COL_BLACK */
4720 "red", /* XO_CLOR_RED */
4721 "green", /* XO_COL_GREEN */
4722 "yellow", /* XO_COL_YELLOW */
4723 "blue", /* XO_COL_BLUE */
4724 "magenta", /* XO_COL_MAGENTA */
4725 "cyan", /* XO_COL_CYAN */
4726 "white", /* XO_COL_WHITE */
4731 xo_color_find (const char *str)
4735 for (i = 0; xo_color_names[i]; i++) {
4736 if (strcmp(xo_color_names[i], str) == 0)
4743 static const char *xo_effect_names[] = {
4744 "reset", /* XO_EFF_RESET */
4745 "normal", /* XO_EFF_NORMAL */
4746 "bold", /* XO_EFF_BOLD */
4747 "underline", /* XO_EFF_UNDERLINE */
4748 "inverse", /* XO_EFF_INVERSE */
4752 static const char *xo_effect_on_codes[] = {
4753 "0", /* XO_EFF_RESET */
4754 "0", /* XO_EFF_NORMAL */
4755 "1", /* XO_EFF_BOLD */
4756 "4", /* XO_EFF_UNDERLINE */
4757 "7", /* XO_EFF_INVERSE */
4763 * See comment below re: joy of terminal standards. These can
4764 * be use by just adding:
4765 * + if (newp->xoc_effects & bit)
4766 * code = xo_effect_on_codes[i];
4768 * + code = xo_effect_off_codes[i];
4769 * in xo_color_handle_text.
4771 static const char *xo_effect_off_codes[] = {
4772 "0", /* XO_EFF_RESET */
4773 "0", /* XO_EFF_NORMAL */
4774 "21", /* XO_EFF_BOLD */
4775 "24", /* XO_EFF_UNDERLINE */
4776 "27", /* XO_EFF_INVERSE */
4782 xo_effect_find (const char *str)
4786 for (i = 0; xo_effect_names[i]; i++) {
4787 if (strcmp(xo_effect_names[i], str) == 0)
4795 xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4800 char *cp, *ep, *np, *xp;
4801 ssize_t len = strlen(str);
4805 * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4807 for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4808 /* Trim leading whitespace */
4809 while (isspace((int) *cp))
4812 np = strchr(cp, ',');
4816 /* Trim trailing whitespace */
4817 xp = cp + strlen(cp) - 1;
4818 while (isspace(*xp) && xp > cp)
4821 if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4822 rc = xo_color_find(cp + 3);
4826 xocp->xoc_col_fg = rc;
4828 } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4829 rc = xo_color_find(cp + 3);
4832 xocp->xoc_col_bg = rc;
4834 } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4835 rc = xo_effect_find(cp + 3);
4838 xocp->xoc_effects &= ~(1 << rc);
4841 rc = xo_effect_find(cp);
4844 xocp->xoc_effects |= 1 << rc;
4848 xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4849 /* Note: not "|=" since we want to wipe out the old value */
4850 xocp->xoc_effects = XO_EFF_RESET;
4854 xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4855 | XO_EFF_INVERSE | XO_EFF_NORMAL);
4862 if (XOF_ISSET(xop, XOF_WARN))
4863 xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4868 xo_colors_enabled (xo_handle_t *xop UNUSED)
4870 #ifdef LIBXO_TEXT_ONLY
4872 #else /* LIBXO_TEXT_ONLY */
4873 return XOF_ISSET(xop, XOF_COLOR);
4874 #endif /* LIBXO_TEXT_ONLY */
4878 * If the color map is in use (--libxo colors=xxxx), then update
4879 * the incoming foreground and background colors from the map.
4882 xo_colors_update (xo_handle_t *xop UNUSED, xo_colors_t *newp UNUSED)
4884 #ifndef LIBXO_TEXT_ONLY
4885 xo_color_t fg = newp->xoc_col_fg;
4886 if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS)
4887 fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */
4888 newp->xoc_col_fg = fg;
4890 xo_color_t bg = newp->xoc_col_bg;
4891 if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS)
4892 bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */
4893 newp->xoc_col_bg = bg;
4894 #endif /* LIBXO_TEXT_ONLY */
4898 xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4901 char *cp = buf, *ep = buf + sizeof(buf);
4903 xo_colors_t *oldp = &xop->xo_colors;
4904 const char *code = NULL;
4907 * Start the buffer with an escape. We don't want to add the '['
4908 * now, since we let xo_effect_text_add unconditionally add the ';'.
4909 * We'll replace the first ';' with a '[' when we're done.
4911 *cp++ = 0x1b; /* Escape */
4914 * Terminals were designed back in the age before "certainty" was
4915 * invented, when standards were more what you'd call "guidelines"
4916 * than actual rules. Anyway we can't depend on them to operate
4917 * correctly. So when display attributes are changed, we punt,
4918 * reseting them all and turning back on the ones we want to keep.
4919 * Longer, but should be completely reliable. Savvy?
4921 if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4922 newp->xoc_effects |= XO_EFF_RESET;
4923 oldp->xoc_effects = 0;
4926 for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4927 if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4930 code = xo_effect_on_codes[i];
4932 cp += snprintf(cp, ep - cp, ";%s", code);
4934 return; /* Should not occur */
4936 if (bit == XO_EFF_RESET) {
4937 /* Mark up the old value so we can detect current values as new */
4938 oldp->xoc_effects = 0;
4939 oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4943 xo_color_t fg = newp->xoc_col_fg;
4944 if (fg != oldp->xoc_col_fg) {
4945 cp += snprintf(cp, ep - cp, ";3%u",
4946 (fg != XO_COL_DEFAULT) ? fg - 1 : 9);
4949 xo_color_t bg = newp->xoc_col_bg;
4950 if (bg != oldp->xoc_col_bg) {
4951 cp += snprintf(cp, ep - cp, ";4%u",
4952 (bg != XO_COL_DEFAULT) ? bg - 1 : 9);
4955 if (cp - buf != 1 && cp < ep - 3) {
4956 buf[1] = '['; /* Overwrite leading ';' */
4959 xo_buf_append(&xop->xo_data, buf, cp - buf);
4964 xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4966 xo_colors_t *oldp = &xop->xo_colors;
4969 * HTML colors are mostly trivial: fill in xo_color_buf with
4970 * a set of class tags representing the colors and effects.
4973 /* If nothing changed, then do nothing */
4974 if (oldp->xoc_effects == newp->xoc_effects
4975 && oldp->xoc_col_fg == newp->xoc_col_fg
4976 && oldp->xoc_col_bg == newp->xoc_col_bg)
4980 xo_buffer_t *xbp = &xop->xo_color_buf;
4982 xo_buf_reset(xbp); /* We rebuild content after each change */
4984 for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4985 if (!(newp->xoc_effects & bit))
4988 xo_buf_append_str(xbp, " effect-");
4989 xo_buf_append_str(xbp, xo_effect_names[i]);
4992 const char *fg = NULL;
4993 const char *bg = NULL;
4995 if (newp->xoc_col_fg != XO_COL_DEFAULT)
4996 fg = xo_color_names[newp->xoc_col_fg];
4997 if (newp->xoc_col_bg != XO_COL_DEFAULT)
4998 bg = xo_color_names[newp->xoc_col_bg];
5000 if (newp->xoc_effects & XO_EFF_INVERSE) {
5001 const char *tmp = fg;
5012 xo_buf_append_str(xbp, " color-fg-");
5013 xo_buf_append_str(xbp, fg);
5017 xo_buf_append_str(xbp, " color-bg-");
5018 xo_buf_append_str(xbp, bg);
5023 xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
5024 const char *value, ssize_t vlen)
5026 const char *fmt = xfip->xfi_format;
5027 ssize_t flen = xfip->xfi_flen;
5031 /* If the string is static and we've in an encoding style, bail */
5032 if (vlen != 0 && xo_style_is_encoding(xop))
5038 xo_buf_append(&xb, value, vlen);
5040 xo_do_format_field(xop, &xb, fmt, flen, 0);
5042 xo_buf_append(&xb, "reset", 6); /* Default if empty */
5044 if (xo_colors_enabled(xop)) {
5045 switch (xo_style(xop)) {
5048 xo_buf_append(&xb, "", 1);
5050 xo_colors_t xoc = xop->xo_colors;
5051 xo_colors_parse(xop, &xoc, xb.xb_bufp);
5052 xo_colors_update(xop, &xoc);
5054 if (xo_style(xop) == XO_STYLE_TEXT) {
5056 * Text mode means emitting the colors as ANSI character
5057 * codes. This will allow people who like colors to have
5058 * colors. The issue is, of course conflicting with the
5059 * user's perfectly reasonable color scheme. Which leads
5060 * to the hell of LSCOLORS, where even app need to have
5061 * customization hooks for adjusting colors. Instead we
5062 * provide a simpler-but-still-annoying answer where one
5063 * can map colors to other colors.
5065 xo_colors_handle_text(xop, &xoc);
5066 xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
5070 * HTML output is wrapped in divs, so the color information
5071 * must appear in every div until cleared. Most pathetic.
5074 xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
5075 xo_colors_handle_html(xop, &xoc);
5078 xop->xo_colors = xoc;
5083 case XO_STYLE_SDPARAMS:
5084 case XO_STYLE_ENCODER:
5086 * Nothing to do; we did all that work just to clear the stack of
5087 * formatting arguments.
5093 xo_buf_cleanup(&xb);
5097 xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
5098 const char *value, ssize_t vlen)
5100 const char *fmt = xfip->xfi_format;
5101 ssize_t flen = xfip->xfi_flen;
5102 xo_xff_flags_t flags = xfip->xfi_flags;
5104 static char units_start_xml[] = " units=\"";
5105 static char units_start_html[] = " data-units=\"";
5107 if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
5108 xo_format_content(xop, "units", NULL, value, vlen, fmt, flen, flags);
5112 xo_buffer_t *xbp = &xop->xo_data;
5113 ssize_t start = xop->xo_units_offset;
5114 ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
5116 if (xo_style(xop) == XO_STYLE_XML)
5117 xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
5118 else if (xo_style(xop) == XO_STYLE_HTML)
5119 xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
5124 xo_data_escape(xop, value, vlen);
5126 xo_do_format_field(xop, NULL, fmt, flen, flags);
5128 xo_buf_append(xbp, "\"", 1);
5130 ssize_t now = xbp->xb_curp - xbp->xb_bufp;
5131 ssize_t delta = now - stop;
5132 if (delta <= 0) { /* Strange; no output to move */
5133 xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
5138 * Now we're in it alright. We've need to insert the unit value
5139 * we just created into the right spot. We make a local copy,
5140 * move it and then insert our copy. We know there's room in the
5141 * buffer, since we're just moving this around.
5143 char *buf = alloca(delta);
5145 memcpy(buf, xbp->xb_bufp + stop, delta);
5146 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
5147 memmove(xbp->xb_bufp + start, buf, delta);
5151 xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
5152 const char *value, ssize_t vlen)
5154 const char *fmt = xfip->xfi_format;
5155 ssize_t flen = xfip->xfi_flen;
5162 bp = alloca(vlen + 1); /* Make local NUL-terminated copy of value */
5163 memcpy(bp, value, vlen);
5166 width = strtol(bp, &cp, 0);
5167 if (width == LONG_MIN || width == LONG_MAX || bp == cp || *cp != '\0') {
5169 xo_failure(xop, "invalid width for anchor: '%s'", bp);
5173 * We really expect the format for width to be "{:/%d}" or
5174 * "{:/%u}", so if that's the case, we just grab our width off
5175 * the argument list. But we need to avoid optimized logic if
5176 * there's a custom formatter.
5178 if (xop->xo_formatter == NULL && flen == 2
5179 && strncmp("%d", fmt, flen) == 0) {
5180 if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5181 width = va_arg(xop->xo_vap, int);
5182 } else if (xop->xo_formatter == NULL && flen == 2
5183 && strncmp("%u", fmt, flen) == 0) {
5184 if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5185 width = va_arg(xop->xo_vap, unsigned);
5188 * So we have a format and it's not a simple one like
5189 * "{:/%d}". That means we need to format the field,
5190 * extract the value from the formatted output, and then
5191 * discard that output.
5193 int anchor_was_set = FALSE;
5194 xo_buffer_t *xbp = &xop->xo_data;
5195 ssize_t start_offset = xo_buf_offset(xbp);
5196 bp = xo_buf_cur(xbp); /* Save start of the string */
5199 if (XOIF_ISSET(xop, XOIF_ANCHOR)) {
5200 XOIF_CLEAR(xop, XOIF_ANCHOR);
5201 anchor_was_set = TRUE;
5204 ssize_t rc = xo_do_format_field(xop, xbp, fmt, flen, 0);
5206 xo_buf_append(xbp, "", 1); /* Append a NUL */
5208 width = strtol(bp, &cp, 0);
5209 if (width == LONG_MIN || width == LONG_MAX
5210 || bp == cp || *cp != '\0') {
5212 xo_failure(xop, "invalid width for anchor: '%s'", bp);
5216 /* Reset the cur pointer to where we found it */
5217 xbp->xb_curp = xbp->xb_bufp + start_offset;
5219 XOIF_SET(xop, XOIF_ANCHOR);
5227 xo_anchor_clear (xo_handle_t *xop)
5229 XOIF_CLEAR(xop, XOIF_ANCHOR);
5230 xop->xo_anchor_offset = 0;
5231 xop->xo_anchor_columns = 0;
5232 xop->xo_anchor_min_width = 0;
5236 * An anchor is a marker used to delay field width implications.
5237 * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
5238 * We are looking for output like " 1/4/5"
5240 * To make this work, we record the anchor and then return to
5241 * format it when the end anchor tag is seen.
5244 xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
5245 const char *value, ssize_t vlen)
5247 if (XOIF_ISSET(xop, XOIF_ANCHOR))
5248 xo_failure(xop, "the anchor already recording is discarded");
5250 XOIF_SET(xop, XOIF_ANCHOR);
5251 xo_buffer_t *xbp = &xop->xo_data;
5252 xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
5253 xop->xo_anchor_columns = 0;
5256 * Now we find the width, if possible. If it's not there,
5257 * we'll get it on the end anchor.
5259 xop->xo_anchor_min_width = xo_find_width(xop, xfip, value, vlen);
5263 xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
5264 const char *value, ssize_t vlen)
5266 if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
5267 xo_failure(xop, "no start anchor");
5271 XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
5273 ssize_t width = xo_find_width(xop, xfip, value, vlen);
5275 width = xop->xo_anchor_min_width;
5277 if (width == 0) /* No width given; nothing to do */
5280 xo_buffer_t *xbp = &xop->xo_data;
5281 ssize_t start = xop->xo_anchor_offset;
5282 ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
5283 ssize_t abswidth = (width > 0) ? width : -width;
5284 ssize_t blen = abswidth - xop->xo_anchor_columns;
5286 if (blen <= 0) /* Already over width */
5289 if (abswidth > XO_MAX_ANCHOR_WIDTH) {
5290 xo_failure(xop, "width over %u are not supported",
5291 XO_MAX_ANCHOR_WIDTH);
5295 /* Make a suitable padding field and emit it */
5296 char *buf = alloca(blen);
5297 memset(buf, ' ', blen);
5298 xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
5300 if (width < 0) /* Already left justified */
5303 ssize_t now = xbp->xb_curp - xbp->xb_bufp;
5304 ssize_t delta = now - stop;
5305 if (delta <= 0) /* Strange; no output to move */
5309 * Now we're in it alright. We've need to insert the padding data
5310 * we just created (which might be an HTML <div> or text) before
5311 * the formatted data. We make a local copy, move it and then
5312 * insert our copy. We know there's room in the buffer, since
5313 * we're just moving this around.
5316 buf = alloca(delta); /* Expand buffer if needed */
5318 memcpy(buf, xbp->xb_bufp + stop, delta);
5319 memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
5320 memmove(xbp->xb_bufp + start, buf, delta);
5323 xo_anchor_clear(xop);
5327 xo_class_name (int ftype)
5330 case 'D': return "decoration";
5331 case 'E': return "error";
5332 case 'L': return "label";
5333 case 'N': return "note";
5334 case 'P': return "padding";
5335 case 'W': return "warning";
5342 xo_tag_name (int ftype)
5345 case 'E': return "__error";
5346 case 'W': return "__warning";
5353 xo_role_wants_default_format (int ftype)
5356 /* These roles can be completely empty and/or without formatting */
5367 static xo_mapping_t xo_role_names[] = {
5369 { 'D', "decoration" },
5378 { '[', "start-anchor" },
5379 { ']', "stop-anchor" },
5383 #define XO_ROLE_EBRACE '{' /* Escaped braces */
5384 #define XO_ROLE_TEXT '+'
5385 #define XO_ROLE_NEWLINE '\n'
5387 static xo_mapping_t xo_modifier_names[] = {
5388 { XFF_ARGUMENT, "argument" },
5389 { XFF_COLON, "colon" },
5390 { XFF_COMMA, "comma" },
5391 { XFF_DISPLAY_ONLY, "display" },
5392 { XFF_ENCODE_ONLY, "encoding" },
5393 { XFF_GT_FIELD, "gettext" },
5394 { XFF_HUMANIZE, "humanize" },
5395 { XFF_HUMANIZE, "hn" },
5396 { XFF_HN_SPACE, "hn-space" },
5397 { XFF_HN_DECIMAL, "hn-decimal" },
5398 { XFF_HN_1000, "hn-1000" },
5400 { XFF_LEAF_LIST, "leaf-list" },
5401 { XFF_LEAF_LIST, "list" },
5402 { XFF_NOQUOTE, "no-quotes" },
5403 { XFF_NOQUOTE, "no-quote" },
5404 { XFF_GT_PLURAL, "plural" },
5405 { XFF_QUOTE, "quotes" },
5406 { XFF_QUOTE, "quote" },
5407 { XFF_TRIM_WS, "trim" },
5408 { XFF_WS, "white" },
5412 #ifdef NOT_NEEDED_YET
5413 static xo_mapping_t xo_modifier_short_names[] = {
5415 { XFF_DISPLAY_ONLY, "d" },
5416 { XFF_ENCODE_ONLY, "e" },
5417 { XFF_GT_FIELD, "g" },
5418 { XFF_HUMANIZE, "h" },
5420 { XFF_LEAF_LIST, "l" },
5421 { XFF_NOQUOTE, "n" },
5422 { XFF_GT_PLURAL, "p" },
5424 { XFF_TRIM_WS, "t" },
5428 #endif /* NOT_NEEDED_YET */
5431 xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5436 for (cp = fmt; *cp; cp++)
5437 if (*cp == '{' || *cp == '\n')
5444 * The field format is:
5445 * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5446 * Roles are optional and include the following field types:
5447 * 'D': decoration; something non-text and non-data (colons, commmas)
5448 * 'E': error message
5449 * 'G': gettext() the entire string; optional domainname as content
5450 * 'L': label; text preceding data
5451 * 'N': note; text following data
5452 * 'P': padding; whitespace
5453 * 'T': Title, where 'content' is a column title
5454 * 'U': Units, where 'content' is the unit label
5455 * 'V': value, where 'content' is the name of the field (the default)
5456 * 'W': warning message
5457 * '[': start a section of anchored text
5458 * ']': end a section of anchored text
5459 * The following modifiers are also supported:
5460 * 'a': content is provided via argument (const char *), not descriptor
5461 * 'c': flag: emit a colon after the label
5462 * 'd': field is only emitted for display styles (text and html)
5463 * 'e': field is only emitted for encoding styles (xml and json)
5464 * 'g': gettext() the field
5465 * 'h': humanize a numeric value (only for display styles)
5466 * 'k': this field is a key, suitable for XPath predicates
5467 * 'l': a leaf-list, a simple list of values
5468 * 'n': no quotes around this field
5469 * 'p': the field has plural gettext semantics (ngettext)
5470 * 'q': add quotes around this field
5471 * 't': trim whitespace around the value
5472 * 'w': emit a blank after the label
5473 * The print-fmt and encode-fmt strings is the printf-style formating
5474 * for this data. JSON and XML will use the encoding-fmt, if present.
5475 * If the encode-fmt is not provided, it defaults to the print-fmt.
5476 * If the print-fmt is not provided, it defaults to 's'.
5479 xo_parse_roles (xo_handle_t *xop, const char *fmt,
5480 const char *basep, xo_field_info_t *xfip)
5484 xo_xff_flags_t flags = 0;
5487 for (sp = basep; sp && *sp; sp++) {
5488 if (*sp == ':' || *sp == '/' || *sp == '}')
5492 if (sp[1] == '\0') {
5493 xo_failure(xop, "backslash at the end of string");
5497 /* Anything backslashed is ignored */
5504 for (np = ++sp; *np; np++)
5505 if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5508 ssize_t slen = np - sp;
5510 xo_xff_flags_t value;
5512 value = xo_name_lookup(xo_role_names, sp, slen);
5516 value = xo_name_lookup(xo_modifier_names, sp, slen);
5520 xo_failure(xop, "unknown keyword ignored: '%.*s'",
5544 xo_failure(xop, "field descriptor uses multiple types: '%s'",
5561 fnum = (fnum * 10) + (*sp - '0');
5565 flags |= XFF_ARGUMENT;
5573 flags |= XFF_DISPLAY_ONLY;
5577 flags |= XFF_ENCODE_ONLY;
5581 flags |= XFF_GT_FIELD;
5585 flags |= XFF_HUMANIZE;
5593 flags |= XFF_LEAF_LIST;
5597 flags |= XFF_NOQUOTE;
5601 flags |= XFF_GT_PLURAL;
5609 flags |= XFF_TRIM_WS;
5617 xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5620 * No good answer here; a bad format will likely
5621 * mean a core file. We just return and hope
5622 * the caller notices there's no output, and while
5623 * that seems, well, bad, there's nothing better.
5628 if (ftype == 'N' || ftype == 'U') {
5629 if (flags & XFF_COLON) {
5630 xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5631 "'%s'", xo_printable(fmt));
5632 flags &= ~XFF_COLON;
5637 xfip->xfi_flags = flags;
5638 xfip->xfi_ftype = ftype ?: 'V';
5639 xfip->xfi_fnum = fnum;
5645 * Number any remaining fields that need numbers. Note that some
5646 * field types (text, newline, escaped braces) never get numbers.
5649 xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5650 const char *fmt UNUSED,
5651 xo_field_info_t *fields)
5653 xo_field_info_t *xfip;
5654 unsigned fnum, max_fields;
5657 /* First make a list of add the explicitly used bits */
5658 for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5659 switch (xfip->xfi_ftype) {
5660 case XO_ROLE_NEWLINE: /* Don't get numbered */
5662 case XO_ROLE_EBRACE:
5672 bits |= 1 << xfip->xfi_fnum;
5677 for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5678 switch (xfip->xfi_ftype) {
5679 case XO_ROLE_NEWLINE: /* Don't get numbered */
5681 case XO_ROLE_EBRACE:
5686 if (xfip->xfi_fnum != 0)
5689 /* Find the next unassigned field */
5690 for (fnum++; bits & (1 << fnum); fnum++)
5693 if (fnum > max_fields)
5696 xfip->xfi_fnum = fnum; /* Mark the field number */
5697 bits |= 1 << fnum; /* Mark it used */
5702 * The format string uses field numbers, so we need to whiffle through it
5703 * and make sure everything's sane and lovely.
5706 xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5707 xo_field_info_t *fields, unsigned num_fields)
5709 xo_field_info_t *xfip;
5710 unsigned field, fnum;
5713 for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5714 /* Fields default to 1:1 with natural position */
5715 if (xfip->xfi_fnum == 0)
5716 xfip->xfi_fnum = field + 1;
5717 else if (xfip->xfi_fnum > num_fields) {
5718 xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5722 fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5723 if (fnum < 64) { /* Only test what fits */
5724 if (bits & (1 << fnum)) {
5725 xo_failure(xop, "field number %u reused: '%s'",
5726 xfip->xfi_fnum, fmt);
5737 xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5738 unsigned num_fields, const char *fmt)
5740 const char *cp, *sp, *ep, *basep;
5742 xo_field_info_t *xfip = fields;
5743 unsigned seen_fnum = 0;
5745 for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5746 xfip->xfi_start = cp;
5749 xfip->xfi_ftype = XO_ROLE_NEWLINE;
5757 for (sp = cp; *sp; sp++) {
5758 if (*sp == '{' || *sp == '\n')
5762 xfip->xfi_ftype = XO_ROLE_TEXT;
5763 xfip->xfi_content = cp;
5764 xfip->xfi_clen = sp - cp;
5765 xfip->xfi_next = sp;
5771 if (cp[1] == '{') { /* Start of {{escaped braces}} */
5772 xfip->xfi_start = cp + 1; /* Start at second brace */
5773 xfip->xfi_ftype = XO_ROLE_EBRACE;
5775 cp += 2; /* Skip over _both_ characters */
5776 for (sp = cp; *sp; sp++) {
5777 if (*sp == '}' && sp[1] == '}')
5781 xo_failure(xop, "missing closing '}}': '%s'",
5786 xfip->xfi_len = sp - xfip->xfi_start + 1;
5788 /* Move along the string, but don't run off the end */
5789 if (*sp == '}' && sp[1] == '}')
5792 xfip->xfi_next = cp;
5796 /* We are looking at the start of a field definition */
5797 xfip->xfi_start = basep = cp + 1;
5799 const char *format = NULL;
5802 /* Looking at roles and modifiers */
5803 sp = xo_parse_roles(xop, fmt, basep, xfip);
5805 /* xo_failure has already been called */
5812 /* Looking at content */
5814 for (ep = ++sp; *sp; sp++) {
5815 if (*sp == '}' || *sp == '/')
5818 if (sp[1] == '\0') {
5819 xo_failure(xop, "backslash at the end of string");
5827 xfip->xfi_clen = sp - ep;
5828 xfip->xfi_content = ep;
5831 xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
5835 /* Looking at main (display) format */
5837 for (ep = ++sp; *sp; sp++) {
5838 if (*sp == '}' || *sp == '/')
5841 if (sp[1] == '\0') {
5842 xo_failure(xop, "backslash at the end of string");
5853 /* Looking at encoding format */
5855 for (ep = ++sp; *sp; sp++) {
5860 xfip->xfi_encoding = ep;
5861 xfip->xfi_elen = sp - ep;
5865 xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
5869 xfip->xfi_len = sp - xfip->xfi_start;
5870 xfip->xfi_next = ++sp;
5872 /* If we have content, then we have a default format */
5873 if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5875 xfip->xfi_format = format;
5876 xfip->xfi_flen = flen;
5877 } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
5878 xfip->xfi_format = xo_default_format;
5889 * If we saw a field number on at least one field, then we need
5890 * to enforce some rules and/or guidelines.
5893 rc = xo_parse_field_numbers(xop, fmt, fields, field);
5899 * We are passed a pointer to a format string just past the "{G:}"
5900 * field. We build a simplified version of the format string.
5903 xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5905 xo_field_info_t *fields,
5907 const char *fmt UNUSED,
5908 xo_simplify_field_func_t field_cb)
5911 xo_xff_flags_t flags;
5912 int field = this_field + 1;
5913 xo_field_info_t *xfip;
5916 for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5917 ftype = xfip->xfi_ftype;
5918 flags = xfip->xfi_flags;
5920 if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5922 field_cb(xfip->xfi_content, xfip->xfi_clen,
5923 (flags & XFF_GT_PLURAL) ? 1 : 0);
5928 /* Ignore gettext roles */
5931 case XO_ROLE_NEWLINE:
5932 xo_buf_append(xbp, "\n", 1);
5935 case XO_ROLE_EBRACE:
5936 xo_buf_append(xbp, "{", 1);
5937 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5938 xo_buf_append(xbp, "}", 1);
5942 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5946 xo_buf_append(xbp, "{", 1);
5949 xo_buf_append(xbp, &ch, 1);
5952 unsigned fnum = xfip->xfi_fnum ?: 0;
5955 /* Field numbers are origin 1, not 0, following printf(3) */
5956 snprintf(num, sizeof(num), "%u", fnum);
5957 xo_buf_append(xbp, num, strlen(num));
5960 xo_buf_append(xbp, ":", 1);
5961 xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5962 xo_buf_append(xbp, "}", 1);
5966 xo_buf_append(xbp, "", 1);
5971 xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5973 xo_dump_fields (xo_field_info_t *fields)
5975 xo_field_info_t *xfip;
5977 for (xfip = fields; xfip->xfi_ftype; xfip++) {
5978 printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5979 (unsigned long) (xfip - fields), xfip->xfi_fnum,
5980 (unsigned long) xfip->xfi_flags,
5981 isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5983 (int) xfip->xfi_clen, xfip->xfi_content ?: "",
5984 (int) xfip->xfi_flen, xfip->xfi_format ?: "",
5985 (int) xfip->xfi_elen, xfip->xfi_encoding ?: "");
5991 * Find the field that matches the given field number
5993 static xo_field_info_t *
5994 xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5996 xo_field_info_t *xfip;
5998 for (xfip = fields; xfip->xfi_ftype; xfip++)
5999 if (xfip->xfi_fnum == fnum)
6006 * At this point, we need to consider if the fields have been reordered,
6007 * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
6009 * We need to rewrite the new_fields using the old fields order,
6010 * so that we can render the message using the arguments as they
6011 * appear on the stack. It's a lot of work, but we don't really
6012 * want to (eventually) fall into the standard printf code which
6013 * means using the arguments straight (and in order) from the
6014 * varargs we were originally passed.
6017 xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
6018 xo_field_info_t *fields, unsigned max_fields)
6020 xo_field_info_t tmp[max_fields];
6021 bzero(tmp, max_fields * sizeof(tmp[0]));
6024 xo_field_info_t *newp, *outp, *zp;
6025 for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
6026 switch (newp->xfi_ftype) {
6027 case XO_ROLE_NEWLINE: /* Don't get numbered */
6029 case XO_ROLE_EBRACE:
6032 outp->xfi_renum = 0;
6036 zp = xo_gettext_find_field(fields, ++fnum);
6037 if (zp == NULL) { /* Should not occur */
6039 outp->xfi_renum = 0;
6044 outp->xfi_renum = newp->xfi_fnum;
6047 memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
6051 * We've got two lists of fields, the old list from the original
6052 * format string and the new one from the parsed gettext reply. The
6053 * new list has the localized words, where the old list has the
6054 * formatting information. We need to combine them into a single list
6057 * If the list needs to be reordered, then we've got more serious work
6061 xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
6062 const char *gtfmt, xo_field_info_t *old_fields,
6063 xo_field_info_t *new_fields, unsigned new_max_fields,
6067 xo_field_info_t *newp, *oldp, *startp = old_fields;
6069 xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
6071 for (newp = new_fields; newp->xfi_ftype; newp++) {
6072 switch (newp->xfi_ftype) {
6073 case XO_ROLE_NEWLINE:
6075 case XO_ROLE_EBRACE:
6079 for (oldp = startp; oldp->xfi_ftype; oldp++) {
6080 if (oldp->xfi_ftype != 'V')
6082 if (newp->xfi_clen != oldp->xfi_clen
6083 || strncmp(newp->xfi_content, oldp->xfi_content,
6084 oldp->xfi_clen) != 0) {
6092 /* Didn't find it on the first pass (starting from start) */
6093 if (oldp->xfi_ftype == 0) {
6094 for (oldp = old_fields; oldp < startp; oldp++) {
6095 if (oldp->xfi_ftype != 'V')
6097 if (newp->xfi_clen != oldp->xfi_clen)
6099 if (strncmp(newp->xfi_content, oldp->xfi_content,
6100 oldp->xfi_clen) != 0)
6105 if (oldp == startp) {
6106 /* Field not found */
6107 xo_failure(xop, "post-gettext format can't find field "
6108 "'%.*s' in format '%s'",
6109 newp->xfi_clen, newp->xfi_content,
6110 xo_printable(gtfmt));
6118 * Other fields don't have names for us to use, so if
6119 * the types aren't the same, then we'll have to assume
6120 * the original field is a match.
6122 for (oldp = startp; oldp->xfi_ftype; oldp++) {
6123 if (oldp->xfi_ftype == 'V') /* Can't go past these */
6125 if (oldp->xfi_ftype == newp->xfi_ftype)
6126 goto copy_it; /* Assumably we have a match */
6132 * Found a match; copy over appropriate fields
6135 newp->xfi_flags = oldp->xfi_flags;
6136 newp->xfi_fnum = oldp->xfi_fnum;
6137 newp->xfi_format = oldp->xfi_format;
6138 newp->xfi_flen = oldp->xfi_flen;
6139 newp->xfi_encoding = oldp->xfi_encoding;
6140 newp->xfi_elen = oldp->xfi_elen;
6143 *reorderedp = reordered;
6145 xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
6146 xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
6153 * We don't want to make gettext() calls here with a complete format
6154 * string, since that means changing a flag would mean a
6155 * labor-intensive re-translation expense. Instead we build a
6156 * simplified form with a reduced level of detail, perform a lookup on
6157 * that string and then re-insert the formating info.
6159 * So something like:
6160 * xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
6161 * would have a lookup string of:
6162 * "close {:fd} returned {:error} {:test}\n"
6164 * We also need to handling reordering of fields, where the gettext()
6165 * reply string uses fields in a different order than the original
6167 * "cluse-a {:fd} retoorned {:test}. Bork {:error} Bork. Bork.\n"
6168 * If we have to reorder fields within the message, then things get
6169 * complicated. See xo_gettext_rewrite_fields.
6171 * Summary: i18n aighn't cheap.
6174 xo_gettext_build_format (xo_handle_t *xop,
6175 xo_field_info_t *fields, int this_field,
6176 const char *fmt, char **new_fmtp)
6178 if (xo_style_is_encoding(xop))
6184 if (xo_gettext_simplify_format(xop, &xb, fields,
6185 this_field, fmt, NULL))
6188 const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
6189 if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
6192 char *new_fmt = xo_strndup(gtfmt, -1);
6193 if (new_fmt == NULL)
6196 xo_buf_cleanup(&xb);
6198 *new_fmtp = new_fmt;
6202 xo_buf_cleanup(&xb);
6209 xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
6210 ssize_t *fstart, unsigned min_fstart,
6211 ssize_t *fend, unsigned max_fend)
6213 xo_field_info_t *xfip;
6215 ssize_t base = fstart[min_fstart];
6216 ssize_t blen = fend[max_fend] - base;
6217 xo_buffer_t *xbp = &xop->xo_data;
6222 buf = xo_realloc(NULL, blen);
6226 memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
6228 unsigned field = min_fstart, len, fnum;
6229 ssize_t soff, doff = base;
6230 xo_field_info_t *zp;
6233 * Be aware there are two competing views of "field number": we
6234 * want the user to thing in terms of "The {1:size}" where {G:},
6235 * newlines, escaped braces, and text don't have numbers. But is
6236 * also the internal view, where we have an array of
6237 * xo_field_info_t and every field have an index. fnum, fstart[]
6238 * and fend[] are the latter, but xfi_renum is the former.
6240 for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
6242 if (xfip->xfi_renum) {
6243 zp = xo_gettext_find_field(fields, xfip->xfi_renum);
6244 fnum = zp ? zp - fields : field;
6247 soff = fstart[fnum];
6248 len = fend[fnum] - soff;
6252 memcpy(xbp->xb_bufp + doff, buf + soff, len);
6259 #else /* HAVE_GETTEXT */
6261 xo_gettext_build_format (xo_handle_t *xop UNUSED,
6262 xo_field_info_t *fields UNUSED,
6263 int this_field UNUSED,
6264 const char *fmt UNUSED, char **new_fmtp)
6271 xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
6272 const char *gtfmt UNUSED,
6273 xo_field_info_t *old_fields UNUSED,
6274 xo_field_info_t *new_fields UNUSED,
6275 unsigned new_max_fields UNUSED,
6276 int *reorderedp UNUSED)
6282 xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
6283 xo_field_info_t *fields UNUSED,
6284 ssize_t *fstart UNUSED, unsigned min_fstart UNUSED,
6285 ssize_t *fend UNUSED, unsigned max_fend UNUSED)
6289 #endif /* HAVE_GETTEXT */
6292 * Emit a set of fields. This is really the core of libxo.
6295 xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
6296 unsigned max_fields, const char *fmt)
6298 int gettext_inuse = 0;
6299 int gettext_changed = 0;
6300 int gettext_reordered = 0;
6302 xo_xff_flags_t flags;
6303 xo_field_info_t *new_fields = NULL;
6304 xo_field_info_t *xfip;
6308 int flush = XOF_ISSET(xop, XOF_FLUSH);
6309 int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
6310 char *new_fmt = NULL;
6312 if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
6316 * Some overhead for gettext; if the fields in the msgstr returned
6317 * by gettext are reordered, then we need to record start and end
6318 * for each field. We'll go ahead and render the fields in the
6319 * normal order, but later we can then reconstruct the reordered
6320 * fields using these fstart/fend values.
6322 unsigned flimit = max_fields * 2; /* Pessimistic limit */
6323 unsigned min_fstart = flimit - 1;
6324 unsigned max_fend = 0; /* Highest recorded fend[] entry */
6325 ssize_t fstart[flimit];
6326 bzero(fstart, flimit * sizeof(fstart[0]));
6327 ssize_t fend[flimit];
6328 bzero(fend, flimit * sizeof(fend[0]));
6330 for (xfip = fields, field = 0; field < max_fields && xfip->xfi_ftype;
6332 ftype = xfip->xfi_ftype;
6333 flags = xfip->xfi_flags;
6335 /* Record field start offset */
6336 if (gettext_reordered) {
6337 fstart[field] = xo_buf_offset(&xop->xo_data);
6338 if (min_fstart > field)
6342 const char *content = xfip->xfi_content;
6343 ssize_t clen = xfip->xfi_clen;
6345 if (flags & XFF_ARGUMENT) {
6347 * Argument flag means the content isn't given in the descriptor,
6348 * but as a UTF-8 string ('const char *') argument in xo_vap.
6350 content = va_arg(xop->xo_vap, char *);
6351 clen = content ? strlen(content) : 0;
6354 if (ftype == XO_ROLE_NEWLINE) {
6356 if (flush_line && xo_flush_h(xop) < 0)
6360 } else if (ftype == XO_ROLE_EBRACE) {
6361 xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6364 } else if (ftype == XO_ROLE_TEXT) {
6366 xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6371 * Notes and units need the 'w' flag handled before the content.
6373 if (ftype == 'N' || ftype == 'U') {
6374 if (flags & XFF_WS) {
6375 xo_format_content(xop, "padding", NULL, " ", 1,
6377 flags &= ~XFF_WS; /* Prevent later handling of this flag */
6382 xo_format_value(xop, content, clen, NULL, 0,
6383 xfip->xfi_format, xfip->xfi_flen,
6384 xfip->xfi_encoding, xfip->xfi_elen, flags);
6385 else if (ftype == '[')
6386 xo_anchor_start(xop, xfip, content, clen);
6387 else if (ftype == ']')
6388 xo_anchor_stop(xop, xfip, content, clen);
6389 else if (ftype == 'C')
6390 xo_format_colors(xop, xfip, content, clen);
6392 else if (ftype == 'G') {
6394 * A {G:domain} field; disect the domain name and translate
6395 * the remaining portion of the input string. If the user
6396 * didn't put the {G:} at the start of the format string, then
6397 * assumably they just want us to translate the rest of it.
6398 * Since gettext returns strings in a static buffer, we make
6399 * a copy in new_fmt.
6401 xo_set_gettext_domain(xop, xfip, content, clen);
6403 if (!gettext_inuse) { /* Only translate once */
6410 xo_gettext_build_format(xop, fields, field,
6411 xfip->xfi_next, &new_fmt);
6413 gettext_changed = 1;
6415 unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6417 if (++new_max_fields < max_fields)
6418 new_max_fields = max_fields;
6420 /* Leave a blank slot at the beginning */
6421 ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6422 new_fields = alloca(sz);
6423 bzero(new_fields, sz);
6425 if (!xo_parse_fields(xop, new_fields + 1,
6426 new_max_fields, new_fmt)) {
6427 gettext_reordered = 0;
6429 if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6430 fields, new_fields + 1,
6431 new_max_fields, &gettext_reordered)) {
6433 if (gettext_reordered) {
6434 if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6435 xo_failure(xop, "gettext finds reordered "
6436 "fields in '%s' and '%s'",
6438 xo_printable(new_fmt));
6439 flush_line = 0; /* Must keep at content */
6440 XOIF_SET(xop, XOIF_REORDER);
6443 field = -1; /* Will be incremented at top of loop */
6445 max_fields = new_max_fields;
6452 } else if (clen || xfip->xfi_format) {
6454 const char *class_name = xo_class_name(ftype);
6456 xo_format_content(xop, class_name, xo_tag_name(ftype),
6458 xfip->xfi_format, xfip->xfi_flen, flags);
6459 else if (ftype == 'T')
6460 xo_format_title(xop, xfip, content, clen);
6461 else if (ftype == 'U')
6462 xo_format_units(xop, xfip, content, clen);
6464 xo_failure(xop, "unknown field type: '%c'", ftype);
6467 if (flags & XFF_COLON)
6468 xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
6471 xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6474 /* Record the end-of-field offset */
6475 if (gettext_reordered) {
6476 fend[field] = xo_buf_offset(&xop->xo_data);
6481 if (gettext_changed && gettext_reordered) {
6482 /* Final step: rebuild the content using the rendered fields */
6483 xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6487 XOIF_CLEAR(xop, XOIF_REORDER);
6490 * If we've got enough data, flush it.
6492 if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER)
6495 /* If we don't have an anchor, write the text out */
6496 if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6497 if (xo_flush_h(xop) < 0)
6505 * We've carried the gettext domainname inside our handle just for
6506 * convenience, but we need to ensure it doesn't survive across
6509 if (xop->xo_gt_domain) {
6510 xo_free(xop->xo_gt_domain);
6511 xop->xo_gt_domain = NULL;
6514 return (rc < 0) ? rc : xop->xo_columns;
6518 * Parse and emit a set of fields
6521 xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
6523 xop->xo_columns = 0; /* Always reset it */
6524 xop->xo_errno = errno; /* Save for "%m" */
6529 unsigned max_fields;
6530 xo_field_info_t *fields = NULL;
6532 /* Adjust XOEF_RETAIN based on global flags */
6533 if (XOF_ISSET(xop, XOF_RETAIN_ALL))
6534 flags |= XOEF_RETAIN;
6535 if (XOF_ISSET(xop, XOF_RETAIN_NONE))
6536 flags &= ~XOEF_RETAIN;
6539 * Check for 'retain' flag, telling us to retain the field
6540 * information. If we've already saved it, then we can avoid
6541 * re-parsing the format string.
6543 if (!(flags & XOEF_RETAIN)
6544 || xo_retain_find(fmt, &fields, &max_fields) != 0
6545 || fields == NULL) {
6547 /* Nothing retained; parse the format string */
6548 max_fields = xo_count_fields(xop, fmt);
6549 fields = alloca(max_fields * sizeof(fields[0]));
6550 bzero(fields, max_fields * sizeof(fields[0]));
6552 if (xo_parse_fields(xop, fields, max_fields, fmt))
6553 return -1; /* Warning already displayed */
6555 if (flags & XOEF_RETAIN) {
6556 /* Retain the info */
6557 xo_retain_add(fmt, fields, max_fields);
6561 return xo_do_emit_fields(xop, fields, max_fields, fmt);
6565 * Rebuild a format string in a gettext-friendly format. This function
6566 * is exposed to tools can perform this function. See xo(1).
6569 xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6570 xo_simplify_field_func_t field_cb)
6572 xop = xo_default(xop);
6574 xop->xo_columns = 0; /* Always reset it */
6575 xop->xo_errno = errno; /* Save for "%m" */
6577 unsigned max_fields = xo_count_fields(xop, fmt);
6578 xo_field_info_t fields[max_fields];
6580 bzero(fields, max_fields * sizeof(fields[0]));
6582 if (xo_parse_fields(xop, fields, max_fields, fmt))
6583 return NULL; /* Warning already displayed */
6589 xo_gettext_finish_numbering_fields(xop, fmt, fields);
6591 if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6598 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
6602 xop = xo_default(xop);
6603 va_copy(xop->xo_vap, vap);
6604 rc = xo_do_emit(xop, 0, fmt);
6605 va_end(xop->xo_vap);
6606 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6612 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
6616 xop = xo_default(xop);
6617 va_start(xop->xo_vap, fmt);
6618 rc = xo_do_emit(xop, 0, fmt);
6619 va_end(xop->xo_vap);
6620 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6626 xo_emit (const char *fmt, ...)
6628 xo_handle_t *xop = xo_default(NULL);
6631 va_start(xop->xo_vap, fmt);
6632 rc = xo_do_emit(xop, 0, fmt);
6633 va_end(xop->xo_vap);
6634 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6640 xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
6641 const char *fmt, va_list vap)
6645 xop = xo_default(xop);
6646 va_copy(xop->xo_vap, vap);
6647 rc = xo_do_emit(xop, flags, fmt);
6648 va_end(xop->xo_vap);
6649 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6655 xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
6659 xop = xo_default(xop);
6660 va_start(xop->xo_vap, fmt);
6661 rc = xo_do_emit(xop, flags, fmt);
6662 va_end(xop->xo_vap);
6663 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6669 xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
6671 xo_handle_t *xop = xo_default(NULL);
6674 va_start(xop->xo_vap, fmt);
6675 rc = xo_do_emit(xop, flags, fmt);
6676 va_end(xop->xo_vap);
6677 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6683 * Emit a single field by providing the info information typically provided
6684 * inside the field description (role, modifiers, and formats). This is
6685 * a convenience function to avoid callers using snprintf to build field
6689 xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
6690 const char *fmt, const char *efmt,
6695 xop = xo_default(xop);
6700 xo_field_info_t xfi;
6702 bzero(&xfi, sizeof(xfi));
6705 cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
6709 xfi.xfi_start = fmt;
6710 xfi.xfi_content = contents;
6711 xfi.xfi_format = fmt;
6712 xfi.xfi_encoding = efmt;
6713 xfi.xfi_clen = contents ? strlen(contents) : 0;
6714 xfi.xfi_flen = fmt ? strlen(fmt) : 0;
6715 xfi.xfi_elen = efmt ? strlen(efmt) : 0;
6717 /* If we have content, then we have a default format */
6718 if (contents && fmt == NULL
6719 && xo_role_wants_default_format(xfi.xfi_ftype)) {
6720 xfi.xfi_format = xo_default_format;
6724 va_copy(xop->xo_vap, vap);
6726 rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
6728 va_end(xop->xo_vap);
6734 xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
6735 const char *fmt, const char *efmt, ...)
6740 va_start(vap, efmt);
6741 rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
6748 xo_emit_field (const char *rolmod, const char *contents,
6749 const char *fmt, const char *efmt, ...)
6754 va_start(vap, efmt);
6755 rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
6762 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
6764 const ssize_t extra = 5; /* space, equals, quote, quote, and nul */
6765 xop = xo_default(xop);
6768 ssize_t nlen = strlen(name);
6769 xo_buffer_t *xbp = &xop->xo_attrs;
6770 ssize_t name_offset, value_offset;
6772 switch (xo_style(xop)) {
6774 if (!xo_buf_has_room(xbp, nlen + extra))
6777 *xbp->xb_curp++ = ' ';
6778 memcpy(xbp->xb_curp, name, nlen);
6779 xbp->xb_curp += nlen;
6780 *xbp->xb_curp++ = '=';
6781 *xbp->xb_curp++ = '"';
6783 rc = xo_vsnprintf(xop, xbp, fmt, vap);
6786 rc = xo_escape_xml(xbp, rc, 1);
6790 if (!xo_buf_has_room(xbp, 2))
6793 *xbp->xb_curp++ = '"';
6794 *xbp->xb_curp = '\0';
6799 case XO_STYLE_ENCODER:
6800 name_offset = xo_buf_offset(xbp);
6801 xo_buf_append(xbp, name, nlen);
6802 xo_buf_append(xbp, "", 1);
6804 value_offset = xo_buf_offset(xbp);
6805 rc = xo_vsnprintf(xop, xbp, fmt, vap);
6808 *xbp->xb_curp = '\0';
6809 rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6810 xo_buf_data(xbp, name_offset),
6811 xo_buf_data(xbp, value_offset), 0);
6819 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
6825 rc = xo_attr_hv(xop, name, fmt, vap);
6832 xo_attr (const char *name, const char *fmt, ...)
6838 rc = xo_attr_hv(NULL, name, fmt, vap);
6845 xo_depth_change (xo_handle_t *xop, const char *name,
6846 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
6848 if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6851 if (XOF_ISSET(xop, XOF_DTRT))
6854 if (delta >= 0) { /* Push operation */
6855 if (xo_depth_check(xop, xop->xo_depth + delta))
6858 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
6859 xsp->xs_flags = flags;
6860 xsp->xs_state = state;
6861 xo_stack_set_flags(xop);
6864 name = XO_FAILURE_NAME;
6866 xsp->xs_name = xo_strndup(name, -1);
6868 } else { /* Pop operation */
6869 if (xop->xo_depth == 0) {
6870 if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
6871 xo_failure(xop, "close with empty stack: '%s'", name);
6875 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6876 if (XOF_ISSET(xop, XOF_WARN)) {
6877 const char *top = xsp->xs_name;
6878 if (top != NULL && name != NULL && strcmp(name, top) != 0) {
6879 xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
6883 if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
6884 xo_failure(xop, "list close on list confict: '%s'",
6888 if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
6889 xo_failure(xop, "list close on instance confict: '%s'",
6896 xo_free(xsp->xs_name);
6897 xsp->xs_name = NULL;
6900 xo_free(xsp->xs_keys);
6901 xsp->xs_keys = NULL;
6905 xop->xo_depth += delta; /* Record new depth */
6906 xop->xo_indent += indent;
6910 xo_set_depth (xo_handle_t *xop, int depth)
6912 xop = xo_default(xop);
6914 if (xo_depth_check(xop, depth))
6917 xop->xo_depth += depth;
6918 xop->xo_indent += depth;
6921 * Handling the "top wrapper" for JSON is a bit of a pain. Here
6922 * we need to detect that the depth has been changed to set the
6923 * "XOIF_TOP_EMITTED" flag correctly.
6925 if (xop->xo_style == XO_STYLE_JSON
6926 && !XOF_ISSET(xop, XOF_NO_TOP) && xop->xo_depth > 0)
6927 XOIF_SET(xop, XOIF_TOP_EMITTED);
6930 static xo_xsf_flags_t
6931 xo_stack_flags (xo_xof_flags_t xflags)
6933 if (xflags & XOF_DTRT)
6939 xo_emit_top (xo_handle_t *xop, const char *ppn)
6941 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6942 XOIF_SET(xop, XOIF_TOP_EMITTED);
6944 if (xop->xo_version) {
6945 xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6946 xo_indent(xop), "", xop->xo_version, ppn);
6947 xo_free(xop->xo_version);
6948 xop->xo_version = NULL;
6953 xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6956 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6957 const char *pre_nl = "";
6960 xo_failure(xop, "NULL passed for container name");
6961 name = XO_FAILURE_NAME;
6964 const char *leader = xo_xml_leader(xop, name);
6965 flags |= xop->xo_flags; /* Pick up handle flags */
6967 switch (xo_style(xop)) {
6969 rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
6971 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6972 rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6973 xo_data_append(xop, xop->xo_attrs.xb_bufp,
6974 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6975 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6978 rc += xo_printf(xop, ">%s", ppn);
6982 xo_stack_set_flags(xop);
6984 if (!XOF_ISSET(xop, XOF_NO_TOP)
6985 && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6986 xo_emit_top(xop, ppn);
6988 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6989 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6990 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6992 rc = xo_printf(xop, "%s%*s\"%s\": {%s",
6993 pre_nl, xo_indent(xop), "", name, ppn);
6996 case XO_STYLE_SDPARAMS:
6999 case XO_STYLE_ENCODER:
7000 rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL, flags);
7004 xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
7005 xo_stack_flags(flags));
7011 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7013 return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
7017 xo_open_container_h (xo_handle_t *xop, const char *name)
7019 return xo_open_container_hf(xop, 0, name);
7023 xo_open_container (const char *name)
7025 return xo_open_container_hf(NULL, 0, name);
7029 xo_open_container_hd (xo_handle_t *xop, const char *name)
7031 return xo_open_container_hf(xop, XOF_DTRT, name);
7035 xo_open_container_d (const char *name)
7037 return xo_open_container_hf(NULL, XOF_DTRT, name);
7041 xo_do_close_container (xo_handle_t *xop, const char *name)
7043 xop = xo_default(xop);
7046 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7047 const char *pre_nl = "";
7050 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7052 name = xsp->xs_name;
7054 ssize_t len = strlen(name) + 1;
7055 /* We need to make a local copy; xo_depth_change will free it */
7056 char *cp = alloca(len);
7057 memcpy(cp, name, len);
7059 } else if (!(xsp->xs_flags & XSF_DTRT)) {
7060 xo_failure(xop, "missing name without 'dtrt' mode");
7061 name = XO_FAILURE_NAME;
7065 const char *leader = xo_xml_leader(xop, name);
7067 switch (xo_style(xop)) {
7069 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
7070 rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
7074 xo_stack_set_flags(xop);
7076 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7077 ppn = (xop->xo_depth <= 1) ? pre_nl : "";
7080 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
7081 rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
7082 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7087 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
7090 case XO_STYLE_SDPARAMS:
7093 case XO_STYLE_ENCODER:
7094 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
7095 rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL, 0);
7103 xo_close_container_h (xo_handle_t *xop, const char *name)
7105 return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
7109 xo_close_container (const char *name)
7111 return xo_close_container_h(NULL, name);
7115 xo_close_container_hd (xo_handle_t *xop)
7117 return xo_close_container_h(xop, NULL);
7121 xo_close_container_d (void)
7123 return xo_close_container_h(NULL, NULL);
7127 xo_do_open_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7132 xop = xo_default(xop);
7134 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7135 const char *pre_nl = "";
7137 switch (xo_style(xop)) {
7141 if (!XOF_ISSET(xop, XOF_NO_TOP)
7142 && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7143 xo_emit_top(xop, ppn);
7146 xo_failure(xop, "NULL passed for list name");
7147 name = XO_FAILURE_NAME;
7150 xo_stack_set_flags(xop);
7152 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7153 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7154 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7156 rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7157 pre_nl, xo_indent(xop), "", name, ppn);
7160 case XO_STYLE_ENCODER:
7161 rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL, flags);
7165 xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
7166 XSF_LIST | xo_stack_flags(flags));
7172 xo_open_list_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7174 return xo_transition(xop, flags, name, XSS_OPEN_LIST);
7178 xo_open_list_h (xo_handle_t *xop, const char *name)
7180 return xo_open_list_hf(xop, 0, name);
7184 xo_open_list (const char *name)
7186 return xo_open_list_hf(NULL, 0, name);
7190 xo_open_list_hd (xo_handle_t *xop, const char *name)
7192 return xo_open_list_hf(xop, XOF_DTRT, name);
7196 xo_open_list_d (const char *name)
7198 return xo_open_list_hf(NULL, XOF_DTRT, name);
7202 xo_do_close_list (xo_handle_t *xop, const char *name)
7205 const char *pre_nl = "";
7208 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7210 name = xsp->xs_name;
7212 ssize_t len = strlen(name) + 1;
7213 /* We need to make a local copy; xo_depth_change will free it */
7214 char *cp = alloca(len);
7215 memcpy(cp, name, len);
7217 } else if (!(xsp->xs_flags & XSF_DTRT)) {
7218 xo_failure(xop, "missing name without 'dtrt' mode");
7219 name = XO_FAILURE_NAME;
7223 switch (xo_style(xop)) {
7225 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7226 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7227 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7229 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
7230 rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7231 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7234 case XO_STYLE_ENCODER:
7235 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7236 rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL, 0);
7240 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7241 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7249 xo_close_list_h (xo_handle_t *xop, const char *name)
7251 return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
7255 xo_close_list (const char *name)
7257 return xo_close_list_h(NULL, name);
7261 xo_close_list_hd (xo_handle_t *xop)
7263 return xo_close_list_h(xop, NULL);
7267 xo_close_list_d (void)
7269 return xo_close_list_h(NULL, NULL);
7273 xo_do_open_leaf_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7278 xop = xo_default(xop);
7280 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7281 const char *pre_nl = "";
7283 switch (xo_style(xop)) {
7287 if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7288 if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
7289 xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
7290 XOIF_SET(xop, XOIF_TOP_EMITTED);
7295 xo_failure(xop, "NULL passed for list name");
7296 name = XO_FAILURE_NAME;
7299 xo_stack_set_flags(xop);
7301 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7302 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7303 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7305 rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7306 pre_nl, xo_indent(xop), "", name, ppn);
7309 case XO_STYLE_ENCODER:
7310 rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL, flags);
7314 xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
7315 XSF_LIST | xo_stack_flags(flags));
7321 xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
7324 const char *pre_nl = "";
7327 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7329 name = xsp->xs_name;
7331 ssize_t len = strlen(name) + 1;
7332 /* We need to make a local copy; xo_depth_change will free it */
7333 char *cp = alloca(len);
7334 memcpy(cp, name, len);
7336 } else if (!(xsp->xs_flags & XSF_DTRT)) {
7337 xo_failure(xop, "missing name without 'dtrt' mode");
7338 name = XO_FAILURE_NAME;
7342 switch (xo_style(xop)) {
7344 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7345 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7346 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7348 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7349 rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7350 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7353 case XO_STYLE_ENCODER:
7354 rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL, 0);
7358 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7359 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7367 xo_do_open_instance (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7369 xop = xo_default(xop);
7372 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7373 const char *pre_nl = "";
7376 xo_failure(xop, "NULL passed for instance name");
7377 name = XO_FAILURE_NAME;
7380 const char *leader = xo_xml_leader(xop, name);
7381 flags |= xop->xo_flags;
7383 switch (xo_style(xop)) {
7385 rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
7387 if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7388 rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7389 xo_data_append(xop, xop->xo_attrs.xb_bufp,
7390 xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7391 xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7394 rc += xo_printf(xop, ">%s", ppn);
7398 xo_stack_set_flags(xop);
7400 if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7401 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7402 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7404 rc = xo_printf(xop, "%s%*s{%s",
7405 pre_nl, xo_indent(xop), "", ppn);
7408 case XO_STYLE_SDPARAMS:
7411 case XO_STYLE_ENCODER:
7412 rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL, flags);
7416 xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7422 xo_open_instance_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7424 return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7428 xo_open_instance_h (xo_handle_t *xop, const char *name)
7430 return xo_open_instance_hf(xop, 0, name);
7434 xo_open_instance (const char *name)
7436 return xo_open_instance_hf(NULL, 0, name);
7440 xo_open_instance_hd (xo_handle_t *xop, const char *name)
7442 return xo_open_instance_hf(xop, XOF_DTRT, name);
7446 xo_open_instance_d (const char *name)
7448 return xo_open_instance_hf(NULL, XOF_DTRT, name);
7452 xo_do_close_instance (xo_handle_t *xop, const char *name)
7454 xop = xo_default(xop);
7457 const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7458 const char *pre_nl = "";
7461 xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7463 name = xsp->xs_name;
7465 ssize_t len = strlen(name) + 1;
7466 /* We need to make a local copy; xo_depth_change will free it */
7467 char *cp = alloca(len);
7468 memcpy(cp, name, len);
7470 } else if (!(xsp->xs_flags & XSF_DTRT)) {
7471 xo_failure(xop, "missing name without 'dtrt' mode");
7472 name = XO_FAILURE_NAME;
7476 const char *leader = xo_xml_leader(xop, name);
7478 switch (xo_style(xop)) {
7480 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7481 rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
7485 pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7487 xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7488 rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
7489 xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7494 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7497 case XO_STYLE_SDPARAMS:
7500 case XO_STYLE_ENCODER:
7501 xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7502 rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL, 0);
7510 xo_close_instance_h (xo_handle_t *xop, const char *name)
7512 return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7516 xo_close_instance (const char *name)
7518 return xo_close_instance_h(NULL, name);
7522 xo_close_instance_hd (xo_handle_t *xop)
7524 return xo_close_instance_h(xop, NULL);
7528 xo_close_instance_d (void)
7530 return xo_close_instance_h(NULL, NULL);
7534 xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7538 xo_xsf_flags_t flags;
7540 for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7541 switch (xsp->xs_state) {
7547 case XSS_OPEN_CONTAINER:
7548 rc = xo_do_close_container(xop, NULL);
7552 rc = xo_do_close_list(xop, NULL);
7555 case XSS_OPEN_INSTANCE:
7556 rc = xo_do_close_instance(xop, NULL);
7559 case XSS_OPEN_LEAF_LIST:
7560 rc = xo_do_close_leaf_list(xop, NULL);
7564 flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7565 xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7566 xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7572 xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7579 * This function is responsible for clearing out whatever is needed
7580 * to get to the desired state, if possible.
7583 xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7585 xo_stack_t *xsp, *limit = NULL;
7587 xo_state_t need_state = new_state;
7589 if (new_state == XSS_CLOSE_CONTAINER)
7590 need_state = XSS_OPEN_CONTAINER;
7591 else if (new_state == XSS_CLOSE_LIST)
7592 need_state = XSS_OPEN_LIST;
7593 else if (new_state == XSS_CLOSE_INSTANCE)
7594 need_state = XSS_OPEN_INSTANCE;
7595 else if (new_state == XSS_CLOSE_LEAF_LIST)
7596 need_state = XSS_OPEN_LEAF_LIST;
7597 else if (new_state == XSS_MARKER)
7598 need_state = XSS_MARKER;
7600 return 0; /* Unknown or useless new states are ignored */
7602 for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7604 * Marker's normally stop us from going any further, unless
7605 * we are popping a marker (new_state == XSS_MARKER).
7607 if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7609 xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7611 xo_state_name(new_state),
7612 xsp->xs_name, name);
7617 xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7622 if (xsp->xs_state != need_state)
7625 if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
7632 if (limit == NULL) {
7633 xo_failure(xop, "xo_%s can't find match for '%s'",
7634 xo_state_name(new_state), name);
7638 rc = xo_do_close_all(xop, limit);
7644 * We are in a given state and need to transition to the new state.
7647 xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
7648 xo_state_t new_state)
7652 int old_state, on_marker;
7654 xop = xo_default(xop);
7656 xsp = &xop->xo_stack[xop->xo_depth];
7657 old_state = xsp->xs_state;
7658 on_marker = (old_state == XSS_MARKER);
7660 /* If there's a marker on top of the stack, we need to find a real state */
7661 while (old_state == XSS_MARKER) {
7662 if (xsp == xop->xo_stack)
7665 old_state = xsp->xs_state;
7669 * At this point, the list of possible states are:
7670 * XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7671 * XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7673 switch (XSS_TRANSITION(old_state, new_state)) {
7676 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7677 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7678 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7679 rc = xo_do_open_container(xop, flags, name);
7682 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7684 goto marker_prevents_close;
7685 rc = xo_do_close_list(xop, NULL);
7687 goto open_container;
7690 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7692 goto marker_prevents_close;
7693 rc = xo_do_close_leaf_list(xop, NULL);
7695 goto open_container;
7698 /*close_container:*/
7699 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7701 goto marker_prevents_close;
7702 rc = xo_do_close(xop, name, new_state);
7705 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7706 /* This is an exception for "xo --close" */
7707 rc = xo_do_close_container(xop, name);
7710 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7711 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7713 goto marker_prevents_close;
7714 rc = xo_do_close(xop, name, new_state);
7717 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7719 goto marker_prevents_close;
7720 rc = xo_do_close_leaf_list(xop, NULL);
7722 rc = xo_do_close(xop, name, new_state);
7726 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7727 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7728 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7729 rc = xo_do_open_list(xop, flags, name);
7732 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7734 goto marker_prevents_close;
7735 rc = xo_do_close_list(xop, NULL);
7740 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7742 goto marker_prevents_close;
7743 rc = xo_do_close_leaf_list(xop, NULL);
7749 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7751 goto marker_prevents_close;
7752 rc = xo_do_close(xop, name, new_state);
7755 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7756 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7757 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7758 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7759 rc = xo_do_close(xop, name, new_state);
7763 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7764 rc = xo_do_open_instance(xop, flags, name);
7767 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7768 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7769 rc = xo_do_open_list(xop, flags, name);
7774 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7776 rc = xo_do_open_list(xop, flags, name);
7778 rc = xo_do_close_instance(xop, NULL);
7784 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7786 goto marker_prevents_close;
7787 rc = xo_do_close_leaf_list(xop, NULL);
7793 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7795 goto marker_prevents_close;
7796 rc = xo_do_close_instance(xop, name);
7799 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7800 /* This one makes no sense; ignore it */
7801 xo_failure(xop, "xo_close_instance ignored when called from "
7802 "initial state ('%s')", name ?: "(unknown)");
7805 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7806 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7808 goto marker_prevents_close;
7809 rc = xo_do_close(xop, name, new_state);
7812 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7814 goto marker_prevents_close;
7815 rc = xo_do_close_leaf_list(xop, NULL);
7817 rc = xo_do_close(xop, name, new_state);
7821 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7822 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7823 case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7824 rc = xo_do_open_leaf_list(xop, flags, name);
7827 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7828 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7830 goto marker_prevents_close;
7831 rc = xo_do_close_list(xop, NULL);
7833 goto open_leaf_list;
7836 /*close_leaf_list:*/
7837 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7839 goto marker_prevents_close;
7840 rc = xo_do_close_leaf_list(xop, name);
7843 case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7844 /* Makes no sense; ignore */
7845 xo_failure(xop, "xo_close_leaf_list ignored when called from "
7846 "initial state ('%s')", name ?: "(unknown)");
7849 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7850 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7851 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7853 goto marker_prevents_close;
7854 rc = xo_do_close(xop, name, new_state);
7858 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7859 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7862 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7864 goto marker_prevents_close;
7865 rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7868 case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7871 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7873 goto marker_prevents_close;
7874 rc = xo_do_close_leaf_list(xop, NULL);
7878 case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7879 case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7880 case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7881 rc = xo_do_open_leaf_list(xop, flags, name);
7884 case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7887 case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7889 * We need to be backward compatible with the pre-xo_open_leaf_list
7890 * API, where both lists and leaf-lists were opened as lists. So
7891 * if we find an open list that hasn't had anything written to it,
7897 xo_failure(xop, "unknown transition: (%u -> %u)",
7898 xsp->xs_state, new_state);
7901 /* Handle the flush flag */
7902 if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
7903 if (xo_flush_h(xop) < 0)
7906 /* We have now official made output */
7907 XOIF_SET(xop, XOIF_MADE_OUTPUT);
7911 marker_prevents_close:
7912 xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7913 xop->xo_stack[xop->xo_depth].xs_name,
7914 xo_state_name(old_state), xo_state_name(new_state));
7919 xo_open_marker_h (xo_handle_t *xop, const char *name)
7921 xop = xo_default(xop);
7923 xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7924 xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7930 xo_open_marker (const char *name)
7932 return xo_open_marker_h(NULL, name);
7936 xo_close_marker_h (xo_handle_t *xop, const char *name)
7938 xop = xo_default(xop);
7940 return xo_do_close(xop, name, XSS_MARKER);
7944 xo_close_marker (const char *name)
7946 return xo_close_marker_h(NULL, name);
7950 * Record custom output functions into the xo handle, allowing
7951 * integration with a variety of output frameworks.
7954 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7955 xo_close_func_t close_func, xo_flush_func_t flush_func)
7957 xop = xo_default(xop);
7959 xop->xo_opaque = opaque;
7960 xop->xo_write = write_func;
7961 xop->xo_close = close_func;
7962 xop->xo_flush = flush_func;
7966 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
7968 xo_realloc = realloc_func;
7969 xo_free = free_func;
7973 xo_flush_h (xo_handle_t *xop)
7977 xop = xo_default(xop);
7979 switch (xo_style(xop)) {
7980 case XO_STYLE_ENCODER:
7981 xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL, 0);
7985 if (rc >= 0 && xop->xo_flush)
7986 if (xop->xo_flush(xop->xo_opaque) < 0)
7995 return xo_flush_h(NULL);
7999 xo_finish_h (xo_handle_t *xop)
8001 const char *open_if_empty = "";
8002 xop = xo_default(xop);
8004 if (!XOF_ISSET(xop, XOF_NO_CLOSE))
8005 xo_do_close_all(xop, xop->xo_stack);
8007 switch (xo_style(xop)) {
8009 if (!XOF_ISSET(xop, XOF_NO_TOP)) {
8010 const char *pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
8012 if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
8013 XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
8014 else if (!XOIF_ISSET(xop, XOIF_MADE_OUTPUT)) {
8015 open_if_empty = "{ ";
8019 xo_printf(xop, "%s%*s%s}\n",
8020 pre_nl, xo_indent(xop), "", open_if_empty);
8024 case XO_STYLE_ENCODER:
8025 xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL, 0);
8029 return xo_flush_h(xop);
8035 return xo_finish_h(NULL);
8039 * xo_finish_atexit is suitable for atexit() calls, to force clear up
8040 * and finalizing output.
8043 xo_finish_atexit (void)
8045 (void) xo_finish_h(NULL);
8049 * Generate an error message, such as would be displayed on stderr
8052 xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
8054 xop = xo_default(xop);
8057 * If the format string doesn't end with a newline, we pop
8060 ssize_t len = strlen(fmt);
8061 if (len > 0 && fmt[len - 1] != '\n') {
8062 char *newfmt = alloca(len + 2);
8063 memcpy(newfmt, fmt, len);
8069 switch (xo_style(xop)) {
8071 vfprintf(stderr, fmt, vap);
8075 va_copy(xop->xo_vap, vap);
8077 xo_buf_append_div(xop, "error", 0, NULL, 0, NULL, 0,
8078 fmt, strlen(fmt), NULL, 0);
8080 if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
8085 va_end(xop->xo_vap);
8086 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
8091 va_copy(xop->xo_vap, vap);
8093 xo_open_container_h(xop, "error");
8094 xo_format_value(xop, "message", 7, NULL, 0,
8095 fmt, strlen(fmt), NULL, 0, 0);
8096 xo_close_container_h(xop, "error");
8098 va_end(xop->xo_vap);
8099 bzero(&xop->xo_vap, sizeof(xop->xo_vap));
8102 case XO_STYLE_SDPARAMS:
8103 case XO_STYLE_ENCODER:
8109 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
8114 xo_error_hv(xop, fmt, vap);
8119 * Generate an error message, such as would be displayed on stderr
8122 xo_error (const char *fmt, ...)
8127 xo_error_hv(NULL, fmt, vap);
8132 * Parse any libxo-specific options from the command line, removing them
8133 * so the main() argument parsing won't see them. We return the new value
8134 * for argc or -1 for error. If an error occurred, the program should
8135 * exit. A suitable error message has already been displayed.
8138 xo_parse_args (int argc, char **argv)
8140 static char libxo_opt[] = "--libxo";
8144 /* Save our program name for xo_err and friends */
8145 xo_program = argv[0];
8146 cp = strrchr(xo_program, '/');
8148 xo_program = cp + 1;
8150 xo_handle_t *xop = xo_default(NULL);
8152 for (save = i = 1; i < argc; i++) {
8154 || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
8156 argv[save] = argv[i];
8161 cp = argv[i] + sizeof(libxo_opt) - 1;
8165 xo_warnx("missing libxo option");
8169 if (xo_set_options(xop, cp) < 0)
8171 } else if (*cp == ':') {
8172 if (xo_set_options(xop, cp) < 0)
8175 } else if (*cp == '=') {
8176 if (xo_set_options(xop, ++cp) < 0)
8179 } else if (*cp == '-') {
8181 if (strcmp(cp, "check") == 0) {
8185 xo_warnx("unknown libxo option: '%s'", argv[i]);
8189 xo_warnx("unknown libxo option: '%s'", argv[i]);
8195 * We only want to do color output on terminals, but we only want
8196 * to do this if the user has asked for color.
8198 if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
8199 XOF_SET(xop, XOF_COLOR);
8206 * Debugging function that dumps the current stack of open libxo constructs,
8207 * suitable for calling from the debugger.
8210 xo_dump_stack (xo_handle_t *xop)
8215 xop = xo_default(xop);
8217 fprintf(stderr, "Stack dump:\n");
8219 xsp = xop->xo_stack;
8220 for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
8221 fprintf(stderr, " [%d] %s '%s' [%x]\n",
8222 i, xo_state_name(xsp->xs_state),
8223 xsp->xs_name ?: "--", xsp->xs_flags);
8228 * Record the program name used for error messages
8231 xo_set_program (const char *name)
8237 xo_set_version_h (xo_handle_t *xop, const char *version)
8239 xop = xo_default(xop);
8241 if (version == NULL || strchr(version, '"') != NULL)
8244 if (!xo_style_is_encoding(xop))
8247 switch (xo_style(xop)) {
8249 /* For XML, we record this as an attribute for the first tag */
8250 xo_attr_h(xop, "version", "%s", version);
8255 * For JSON, we record the version string in our handle, and emit
8256 * it in xo_emit_top.
8258 xop->xo_version = xo_strndup(version, -1);
8261 case XO_STYLE_ENCODER:
8262 xo_encoder_handle(xop, XO_OP_VERSION, NULL, version, 0);
8268 * Set the version number for the API content being carried through
8272 xo_set_version (const char *version)
8274 xo_set_version_h(NULL, version);
8278 * Generate a warning. Normally, this is a text message written to
8279 * standard error. If the XOF_WARN_XML flag is set, then we generate
8280 * XMLified content on standard output.
8283 xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
8284 const char *fmt, va_list vap)
8286 xop = xo_default(xop);
8291 xo_open_marker_h(xop, "xo_emit_warn_hcv");
8292 xo_open_container_h(xop, as_warning ? "__warning" : "__error");
8295 xo_emit("{wc:program}", xo_program);
8297 if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
8301 bzero(&temp, sizeof(temp));
8302 temp.xo_style = XO_STYLE_TEXT;
8303 xo_buf_init(&temp.xo_data);
8304 xo_depth_check(&temp, XO_DEPTH);
8307 (void) xo_emit_hv(&temp, fmt, ap);
8310 xo_buffer_t *src = &temp.xo_data;
8311 xo_format_value(xop, "message", 7, src->xb_bufp,
8312 src->xb_curp - src->xb_bufp, NULL, 0, NULL, 0, 0);
8314 xo_free(temp.xo_stack);
8315 xo_buf_cleanup(src);
8318 (void) xo_emit_hv(xop, fmt, vap);
8320 ssize_t len = strlen(fmt);
8321 if (len > 0 && fmt[len - 1] != '\n') {
8323 const char *msg = strerror(code);
8325 xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
8330 xo_close_marker_h(xop, "xo_emit_warn_hcv");
8335 xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
8340 xo_emit_warn_hcv(xop, 1, code, fmt, vap);
8345 xo_emit_warn_c (int code, const char *fmt, ...)
8350 xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8355 xo_emit_warn (const char *fmt, ...)
8361 xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8366 xo_emit_warnx (const char *fmt, ...)
8371 xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
8376 xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
8378 xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8384 xo_emit_err (int eval, const char *fmt, ...)
8389 xo_emit_err_v(0, code, fmt, vap);
8395 xo_emit_errx (int eval, const char *fmt, ...)
8400 xo_emit_err_v(0, -1, fmt, vap);
8407 xo_emit_errc (int eval, int code, const char *fmt, ...)
8412 xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8419 * Get the opaque private pointer for an xo handle
8422 xo_get_private (xo_handle_t *xop)
8424 xop = xo_default(xop);
8425 return xop->xo_private;
8429 * Set the opaque private pointer for an xo handle.
8432 xo_set_private (xo_handle_t *xop, void *opaque)
8434 xop = xo_default(xop);
8435 xop->xo_private = opaque;
8439 * Get the encoder function
8442 xo_get_encoder (xo_handle_t *xop)
8444 xop = xo_default(xop);
8445 return xop->xo_encoder;
8449 * Record an encoder callback function in an xo handle.
8452 xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8454 xop = xo_default(xop);
8456 xop->xo_style = XO_STYLE_ENCODER;
8457 xop->xo_encoder = encoder;
8461 * The xo(1) utility needs to be able to open and close lists and
8462 * instances, but since it's called without "state", we cannot
8463 * rely on the state transitions (in xo_transition) to DTRT, so
8464 * we have a mechanism for external parties to "force" transitions
8465 * that would otherwise be impossible. This is not a general
8466 * mechanism, and is really tailored only for xo(1).
8469 xo_explicit_transition (xo_handle_t *xop, xo_state_t new_state,
8470 const char *name, xo_xof_flags_t flags)
8472 xo_xsf_flags_t xsf_flags;
8474 xop = xo_default(xop);
8476 switch (new_state) {
8479 xo_do_open_list(xop, flags, name);
8482 case XSS_OPEN_INSTANCE:
8483 xo_do_open_instance(xop, flags, name);
8486 case XSS_CLOSE_INSTANCE:
8487 xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE,
8488 xo_stack_flags(flags));
8489 xo_stack_set_flags(xop);
8490 xo_do_close_instance(xop, name);
8493 case XSS_CLOSE_LIST:
8494 xsf_flags = XOF_ISSET(xop, XOF_NOT_FIRST) ? XSF_NOT_FIRST : 0;
8496 xo_depth_change(xop, name, 1, 1, XSS_OPEN_LIST,
8497 XSF_LIST | xsf_flags | xo_stack_flags(flags));
8498 xo_do_close_list(xop, name);