2016-03-16 23:06:34 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014-2015, Juniper Networks, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
* This SOFTWARE is licensed under the LICENSE provided in the
|
|
|
|
* ../Copyright file. By downloading, installing, copying, or otherwise
|
|
|
|
* using the SOFTWARE, you agree to be bound by the terms of that
|
|
|
|
* LICENSE.
|
|
|
|
* Phil Shafer, July 2014
|
|
|
|
*
|
|
|
|
* This is the implementation of libxo, the formatting library that
|
|
|
|
* generates multiple styles of output from a single code path.
|
|
|
|
* Command line utilities can have their normal text output while
|
|
|
|
* automation tools can see XML or JSON output, and web tools can use
|
|
|
|
* HTML output that encodes the text output annotated with additional
|
|
|
|
* information. Specialized encoders can be built that allow custom
|
|
|
|
* encoding including binary ones like CBOR, thrift, protobufs, etc.
|
|
|
|
*
|
|
|
|
* Full documentation is available in ./doc/libxo.txt or online at:
|
|
|
|
* http://juniper.github.io/libxo/libxo-manual.html
|
|
|
|
*
|
|
|
|
* For first time readers, the core bits of code to start looking at are:
|
|
|
|
* - xo_do_emit() -- the central function of the library
|
|
|
|
* - xo_do_format_field() -- handles formatting a single field
|
|
|
|
* - xo_transiton() -- the state machine that keeps things sane
|
|
|
|
* and of course the "xo_handle_t" data structure, which carries all
|
|
|
|
* configuration and state.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <wchar.h>
|
|
|
|
#include <locale.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <wctype.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
|
|
|
|
#include "xo_config.h"
|
|
|
|
#include "xo.h"
|
|
|
|
#include "xo_encoder.h"
|
|
|
|
#include "xo_buf.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We ask wcwidth() to do an impossible job, really. It's supposed to
|
|
|
|
* need to tell us the number of columns consumed to display a unicode
|
|
|
|
* character. It returns that number without any sort of context, but
|
|
|
|
* we know they are characters whose glyph differs based on placement
|
|
|
|
* (end of word, middle of word, etc) and many that affect characters
|
|
|
|
* previously emitted. Without content, it can't hope to tell us.
|
|
|
|
* But it's the only standard tool we've got, so we use it. We would
|
|
|
|
* use wcswidth() but it typically just loops thru adding the results
|
|
|
|
* of wcwidth() calls in an entirely unhelpful way.
|
|
|
|
*
|
|
|
|
* Even then, there are many poor implementations (macosx), so we have
|
|
|
|
* to carry our own. We could have configure.ac test this (with
|
|
|
|
* something like 'assert(wcwidth(0x200d) == 0)'), but it would have
|
|
|
|
* to run a binary, which breaks cross-compilation. Hmm... I could
|
|
|
|
* run this test at init time and make a warning for our dear user.
|
|
|
|
*
|
|
|
|
* Anyhow, it remains a best-effort sort of thing. And it's all made
|
|
|
|
* more hopeless because we assume the display code doing the rendering is
|
|
|
|
* playing by the same rules we are. If it display 0x200d as a square
|
|
|
|
* box or a funky question mark, the output will be hosed.
|
|
|
|
*/
|
|
|
|
#ifdef LIBXO_WCWIDTH
|
|
|
|
#include "xo_wcwidth.h"
|
|
|
|
#else /* LIBXO_WCWIDTH */
|
|
|
|
#define xo_wcwidth(_x) wcwidth(_x)
|
|
|
|
#endif /* LIBXO_WCWIDTH */
|
|
|
|
|
|
|
|
#ifdef HAVE_STDIO_EXT_H
|
|
|
|
#include <stdio_ext.h>
|
|
|
|
#endif /* HAVE_STDIO_EXT_H */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* humanize_number is a great function, unless you don't have it. So
|
|
|
|
* we carry one in our pocket.
|
|
|
|
*/
|
|
|
|
#ifdef HAVE_HUMANIZE_NUMBER
|
|
|
|
#include <libutil.h>
|
|
|
|
#define xo_humanize_number humanize_number
|
|
|
|
#else /* HAVE_HUMANIZE_NUMBER */
|
|
|
|
#include "xo_humanize.h"
|
|
|
|
#endif /* HAVE_HUMANIZE_NUMBER */
|
|
|
|
|
|
|
|
#ifdef HAVE_GETTEXT
|
|
|
|
#include <libintl.h>
|
|
|
|
#endif /* HAVE_GETTEXT */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Three styles of specifying thread-local variables are supported.
|
|
|
|
* configure.ac has the brains to run each possibility thru the
|
|
|
|
* compiler and see what works; we are left to define the THREAD_LOCAL
|
|
|
|
* macro to the right value. Most toolchains (clang, gcc) use
|
|
|
|
* "before", but some (borland) use "after" and I've heard of some
|
|
|
|
* (ms) that use __declspec. Any others out there?
|
|
|
|
*/
|
|
|
|
#define THREAD_LOCAL_before 1
|
|
|
|
#define THREAD_LOCAL_after 2
|
|
|
|
#define THREAD_LOCAL_declspec 3
|
|
|
|
|
|
|
|
#ifndef HAVE_THREAD_LOCAL
|
|
|
|
#define THREAD_LOCAL(_x) _x
|
|
|
|
#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
|
|
|
|
#define THREAD_LOCAL(_x) __thread _x
|
|
|
|
#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
|
|
|
|
#define THREAD_LOCAL(_x) _x __thread
|
|
|
|
#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
|
|
|
|
#define THREAD_LOCAL(_x) __declspec(_x)
|
|
|
|
#else
|
|
|
|
#error unknown thread-local setting
|
|
|
|
#endif /* HAVE_THREADS_H */
|
|
|
|
|
|
|
|
const char xo_version[] = LIBXO_VERSION;
|
|
|
|
const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
|
|
|
|
|
|
|
|
#ifndef UNUSED
|
|
|
|
#define UNUSED __attribute__ ((__unused__))
|
|
|
|
#endif /* UNUSED */
|
|
|
|
|
|
|
|
#define XO_INDENT_BY 2 /* Amount to indent when pretty printing */
|
|
|
|
#define XO_DEPTH 128 /* Default stack depth */
|
|
|
|
#define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
|
|
|
|
|
|
|
|
#define XO_FAILURE_NAME "failure"
|
|
|
|
|
|
|
|
/* Flags for the stack frame */
|
|
|
|
typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
|
|
|
|
#define XSF_NOT_FIRST (1<<0) /* Not the first element */
|
|
|
|
#define XSF_LIST (1<<1) /* Frame is a list */
|
|
|
|
#define XSF_INSTANCE (1<<2) /* Frame is an instance */
|
|
|
|
#define XSF_DTRT (1<<3) /* Save the name for DTRT mode */
|
|
|
|
|
|
|
|
#define XSF_CONTENT (1<<4) /* Some content has been emitted */
|
|
|
|
#define XSF_EMIT (1<<5) /* Some field has been emitted */
|
|
|
|
#define XSF_EMIT_KEY (1<<6) /* A key has been emitted */
|
|
|
|
#define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
|
|
|
|
|
|
|
|
/* These are the flags we propagate between markers and their parents */
|
|
|
|
#define XSF_MARKER_FLAGS \
|
|
|
|
(XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A word about states: We use a finite state machine (FMS) approach
|
|
|
|
* to help remove fragility from the caller's code. Instead of
|
|
|
|
* requiring a specific order of calls, we'll allow the caller more
|
|
|
|
* flexibility and make the library responsible for recovering from
|
|
|
|
* missed steps. The goal is that the library should not be capable
|
|
|
|
* of emitting invalid xml or json, but the developer shouldn't need
|
|
|
|
* to know or understand all the details about these encodings.
|
|
|
|
*
|
|
|
|
* You can think of states as either states or events, since they
|
|
|
|
* function rather like both. None of the XO_CLOSE_* events will
|
|
|
|
* persist as states, since the matching stack frame will be popped.
|
|
|
|
* Same is true of XSS_EMIT, which is an event that asks us to
|
|
|
|
* prep for emitting output fields.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Stack frame states */
|
|
|
|
typedef unsigned xo_state_t;
|
|
|
|
#define XSS_INIT 0 /* Initial stack state */
|
|
|
|
#define XSS_OPEN_CONTAINER 1
|
|
|
|
#define XSS_CLOSE_CONTAINER 2
|
|
|
|
#define XSS_OPEN_LIST 3
|
|
|
|
#define XSS_CLOSE_LIST 4
|
|
|
|
#define XSS_OPEN_INSTANCE 5
|
|
|
|
#define XSS_CLOSE_INSTANCE 6
|
|
|
|
#define XSS_OPEN_LEAF_LIST 7
|
|
|
|
#define XSS_CLOSE_LEAF_LIST 8
|
|
|
|
#define XSS_DISCARDING 9 /* Discarding data until recovered */
|
|
|
|
#define XSS_MARKER 10 /* xo_open_marker's marker */
|
|
|
|
#define XSS_EMIT 11 /* xo_emit has a leaf field */
|
|
|
|
#define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */
|
|
|
|
#define XSS_FINISH 13 /* xo_finish was called */
|
|
|
|
|
|
|
|
#define XSS_MAX 13
|
|
|
|
|
|
|
|
#define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xo_stack_t: As we open and close containers and levels, we
|
|
|
|
* create a stack of frames to track them. This is needed for
|
|
|
|
* XOF_WARN and XOF_XPATH.
|
|
|
|
*/
|
|
|
|
typedef struct xo_stack_s {
|
|
|
|
xo_xsf_flags_t xs_flags; /* Flags for this frame */
|
|
|
|
xo_state_t xs_state; /* State for this stack frame */
|
|
|
|
char *xs_name; /* Name (for XPath value) */
|
|
|
|
char *xs_keys; /* XPath predicate for any key fields */
|
|
|
|
} xo_stack_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* libxo supports colors and effects, for those who like them.
|
|
|
|
* XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
|
|
|
|
* ("effects") are bits since we need to maintain state.
|
|
|
|
*/
|
|
|
|
#define XO_COL_DEFAULT 0
|
|
|
|
#define XO_COL_BLACK 1
|
|
|
|
#define XO_COL_RED 2
|
|
|
|
#define XO_COL_GREEN 3
|
|
|
|
#define XO_COL_YELLOW 4
|
|
|
|
#define XO_COL_BLUE 5
|
|
|
|
#define XO_COL_MAGENTA 6
|
|
|
|
#define XO_COL_CYAN 7
|
|
|
|
#define XO_COL_WHITE 8
|
|
|
|
|
|
|
|
#define XO_NUM_COLORS 9
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Yes, there's no blink. We're civilized. We like users. Blink
|
|
|
|
* isn't something one does to someone you like. Friends don't let
|
|
|
|
* friends use blink. On friends. You know what I mean. Blink is
|
|
|
|
* like, well, it's like bursting into show tunes at a funeral. It's
|
|
|
|
* just not done. Not something anyone wants. And on those rare
|
|
|
|
* instances where it might actually be appropriate, it's still wrong,
|
|
|
|
* since it's likely done by the wrong person for the wrong reason.
|
|
|
|
* Just like blink. And if I implemented blink, I'd be like a funeral
|
|
|
|
* director who adds "Would you like us to burst into show tunes?" on
|
|
|
|
* the list of questions asked while making funeral arrangements.
|
|
|
|
* It's formalizing wrongness in the wrong way. And we're just too
|
|
|
|
* civilized to do that. Hhhmph!
|
|
|
|
*/
|
|
|
|
#define XO_EFF_RESET (1<<0)
|
|
|
|
#define XO_EFF_NORMAL (1<<1)
|
|
|
|
#define XO_EFF_BOLD (1<<2)
|
|
|
|
#define XO_EFF_UNDERLINE (1<<3)
|
|
|
|
#define XO_EFF_INVERSE (1<<4)
|
|
|
|
|
|
|
|
#define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
|
|
|
|
|
|
|
|
typedef uint8_t xo_effect_t;
|
|
|
|
typedef uint8_t xo_color_t;
|
|
|
|
typedef struct xo_colors_s {
|
|
|
|
xo_effect_t xoc_effects; /* Current effect set */
|
|
|
|
xo_color_t xoc_col_fg; /* Foreground color */
|
|
|
|
xo_color_t xoc_col_bg; /* Background color */
|
|
|
|
} xo_colors_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xo_handle_t: this is the principle data structure for libxo.
|
|
|
|
* It's used as a store for state, options, content, and all manor
|
|
|
|
* of other information.
|
|
|
|
*/
|
|
|
|
struct xo_handle_s {
|
|
|
|
xo_xof_flags_t xo_flags; /* Flags (XOF_*) from the user*/
|
|
|
|
xo_xof_flags_t xo_iflags; /* Internal flags (XOIF_*) */
|
|
|
|
xo_style_t xo_style; /* XO_STYLE_* value */
|
|
|
|
unsigned short xo_indent; /* Indent level (if pretty) */
|
|
|
|
unsigned short xo_indent_by; /* Indent amount (tab stop) */
|
|
|
|
xo_write_func_t xo_write; /* Write callback */
|
|
|
|
xo_close_func_t xo_close; /* Close callback */
|
|
|
|
xo_flush_func_t xo_flush; /* Flush callback */
|
|
|
|
xo_formatter_t xo_formatter; /* Custom formating function */
|
|
|
|
xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
|
|
|
|
void *xo_opaque; /* Opaque data for write function */
|
|
|
|
xo_buffer_t xo_data; /* Output data */
|
|
|
|
xo_buffer_t xo_fmt; /* Work area for building format strings */
|
|
|
|
xo_buffer_t xo_attrs; /* Work area for building XML attributes */
|
|
|
|
xo_buffer_t xo_predicate; /* Work area for building XPath predicates */
|
|
|
|
xo_stack_t *xo_stack; /* Stack pointer */
|
|
|
|
int xo_depth; /* Depth of stack */
|
|
|
|
int xo_stack_size; /* Size of the stack */
|
|
|
|
xo_info_t *xo_info; /* Info fields for all elements */
|
|
|
|
int xo_info_count; /* Number of info entries */
|
|
|
|
va_list xo_vap; /* Variable arguments (stdargs) */
|
|
|
|
char *xo_leading_xpath; /* A leading XPath expression */
|
|
|
|
mbstate_t xo_mbstate; /* Multi-byte character conversion state */
|
|
|
|
unsigned xo_anchor_offset; /* Start of anchored text */
|
|
|
|
unsigned xo_anchor_columns; /* Number of columns since the start anchor */
|
|
|
|
int xo_anchor_min_width; /* Desired width of anchored text */
|
|
|
|
unsigned xo_units_offset; /* Start of units insertion point */
|
|
|
|
unsigned xo_columns; /* Columns emitted during this xo_emit call */
|
|
|
|
uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
|
|
|
|
uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
|
|
|
|
xo_colors_t xo_colors; /* Current color and effect values */
|
|
|
|
xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */
|
|
|
|
char *xo_version; /* Version string */
|
|
|
|
int xo_errno; /* Saved errno for "%m" */
|
|
|
|
char *xo_gt_domain; /* Gettext domain, suitable for dgettext(3) */
|
|
|
|
xo_encoder_func_t xo_encoder; /* Encoding function */
|
|
|
|
void *xo_private; /* Private data for external encoders */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Flag operations */
|
|
|
|
#define XOF_BIT_ISSET(_flag, _bit) (((_flag) & (_bit)) ? 1 : 0)
|
|
|
|
#define XOF_BIT_SET(_flag, _bit) do { (_flag) |= (_bit); } while (0)
|
|
|
|
#define XOF_BIT_CLEAR(_flag, _bit) do { (_flag) &= ~(_bit); } while (0)
|
|
|
|
|
|
|
|
#define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
|
|
|
|
#define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
|
|
|
|
#define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
|
|
|
|
|
|
|
|
#define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
|
|
|
|
#define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
|
|
|
|
#define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
|
|
|
|
|
|
|
|
/* Internal flags */
|
|
|
|
#define XOIF_REORDER XOF_BIT(0) /* Reordering fields; record field info */
|
|
|
|
#define XOIF_DIV_OPEN XOF_BIT(1) /* A <div> is open */
|
|
|
|
#define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
|
|
|
|
#define XOIF_ANCHOR XOF_BIT(3) /* An anchor is in place */
|
|
|
|
|
|
|
|
#define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
|
|
|
|
#define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
|
|
|
|
|
|
|
|
/* Flags for formatting functions */
|
|
|
|
typedef unsigned long xo_xff_flags_t;
|
|
|
|
#define XFF_COLON (1<<0) /* Append a ":" */
|
|
|
|
#define XFF_COMMA (1<<1) /* Append a "," iff there's more output */
|
|
|
|
#define XFF_WS (1<<2) /* Append a blank */
|
|
|
|
#define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding styles (XML, JSON) */
|
|
|
|
|
|
|
|
#define XFF_QUOTE (1<<4) /* Force quotes */
|
|
|
|
#define XFF_NOQUOTE (1<<5) /* Force no quotes */
|
|
|
|
#define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display styles (text, html) */
|
|
|
|
#define XFF_KEY (1<<7) /* Field is a key (for XPath) */
|
|
|
|
|
|
|
|
#define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */
|
|
|
|
#define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */
|
|
|
|
#define XFF_BLANK_LINE (1<<10) /* Emit a blank line */
|
|
|
|
#define XFF_NO_OUTPUT (1<<11) /* Do not make any output */
|
|
|
|
|
|
|
|
#define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */
|
|
|
|
#define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */
|
|
|
|
#define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */
|
|
|
|
#define XFF_HUMANIZE (1<<15) /* Humanize the value (for display styles) */
|
|
|
|
|
|
|
|
#define XFF_HN_SPACE (1<<16) /* Humanize: put space before suffix */
|
|
|
|
#define XFF_HN_DECIMAL (1<<17) /* Humanize: add one decimal place if <10 */
|
|
|
|
#define XFF_HN_1000 (1<<18) /* Humanize: use 1000, not 1024 */
|
|
|
|
#define XFF_GT_FIELD (1<<19) /* Call gettext() on a field */
|
|
|
|
|
|
|
|
#define XFF_GT_PLURAL (1<<20) /* Call dngettext to find plural form */
|
|
|
|
|
|
|
|
/* Flags to turn off when we don't want i18n processing */
|
|
|
|
#define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Normal printf has width and precision, which for strings operate as
|
|
|
|
* min and max number of columns. But this depends on the idea that
|
|
|
|
* one byte means one column, which UTF-8 and multi-byte characters
|
|
|
|
* pitches on its ear. It may take 40 bytes of data to populate 14
|
|
|
|
* columns, but we can't go off looking at 40 bytes of data without the
|
|
|
|
* caller's permission for fear/knowledge that we'll generate core files.
|
|
|
|
*
|
|
|
|
* So we make three values, distinguishing between "max column" and
|
|
|
|
* "number of bytes that we will inspect inspect safely" We call the
|
|
|
|
* later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
|
|
|
|
*
|
|
|
|
* Under the "first do no harm" theory, we default "max" to "size".
|
|
|
|
* This is a reasonable assumption for folks that don't grok the
|
|
|
|
* MBS/WCS/UTF-8 world, and while it will be annoying, it will never
|
|
|
|
* be evil.
|
|
|
|
*
|
|
|
|
* For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
|
|
|
|
* columns of output, but will never look at more than 14 bytes of the
|
|
|
|
* input buffer. This is mostly compatible with printf and caller's
|
|
|
|
* expectations.
|
|
|
|
*
|
|
|
|
* In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
|
|
|
|
* many bytes (or until a NUL is seen) are needed to fill 14 columns
|
|
|
|
* of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
|
|
|
|
* to xx bytes (or until a NUL is seen) in order to fill 14 columns
|
|
|
|
* of output.
|
|
|
|
*
|
|
|
|
* It's fairly amazing how a good idea (handle all languages of the
|
|
|
|
* world) blows such a big hole in the bottom of the fairly weak boat
|
|
|
|
* that is C string handling. The simplicity and completenesss are
|
|
|
|
* sunk in ways we haven't even begun to understand.
|
|
|
|
*/
|
|
|
|
#define XF_WIDTH_MIN 0 /* Minimal width */
|
|
|
|
#define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */
|
|
|
|
#define XF_WIDTH_MAX 2 /* Maximum width */
|
|
|
|
#define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */
|
|
|
|
|
|
|
|
/* Input and output string encodings */
|
|
|
|
#define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */
|
|
|
|
#define XF_ENC_UTF8 2 /* UTF-8 */
|
|
|
|
#define XF_ENC_LOCALE 3 /* Current locale */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A place to parse printf-style format flags for each field
|
|
|
|
*/
|
|
|
|
typedef struct xo_format_s {
|
|
|
|
unsigned char xf_fc; /* Format character */
|
|
|
|
unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */
|
|
|
|
unsigned char xf_skip; /* Skip this field */
|
|
|
|
unsigned char xf_lflag; /* 'l' (long) */
|
|
|
|
unsigned char xf_hflag;; /* 'h' (half) */
|
|
|
|
unsigned char xf_jflag; /* 'j' (intmax_t) */
|
|
|
|
unsigned char xf_tflag; /* 't' (ptrdiff_t) */
|
|
|
|
unsigned char xf_zflag; /* 'z' (size_t) */
|
|
|
|
unsigned char xf_qflag; /* 'q' (quad_t) */
|
|
|
|
unsigned char xf_seen_minus; /* Seen a minus */
|
|
|
|
int xf_leading_zero; /* Seen a leading zero (zero fill) */
|
|
|
|
unsigned xf_dots; /* Seen one or more '.'s */
|
|
|
|
int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
|
|
|
|
unsigned xf_stars; /* Seen one or more '*'s */
|
|
|
|
unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
|
|
|
|
} xo_format_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This structure represents the parsed field information, suitable for
|
|
|
|
* processing by xo_do_emit and anything else that needs to parse fields.
|
|
|
|
* Note that all pointers point to the main format string.
|
|
|
|
*
|
|
|
|
* XXX This is a first step toward compilable or cachable format
|
|
|
|
* strings. We can also cache the results of dgettext when no format
|
|
|
|
* is used, assuming the 'p' modifier has _not_ been set.
|
|
|
|
*/
|
|
|
|
typedef struct xo_field_info_s {
|
|
|
|
xo_xff_flags_t xfi_flags; /* Flags for this field */
|
|
|
|
unsigned xfi_ftype; /* Field type, as character (e.g. 'V') */
|
|
|
|
const char *xfi_start; /* Start of field in the format string */
|
|
|
|
const char *xfi_content; /* Field's content */
|
|
|
|
const char *xfi_format; /* Field's Format */
|
|
|
|
const char *xfi_encoding; /* Field's encoding format */
|
|
|
|
const char *xfi_next; /* Next character in format string */
|
|
|
|
unsigned xfi_len; /* Length of field */
|
|
|
|
unsigned xfi_clen; /* Content length */
|
|
|
|
unsigned xfi_flen; /* Format length */
|
|
|
|
unsigned xfi_elen; /* Encoding length */
|
|
|
|
unsigned xfi_fnum; /* Field number (if used; 0 otherwise) */
|
|
|
|
unsigned xfi_renum; /* Reordered number (0 == no renumbering) */
|
|
|
|
} xo_field_info_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We keep a 'default' handle to allow callers to avoid having to
|
|
|
|
* allocate one. Passing NULL to any of our functions will use
|
|
|
|
* this default handle. Most functions have a variant that doesn't
|
|
|
|
* require a handle at all, since most output is to stdout, which
|
|
|
|
* the default handle handles handily.
|
|
|
|
*/
|
|
|
|
static THREAD_LOCAL(xo_handle_t) xo_default_handle;
|
|
|
|
static THREAD_LOCAL(int) xo_default_inited;
|
|
|
|
static int xo_locale_inited;
|
|
|
|
static const char *xo_program;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* To allow libxo to be used in diverse environment, we allow the
|
|
|
|
* caller to give callbacks for memory allocation.
|
|
|
|
*/
|
|
|
|
xo_realloc_func_t xo_realloc = realloc;
|
|
|
|
xo_free_func_t xo_free = free;
|
|
|
|
|
|
|
|
/* Forward declarations */
|
|
|
|
static void
|
|
|
|
xo_failure (xo_handle_t *xop, const char *fmt, ...);
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
|
|
|
|
xo_state_t new_state);
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
|
|
|
|
const char *name, int nlen,
|
|
|
|
const char *value, int vlen,
|
|
|
|
const char *encoding, int elen);
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_anchor_clear (xo_handle_t *xop);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xo_style is used to retrieve the current style. When we're built
|
|
|
|
* for "text only" mode, we use this function to drive the removal
|
|
|
|
* of most of the code in libxo. We return a constant and the compiler
|
|
|
|
* happily removes the non-text code that is not longer executed. This
|
|
|
|
* trims our code nicely without needing to trampel perfectly readable
|
|
|
|
* code with ifdefs.
|
|
|
|
*/
|
|
|
|
static inline xo_style_t
|
|
|
|
xo_style (xo_handle_t *xop UNUSED)
|
|
|
|
{
|
|
|
|
#ifdef LIBXO_TEXT_ONLY
|
|
|
|
return XO_STYLE_TEXT;
|
|
|
|
#else /* LIBXO_TEXT_ONLY */
|
|
|
|
return xop->xo_style;
|
|
|
|
#endif /* LIBXO_TEXT_ONLY */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Callback to write data to a FILE pointer
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_write_to_file (void *opaque, const char *data)
|
|
|
|
{
|
|
|
|
FILE *fp = (FILE *) opaque;
|
|
|
|
|
|
|
|
return fprintf(fp, "%s", data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Callback to close a file
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_close_file (void *opaque)
|
|
|
|
{
|
|
|
|
FILE *fp = (FILE *) opaque;
|
|
|
|
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Callback to flush a FILE pointer
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_flush_file (void *opaque)
|
|
|
|
{
|
|
|
|
FILE *fp = (FILE *) opaque;
|
|
|
|
|
|
|
|
return fflush(fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use a rotating stock of buffers to make a printable string
|
|
|
|
*/
|
|
|
|
#define XO_NUMBUFS 8
|
|
|
|
#define XO_SMBUFSZ 128
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
xo_printable (const char *str)
|
|
|
|
{
|
|
|
|
static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
|
|
|
|
static THREAD_LOCAL(int) bufnum = 0;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
if (++bufnum == XO_NUMBUFS)
|
|
|
|
bufnum = 0;
|
|
|
|
|
|
|
|
char *res = bufset[bufnum], *cp, *ep;
|
|
|
|
|
|
|
|
for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
|
|
|
|
if (*str == '\n') {
|
|
|
|
*cp++ = '\\';
|
|
|
|
*cp = 'n';
|
|
|
|
} else if (*str == '\r') {
|
|
|
|
*cp++ = '\\';
|
|
|
|
*cp = 'r';
|
|
|
|
} else if (*str == '\"') {
|
|
|
|
*cp++ = '\\';
|
|
|
|
*cp = '"';
|
|
|
|
} else
|
|
|
|
*cp = *str;
|
|
|
|
}
|
|
|
|
|
|
|
|
*cp = '\0';
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_depth_check (xo_handle_t *xop, int depth)
|
|
|
|
{
|
|
|
|
xo_stack_t *xsp;
|
|
|
|
|
|
|
|
if (depth >= xop->xo_stack_size) {
|
|
|
|
depth += XO_DEPTH; /* Extra room */
|
|
|
|
|
|
|
|
xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
|
|
|
|
if (xsp == NULL) {
|
|
|
|
xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int count = depth - xop->xo_stack_size;
|
|
|
|
|
|
|
|
bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
|
|
|
|
xop->xo_stack_size = depth;
|
|
|
|
xop->xo_stack = xsp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_no_setlocale (void)
|
|
|
|
{
|
|
|
|
xo_locale_inited = 1; /* Skip initialization */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We need to decide if stdout is line buffered (_IOLBF). Lacking a
|
|
|
|
* standard way to decide this (e.g. getlinebuf()), we have configure
|
|
|
|
* look to find __flbf, which glibc supported. If not, we'll rely on
|
|
|
|
* isatty, with the assumption that terminals are the only thing
|
|
|
|
* that's line buffered. We _could_ test for "steam._flags & _IOLBF",
|
|
|
|
* which is all __flbf does, but that's even tackier. Like a
|
|
|
|
* bedazzled Elvis outfit on an ugly lap dog sort of tacky. Not
|
|
|
|
* something we're willing to do.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_is_line_buffered (FILE *stream)
|
|
|
|
{
|
|
|
|
#if HAVE___FLBF
|
|
|
|
if (__flbf(stream))
|
|
|
|
return 1;
|
|
|
|
#else /* HAVE___FLBF */
|
|
|
|
if (isatty(fileno(stream)))
|
|
|
|
return 1;
|
|
|
|
#endif /* HAVE___FLBF */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize an xo_handle_t, using both static defaults and
|
|
|
|
* the global settings from the LIBXO_OPTIONS environment
|
|
|
|
* variable.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_init_handle (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop->xo_opaque = stdout;
|
|
|
|
xop->xo_write = xo_write_to_file;
|
|
|
|
xop->xo_flush = xo_flush_file;
|
|
|
|
|
|
|
|
if (xo_is_line_buffered(stdout))
|
|
|
|
XOF_SET(xop, XOF_FLUSH_LINE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only want to do color output on terminals, but we only want
|
|
|
|
* to do this if the user has asked for color.
|
|
|
|
*/
|
|
|
|
if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
|
|
|
|
XOF_SET(xop, XOF_COLOR);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We need to initialize the locale, which isn't really pretty.
|
|
|
|
* Libraries should depend on their caller to set up the
|
|
|
|
* environment. But we really can't count on the caller to do
|
|
|
|
* this, because well, they won't. Trust me.
|
|
|
|
*/
|
|
|
|
if (!xo_locale_inited) {
|
|
|
|
xo_locale_inited = 1; /* Only do this once */
|
|
|
|
|
|
|
|
const char *cp = getenv("LC_CTYPE");
|
|
|
|
if (cp == NULL)
|
|
|
|
cp = getenv("LANG");
|
|
|
|
if (cp == NULL)
|
|
|
|
cp = getenv("LC_ALL");
|
|
|
|
if (cp == NULL)
|
|
|
|
cp = "C"; /* Default for C programs */
|
|
|
|
(void) setlocale(LC_CTYPE, cp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize only the xo_buffers we know we'll need; the others
|
|
|
|
* can be allocated as needed.
|
|
|
|
*/
|
|
|
|
xo_buf_init(&xop->xo_data);
|
|
|
|
xo_buf_init(&xop->xo_fmt);
|
|
|
|
|
|
|
|
if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
|
|
|
|
return;
|
|
|
|
XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
|
|
|
|
|
|
|
|
xop->xo_indent_by = XO_INDENT_BY;
|
|
|
|
xo_depth_check(xop, XO_DEPTH);
|
|
|
|
|
|
|
|
#if !defined(NO_LIBXO_OPTIONS)
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_ENV)) {
|
|
|
|
char *env = getenv("LIBXO_OPTIONS");
|
|
|
|
if (env)
|
|
|
|
xo_set_options(xop, env);
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif /* NO_GETENV */
|
|
|
|
|
|
|
|
XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the default handle.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_default_init (void)
|
|
|
|
{
|
|
|
|
xo_handle_t *xop = &xo_default_handle;
|
|
|
|
|
|
|
|
xo_init_handle(xop);
|
|
|
|
|
|
|
|
xo_default_inited = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cheap convenience function to return either the argument, or
|
|
|
|
* the internal handle, after it has been initialized. The usage
|
|
|
|
* is:
|
|
|
|
* xop = xo_default(xop);
|
|
|
|
*/
|
|
|
|
static xo_handle_t *
|
|
|
|
xo_default (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
if (xop == NULL) {
|
|
|
|
if (xo_default_inited == 0)
|
|
|
|
xo_default_init();
|
|
|
|
xop = &xo_default_handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
return xop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the number of spaces we should be indenting. If
|
|
|
|
* we are pretty-printing, this is indent * indent_by.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_indent (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY)) {
|
|
|
|
rc = xop->xo_indent * xop->xo_indent_by;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
|
|
|
|
rc += xop->xo_indent_by;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (rc > 0) ? rc : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_buf_indent (xo_handle_t *xop, int indent)
|
|
|
|
{
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
if (indent <= 0)
|
|
|
|
indent = xo_indent(xop);
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, indent))
|
|
|
|
return;
|
|
|
|
|
|
|
|
memset(xbp->xb_curp, ' ', indent);
|
|
|
|
xbp->xb_curp += indent;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char xo_xml_amp[] = "&";
|
|
|
|
static char xo_xml_lt[] = "<";
|
|
|
|
static char xo_xml_gt[] = ">";
|
|
|
|
static char xo_xml_quot[] = """;
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
int slen;
|
|
|
|
unsigned delta = 0;
|
|
|
|
char *cp, *ep, *ip;
|
|
|
|
const char *sp;
|
|
|
|
int attr = (flags & XFF_ATTR);
|
|
|
|
|
|
|
|
for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
|
|
|
|
/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
|
|
|
|
if (*cp == '<')
|
|
|
|
delta += sizeof(xo_xml_lt) - 2;
|
|
|
|
else if (*cp == '>')
|
|
|
|
delta += sizeof(xo_xml_gt) - 2;
|
|
|
|
else if (*cp == '&')
|
|
|
|
delta += sizeof(xo_xml_amp) - 2;
|
|
|
|
else if (attr && *cp == '"')
|
|
|
|
delta += sizeof(xo_xml_quot) - 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (delta == 0) /* Nothing to escape; bail */
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ep = xbp->xb_curp;
|
|
|
|
cp = ep + len;
|
|
|
|
ip = cp + delta;
|
|
|
|
do {
|
|
|
|
cp -= 1;
|
|
|
|
ip -= 1;
|
|
|
|
|
|
|
|
if (*cp == '<')
|
|
|
|
sp = xo_xml_lt;
|
|
|
|
else if (*cp == '>')
|
|
|
|
sp = xo_xml_gt;
|
|
|
|
else if (*cp == '&')
|
|
|
|
sp = xo_xml_amp;
|
|
|
|
else if (attr && *cp == '"')
|
|
|
|
sp = xo_xml_quot;
|
|
|
|
else {
|
|
|
|
*ip = *cp;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
slen = strlen(sp);
|
|
|
|
ip -= slen - 1;
|
|
|
|
memcpy(ip, sp, slen);
|
|
|
|
|
|
|
|
} while (cp > ep && cp != ip);
|
|
|
|
|
|
|
|
return len + delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
|
|
|
|
{
|
|
|
|
unsigned delta = 0;
|
|
|
|
char *cp, *ep, *ip;
|
|
|
|
|
|
|
|
for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
|
|
|
|
if (*cp == '\\' || *cp == '"')
|
|
|
|
delta += 1;
|
|
|
|
else if (*cp == '\n' || *cp == '\r')
|
|
|
|
delta += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (delta == 0) /* Nothing to escape; bail */
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ep = xbp->xb_curp;
|
|
|
|
cp = ep + len;
|
|
|
|
ip = cp + delta;
|
|
|
|
do {
|
|
|
|
cp -= 1;
|
|
|
|
ip -= 1;
|
|
|
|
|
|
|
|
if (*cp == '\\' || *cp == '"') {
|
|
|
|
*ip-- = *cp;
|
|
|
|
*ip = '\\';
|
|
|
|
} else if (*cp == '\n') {
|
|
|
|
*ip-- = 'n';
|
|
|
|
*ip = '\\';
|
|
|
|
} else if (*cp == '\r') {
|
|
|
|
*ip-- = 'r';
|
|
|
|
*ip = '\\';
|
|
|
|
} else {
|
|
|
|
*ip = *cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (cp > ep && cp != ip);
|
|
|
|
|
|
|
|
return len + delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
|
|
|
|
* ; ']' MUST be escaped.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
|
|
|
|
{
|
|
|
|
unsigned delta = 0;
|
|
|
|
char *cp, *ep, *ip;
|
|
|
|
|
|
|
|
for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
|
|
|
|
if (*cp == '\\' || *cp == '"' || *cp == ']')
|
|
|
|
delta += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (delta == 0) /* Nothing to escape; bail */
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ep = xbp->xb_curp;
|
|
|
|
cp = ep + len;
|
|
|
|
ip = cp + delta;
|
|
|
|
do {
|
|
|
|
cp -= 1;
|
|
|
|
ip -= 1;
|
|
|
|
|
|
|
|
if (*cp == '\\' || *cp == '"' || *cp == ']') {
|
|
|
|
*ip-- = *cp;
|
|
|
|
*ip = '\\';
|
|
|
|
} else {
|
|
|
|
*ip = *cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (cp > ep && cp != ip);
|
|
|
|
|
|
|
|
return len + delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
const char *str, int len, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
if (!xo_buf_has_room(xbp, len))
|
|
|
|
return;
|
|
|
|
|
|
|
|
memcpy(xbp->xb_curp, str, len);
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
len = xo_escape_xml(xbp, len, flags);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
len = xo_escape_json(xbp, len, flags);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
len = xo_escape_sdparams(xbp, len, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xbp->xb_curp += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write the current contents of the data buffer using the handle's
|
|
|
|
* xo_write function.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_write (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
if (xbp->xb_curp != xbp->xb_bufp) {
|
|
|
|
xo_buf_append(xbp, "", 1); /* Append ending NUL */
|
|
|
|
xo_anchor_clear(xop);
|
|
|
|
if (xop->xo_write)
|
|
|
|
rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
|
|
|
|
xbp->xb_curp = xbp->xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Turn off the flags that don't survive across writes */
|
|
|
|
XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Format arguments into our buffer. If a custom formatter has been set,
|
|
|
|
* we use that to do the work; otherwise we vsnprintf().
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
va_list va_local;
|
|
|
|
int rc;
|
|
|
|
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
if (xop->xo_formatter)
|
|
|
|
rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
|
|
|
|
else
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
|
|
|
|
|
|
|
|
if (rc >= left) {
|
|
|
|
if (!xo_buf_has_room(xbp, rc)) {
|
|
|
|
va_end(va_local);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After we call vsnprintf(), the stage of vap is not defined.
|
|
|
|
* We need to copy it before we pass. Then we have to do our
|
|
|
|
* own logic below to move it along. This is because the
|
|
|
|
* implementation can have va_list be a pointer (bsd) or a
|
|
|
|
* structure (macosx) or anything in between.
|
|
|
|
*/
|
|
|
|
|
|
|
|
va_end(va_local); /* Reset vap to the start */
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
if (xop->xo_formatter)
|
|
|
|
rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
|
|
|
|
else
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
|
|
|
|
}
|
|
|
|
va_end(va_local);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print some data thru the handle.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
int rc;
|
|
|
|
va_list va_local;
|
|
|
|
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
|
|
|
|
|
|
|
|
if (rc >= left) {
|
|
|
|
if (!xo_buf_has_room(xbp, rc)) {
|
|
|
|
va_end(va_local);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(va_local); /* Reset vap to the start */
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(va_local);
|
|
|
|
|
|
|
|
if (rc > 0)
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_printf (xo_handle_t *xop, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
|
|
|
|
rc = xo_printf_v(xop, fmt, vap);
|
|
|
|
|
|
|
|
va_end(vap);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* These next few function are make The Essential UTF-8 Ginsu Knife.
|
|
|
|
* Identify an input and output character, and convert it.
|
|
|
|
*/
|
|
|
|
static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_is_utf8 (char ch)
|
|
|
|
{
|
|
|
|
return (ch & 0x80);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_utf8_to_wc_len (const char *buf)
|
|
|
|
{
|
|
|
|
unsigned b = (unsigned char) *buf;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if ((b & 0x80) == 0x0)
|
|
|
|
len = 1;
|
|
|
|
else if ((b & 0xe0) == 0xc0)
|
|
|
|
len = 2;
|
|
|
|
else if ((b & 0xf0) == 0xe0)
|
|
|
|
len = 3;
|
|
|
|
else if ((b & 0xf8) == 0xf0)
|
|
|
|
len = 4;
|
|
|
|
else if ((b & 0xfc) == 0xf8)
|
|
|
|
len = 5;
|
|
|
|
else if ((b & 0xfe) == 0xfc)
|
|
|
|
len = 6;
|
|
|
|
else
|
|
|
|
len = -1;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
|
|
|
|
{
|
|
|
|
|
|
|
|
unsigned b = (unsigned char) *buf;
|
|
|
|
int len, i;
|
|
|
|
|
|
|
|
len = xo_utf8_to_wc_len(buf);
|
|
|
|
if (len == -1) {
|
|
|
|
xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len > bufsiz) {
|
|
|
|
xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
|
|
|
|
b, len, bufsiz);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 2; i < len; i++) {
|
|
|
|
b = (unsigned char ) buf[i];
|
|
|
|
if ((b & 0xc0) != 0x80) {
|
|
|
|
xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build a wide character from the input buffer; the number of
|
|
|
|
* bits we pull off the first character is dependent on the length,
|
|
|
|
* but we put 6 bits off all other bytes.
|
|
|
|
*/
|
|
|
|
static wchar_t
|
|
|
|
xo_utf8_char (const char *buf, int len)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
wchar_t wc;
|
|
|
|
const unsigned char *cp = (const unsigned char *) buf;
|
|
|
|
|
|
|
|
wc = *cp & xo_utf8_bits[len];
|
|
|
|
for (i = 1; i < len; i++) {
|
|
|
|
wc <<= 6;
|
|
|
|
wc |= cp[i] & 0x3f;
|
|
|
|
if ((cp[i] & 0xc0) != 0x80)
|
|
|
|
return (wchar_t) -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return wc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine the number of bytes needed to encode a wide character.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_utf8_emit_len (wchar_t wc)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
|
|
|
|
len = 1;
|
|
|
|
else if ((wc & ((1<<11) - 1)) == wc)
|
|
|
|
len = 2;
|
|
|
|
else if ((wc & ((1<<16) - 1)) == wc)
|
|
|
|
len = 3;
|
|
|
|
else if ((wc & ((1<<21) - 1)) == wc)
|
|
|
|
len = 4;
|
|
|
|
else if ((wc & ((1<<26) - 1)) == wc)
|
|
|
|
len = 5;
|
|
|
|
else
|
|
|
|
len = 6;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_utf8_emit_char (char *buf, int len, wchar_t wc)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (len == 1) { /* Simple case */
|
|
|
|
buf[0] = wc & 0x7f;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = len - 1; i >= 0; i--) {
|
|
|
|
buf[i] = 0x80 | (wc & 0x3f);
|
|
|
|
wc >>= 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf[0] &= xo_utf8_bits[len];
|
|
|
|
buf[0] |= ~xo_utf8_bits[len] << 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
const char *ibuf, int ilen)
|
|
|
|
{
|
|
|
|
wchar_t wc;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build our wide character from the input buffer; the number of
|
|
|
|
* bits we pull off the first character is dependent on the length,
|
|
|
|
* but we put 6 bits off all other bytes.
|
|
|
|
*/
|
|
|
|
wc = xo_utf8_char(ibuf, ilen);
|
|
|
|
if (wc == (wchar_t) -1) {
|
|
|
|
xo_failure(xop, "invalid utf-8 byte sequence");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
|
|
|
|
if (!xo_buf_has_room(xbp, ilen))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
memcpy(xbp->xb_curp, ibuf, ilen);
|
|
|
|
xbp->xb_curp += ilen;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
|
|
|
|
len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
|
|
|
|
|
|
|
|
if (len <= 0) {
|
|
|
|
xo_failure(xop, "could not convert wide char: %lx",
|
|
|
|
(unsigned long) wc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
xbp->xb_curp += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return xo_wcwidth(wc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
const char *cp, int len)
|
|
|
|
{
|
|
|
|
const char *sp = cp, *ep = cp + len;
|
|
|
|
unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
|
|
|
|
int slen;
|
|
|
|
int cols = 0;
|
|
|
|
|
|
|
|
for ( ; cp < ep; cp++) {
|
|
|
|
if (!xo_is_utf8(*cp)) {
|
|
|
|
cols += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We're looking at a non-ascii UTF-8 character.
|
|
|
|
* First we copy the previous data.
|
|
|
|
* Then we need find the length and validate it.
|
|
|
|
* Then we turn it into a wide string.
|
|
|
|
* Then we turn it into a localized string.
|
|
|
|
* Then we repeat. Isn't i18n fun?
|
|
|
|
*/
|
|
|
|
if (sp != cp)
|
|
|
|
xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
|
|
|
|
|
|
|
|
slen = xo_buf_utf8_len(xop, cp, ep - cp);
|
|
|
|
if (slen <= 0) {
|
|
|
|
/* Bad data; back it all out */
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + save_off;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
|
|
|
|
|
|
|
|
/* Next time thru, we'll start at the next character */
|
|
|
|
cp += slen - 1;
|
|
|
|
sp = cp + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update column values */
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += cols;
|
|
|
|
|
|
|
|
/* Before we fall into the basic logic below, we need reset len */
|
|
|
|
len = ep - sp;
|
|
|
|
if (len != 0) /* Append trailing data */
|
|
|
|
xo_buf_append(xbp, sp, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Append the given string to the given buffer, without escaping or
|
|
|
|
* character set conversion. This is the straight copy to the data
|
|
|
|
* buffer with no fanciness.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_data_append (xo_handle_t *xop, const char *str, int len)
|
|
|
|
{
|
|
|
|
xo_buf_append(&xop->xo_data, str, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Append the given string to the given buffer
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_data_escape (xo_handle_t *xop, const char *str, int len)
|
|
|
|
{
|
|
|
|
xo_buf_escape(xop, &xop->xo_data, str, len, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate a warning. Normally, this is a text message written to
|
|
|
|
* standard error. If the XOF_WARN_XML flag is set, then we generate
|
|
|
|
* XMLified content on standard output.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
|
|
|
|
const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
if (check_warn && !XOF_ISSET(xop, XOF_WARN))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (fmt == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int len = strlen(fmt);
|
|
|
|
int plen = xo_program ? strlen(xo_program) : 0;
|
|
|
|
char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
|
|
|
|
|
|
|
|
if (plen) {
|
|
|
|
memcpy(newfmt, xo_program, plen);
|
|
|
|
newfmt[plen++] = ':';
|
|
|
|
newfmt[plen++] = ' ';
|
|
|
|
}
|
|
|
|
memcpy(newfmt + plen, fmt, len);
|
|
|
|
newfmt[len + plen] = '\0';
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_WARN_XML)) {
|
|
|
|
static char err_open[] = "<error>";
|
|
|
|
static char err_close[] = "</error>";
|
|
|
|
static char msg_open[] = "<message>";
|
|
|
|
static char msg_close[] = "</message>";
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
|
|
|
|
xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
|
|
|
|
|
|
|
|
va_list va_local;
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
|
|
|
|
if (rc >= left) {
|
|
|
|
if (!xo_buf_has_room(xbp, rc)) {
|
|
|
|
va_end(va_local);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(vap); /* Reset vap to the start */
|
|
|
|
va_copy(vap, va_local);
|
|
|
|
|
|
|
|
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
|
|
|
|
}
|
|
|
|
va_end(va_local);
|
|
|
|
|
|
|
|
rc = xo_escape_xml(xbp, rc, 1);
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
|
|
|
|
xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
|
|
|
|
xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
|
|
|
|
|
|
|
|
if (code >= 0) {
|
|
|
|
const char *msg = strerror(code);
|
|
|
|
if (msg) {
|
|
|
|
xo_buf_append(xbp, ": ", 2);
|
|
|
|
xo_buf_append(xbp, msg, strlen(msg));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
|
|
|
|
(void) xo_write(xop);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
vfprintf(stderr, newfmt, vap);
|
|
|
|
if (code >= 0) {
|
|
|
|
const char *msg = strerror(code);
|
|
|
|
if (msg)
|
|
|
|
fprintf(stderr, ": %s", msg);
|
|
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(xop, code, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_warn_c (int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, code, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_warn (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int code = errno;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, code, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_warnx (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, -1, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_err (int eval, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int code = errno;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, code, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_errx (int eval, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, -1, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_errc (int eval, int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(NULL, code, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate a warning. Normally, this is a text message written to
|
|
|
|
* standard error. If the XOF_WARN_XML flag is set, then we generate
|
|
|
|
* XMLified content on standard output.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
static char msg_open[] = "<message>";
|
|
|
|
static char msg_close[] = "</message>";
|
|
|
|
xo_buffer_t *xbp;
|
|
|
|
int rc;
|
|
|
|
va_list va_local;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (fmt == NULL || *fmt == '\0')
|
|
|
|
return;
|
|
|
|
|
|
|
|
int need_nl = (fmt[strlen(fmt) - 1] != '\n');
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
xbp = &xop->xo_data;
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_buf_indent(xop, xop->xo_indent_by);
|
|
|
|
xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
|
|
|
|
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
|
|
|
|
if (rc >= left) {
|
|
|
|
if (!xo_buf_has_room(xbp, rc)) {
|
|
|
|
va_end(va_local);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(vap); /* Reset vap to the start */
|
|
|
|
va_copy(vap, va_local);
|
|
|
|
|
|
|
|
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
|
|
|
|
}
|
|
|
|
va_end(va_local);
|
|
|
|
|
|
|
|
rc = xo_escape_xml(xbp, rc, 0);
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
|
|
|
|
if (need_nl && code > 0) {
|
|
|
|
const char *msg = strerror(code);
|
|
|
|
if (msg) {
|
|
|
|
xo_buf_append(xbp, ": ", 2);
|
|
|
|
xo_buf_append(xbp, msg, strlen(msg));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_nl)
|
|
|
|
xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
|
|
|
|
|
|
|
|
xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
|
|
|
|
|
|
|
|
(void) xo_write(xop);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ], *bp = buf, *cp;
|
|
|
|
int bufsiz = sizeof(buf);
|
|
|
|
int rc2;
|
|
|
|
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
|
|
|
|
rc = vsnprintf(bp, bufsiz, fmt, va_local);
|
|
|
|
if (rc > bufsiz) {
|
|
|
|
bufsiz = rc + BUFSIZ;
|
|
|
|
bp = alloca(bufsiz);
|
|
|
|
va_end(va_local);
|
|
|
|
va_copy(va_local, vap);
|
|
|
|
rc = vsnprintf(bp, bufsiz, fmt, va_local);
|
|
|
|
}
|
|
|
|
va_end(va_local);
|
|
|
|
cp = bp + rc;
|
|
|
|
|
|
|
|
if (need_nl) {
|
|
|
|
rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
|
|
|
|
(code > 0) ? ": " : "",
|
|
|
|
(code > 0) ? strerror(code) : "");
|
|
|
|
if (rc2 > 0)
|
|
|
|
rc += rc2;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
/* No means of representing messages */
|
|
|
|
return;
|
|
|
|
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
rc = xo_printf_v(xop, fmt, vap);
|
|
|
|
/*
|
|
|
|
* XXX need to handle UTF-8 widths
|
|
|
|
*/
|
|
|
|
if (rc > 0) {
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += rc;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_nl && code > 0) {
|
|
|
|
const char *msg = strerror(code);
|
|
|
|
if (msg) {
|
|
|
|
xo_printf(xop, ": %s", msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (need_nl)
|
|
|
|
xo_printf(xop, "\n");
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
(void) xo_flush_h(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_message_hcv(xop, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_message_c (int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_message_hcv(NULL, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_message_e (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int code = errno;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_message_hcv(NULL, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_message (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_message_hcv(NULL, 0, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_failure (xo_handle_t *xop, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
if (!XOF_ISSET(xop, XOF_WARN))
|
|
|
|
return;
|
|
|
|
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_warn_hcv(xop, -1, 1, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a handle for use by later libxo functions.
|
|
|
|
*
|
|
|
|
* Note: normal use of libxo does not require a distinct handle, since
|
|
|
|
* the default handle (used when NULL is passed) generates text on stdout.
|
|
|
|
*
|
|
|
|
* @style Style of output desired (XO_STYLE_* value)
|
|
|
|
* @flags Set of XOF_* flags in use with this handle
|
|
|
|
*/
|
|
|
|
xo_handle_t *
|
|
|
|
xo_create (xo_style_t style, xo_xof_flags_t flags)
|
|
|
|
{
|
|
|
|
xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
|
|
|
|
|
|
|
|
if (xop) {
|
|
|
|
bzero(xop, sizeof(*xop));
|
|
|
|
|
|
|
|
xop->xo_style = style;
|
|
|
|
XOF_SET(xop, flags);
|
|
|
|
xo_init_handle(xop);
|
|
|
|
xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */
|
|
|
|
}
|
|
|
|
|
|
|
|
return xop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a handle that will write to the given file. Use
|
|
|
|
* the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
|
|
|
|
* @fp FILE pointer to use
|
|
|
|
* @style Style of output desired (XO_STYLE_* value)
|
|
|
|
* @flags Set of XOF_* flags to use with this handle
|
|
|
|
*/
|
|
|
|
xo_handle_t *
|
|
|
|
xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
|
|
|
|
{
|
|
|
|
xo_handle_t *xop = xo_create(style, flags);
|
|
|
|
|
|
|
|
if (xop) {
|
|
|
|
xop->xo_opaque = fp;
|
|
|
|
xop->xo_write = xo_write_to_file;
|
|
|
|
xop->xo_close = xo_close_file;
|
|
|
|
xop->xo_flush = xo_flush_file;
|
|
|
|
}
|
|
|
|
|
|
|
|
return xop;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Release any resources held by the handle.
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_destroy (xo_handle_t *xop_arg)
|
|
|
|
{
|
|
|
|
xo_handle_t *xop = xo_default(xop_arg);
|
|
|
|
|
|
|
|
xo_flush_h(xop);
|
|
|
|
|
|
|
|
if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
|
|
|
|
xop->xo_close(xop->xo_opaque);
|
|
|
|
|
|
|
|
xo_free(xop->xo_stack);
|
|
|
|
xo_buf_cleanup(&xop->xo_data);
|
|
|
|
xo_buf_cleanup(&xop->xo_fmt);
|
|
|
|
xo_buf_cleanup(&xop->xo_predicate);
|
|
|
|
xo_buf_cleanup(&xop->xo_attrs);
|
|
|
|
xo_buf_cleanup(&xop->xo_color_buf);
|
|
|
|
|
|
|
|
if (xop->xo_version)
|
|
|
|
xo_free(xop->xo_version);
|
|
|
|
|
|
|
|
if (xop_arg == NULL) {
|
|
|
|
bzero(&xo_default_handle, sizeof(xo_default_handle));
|
|
|
|
xo_default_inited = 0;
|
|
|
|
} else
|
|
|
|
xo_free(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Record a new output style to use for the given handle (or default if
|
|
|
|
* handle is NULL). This output style will be used for any future output.
|
|
|
|
*
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
* @style new output style (XO_STYLE_*)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_style (xo_handle_t *xop, xo_style_t style)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
xop->xo_style = style;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_style_t
|
|
|
|
xo_get_style (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
return xo_style(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_name_to_style (const char *name)
|
|
|
|
{
|
|
|
|
if (strcmp(name, "xml") == 0)
|
|
|
|
return XO_STYLE_XML;
|
|
|
|
else if (strcmp(name, "json") == 0)
|
|
|
|
return XO_STYLE_JSON;
|
|
|
|
else if (strcmp(name, "encoder") == 0)
|
|
|
|
return XO_STYLE_ENCODER;
|
|
|
|
else if (strcmp(name, "text") == 0)
|
|
|
|
return XO_STYLE_TEXT;
|
|
|
|
else if (strcmp(name, "html") == 0)
|
|
|
|
return XO_STYLE_HTML;
|
|
|
|
else if (strcmp(name, "sdparams") == 0)
|
|
|
|
return XO_STYLE_SDPARAMS;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Indicate if the style is an "encoding" one as opposed to a "display" one.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_style_is_encoding (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
if (xo_style(xop) == XO_STYLE_JSON
|
|
|
|
|| xo_style(xop) == XO_STYLE_XML
|
|
|
|
|| xo_style(xop) == XO_STYLE_SDPARAMS
|
|
|
|
|| xo_style(xop) == XO_STYLE_ENCODER)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Simple name-value mapping */
|
|
|
|
typedef struct xo_mapping_s {
|
|
|
|
xo_xff_flags_t xm_value;
|
|
|
|
const char *xm_name;
|
|
|
|
} xo_mapping_t;
|
|
|
|
|
|
|
|
static xo_xff_flags_t
|
|
|
|
xo_name_lookup (xo_mapping_t *map, const char *value, int len)
|
|
|
|
{
|
|
|
|
if (len == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (len < 0)
|
|
|
|
len = strlen(value);
|
|
|
|
|
|
|
|
while (isspace((int) *value)) {
|
|
|
|
value += 1;
|
|
|
|
len -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (isspace((int) value[len]))
|
|
|
|
len -= 1;
|
|
|
|
|
|
|
|
if (*value == '\0')
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for ( ; map->xm_name; map++)
|
|
|
|
if (strncmp(map->xm_name, value, len) == 0)
|
|
|
|
return map->xm_value;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NOT_NEEDED_YET
|
|
|
|
static const char *
|
|
|
|
xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
|
|
|
|
{
|
|
|
|
if (value == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for ( ; map->xm_name; map++)
|
|
|
|
if (map->xm_value == value)
|
|
|
|
return map->xm_name;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif /* NOT_NEEDED_YET */
|
|
|
|
|
|
|
|
static xo_mapping_t xo_xof_names[] = {
|
|
|
|
{ XOF_COLOR_ALLOWED, "color" },
|
|
|
|
{ XOF_COLUMNS, "columns" },
|
|
|
|
{ XOF_DTRT, "dtrt" },
|
|
|
|
{ XOF_FLUSH, "flush" },
|
|
|
|
{ XOF_IGNORE_CLOSE, "ignore-close" },
|
|
|
|
{ XOF_INFO, "info" },
|
|
|
|
{ XOF_KEYS, "keys" },
|
|
|
|
{ XOF_LOG_GETTEXT, "log-gettext" },
|
|
|
|
{ XOF_LOG_SYSLOG, "log-syslog" },
|
|
|
|
{ XOF_NO_HUMANIZE, "no-humanize" },
|
|
|
|
{ XOF_NO_LOCALE, "no-locale" },
|
|
|
|
{ XOF_NO_TOP, "no-top" },
|
|
|
|
{ XOF_NOT_FIRST, "not-first" },
|
|
|
|
{ XOF_PRETTY, "pretty" },
|
|
|
|
{ XOF_UNDERSCORES, "underscores" },
|
|
|
|
{ XOF_UNITS, "units" },
|
|
|
|
{ XOF_WARN, "warn" },
|
|
|
|
{ XOF_WARN_XML, "warn-xml" },
|
|
|
|
{ XOF_XPATH, "xpath" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert string name to XOF_* flag value.
|
|
|
|
* Not all are useful. Or safe. Or sane.
|
|
|
|
*/
|
|
|
|
static unsigned
|
|
|
|
xo_name_to_flag (const char *name)
|
|
|
|
{
|
|
|
|
return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_set_style_name (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
if (name == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
int style = xo_name_to_style(name);
|
|
|
|
if (style < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
xo_set_style(xop, style);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the options for a handle using a string of options
|
|
|
|
* passed in. The input is a comma-separated set of names
|
|
|
|
* and optional values: "xml,pretty,indent=4"
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xo_set_options (xo_handle_t *xop, const char *input)
|
|
|
|
{
|
|
|
|
char *cp, *ep, *vp, *np, *bp;
|
|
|
|
int style = -1, new_style, len, rc = 0;
|
|
|
|
xo_xof_flags_t new_flag;
|
|
|
|
|
|
|
|
if (input == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
#ifdef LIBXO_COLOR_ON_BY_DEFAULT
|
|
|
|
/* If the installer used --enable-color-on-by-default, then we allow it */
|
|
|
|
XOF_SET(xop, XOF_COLOR_ALLOWED);
|
|
|
|
#endif /* LIBXO_COLOR_ON_BY_DEFAULT */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We support a simpler, old-school style of giving option
|
|
|
|
* also, using a single character for each option. It's
|
|
|
|
* ideal for lazy people, such as myself.
|
|
|
|
*/
|
|
|
|
if (*input == ':') {
|
|
|
|
int sz;
|
|
|
|
|
|
|
|
for (input++ ; *input; input++) {
|
|
|
|
switch (*input) {
|
|
|
|
case 'c':
|
|
|
|
XOF_SET(xop, XOF_COLOR_ALLOWED);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
XOF_SET(xop, XOF_FLUSH);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'F':
|
|
|
|
XOF_SET(xop, XOF_FLUSH_LINE);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'g':
|
|
|
|
XOF_SET(xop, XOF_LOG_GETTEXT);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'H':
|
|
|
|
xop->xo_style = XO_STYLE_HTML;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'I':
|
|
|
|
XOF_SET(xop, XOF_INFO);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'i':
|
|
|
|
sz = strspn(input + 1, "0123456789");
|
|
|
|
if (sz > 0) {
|
|
|
|
xop->xo_indent_by = atoi(input + 1);
|
|
|
|
input += sz - 1; /* Skip value */
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'J':
|
|
|
|
xop->xo_style = XO_STYLE_JSON;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'k':
|
|
|
|
XOF_SET(xop, XOF_KEYS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
XOF_SET(xop, XOF_NO_HUMANIZE);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'P':
|
|
|
|
XOF_SET(xop, XOF_PRETTY);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'T':
|
|
|
|
xop->xo_style = XO_STYLE_TEXT;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'U':
|
|
|
|
XOF_SET(xop, XOF_UNITS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'u':
|
|
|
|
XOF_SET(xop, XOF_UNDERSCORES);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'W':
|
|
|
|
XOF_SET(xop, XOF_WARN);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'X':
|
|
|
|
xop->xo_style = XO_STYLE_XML;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
XOF_SET(xop, XOF_XPATH);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = strlen(input) + 1;
|
|
|
|
bp = alloca(len);
|
|
|
|
memcpy(bp, input, len);
|
|
|
|
|
|
|
|
for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
|
|
|
|
np = strchr(cp, ',');
|
|
|
|
if (np)
|
|
|
|
*np++ = '\0';
|
|
|
|
|
|
|
|
vp = strchr(cp, '=');
|
|
|
|
if (vp)
|
|
|
|
*vp++ = '\0';
|
|
|
|
|
|
|
|
if (strcmp("colors", cp) == 0) {
|
|
|
|
/* XXX Look for colors=red-blue+green-yellow */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For options, we don't allow "encoder" since we want to
|
|
|
|
* handle it explicitly below as "encoder=xxx".
|
|
|
|
*/
|
|
|
|
new_style = xo_name_to_style(cp);
|
|
|
|
if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
|
|
|
|
if (style >= 0)
|
|
|
|
xo_warnx("ignoring multiple styles: '%s'", cp);
|
|
|
|
else
|
|
|
|
style = new_style;
|
|
|
|
} else {
|
|
|
|
new_flag = xo_name_to_flag(cp);
|
|
|
|
if (new_flag != 0)
|
|
|
|
XOF_SET(xop, new_flag);
|
|
|
|
else {
|
|
|
|
if (strcmp(cp, "no-color") == 0) {
|
|
|
|
XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
|
|
|
|
} else if (strcmp(cp, "indent") == 0) {
|
|
|
|
if (vp)
|
|
|
|
xop->xo_indent_by = atoi(vp);
|
|
|
|
else
|
|
|
|
xo_failure(xop, "missing value for indent option");
|
|
|
|
} else if (strcmp(cp, "encoder") == 0) {
|
|
|
|
if (vp == NULL)
|
|
|
|
xo_failure(xop, "missing value for encoder option");
|
|
|
|
else {
|
|
|
|
if (xo_encoder_init(xop, vp)) {
|
|
|
|
xo_failure(xop, "encoder not found: %s", vp);
|
|
|
|
rc = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
xo_warnx("unknown libxo option value: '%s'", cp);
|
|
|
|
rc = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (style > 0)
|
|
|
|
xop->xo_style= style;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set one or more flags for a given handle (or default if handle is NULL).
|
|
|
|
* These flags will affect future output.
|
|
|
|
*
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
* @flags Flags to be set (XOF_*)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
XOF_SET(xop, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_xof_flags_t
|
|
|
|
xo_get_flags (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
return xop->xo_flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* strndup with a twist: len < 0 means strlen
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
xo_strndup (const char *str, int len)
|
|
|
|
{
|
|
|
|
if (len < 0)
|
|
|
|
len = strlen(str);
|
|
|
|
|
|
|
|
char *cp = xo_realloc(NULL, len + 1);
|
|
|
|
if (cp) {
|
|
|
|
memcpy(cp, str, len);
|
|
|
|
cp[len] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
return cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Record a leading prefix for the XPath we generate. This allows the
|
|
|
|
* generated data to be placed within an XML hierarchy but still have
|
|
|
|
* accurate XPath expressions.
|
|
|
|
*
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
* @path The XPath expression
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_leading_xpath (xo_handle_t *xop, const char *path)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (xop->xo_leading_xpath) {
|
|
|
|
xo_free(xop->xo_leading_xpath);
|
|
|
|
xop->xo_leading_xpath = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
xop->xo_leading_xpath = xo_strndup(path, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Record the info data for a set of tags
|
|
|
|
*
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
* @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
|
|
|
|
* @count Number of entries in info (or -1 to count them ourselves)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (count < 0 && infop) {
|
|
|
|
xo_info_t *xip;
|
|
|
|
|
|
|
|
for (xip = infop, count = 0; xip->xi_name; xip++, count++)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
xop->xo_info = infop;
|
|
|
|
xop->xo_info_count = count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the formatter callback for a handle. The callback should
|
|
|
|
* return a newly formatting contents of a formatting instruction,
|
|
|
|
* meaning the bits inside the braces.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
|
|
|
|
xo_checkpointer_t cfunc)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
xop->xo_formatter = func;
|
|
|
|
xop->xo_checkpointer = cfunc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear one or more flags for a given handle (or default if handle is NULL).
|
|
|
|
* These flags will affect future output.
|
|
|
|
*
|
|
|
|
* @xop XO handle to alter (or NULL for default handle)
|
|
|
|
* @flags Flags to be cleared (XOF_*)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
XOF_CLEAR(xop, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
xo_state_name (xo_state_t state)
|
|
|
|
{
|
|
|
|
static const char *names[] = {
|
|
|
|
"init",
|
|
|
|
"open_container",
|
|
|
|
"close_container",
|
|
|
|
"open_list",
|
|
|
|
"close_list",
|
|
|
|
"open_instance",
|
|
|
|
"close_instance",
|
|
|
|
"open_leaf_list",
|
|
|
|
"close_leaf_list",
|
|
|
|
"discarding",
|
|
|
|
"marker",
|
|
|
|
"emit",
|
|
|
|
"emit_leaf_list",
|
|
|
|
"finish",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
if (state < (sizeof(names) / sizeof(names[0])))
|
|
|
|
return names[state];
|
|
|
|
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
|
|
|
|
{
|
|
|
|
static char div_open[] = "<div class=\"line\">";
|
|
|
|
static char div_open_blank[] = "<div class=\"blank-line\">";
|
|
|
|
|
|
|
|
if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (xo_style(xop) != XO_STYLE_HTML)
|
|
|
|
return;
|
|
|
|
|
|
|
|
XOIF_SET(xop, XOIF_DIV_OPEN);
|
|
|
|
if (flags & XFF_BLANK_LINE)
|
|
|
|
xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
|
|
|
|
else
|
|
|
|
xo_data_append(xop, div_open, sizeof(div_open) - 1);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_line_close (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
static char div_close[] = "</div>";
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
|
|
|
|
xo_line_ensure_open(xop, 0);
|
|
|
|
|
|
|
|
XOIF_CLEAR(xop, XOIF_DIV_OPEN);
|
|
|
|
xo_data_append(xop, div_close, sizeof(div_close) - 1);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_info_compare (const void *key, const void *data)
|
|
|
|
{
|
|
|
|
const char *name = key;
|
|
|
|
const xo_info_t *xip = data;
|
|
|
|
|
|
|
|
return strcmp(name, xip->xi_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static xo_info_t *
|
|
|
|
xo_info_find (xo_handle_t *xop, const char *name, int nlen)
|
|
|
|
{
|
|
|
|
xo_info_t *xip;
|
|
|
|
char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
|
|
|
|
|
|
|
|
memcpy(cp, name, nlen);
|
|
|
|
cp[nlen] = '\0';
|
|
|
|
|
|
|
|
xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
|
|
|
|
sizeof(xop->xo_info[0]), xo_info_compare);
|
|
|
|
return xip;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CONVERT(_have, _need) (((_have) << 8) | (_need))
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check to see that the conversion is safe and sane.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
|
|
|
|
{
|
|
|
|
switch (CONVERT(have_enc, need_enc)) {
|
|
|
|
case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
|
|
|
|
case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
|
|
|
|
case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
|
|
|
|
case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
|
|
|
|
case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
|
|
|
|
case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
xo_xff_flags_t flags,
|
|
|
|
const wchar_t *wcp, const char *cp, int len, int max,
|
|
|
|
int need_enc, int have_enc)
|
|
|
|
{
|
|
|
|
int cols = 0;
|
|
|
|
wchar_t wc = 0;
|
|
|
|
int ilen, olen, width;
|
|
|
|
int attr = (flags & XFF_ATTR);
|
|
|
|
const char *sp;
|
|
|
|
|
|
|
|
if (len > 0 && !xo_buf_has_room(xbp, len))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (len == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (cp) {
|
|
|
|
if (*cp == '\0')
|
|
|
|
break;
|
|
|
|
if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
|
|
|
|
cp += 1;
|
|
|
|
len -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wcp && *wcp == L'\0')
|
|
|
|
break;
|
|
|
|
|
|
|
|
ilen = 0;
|
|
|
|
|
|
|
|
switch (have_enc) {
|
|
|
|
case XF_ENC_WIDE: /* Wide character */
|
|
|
|
wc = *wcp++;
|
|
|
|
ilen = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XF_ENC_UTF8: /* UTF-8 */
|
|
|
|
ilen = xo_utf8_to_wc_len(cp);
|
|
|
|
if (ilen < 0) {
|
|
|
|
xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
|
|
|
|
return -1; /* Can't continue; we can't find the end */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len > 0 && len < ilen) {
|
|
|
|
len = 0; /* Break out of the loop */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
wc = xo_utf8_char(cp, ilen);
|
|
|
|
if (wc == (wchar_t) -1) {
|
|
|
|
xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
|
|
|
|
*cp, ilen);
|
|
|
|
return -1; /* Can't continue; we can't find the end */
|
|
|
|
}
|
|
|
|
cp += ilen;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XF_ENC_LOCALE: /* Native locale */
|
|
|
|
ilen = (len > 0) ? len : MB_LEN_MAX;
|
|
|
|
ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
|
|
|
|
if (ilen < 0) { /* Invalid data; skip */
|
|
|
|
xo_failure(xop, "invalid mbs char: %02hhx", *cp);
|
|
|
|
wc = L'?';
|
|
|
|
ilen = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ilen == 0) { /* Hit a wide NUL character */
|
|
|
|
len = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
cp += ilen;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reduce len, but not below zero */
|
|
|
|
if (len > 0) {
|
|
|
|
len -= ilen;
|
|
|
|
if (len < 0)
|
|
|
|
len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the width-in-columns of this character, which must be done
|
|
|
|
* in wide characters, since we lack a mbswidth() function. If
|
|
|
|
* it doesn't fit
|
|
|
|
*/
|
|
|
|
width = xo_wcwidth(wc);
|
|
|
|
if (width < 0)
|
|
|
|
width = iswcntrl(wc) ? 0 : 1;
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
|
|
|
|
if (max > 0 && cols + width > max)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (need_enc) {
|
|
|
|
case XF_ENC_UTF8:
|
|
|
|
|
|
|
|
/* Output in UTF-8 needs to be escaped, based on the style */
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
if (wc == '<')
|
|
|
|
sp = xo_xml_lt;
|
|
|
|
else if (wc == '>')
|
|
|
|
sp = xo_xml_gt;
|
|
|
|
else if (wc == '&')
|
|
|
|
sp = xo_xml_amp;
|
|
|
|
else if (attr && wc == '"')
|
|
|
|
sp = xo_xml_quot;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
|
|
|
|
int slen = strlen(sp);
|
|
|
|
if (!xo_buf_has_room(xbp, slen - 1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memcpy(xbp->xb_curp, sp, slen);
|
|
|
|
xbp->xb_curp += slen;
|
|
|
|
goto done_with_encoding; /* Need multi-level 'break' */
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, 2))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*xbp->xb_curp++ = '\\';
|
|
|
|
if (wc == '\n')
|
|
|
|
wc = 'n';
|
|
|
|
else if (wc == '\r')
|
|
|
|
wc = 'r';
|
|
|
|
else wc = wc & 0x7f;
|
|
|
|
|
|
|
|
*xbp->xb_curp++ = wc;
|
|
|
|
goto done_with_encoding;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
if (wc != '\\' && wc != '"' && wc != ']')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, 2))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*xbp->xb_curp++ = '\\';
|
|
|
|
wc = wc & 0x7f;
|
|
|
|
*xbp->xb_curp++ = wc;
|
|
|
|
goto done_with_encoding;
|
|
|
|
}
|
|
|
|
|
|
|
|
olen = xo_utf8_emit_len(wc);
|
|
|
|
if (olen < 0) {
|
|
|
|
xo_failure(xop, "ignoring bad length");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, olen))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
xo_utf8_emit_char(xbp->xb_curp, olen, wc);
|
|
|
|
xbp->xb_curp += olen;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XF_ENC_LOCALE:
|
|
|
|
if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
|
|
|
|
if (olen <= 0) {
|
|
|
|
xo_failure(xop, "could not convert wide char: %lx",
|
|
|
|
(unsigned long) wc);
|
|
|
|
width = 1;
|
|
|
|
*xbp->xb_curp++ = '?';
|
|
|
|
} else
|
|
|
|
xbp->xb_curp += olen;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
done_with_encoding:
|
|
|
|
cols += width;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_needed_encoding (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
|
|
|
|
return XF_ENC_UTF8;
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
|
|
|
|
return XF_ENC_LOCALE;
|
|
|
|
|
|
|
|
return XF_ENC_UTF8; /* Otherwise, we love UTF-8 */
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
|
|
|
|
xo_format_t *xfp)
|
|
|
|
{
|
|
|
|
static char null[] = "(null)";
|
|
|
|
static char null_no_quotes[] = "null";
|
|
|
|
|
|
|
|
char *cp = NULL;
|
|
|
|
wchar_t *wcp = NULL;
|
|
|
|
int len, cols = 0, rc = 0;
|
|
|
|
int off = xbp->xb_curp - xbp->xb_bufp, off2;
|
|
|
|
int need_enc = xo_needed_encoding(xop);
|
|
|
|
|
|
|
|
if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
len = xfp->xf_width[XF_WIDTH_SIZE];
|
|
|
|
|
|
|
|
if (xfp->xf_fc == 'm') {
|
|
|
|
cp = strerror(xop->xo_errno);
|
|
|
|
if (len < 0)
|
|
|
|
len = cp ? strlen(cp) : 0;
|
|
|
|
goto normal_string;
|
|
|
|
|
|
|
|
} else if (xfp->xf_enc == XF_ENC_WIDE) {
|
|
|
|
wcp = va_arg(xop->xo_vap, wchar_t *);
|
|
|
|
if (xfp->xf_skip)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dont' deref NULL; use the traditional "(null)" instead
|
|
|
|
* of the more accurate "who's been a naughty boy, then?".
|
|
|
|
*/
|
|
|
|
if (wcp == NULL) {
|
|
|
|
cp = null;
|
|
|
|
len = sizeof(null) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
|
|
|
|
|
|
|
|
normal_string:
|
|
|
|
if (xfp->xf_skip)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Echo "Dont' deref NULL" logic */
|
|
|
|
if (cp == NULL) {
|
|
|
|
if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
|
|
|
|
cp = null_no_quotes;
|
|
|
|
len = sizeof(null_no_quotes) - 1;
|
|
|
|
} else {
|
|
|
|
cp = null;
|
|
|
|
len = sizeof(null) - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Optimize the most common case, which is "%s". We just
|
|
|
|
* need to copy the complete string to the output buffer.
|
|
|
|
*/
|
|
|
|
if (xfp->xf_enc == need_enc
|
|
|
|
&& xfp->xf_width[XF_WIDTH_MIN] < 0
|
|
|
|
&& xfp->xf_width[XF_WIDTH_SIZE] < 0
|
|
|
|
&& xfp->xf_width[XF_WIDTH_MAX] < 0
|
|
|
|
&& !(XOIF_ISSET(xop, XOIF_ANCHOR)
|
|
|
|
|| XOF_ISSET(xop, XOF_COLUMNS))) {
|
|
|
|
len = strlen(cp);
|
|
|
|
xo_buf_escape(xop, xbp, cp, len, flags);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Our caller expects xb_curp left untouched, so we have
|
|
|
|
* to reset it and return the number of bytes written to
|
|
|
|
* the buffer.
|
|
|
|
*/
|
|
|
|
off2 = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
rc = off2 - off;
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + off;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
|
|
|
|
xfp->xf_width[XF_WIDTH_MAX],
|
|
|
|
need_enc, xfp->xf_enc);
|
|
|
|
if (cols < 0)
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xo_buf_append* will move xb_curp, so we save/restore it.
|
|
|
|
*/
|
|
|
|
off2 = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
rc = off2 - off;
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + off;
|
|
|
|
|
|
|
|
if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
|
|
|
|
/*
|
|
|
|
* Find the number of columns needed to display the string.
|
|
|
|
* If we have the original wide string, we just call wcswidth,
|
|
|
|
* but if we did the work ourselves, then we need to do it.
|
|
|
|
*/
|
|
|
|
int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
|
|
|
|
if (!xo_buf_has_room(xbp, delta))
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If seen_minus, then pad on the right; otherwise move it so
|
|
|
|
* we can pad on the left.
|
|
|
|
*/
|
|
|
|
if (xfp->xf_seen_minus) {
|
|
|
|
cp = xbp->xb_curp + rc;
|
|
|
|
} else {
|
|
|
|
cp = xbp->xb_curp;
|
|
|
|
memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the padding */
|
|
|
|
memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
|
|
|
|
rc += delta;
|
|
|
|
cols += delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += cols;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
bail:
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + off;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Look backwards in a buffer to find a numeric value
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_buf_find_last_number (xo_buffer_t *xbp, int start_offset)
|
|
|
|
{
|
|
|
|
int rc = 0; /* Fail with zero */
|
|
|
|
int digit = 1;
|
|
|
|
char *sp = xbp->xb_bufp;
|
|
|
|
char *cp = sp + start_offset;
|
|
|
|
|
|
|
|
while (--cp >= sp)
|
|
|
|
if (isdigit((int) *cp))
|
|
|
|
break;
|
|
|
|
|
|
|
|
for ( ; cp >= sp; cp--) {
|
|
|
|
if (!isdigit((int) *cp))
|
|
|
|
break;
|
|
|
|
rc += (*cp - '0') * digit;
|
|
|
|
digit *= 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_count_utf8_cols (const char *str, int len)
|
|
|
|
{
|
|
|
|
int tlen;
|
|
|
|
wchar_t wc;
|
|
|
|
int cols = 0;
|
|
|
|
const char *ep = str + len;
|
|
|
|
|
|
|
|
while (str < ep) {
|
|
|
|
tlen = xo_utf8_to_wc_len(str);
|
|
|
|
if (tlen < 0) /* Broken input is very bad */
|
|
|
|
return cols;
|
|
|
|
|
|
|
|
wc = xo_utf8_char(str, tlen);
|
|
|
|
if (wc == (wchar_t) -1)
|
|
|
|
return cols;
|
|
|
|
|
|
|
|
/* We only print printable characters */
|
|
|
|
if (iswprint((wint_t) wc)) {
|
|
|
|
/*
|
|
|
|
* Find the width-in-columns of this character, which must be done
|
|
|
|
* in wide characters, since we lack a mbswidth() function.
|
|
|
|
*/
|
|
|
|
int width = xo_wcwidth(wc);
|
|
|
|
if (width < 0)
|
|
|
|
width = iswcntrl(wc) ? 0 : 1;
|
|
|
|
|
|
|
|
cols += width;
|
|
|
|
}
|
|
|
|
|
|
|
|
str += tlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_GETTEXT
|
|
|
|
static inline const char *
|
|
|
|
xo_dgettext (xo_handle_t *xop, const char *str)
|
|
|
|
{
|
|
|
|
const char *domainname = xop->xo_gt_domain;
|
|
|
|
const char *res;
|
|
|
|
|
|
|
|
res = dgettext(domainname, str);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
|
|
|
|
fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
|
|
|
|
domainname ? "domain \"" : "", xo_printable(domainname),
|
|
|
|
domainname ? "\", " : "", xo_printable(str), xo_printable(res));
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline const char *
|
|
|
|
xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
|
|
|
|
unsigned long int n)
|
|
|
|
{
|
|
|
|
const char *domainname = xop->xo_gt_domain;
|
|
|
|
const char *res;
|
|
|
|
|
|
|
|
res = dngettext(domainname, sing, plural, n);
|
|
|
|
if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
|
|
|
|
fprintf(stderr, "xo: gettext: %s%s%s"
|
|
|
|
"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
|
|
|
|
domainname ? "domain \"" : "",
|
|
|
|
xo_printable(domainname), domainname ? "\", " : "",
|
|
|
|
xo_printable(sing),
|
|
|
|
xo_printable(plural), n, xo_printable(res));
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
#else /* HAVE_GETTEXT */
|
|
|
|
static inline const char *
|
|
|
|
xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
|
|
|
|
{
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline const char *
|
|
|
|
xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
|
|
|
|
const char *plural, unsigned long int n)
|
|
|
|
{
|
|
|
|
return (n == 1) ? singular : plural;
|
|
|
|
}
|
|
|
|
#endif /* HAVE_GETTEXT */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is really _re_formatting, since the normal format code has
|
|
|
|
* generated a beautiful string into xo_data, starting at
|
|
|
|
* start_offset. We need to see if it's plural, which means
|
|
|
|
* comma-separated options, or singular. Then we make the appropriate
|
|
|
|
* call to d[n]gettext() to get the locale-based version. Note that
|
|
|
|
* both input and output of gettext() this should be UTF-8.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
|
|
|
|
int start_offset, int cols, int need_enc)
|
|
|
|
{
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, 1))
|
|
|
|
return cols;
|
|
|
|
|
|
|
|
xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
|
|
|
|
|
|
|
|
char *cp = xbp->xb_bufp + start_offset;
|
|
|
|
int len = xbp->xb_curp - cp;
|
|
|
|
const char *newstr = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The plural flag asks us to look backwards at the last numeric
|
|
|
|
* value rendered and disect the string into two pieces.
|
|
|
|
*/
|
|
|
|
if (flags & XFF_GT_PLURAL) {
|
|
|
|
int n = xo_buf_find_last_number(xbp, start_offset);
|
|
|
|
char *two = memchr(cp, (int) ',', len);
|
|
|
|
if (two == NULL) {
|
|
|
|
xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (two == cp) {
|
|
|
|
xo_failure(xop, "nothing before comma in plural gettext "
|
|
|
|
"field: '%s'", cp);
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (two == xbp->xb_curp) {
|
|
|
|
xo_failure(xop, "nothing after comma in plural gettext "
|
|
|
|
"field: '%s'", cp);
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
*two++ = '\0';
|
|
|
|
if (flags & XFF_GT_FIELD) {
|
|
|
|
newstr = xo_dngettext(xop, cp, two, n);
|
|
|
|
} else {
|
|
|
|
/* Don't do a gettext() look up, just get the plural form */
|
|
|
|
newstr = (n == 1) ? cp : two;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we returned the first string, optimize a bit by
|
|
|
|
* backing up over comma
|
|
|
|
*/
|
|
|
|
if (newstr == cp) {
|
|
|
|
xbp->xb_curp = two - 1; /* One for comma */
|
|
|
|
/*
|
|
|
|
* If the caller wanted UTF8, we're done; nothing changed,
|
|
|
|
* but we need to count the columns used.
|
|
|
|
*/
|
|
|
|
if (need_enc == XF_ENC_UTF8)
|
|
|
|
return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* The simple case (singular) */
|
|
|
|
newstr = xo_dgettext(xop, cp);
|
|
|
|
|
|
|
|
if (newstr == cp) {
|
|
|
|
/* If the caller wanted UTF8, we're done; nothing changed */
|
|
|
|
if (need_enc == XF_ENC_UTF8)
|
|
|
|
return cols;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Since the new string string might be in gettext's buffer or
|
|
|
|
* in the buffer (as the plural form), we make a copy.
|
|
|
|
*/
|
|
|
|
int nlen = strlen(newstr);
|
|
|
|
char *newcopy = alloca(nlen + 1);
|
|
|
|
memcpy(newcopy, newstr, nlen + 1);
|
|
|
|
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
|
|
|
|
return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
|
|
|
|
need_enc, XF_ENC_UTF8);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_data_append_content (xo_handle_t *xop, const char *str, int len,
|
|
|
|
xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
int cols;
|
|
|
|
int need_enc = xo_needed_encoding(xop);
|
|
|
|
int start_offset = xo_buf_offset(&xop->xo_data);
|
|
|
|
|
|
|
|
cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
|
|
|
|
NULL, str, len, -1,
|
|
|
|
need_enc, XF_ENC_UTF8);
|
|
|
|
if (flags & XFF_GT_FLAGS)
|
|
|
|
cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_bump_width (xo_format_t *xfp, int digit)
|
|
|
|
{
|
|
|
|
int *ip = &xfp->xf_width[xfp->xf_dots];
|
|
|
|
|
|
|
|
*ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_trim_ws (xo_buffer_t *xbp, int len)
|
|
|
|
{
|
|
|
|
char *cp, *sp, *ep;
|
|
|
|
int delta;
|
|
|
|
|
|
|
|
/* First trim leading space */
|
|
|
|
for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
|
|
|
|
if (*cp != ' ')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
delta = cp - sp;
|
|
|
|
if (delta) {
|
|
|
|
len -= delta;
|
|
|
|
memmove(sp, cp, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Then trim off the end */
|
|
|
|
for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
|
|
|
|
if (ep[-1] != ' ')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
delta = sp - ep;
|
|
|
|
if (delta) {
|
|
|
|
len -= delta;
|
|
|
|
cp[len] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interface to format a single field. The arguments are in xo_vap,
|
|
|
|
* and the format is in 'fmt'. If 'xbp' is null, we use xop->xo_data;
|
|
|
|
* this is the most common case.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
const char *fmt, int flen, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
xo_format_t xf;
|
|
|
|
const char *cp, *ep, *sp, *xp = NULL;
|
|
|
|
int rc, cols;
|
|
|
|
int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
|
|
|
|
unsigned make_output = !(flags & XFF_NO_OUTPUT);
|
|
|
|
int need_enc = xo_needed_encoding(xop);
|
|
|
|
int real_need_enc = need_enc;
|
|
|
|
int old_cols = xop->xo_columns;
|
|
|
|
|
|
|
|
/* The gettext interface is UTF-8, so we'll need that for now */
|
|
|
|
if (flags & XFF_GT_FIELD)
|
|
|
|
need_enc = XF_ENC_UTF8;
|
|
|
|
|
|
|
|
if (xbp == NULL)
|
|
|
|
xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
unsigned start_offset = xo_buf_offset(xbp);
|
|
|
|
|
|
|
|
for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
|
|
|
|
/*
|
|
|
|
* Since we're starting a new field, save the starting offset.
|
|
|
|
* We'll need this later for field-related operations.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (*cp != '%') {
|
|
|
|
add_one:
|
|
|
|
if (xp == NULL)
|
|
|
|
xp = cp;
|
|
|
|
|
|
|
|
if (*cp == '\\' && cp[1] != '\0')
|
|
|
|
cp += 1;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} if (cp + 1 < ep && cp[1] == '%') {
|
|
|
|
cp += 1;
|
|
|
|
goto add_one;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xp) {
|
|
|
|
if (make_output) {
|
|
|
|
cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
|
|
|
|
NULL, xp, cp - xp, -1,
|
|
|
|
need_enc, XF_ENC_UTF8);
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
xp = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bzero(&xf, sizeof(xf));
|
|
|
|
xf.xf_leading_zero = -1;
|
|
|
|
xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "%@" starts an XO-specific set of flags:
|
|
|
|
* @X@ - XML-only field; ignored if style isn't XML
|
|
|
|
*/
|
|
|
|
if (cp[1] == '@') {
|
|
|
|
for (cp += 2; cp < ep; cp++) {
|
|
|
|
if (*cp == '@') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (*cp == '*') {
|
|
|
|
/*
|
|
|
|
* '*' means there's a "%*.*s" value in vap that
|
|
|
|
* we want to ignore
|
|
|
|
*/
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hidden fields are only visible to JSON and XML */
|
|
|
|
if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
|
|
|
|
if (style != XO_STYLE_XML
|
|
|
|
&& !xo_style_is_encoding(xop))
|
|
|
|
xf.xf_skip = 1;
|
|
|
|
} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
|
|
|
|
if (style != XO_STYLE_TEXT
|
|
|
|
&& xo_style(xop) != XO_STYLE_HTML)
|
|
|
|
xf.xf_skip = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!make_output)
|
|
|
|
xf.xf_skip = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Looking at one piece of a format; find the end and
|
|
|
|
* call snprintf. Then advance xo_vap on our own.
|
|
|
|
*
|
|
|
|
* Note that 'n', 'v', and '$' are not supported.
|
|
|
|
*/
|
|
|
|
sp = cp; /* Save start pointer */
|
|
|
|
for (cp += 1; cp < ep; cp++) {
|
|
|
|
if (*cp == 'l')
|
|
|
|
xf.xf_lflag += 1;
|
|
|
|
else if (*cp == 'h')
|
|
|
|
xf.xf_hflag += 1;
|
|
|
|
else if (*cp == 'j')
|
|
|
|
xf.xf_jflag += 1;
|
|
|
|
else if (*cp == 't')
|
|
|
|
xf.xf_tflag += 1;
|
|
|
|
else if (*cp == 'z')
|
|
|
|
xf.xf_zflag += 1;
|
|
|
|
else if (*cp == 'q')
|
|
|
|
xf.xf_qflag += 1;
|
|
|
|
else if (*cp == '.') {
|
|
|
|
if (++xf.xf_dots >= XF_WIDTH_NUM) {
|
|
|
|
xo_failure(xop, "Too many dots in format: '%s'", fmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (*cp == '-')
|
|
|
|
xf.xf_seen_minus = 1;
|
|
|
|
else if (isdigit((int) *cp)) {
|
|
|
|
if (xf.xf_leading_zero < 0)
|
|
|
|
xf.xf_leading_zero = (*cp == '0');
|
|
|
|
xo_bump_width(&xf, *cp - '0');
|
|
|
|
} else if (*cp == '*') {
|
|
|
|
xf.xf_stars += 1;
|
|
|
|
xf.xf_star[xf.xf_dots] = 1;
|
|
|
|
} else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
|
|
|
|
break;
|
|
|
|
else if (*cp == 'n' || *cp == 'v') {
|
|
|
|
xo_failure(xop, "unsupported format: '%s'", fmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cp == ep)
|
|
|
|
xo_failure(xop, "field format missing format character: %s",
|
|
|
|
fmt);
|
|
|
|
|
|
|
|
xf.xf_fc = *cp;
|
|
|
|
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
|
|
|
|
if (*cp == 's' || *cp == 'S') {
|
|
|
|
/* Handle "%*.*.*s" */
|
|
|
|
int s;
|
|
|
|
for (s = 0; s < XF_WIDTH_NUM; s++) {
|
|
|
|
if (xf.xf_star[s]) {
|
|
|
|
xf.xf_width[s] = va_arg(xop->xo_vap, int);
|
|
|
|
|
|
|
|
/* Normalize a negative width value */
|
|
|
|
if (xf.xf_width[s] < 0) {
|
|
|
|
if (s == 0) {
|
|
|
|
xf.xf_width[0] = -xf.xf_width[0];
|
|
|
|
xf.xf_seen_minus = 1;
|
|
|
|
} else
|
|
|
|
xf.xf_width[s] = -1; /* Ignore negative values */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If no max is given, it defaults to size */
|
|
|
|
if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
|
|
|
|
xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
|
|
|
|
|
|
|
|
if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
|
|
|
|
xf.xf_lflag = 1;
|
|
|
|
|
|
|
|
if (!xf.xf_skip) {
|
|
|
|
xo_buffer_t *fbp = &xop->xo_fmt;
|
|
|
|
int len = cp - sp + 1;
|
|
|
|
if (!xo_buf_has_room(fbp, len + 1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
char *newfmt = fbp->xb_curp;
|
|
|
|
memcpy(newfmt, sp, len);
|
|
|
|
newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */
|
|
|
|
newfmt[len] = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bad news: our strings are UTF-8, but the stock printf
|
|
|
|
* functions won't handle field widths for wide characters
|
|
|
|
* correctly. So we have to handle this ourselves.
|
|
|
|
*/
|
|
|
|
if (xop->xo_formatter == NULL
|
|
|
|
&& (xf.xf_fc == 's' || xf.xf_fc == 'S'
|
|
|
|
|| xf.xf_fc == 'm')) {
|
|
|
|
|
|
|
|
xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
|
|
|
|
: (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
|
|
|
|
: xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
|
|
|
|
|
|
|
|
rc = xo_format_string(xop, xbp, flags, &xf);
|
|
|
|
|
|
|
|
if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
|
|
|
|
rc = xo_trim_ws(xbp, rc);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For XML and HTML, we need "&<>" processing; for JSON,
|
|
|
|
* it's quotes. Text gets nothing.
|
|
|
|
*/
|
|
|
|
switch (style) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
if (flags & XFF_TRIM_WS)
|
|
|
|
columns = rc = xo_trim_ws(xbp, rc);
|
|
|
|
/* fall thru */
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (flags & XFF_TRIM_WS)
|
|
|
|
columns = rc = xo_trim_ws(xbp, rc);
|
|
|
|
rc = xo_escape_json(xbp, rc, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
if (flags & XFF_TRIM_WS)
|
|
|
|
columns = rc = xo_trim_ws(xbp, rc);
|
|
|
|
rc = xo_escape_sdparams(xbp, rc, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
if (flags & XFF_TRIM_WS)
|
|
|
|
columns = rc = xo_trim_ws(xbp, rc);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We can assume all the non-%s data we've
|
|
|
|
* added is ASCII, so the columns and bytes are the
|
|
|
|
* same. xo_format_string handles all the fancy
|
|
|
|
* string conversions and updates xo_anchor_columns
|
|
|
|
* accordingly.
|
|
|
|
*/
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += columns;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now for the tricky part: we need to move the argument pointer
|
|
|
|
* along by the amount needed.
|
|
|
|
*/
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
|
|
|
|
|
|
|
|
if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
|
|
|
|
/*
|
|
|
|
* The 'S' and 's' formats are normally handled in
|
|
|
|
* xo_format_string, but if we skipped it, then we
|
|
|
|
* need to pop it.
|
|
|
|
*/
|
|
|
|
if (xf.xf_skip)
|
|
|
|
va_arg(xop->xo_vap, char *);
|
|
|
|
|
|
|
|
} else if (xf.xf_fc == 'm') {
|
|
|
|
/* Nothing on the stack for "%m" */
|
|
|
|
|
|
|
|
} else {
|
|
|
|
int s;
|
|
|
|
for (s = 0; s < XF_WIDTH_NUM; s++) {
|
|
|
|
if (xf.xf_star[s])
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
|
|
|
|
if (xf.xf_hflag > 1) {
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
|
|
|
|
} else if (xf.xf_hflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
|
|
|
|
} else if (xf.xf_lflag > 1) {
|
|
|
|
va_arg(xop->xo_vap, unsigned long long);
|
|
|
|
|
|
|
|
} else if (xf.xf_lflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, unsigned long);
|
|
|
|
|
|
|
|
} else if (xf.xf_jflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, intmax_t);
|
|
|
|
|
|
|
|
} else if (xf.xf_tflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, ptrdiff_t);
|
|
|
|
|
|
|
|
} else if (xf.xf_zflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, size_t);
|
|
|
|
|
|
|
|
} else if (xf.xf_qflag > 0) {
|
|
|
|
va_arg(xop->xo_vap, quad_t);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
}
|
|
|
|
} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
|
|
|
|
if (xf.xf_lflag)
|
|
|
|
va_arg(xop->xo_vap, long double);
|
|
|
|
else
|
|
|
|
va_arg(xop->xo_vap, double);
|
|
|
|
|
|
|
|
else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
|
|
|
|
va_arg(xop->xo_vap, wint_t);
|
|
|
|
|
|
|
|
else if (xf.xf_fc == 'c')
|
|
|
|
va_arg(xop->xo_vap, int);
|
|
|
|
|
|
|
|
else if (xf.xf_fc == 'p')
|
|
|
|
va_arg(xop->xo_vap, void *);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xp) {
|
|
|
|
if (make_output) {
|
|
|
|
cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
|
|
|
|
NULL, xp, cp - xp, -1,
|
|
|
|
need_enc, XF_ENC_UTF8);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
xp = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & XFF_GT_FLAGS) {
|
|
|
|
/*
|
|
|
|
* Handle gettext()ing the field by looking up the value
|
|
|
|
* and then copying it in, while converting to locale, if
|
|
|
|
* needed.
|
|
|
|
*/
|
|
|
|
int new_cols = xo_format_gettext(xop, flags, start_offset,
|
|
|
|
old_cols, real_need_enc);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += new_cols - old_cols;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += new_cols - old_cols;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
|
|
|
|
{
|
|
|
|
char *cp = encoding;
|
|
|
|
|
|
|
|
if (cp[0] != '%' || !isdigit((int) cp[1]))
|
|
|
|
return encoding;
|
|
|
|
|
|
|
|
for (cp += 2; *cp; cp++) {
|
|
|
|
if (!isdigit((int) *cp))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cp -= 1;
|
|
|
|
*cp = '%';
|
|
|
|
|
|
|
|
return cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_color_append_html (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If the color buffer has content, we add it now. It's already
|
|
|
|
* prebuilt and ready, since we want to add it to every <div>.
|
|
|
|
*/
|
|
|
|
if (!xo_buf_is_empty(&xop->xo_color_buf)) {
|
|
|
|
xo_buffer_t *xbp = &xop->xo_color_buf;
|
|
|
|
|
|
|
|
xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A wrapper for humanize_number that autoscales, since the
|
|
|
|
* HN_AUTOSCALE flag scales as needed based on the size of
|
|
|
|
* the output buffer, not the size of the value. I also
|
|
|
|
* wish HN_DECIMAL was more imperative, without the <10
|
|
|
|
* test. But the boat only goes where we want when we hold
|
|
|
|
* the rudder, so xo_humanize fixes part of the problem.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_humanize (char *buf, int len, uint64_t value, int flags)
|
|
|
|
{
|
|
|
|
int scale = 0;
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
uint64_t left = value;
|
|
|
|
|
|
|
|
if (flags & HN_DIVISOR_1000) {
|
|
|
|
for ( ; left; scale++)
|
|
|
|
left /= 1000;
|
|
|
|
} else {
|
|
|
|
for ( ; left; scale++)
|
|
|
|
left /= 1024;
|
|
|
|
}
|
|
|
|
scale -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return xo_humanize_number(buf, len, value, "", scale, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is an area where we can save information from the handle for
|
|
|
|
* later restoration. We need to know what data was rendered to know
|
|
|
|
* what needs cleaned up.
|
|
|
|
*/
|
|
|
|
typedef struct xo_humanize_save_s {
|
|
|
|
unsigned xhs_offset; /* Saved xo_offset */
|
|
|
|
unsigned xhs_columns; /* Saved xo_columns */
|
|
|
|
unsigned xhs_anchor_columns; /* Saved xo_anchor_columns */
|
|
|
|
} xo_humanize_save_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Format a "humanized" value for a numeric, meaning something nice
|
|
|
|
* like "44M" instead of "44470272". We autoscale, choosing the
|
|
|
|
* most appropriate value for K/M/G/T/P/E based on the value given.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
|
|
|
|
xo_humanize_save_t *savep, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have a string that's allegedly a number. We want to
|
|
|
|
* humanize it, which means turning it back into a number
|
|
|
|
* and calling xo_humanize_number on it.
|
|
|
|
*/
|
|
|
|
uint64_t value;
|
|
|
|
char *ep;
|
|
|
|
|
|
|
|
xo_buf_append(xbp, "", 1); /* NUL-terminate it */
|
|
|
|
|
|
|
|
value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
|
|
|
|
if (!(value == ULLONG_MAX && errno == ERANGE)
|
|
|
|
&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
|
|
|
|
/*
|
|
|
|
* There are few values where humanize_number needs
|
|
|
|
* more bytes than the original value. I've used
|
|
|
|
* 10 as a rectal number to cover those scenarios.
|
|
|
|
*/
|
|
|
|
if (xo_buf_has_room(xbp, 10)) {
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
int left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
|
|
|
|
int hn_flags = HN_NOSPACE; /* On by default */
|
|
|
|
|
|
|
|
if (flags & XFF_HN_SPACE)
|
|
|
|
hn_flags &= ~HN_NOSPACE;
|
|
|
|
|
|
|
|
if (flags & XFF_HN_DECIMAL)
|
|
|
|
hn_flags |= HN_DECIMAL;
|
|
|
|
|
|
|
|
if (flags & XFF_HN_1000)
|
|
|
|
hn_flags |= HN_DIVISOR_1000;
|
|
|
|
|
|
|
|
rc = xo_humanize(xbp->xb_curp,
|
|
|
|
left, value, hn_flags);
|
|
|
|
if (rc > 0) {
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
xop->xo_columns = savep->xhs_columns + rc;
|
|
|
|
xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
|
|
|
|
const char *name, int nlen,
|
|
|
|
const char *value, int vlen,
|
|
|
|
const char *encoding, int elen)
|
|
|
|
{
|
|
|
|
static char div_start[] = "<div class=\"";
|
|
|
|
static char div_tag[] = "\" data-tag=\"";
|
|
|
|
static char div_xpath[] = "\" data-xpath=\"";
|
|
|
|
static char div_key[] = "\" data-key=\"key";
|
|
|
|
static char div_end[] = "\">";
|
|
|
|
static char div_close[] = "</div>";
|
|
|
|
|
2016-04-15 15:42:12 +00:00
|
|
|
/* The encoding format defaults to the normal format */
|
|
|
|
if (encoding == NULL) {
|
|
|
|
char *enc = alloca(vlen + 1);
|
|
|
|
memcpy(enc, value, vlen);
|
|
|
|
enc[vlen] = '\0';
|
|
|
|
encoding = xo_fix_encoding(xop, enc);
|
|
|
|
elen = strlen(encoding);
|
|
|
|
}
|
|
|
|
|
2016-03-16 23:06:34 +00:00
|
|
|
/*
|
|
|
|
* To build our XPath predicate, we need to save the va_list before
|
|
|
|
* we format our data, and then restore it before we format the
|
|
|
|
* xpath expression.
|
|
|
|
* Display-only keys implies that we've got an encode-only key
|
|
|
|
* elsewhere, so we don't use them from making predicates.
|
|
|
|
*/
|
|
|
|
int need_predidate =
|
|
|
|
(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
|
|
|
|
&& XOF_ISSET(xop, XOF_XPATH));
|
|
|
|
|
|
|
|
if (need_predidate) {
|
|
|
|
va_list va_local;
|
|
|
|
|
|
|
|
va_copy(va_local, xop->xo_vap);
|
|
|
|
if (xop->xo_checkpointer)
|
|
|
|
xop->xo_checkpointer(xop, xop->xo_vap, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build an XPath predicate expression to match this key.
|
|
|
|
* We use the format buffer.
|
|
|
|
*/
|
|
|
|
xo_buffer_t *pbp = &xop->xo_predicate;
|
|
|
|
pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
|
|
|
|
|
|
|
|
xo_buf_append(pbp, "[", 1);
|
|
|
|
xo_buf_escape(xop, pbp, name, nlen, 0);
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_buf_append(pbp, " = '", 4);
|
|
|
|
else
|
|
|
|
xo_buf_append(pbp, "='", 2);
|
|
|
|
|
|
|
|
xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
|
|
|
|
pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
|
|
|
|
xo_do_format_field(xop, pbp, encoding, elen, pflags);
|
|
|
|
|
|
|
|
xo_buf_append(pbp, "']", 2);
|
|
|
|
|
|
|
|
/* Now we record this predicate expression in the stack */
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
|
|
|
|
int dlen = pbp->xb_curp - pbp->xb_bufp;
|
|
|
|
|
|
|
|
char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
|
|
|
|
if (cp) {
|
|
|
|
memcpy(cp + olen, pbp->xb_bufp, dlen);
|
|
|
|
cp[olen + dlen] = '\0';
|
|
|
|
xsp->xs_keys = cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now we reset the xo_vap as if we were never here */
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
va_copy(xop->xo_vap, va_local);
|
|
|
|
va_end(va_local);
|
|
|
|
if (xop->xo_checkpointer)
|
|
|
|
xop->xo_checkpointer(xop, xop->xo_vap, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & XFF_ENCODE_ONLY) {
|
|
|
|
/*
|
|
|
|
* Even if this is encode-only, we need to go thru the
|
|
|
|
* work of formatting it to make sure the args are cleared
|
|
|
|
* from xo_vap.
|
|
|
|
*/
|
|
|
|
xo_do_format_field(xop, NULL, encoding, elen,
|
|
|
|
flags | XFF_NO_OUTPUT);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_line_ensure_open(xop, 0);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_buf_indent(xop, xop->xo_indent_by);
|
|
|
|
|
|
|
|
xo_data_append(xop, div_start, sizeof(div_start) - 1);
|
|
|
|
xo_data_append(xop, class, strlen(class));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the color buffer has content, we add it now. It's already
|
|
|
|
* prebuilt and ready, since we want to add it to every <div>.
|
|
|
|
*/
|
|
|
|
if (!xo_buf_is_empty(&xop->xo_color_buf)) {
|
|
|
|
xo_buffer_t *xbp = &xop->xo_color_buf;
|
|
|
|
|
|
|
|
xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name) {
|
|
|
|
xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Save the offset at which we'd place units. See xo_format_units.
|
|
|
|
*/
|
|
|
|
if (XOF_ISSET(xop, XOF_UNITS)) {
|
|
|
|
XOIF_SET(xop, XOIF_UNITS_PENDING);
|
|
|
|
/*
|
|
|
|
* Note: We need the '+1' here because we know we've not
|
|
|
|
* added the closing quote. We add one, knowing the quote
|
|
|
|
* will be added shortly.
|
|
|
|
*/
|
|
|
|
xop->xo_units_offset =
|
|
|
|
xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_XPATH)) {
|
|
|
|
int i;
|
|
|
|
xo_stack_t *xsp;
|
|
|
|
|
|
|
|
xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
|
|
|
|
if (xop->xo_leading_xpath)
|
|
|
|
xo_data_append(xop, xop->xo_leading_xpath,
|
|
|
|
strlen(xop->xo_leading_xpath));
|
|
|
|
|
|
|
|
for (i = 0; i <= xop->xo_depth; i++) {
|
|
|
|
xsp = &xop->xo_stack[i];
|
|
|
|
if (xsp->xs_name == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
|
|
|
|
* are directly under XSS_OPEN_INSTANCE frames so we
|
|
|
|
* don't need to put these in our XPath expressions.
|
|
|
|
*/
|
|
|
|
if (xsp->xs_state == XSS_OPEN_LIST
|
|
|
|
|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
xo_data_append(xop, "/", 1);
|
|
|
|
xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
|
|
|
|
if (xsp->xs_keys) {
|
|
|
|
/* Don't show keys for the key field */
|
|
|
|
if (i != xop->xo_depth || !(flags & XFF_KEY))
|
|
|
|
xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_data_append(xop, "/", 1);
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
|
|
|
|
static char in_type[] = "\" data-type=\"";
|
|
|
|
static char in_help[] = "\" data-help=\"";
|
|
|
|
|
|
|
|
xo_info_t *xip = xo_info_find(xop, name, nlen);
|
|
|
|
if (xip) {
|
|
|
|
if (xip->xi_type) {
|
|
|
|
xo_data_append(xop, in_type, sizeof(in_type) - 1);
|
|
|
|
xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
|
|
|
|
}
|
|
|
|
if (xip->xi_help) {
|
|
|
|
xo_data_append(xop, in_help, sizeof(in_help) - 1);
|
|
|
|
xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
|
|
|
|
xo_data_append(xop, div_key, sizeof(div_key) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
unsigned base_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
|
|
|
|
xo_data_append(xop, div_end, sizeof(div_end) - 1);
|
|
|
|
|
|
|
|
xo_humanize_save_t save; /* Save values for humanizing logic */
|
|
|
|
|
|
|
|
save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
save.xhs_columns = xop->xo_columns;
|
|
|
|
save.xhs_anchor_columns = xop->xo_anchor_columns;
|
|
|
|
|
|
|
|
xo_do_format_field(xop, NULL, value, vlen, flags);
|
|
|
|
|
|
|
|
if (flags & XFF_HUMANIZE) {
|
|
|
|
/*
|
|
|
|
* Unlike text style, we want to retain the original value and
|
|
|
|
* stuff it into the "data-number" attribute.
|
|
|
|
*/
|
|
|
|
static const char div_number[] = "\" data-number=\"";
|
|
|
|
int div_len = sizeof(div_number) - 1;
|
|
|
|
|
|
|
|
unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
int olen = end_offset - save.xhs_offset;
|
|
|
|
|
|
|
|
char *cp = alloca(olen + 1);
|
|
|
|
memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
|
|
|
|
cp[olen] = '\0';
|
|
|
|
|
|
|
|
xo_format_humanize(xop, xbp, &save, flags);
|
|
|
|
|
|
|
|
if (xo_buf_has_room(xbp, div_len + olen)) {
|
|
|
|
unsigned new_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
|
|
|
|
|
|
|
|
/* Move the humanized string off to the left */
|
|
|
|
memmove(xbp->xb_bufp + base_offset + div_len + olen,
|
|
|
|
xbp->xb_bufp + base_offset, new_offset - base_offset);
|
|
|
|
|
|
|
|
/* Copy the data_number attribute name */
|
|
|
|
memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
|
|
|
|
|
|
|
|
/* Copy the original long value */
|
|
|
|
memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
|
|
|
|
xbp->xb_curp += div_len + olen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_data_append(xop, div_close, sizeof(div_close) - 1);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_text (xo_handle_t *xop, const char *str, int len)
|
|
|
|
{
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
xo_buf_append_locale(xop, &xop->xo_data, str, len);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *str = xfip->xfi_content;
|
|
|
|
unsigned len = xfip->xfi_clen;
|
|
|
|
const char *fmt = xfip->xfi_format;
|
|
|
|
unsigned flen = xfip->xfi_flen;
|
|
|
|
xo_xff_flags_t flags = xfip->xfi_flags;
|
|
|
|
|
|
|
|
static char div_open[] = "<div class=\"title";
|
|
|
|
static char div_middle[] = "\">";
|
|
|
|
static char div_close[] = "</div>";
|
|
|
|
|
|
|
|
if (flen == 0) {
|
|
|
|
fmt = "%s";
|
|
|
|
flen = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
/*
|
|
|
|
* Even though we don't care about text, we need to do
|
|
|
|
* enough parsing work to skip over the right bits of xo_vap.
|
|
|
|
*/
|
|
|
|
if (len == 0)
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
int start = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
int left = xbp->xb_size - start;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_HTML) {
|
|
|
|
xo_line_ensure_open(xop, 0);
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_buf_indent(xop, xop->xo_indent_by);
|
|
|
|
xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
|
|
|
|
xo_color_append_html(xop);
|
|
|
|
xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
|
|
|
|
if (len) {
|
|
|
|
char *newfmt = alloca(flen + 1);
|
|
|
|
memcpy(newfmt, fmt, flen);
|
|
|
|
newfmt[flen] = '\0';
|
|
|
|
|
|
|
|
/* If len is non-zero, the format string apply to the name */
|
|
|
|
char *newstr = alloca(len + 1);
|
|
|
|
memcpy(newstr, str, len);
|
|
|
|
newstr[len] = '\0';
|
|
|
|
|
|
|
|
if (newstr[len - 1] == 's') {
|
|
|
|
char *bp;
|
|
|
|
|
|
|
|
rc = snprintf(NULL, 0, newfmt, newstr);
|
|
|
|
if (rc > 0) {
|
|
|
|
/*
|
|
|
|
* We have to do this the hard way, since we might need
|
|
|
|
* the columns.
|
|
|
|
*/
|
|
|
|
bp = alloca(rc + 1);
|
|
|
|
rc = snprintf(bp, rc + 1, newfmt, newstr);
|
|
|
|
|
|
|
|
xo_data_append_content(xop, bp, rc, flags);
|
|
|
|
}
|
|
|
|
goto move_along;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
|
|
|
|
if (rc >= left) {
|
|
|
|
if (!xo_buf_has_room(xbp, rc))
|
|
|
|
return;
|
|
|
|
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
|
|
|
|
rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc > 0) {
|
|
|
|
if (XOF_ISSET(xop, XOF_COLUMNS))
|
|
|
|
xop->xo_columns += rc;
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xop->xo_anchor_columns += rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen, flags);
|
|
|
|
|
|
|
|
/* xo_do_format_field moved curp, so we need to reset it */
|
|
|
|
rc = xbp->xb_curp - (xbp->xb_bufp + start);
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + start;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we're styling HTML, then we need to escape it */
|
|
|
|
if (xo_style(xop) == XO_STYLE_HTML) {
|
|
|
|
rc = xo_escape_xml(xbp, rc, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc > 0)
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
|
|
|
|
move_along:
|
|
|
|
if (xo_style(xop) == XO_STYLE_HTML) {
|
|
|
|
xo_data_append(xop, div_close, sizeof(div_close) - 1);
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
|
|
|
|
xo_data_append(xop, ",", 1);
|
|
|
|
if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
} else
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/* Useful debugging function */
|
|
|
|
void
|
|
|
|
xo_arg (xo_handle_t *xop);
|
|
|
|
void
|
|
|
|
xo_arg (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
|
|
|
|
}
|
|
|
|
#endif /* 0 */
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_value (xo_handle_t *xop, const char *name, int nlen,
|
|
|
|
const char *format, int flen,
|
|
|
|
const char *encoding, int elen, xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
int pretty = XOF_ISSET(xop, XOF_PRETTY);
|
|
|
|
int quote;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Before we emit a value, we need to know that the frame is ready.
|
|
|
|
*/
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
if (flags & XFF_LEAF_LIST) {
|
|
|
|
/*
|
|
|
|
* Check if we've already started to emit normal leafs
|
|
|
|
* or if we're not in a leaf list.
|
|
|
|
*/
|
|
|
|
if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
|
|
|
|
|| !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
|
|
|
|
char nbuf[nlen + 1];
|
|
|
|
memcpy(nbuf, name, nlen);
|
|
|
|
nbuf[nlen] = '\0';
|
|
|
|
|
|
|
|
int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
|
|
|
|
if (rc < 0)
|
|
|
|
flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
|
|
|
|
else
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
|
|
|
|
}
|
|
|
|
|
|
|
|
xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
if (xsp->xs_name) {
|
|
|
|
name = xsp->xs_name;
|
|
|
|
nlen = strlen(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (flags & XFF_KEY) {
|
|
|
|
/* Emitting a 'k' (key) field */
|
|
|
|
if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
|
|
|
|
xo_failure(xop, "key field emitted after normal value field: '%.*s'",
|
|
|
|
nlen, name);
|
|
|
|
|
|
|
|
} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
|
|
|
|
char nbuf[nlen + 1];
|
|
|
|
memcpy(nbuf, name, nlen);
|
|
|
|
nbuf[nlen] = '\0';
|
|
|
|
|
|
|
|
int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
|
|
|
|
if (rc < 0)
|
|
|
|
flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
|
|
|
|
else
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
|
|
|
|
|
|
|
|
xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
xsp->xs_flags |= XSF_EMIT_KEY;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* Emitting a normal value field */
|
|
|
|
if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
|
|
|
|
|| !(xsp->xs_flags & XSF_EMIT)) {
|
|
|
|
char nbuf[nlen + 1];
|
|
|
|
memcpy(nbuf, name, nlen);
|
|
|
|
nbuf[nlen] = '\0';
|
|
|
|
|
|
|
|
int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
|
|
|
|
if (rc < 0)
|
|
|
|
flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
|
|
|
|
else
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
|
|
|
|
|
|
|
|
xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
xsp->xs_flags |= XSF_EMIT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
xo_humanize_save_t save; /* Save values for humanizing logic */
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
if (flags & XFF_ENCODE_ONLY)
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
|
|
|
|
save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
save.xhs_columns = xop->xo_columns;
|
|
|
|
save.xhs_anchor_columns = xop->xo_anchor_columns;
|
|
|
|
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
|
|
|
|
if (flags & XFF_HUMANIZE)
|
|
|
|
xo_format_humanize(xop, xbp, &save, flags);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
if (flags & XFF_ENCODE_ONLY)
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
|
|
|
|
xo_buf_append_div(xop, "data", flags, name, nlen,
|
|
|
|
format, flen, encoding, elen);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
/*
|
|
|
|
* Even though we're not making output, we still need to
|
|
|
|
* let the formatting code handle the va_arg popping.
|
|
|
|
*/
|
|
|
|
if (flags & XFF_DISPLAY_ONLY) {
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (encoding) {
|
|
|
|
format = encoding;
|
|
|
|
flen = elen;
|
|
|
|
} else {
|
|
|
|
char *enc = alloca(flen + 1);
|
|
|
|
memcpy(enc, format, flen);
|
|
|
|
enc[flen] = '\0';
|
|
|
|
format = xo_fix_encoding(xop, enc);
|
|
|
|
flen = strlen(format);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nlen == 0) {
|
|
|
|
static char missing[] = "missing-field-name";
|
|
|
|
xo_failure(xop, "missing field name: %s", format);
|
|
|
|
name = missing;
|
|
|
|
nlen = sizeof(missing) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pretty)
|
|
|
|
xo_buf_indent(xop, -1);
|
|
|
|
xo_data_append(xop, "<", 1);
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
|
|
|
|
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
|
|
|
|
xo_data_append(xop, xop->xo_attrs.xb_bufp,
|
|
|
|
xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
|
|
|
|
xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We indicate 'key' fields using the 'key' attribute. While
|
|
|
|
* this is really committing the crime of mixing meta-data with
|
|
|
|
* data, it's often useful. Especially when format meta-data is
|
|
|
|
* difficult to come by.
|
|
|
|
*/
|
|
|
|
if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
|
|
|
|
static char attr[] = " key=\"key\"";
|
|
|
|
xo_data_append(xop, attr, sizeof(attr) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Save the offset at which we'd place units. See xo_format_units.
|
|
|
|
*/
|
|
|
|
if (XOF_ISSET(xop, XOF_UNITS)) {
|
|
|
|
XOIF_SET(xop, XOIF_UNITS_PENDING);
|
|
|
|
xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_data_append(xop, ">", 1);
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
xo_data_append(xop, "</", 2);
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
xo_data_append(xop, ">", 1);
|
|
|
|
if (pretty)
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (flags & XFF_DISPLAY_ONLY) {
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (encoding) {
|
|
|
|
format = encoding;
|
|
|
|
flen = elen;
|
|
|
|
} else {
|
|
|
|
char *enc = alloca(flen + 1);
|
|
|
|
memcpy(enc, format, flen);
|
|
|
|
enc[flen] = '\0';
|
|
|
|
format = xo_fix_encoding(xop, enc);
|
|
|
|
flen = strlen(format);
|
|
|
|
}
|
|
|
|
|
|
|
|
int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
|
|
|
|
|
|
|
|
xo_format_prep(xop, flags);
|
|
|
|
|
|
|
|
if (flags & XFF_QUOTE)
|
|
|
|
quote = 1;
|
|
|
|
else if (flags & XFF_NOQUOTE)
|
|
|
|
quote = 0;
|
|
|
|
else if (flen == 0) {
|
|
|
|
quote = 0;
|
|
|
|
format = "true"; /* JSON encodes empty tags as a boolean true */
|
|
|
|
flen = 4;
|
|
|
|
} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
|
|
|
|
quote = 1;
|
|
|
|
else
|
|
|
|
quote = 0;
|
|
|
|
|
|
|
|
if (nlen == 0) {
|
|
|
|
static char missing[] = "missing-field-name";
|
|
|
|
xo_failure(xop, "missing field name: %s", format);
|
|
|
|
name = missing;
|
|
|
|
nlen = sizeof(missing) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & XFF_LEAF_LIST) {
|
|
|
|
if (!first && pretty)
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
if (pretty)
|
|
|
|
xo_buf_indent(xop, -1);
|
|
|
|
} else {
|
|
|
|
if (pretty)
|
|
|
|
xo_buf_indent(xop, -1);
|
|
|
|
xo_data_append(xop, "\"", 1);
|
|
|
|
|
|
|
|
xbp = &xop->xo_data;
|
|
|
|
int off = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
|
|
|
|
int now = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
for ( ; off < now; off++)
|
|
|
|
if (xbp->xb_bufp[off] == '-')
|
|
|
|
xbp->xb_bufp[off] = '_';
|
|
|
|
}
|
|
|
|
xo_data_append(xop, "\":", 2);
|
|
|
|
if (pretty)
|
|
|
|
xo_data_append(xop, " ", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quote)
|
|
|
|
xo_data_append(xop, "\"", 1);
|
|
|
|
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
|
|
|
|
if (quote)
|
|
|
|
xo_data_append(xop, "\"", 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
if (flags & XFF_DISPLAY_ONLY) {
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (encoding) {
|
|
|
|
format = encoding;
|
|
|
|
flen = elen;
|
|
|
|
} else {
|
|
|
|
char *enc = alloca(flen + 1);
|
|
|
|
memcpy(enc, format, flen);
|
|
|
|
enc[flen] = '\0';
|
|
|
|
format = xo_fix_encoding(xop, enc);
|
|
|
|
flen = strlen(format);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nlen == 0) {
|
|
|
|
static char missing[] = "missing-field-name";
|
|
|
|
xo_failure(xop, "missing field name: %s", format);
|
|
|
|
name = missing;
|
|
|
|
nlen = sizeof(missing) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_data_escape(xop, name, nlen);
|
|
|
|
xo_data_append(xop, "=\"", 2);
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
xo_data_append(xop, "\" ", 2);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
if (flags & XFF_DISPLAY_ONLY) {
|
|
|
|
flags |= XFF_NO_OUTPUT;
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & XFF_QUOTE)
|
|
|
|
quote = 1;
|
|
|
|
else if (flags & XFF_NOQUOTE)
|
|
|
|
quote = 0;
|
|
|
|
else if (flen == 0) {
|
|
|
|
quote = 0;
|
|
|
|
format = "true"; /* JSON encodes empty tags as a boolean true */
|
|
|
|
flen = 4;
|
|
|
|
} else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
|
|
|
|
quote = 1;
|
|
|
|
else
|
|
|
|
quote = 0;
|
|
|
|
|
|
|
|
if (encoding) {
|
|
|
|
format = encoding;
|
|
|
|
flen = elen;
|
|
|
|
} else {
|
|
|
|
char *enc = alloca(flen + 1);
|
|
|
|
memcpy(enc, format, flen);
|
|
|
|
enc[flen] = '\0';
|
|
|
|
format = xo_fix_encoding(xop, enc);
|
|
|
|
flen = strlen(format);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nlen == 0) {
|
|
|
|
static char missing[] = "missing-field-name";
|
|
|
|
xo_failure(xop, "missing field name: %s", format);
|
|
|
|
name = missing;
|
|
|
|
nlen = sizeof(missing) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned name_offset = xo_buf_offset(&xop->xo_data);
|
|
|
|
xo_data_append(xop, name, nlen);
|
|
|
|
xo_data_append(xop, "", 1);
|
|
|
|
|
|
|
|
unsigned value_offset = xo_buf_offset(&xop->xo_data);
|
|
|
|
xo_do_format_field(xop, NULL, format, flen, flags);
|
|
|
|
xo_data_append(xop, "", 1);
|
|
|
|
|
|
|
|
xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
|
|
|
|
xo_buf_data(&xop->xo_data, name_offset),
|
|
|
|
xo_buf_data(&xop->xo_data, value_offset));
|
|
|
|
xo_buf_reset(&xop->xo_data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *str = xfip->xfi_content;
|
|
|
|
unsigned len = xfip->xfi_clen;
|
|
|
|
const char *fmt = xfip->xfi_format;
|
|
|
|
unsigned flen = xfip->xfi_flen;
|
|
|
|
|
|
|
|
/* Start by discarding previous domain */
|
|
|
|
if (xop->xo_gt_domain) {
|
|
|
|
xo_free(xop->xo_gt_domain);
|
|
|
|
xop->xo_gt_domain = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* An empty {G:} means no domainname */
|
|
|
|
if (len == 0 && flen == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int start_offset = -1;
|
|
|
|
if (len == 0 && flen != 0) {
|
|
|
|
/* Need to do format the data to get the domainname from args */
|
|
|
|
start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen, 0);
|
|
|
|
|
|
|
|
int end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
|
|
|
|
len = end_offset - start_offset;
|
|
|
|
str = xop->xo_data.xb_bufp + start_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
xop->xo_gt_domain = xo_strndup(str, len);
|
|
|
|
|
|
|
|
/* Reset the current buffer point to avoid emitting the name as output */
|
|
|
|
if (start_offset >= 0)
|
|
|
|
xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_content (xo_handle_t *xop, const char *class_name,
|
|
|
|
const char *tag_name,
|
|
|
|
const char *str, int len, const char *fmt, int flen,
|
|
|
|
xo_xff_flags_t flags)
|
|
|
|
{
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
if (len)
|
|
|
|
xo_data_append_content(xop, str, len, flags);
|
|
|
|
else
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen, flags);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
if (len == 0) {
|
|
|
|
str = fmt;
|
|
|
|
len = flen;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
if (tag_name) {
|
|
|
|
if (len == 0) {
|
|
|
|
str = fmt;
|
|
|
|
len = flen;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_open_container_h(xop, tag_name);
|
|
|
|
xo_format_value(xop, "message", 7, str, len, NULL, 0, flags);
|
|
|
|
xo_close_container_h(xop, tag_name);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Even though we don't care about labels, we need to do
|
|
|
|
* enough parsing work to skip over the right bits of xo_vap.
|
|
|
|
*/
|
|
|
|
if (len == 0)
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen,
|
|
|
|
flags | XFF_NO_OUTPUT);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
if (len == 0)
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen,
|
|
|
|
flags | XFF_NO_OUTPUT);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *xo_color_names[] = {
|
|
|
|
"default", /* XO_COL_DEFAULT */
|
|
|
|
"black", /* XO_COL_BLACK */
|
|
|
|
"red", /* XO_CLOR_RED */
|
|
|
|
"green", /* XO_COL_GREEN */
|
|
|
|
"yellow", /* XO_COL_YELLOW */
|
|
|
|
"blue", /* XO_COL_BLUE */
|
|
|
|
"magenta", /* XO_COL_MAGENTA */
|
|
|
|
"cyan", /* XO_COL_CYAN */
|
|
|
|
"white", /* XO_COL_WHITE */
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_color_find (const char *str)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; xo_color_names[i]; i++) {
|
|
|
|
if (strcmp(xo_color_names[i], str) == 0)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *xo_effect_names[] = {
|
|
|
|
"reset", /* XO_EFF_RESET */
|
|
|
|
"normal", /* XO_EFF_NORMAL */
|
|
|
|
"bold", /* XO_EFF_BOLD */
|
|
|
|
"underline", /* XO_EFF_UNDERLINE */
|
|
|
|
"inverse", /* XO_EFF_INVERSE */
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *xo_effect_on_codes[] = {
|
|
|
|
"0", /* XO_EFF_RESET */
|
|
|
|
"0", /* XO_EFF_NORMAL */
|
|
|
|
"1", /* XO_EFF_BOLD */
|
|
|
|
"4", /* XO_EFF_UNDERLINE */
|
|
|
|
"7", /* XO_EFF_INVERSE */
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
* See comment below re: joy of terminal standards. These can
|
|
|
|
* be use by just adding:
|
|
|
|
* + if (newp->xoc_effects & bit)
|
|
|
|
* code = xo_effect_on_codes[i];
|
|
|
|
* + else
|
|
|
|
* + code = xo_effect_off_codes[i];
|
|
|
|
* in xo_color_handle_text.
|
|
|
|
*/
|
|
|
|
static const char *xo_effect_off_codes[] = {
|
|
|
|
"0", /* XO_EFF_RESET */
|
|
|
|
"0", /* XO_EFF_NORMAL */
|
|
|
|
"21", /* XO_EFF_BOLD */
|
|
|
|
"24", /* XO_EFF_UNDERLINE */
|
|
|
|
"27", /* XO_EFF_INVERSE */
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
#endif /* 0 */
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_effect_find (const char *str)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; xo_effect_names[i]; i++) {
|
|
|
|
if (strcmp(xo_effect_names[i], str) == 0)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
|
|
|
|
{
|
|
|
|
#ifdef LIBXO_TEXT_ONLY
|
|
|
|
return;
|
|
|
|
#endif /* LIBXO_TEXT_ONLY */
|
|
|
|
|
|
|
|
char *cp, *ep, *np, *xp;
|
|
|
|
int len = strlen(str);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Possible tokens: colors, bg-colors, effects, no-effects, "reset".
|
|
|
|
*/
|
|
|
|
for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
|
|
|
|
/* Trim leading whitespace */
|
|
|
|
while (isspace((int) *cp))
|
|
|
|
cp += 1;
|
|
|
|
|
|
|
|
np = strchr(cp, ',');
|
|
|
|
if (np)
|
|
|
|
*np++ = '\0';
|
|
|
|
|
|
|
|
/* Trim trailing whitespace */
|
|
|
|
xp = cp + strlen(cp) - 1;
|
|
|
|
while (isspace(*xp) && xp > cp)
|
|
|
|
*xp-- = '\0';
|
|
|
|
|
|
|
|
if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
|
|
|
|
rc = xo_color_find(cp + 3);
|
|
|
|
if (rc < 0)
|
|
|
|
goto unknown;
|
|
|
|
|
|
|
|
xocp->xoc_col_fg = rc;
|
|
|
|
|
|
|
|
} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
|
|
|
|
rc = xo_color_find(cp + 3);
|
|
|
|
if (rc < 0)
|
|
|
|
goto unknown;
|
|
|
|
xocp->xoc_col_bg = rc;
|
|
|
|
|
|
|
|
} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
|
|
|
|
rc = xo_effect_find(cp + 3);
|
|
|
|
if (rc < 0)
|
|
|
|
goto unknown;
|
|
|
|
xocp->xoc_effects &= ~(1 << rc);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
rc = xo_effect_find(cp);
|
|
|
|
if (rc < 0)
|
|
|
|
goto unknown;
|
|
|
|
xocp->xoc_effects |= 1 << rc;
|
|
|
|
|
|
|
|
switch (1 << rc) {
|
|
|
|
case XO_EFF_RESET:
|
|
|
|
xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
|
|
|
|
/* Note: not "|=" since we want to wipe out the old value */
|
|
|
|
xocp->xoc_effects = XO_EFF_RESET;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_EFF_NORMAL:
|
|
|
|
xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
|
|
|
|
| XO_EFF_INVERSE | XO_EFF_NORMAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
unknown:
|
|
|
|
if (XOF_ISSET(xop, XOF_WARN))
|
|
|
|
xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
xo_colors_enabled (xo_handle_t *xop UNUSED)
|
|
|
|
{
|
|
|
|
#ifdef LIBXO_TEXT_ONLY
|
|
|
|
return 0;
|
|
|
|
#else /* LIBXO_TEXT_ONLY */
|
|
|
|
return XOF_ISSET(xop, XOF_COLOR);
|
|
|
|
#endif /* LIBXO_TEXT_ONLY */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
char *cp = buf, *ep = buf + sizeof(buf);
|
|
|
|
unsigned i, bit;
|
|
|
|
xo_colors_t *oldp = &xop->xo_colors;
|
2016-04-12 22:54:19 +00:00
|
|
|
const char *code;
|
2016-03-16 23:06:34 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the buffer with an escape. We don't want to add the '['
|
|
|
|
* now, since we let xo_effect_text_add unconditionally add the ';'.
|
|
|
|
* We'll replace the first ';' with a '[' when we're done.
|
|
|
|
*/
|
|
|
|
*cp++ = 0x1b; /* Escape */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Terminals were designed back in the age before "certainty" was
|
|
|
|
* invented, when standards were more what you'd call "guidelines"
|
|
|
|
* than actual rules. Anyway we can't depend on them to operate
|
|
|
|
* correctly. So when display attributes are changed, we punt,
|
|
|
|
* reseting them all and turning back on the ones we want to keep.
|
|
|
|
* Longer, but should be completely reliable. Savvy?
|
|
|
|
*/
|
|
|
|
if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
|
|
|
|
newp->xoc_effects |= XO_EFF_RESET;
|
|
|
|
oldp->xoc_effects = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
|
|
|
|
if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
code = xo_effect_on_codes[i];
|
|
|
|
|
|
|
|
cp += snprintf(cp, ep - cp, ";%s", code);
|
|
|
|
if (cp >= ep)
|
|
|
|
return; /* Should not occur */
|
|
|
|
|
|
|
|
if (bit == XO_EFF_RESET) {
|
|
|
|
/* Mark up the old value so we can detect current values as new */
|
|
|
|
oldp->xoc_effects = 0;
|
|
|
|
oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newp->xoc_col_fg != oldp->xoc_col_fg) {
|
|
|
|
cp += snprintf(cp, ep - cp, ";3%u",
|
|
|
|
(newp->xoc_col_fg != XO_COL_DEFAULT)
|
|
|
|
? newp->xoc_col_fg - 1 : 9);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newp->xoc_col_bg != oldp->xoc_col_bg) {
|
|
|
|
cp += snprintf(cp, ep - cp, ";4%u",
|
|
|
|
(newp->xoc_col_bg != XO_COL_DEFAULT)
|
|
|
|
? newp->xoc_col_bg - 1 : 9);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cp - buf != 1 && cp < ep - 3) {
|
|
|
|
buf[1] = '['; /* Overwrite leading ';' */
|
|
|
|
*cp++ = 'm';
|
|
|
|
*cp = '\0';
|
|
|
|
xo_buf_append(&xop->xo_data, buf, cp - buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
|
|
|
|
{
|
|
|
|
xo_colors_t *oldp = &xop->xo_colors;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* HTML colors are mostly trivial: fill in xo_color_buf with
|
|
|
|
* a set of class tags representing the colors and effects.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* If nothing changed, then do nothing */
|
|
|
|
if (oldp->xoc_effects == newp->xoc_effects
|
|
|
|
&& oldp->xoc_col_fg == newp->xoc_col_fg
|
|
|
|
&& oldp->xoc_col_bg == newp->xoc_col_bg)
|
|
|
|
return;
|
|
|
|
|
|
|
|
unsigned i, bit;
|
|
|
|
xo_buffer_t *xbp = &xop->xo_color_buf;
|
|
|
|
|
|
|
|
xo_buf_reset(xbp); /* We rebuild content after each change */
|
|
|
|
|
|
|
|
for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
|
|
|
|
if (!(newp->xoc_effects & bit))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
xo_buf_append_str(xbp, " effect-");
|
|
|
|
xo_buf_append_str(xbp, xo_effect_names[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *fg = NULL;
|
|
|
|
const char *bg = NULL;
|
|
|
|
|
|
|
|
if (newp->xoc_col_fg != XO_COL_DEFAULT)
|
|
|
|
fg = xo_color_names[newp->xoc_col_fg];
|
|
|
|
if (newp->xoc_col_bg != XO_COL_DEFAULT)
|
|
|
|
bg = xo_color_names[newp->xoc_col_bg];
|
|
|
|
|
|
|
|
if (newp->xoc_effects & XO_EFF_INVERSE) {
|
|
|
|
const char *tmp = fg;
|
|
|
|
fg = bg;
|
|
|
|
bg = tmp;
|
|
|
|
if (fg == NULL)
|
|
|
|
fg = "inverse";
|
|
|
|
if (bg == NULL)
|
|
|
|
bg = "inverse";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fg) {
|
|
|
|
xo_buf_append_str(xbp, " color-fg-");
|
|
|
|
xo_buf_append_str(xbp, fg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bg) {
|
|
|
|
xo_buf_append_str(xbp, " color-bg-");
|
|
|
|
xo_buf_append_str(xbp, bg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *str = xfip->xfi_content;
|
|
|
|
unsigned len = xfip->xfi_clen;
|
|
|
|
const char *fmt = xfip->xfi_format;
|
|
|
|
unsigned flen = xfip->xfi_flen;
|
|
|
|
|
|
|
|
xo_buffer_t xb;
|
|
|
|
|
|
|
|
/* If the string is static and we've in an encoding style, bail */
|
|
|
|
if (len != 0 && xo_style_is_encoding(xop))
|
|
|
|
return;
|
|
|
|
|
|
|
|
xo_buf_init(&xb);
|
|
|
|
|
|
|
|
if (len)
|
|
|
|
xo_buf_append(&xb, str, len);
|
|
|
|
else if (flen)
|
|
|
|
xo_do_format_field(xop, &xb, fmt, flen, 0);
|
|
|
|
else
|
|
|
|
xo_buf_append(&xb, "reset", 6); /* Default if empty */
|
|
|
|
|
|
|
|
if (xo_colors_enabled(xop)) {
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
xo_buf_append(&xb, "", 1);
|
|
|
|
|
|
|
|
xo_colors_t xoc = xop->xo_colors;
|
|
|
|
xo_colors_parse(xop, &xoc, xb.xb_bufp);
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_TEXT) {
|
|
|
|
/*
|
|
|
|
* Text mode means emitting the colors as ANSI character
|
|
|
|
* codes. This will allow people who like colors to have
|
|
|
|
* colors. The issue is, of course conflicting with the
|
|
|
|
* user's perfectly reasonable color scheme. Which leads
|
|
|
|
* to the hell of LSCOLORS, where even app need to have
|
|
|
|
* customization hooks for adjusting colors. Instead we
|
|
|
|
* provide a simpler-but-still-annoying answer where one
|
|
|
|
* can map colors to other colors.
|
|
|
|
*/
|
|
|
|
xo_colors_handle_text(xop, &xoc);
|
|
|
|
xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* HTML output is wrapped in divs, so the color information
|
|
|
|
* must appear in every div until cleared. Most pathetic.
|
|
|
|
* Most unavoidable.
|
|
|
|
*/
|
|
|
|
xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
|
|
|
|
xo_colors_handle_html(xop, &xoc);
|
|
|
|
}
|
|
|
|
|
|
|
|
xop->xo_colors = xoc;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
/*
|
|
|
|
* Nothing to do; we did all that work just to clear the stack of
|
|
|
|
* formatting arguments.
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_cleanup(&xb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *str = xfip->xfi_content;
|
|
|
|
unsigned len = xfip->xfi_clen;
|
|
|
|
const char *fmt = xfip->xfi_format;
|
|
|
|
unsigned flen = xfip->xfi_flen;
|
|
|
|
xo_xff_flags_t flags = xfip->xfi_flags;
|
|
|
|
|
|
|
|
static char units_start_xml[] = " units=\"";
|
|
|
|
static char units_start_html[] = " data-units=\"";
|
|
|
|
|
|
|
|
if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
|
|
|
|
xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
int start = xop->xo_units_offset;
|
|
|
|
int stop = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_XML)
|
|
|
|
xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
|
|
|
|
else if (xo_style(xop) == XO_STYLE_HTML)
|
|
|
|
xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (len)
|
|
|
|
xo_data_escape(xop, str, len);
|
|
|
|
else
|
|
|
|
xo_do_format_field(xop, NULL, fmt, flen, flags);
|
|
|
|
|
|
|
|
xo_buf_append(xbp, "\"", 1);
|
|
|
|
|
|
|
|
int now = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
int delta = now - stop;
|
|
|
|
if (delta <= 0) { /* Strange; no output to move */
|
|
|
|
xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we're in it alright. We've need to insert the unit value
|
|
|
|
* we just created into the right spot. We make a local copy,
|
|
|
|
* move it and then insert our copy. We know there's room in the
|
|
|
|
* buffer, since we're just moving this around.
|
|
|
|
*/
|
|
|
|
char *buf = alloca(delta);
|
|
|
|
|
|
|
|
memcpy(buf, xbp->xb_bufp + stop, delta);
|
|
|
|
memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
|
|
|
|
memmove(xbp->xb_bufp + start, buf, delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *str = xfip->xfi_content;
|
|
|
|
unsigned len = xfip->xfi_clen;
|
|
|
|
const char *fmt = xfip->xfi_format;
|
|
|
|
unsigned flen = xfip->xfi_flen;
|
|
|
|
|
|
|
|
long width = 0;
|
|
|
|
char *bp;
|
|
|
|
char *cp;
|
|
|
|
|
|
|
|
if (len) {
|
|
|
|
bp = alloca(len + 1); /* Make local NUL-terminated copy of str */
|
|
|
|
memcpy(bp, str, len);
|
|
|
|
bp[len] = '\0';
|
|
|
|
|
|
|
|
width = strtol(bp, &cp, 0);
|
|
|
|
if (width == LONG_MIN || width == LONG_MAX
|
|
|
|
|| bp == cp || *cp != '\0' ) {
|
|
|
|
width = 0;
|
|
|
|
xo_failure(xop, "invalid width for anchor: '%s'", bp);
|
|
|
|
}
|
|
|
|
} else if (flen) {
|
|
|
|
if (flen != 2 || strncmp("%d", fmt, flen) != 0)
|
|
|
|
xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
|
|
|
|
width = va_arg(xop->xo_vap, int);
|
|
|
|
}
|
|
|
|
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_anchor_clear (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
XOIF_CLEAR(xop, XOIF_ANCHOR);
|
|
|
|
xop->xo_anchor_offset = 0;
|
|
|
|
xop->xo_anchor_columns = 0;
|
|
|
|
xop->xo_anchor_min_width = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An anchor is a marker used to delay field width implications.
|
|
|
|
* Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
|
|
|
|
* We are looking for output like " 1/4/5"
|
|
|
|
*
|
|
|
|
* To make this work, we record the anchor and then return to
|
|
|
|
* format it when the end anchor tag is seen.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (XOIF_ISSET(xop, XOIF_ANCHOR))
|
|
|
|
xo_failure(xop, "the anchor already recording is discarded");
|
|
|
|
|
|
|
|
XOIF_SET(xop, XOIF_ANCHOR);
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
xop->xo_anchor_columns = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we find the width, if possible. If it's not there,
|
|
|
|
* we'll get it on the end anchor.
|
|
|
|
*/
|
|
|
|
xop->xo_anchor_min_width = xo_find_width(xop, xfip);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
|
|
|
|
xo_failure(xop, "no start anchor");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
|
|
|
|
|
|
|
|
int width = xo_find_width(xop, xfip);
|
|
|
|
if (width == 0)
|
|
|
|
width = xop->xo_anchor_min_width;
|
|
|
|
|
|
|
|
if (width == 0) /* No width given; nothing to do */
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
int start = xop->xo_anchor_offset;
|
|
|
|
int stop = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
int abswidth = (width > 0) ? width : -width;
|
|
|
|
int blen = abswidth - xop->xo_anchor_columns;
|
|
|
|
|
|
|
|
if (blen <= 0) /* Already over width */
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
if (abswidth > XO_MAX_ANCHOR_WIDTH) {
|
|
|
|
xo_failure(xop, "width over %u are not supported",
|
|
|
|
XO_MAX_ANCHOR_WIDTH);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make a suitable padding field and emit it */
|
|
|
|
char *buf = alloca(blen);
|
|
|
|
memset(buf, ' ', blen);
|
|
|
|
xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
|
|
|
|
|
|
|
|
if (width < 0) /* Already left justified */
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
int now = xbp->xb_curp - xbp->xb_bufp;
|
|
|
|
int delta = now - stop;
|
|
|
|
if (delta <= 0) /* Strange; no output to move */
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we're in it alright. We've need to insert the padding data
|
|
|
|
* we just created (which might be an HTML <div> or text) before
|
|
|
|
* the formatted data. We make a local copy, move it and then
|
|
|
|
* insert our copy. We know there's room in the buffer, since
|
|
|
|
* we're just moving this around.
|
|
|
|
*/
|
|
|
|
if (delta > blen)
|
|
|
|
buf = alloca(delta); /* Expand buffer if needed */
|
|
|
|
|
|
|
|
memcpy(buf, xbp->xb_bufp + stop, delta);
|
|
|
|
memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
|
|
|
|
memmove(xbp->xb_bufp + start, buf, delta);
|
|
|
|
|
|
|
|
done:
|
|
|
|
xo_anchor_clear(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
xo_class_name (int ftype)
|
|
|
|
{
|
|
|
|
switch (ftype) {
|
|
|
|
case 'D': return "decoration";
|
|
|
|
case 'E': return "error";
|
|
|
|
case 'L': return "label";
|
|
|
|
case 'N': return "note";
|
|
|
|
case 'P': return "padding";
|
|
|
|
case 'W': return "warning";
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
xo_tag_name (int ftype)
|
|
|
|
{
|
|
|
|
switch (ftype) {
|
|
|
|
case 'E': return "__error";
|
|
|
|
case 'W': return "__warning";
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_role_wants_default_format (int ftype)
|
|
|
|
{
|
|
|
|
switch (ftype) {
|
|
|
|
/* These roles can be completely empty and/or without formatting */
|
|
|
|
case 'C':
|
|
|
|
case 'G':
|
|
|
|
case '[':
|
|
|
|
case ']':
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static xo_mapping_t xo_role_names[] = {
|
|
|
|
{ 'C', "color" },
|
|
|
|
{ 'D', "decoration" },
|
|
|
|
{ 'E', "error" },
|
|
|
|
{ 'L', "label" },
|
|
|
|
{ 'N', "note" },
|
|
|
|
{ 'P', "padding" },
|
|
|
|
{ 'T', "title" },
|
|
|
|
{ 'U', "units" },
|
|
|
|
{ 'V', "value" },
|
|
|
|
{ 'W', "warning" },
|
|
|
|
{ '[', "start-anchor" },
|
|
|
|
{ ']', "stop-anchor" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
#define XO_ROLE_EBRACE '{' /* Escaped braces */
|
|
|
|
#define XO_ROLE_TEXT '+'
|
|
|
|
#define XO_ROLE_NEWLINE '\n'
|
|
|
|
|
|
|
|
static xo_mapping_t xo_modifier_names[] = {
|
|
|
|
{ XFF_COLON, "colon" },
|
|
|
|
{ XFF_COMMA, "comma" },
|
|
|
|
{ XFF_DISPLAY_ONLY, "display" },
|
|
|
|
{ XFF_ENCODE_ONLY, "encoding" },
|
|
|
|
{ XFF_GT_FIELD, "gettext" },
|
|
|
|
{ XFF_HUMANIZE, "humanize" },
|
|
|
|
{ XFF_HUMANIZE, "hn" },
|
|
|
|
{ XFF_HN_SPACE, "hn-space" },
|
|
|
|
{ XFF_HN_DECIMAL, "hn-decimal" },
|
|
|
|
{ XFF_HN_1000, "hn-1000" },
|
|
|
|
{ XFF_KEY, "key" },
|
|
|
|
{ XFF_LEAF_LIST, "leaf-list" },
|
|
|
|
{ XFF_LEAF_LIST, "list" },
|
|
|
|
{ XFF_NOQUOTE, "no-quotes" },
|
|
|
|
{ XFF_NOQUOTE, "no-quote" },
|
|
|
|
{ XFF_GT_PLURAL, "plural" },
|
|
|
|
{ XFF_QUOTE, "quotes" },
|
|
|
|
{ XFF_QUOTE, "quote" },
|
|
|
|
{ XFF_TRIM_WS, "trim" },
|
|
|
|
{ XFF_WS, "white" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef NOT_NEEDED_YET
|
|
|
|
static xo_mapping_t xo_modifier_short_names[] = {
|
|
|
|
{ XFF_COLON, "c" },
|
|
|
|
{ XFF_DISPLAY_ONLY, "d" },
|
|
|
|
{ XFF_ENCODE_ONLY, "e" },
|
|
|
|
{ XFF_GT_FIELD, "g" },
|
|
|
|
{ XFF_HUMANIZE, "h" },
|
|
|
|
{ XFF_KEY, "k" },
|
|
|
|
{ XFF_LEAF_LIST, "l" },
|
|
|
|
{ XFF_NOQUOTE, "n" },
|
|
|
|
{ XFF_GT_PLURAL, "p" },
|
|
|
|
{ XFF_QUOTE, "q" },
|
|
|
|
{ XFF_TRIM_WS, "t" },
|
|
|
|
{ XFF_WS, "w" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
#endif /* NOT_NEEDED_YET */
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
|
|
|
|
{
|
|
|
|
int rc = 1;
|
|
|
|
const char *cp;
|
|
|
|
|
|
|
|
for (cp = fmt; *cp; cp++)
|
|
|
|
if (*cp == '{' || *cp == '\n')
|
|
|
|
rc += 1;
|
|
|
|
|
|
|
|
return rc * 2 + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The field format is:
|
|
|
|
* '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
|
|
|
|
* Roles are optional and include the following field types:
|
|
|
|
* 'D': decoration; something non-text and non-data (colons, commmas)
|
|
|
|
* 'E': error message
|
|
|
|
* 'G': gettext() the entire string; optional domainname as content
|
|
|
|
* 'L': label; text preceding data
|
|
|
|
* 'N': note; text following data
|
|
|
|
* 'P': padding; whitespace
|
|
|
|
* 'T': Title, where 'content' is a column title
|
|
|
|
* 'U': Units, where 'content' is the unit label
|
|
|
|
* 'V': value, where 'content' is the name of the field (the default)
|
|
|
|
* 'W': warning message
|
|
|
|
* '[': start a section of anchored text
|
|
|
|
* ']': end a section of anchored text
|
|
|
|
* The following modifiers are also supported:
|
|
|
|
* 'c': flag: emit a colon after the label
|
|
|
|
* 'd': field is only emitted for display styles (text and html)
|
|
|
|
* 'e': field is only emitted for encoding styles (xml and json)
|
|
|
|
* 'g': gettext() the field
|
|
|
|
* 'h': humanize a numeric value (only for display styles)
|
|
|
|
* 'k': this field is a key, suitable for XPath predicates
|
|
|
|
* 'l': a leaf-list, a simple list of values
|
|
|
|
* 'n': no quotes around this field
|
|
|
|
* 'p': the field has plural gettext semantics (ngettext)
|
|
|
|
* 'q': add quotes around this field
|
|
|
|
* 't': trim whitespace around the value
|
|
|
|
* 'w': emit a blank after the label
|
|
|
|
* The print-fmt and encode-fmt strings is the printf-style formating
|
|
|
|
* for this data. JSON and XML will use the encoding-fmt, if present.
|
|
|
|
* If the encode-fmt is not provided, it defaults to the print-fmt.
|
|
|
|
* If the print-fmt is not provided, it defaults to 's'.
|
|
|
|
*/
|
|
|
|
static const char *
|
|
|
|
xo_parse_roles (xo_handle_t *xop, const char *fmt,
|
|
|
|
const char *basep, xo_field_info_t *xfip)
|
|
|
|
{
|
|
|
|
const char *sp;
|
|
|
|
unsigned ftype = 0;
|
|
|
|
xo_xff_flags_t flags = 0;
|
|
|
|
uint8_t fnum = 0;
|
|
|
|
|
|
|
|
for (sp = basep; sp; sp++) {
|
|
|
|
if (*sp == ':' || *sp == '/' || *sp == '}')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (*sp == '\\') {
|
|
|
|
if (sp[1] == '\0') {
|
|
|
|
xo_failure(xop, "backslash at the end of string");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Anything backslashed is ignored */
|
|
|
|
sp += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*sp == ',') {
|
|
|
|
const char *np;
|
|
|
|
for (np = ++sp; *np; np++)
|
|
|
|
if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
|
|
|
|
break;
|
|
|
|
|
|
|
|
int slen = np - sp;
|
|
|
|
if (slen > 0) {
|
|
|
|
xo_xff_flags_t value;
|
|
|
|
|
|
|
|
value = xo_name_lookup(xo_role_names, sp, slen);
|
|
|
|
if (value)
|
|
|
|
ftype = value;
|
|
|
|
else {
|
|
|
|
value = xo_name_lookup(xo_modifier_names, sp, slen);
|
|
|
|
if (value)
|
|
|
|
flags |= value;
|
|
|
|
else
|
|
|
|
xo_failure(xop, "unknown keyword ignored: '%.*s'",
|
|
|
|
slen, sp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sp = np - 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (*sp) {
|
|
|
|
case 'C':
|
|
|
|
case 'D':
|
|
|
|
case 'E':
|
|
|
|
case 'G':
|
|
|
|
case 'L':
|
|
|
|
case 'N':
|
|
|
|
case 'P':
|
|
|
|
case 'T':
|
|
|
|
case 'U':
|
|
|
|
case 'V':
|
|
|
|
case 'W':
|
|
|
|
case '[':
|
|
|
|
case ']':
|
|
|
|
if (ftype != 0) {
|
|
|
|
xo_failure(xop, "field descriptor uses multiple types: '%s'",
|
|
|
|
xo_printable(fmt));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ftype = *sp;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
fnum = (fnum * 10) + (*sp - '0');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c':
|
|
|
|
flags |= XFF_COLON;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'd':
|
|
|
|
flags |= XFF_DISPLAY_ONLY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
flags |= XFF_ENCODE_ONLY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'g':
|
|
|
|
flags |= XFF_GT_FIELD;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h':
|
|
|
|
flags |= XFF_HUMANIZE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'k':
|
|
|
|
flags |= XFF_KEY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'l':
|
|
|
|
flags |= XFF_LEAF_LIST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
flags |= XFF_NOQUOTE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
|
|
|
flags |= XFF_GT_PLURAL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'q':
|
|
|
|
flags |= XFF_QUOTE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 't':
|
|
|
|
flags |= XFF_TRIM_WS;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'w':
|
|
|
|
flags |= XFF_WS;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
|
|
|
|
xo_printable(fmt));
|
|
|
|
/*
|
|
|
|
* No good answer here; a bad format will likely
|
|
|
|
* mean a core file. We just return and hope
|
|
|
|
* the caller notices there's no output, and while
|
|
|
|
* that seems, well, bad, there's nothing better.
|
|
|
|
*/
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ftype == 'N' || ftype == 'U') {
|
|
|
|
if (flags & XFF_COLON) {
|
|
|
|
xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
|
|
|
|
"'%s'", xo_printable(fmt));
|
|
|
|
flags &= ~XFF_COLON;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xfip->xfi_flags = flags;
|
|
|
|
xfip->xfi_ftype = ftype ?: 'V';
|
|
|
|
xfip->xfi_fnum = fnum;
|
|
|
|
|
|
|
|
return sp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Number any remaining fields that need numbers. Note that some
|
|
|
|
* field types (text, newline, escaped braces) never get numbers.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
|
|
|
|
const char *fmt UNUSED,
|
|
|
|
xo_field_info_t *fields)
|
|
|
|
{
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
unsigned fnum, max_fields;
|
|
|
|
uint64_t bits = 0;
|
|
|
|
|
|
|
|
/* First make a list of add the explicitly used bits */
|
|
|
|
for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
|
|
|
|
switch (xfip->xfi_ftype) {
|
|
|
|
case XO_ROLE_NEWLINE: /* Don't get numbered */
|
|
|
|
case XO_ROLE_TEXT:
|
|
|
|
case XO_ROLE_EBRACE:
|
|
|
|
case 'G':
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
fnum += 1;
|
|
|
|
if (fnum >= 63)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (xfip->xfi_fnum)
|
|
|
|
bits |= 1 << xfip->xfi_fnum;
|
|
|
|
}
|
|
|
|
|
|
|
|
max_fields = fnum;
|
|
|
|
|
|
|
|
for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
|
|
|
|
switch (xfip->xfi_ftype) {
|
|
|
|
case XO_ROLE_NEWLINE: /* Don't get numbered */
|
|
|
|
case XO_ROLE_TEXT:
|
|
|
|
case XO_ROLE_EBRACE:
|
|
|
|
case 'G':
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xfip->xfi_fnum != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Find the next unassigned field */
|
|
|
|
for (fnum++; bits & (1 << fnum); fnum++)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (fnum > max_fields)
|
|
|
|
break;
|
|
|
|
|
|
|
|
xfip->xfi_fnum = fnum; /* Mark the field number */
|
|
|
|
bits |= 1 << fnum; /* Mark it used */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The format string uses field numbers, so we need to whiffle thru it
|
|
|
|
* and make sure everything's sane and lovely.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
|
|
|
|
xo_field_info_t *fields, unsigned num_fields)
|
|
|
|
{
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
unsigned field, fnum;
|
|
|
|
uint64_t bits = 0;
|
|
|
|
|
|
|
|
for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
|
|
|
|
/* Fields default to 1:1 with natural position */
|
|
|
|
if (xfip->xfi_fnum == 0)
|
|
|
|
xfip->xfi_fnum = field + 1;
|
|
|
|
else if (xfip->xfi_fnum > num_fields) {
|
|
|
|
xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
|
|
|
|
if (fnum < 64) { /* Only test what fits */
|
|
|
|
if (bits & (1 << fnum)) {
|
|
|
|
xo_failure(xop, "field number %u reused: '%s'",
|
|
|
|
xfip->xfi_fnum, fmt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
bits |= 1 << fnum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
|
|
|
|
unsigned num_fields, const char *fmt)
|
|
|
|
{
|
|
|
|
static const char default_format[] = "%s";
|
|
|
|
const char *cp, *sp, *ep, *basep;
|
|
|
|
unsigned field = 0;
|
|
|
|
xo_field_info_t *xfip = fields;
|
|
|
|
unsigned seen_fnum = 0;
|
|
|
|
|
|
|
|
for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
|
|
|
|
xfip->xfi_start = cp;
|
|
|
|
|
|
|
|
if (*cp == '\n') {
|
|
|
|
xfip->xfi_ftype = XO_ROLE_NEWLINE;
|
|
|
|
xfip->xfi_len = 1;
|
|
|
|
cp += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*cp != '{') {
|
|
|
|
/* Normal text */
|
|
|
|
for (sp = cp; *sp; sp++) {
|
|
|
|
if (*sp == '{' || *sp == '\n')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xfip->xfi_ftype = XO_ROLE_TEXT;
|
|
|
|
xfip->xfi_content = cp;
|
|
|
|
xfip->xfi_clen = sp - cp;
|
|
|
|
xfip->xfi_next = sp;
|
|
|
|
|
|
|
|
cp = sp;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cp[1] == '{') { /* Start of {{escaped braces}} */
|
|
|
|
xfip->xfi_start = cp + 1; /* Start at second brace */
|
|
|
|
xfip->xfi_ftype = XO_ROLE_EBRACE;
|
|
|
|
|
|
|
|
cp += 2; /* Skip over _both_ characters */
|
|
|
|
for (sp = cp; *sp; sp++) {
|
|
|
|
if (*sp == '}' && sp[1] == '}')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (*sp == '\0') {
|
|
|
|
xo_failure(xop, "missing closing '}}': '%s'",
|
|
|
|
xo_printable(fmt));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
xfip->xfi_len = sp - xfip->xfi_start + 1;
|
|
|
|
|
|
|
|
/* Move along the string, but don't run off the end */
|
|
|
|
if (*sp == '}' && sp[1] == '}')
|
|
|
|
sp += 2;
|
|
|
|
cp = *sp ? sp : sp;
|
|
|
|
xfip->xfi_next = cp;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We are looking at the start of a field definition */
|
|
|
|
xfip->xfi_start = basep = cp + 1;
|
|
|
|
|
|
|
|
const char *format = NULL;
|
|
|
|
int flen = 0;
|
|
|
|
|
|
|
|
/* Looking at roles and modifiers */
|
|
|
|
sp = xo_parse_roles(xop, fmt, basep, xfip);
|
|
|
|
if (sp == NULL) {
|
|
|
|
/* xo_failure has already been called */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xfip->xfi_fnum)
|
|
|
|
seen_fnum = 1;
|
|
|
|
|
|
|
|
/* Looking at content */
|
|
|
|
if (*sp == ':') {
|
|
|
|
for (ep = ++sp; *sp; sp++) {
|
|
|
|
if (*sp == '}' || *sp == '/')
|
|
|
|
break;
|
|
|
|
if (*sp == '\\') {
|
|
|
|
if (sp[1] == '\0') {
|
|
|
|
xo_failure(xop, "backslash at the end of string");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
sp += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ep != sp) {
|
|
|
|
xfip->xfi_clen = sp - ep;
|
|
|
|
xfip->xfi_content = ep;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Looking at main (display) format */
|
|
|
|
if (*sp == '/') {
|
|
|
|
for (ep = ++sp; *sp; sp++) {
|
|
|
|
if (*sp == '}' || *sp == '/')
|
|
|
|
break;
|
|
|
|
if (*sp == '\\') {
|
|
|
|
if (sp[1] == '\0') {
|
|
|
|
xo_failure(xop, "backslash at the end of string");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
sp += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
flen = sp - ep;
|
|
|
|
format = ep;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Looking at encoding format */
|
|
|
|
if (*sp == '/') {
|
|
|
|
for (ep = ++sp; *sp; sp++) {
|
|
|
|
if (*sp == '}')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xfip->xfi_encoding = ep;
|
|
|
|
xfip->xfi_elen = sp - ep;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*sp != '}') {
|
|
|
|
xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
xfip->xfi_len = sp - xfip->xfi_start;
|
|
|
|
xfip->xfi_next = ++sp;
|
|
|
|
|
|
|
|
/* If we have content, then we have a default format */
|
|
|
|
if (xfip->xfi_clen || format) {
|
|
|
|
if (format) {
|
|
|
|
xfip->xfi_format = format;
|
|
|
|
xfip->xfi_flen = flen;
|
|
|
|
} else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
|
|
|
|
xfip->xfi_format = default_format;
|
|
|
|
xfip->xfi_flen = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cp = sp;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we saw a field number on at least one field, then we need
|
|
|
|
* to enforce some rules and/or guidelines.
|
|
|
|
*/
|
|
|
|
if (seen_fnum)
|
|
|
|
rc = xo_parse_field_numbers(xop, fmt, fields, field);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We are passed a pointer to a format string just past the "{G:}"
|
|
|
|
* field. We build a simplified version of the format string.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
|
|
|
|
xo_buffer_t *xbp,
|
|
|
|
xo_field_info_t *fields,
|
|
|
|
int this_field,
|
|
|
|
const char *fmt UNUSED,
|
|
|
|
xo_simplify_field_func_t field_cb)
|
|
|
|
{
|
|
|
|
unsigned ftype;
|
|
|
|
xo_xff_flags_t flags;
|
|
|
|
int field = this_field + 1;
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
char ch;
|
|
|
|
|
|
|
|
for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
|
|
|
|
ftype = xfip->xfi_ftype;
|
|
|
|
flags = xfip->xfi_flags;
|
|
|
|
|
|
|
|
if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
|
|
|
|
if (field_cb)
|
|
|
|
field_cb(xfip->xfi_content, xfip->xfi_clen,
|
|
|
|
(flags & XFF_GT_PLURAL) ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ftype) {
|
|
|
|
case 'G':
|
|
|
|
/* Ignore gettext roles */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_ROLE_NEWLINE:
|
|
|
|
xo_buf_append(xbp, "\n", 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_ROLE_EBRACE:
|
|
|
|
xo_buf_append(xbp, "{", 1);
|
|
|
|
xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
|
|
|
|
xo_buf_append(xbp, "}", 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_ROLE_TEXT:
|
|
|
|
xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_buf_append(xbp, "{", 1);
|
|
|
|
if (ftype != 'V') {
|
|
|
|
ch = ftype;
|
|
|
|
xo_buf_append(xbp, &ch, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned fnum = xfip->xfi_fnum ?: 0;
|
|
|
|
if (fnum) {
|
|
|
|
char num[12];
|
|
|
|
/* Field numbers are origin 1, not 0, following printf(3) */
|
|
|
|
snprintf(num, sizeof(num), "%u", fnum);
|
|
|
|
xo_buf_append(xbp, num, strlen(num));
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_append(xbp, ":", 1);
|
|
|
|
xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
|
|
|
|
xo_buf_append(xbp, "}", 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_buf_append(xbp, "", 1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
|
|
|
|
void
|
|
|
|
xo_dump_fields (xo_field_info_t *fields)
|
|
|
|
{
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
|
|
|
|
for (xfip = fields; xfip->xfi_ftype; xfip++) {
|
|
|
|
printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
|
|
|
|
(unsigned long) (xfip - fields), xfip->xfi_fnum,
|
|
|
|
(unsigned long) xfip->xfi_flags,
|
|
|
|
isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
|
|
|
|
xfip->xfi_ftype,
|
|
|
|
xfip->xfi_clen, xfip->xfi_content ?: "",
|
|
|
|
xfip->xfi_flen, xfip->xfi_format ?: "",
|
|
|
|
xfip->xfi_elen, xfip->xfi_encoding ?: "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_GETTEXT
|
|
|
|
/*
|
|
|
|
* Find the field that matches the given field number
|
|
|
|
*/
|
|
|
|
static xo_field_info_t *
|
|
|
|
xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
|
|
|
|
{
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
|
|
|
|
for (xfip = fields; xfip->xfi_ftype; xfip++)
|
|
|
|
if (xfip->xfi_fnum == fnum)
|
|
|
|
return xfip;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, we need to consider if the fields have been reordered,
|
|
|
|
* such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
|
|
|
|
*
|
|
|
|
* We need to rewrite the new_fields using the old fields order,
|
|
|
|
* so that we can render the message using the arguments as they
|
|
|
|
* appear on the stack. It's a lot of work, but we don't really
|
|
|
|
* want to (eventually) fall into the standard printf code which
|
|
|
|
* means using the arguments straight (and in order) from the
|
|
|
|
* varargs we were originally passed.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
|
|
|
|
xo_field_info_t *fields, unsigned max_fields)
|
|
|
|
{
|
|
|
|
xo_field_info_t tmp[max_fields];
|
|
|
|
bzero(tmp, max_fields * sizeof(tmp[0]));
|
|
|
|
|
|
|
|
unsigned fnum = 0;
|
|
|
|
xo_field_info_t *newp, *outp, *zp;
|
|
|
|
for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
|
|
|
|
switch (newp->xfi_ftype) {
|
|
|
|
case XO_ROLE_NEWLINE: /* Don't get numbered */
|
|
|
|
case XO_ROLE_TEXT:
|
|
|
|
case XO_ROLE_EBRACE:
|
|
|
|
case 'G':
|
|
|
|
*outp = *newp;
|
|
|
|
outp->xfi_renum = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
zp = xo_gettext_find_field(fields, ++fnum);
|
|
|
|
if (zp == NULL) { /* Should not occur */
|
|
|
|
*outp = *newp;
|
|
|
|
outp->xfi_renum = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
*outp = *zp;
|
|
|
|
outp->xfi_renum = newp->xfi_fnum;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We've got two lists of fields, the old list from the original
|
|
|
|
* format string and the new one from the parsed gettext reply. The
|
|
|
|
* new list has the localized words, where the old list has the
|
|
|
|
* formatting information. We need to combine them into a single list
|
|
|
|
* (the new list).
|
|
|
|
*
|
|
|
|
* If the list needs to be reordered, then we've got more serious work
|
|
|
|
* to do.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
|
|
|
|
const char *gtfmt, xo_field_info_t *old_fields,
|
|
|
|
xo_field_info_t *new_fields, unsigned new_max_fields,
|
|
|
|
int *reorderedp)
|
|
|
|
{
|
|
|
|
int reordered = 0;
|
|
|
|
xo_field_info_t *newp, *oldp, *startp = old_fields;
|
|
|
|
|
|
|
|
xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
|
|
|
|
|
|
|
|
for (newp = new_fields; newp->xfi_ftype; newp++) {
|
|
|
|
switch (newp->xfi_ftype) {
|
|
|
|
case XO_ROLE_NEWLINE:
|
|
|
|
case XO_ROLE_TEXT:
|
|
|
|
case XO_ROLE_EBRACE:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case 'V':
|
|
|
|
for (oldp = startp; oldp->xfi_ftype; oldp++) {
|
|
|
|
if (oldp->xfi_ftype != 'V')
|
|
|
|
continue;
|
|
|
|
if (newp->xfi_clen != oldp->xfi_clen
|
|
|
|
|| strncmp(newp->xfi_content, oldp->xfi_content,
|
|
|
|
oldp->xfi_clen) != 0) {
|
|
|
|
reordered = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
startp = oldp + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Didn't find it on the first pass (starting from start) */
|
|
|
|
if (oldp->xfi_ftype == 0) {
|
|
|
|
for (oldp = old_fields; oldp < startp; oldp++) {
|
|
|
|
if (oldp->xfi_ftype != 'V')
|
|
|
|
continue;
|
|
|
|
if (newp->xfi_clen != oldp->xfi_clen)
|
|
|
|
continue;
|
|
|
|
if (strncmp(newp->xfi_content, oldp->xfi_content,
|
|
|
|
oldp->xfi_clen) != 0)
|
|
|
|
continue;
|
|
|
|
reordered = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (oldp == startp) {
|
|
|
|
/* Field not found */
|
|
|
|
xo_failure(xop, "post-gettext format can't find field "
|
|
|
|
"'%.*s' in format '%s'",
|
|
|
|
newp->xfi_clen, newp->xfi_content,
|
|
|
|
xo_printable(gtfmt));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* Other fields don't have names for us to use, so if
|
|
|
|
* the types aren't the same, then we'll have to assume
|
|
|
|
* the original field is a match.
|
|
|
|
*/
|
|
|
|
for (oldp = startp; oldp->xfi_ftype; oldp++) {
|
|
|
|
if (oldp->xfi_ftype == 'V') /* Can't go past these */
|
|
|
|
break;
|
|
|
|
if (oldp->xfi_ftype == newp->xfi_ftype)
|
|
|
|
goto copy_it; /* Assumably we have a match */
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Found a match; copy over appropriate fields
|
|
|
|
*/
|
|
|
|
copy_it:
|
|
|
|
newp->xfi_flags = oldp->xfi_flags;
|
|
|
|
newp->xfi_fnum = oldp->xfi_fnum;
|
|
|
|
newp->xfi_format = oldp->xfi_format;
|
|
|
|
newp->xfi_flen = oldp->xfi_flen;
|
|
|
|
newp->xfi_encoding = oldp->xfi_encoding;
|
|
|
|
newp->xfi_elen = oldp->xfi_elen;
|
|
|
|
}
|
|
|
|
|
|
|
|
*reorderedp = reordered;
|
|
|
|
if (reordered) {
|
|
|
|
xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
|
|
|
|
xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We don't want to make gettext() calls here with a complete format
|
|
|
|
* string, since that means changing a flag would mean a
|
|
|
|
* labor-intensive re-translation expense. Instead we build a
|
|
|
|
* simplified form with a reduced level of detail, perform a lookup on
|
|
|
|
* that string and then re-insert the formating info.
|
|
|
|
*
|
|
|
|
* So something like:
|
|
|
|
* xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
|
|
|
|
* would have a lookup string of:
|
|
|
|
* "close {:fd} returned {:error} {:test}\n"
|
|
|
|
*
|
|
|
|
* We also need to handling reordering of fields, where the gettext()
|
|
|
|
* reply string uses fields in a different order than the original
|
|
|
|
* format string:
|
|
|
|
* "cluse-a {:fd} retoorned {:test}. Bork {:error} Bork. Bork.\n"
|
|
|
|
* If we have to reorder fields within the message, then things get
|
|
|
|
* complicated. See xo_gettext_rewrite_fields.
|
|
|
|
*
|
|
|
|
* Summary: i18n aighn't cheap.
|
|
|
|
*/
|
|
|
|
static const char *
|
|
|
|
xo_gettext_build_format (xo_handle_t *xop UNUSED,
|
|
|
|
xo_field_info_t *fields UNUSED,
|
|
|
|
int this_field UNUSED,
|
|
|
|
const char *fmt, char **new_fmtp)
|
|
|
|
{
|
|
|
|
if (xo_style_is_encoding(xop))
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
xo_buffer_t xb;
|
|
|
|
xo_buf_init(&xb);
|
|
|
|
|
|
|
|
if (xo_gettext_simplify_format(xop, &xb, fields,
|
|
|
|
this_field, fmt, NULL))
|
|
|
|
goto bail2;
|
|
|
|
|
|
|
|
const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
|
|
|
|
if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
|
|
|
|
goto bail2;
|
|
|
|
|
|
|
|
xo_buf_cleanup(&xb);
|
|
|
|
|
|
|
|
char *new_fmt = xo_strndup(gtfmt, -1);
|
|
|
|
if (new_fmt == NULL)
|
|
|
|
goto bail2;
|
|
|
|
|
|
|
|
*new_fmtp = new_fmt;
|
|
|
|
return new_fmt;
|
|
|
|
|
|
|
|
bail2:
|
|
|
|
xo_buf_cleanup(&xb);
|
|
|
|
bail:
|
|
|
|
*new_fmtp = NULL;
|
|
|
|
return fmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
|
|
|
|
unsigned *fstart, unsigned min_fstart,
|
|
|
|
unsigned *fend, unsigned max_fend)
|
|
|
|
{
|
|
|
|
xo_field_info_t *xfip;
|
|
|
|
char *buf;
|
|
|
|
unsigned base = fstart[min_fstart];
|
|
|
|
unsigned blen = fend[max_fend] - base;
|
|
|
|
xo_buffer_t *xbp = &xop->xo_data;
|
|
|
|
|
|
|
|
if (blen == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
buf = xo_realloc(NULL, blen);
|
|
|
|
if (buf == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
|
|
|
|
|
|
|
|
unsigned field = min_fstart, soff, doff = base, len, fnum;
|
|
|
|
xo_field_info_t *zp;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Be aware there are two competing views of "field number": we
|
|
|
|
* want the user to thing in terms of "The {1:size}" where {G:},
|
|
|
|
* newlines, escaped braces, and text don't have numbers. But is
|
|
|
|
* also the internal view, where we have an array of
|
|
|
|
* xo_field_info_t and every field have an index. fnum, fstart[]
|
|
|
|
* and fend[] are the latter, but xfi_renum is the former.
|
|
|
|
*/
|
|
|
|
for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
|
|
|
|
fnum = field;
|
|
|
|
if (xfip->xfi_renum) {
|
|
|
|
zp = xo_gettext_find_field(fields, xfip->xfi_renum);
|
|
|
|
fnum = zp ? zp - fields : field;
|
|
|
|
}
|
|
|
|
|
|
|
|
soff = fstart[fnum];
|
|
|
|
len = fend[fnum] - soff;
|
|
|
|
|
|
|
|
if (len > 0) {
|
|
|
|
soff -= base;
|
|
|
|
memcpy(xbp->xb_bufp + doff, buf + soff, len);
|
|
|
|
doff += len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_free(buf);
|
|
|
|
}
|
|
|
|
#else /* HAVE_GETTEXT */
|
|
|
|
static const char *
|
|
|
|
xo_gettext_build_format (xo_handle_t *xop UNUSED,
|
|
|
|
xo_field_info_t *fields UNUSED,
|
|
|
|
int this_field UNUSED,
|
|
|
|
const char *fmt UNUSED, char **new_fmtp)
|
|
|
|
{
|
|
|
|
*new_fmtp = NULL;
|
|
|
|
return fmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
|
|
|
|
const char *gtfmt UNUSED,
|
|
|
|
xo_field_info_t *old_fields UNUSED,
|
|
|
|
xo_field_info_t *new_fields UNUSED,
|
|
|
|
unsigned new_max_fields UNUSED,
|
|
|
|
int *reorderedp UNUSED)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
|
|
|
|
xo_field_info_t *fields UNUSED,
|
|
|
|
unsigned *fstart UNUSED, unsigned min_fstart UNUSED,
|
|
|
|
unsigned *fend UNUSED, unsigned max_fend UNUSED)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif /* HAVE_GETTEXT */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The central function for emitting libxo output.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_do_emit (xo_handle_t *xop, const char *fmt)
|
|
|
|
{
|
|
|
|
int gettext_inuse = 0;
|
|
|
|
int gettext_changed = 0;
|
|
|
|
int gettext_reordered = 0;
|
|
|
|
xo_field_info_t *new_fields = NULL;
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
int flush = XOF_ISSET(xop, XOF_FLUSH);
|
|
|
|
int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
|
|
|
|
char *new_fmt = NULL;
|
|
|
|
|
|
|
|
if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
|
|
|
|
flush_line = 0;
|
|
|
|
|
|
|
|
xop->xo_columns = 0; /* Always reset it */
|
|
|
|
xop->xo_errno = errno; /* Save for "%m" */
|
|
|
|
|
|
|
|
unsigned max_fields = xo_count_fields(xop, fmt), field;
|
|
|
|
xo_field_info_t fields[max_fields], *xfip;
|
|
|
|
|
|
|
|
bzero(fields, max_fields * sizeof(fields[0]));
|
|
|
|
|
|
|
|
if (xo_parse_fields(xop, fields, max_fields, fmt))
|
|
|
|
return -1; /* Warning already displayed */
|
|
|
|
|
|
|
|
unsigned ftype;
|
|
|
|
xo_xff_flags_t flags;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Some overhead for gettext; if the fields in the msgstr returned
|
|
|
|
* by gettext are reordered, then we need to record start and end
|
|
|
|
* for each field. We'll go ahead and render the fields in the
|
|
|
|
* normal order, but later we can then reconstruct the reordered
|
|
|
|
* fields using these fstart/fend values.
|
|
|
|
*/
|
|
|
|
unsigned flimit = max_fields * 2; /* Pessimistic limit */
|
|
|
|
unsigned min_fstart = flimit - 1;
|
|
|
|
unsigned max_fend = 0; /* Highest recorded fend[] entry */
|
|
|
|
unsigned fstart[flimit];
|
|
|
|
bzero(fstart, flimit * sizeof(fstart[0]));
|
|
|
|
unsigned fend[flimit];
|
|
|
|
bzero(fend, flimit * sizeof(fend[0]));
|
|
|
|
|
|
|
|
for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields;
|
|
|
|
xfip++, field++) {
|
|
|
|
ftype = xfip->xfi_ftype;
|
|
|
|
flags = xfip->xfi_flags;
|
|
|
|
|
|
|
|
/* Record field start offset */
|
|
|
|
if (gettext_reordered) {
|
|
|
|
fstart[field] = xo_buf_offset(&xop->xo_data);
|
|
|
|
if (min_fstart > field)
|
|
|
|
min_fstart = field;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ftype == XO_ROLE_NEWLINE) {
|
|
|
|
xo_line_close(xop);
|
|
|
|
if (flush_line && xo_flush_h(xop) < 0)
|
|
|
|
return -1;
|
|
|
|
goto bottom;
|
|
|
|
|
|
|
|
} else if (ftype == XO_ROLE_EBRACE) {
|
|
|
|
xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
|
|
|
|
goto bottom;
|
|
|
|
|
|
|
|
} else if (ftype == XO_ROLE_TEXT) {
|
|
|
|
/* Normal text */
|
|
|
|
xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
|
|
|
|
goto bottom;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Notes and units need the 'w' flag handled before the content.
|
|
|
|
*/
|
|
|
|
if (ftype == 'N' || ftype == 'U') {
|
|
|
|
if (flags & XFF_WS) {
|
|
|
|
xo_format_content(xop, "padding", NULL, " ", 1,
|
|
|
|
NULL, 0, flags);
|
|
|
|
flags &= ~XFF_WS; /* Block later handling of this */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ftype == 'V')
|
|
|
|
xo_format_value(xop, xfip->xfi_content, xfip->xfi_clen,
|
|
|
|
xfip->xfi_format, xfip->xfi_flen,
|
|
|
|
xfip->xfi_encoding, xfip->xfi_elen, flags);
|
|
|
|
else if (ftype == '[')
|
|
|
|
xo_anchor_start(xop, xfip);
|
|
|
|
else if (ftype == ']')
|
|
|
|
xo_anchor_stop(xop, xfip);
|
|
|
|
else if (ftype == 'C')
|
|
|
|
xo_format_colors(xop, xfip);
|
|
|
|
|
|
|
|
else if (ftype == 'G') {
|
|
|
|
/*
|
|
|
|
* A {G:domain} field; disect the domain name and translate
|
|
|
|
* the remaining portion of the input string. If the user
|
|
|
|
* didn't put the {G:} at the start of the format string, then
|
|
|
|
* assumably they just want us to translate the rest of it.
|
|
|
|
* Since gettext returns strings in a static buffer, we make
|
|
|
|
* a copy in new_fmt.
|
|
|
|
*/
|
|
|
|
xo_set_gettext_domain(xop, xfip);
|
|
|
|
|
|
|
|
if (!gettext_inuse) { /* Only translate once */
|
|
|
|
gettext_inuse = 1;
|
|
|
|
if (new_fmt) {
|
|
|
|
xo_free(new_fmt);
|
|
|
|
new_fmt = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_gettext_build_format(xop, fields, field,
|
|
|
|
xfip->xfi_next, &new_fmt);
|
|
|
|
if (new_fmt) {
|
|
|
|
gettext_changed = 1;
|
|
|
|
|
|
|
|
unsigned new_max_fields = xo_count_fields(xop, new_fmt);
|
|
|
|
|
|
|
|
if (++new_max_fields < max_fields)
|
|
|
|
new_max_fields = max_fields;
|
|
|
|
|
|
|
|
/* Leave a blank slot at the beginning */
|
|
|
|
int sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
|
|
|
|
new_fields = alloca(sz);
|
|
|
|
bzero(new_fields, sz);
|
|
|
|
|
|
|
|
if (!xo_parse_fields(xop, new_fields + 1,
|
|
|
|
new_max_fields, new_fmt)) {
|
|
|
|
gettext_reordered = 0;
|
|
|
|
|
|
|
|
if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
|
|
|
|
fields, new_fields + 1,
|
|
|
|
new_max_fields, &gettext_reordered)) {
|
|
|
|
|
|
|
|
if (gettext_reordered) {
|
|
|
|
if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
|
|
|
|
xo_failure(xop, "gettext finds reordered "
|
|
|
|
"fields in '%s' and '%s'",
|
|
|
|
xo_printable(fmt),
|
|
|
|
xo_printable(new_fmt));
|
|
|
|
flush_line = 0; /* Must keep at content */
|
|
|
|
XOIF_SET(xop, XOIF_REORDER);
|
|
|
|
}
|
|
|
|
|
|
|
|
field = -1; /* Will be incremented at top of loop */
|
|
|
|
xfip = new_fields;
|
|
|
|
max_fields = new_max_fields;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} else if (xfip->xfi_clen || xfip->xfi_format) {
|
|
|
|
|
|
|
|
const char *class_name = xo_class_name(ftype);
|
|
|
|
if (class_name)
|
|
|
|
xo_format_content(xop, class_name, xo_tag_name(ftype),
|
|
|
|
xfip->xfi_content, xfip->xfi_clen,
|
|
|
|
xfip->xfi_format, xfip->xfi_flen, flags);
|
|
|
|
else if (ftype == 'T')
|
|
|
|
xo_format_title(xop, xfip);
|
|
|
|
else if (ftype == 'U')
|
|
|
|
xo_format_units(xop, xfip);
|
|
|
|
else
|
|
|
|
xo_failure(xop, "unknown field type: '%c'", ftype);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & XFF_COLON)
|
|
|
|
xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
|
|
|
|
|
|
|
|
if (flags & XFF_WS)
|
|
|
|
xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
|
|
|
|
|
|
|
|
bottom:
|
|
|
|
/* Record the end-of-field offset */
|
|
|
|
if (gettext_reordered) {
|
|
|
|
fend[field] = xo_buf_offset(&xop->xo_data);
|
|
|
|
max_fend = field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gettext_changed && gettext_reordered) {
|
|
|
|
/* Final step: rebuild the content using the rendered fields */
|
|
|
|
xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
|
|
|
|
fend, max_fend);
|
|
|
|
}
|
|
|
|
|
|
|
|
XOIF_CLEAR(xop, XOIF_REORDER);
|
|
|
|
|
|
|
|
/* If we don't have an anchor, write the text out */
|
|
|
|
if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
|
|
|
|
if (xo_write(xop) < 0)
|
|
|
|
rc = -1; /* Report failure */
|
|
|
|
else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0)
|
|
|
|
rc = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_fmt)
|
|
|
|
xo_free(new_fmt);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We've carried the gettext domainname inside our handle just for
|
|
|
|
* convenience, but we need to ensure it doesn't survive across
|
|
|
|
* xo_emit calls.
|
|
|
|
*/
|
|
|
|
if (xop->xo_gt_domain) {
|
|
|
|
xo_free(xop->xo_gt_domain);
|
|
|
|
xop->xo_gt_domain = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (rc < 0) ? rc : (int) xop->xo_columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Rebuild a format string in a gettext-friendly format. This function
|
|
|
|
* is exposed to tools can perform this function. See xo(1).
|
|
|
|
*/
|
|
|
|
char *
|
|
|
|
xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
|
|
|
|
xo_simplify_field_func_t field_cb)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
xop->xo_columns = 0; /* Always reset it */
|
|
|
|
xop->xo_errno = errno; /* Save for "%m" */
|
|
|
|
|
|
|
|
unsigned max_fields = xo_count_fields(xop, fmt);
|
|
|
|
xo_field_info_t fields[max_fields];
|
|
|
|
|
|
|
|
bzero(fields, max_fields * sizeof(fields[0]));
|
|
|
|
|
|
|
|
if (xo_parse_fields(xop, fields, max_fields, fmt))
|
|
|
|
return NULL; /* Warning already displayed */
|
|
|
|
|
|
|
|
xo_buffer_t xb;
|
|
|
|
xo_buf_init(&xb);
|
|
|
|
|
|
|
|
if (with_numbers)
|
|
|
|
xo_gettext_finish_numbering_fields(xop, fmt, fields);
|
|
|
|
|
|
|
|
if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return xb.xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
va_copy(xop->xo_vap, vap);
|
|
|
|
rc = xo_do_emit(xop, fmt);
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
va_start(xop->xo_vap, fmt);
|
|
|
|
rc = xo_do_emit(xop, fmt);
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_emit (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
xo_handle_t *xop = xo_default(NULL);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
va_start(xop->xo_vap, fmt);
|
|
|
|
rc = xo_do_emit(xop, fmt);
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
const int extra = 5; /* space, equals, quote, quote, and nul */
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
int nlen = strlen(name);
|
|
|
|
xo_buffer_t *xbp = &xop->xo_attrs;
|
|
|
|
unsigned name_offset, value_offset;
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
if (!xo_buf_has_room(xbp, nlen + extra))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*xbp->xb_curp++ = ' ';
|
|
|
|
memcpy(xbp->xb_curp, name, nlen);
|
|
|
|
xbp->xb_curp += nlen;
|
|
|
|
*xbp->xb_curp++ = '=';
|
|
|
|
*xbp->xb_curp++ = '"';
|
|
|
|
|
|
|
|
rc = xo_vsnprintf(xop, xbp, fmt, vap);
|
|
|
|
|
|
|
|
if (rc >= 0) {
|
|
|
|
rc = xo_escape_xml(xbp, rc, 1);
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!xo_buf_has_room(xbp, 2))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*xbp->xb_curp++ = '"';
|
|
|
|
*xbp->xb_curp = '\0';
|
|
|
|
|
|
|
|
rc += nlen + extra;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
name_offset = xo_buf_offset(xbp);
|
|
|
|
xo_buf_append(xbp, name, nlen);
|
|
|
|
xo_buf_append(xbp, "", 1);
|
|
|
|
|
|
|
|
value_offset = xo_buf_offset(xbp);
|
|
|
|
rc = xo_vsnprintf(xop, xbp, fmt, vap);
|
|
|
|
if (rc >= 0) {
|
|
|
|
xbp->xb_curp += rc;
|
|
|
|
*xbp->xb_curp = '\0';
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
|
|
|
|
xo_buf_data(xbp, name_offset),
|
|
|
|
xo_buf_data(xbp, value_offset));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
rc = xo_attr_hv(xop, name, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_attr (const char *name, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
rc = xo_attr_hv(NULL, name, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_stack_set_flags (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
xsp->xs_flags |= XSF_NOT_FIRST;
|
|
|
|
XOF_CLEAR(xop, XOF_NOT_FIRST);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_depth_change (xo_handle_t *xop, const char *name,
|
|
|
|
int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
|
|
|
|
{
|
|
|
|
if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
|
|
|
|
indent = 0;
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_DTRT))
|
|
|
|
flags |= XSF_DTRT;
|
|
|
|
|
|
|
|
if (delta >= 0) { /* Push operation */
|
|
|
|
if (xo_depth_check(xop, xop->xo_depth + delta))
|
|
|
|
return;
|
|
|
|
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
|
|
|
|
xsp->xs_flags = flags;
|
|
|
|
xsp->xs_state = state;
|
|
|
|
xo_stack_set_flags(xop);
|
|
|
|
|
|
|
|
if (name == NULL)
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
|
|
|
|
xsp->xs_name = xo_strndup(name, -1);
|
|
|
|
|
|
|
|
} else { /* Pop operation */
|
|
|
|
if (xop->xo_depth == 0) {
|
|
|
|
if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
|
|
|
|
xo_failure(xop, "close with empty stack: '%s'", name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
if (XOF_ISSET(xop, XOF_WARN)) {
|
|
|
|
const char *top = xsp->xs_name;
|
|
|
|
if (top && strcmp(name, top) != 0) {
|
|
|
|
xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
|
|
|
|
name, top);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
|
|
|
|
xo_failure(xop, "list close on list confict: '%s'",
|
|
|
|
name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
|
|
|
|
xo_failure(xop, "list close on instance confict: '%s'",
|
|
|
|
name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xsp->xs_name) {
|
|
|
|
xo_free(xsp->xs_name);
|
|
|
|
xsp->xs_name = NULL;
|
|
|
|
}
|
|
|
|
if (xsp->xs_keys) {
|
|
|
|
xo_free(xsp->xs_keys);
|
|
|
|
xsp->xs_keys = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xop->xo_depth += delta; /* Record new depth */
|
|
|
|
xop->xo_indent += indent;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_set_depth (xo_handle_t *xop, int depth)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (xo_depth_check(xop, depth))
|
|
|
|
return;
|
|
|
|
|
|
|
|
xop->xo_depth += depth;
|
|
|
|
xop->xo_indent += depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
static xo_xsf_flags_t
|
|
|
|
xo_stack_flags (unsigned xflags)
|
|
|
|
{
|
|
|
|
if (xflags & XOF_DTRT)
|
|
|
|
return XSF_DTRT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xo_emit_top (xo_handle_t *xop, const char *ppn)
|
|
|
|
{
|
|
|
|
xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
|
|
|
|
XOIF_SET(xop, XOIF_TOP_EMITTED);
|
|
|
|
|
|
|
|
if (xop->xo_version) {
|
|
|
|
xo_printf(xop, "%*s\"__version\": \"%s\", %s",
|
|
|
|
xo_indent(xop), "", xop->xo_version, ppn);
|
|
|
|
xo_free(xop->xo_version);
|
|
|
|
xop->xo_version = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_failure(xop, "NULL passed for container name");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
flags |= xop->xo_flags; /* Pick up handle flags */
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
|
|
|
|
|
|
|
|
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
|
|
|
|
rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
|
|
|
|
xo_data_append(xop, xop->xo_attrs.xb_bufp,
|
|
|
|
xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
|
|
|
|
xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc += xo_printf(xop, ">%s", ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
xo_stack_set_flags(xop);
|
|
|
|
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_TOP)
|
|
|
|
&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
|
|
|
|
xo_emit_top(xop, ppn);
|
|
|
|
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
rc = xo_printf(xop, "%s%*s\"%s\": {%s",
|
|
|
|
pre_nl, xo_indent(xop), "", name, ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
|
|
|
|
xo_stack_flags(flags));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_container_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_container_hf(xop, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_container (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_container_hf(NULL, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_container_hd (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_container_hf(xop, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_container_d (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_container_hf(NULL, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_close_container (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
name = xsp->xs_name;
|
|
|
|
if (name) {
|
|
|
|
int len = strlen(name) + 1;
|
|
|
|
/* We need to make a local copy; xo_depth_change will free it */
|
|
|
|
char *cp = alloca(len);
|
|
|
|
memcpy(cp, name, len);
|
|
|
|
name = cp;
|
|
|
|
} else if (!(xsp->xs_flags & XSF_DTRT)) {
|
|
|
|
xo_failure(xop, "missing name without 'dtrt' mode");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
|
|
|
|
rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
ppn = (xop->xo_depth <= 1) ? "\n" : "";
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
|
|
|
|
rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_container_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_container (const char *name)
|
|
|
|
{
|
|
|
|
return xo_close_container_h(NULL, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_container_hd (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
return xo_close_container_h(xop, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_container_d (void)
|
|
|
|
{
|
|
|
|
return xo_close_container_h(NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
int indent = 0;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
|
|
|
|
indent = 1;
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_TOP)
|
|
|
|
&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
|
|
|
|
xo_emit_top(xop, ppn);
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_failure(xop, "NULL passed for list name");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_stack_set_flags(xop);
|
|
|
|
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
rc = xo_printf(xop, "%s%*s\"%s\": [%s",
|
|
|
|
pre_nl, xo_indent(xop), "", name, ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
|
|
|
|
XSF_LIST | xo_stack_flags(flags));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, flags, name, XSS_OPEN_LIST);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
|
|
|
|
{
|
|
|
|
return xo_open_list_hf(xop, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_list (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_list_hf(NULL, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
|
|
|
|
{
|
|
|
|
return xo_open_list_hf(xop, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_list_d (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_list_hf(NULL, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_close_list (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
name = xsp->xs_name;
|
|
|
|
if (name) {
|
|
|
|
int len = strlen(name) + 1;
|
|
|
|
/* We need to make a local copy; xo_depth_change will free it */
|
|
|
|
char *cp = alloca(len);
|
|
|
|
memcpy(cp, name, len);
|
|
|
|
name = cp;
|
|
|
|
} else if (!(xsp->xs_flags & XSF_DTRT)) {
|
|
|
|
xo_failure(xop, "missing name without 'dtrt' mode");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
|
|
|
|
rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_list_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_list (const char *name)
|
|
|
|
{
|
|
|
|
return xo_close_list_h(NULL, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_list_hd (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
return xo_close_list_h(xop, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_list_d (void)
|
|
|
|
{
|
|
|
|
return xo_close_list_h(NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
int indent = 0;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
indent = 1;
|
|
|
|
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_TOP)) {
|
|
|
|
if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
|
|
|
|
xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
|
|
|
|
XOIF_SET(xop, XOIF_TOP_EMITTED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_failure(xop, "NULL passed for list name");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_stack_set_flags(xop);
|
|
|
|
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
rc = xo_printf(xop, "%s%*s\"%s\": [%s",
|
|
|
|
pre_nl, xo_indent(xop), "", name, ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
|
|
|
|
XSF_LIST | xo_stack_flags(flags));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
name = xsp->xs_name;
|
|
|
|
if (name) {
|
|
|
|
int len = strlen(name) + 1;
|
|
|
|
/* We need to make a local copy; xo_depth_change will free it */
|
|
|
|
char *cp = alloca(len);
|
|
|
|
memcpy(cp, name, len);
|
|
|
|
name = cp;
|
|
|
|
} else if (!(xsp->xs_flags & XSF_DTRT)) {
|
|
|
|
xo_failure(xop, "missing name without 'dtrt' mode");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
|
|
|
|
rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL);
|
|
|
|
/*fallthru*/
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
flags |= xop->xo_flags;
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_failure(xop, "NULL passed for instance name");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
|
|
|
|
|
|
|
|
if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
|
|
|
|
rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
|
|
|
|
xo_data_append(xop, xop->xo_attrs.xb_bufp,
|
|
|
|
xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
|
|
|
|
xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc += xo_printf(xop, ">%s", ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
xo_stack_set_flags(xop);
|
|
|
|
|
|
|
|
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
|
|
|
|
rc = xo_printf(xop, "%s%*s{%s",
|
|
|
|
pre_nl, xo_indent(xop), "", ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_instance_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_instance_hf(xop, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_instance (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_instance_hf(NULL, 0, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_instance_hd (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_instance_hf(xop, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_instance_d (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_instance_hf(NULL, XOF_DTRT, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_close_instance (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
const char *pre_nl = "";
|
|
|
|
|
|
|
|
if (name == NULL) {
|
|
|
|
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
|
|
|
|
name = xsp->xs_name;
|
|
|
|
if (name) {
|
|
|
|
int len = strlen(name) + 1;
|
|
|
|
/* We need to make a local copy; xo_depth_change will free it */
|
|
|
|
char *cp = alloca(len);
|
|
|
|
memcpy(cp, name, len);
|
|
|
|
name = cp;
|
|
|
|
} else if (!(xsp->xs_flags & XSF_DTRT)) {
|
|
|
|
xo_failure(xop, "missing name without 'dtrt' mode");
|
|
|
|
name = XO_FAILURE_NAME;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
|
|
|
|
rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
|
|
|
|
rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
|
|
|
|
rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_instance_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_instance (const char *name)
|
|
|
|
{
|
|
|
|
return xo_close_instance_h(NULL, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_instance_hd (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
return xo_close_instance_h(xop, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_instance_d (void)
|
|
|
|
{
|
|
|
|
return xo_close_instance_h(NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
|
|
|
|
{
|
|
|
|
xo_stack_t *xsp;
|
|
|
|
int rc = 0;
|
|
|
|
xo_xsf_flags_t flags;
|
|
|
|
|
|
|
|
for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
|
|
|
|
switch (xsp->xs_state) {
|
|
|
|
case XSS_INIT:
|
|
|
|
/* Nothing */
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_OPEN_CONTAINER:
|
|
|
|
rc = xo_do_close_container(xop, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_OPEN_LIST:
|
|
|
|
rc = xo_do_close_list(xop, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_OPEN_INSTANCE:
|
|
|
|
rc = xo_do_close_instance(xop, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_OPEN_LEAF_LIST:
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_MARKER:
|
|
|
|
flags = xsp->xs_flags & XSF_MARKER_FLAGS;
|
|
|
|
xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags |= flags;
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc < 0)
|
|
|
|
xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function is responsible for clearing out whatever is needed
|
|
|
|
* to get to the desired state, if possible.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
|
|
|
|
{
|
|
|
|
xo_stack_t *xsp, *limit = NULL;
|
|
|
|
int rc;
|
|
|
|
xo_state_t need_state = new_state;
|
|
|
|
|
|
|
|
if (new_state == XSS_CLOSE_CONTAINER)
|
|
|
|
need_state = XSS_OPEN_CONTAINER;
|
|
|
|
else if (new_state == XSS_CLOSE_LIST)
|
|
|
|
need_state = XSS_OPEN_LIST;
|
|
|
|
else if (new_state == XSS_CLOSE_INSTANCE)
|
|
|
|
need_state = XSS_OPEN_INSTANCE;
|
|
|
|
else if (new_state == XSS_CLOSE_LEAF_LIST)
|
|
|
|
need_state = XSS_OPEN_LEAF_LIST;
|
|
|
|
else if (new_state == XSS_MARKER)
|
|
|
|
need_state = XSS_MARKER;
|
|
|
|
else
|
|
|
|
return 0; /* Unknown or useless new states are ignored */
|
|
|
|
|
|
|
|
for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
|
|
|
|
/*
|
|
|
|
* Marker's normally stop us from going any further, unless
|
|
|
|
* we are popping a marker (new_state == XSS_MARKER).
|
|
|
|
*/
|
|
|
|
if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
|
|
|
|
if (name) {
|
|
|
|
xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
|
|
|
|
"not found '%s'",
|
|
|
|
xo_state_name(new_state),
|
|
|
|
xsp->xs_name, name);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
limit = xsp;
|
|
|
|
xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xsp->xs_state != need_state)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
limit = xsp;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (limit == NULL) {
|
|
|
|
xo_failure(xop, "xo_%s can't find match for '%s'",
|
|
|
|
xo_state_name(new_state), name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = xo_do_close_all(xop, limit);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We are in a given state and need to transition to the new state.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
|
|
|
|
xo_state_t new_state)
|
|
|
|
{
|
|
|
|
xo_stack_t *xsp;
|
|
|
|
int rc;
|
|
|
|
int old_state, on_marker;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
rc = 0;
|
|
|
|
xsp = &xop->xo_stack[xop->xo_depth];
|
|
|
|
old_state = xsp->xs_state;
|
|
|
|
on_marker = (old_state == XSS_MARKER);
|
|
|
|
|
|
|
|
/* If there's a marker on top of the stack, we need to find a real state */
|
|
|
|
while (old_state == XSS_MARKER) {
|
|
|
|
if (xsp == xop->xo_stack)
|
|
|
|
break;
|
|
|
|
xsp -= 1;
|
|
|
|
old_state = xsp->xs_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, the list of possible states are:
|
|
|
|
* XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
|
|
|
|
* XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
|
|
|
|
*/
|
|
|
|
switch (XSS_TRANSITION(old_state, new_state)) {
|
|
|
|
|
|
|
|
open_container:
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
|
|
|
|
rc = xo_do_open_container(xop, flags, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_container;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_container;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*close_container:*/
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
|
|
|
|
/* This is an exception for "xo --close" */
|
|
|
|
rc = xo_do_close_container(xop, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
open_list:
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
|
|
|
|
rc = xo_do_open_list(xop, flags, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_list;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_list;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*close_list:*/
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
open_instance:
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
|
|
|
|
rc = xo_do_open_instance(xop, flags, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
|
|
|
|
rc = xo_do_open_list(xop, flags, name);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_instance;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
|
|
|
|
if (on_marker) {
|
|
|
|
rc = xo_do_open_list(xop, flags, name);
|
|
|
|
} else {
|
|
|
|
rc = xo_do_close_instance(xop, NULL);
|
|
|
|
}
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_instance;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_instance;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*close_instance:*/
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_instance(xop, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
|
|
|
|
/* This one makes no sense; ignore it */
|
|
|
|
xo_failure(xop, "xo_close_instance ignored when called from "
|
|
|
|
"initial state ('%s')", name ?: "(unknown)");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
open_leaf_list:
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
|
|
|
|
rc = xo_do_open_leaf_list(xop, flags, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_list(xop, NULL);
|
|
|
|
if (rc >= 0)
|
|
|
|
goto open_leaf_list;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*close_leaf_list:*/
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
|
|
|
|
/* Makes no sense; ignore */
|
|
|
|
xo_failure(xop, "xo_close_leaf_list ignored when called from "
|
|
|
|
"initial state ('%s')", name ?: "(unknown)");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, name, new_state);
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*emit:*/
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
|
|
|
|
if (on_marker)
|
|
|
|
goto marker_prevents_close;
|
|
|
|
rc = xo_do_close_leaf_list(xop, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*emit_leaf_list:*/
|
|
|
|
case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
|
|
|
|
rc = xo_do_open_leaf_list(xop, flags, name);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
|
|
|
|
/*
|
|
|
|
* We need to be backward compatible with the pre-xo_open_leaf_list
|
|
|
|
* API, where both lists and leaf-lists were opened as lists. So
|
|
|
|
* if we find an open list that hasn't had anything written to it,
|
|
|
|
* we'll accept it.
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
xo_failure(xop, "unknown transition: (%u -> %u)",
|
|
|
|
xsp->xs_state, new_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
marker_prevents_close:
|
|
|
|
xo_failure(xop, "marker '%s' prevents transition from %s to %s",
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_name,
|
|
|
|
xo_state_name(old_state), xo_state_name(new_state));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_marker_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
xo_depth_change(xop, name, 1, 0, XSS_MARKER,
|
|
|
|
xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_open_marker (const char *name)
|
|
|
|
{
|
|
|
|
return xo_open_marker_h(NULL, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_marker_h (xo_handle_t *xop, const char *name)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
return xo_do_close(xop, name, XSS_MARKER);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_close_marker (const char *name)
|
|
|
|
{
|
|
|
|
return xo_close_marker_h(NULL, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record custom output functions into the xo handle, allowing
|
|
|
|
* integration with a variety of output frameworks.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
|
|
|
|
xo_close_func_t close_func, xo_flush_func_t flush_func)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
xop->xo_opaque = opaque;
|
|
|
|
xop->xo_write = write_func;
|
|
|
|
xop->xo_close = close_func;
|
|
|
|
xop->xo_flush = flush_func;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
|
|
|
|
{
|
|
|
|
xo_realloc = realloc_func;
|
|
|
|
xo_free = free_func;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_flush_h (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
static char div_close[] = "</div>";
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
|
|
|
|
XOIF_CLEAR(xop, XOIF_DIV_OPEN);
|
|
|
|
xo_data_append(xop, div_close, sizeof(div_close) - 1);
|
|
|
|
|
|
|
|
if (XOF_ISSET(xop, XOF_PRETTY))
|
|
|
|
xo_data_append(xop, "\n", 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = xo_write(xop);
|
|
|
|
if (rc >= 0 && xop->xo_flush)
|
|
|
|
if (xop->xo_flush(xop->xo_opaque) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_flush (void)
|
|
|
|
{
|
|
|
|
return xo_flush_h(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_finish_h (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
const char *cp = "";
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_CLOSE))
|
|
|
|
xo_do_close_all(xop, xop->xo_stack);
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
if (!XOF_ISSET(xop, XOF_NO_TOP)) {
|
|
|
|
if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
|
|
|
|
XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
|
|
|
|
else
|
|
|
|
cp = "{ ";
|
|
|
|
xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return xo_flush_h(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xo_finish (void)
|
|
|
|
{
|
|
|
|
return xo_finish_h(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* xo_finish_atexit is suitable for atexit() calls, to force clear up
|
|
|
|
* and finalizing output.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_finish_atexit (void)
|
|
|
|
{
|
|
|
|
(void) xo_finish_h(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate an error message, such as would be displayed on stderr
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the format string doesn't end with a newline, we pop
|
|
|
|
* one on ourselves.
|
|
|
|
*/
|
|
|
|
int len = strlen(fmt);
|
|
|
|
if (len > 0 && fmt[len - 1] != '\n') {
|
|
|
|
char *newfmt = alloca(len + 2);
|
|
|
|
memcpy(newfmt, fmt, len);
|
|
|
|
newfmt[len] = '\n';
|
|
|
|
newfmt[len] = '\0';
|
|
|
|
fmt = newfmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_TEXT:
|
|
|
|
vfprintf(stderr, fmt, vap);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_HTML:
|
|
|
|
va_copy(xop->xo_vap, vap);
|
|
|
|
|
|
|
|
xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
|
|
|
|
|
|
|
|
if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
|
|
|
|
xo_line_close(xop);
|
|
|
|
|
|
|
|
xo_write(xop);
|
|
|
|
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
va_copy(xop->xo_vap, vap);
|
|
|
|
|
|
|
|
xo_open_container_h(xop, "error");
|
|
|
|
xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
|
|
|
|
xo_close_container_h(xop, "error");
|
|
|
|
|
|
|
|
va_end(xop->xo_vap);
|
|
|
|
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_SDPARAMS:
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_error_h (xo_handle_t *xop, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_error_hv(xop, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate an error message, such as would be displayed on stderr
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_error (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_error_hv(NULL, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse any libxo-specific options from the command line, removing them
|
|
|
|
* so the main() argument parsing won't see them. We return the new value
|
|
|
|
* for argc or -1 for error. If an error occurred, the program should
|
|
|
|
* exit. A suitable error message has already been displayed.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xo_parse_args (int argc, char **argv)
|
|
|
|
{
|
|
|
|
static char libxo_opt[] = "--libxo";
|
|
|
|
char *cp;
|
|
|
|
int i, save;
|
|
|
|
|
|
|
|
/* Save our program name for xo_err and friends */
|
|
|
|
xo_program = argv[0];
|
|
|
|
cp = strrchr(xo_program, '/');
|
|
|
|
if (cp)
|
|
|
|
xo_program = cp + 1;
|
|
|
|
|
|
|
|
for (save = i = 1; i < argc; i++) {
|
|
|
|
if (argv[i] == NULL
|
|
|
|
|| strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
|
|
|
|
if (save != i)
|
|
|
|
argv[save] = argv[i];
|
|
|
|
save += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
cp = argv[i] + sizeof(libxo_opt) - 1;
|
|
|
|
if (*cp == 0) {
|
|
|
|
cp = argv[++i];
|
|
|
|
if (cp == 0) {
|
|
|
|
xo_warnx("missing libxo option");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xo_set_options(NULL, cp) < 0)
|
|
|
|
return -1;
|
|
|
|
} else if (*cp == ':') {
|
|
|
|
if (xo_set_options(NULL, cp) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
} else if (*cp == '=') {
|
|
|
|
if (xo_set_options(NULL, ++cp) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
} else if (*cp == '-') {
|
|
|
|
cp += 1;
|
|
|
|
if (strcmp(cp, "check") == 0) {
|
|
|
|
exit(XO_HAS_LIBXO);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
xo_warnx("unknown libxo option: '%s'", argv[i]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xo_warnx("unknown libxo option: '%s'", argv[i]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
argv[save] = NULL;
|
|
|
|
return save;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Debugging function that dumps the current stack of open libxo constructs,
|
|
|
|
* suitable for calling from the debugger.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_dump_stack (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
xo_stack_t *xsp;
|
|
|
|
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
fprintf(stderr, "Stack dump:\n");
|
|
|
|
|
|
|
|
xsp = xop->xo_stack;
|
|
|
|
for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
|
|
|
|
fprintf(stderr, " [%d] %s '%s' [%x]\n",
|
|
|
|
i, xo_state_name(xsp->xs_state),
|
|
|
|
xsp->xs_name ?: "--", xsp->xs_flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record the program name used for error messages
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_program (const char *name)
|
|
|
|
{
|
|
|
|
xo_program = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (version == NULL || strchr(version, '"') != NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!xo_style_is_encoding(xop))
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (xo_style(xop)) {
|
|
|
|
case XO_STYLE_XML:
|
|
|
|
/* For XML, we record this as an attribute for the first tag */
|
|
|
|
xo_attr_h(xop, "__version", "%s", version);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_JSON:
|
|
|
|
/*
|
|
|
|
* For JSON, we record the version string in our handle, and emit
|
|
|
|
* it in xo_emit_top.
|
|
|
|
*/
|
|
|
|
xop->xo_version = xo_strndup(version, -1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XO_STYLE_ENCODER:
|
|
|
|
xo_encoder_handle(xop, XO_OP_VERSION, NULL, version);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the version number for the API content being carried thru
|
|
|
|
* the xo handle.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_version (const char *version)
|
|
|
|
{
|
|
|
|
xo_set_version_h(NULL, version);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate a warning. Normally, this is a text message written to
|
|
|
|
* standard error. If the XOF_WARN_XML flag is set, then we generate
|
|
|
|
* XMLified content on standard output.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
|
|
|
|
const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
if (fmt == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
xo_open_marker_h(xop, "xo_emit_warn_hcv");
|
|
|
|
xo_open_container_h(xop, as_warning ? "__warning" : "__error");
|
|
|
|
|
|
|
|
if (xo_program)
|
|
|
|
xo_emit("{wc:program}", xo_program);
|
|
|
|
|
|
|
|
if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
|
|
|
|
va_list ap;
|
|
|
|
xo_handle_t temp;
|
|
|
|
|
|
|
|
bzero(&temp, sizeof(temp));
|
|
|
|
temp.xo_style = XO_STYLE_TEXT;
|
|
|
|
xo_buf_init(&temp.xo_data);
|
|
|
|
xo_depth_check(&temp, XO_DEPTH);
|
|
|
|
|
|
|
|
va_copy(ap, vap);
|
|
|
|
(void) xo_emit_hv(&temp, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
xo_buffer_t *src = &temp.xo_data;
|
|
|
|
xo_format_value(xop, "message", 7, src->xb_bufp,
|
|
|
|
src->xb_curp - src->xb_bufp, NULL, 0, 0);
|
|
|
|
|
|
|
|
xo_free(temp.xo_stack);
|
|
|
|
xo_buf_cleanup(src);
|
|
|
|
}
|
|
|
|
|
|
|
|
(void) xo_emit_hv(xop, fmt, vap);
|
|
|
|
|
|
|
|
int len = strlen(fmt);
|
|
|
|
if (len > 0 && fmt[len - 1] != '\n') {
|
|
|
|
if (code > 0) {
|
|
|
|
const char *msg = strerror(code);
|
|
|
|
if (msg)
|
|
|
|
xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
|
|
|
|
}
|
|
|
|
xo_emit("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
xo_close_marker_h(xop, "xo_emit_warn_hcv");
|
|
|
|
xo_flush_h(xop);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_warn_hcv(xop, 1, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_warn_c (int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_warn (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int code = errno;
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_warnx (const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
|
|
|
|
{
|
|
|
|
xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_err (int eval, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int code = errno;
|
|
|
|
va_list vap;
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_err_v(0, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_errx (int eval, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_err_v(0, -1, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xo_emit_errc (int eval, int code, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list vap;
|
|
|
|
|
|
|
|
va_start(vap, fmt);
|
|
|
|
xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
|
|
|
|
va_end(vap);
|
|
|
|
xo_finish();
|
|
|
|
exit(eval);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the opaque private pointer for an xo handle
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
xo_get_private (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
return xop->xo_private;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the opaque private pointer for an xo handle.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_private (xo_handle_t *xop, void *opaque)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
xop->xo_private = opaque;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the encoder function
|
|
|
|
*/
|
|
|
|
xo_encoder_func_t
|
|
|
|
xo_get_encoder (xo_handle_t *xop)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
return xop->xo_encoder;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record an encoder callback function in an xo handle.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
|
|
|
|
{
|
|
|
|
xop = xo_default(xop);
|
|
|
|
|
|
|
|
xop->xo_style = XO_STYLE_ENCODER;
|
|
|
|
xop->xo_encoder = encoder;
|
|
|
|
}
|