diff options
Diffstat (limited to 'contrib/libxo/libxo/libxo.c')
-rw-r--r-- | contrib/libxo/libxo/libxo.c | 1790 |
1 files changed, 1584 insertions, 206 deletions
diff --git a/contrib/libxo/libxo/libxo.c b/contrib/libxo/libxo/libxo.c index 89adc03fe2f5..4fd18fd01bb4 100644 --- a/contrib/libxo/libxo/libxo.c +++ b/contrib/libxo/libxo/libxo.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Juniper Networks, Inc. + * 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 @@ -11,6 +11,7 @@ #include <stdio.h> #include <stdlib.h> #include <stdint.h> +#include <unistd.h> #include <stddef.h> #include <wchar.h> #include <locale.h> @@ -27,6 +28,10 @@ #include "xo.h" #include "xoversion.h" +#ifdef HAVE_STDIO_EXT_H +#include <stdio_ext.h> +#endif /* HAVE_STDIO_EXT_H */ + const char xo_version[] = LIBXO_VERSION; const char xo_version_extra[] = LIBXO_VERSION_EXTRA; @@ -58,6 +63,52 @@ typedef unsigned xo_xsf_flags_t; /* XSF_* flags */ #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're moving to 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 event, since they + * function rather like both. None of the XO_CLOSE_* events will + * persist as states, since their 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 @@ -65,25 +116,70 @@ typedef unsigned xo_xsf_flags_t; /* XSF_* flags */ */ 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; +/* "colors" refers to fancy ansi codes */ +#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 + +/* "effects" refers to fancy ansi codes */ +/* + * 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. + * It's likely done my 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 asking 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 + +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, and content. */ struct xo_handle_s { - unsigned long xo_flags; /* Flags */ + xo_xof_flags_t xo_flags; /* Flags */ unsigned short 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 */ - FILE *xo_fp; /* XXX File pointer */ 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 */ @@ -101,6 +197,11 @@ struct xo_handle_s { 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 */ }; /* Flags for formatting functions */ @@ -108,7 +209,7 @@ 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 formats (xml and json) */ +#define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding formats (xml, json) */ #define XFF_QUOTE (1<<4) /* Force quotes */ #define XFF_NOQUOTE (1<<5) /* Force no quotes */ @@ -197,7 +298,7 @@ typedef struct xo_format_s { static xo_handle_t xo_default_handle; static int xo_default_inited; static int xo_locale_inited; -static char *xo_program; +static const char *xo_program; /* * To allow libxo to be used in diverse environment, we allow the @@ -210,6 +311,10 @@ static xo_free_func_t xo_free = free; 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, @@ -220,12 +325,31 @@ 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 unsigned short +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); } @@ -236,10 +360,22 @@ 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); +} + +/* * Initialize the contents of an xo_buffer_t. */ static void @@ -251,6 +387,24 @@ xo_buf_init (xo_buffer_t *xbp) } /* + * Reset the buffer to empty + */ +static void +xo_buf_reset (xo_buffer_t *xbp) +{ + xbp->xb_curp = xbp->xb_bufp; +} + +/* + * Reset the buffer to empty + */ +static int +xo_buf_is_empty (xo_buffer_t *xbp) +{ + return (xbp->xb_curp == xbp->xb_bufp); +} + +/* * Initialize the contents of an xo_buffer_t. */ static void @@ -291,6 +445,29 @@ xo_no_setlocale (void) } /* + * 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. @@ -300,6 +477,17 @@ 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)) + xop->xo_flags |= 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 ((xop->xo_flags & XOF_COLOR_ALLOWED) && isatty(1)) + xop->xo_flags |= XOF_COLOR; /* * We need to initialize the locale, which isn't really pretty. @@ -400,7 +588,7 @@ xo_default (xo_handle_t *xop) /* * Return the number of spaces we should be indenting. If - * we are pretty-printing, theis is indent * indent_by. + * we are pretty-printing, this is indent * indent_by. */ static int xo_indent (xo_handle_t *xop) @@ -415,7 +603,7 @@ xo_indent (xo_handle_t *xop) rc += xop->xo_indent_by; } - return rc; + return (rc > 0) ? rc : 0; } static void @@ -500,9 +688,9 @@ xo_escape_json (xo_buffer_t *xbp, int len) char *cp, *ep, *ip; for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) { - if (*cp == '\\') + if (*cp == '\\' || *cp == '"') delta += 1; - else if (*cp == '"') + else if (*cp == '\n' || *cp == '\r') delta += 1; } @@ -519,13 +707,18 @@ xo_escape_json (xo_buffer_t *xbp, int len) cp -= 1; ip -= 1; - if (*cp != '\\' && *cp != '"') { + if (*cp == '\\' || *cp == '"') { + *ip-- = *cp; + *ip = '\\'; + } else if (*cp == '\n') { + *ip-- = 'n'; + *ip = '\\'; + } else if (*cp == '\r') { + *ip-- = 'r'; + *ip = '\\'; + } else { *ip = *cp; - continue; } - - *ip-- = *cp; - *ip = '\\'; } while (cp > ep && cp != ip); @@ -545,6 +738,21 @@ xo_buf_append (xo_buffer_t *xbp, const char *str, int len) xbp->xb_curp += len; } +/* + * Append the given NUL-terminated string to the given buffer + */ +static void +xo_buf_append_str (xo_buffer_t *xbp, const char *str) +{ + int len = strlen(str); + + if (!xo_buf_has_room(xbp, len)) + return; + + memcpy(xbp->xb_curp, str, len); + xbp->xb_curp += len; +} + static void xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, const char *str, int len, xo_xff_flags_t flags) @@ -554,7 +762,7 @@ xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, memcpy(xbp->xb_curp, str, len); - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_HTML: len = xo_escape_xml(xbp, len, (flags & XFF_ATTR)); @@ -572,20 +780,23 @@ xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp, * Write the current contents of the data buffer using the handle's * xo_write function. */ -static void +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); - xop->xo_write(xop->xo_opaque, xbp->xb_bufp); + 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 */ xop->xo_flags &= ~(XOF_UNITS_PENDING); + + return rc; } /* @@ -606,7 +817,7 @@ xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) else rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); - if (rc > xbp->xb_size) { + if (rc >= left) { if (!xo_buf_has_room(xbp, rc)) { va_end(va_local); return -1; @@ -616,7 +827,7 @@ xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) * 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 point (bsd) or a + * implementation can have va_list be a pointer (bsd) or a * structure (macosx) or anything in between. */ @@ -625,7 +836,7 @@ xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap) left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp); if (xop->xo_formatter) - xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); + rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local); else rc = vsnprintf(xbp->xb_curp, left, fmt, va_local); } @@ -947,7 +1158,7 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, int len = strlen(fmt); int plen = xo_program ? strlen(xo_program) : 0; - char *newfmt = alloca(len + 2 + plen + 2); /* newline, NUL, and ": " */ + char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */ if (plen) { memcpy(newfmt, xo_program, plen); @@ -955,10 +1166,6 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, newfmt[plen++] = ' '; } memcpy(newfmt + plen, fmt, len); - - /* Add a newline to the fmt string */ - if (!(xop->xo_flags & XOF_WARN_XML)) - newfmt[len++ + plen] = '\n'; newfmt[len + plen] = '\0'; if (xop->xo_flags & XOF_WARN_XML) { @@ -997,7 +1204,7 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); xo_buf_append(xbp, err_close, sizeof(err_close) - 1); - if (code > 0) { + if (code >= 0) { const char *msg = strerror(code); if (msg) { xo_buf_append(xbp, ": ", 2); @@ -1006,10 +1213,16 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn, } xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */ - xo_write(xop); + (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"); } } @@ -1029,7 +1242,7 @@ xo_warn_c (int code, const char *fmt, ...) va_list vap; va_start(vap, fmt); - xo_warn_hcv(NULL, 0, code, fmt, vap); + xo_warn_hcv(NULL, code, 0, fmt, vap); va_end(vap); } @@ -1112,7 +1325,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) int need_nl = (fmt[strlen(fmt) - 1] != '\n'); - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: xbp = &xop->xo_data; if (xop->xo_flags & XOF_PRETTY) @@ -1151,7 +1364,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1); if (need_nl) xo_buf_append(xbp, "\n", 2); /* Append newline and NUL to string */ - xo_write(xop); + (void) xo_write(xop); break; case XO_STYLE_HTML: @@ -1213,7 +1426,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) break; } - xo_flush_h(xop); + (void) xo_flush_h(xop); } void @@ -1301,6 +1514,7 @@ xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags) 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; @@ -1323,9 +1537,13 @@ xo_destroy (xo_handle_t *xop_arg) 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)); + bzero(&xo_default_handle, sizeof(xo_default_handle)); xo_default_inited = 0; } else xo_free(xop); @@ -1349,7 +1567,7 @@ xo_style_t xo_get_style (xo_handle_t *xop) { xop = xo_default(xop); - return xop->xo_style; + return xo_style(xop); } static int @@ -1384,6 +1602,8 @@ xo_name_to_flag (const char *name) return XOF_INFO; if (strcmp(name, "warn-xml") == 0) return XOF_WARN_XML; + if (strcmp(name, "color") == 0) + return XOF_COLOR_ALLOWED; if (strcmp(name, "columns") == 0) return XOF_COLUMNS; if (strcmp(name, "dtrt") == 0) @@ -1439,6 +1659,11 @@ xo_set_options (xo_handle_t *xop, const char *input) xop = xo_default(xop); +#ifdef LIBXO_COLOR_ON_BY_DEFAULT + /* If the installer used --enable-color-on-by-default, then we allow it */ + xop->xo_flags |= 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 @@ -1449,10 +1674,18 @@ xo_set_options (xo_handle_t *xop, const char *input) for (input++ ; *input; input++) { switch (*input) { + case 'c': + xop->xo_flags |= XOF_COLOR_ALLOWED; + break; + case 'f': xop->xo_flags |= XOF_FLUSH; break; + case 'F': + xop->xo_flags |= XOF_FLUSH_LINE; + break; + case 'H': xop->xo_style = XO_STYLE_HTML; break; @@ -1522,6 +1755,11 @@ xo_set_options (xo_handle_t *xop, const char *input) if (vp) *vp++ = '\0'; + if (strcmp("colors", cp) == 0) { + /* XXX Look for colors=red-blue+green-yellow */ + continue; + } + new_style = xo_name_to_style(cp); if (new_style >= 0) { if (style >= 0) @@ -1533,7 +1771,9 @@ xo_set_options (xo_handle_t *xop, const char *input) if (new_flag != 0) xop->xo_flags |= new_flag; else { - if (strcmp(cp, "indent") == 0) { + if (strcmp(cp, "no-color") == 0) { + xop->xo_flags &= ~XOF_COLOR_ALLOWED; + } else if (strcmp(cp, "indent") == 0) { xop->xo_indent_by = atoi(vp); } else { xo_warnx("unknown option: '%s'", cp); @@ -1653,6 +1893,33 @@ xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags) xop->xo_flags &= ~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) { @@ -1662,7 +1929,7 @@ xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED) if (xop->xo_flags & XOF_DIV_OPEN) return; - if (xop->xo_style != XO_STYLE_HTML) + if (xo_style(xop) != XO_STYLE_HTML) return; xop->xo_flags |= XOF_DIV_OPEN; @@ -1680,7 +1947,7 @@ xo_line_close (xo_handle_t *xop) { static char div_close[] = "</div>"; - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_HTML: if (!(xop->xo_flags & XOF_DIV_OPEN)) xo_line_ensure_open(xop, 0); @@ -1837,7 +2104,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, if (width < 0) width = iswcntrl(wc) ? 0 : 1; - if (xop->xo_style == XO_STYLE_TEXT || xop->xo_style == XO_STYLE_HTML) { + if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) { if (max > 0 && cols + width > max) break; } @@ -1846,7 +2113,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, case XF_ENC_UTF8: /* Output in UTF-8 needs to be escaped, based on the style */ - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_HTML: if (wc == '<') @@ -1869,14 +2136,20 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp, goto done_with_encoding; /* Need multi-level 'break' */ case XO_STYLE_JSON: - if (wc != '\\' && wc != '"') + if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r') break; if (!xo_buf_has_room(xbp, 2)) return -1; *xbp->xb_curp++ = '\\'; - *xbp->xb_curp++ = wc & 0x7f; + if (wc == '\n') + wc = 'n'; + else if (wc == '\r') + wc = 'r'; + else wc = wc & 0x7f; + + *xbp->xb_curp++ = wc; goto done_with_encoding; } @@ -1926,7 +2199,7 @@ xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags, wchar_t *wcp = NULL; int len, cols = 0, rc = 0; int off = xbp->xb_curp - xbp->xb_bufp, off2; - int need_enc = (xop->xo_style == XO_STYLE_TEXT) + int need_enc = (xo_style(xop) == XO_STYLE_TEXT) ? XF_ENC_LOCALE : XF_ENC_UTF8; if (xo_check_conversion(xop, xfp->xf_enc, need_enc)) @@ -2040,7 +2313,7 @@ static void xo_data_append_content (xo_handle_t *xop, const char *str, int len) { int cols; - int need_enc = (xop->xo_style == XO_STYLE_TEXT) + int need_enc = (xo_style(xop) == XO_STYLE_TEXT) ? XF_ENC_LOCALE : XF_ENC_UTF8; cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE, @@ -2101,9 +2374,9 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp, xo_format_t xf; const char *cp, *ep, *sp, *xp = NULL; int rc, cols; - int style = (flags & XFF_XML) ? XO_STYLE_XML : xop->xo_style; + int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop); unsigned make_output = !(flags & XFF_NO_OUTPUT); - int need_enc = (xop->xo_style == XO_STYLE_TEXT) + int need_enc = (xo_style(xop) == XO_STYLE_TEXT) ? XF_ENC_LOCALE : XF_ENC_UTF8; if (xbp == NULL) @@ -2165,11 +2438,11 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp, /* Hidden fields are only visible to JSON and XML */ if (xop->xo_flags & XFF_ENCODE_ONLY) { if (style != XO_STYLE_XML - && xop->xo_style != XO_STYLE_JSON) + && xo_style(xop) != XO_STYLE_JSON) xf.xf_skip = 1; } else if (xop->xo_flags & XFF_DISPLAY_ONLY) { if (style != XO_STYLE_TEXT - && xop->xo_style != XO_STYLE_HTML) + && xo_style(xop) != XO_STYLE_HTML) xf.xf_skip = 1; } @@ -2275,8 +2548,8 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp, rc = xo_format_string(xop, xbp, flags, &xf); if ((flags & XFF_TRIM_WS) - && (xop->xo_style == XO_STYLE_XML - || xop->xo_style == XO_STYLE_JSON)) + && (xo_style(xop) == XO_STYLE_XML + || xo_style(xop) == XO_STYLE_JSON)) rc = xo_trim_ws(xbp, rc); } else { @@ -2422,6 +2695,20 @@ xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding) } 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); + } +} + +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, @@ -2518,6 +2805,16 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 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); @@ -2552,6 +2849,15 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, 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) { @@ -2599,7 +2905,7 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags, static void xo_format_text (xo_handle_t *xop, const char *str, int len) { - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_TEXT: xo_buf_append_locale(xop, &xop->xo_data, str, len); break; @@ -2614,10 +2920,16 @@ static void xo_format_title (xo_handle_t *xop, const char *str, int len, const char *fmt, int flen) { - static char div_open[] = "<div class=\"title\">"; + static char div_open[] = "<div class=\"title"; + static char div_middle[] = "\">"; static char div_close[] = "</div>"; - switch (xop->xo_style) { + if (flen == 0) { + fmt = "%s"; + flen = 2; + } + + switch (xo_style(xop)) { case XO_STYLE_XML: case XO_STYLE_JSON: /* @@ -2635,12 +2947,14 @@ xo_format_title (xo_handle_t *xop, const char *str, int len, int rc; int need_enc = XF_ENC_LOCALE; - if (xop->xo_style == XO_STYLE_HTML) { + if (xo_style(xop) == XO_STYLE_HTML) { need_enc = XF_ENC_UTF8; xo_line_ensure_open(xop, 0); if (xop->xo_flags & 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 */ @@ -2703,7 +3017,7 @@ xo_format_title (xo_handle_t *xop, const char *str, int len, } /* If we're styling HTML, then we need to escape it */ - if (xop->xo_style == XO_STYLE_HTML) { + if (xo_style(xop) == XO_STYLE_HTML) { rc = xo_escape_xml(xbp, rc, 0); } @@ -2711,7 +3025,7 @@ xo_format_title (xo_handle_t *xop, const char *str, int len, xbp->xb_curp += rc; move_along: - if (xop->xo_style == XO_STYLE_HTML) { + if (xo_style(xop) == XO_STYLE_HTML) { xo_data_append(xop, div_close, sizeof(div_close) - 1); if (xop->xo_flags & XOF_PRETTY) xo_data_append(xop, "\n", 1); @@ -2750,7 +3064,76 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen, int quote; xo_buffer_t *xbp; - switch (xop->xo_style) { + /* + * 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; + } + } + + switch (xo_style(xop)) { case XO_STYLE_TEXT: if (flags & XFF_ENCODE_ONLY) flags |= XFF_NO_OUTPUT; @@ -2875,7 +3258,9 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen, } if (flags & XFF_LEAF_LIST) { - if (first && pretty) + if (!first && pretty) + xo_data_append(xop, "\n", 1); + if (pretty) xo_buf_indent(xop, -1); } else { if (pretty) @@ -2894,10 +3279,10 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen, xbp->xb_bufp[off] = '_'; } xo_data_append(xop, "\":", 2); + if (pretty) + xo_data_append(xop, " ", 1); } - if (pretty) - xo_data_append(xop, " ", 1); if (quote) xo_data_append(xop, "\"", 1); @@ -2914,7 +3299,7 @@ xo_format_content (xo_handle_t *xop, const char *class_name, const char *xml_tag, int display_only, const char *str, int len, const char *fmt, int flen) { - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_TEXT: if (len) { xo_data_append_content(xop, str, len); @@ -2967,6 +3352,362 @@ xo_format_content (xo_handle_t *xop, const char *class_name, } } +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 (xop->xo_flags & 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 ((xop->xo_flags & XOF_COLOR) ? 1 : 0); +#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; + const char *code = NULL; + + /* + * 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; + + if (newp->xoc_effects & bit) + 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, const char *str, int len, + const char *fmt, int flen) +{ + xo_buffer_t xb; + + /* If the string is static and we've in an encoding style, bail */ + if (len != 0 + && (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON)) + return; + + xo_buf_init(&xb); + + if (len) + xo_buf_append(&xb, str, len); + else if (flen) + xo_format_data(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: + /* + * 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, const char *str, int len, const char *fmt, int flen) @@ -2983,9 +3724,9 @@ xo_format_units (xo_handle_t *xop, const char *str, int len, int start = xop->xo_units_offset; int stop = xbp->xb_curp - xbp->xb_bufp; - if (xop->xo_style == XO_STYLE_XML) + if (xo_style(xop) == XO_STYLE_XML) xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1); - else if (xop->xo_style == XO_STYLE_HTML) + else if (xo_style(xop) == XO_STYLE_HTML) xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1); else return; @@ -3067,7 +3808,7 @@ static void xo_anchor_start (xo_handle_t *xop, const char *str, int len, const char *fmt, int flen) { - if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML) + if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) return; if (xop->xo_flags & XOF_ANCHOR) @@ -3089,7 +3830,7 @@ static void xo_anchor_stop (xo_handle_t *xop, const char *str, int len, const char *fmt, int flen) { - if (xop->xo_style != XO_STYLE_TEXT && xop->xo_style != XO_STYLE_HTML) + if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML) return; if (!(xop->xo_flags & XOF_ANCHOR)) { @@ -3159,13 +3900,15 @@ xo_do_emit (xo_handle_t *xop, const char *fmt) const char *cp, *sp, *ep, *basep; char *newp = NULL; int flush = (xop->xo_flags & XOF_FLUSH) ? 1 : 0; + int flush_line = (xop->xo_flags & XOF_FLUSH_LINE) ? 1 : 0; xop->xo_columns = 0; /* Always reset it */ for (cp = fmt; *cp; ) { if (*cp == '\n') { xo_line_close(xop); - xo_flush_h(xop); + if (flush_line && xo_flush_h(xop) < 0) + return -1; cp += 1; continue; @@ -3254,6 +3997,7 @@ xo_do_emit (xo_handle_t *xop, const char *fmt) } switch (*sp) { + case 'C': case 'D': case 'E': case 'L': @@ -3377,42 +4121,55 @@ xo_do_emit (xo_handle_t *xop, const char *fmt) return -1; } - if (format == NULL && ftype != '[' && ftype != ']' ) { - format = "%s"; - flen = 2; - } + if (ftype == 0 || ftype == 'V') { + if (format == NULL) { + /* Default format for value fields is '%s' */ + format = "%s"; + flen = 2; + } - if (ftype == 0 || ftype == 'V') xo_format_value(xop, content, clen, format, flen, encoding, elen, flags); - else if (ftype == 'D') - xo_format_content(xop, "decoration", NULL, 1, - content, clen, format, flen); - else if (ftype == 'E') - xo_format_content(xop, "error", "error", 0, - content, clen, format, flen); - else if (ftype == 'L') - xo_format_content(xop, "label", NULL, 1, - content, clen, format, flen); - else if (ftype == 'N') - xo_format_content(xop, "note", NULL, 1, - content, clen, format, flen); - else if (ftype == 'P') - xo_format_content(xop, "padding", NULL, 1, - content, clen, format, flen); - else if (ftype == 'T') - xo_format_title(xop, content, clen, format, flen); - else if (ftype == 'U') { - if (flags & XFF_WS) - xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0); - xo_format_units(xop, content, clen, format, flen); - } else if (ftype == 'W') - xo_format_content(xop, "warning", "warning", 0, - content, clen, format, flen); - else if (ftype == '[') - xo_anchor_start(xop, content, clen, format, flen); + + } else if (ftype == '[') + xo_anchor_start(xop, content, clen, format, flen); else if (ftype == ']') - xo_anchor_stop(xop, content, clen, format, flen); + xo_anchor_stop(xop, content, clen, format, flen); + else if (ftype == 'C') + xo_format_colors(xop, content, clen, format, flen); + + else if (clen || format) { /* Need either content or format */ + if (format == NULL) { + /* Default format for value fields is '%s' */ + format = "%s"; + flen = 2; + } + + if (ftype == 'D') + xo_format_content(xop, "decoration", NULL, 1, + content, clen, format, flen); + else if (ftype == 'E') + xo_format_content(xop, "error", "error", 0, + content, clen, format, flen); + else if (ftype == 'L') + xo_format_content(xop, "label", NULL, 1, + content, clen, format, flen); + else if (ftype == 'N') + xo_format_content(xop, "note", NULL, 1, + content, clen, format, flen); + else if (ftype == 'P') + xo_format_content(xop, "padding", NULL, 1, + content, clen, format, flen); + else if (ftype == 'T') + xo_format_title(xop, content, clen, format, flen); + else if (ftype == 'U') { + if (flags & XFF_WS) + xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0); + xo_format_units(xop, content, clen, format, flen); + } else if (ftype == 'W') + xo_format_content(xop, "warning", "warning", 0, + content, clen, format, flen); + } if (flags & XFF_COLON) xo_format_content(xop, "decoration", NULL, 1, ":", 1, NULL, 0); @@ -3427,8 +4184,12 @@ xo_do_emit (xo_handle_t *xop, const char *fmt) } /* If we don't have an anchor, write the text out */ - if (flush && !(xop->xo_flags & XOF_ANCHOR)) - xo_write(xop); + if (flush && !(xop->xo_flags & XOF_ANCHOR)) { + if (xo_write(xop) < 0) + rc = -1; /* Report failure */ + else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0) + rc = -1; + } return (rc < 0) ? rc : (int) xop->xo_columns; } @@ -3481,7 +4242,7 @@ 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); - if (xop->xo_style != XO_STYLE_XML) + if (xo_style(xop) != XO_STYLE_XML) return 0; int nlen = strlen(name); @@ -3551,8 +4312,11 @@ xo_stack_set_flags (xo_handle_t *xop) static void xo_depth_change (xo_handle_t *xop, const char *name, - int delta, int indent, xo_xsf_flags_t flags) + 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 (xop->xo_flags & XOF_DTRT) flags |= XSF_DTRT; @@ -3562,18 +4326,17 @@ xo_depth_change (xo_handle_t *xop, const char *name, xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta]; xsp->xs_flags = flags; + xsp->xs_state = state; xo_stack_set_flags(xop); - unsigned save = (xop->xo_flags & (XOF_XPATH | XOF_WARN | XOF_DTRT)); - save |= (flags & XSF_DTRT); + if (name == NULL) + name = XO_FAILURE_NAME; - if (name && save) { - int len = strlen(name) + 1; - char *cp = xo_realloc(NULL, len); - if (cp) { - memcpy(cp, name, len); - xsp->xs_name = cp; - } + int len = strlen(name) + 1; + char *cp = xo_realloc(NULL, len); + if (cp) { + memcpy(cp, name, len); + xsp->xs_name = cp; } } else { /* Pop operation */ @@ -3637,11 +4400,23 @@ xo_stack_flags (unsigned xflags) return 0; } -static int -xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) +static void +xo_emit_top (xo_handle_t *xop, const char *ppn) { - xop = xo_default(xop); + xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); + xop->xo_flags |= XOF_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 = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; const char *pre_nl = ""; @@ -3653,22 +4428,26 @@ xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) flags |= xop->xo_flags; /* Pick up handle flags */ - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: - rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", - name, ppn); - xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); + 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_flags & XOF_NO_TOP)) { - if (!(xop->xo_flags & XOF_TOP_EMITTED)) { - xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); - xop->xo_flags |= XOF_TOP_EMITTED; - } - } + if (!(xop->xo_flags & XOF_NO_TOP) + && !(xop->xo_flags & XOF_TOP_EMITTED)) + xo_emit_top(xop, ppn); if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", "; @@ -3676,18 +4455,21 @@ xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name) rc = xo_printf(xop, "%s%*s\"%s\": {%s", pre_nl, xo_indent(xop), "", name, ppn); - xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); - break; - - case XO_STYLE_HTML: - case XO_STYLE_TEXT: - xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags)); 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) { @@ -3712,8 +4494,8 @@ xo_open_container_d (const char *name) return xo_open_container_hf(NULL, XOF_DTRT, name); } -int -xo_close_container_h (xo_handle_t *xop, const char *name) +static int +xo_do_close_container (xo_handle_t *xop, const char *name) { xop = xo_default(xop); @@ -3723,8 +4505,6 @@ xo_close_container_h (xo_handle_t *xop, const char *name) if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; - if (!(xsp->xs_flags & XSF_DTRT)) - xo_failure(xop, "missing name without 'dtrt' mode"); name = xsp->xs_name; if (name) { @@ -3733,13 +4513,15 @@ xo_close_container_h (xo_handle_t *xop, const char *name) char *cp = alloca(len); memcpy(cp, name, len); name = cp; - } else + } else if (!(xsp->xs_flags & XSF_DTRT)) { + xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; + } } - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: - xo_depth_change(xop, name, -1, -1, 0); + xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0); rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn); break; @@ -3747,14 +4529,14 @@ xo_close_container_h (xo_handle_t *xop, const char *name) pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; ppn = (xop->xo_depth <= 1) ? "\n" : ""; - xo_depth_change(xop, name, -1, -1, 0); + 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, 0); + xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0); break; } @@ -3762,6 +4544,12 @@ xo_close_container_h (xo_handle_t *xop, const char *name) } 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); @@ -3780,42 +4568,49 @@ xo_close_container_d (void) } static int -xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) +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); - if (xop->xo_style != XO_STYLE_JSON) - return 0; + if (xo_style(xop) == XO_STYLE_JSON) { + const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; + const char *pre_nl = ""; - int rc = 0; - const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; - const char *pre_nl = ""; + indent = 1; + if (!(xop->xo_flags & XOF_NO_TOP) + && !(xop->xo_flags & XOF_TOP_EMITTED)) + xo_emit_top(xop, ppn); - if (!(xop->xo_flags & XOF_NO_TOP)) { - if (!(xop->xo_flags & XOF_TOP_EMITTED)) { - xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); - xop->xo_flags |= XOF_TOP_EMITTED; + if (name == NULL) { + xo_failure(xop, "NULL passed for list name"); + name = XO_FAILURE_NAME; } - } - if (name == NULL) { - xo_failure(xop, "NULL passed for list name"); - name = XO_FAILURE_NAME; - } + xo_stack_set_flags(xop); - xo_stack_set_flags(xop); + if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) + pre_nl = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", "; + xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; - if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) - pre_nl = (xop->xo_flags & 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); + } - rc = xo_printf(xop, "%s%*s\"%s\": [%s", - pre_nl, xo_indent(xop), "", name, ppn); - xo_depth_change(xop, name, 1, 1, XSF_LIST | xo_stack_flags(flags)); + 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) { @@ -3840,21 +4635,14 @@ xo_open_list_d (const char *name) return xo_open_list_hf(NULL, XOF_DTRT, name); } -int -xo_close_list_h (xo_handle_t *xop, const char *name) +static int +xo_do_close_list (xo_handle_t *xop, const char *name) { int rc = 0; const char *pre_nl = ""; - xop = xo_default(xop); - - if (xop->xo_style != XO_STYLE_JSON) - return 0; - if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; - if (!(xsp->xs_flags & XSF_DTRT)) - xo_failure(xop, "missing name without 'dtrt' mode"); name = xsp->xs_name; if (name) { @@ -3863,22 +4651,36 @@ xo_close_list_h (xo_handle_t *xop, const char *name) char *cp = alloca(len); memcpy(cp, name, len); name = cp; - } else + } else if (!(xsp->xs_flags & XSF_DTRT)) { + xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; + } } - if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) - pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; - xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; + if (xo_style(xop) == XO_STYLE_JSON) { + if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) + pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; + xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; - xo_depth_change(xop, name, -1, -1, XSF_LIST); - rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), ""); - 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; + + } else { + xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST); + xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; + } 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); @@ -3897,7 +4699,88 @@ xo_close_list_d (void) } static int -xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) +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); + + if (xo_style(xop) == XO_STYLE_JSON) { + const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; + const char *pre_nl = ""; + + indent = 1; + + if (!(xop->xo_flags & XOF_NO_TOP)) { + if (!(xop->xo_flags & XOF_TOP_EMITTED)) { + xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn); + xop->xo_flags |= XOF_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 = (xop->xo_flags & 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); + } + + 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; + } + } + + if (xo_style(xop) == XO_STYLE_JSON) { + if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) + pre_nl = (xop->xo_flags & 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; + + } else { + xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST); + xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST; + } + + return rc; +} + +static int +xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) { xop = xo_default(xop); @@ -3912,10 +4795,18 @@ xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) name = XO_FAILURE_NAME; } - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: - rc = xo_printf(xop, "%*s<%s>%s", xo_indent(xop), "", name, ppn); - xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); + 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: @@ -3927,18 +4818,20 @@ xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name) rc = xo_printf(xop, "%s%*s{%s", pre_nl, xo_indent(xop), "", ppn); - xo_depth_change(xop, name, 1, 1, xo_stack_flags(flags)); - break; - - case XO_STYLE_HTML: - case XO_STYLE_TEXT: - xo_depth_change(xop, name, 1, 0, xo_stack_flags(flags)); 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) { @@ -3963,8 +4856,8 @@ xo_open_instance_d (const char *name) return xo_open_instance_hf(NULL, XOF_DTRT, name); } -int -xo_close_instance_h (xo_handle_t *xop, const char *name) +static int +xo_do_close_instance (xo_handle_t *xop, const char *name) { xop = xo_default(xop); @@ -3974,8 +4867,6 @@ xo_close_instance_h (xo_handle_t *xop, const char *name) if (name == NULL) { xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth]; - if (!(xsp->xs_flags & XSF_DTRT)) - xo_failure(xop, "missing name without 'dtrt' mode"); name = xsp->xs_name; if (name) { @@ -3984,27 +4875,29 @@ xo_close_instance_h (xo_handle_t *xop, const char *name) char *cp = alloca(len); memcpy(cp, name, len); name = cp; - } else + } else if (!(xsp->xs_flags & XSF_DTRT)) { + xo_failure(xop, "missing name without 'dtrt' mode"); name = XO_FAILURE_NAME; + } } - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_XML: - xo_depth_change(xop, name, -1, -1, 0); + 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 = (xop->xo_flags & XOF_PRETTY) ? "\n" : ""; - xo_depth_change(xop, name, -1, -1, 0); + 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, 0); + xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0); break; } @@ -4012,6 +4905,12 @@ xo_close_instance_h (xo_handle_t *xop, const char *name) } 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); @@ -4029,15 +4928,425 @@ 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); +} + void xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func, - xo_close_func_t close_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 @@ -4047,14 +5356,15 @@ xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func) xo_free = free_func; } -void +int xo_flush_h (xo_handle_t *xop) { static char div_close[] = "</div>"; + int rc; xop = xo_default(xop); - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_HTML: if (xop->xo_flags & XOF_DIV_OPEN) { xop->xo_flags &= ~XOF_DIV_OPEN; @@ -4066,22 +5376,30 @@ xo_flush_h (xo_handle_t *xop) break; } - xo_write(xop); + rc = xo_write(xop); + if (rc >= 0 && xop->xo_flush) + if (xop->xo_flush(xop->xo_opaque) < 0) + return -1; + + return rc; } -void +int xo_flush (void) { - xo_flush_h(NULL); + return xo_flush_h(NULL); } -void +int xo_finish_h (xo_handle_t *xop) { const char *cp = ""; xop = xo_default(xop); - switch (xop->xo_style) { + if (!(xop->xo_flags & XOF_NO_CLOSE)) + xo_do_close_all(xop, xop->xo_stack); + + switch (xo_style(xop)) { case XO_STYLE_JSON: if (!(xop->xo_flags & XOF_NO_TOP)) { if (xop->xo_flags & XOF_TOP_EMITTED) @@ -4093,13 +5411,13 @@ xo_finish_h (xo_handle_t *xop) break; } - xo_flush_h(xop); + return xo_flush_h(xop); } -void +int xo_finish (void) { - xo_finish_h(NULL); + return xo_finish_h(NULL); } /* @@ -4123,7 +5441,7 @@ xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap) fmt = newfmt; } - switch (xop->xo_style) { + switch (xo_style(xop)) { case XO_STYLE_TEXT: vfprintf(stderr, fmt, vap); break; @@ -4143,6 +5461,7 @@ xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap) break; case XO_STYLE_XML: + case XO_STYLE_JSON: va_copy(xop->xo_vap, vap); xo_open_container_h(xop, "error"); @@ -4237,6 +5556,65 @@ xo_parse_args (int argc, char **argv) return save; } +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); + } +} + +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; + + 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 XML, we record the version string in our handle, and emit + * it in xo_emit_top. + */ + int len = strlen(version) + 1; + xop->xo_version = xo_realloc(NULL, len); + if (xop->xo_version) + memcpy(xop->xo_version, version, len); + } + break; + } +} + +void +xo_set_version (const char *version) +{ + xo_set_version_h(NULL, version); +} + #ifdef UNIT_TEST int main (int argc, char **argv) |