summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bin/ls/Makefile2
-rw-r--r--bin/ps/Makefile2
-rw-r--r--contrib/libxo/.gitignore3
-rw-r--r--contrib/libxo/.travis.yml2
-rw-r--r--contrib/libxo/INSTALL.md15
-rw-r--r--contrib/libxo/Makefile.am8
-rw-r--r--contrib/libxo/bin/Zaliases5
-rwxr-xr-xcontrib/libxo/bin/setup.sh2
-rw-r--r--contrib/libxo/configure.ac182
-rw-r--r--contrib/libxo/doc/Makefile.am23
-rw-r--r--contrib/libxo/doc/libxo.txt1359
-rw-r--r--contrib/libxo/encoder/Makefile.am9
-rw-r--r--contrib/libxo/encoder/cbor/Makefile.am51
-rw-r--r--contrib/libxo/encoder/cbor/enc_cbor.c365
-rw-r--r--contrib/libxo/encoder/test/Makefile.am51
-rw-r--r--contrib/libxo/encoder/test/enc_test.c30
-rw-r--r--contrib/libxo/libxo/Makefile.am51
-rw-r--r--contrib/libxo/libxo/add.man29
-rw-r--r--contrib/libxo/libxo/add.man.in29
-rw-r--r--contrib/libxo/libxo/libxo.365
-rw-r--r--contrib/libxo/libxo/libxo.c3419
-rw-r--r--contrib/libxo/libxo/xo.h214
-rw-r--r--contrib/libxo/libxo/xo_attr.329
-rw-r--r--contrib/libxo/libxo/xo_buf.h158
-rw-r--r--contrib/libxo/libxo/xo_config.h (renamed from contrib/libxo/libxo/xoconfig.h)50
-rw-r--r--contrib/libxo/libxo/xo_create.332
-rw-r--r--contrib/libxo/libxo/xo_emit.378
-rw-r--r--contrib/libxo/libxo/xo_emit_err.372
-rw-r--r--contrib/libxo/libxo/xo_encoder.c375
-rw-r--r--contrib/libxo/libxo/xo_encoder.h116
-rw-r--r--contrib/libxo/libxo/xo_err.355
-rw-r--r--contrib/libxo/libxo/xo_error.330
-rw-r--r--contrib/libxo/libxo/xo_finish.332
-rw-r--r--contrib/libxo/libxo/xo_flush.332
-rw-r--r--contrib/libxo/libxo/xo_format.5371
-rw-r--r--contrib/libxo/libxo/xo_humanize.h169
-rw-r--r--contrib/libxo/libxo/xo_message.368
-rw-r--r--contrib/libxo/libxo/xo_no_setlocale.331
-rw-r--r--contrib/libxo/libxo/xo_open_container.344
-rw-r--r--contrib/libxo/libxo/xo_open_list.356
-rw-r--r--contrib/libxo/libxo/xo_open_marker.339
-rw-r--r--contrib/libxo/libxo/xo_parse_args.343
-rw-r--r--contrib/libxo/libxo/xo_set_allocator.332
-rw-r--r--contrib/libxo/libxo/xo_set_flags.345
-rw-r--r--contrib/libxo/libxo/xo_set_info.332
-rw-r--r--contrib/libxo/libxo/xo_set_options.330
-rw-r--r--contrib/libxo/libxo/xo_set_style.332
-rw-r--r--contrib/libxo/libxo/xo_set_syslog_enterprise_id.336
-rw-r--r--contrib/libxo/libxo/xo_set_version.329
-rw-r--r--contrib/libxo/libxo/xo_set_writer.332
-rw-r--r--contrib/libxo/libxo/xo_syslog.379
-rw-r--r--contrib/libxo/libxo/xo_syslog.c706
-rw-r--r--contrib/libxo/libxo/xo_wcwidth.h313
-rw-r--r--contrib/libxo/libxo/xoconfig.h.in206
-rw-r--r--contrib/libxo/libxo/xoversion.h38
-rw-r--r--contrib/libxo/libxo/xoversion.h.in38
-rw-r--r--contrib/libxo/tests/Makefile.am6
-rw-r--r--contrib/libxo/tests/core/Makefile.am24
-rw-r--r--contrib/libxo/tests/core/saved/test_01.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_01.E.out119
-rw-r--r--contrib/libxo/tests/core/saved/test_01.H.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_01.HIPx.out77
-rw-r--r--contrib/libxo/tests/core/saved/test_01.HP.out7
-rw-r--r--contrib/libxo/tests/core/saved/test_01.J.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_01.JP.out8
-rw-r--r--contrib/libxo/tests/core/saved/test_01.T.out1
-rw-r--r--contrib/libxo/tests/core/saved/test_01.X.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_01.XP.out14
-rw-r--r--contrib/libxo/tests/core/saved/test_02.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_02.E.out68
-rw-r--r--contrib/libxo/tests/core/saved/test_02.H.out6
-rw-r--r--contrib/libxo/tests/core/saved/test_02.HIPx.out100
-rw-r--r--contrib/libxo/tests/core/saved/test_02.HP.out100
-rw-r--r--contrib/libxo/tests/core/saved/test_02.J.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_02.JP.out37
-rw-r--r--contrib/libxo/tests/core/saved/test_02.T.out19
-rw-r--r--contrib/libxo/tests/core/saved/test_02.X.out8
-rw-r--r--contrib/libxo/tests/core/saved/test_02.XP.out50
-rw-r--r--contrib/libxo/tests/core/saved/test_03.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_03.E.out22
-rw-r--r--contrib/libxo/tests/core/saved/test_04.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_04.E.out22
-rw-r--r--contrib/libxo/tests/core/saved/test_05.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_05.E.out96
-rw-r--r--contrib/libxo/tests/core/saved/test_05.H.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_05.HIPx.out86
-rw-r--r--contrib/libxo/tests/core/saved/test_05.HP.out86
-rw-r--r--contrib/libxo/tests/core/saved/test_05.J.out3
-rw-r--r--contrib/libxo/tests/core/saved/test_05.JP.out24
-rw-r--r--contrib/libxo/tests/core/saved/test_05.T.out28
-rw-r--r--contrib/libxo/tests/core/saved/test_05.X.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_05.XP.out21
-rw-r--r--contrib/libxo/tests/core/saved/test_06.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_06.E.out22
-rw-r--r--contrib/libxo/tests/core/saved/test_07.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_07.E.out76
-rw-r--r--contrib/libxo/tests/core/saved/test_08.E.err18
-rw-r--r--contrib/libxo/tests/core/saved/test_08.E.out186
-rw-r--r--contrib/libxo/tests/core/saved/test_08.H.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.HIPx.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.HIPx.out134
-rw-r--r--contrib/libxo/tests/core/saved/test_08.HP.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.J.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.J.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_08.JP.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.JP.out6
-rw-r--r--contrib/libxo/tests/core/saved/test_08.T.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.X.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.X.out2
-rw-r--r--contrib/libxo/tests/core/saved/test_08.XP.err34
-rw-r--r--contrib/libxo/tests/core/saved/test_08.XP.out12
-rw-r--r--contrib/libxo/tests/core/saved/test_09.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_09.E.out40
-rw-r--r--contrib/libxo/tests/core/saved/test_10.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_10.E.out126
-rw-r--r--contrib/libxo/tests/core/saved/test_11.E.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.E.out26
-rw-r--r--contrib/libxo/tests/core/saved/test_11.H.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.H.out16
-rw-r--r--contrib/libxo/tests/core/saved/test_11.HIPx.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.HIPx.out16
-rw-r--r--contrib/libxo/tests/core/saved/test_11.HP.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.HP.out16
-rw-r--r--contrib/libxo/tests/core/saved/test_11.J.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.J.out18
-rw-r--r--contrib/libxo/tests/core/saved/test_11.JP.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.JP.out22
-rw-r--r--contrib/libxo/tests/core/saved/test_11.T.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.T.out16
-rw-r--r--contrib/libxo/tests/core/saved/test_11.X.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.X.out17
-rw-r--r--contrib/libxo/tests/core/saved/test_11.XP.err0
-rw-r--r--contrib/libxo/tests/core/saved/test_11.XP.out18
-rw-r--r--contrib/libxo/tests/core/test_01.c21
-rw-r--r--contrib/libxo/tests/core/test_02.c54
-rw-r--r--contrib/libxo/tests/core/test_05.c49
-rw-r--r--contrib/libxo/tests/core/test_08.c16
-rw-r--r--contrib/libxo/tests/core/test_11.c109
-rw-r--r--contrib/libxo/tests/gettext/Makefile.am224
-rw-r--r--contrib/libxo/tests/gettext/gt_01.c115
-rw-r--r--contrib/libxo/tests/gettext/gt_01.pot105
-rw-r--r--contrib/libxo/tests/gettext/ldns.pot28
-rw-r--r--contrib/libxo/tests/gettext/po/pig_latin/gt_01.po109
-rw-r--r--contrib/libxo/tests/gettext/po/pig_latin/ldns.po30
-rw-r--r--contrib/libxo/tests/gettext/po/pig_latin/strerror.po459
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.H.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.H.out1
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.HIPx.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.HIPx.out139
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.HP.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.HP.out139
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.J.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.J.out2
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.JP.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.JP.out53
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.T.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.T.out17
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.X.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.X.out1
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.XP.err0
-rw-r--r--contrib/libxo/tests/gettext/saved/gt_01.XP.out49
-rw-r--r--contrib/libxo/tests/gettext/strerror.pot468
-rw-r--r--contrib/libxo/xo/Makefile.am8
-rw-r--r--contrib/libxo/xo/xo.125
-rw-r--r--contrib/libxo/xo/xo.c9
-rw-r--r--contrib/libxo/xohtml/Makefile.am4
-rw-r--r--contrib/libxo/xohtml/xohtml.126
-rw-r--r--contrib/libxo/xolint/Makefile.am4
-rw-r--r--contrib/libxo/xolint/xolint.125
-rwxr-xr-xcontrib/libxo/xolint/xolint.pl84
-rw-r--r--contrib/libxo/xopo/Makefile.am43
-rw-r--r--contrib/libxo/xopo/xopo.177
-rw-r--r--contrib/libxo/xopo/xopo.c292
-rw-r--r--etc/mtree/BSD.usr.dist4
-rw-r--r--lib/libxo/Makefile43
-rw-r--r--libexec/ftpd/Makefile2
-rw-r--r--sbin/savecore/Makefile2
-rw-r--r--usr.bin/iscsictl/Makefile2
-rw-r--r--usr.bin/netstat/Makefile4
-rw-r--r--usr.bin/w/Makefile2
-rw-r--r--usr.bin/wc/Makefile2
-rw-r--r--usr.bin/xo/Makefile2
182 files changed, 12038 insertions, 2359 deletions
diff --git a/bin/ls/Makefile b/bin/ls/Makefile
index 422d891..a9431f1 100644
--- a/bin/ls/Makefile
+++ b/bin/ls/Makefile
@@ -5,7 +5,7 @@
PROG= ls
SRCS= cmp.c ls.c print.c util.c
-LIBADD= util xo
+LIBADD= xo util
.if !defined(RELEASE_CRUNCH) && \
${MK_LS_COLORS} != no
diff --git a/bin/ps/Makefile b/bin/ps/Makefile
index 79e9fc6..5177832 100644
--- a/bin/ps/Makefile
+++ b/bin/ps/Makefile
@@ -11,6 +11,6 @@ SRCS= fmt.c keyword.c nlist.c print.c ps.c
# on large systems.
#
CFLAGS+=-DLAZY_PS
-LIBADD= m kvm jail xo
+LIBADD= m kvm jail xo util
.include <bsd.prog.mk>
diff --git a/contrib/libxo/.gitignore b/contrib/libxo/.gitignore
index 386bfc8..8d70b6c 100644
--- a/contrib/libxo/.gitignore
+++ b/contrib/libxo/.gitignore
@@ -27,14 +27,17 @@ config.guess
config.h.in
config.sub
depcomp
+install-sh
ltmain.sh
missing
+m4
Makefile.in
configure
.DS_Store
xoconfig.h.in
+xo_config.h.in
.gdbinit
.gdbinit.local
diff --git a/contrib/libxo/.travis.yml b/contrib/libxo/.travis.yml
index e26a769..1173578 100644
--- a/contrib/libxo/.travis.yml
+++ b/contrib/libxo/.travis.yml
@@ -1,6 +1,6 @@
language: c
-script: printenv && uname -a && /bin/sh ./bin/setup.sh && cd build && ../configure --enable-warnings && make && sudo make install && make test
+script: printenv && uname -a && ls -l && /bin/sh -x ./bin/setup.sh && cd build && ../configure --enable-warnings && make && sudo make install && make test
notifications:
recipients:
diff --git a/contrib/libxo/INSTALL.md b/contrib/libxo/INSTALL.md
new file mode 100644
index 0000000..70b80bc
--- /dev/null
+++ b/contrib/libxo/INSTALL.md
@@ -0,0 +1,15 @@
+<!---
+# $Id$
+#
+# Copyright 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.
+#-->
+
+## Instructions for building libxo
+
+Instructions for building libxo are now available in the
+[wiki](http://juniper.github.io/libxo/libxo-manual.html#getting-libxo).
diff --git a/contrib/libxo/Makefile.am b/contrib/libxo/Makefile.am
index 1abfd5e..e050bc4 100644
--- a/contrib/libxo/Makefile.am
+++ b/contrib/libxo/Makefile.am
@@ -10,7 +10,7 @@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = libxo xo xolint xohtml tests doc
+SUBDIRS = libxo xo xopo xolint xohtml tests doc encoder
bin_SCRIPTS=libxo-config
dist_doc_DATA = Copyright
@@ -94,3 +94,9 @@ packages:
&& git commit -m 'new packaging data' \
${GH_PACKAGING_DIR} \
&& git push origin gh-pages ) ; true
+
+ANALYZE_DIR = ~/trash/libxo
+ANALYZE_CMD = scan-build-mp-3.6
+
+analyze:
+ ${ANALYZE_CMD} -o ${ANALYZE_DIR} ${MAKE}
diff --git a/contrib/libxo/bin/Zaliases b/contrib/libxo/bin/Zaliases
index b8fb5db..04cdec7 100644
--- a/contrib/libxo/bin/Zaliases
+++ b/contrib/libxo/bin/Zaliases
@@ -6,6 +6,7 @@ set opts=' \
--enable-debug \
--enable-warnings \
--enable-printflike \
+--with-gettext=/opt/local \
--prefix ${HOME}/work/root \
'
set opts=`echo $opts`
@@ -22,3 +23,7 @@ cd build
alias xx 'cc -I.. -W -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Werror -Waggregate-return -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wformat -Wimplicit -Wmissing-declarations -Wnested-externs -Wparentheses -Wreturn-type -Wshadow -Wswitch -Wtrigraphs -Wuninitialized -Wunused -Wwrite-strings -fno-inline-functions-called-once -g -O2 -o xtest -DUNIT_TEST libxo.c'
+
+alias mm "make CFLAGS='-O0 -g'"
+
+alias mmi 'mm && mi'
diff --git a/contrib/libxo/bin/setup.sh b/contrib/libxo/bin/setup.sh
index 5e03ff3..f49dd48 100755
--- a/contrib/libxo/bin/setup.sh
+++ b/contrib/libxo/bin/setup.sh
@@ -11,6 +11,8 @@ if [ ! -f configure ]; then
vers=`autoreconf --version | head -1`
echo "Using" $vers
+ mkdir -p m4
+
autoreconf --install
if [ ! -f configure ]; then
diff --git a/contrib/libxo/configure.ac b/contrib/libxo/configure.ac
index 5491d57..1783120 100644
--- a/contrib/libxo/configure.ac
+++ b/contrib/libxo/configure.ac
@@ -12,7 +12,7 @@
#
AC_PREREQ(2.2)
-AC_INIT([libxo], [0.3.2], [phil@juniper.net])
+AC_INIT([libxo], [0.4.5], [phil@juniper.net])
AM_INIT_AUTOMAKE([-Wall -Werror foreign -Wno-portability])
# Support silent build rules. Requires at least automake-1.11.
@@ -54,12 +54,16 @@ AC_CHECK_FUNCS([sranddev srand strlcpy])
AC_CHECK_FUNCS([fdopen getrusage])
AC_CHECK_FUNCS([gettimeofday ctime])
AC_CHECK_FUNCS([getpass])
+AC_CHECK_FUNCS([getprogname])
AC_CHECK_FUNCS([sysctlbyname])
AC_CHECK_FUNCS([flock])
AC_CHECK_FUNCS([asprintf])
AC_CHECK_FUNCS([__flbf])
+AC_CHECK_FUNCS([sysctlbyname])
+
AC_CHECK_HEADERS([dlfcn.h])
+AC_CHECK_HEADERS([dlfcn.h])
AC_CHECK_HEADERS([stdio_ext.h])
AC_CHECK_HEADERS([tzfile.h])
AC_CHECK_HEADERS([stdtime/tzfile.h])
@@ -69,10 +73,139 @@ AC_CHECK_HEADERS([sys/time.h])
AC_CHECK_HEADERS([ctype.h errno.h stdio.h stdlib.h])
AC_CHECK_HEADERS([string.h sys/param.h unistd.h ])
AC_CHECK_HEADERS([sys/sysctl.h])
+AC_CHECK_HEADERS([threads.h])
+
+dnl humanize_number(3) is a great function, but it's not standard.
+dnl Note Macosx has the function in libutil.a but doesn't ship the
+dnl header file, so I'll need to carry my own implementation. See:
+dnl https://devforums.apple.com/thread/271121
+AC_CHECK_HEADERS([libutil.h])
+AC_CHECK_LIB([util], [humanize_number],
+ [HAVE_HUMANIZE_NUMBER=$ac_cv_header_libutil_h],
+ [HAVE_HUMANIZE_NUMBER=no])
+
+AC_MSG_RESULT(humanize_number results: :${HAVE_HUMANIZE_NUMBER}:${ac_cv_header_libutil_h}:)
+
+if test "$HAVE_HUMANIZE_NUMBER" = "yes"; then
+ AC_DEFINE([HAVE_HUMANIZE_NUMBER], [1], [humanize_number(3)])
+fi
+
+AM_CONDITIONAL([HAVE_HUMANIZE_NUMBER], [test "$HAVE_HUMANIZE_NUMBER" = "yes"])
+
+AC_ARG_ENABLE([gettext],
+ [ --disable-gettext Turn off support for gettext],
+ [GETTEXT_ENABLE=$enableval],
+ [GETTEXT_ENABLE=yes])
+
+dnl Looking for gettext(), assumably in libintl
+AC_ARG_WITH(gettext,
+ [ --with-gettext=[PFX] Specify location of gettext installation],
+ [GETTEXT_PREFIX=$withval],
+ [GETTEXT_PREFIX=/usr],
+)
+
+HAVE_GETTEXT=no
+if test "$GETTEXT_ENABLE" != "no"; then
+
+ AC_MSG_CHECKING([gettext in ${GETTEXT_PREFIX}])
+
+ _save_cflags="$CFLAGS"
+ CFLAGS="$CFLAGS -I${GETTEXT_PREFIX}/include -L${GETTEXT_PREFIX}/lib -Werror -lintl"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[#include <libintl.h>]
+ [int main() {char *cp = dgettext(NULL, "xx"); return 0; }]])],
+ [HAVE_GETTEXT=yes],
+ [HAVE_GETTEXT=no])
+ CFLAGS="$_save_cflags"
+
+ AC_MSG_RESULT([$HAVE_GETTEXT])
+
+ if test "$HAVE_GETTEXT" != "yes"; then
+ GETTEXT_PREFIX=/opt/local
+ AC_MSG_CHECKING([gettext in ${GETTEXT_PREFIX}])
+
+ _save_cflags="$CFLAGS"
+ CFLAGS="$CFLAGS -I${GETTEXT_PREFIX}/include -L${GETTEXT_PREFIX}/lib -Werror -lintl"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[#include <libintl.h>]
+ [int main() {char *cp = dgettext(NULL, "xx"); return 0; }]])],
+ [HAVE_GETTEXT=yes],
+ [HAVE_GETTEXT=no])
+ CFLAGS="$_save_cflags"
+
+ AC_MSG_RESULT([$HAVE_GETTEXT])
+ fi
+fi
+
+if test "$HAVE_GETTEXT" = "yes"; then
+ AC_DEFINE([HAVE_GETTEXT], [1], [gettext(3)])
+ GETTEXT_CFLAGS="-I${GETTEXT_PREFIX}/include"
+ GETTEXT_LIBS="-L${GETTEXT_PREFIX}/lib -lintl"
+else
+ GETTEXT_PREFIX=none
+ GETTEXT_CFLAGS=
+ GETTEXT_LIBS=
+fi
+AC_SUBST(GETTEXT_CFLAGS)
+AC_SUBST(GETTEXT_LIBS)
+
+GETTEXT_BINDIR=${GETTEXT_PREFIX}/bin
+AC_SUBST(GETTEXT_BINDIR)
+GETTEXT_LIBDIR=${GETTEXT_PREFIX}/lib
+AC_SUBST(GETTEXT_LIBDIR)
+
+AM_CONDITIONAL([HAVE_GETTEXT], [test "$HAVE_GETTEXT" = "yes"])
+
+dnl Looking for how to do thread-local variables
+AC_ARG_WITH(threads,
+ [ --with-threads=[STYLE] Specify style of thread-local support (none)],
+ [THREAD_LOCAL=$withval],
+ [THREAD_LOCAL=unknown],
+)
+
+AC_MSG_CHECKING([thread-locals are ${THREAD_LOCAL}])
+
+if test "$THREAD_LOCAL" = "unknown"; then
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[]
+ [__thread int foo; int main() { foo++; return foo; }]])],
+ [THREAD_LOCAL=before],
+ [THREAD_LOCAL=unknown])
+
+ AC_MSG_RESULT([$THREAD_LOCAL])
+fi
+
+if test "$THREAD_LOCAL" = "unknown"; then
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[]
+ [int __thread foo; int main() { foo++; return foo; }]])],
+ [THREAD_LOCAL=after],
+ [THREAD_LOCAL=unknown])
+ AC_MSG_RESULT([$THREAD_LOCAL])
+fi
+
+if test "$THREAD_LOCAL" = "unknown"; then
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[]
+ [__declspec(int) foo; int main() { foo++; return foo; }]])],
+ [THREAD_LOCAL=declspec],
+ [THREAD_LOCAL=unknown])
+ AC_MSG_RESULT([$THREAD_LOCAL])
+fi
+
+if test "$THREAD_LOCAL" != "unknown"; then
+ AC_DEFINE_UNQUOTED([HAVE_THREAD_LOCAL],
+ THREAD_LOCAL_${THREAD_LOCAL}, [thread-local setting])
+fi
+
+dnl Looking for libcrypto....
AC_CHECK_LIB([crypto], [MD5_Init])
AM_CONDITIONAL([HAVE_LIBCRYPTO], [test "$HAVE_LIBCRYPTO" != "no"])
+AC_CHECK_MEMBER([struct sockaddr_un.sun_len],
+ [HAVE_SUN_LEN=yes ;
+ AC_DEFINE([HAVE_SUN_LEN], [1], [Have struct sockaddr_un.sun_len])],
+ [HAS_SUN_LEN=no], [[#include <sys/un.h>]])
+
+AC_CHECK_DECLS([__isthreaded], [], [], [#include <stdio.h>])
+HAVE_ISTHREADED=${ac_cv_have_decl___isthreaded}
+
dnl
dnl Some packages need to be checked against version numbers so we
dnl define a function here for later use
@@ -107,12 +240,15 @@ then
SLAX_BINDIR="`$SLAX_CONFIG --bindir | head -1`"
SLAX_OXTRADOCDIR="`$SLAX_CONFIG --oxtradoc | head -1`"
AC_MSG_RESULT($LIBSLAX_VERSION found)
+ HAVE_OXTRADOC=yes
else
LIBSLAX_VERSION=
SLAX_BINDIR=
SLAX_OXTRADOCDIR=
AC_MSG_RESULT([no])
+ HAVE_OXTRADOC=no
fi
+AM_CONDITIONAL([HAVE_OXTRADOC], [test "$HAVE_OXTRADOC" != "no"])
AC_SUBST(SLAX_BINDIR)
AC_SUBST(SLAX_OXTRADOCDIR)
@@ -141,6 +277,16 @@ AC_ARG_ENABLE([text-only],
AC_MSG_RESULT([$LIBXO_TEXT_ONLY])
AM_CONDITIONAL([LIBXO_TEXT_ONLY], [test "$LIBXO_TEXT_ONLY" != "no"])
+AC_MSG_CHECKING([whether to build with local wcwidth implementation])
+AC_ARG_ENABLE([wcwidth],
+ [ --disable-wcwidth Disable local wcwidth implementation],
+ [LIBXO_WCWIDTH=$enableval],
+ [LIBXO_WCWIDTH=yes])
+AC_MSG_RESULT([$LIBXO_WCWIDTH])
+if test "${LIBXO_WCWIDTH}" != "no"; then
+ AC_DEFINE([LIBXO_WCWIDTH], [1], [Enable local wcwidth implementation])
+fi
+
AC_CHECK_LIB([m], [lrint])
AM_CONDITIONAL([HAVE_LIBM], [test "$HAVE_LIBM" != "no"])
@@ -177,13 +323,16 @@ AM_CONDITIONAL([NO_LIBXO_OPTIONS], [test "$LIBXO_OPTS" != "yes"])
case $host_os in
darwin*)
LIBTOOL=glibtool
+ XO_LIBEXT=dylib
;;
Linux*|linux*)
CFLAGS="-D_GNU_SOURCE $CFLAGS"
LDFLAGS=-ldl
+ XO_LIBEXT=so
;;
cygwin*|CYGWIN*)
LDFLAGS=-no-undefined
+ XO_LIBEXT=ddl
;;
esac
@@ -203,6 +352,14 @@ AC_SUBST(XO_SRCDIR)
AC_SUBST(XO_LIBDIR)
AC_SUBST(XO_BINDIR)
AC_SUBST(XO_INCLUDEDIR)
+AC_SUBST(XO_LIBEXT)
+
+AC_ARG_WITH(encoder-dir,
+ [ --with-encoder-dir=[DIR] Specify location of encoder libraries],
+ [XO_ENCODERDIR=$withval],
+ [XO_ENCODERDIR=$libdir/libxo/encoder]
+)
+AC_SUBST(XO_ENCODERDIR)
AC_ARG_WITH(share-dir,
[ --with-share-dir=[DIR] Specify location of shared files],
@@ -232,20 +389,34 @@ AC_SUBST(LIBXO_VERSION)
AC_SUBST(LIBXO_VERSION_NUMBER)
AC_SUBST(LIBXO_VERSION_EXTRA)
-AC_CONFIG_HEADERS([libxo/xoconfig.h])
+AC_DEFINE_UNQUOTED(LIBXO_VERSION, ["$LIBXO_VERSION"],
+ [Version number as dotted value])
+AC_DEFINE_UNQUOTED(LIBXO_VERSION_NUMBER, [$LIBXO_VERSION_NUMBER],
+ [Version number as a number])
+AC_DEFINE_UNQUOTED(LIBXO_VERSION_STRING, ["$LIBXO_VERSION_NUMBER"],
+ [Version number as string])
+AC_DEFINE_UNQUOTED(LIBXO_VERSION_EXTRA, ["$LIBXO_VERSION_EXTRA"],
+ [Version number extra information])
+
+AC_CONFIG_HEADERS([libxo/xo_config.h])
AC_CONFIG_FILES([
Makefile
libxo-config
xohtml/xohtml.sh
libxo/Makefile
- libxo/xoversion.h
+ libxo/add.man
+ encoder/Makefile
+ encoder/cbor/Makefile
+ encoder/test/Makefile
xo/Makefile
xolint/Makefile
xohtml/Makefile
+ xopo/Makefile
packaging/libxo.pc
doc/Makefile
tests/Makefile
tests/core/Makefile
+ tests/gettext/Makefile
tests/xo/Makefile
packaging/libxo.spec
packaging/libxo.rb.base
@@ -262,6 +433,7 @@ AC_MSG_NOTICE([summary of build options:
bindir: ${XO_BINDIR}
includedir: ${XO_INCLUDEDIR}
share dir: ${XO_SHAREDIR}
+ extensions dir: ${XO_ENCODERDIR}
oxtradoc dir: ${SLAX_OXTRADOCDIR}
compiler: ${CC} (${HAVE_GCC:-no})
@@ -273,4 +445,8 @@ AC_MSG_NOTICE([summary of build options:
printf-like: ${HAVE_PRINTFLIKE:-no}
libxo-options: ${LIBXO_OPTS:-no}
text-only: ${LIBXO_TEXT_ONLY:-no}
+ gettext: ${HAVE_GETTEXT:-no} (${GETTEXT_PREFIX})
+ isthreaded: ${HAVE_ISTHREADED:-no}
+ thread-local: ${THREAD_LOCAL:-no}
+ local wcwidth: ${LIBXO_WCWIDTH:-no}
])
diff --git a/contrib/libxo/doc/Makefile.am b/contrib/libxo/doc/Makefile.am
index c0c3271..16d6ba5 100644
--- a/contrib/libxo/doc/Makefile.am
+++ b/contrib/libxo/doc/Makefile.am
@@ -8,6 +8,7 @@
# using the SOFTWARE, you agree to be bound by the terms of that
# LICENSE.
+if HAVE_OXTRADOC
OXTRADOC_DIR = ${SLAX_OXTRADOCDIR}
OXTRADOC_PREFIX = ${OXTRADOC_DIR}
OXTRADOC = ${OXTRADOC_DIR}/oxtradoc
@@ -38,28 +39,32 @@ OX_ARGS += -S ${SLAXPROC} -p doc
OX_CMD = ${PERL} ${PERLOPTS} ${OXTRADOC} ${OX_ARGS}
OXTRADOC_CMD = ${OX_CMD}
-
OUTPUT = libxo-manual
-INPUT = libxo.txt
+INPUT = libxo
EXTRA_DIST = \
- ${INPUT} \
+ ${INPUT}.txt \
${OUTPUT}.html \
${OUTPUT}.txt
doc docs: ${OUTPUT}.txt ${OUTPUT}.html
-${OUTPUT}.txt: ${INPUT} ${OXTRADOC} xolint.txt
+${OUTPUT}.txt: ${INPUT}.txt ${OXTRADOC} xolint.txt
${OXTRADOC_CMD} -m text -o $@ $<
-${OUTPUT}.html: ${INPUT} ${OXTRADOC} ${XML2HTMLBIN} xolint.txt
+${OUTPUT}.html: ${INPUT}.txt ${OXTRADOC} ${XML2HTMLBIN} xolint.txt
${OXTRADOC_CMD} -m html -o $@ $<
xolint.txt: ${top_srcdir}/xolint/xolint.pl
perl ${top_srcdir}/xolint/xolint.pl -D > xolint.txt
CLEANFILES = \
-${OUTPUT}.xml \
-${OUTPUT}.txt \
-${OUTPUT}.fxml \
-${OUTPUT}.html
+xolint.txt \
+${INPUT}.xml \
+${INPUT}.txt \
+${INPUT}.fxml \
+${INPUT}.html
+else
+doc docs:
+ @${ECHO} "The 'oxtradoc' tool is not installed; see libslax.org"
+endif
diff --git a/contrib/libxo/doc/libxo.txt b/contrib/libxo/doc/libxo.txt
index 81a40b6..1e7acc7 100644
--- a/contrib/libxo/doc/libxo.txt
+++ b/contrib/libxo/doc/libxo.txt
@@ -8,7 +8,7 @@
# Phil Shafer, July 2014
#
-* libxo
+* Overview
libxo - A Library for Generating Text, XML, JSON, and HTML Output
@@ -28,10 +28,10 @@ decides at run time which output style should be produced. The
application calls a function "xo_emit" to product output that is
described in a format string. A "field descriptor" tells libxo what
the field is and what it means. Each field descriptor is placed in
-braces with a printf-like format string:
+braces with a printf-like format string (^format-strings^):
xo_emit(" {:lines/%7ju} {:words/%7ju} "
- "{:characters/%7ju}{d:filename/%s}\n",
+ "{:characters/%7ju} {d:filename/%s}\n",
linect, wordct, charct, file);
Each field can have a role, with the 'value' role being the default,
@@ -43,10 +43,10 @@ can then be generated in various style, using the "--libxo" option:
% wc --libxo xml,pretty,warn /etc/motd
<wc>
<file>
- <filename>/etc/motd</filename>
<lines>25</lines>
<words>165</words>
<characters>1140</characters>
+ <filename>/etc/motd</filename>
</file>
</wc>
% wc --libxo json,pretty,warn /etc/motd
@@ -54,10 +54,10 @@ can then be generated in various style, using the "--libxo" option:
"wc": {
"file": [
{
- "filename": "/etc/motd",
"lines": 25,
"words": 165,
- "characters": 1140
+ "characters": 1140,
+ "filename": "/etc/motd"
}
]
}
@@ -95,10 +95,151 @@ command:
We're using semantic release numbering, as defined in
^http://semver.org/spec/v2.0.0.html^.
-libxo is open source, distributed under the BSD license. It
-is shipped as part of FreeBSD 11.0.
+libxo is open source, distributed under the BSD license. It shipped
+as part of the FreeBSD operating system starting with release 11.0.
-* Overview
+Issues, problems, and bugs should be directly to the issues page on
+our github site.
+
+*** Downloading libxo Source Code
+
+You can retrieve the source for libxo in two ways:
+
+A) Use a "distfile" for a specific release. We use
+github to maintain our releases. Visit
+github release page (^https://github.com/Juniper/libxo/releases^)
+to see the list of releases. To download the latest, look for the
+release with the green "Latest release" button and the green
+"libxo-RELEASE.tar.gz" button under that section.
+
+After downloading that release's distfile, untar it as follows:
+
+ tar -zxf libxo-RELEASE.tar.gz
+ cd libxo-RELEASE
+
+[Note: for Solaris users, your "tar" command lacks the "-z" flag,
+so you'll need to substitute "gzip -dc "file" | tar xf -" instead of
+"tar -zxf "file"".]
+
+B) Use the current build from github. This gives you the most recent
+source code, which might be less stable than a specific release. To
+build libxo from the git repo:
+
+ git clone https://github.com/Juniper/libxo.git
+ cd libxo
+
+_BE AWARE_: The github repository does _not_ contain the files
+generated by "autoreconf", with the notable exception of the "m4"
+directory. Since these files (depcomp, configure, missing,
+install-sh, etc) are generated files, we keep them out of the source
+code repository.
+
+This means that if you download the a release distfile, these files
+will be ready and you'll just need to run "configure", but if you
+download the source code from svn, then you'll need to run
+"autoreconf" by hand. This step is done for you by the "setup.sh"
+script, described in the next section.
+
+*** Building libxo
+
+To build libxo, you'll need to set up the build, run the "configure"
+script, run the "make" command, and run the regression tests.
+
+The following is a summary of the commands needed. These commands are
+explained in detail in the rest of this section.
+
+ sh bin/setup.sh
+ cd build
+ ../configure
+ make
+ make test
+ sudo make install
+
+The following sections will walk thru each of these steps with
+additional details and options, but the above directions should be all
+that's needed.
+
+**** Setting up the build
+
+[If you downloaded a distfile, you can skip this step.]
+
+Run the "setup.sh" script to set up the build. This script runs the
+"autoreconf" command to generate the "configure" script and other
+generated files.
+
+ sh bin/setup.sh
+
+Note: We're are currently using autoreconf version 2.69.
+
+**** Running the "configure" Script
+
+Configure (and autoconf in general) provides a means of building
+software in diverse environments. Our configure script supports
+a set of options that can be used to adjust to your operating
+environment. Use "configure --help" to view these options.
+
+We use the "build" directory to keep object files and generated files
+away from the source tree.
+
+To run the configure script, change into the "build" directory, and
+run the "configure" script. Add any required options to the
+"../configure" command line.
+
+ cd build
+ ../configure
+
+Expect to see the "configure" script generate the following error:
+
+ /usr/bin/rm: cannot remove `libtoolT': No such file or directory
+
+This error is harmless and can be safely ignored.
+
+By default, libxo installs architecture-independent files, including
+extension library files, in the /usr/local directories. To specify an
+installation prefix other than /usr/local for all installation files,
+include the --prefix=prefix option and specify an alternate
+location. To install just the extension library files in a different,
+user-defined location, include the --with-extensions-dir=dir option
+and specify the location where the extension libraries will live.
+
+ cd build
+ ../configure [OPTION]... [VAR=VALUE]...
+
+**** Running the "make" command
+
+Once the "configure" script is run, build the images using the "make"
+command:
+
+ make
+
+**** Running the Regression Tests
+
+libxo includes a set of regression tests that can be run to ensure
+the software is working properly. These test are optional, but will
+help determine if there are any issues running libxo on your
+machine. To run the regression tests:
+
+ make test
+
+**** Installing libxo
+
+Once the software is built, you'll need to install libxo using the
+"make install" command. If you are the root user, or the owner of the
+installation directory, simply issue the command:
+
+ make install
+
+If you are not the "root" user and are using the "sudo" package, use:
+
+ sudo make install
+
+Verify the installation by viewing the output of "xo --version":
+
+ % xo --version
+ libxo version 0.3.5-git-develop
+ xo version 0.3.5-git-develop
+
+* Formatting with libxo
Most unix commands emit text output aimed at humans. It is designed
to be parsed and understood by a user. Humans are gifted at
@@ -128,26 +269,41 @@ A single libxo function call in source code is all that's required:
xo_emit("Connecting to {:host}.{:domain}...\n", host, domain);
- Text:
- Connection to my-box.example.com...
+ TEXT:
+ Connecting to my-box.example.com...
XML:
<host>my-box</host>
<domain>example.com</domain>
JSON:
"host": "my-box",
"domain": "example.com"
-
-For brevity, the HTML output is emitted.
+ HTML:
+ <div class="line">
+ <div class="text">Connecting to </div>
+ <div class="data" data-tag="host"
+ data-xpath="/top/host">my-box</div>
+ <div class="text">.</div>
+ <div class="data" data-tag="domain"
+ data-xpath="/top/domain">example.com</div>
+ <div class="text">...</div>
+ </div>
** Encoding Styles
-There are four encoding styles supported by libxo: TEXT, HTML, JSON,
-and XML. JSON and XML are suitable for encoding data, while TEXT and
-HTML are suited for display to the user. TEXT output can be display
-on a terminal session, allowing compatibility with traditional usage.
-HTML can be matched with a small CSS file to permit rendering in any
-HTML5 browser. XML output is suitable for tools like XPath and
-protocols like NETCONF. JSON output can be used for RESTful APIs.
+There are four encoding styles supported by libxo:
+
+- TEXT output can be display on a terminal session, allowing
+compatibility with traditional command line usage.
+- XML output is suitable for tools like XPath and protocols like
+NETCONF.
+- JSON output can be used for RESTful APIs and integration with
+languages like Javascript and Python.
+- HTML can be matched with a small CSS file to permit rendering in any
+HTML5 browser.
+
+In general, XML and JSON are suitable for encoding data, while TEXT is
+suited for terminal output and HTML is suited for display in a web
+browser (see ^xohtml^).
*** Text Output
@@ -164,7 +320,7 @@ data might look like:
printf("%d\t%s\n", num_blocks, path);
Simple, direct, obvious. But it's only making text output. Imagine
-using a single code path to make text, XML, JSON or HTML, deciding at
+using a single code path to make TEXT, XML, JSON or HTML, deciding at
run time which to generate.
libxo expands on the idea of printf format strings to make a single
@@ -257,7 +413,7 @@ field descriptions within the format string.
The field description is given as follows:
- '{' [ role | modifier ]* ':' [ content ]
+ '{' [ role | modifier ]* [',' long-names ]* ':' [ content ]
[ '/' field-format [ '/' encoding-format ]] '}'
The role describes the function of the field, while the modifiers
@@ -271,23 +427,31 @@ label ("In stock"), and the third is a value field ("in-stock"). The
in-stock field has a "%u" format that will parse the next argument
passed to the xo_emit function as an unsigned integer.
- xo_emit("{P: }{Lwc:In stock}{:in-stock/%u}\n", 65);
+ xo_emit("{P: }{Lwc:In stock}{:in-stock/%u}\n", 65);
This single line of code can generate text (" In stock: 65\n"), XML
("<in-stock>65</in-stock>"), JSON ('"in-stock": 6'), or HTML (too
lengthy to be listed here).
-*** Modifier Roles
+While roles and modifiers typically use single character for brevity,
+there are alternative names for each which allow more verbose
+formatting strings. These names must be preceded by a comma, and may
+follow any single-character values:
+
+ xo_emit("{L,white,colon:In stock}{,key:in-stock/%u}\n", 65);
-Modifiers are optional, and indicate the role and formatting of the
+*** Field Roles
+
+Field roles are optional, and indicate the role and formatting of the
content. The roles are listed below; only one role is permitted:
|---+--------------+-------------------------------------------------|
-| M | Name | Description |
+| R | Name | Description |
|---+--------------+-------------------------------------------------|
-| C | color/effect | Field has color and effect controls |
+| C | color | Field has color and effect controls |
| D | decoration | Field is non-text (e.g., colon, comma) |
| E | error | Field is an error message |
+| G | gettext | Call gettext(3) on the format string |
| L | label | Field is text that prefixes a value |
| N | note | Field is text that follows a value |
| P | padding | Field is spaces needed for vertical alignment |
@@ -295,25 +459,59 @@ content. The roles are listed below; only one role is permitted:
| U | units | Field is the units for the previous value field |
| V | value | Field is the name of field (the default) |
| W | warning | Field is a warning message |
-| [ | start anchor | Begin a section of anchored variable-width text |
-| ] | stop anchor | End a section of anchored variable-width text |
+| [ | start-anchor | Begin a section of anchored variable-width text |
+| ] | stop-anchor | End a section of anchored variable-width text |
|---+--------------+-------------------------------------------------|
-**** The Color Role ({C:})
+ EXAMPLE:
+ xo_emit("{L:Free}{D::}{P: }{:free/%u} {U:Blocks}\n",
+ free_blocks);
+
+When a role is not provided, the "value" role is used as the default.
+
+Roles and modifiers can also use more verbose names, when preceeded by
+a comma:
+
+ EXAMPLE:
+ xo_emit("{,label:Free}{,decoration::}{,padding: }"
+ "{,value:free/%u} {,units:Blocks}\n",
+ free_blocks);
+
+**** The Color Role ({C:}) @color-role@
Colors and effects control how text values are displayed; they are
-used for display styles (TEXT and HTML). The color content can be
-either static, when placed directly within the field descriptor, or a
-printf-style format descriptor can be used, if preceded by a slash ("/"):
+used for display styles (TEXT and HTML).
+
+ xo_emit("{C:bold}{:value}{C:no-bold}\n", value);
+
+Colors and effects remain in effect until modified by other "C"-role
+fields.
+
+ xo_emit("{C:bold}{C:inverse}both{C:no-bold}only inverse\n");
+
+If the content is empty, the "reset" action is performed.
+
+ xo_emit("{C:both,underline}{:value}{C:}\n", value);
+
+The content should be a comma-separated list of zero or more colors or
+display effects.
+
+ xo_emit("{C:bold,inverse}Ugly{C:no-bold,no-inverse}\n");
+
+The color content can be either static, when placed directly within
+the field descriptor, or a printf-style format descriptor can be used,
+if preceded by a slash ("/"):
+
+ xo_emit("{C:/%s%s}{:value}{C:}", need_bold ? "bold" : "",
+ need_underline ? "underline" : "", value);
+
+Color names are prefixed with either "fg-" or "bg-" to change the
+foreground and background colors, respectively.
- xo_emit("{C:bold}{Lwc:Cost}{:cost/%u}{C:reset}\n", cost);
xo_emit("{C:/fg-%s,bg-%s}{Lwc:Cost}{:cost/%u}{C:reset}\n",
fg_color, bg_color, cost);
-The content should be a comma-separated list of zero or more colors or
-display effects. Colors and effects remain in effect until
-modified by other "C" roles. If the content is empty, the "reset"
-action is performed.
+The following table lists the supported effects:
|---------------+-------------------------------------------------|
| Name | Description |
@@ -332,22 +530,19 @@ action is performed.
The following color names are supported:
-|---------------|
-| Name |
-|---------------|
-| black |
-| blue |
-| cyan |
-| default |
-| green |
-| magenta |
-| red |
-| white |
-| yellow |
-|---------------|
-
-Color names are prefixed with either "fg-" or "bg-" to change the
-foreground and background colors, respectively.
+|---------+--------------------------------------------|
+| Name | Description |
+|---------+--------------------------------------------|
+| black | |
+| blue | |
+| cyan | |
+| default | Default color for foreground or background |
+| green | |
+| magenta | |
+| red | |
+| white | |
+| yellow | |
+|---------+--------------------------------------------|
**** The Decoration Role ({D:})
@@ -358,6 +553,37 @@ can use CSS to direct their display parameters.
xo_emit("{D:((}{:name}{D:))}\n", name);
+**** The Gettext Role ({G:}) @gettext-role@
+
+libxo supports internationalization (i18n) through its use of
+gettext(3). Use the "{G:}" role to request that the remaining part of
+the format string, following the "{G:}" field, be handled using
+gettext().
+
+Since gettext() uses the string as the key into the message catalog,
+libxo uses a simplified version of the format string that removes
+unimportant field formatting and modifiers, stopping minor formatting
+changes from impacting the expensive translation process. A developer
+change such as changing "/%06d" to "/%08d" should not force hand
+inspection of all .po files.
+
+The simplified version can be generated for a single message using the
+"xopo -s <text>" command, or an entire .pot can be translated using
+the "xopo -f <input> -o <output>" command.
+
+ xo_emit("{G:}Invalid token\n");
+
+The {G:} role allows a domain name to be set. gettext calls will
+continue to use that domain name until the current format string
+processing is complete, enabling a library function to emit strings
+using it's own catalog. The domain name can be either static as the
+content of the field, or a format can be used to get the domain name
+from the arguments.
+
+ xo_emit("{G:libc}Service unavailable in restricted mode\n");
+
+See ^howto-i18n^ for additional details.
+
**** The Label Role ({L:})
Labels are text that appears before a value.
@@ -370,7 +596,7 @@ Notes are text that appears after a value.
xo_emit("{:cost/%u} {N:per year}\n", cost);
-**** The Padding Role ({P:})
+**** The Padding Role ({P:}) @padding-role@
Padding represents whitespace used before and between fields.
@@ -440,7 +666,7 @@ format descriptors default to "%s".
xo_emit("{:author} wrote \"{:poem}\" in {:year/%4d}\n,
author, poem, year);
-**** The Anchor Modifiers ({[:} and {]:})
+**** The Anchor Roles ({[:} and {]:}) @anchor-role@
The anchor roles allow a set of strings by be padded as a group,
but still be visible to xo_emit as distinct fields. Either the start
@@ -468,29 +694,38 @@ than the absolute value of the given width, nothing happens.
Widths over 8k are considered probable errors and not supported. If
XOF_WARN is set, a warning will be generated.
-*** Modifier Flags
-
-The modifiers can also include the following flags, which modify the
-content emitted for some output styles:
-
-|---+--------------+-------------------------------------------------|
-| M | Name | Description |
-|---+--------------+-------------------------------------------------|
-| c | colon | A colon (":") is appended after the label |
-| d | display | Only emit field for display styles (text/HTML) |
-| e | encoding | Only emit for encoding styles (XML/JSON) |
-| k | key | Field is a key, suitable for XPath predicates |
-| l | leaf-list | Field is a leaf-list
-| n | no-quotes | Do not quote the field when using JSON style |
-| q | quotes | Quote the field when using JSON style |
-| w | white space | A blank (" ") is appended after the label |
-|---+--------------+-------------------------------------------------|
-
-For example, the modifier string "Lwc" means the field has a label
-role (text that describes the next field) and should be followed by a
-colon ('c') and a space ('w'). The modifier string "Vkq" means the
-field has a value role, that it is a key for the current instance, and
-that the value should be quoted when encoded for JSON.
+*** Field Modifiers
+
+Field modifiers are flags which modify the way content emitted for
+particular output styles:
+
+|---+---------------+-------------------------------------------------|
+| M | Name | Description |
+|---+---------------+-------------------------------------------------|
+| c | colon | A colon (":") is appended after the label |
+| d | display | Only emit field for display styles (text/HTML) |
+| e | encoding | Only emit for encoding styles (XML/JSON) |
+| g | gettext | Call gettext on field's render content |
+| h | humanize (hn) | Format large numbers in human-readable style |
+| | hn-space | Humanize: Place space between numeric and unit |
+| | hn-decimal | Humanize: Add a decimal digit, if number < 10 |
+| | hn-1000 | Humanize: Use 1000 as divisor instead of 1024 |
+| k | key | Field is a key, suitable for XPath predicates |
+| l | leaf-list | Field is a leaf-list |
+| n | no-quotes | Do not quote the field when using JSON style |
+| p | plural | Gettext: Use comma-separated plural form |
+| q | quotes | Quote the field when using JSON style |
+| t | trim | Trim leading and trailing whitespace |
+| w | white | A blank (" ") is appended after the label |
+|---+---------------+-------------------------------------------------|
+
+Roles and modifiers can also use more verbose names, when preceeded by
+a comma. For example, the modifier string "Lwc" (or "L,white,colon")
+means the field has a label role (text that describes the next field)
+and should be followed by a colon ('c') and a space ('w'). The
+modifier string "Vkq" (or ":key,quote") means the field has a value
+role (the default role), that it is a key for the current instance,
+and that the value should be quoted when encoded for JSON.
**** The Colon Modifier ({c:})
@@ -535,6 +770,58 @@ the display output styles, TEXT and HTML.
The encoding modifier is the opposite of the display modifier, and
they are often used to give to distinct views of the underlying data.
+**** The Gettext Modifier ({g:}) @gettext-modifier@
+
+The gettext modifier is used to translate individual fields using the
+gettext domain (typically set using the "{G:}" role) and current
+language settings. Once libxo renders the field value, it is passed
+to gettext(3), where it is used as a key to find the native language
+translation.
+
+In the following example, the strings "State" and "full" are passed
+to gettext() to find locale-based translated strings.
+
+ xo_emit("{Lgwc:State}{g:state}\n", "full");
+
+See ^gettext-role^, ^plural-modifier^, and ^howto-i18n^ for additional
+details.
+
+**** The Humanize Modifier ({h:})
+
+The humanize modifier is used to render large numbers as in a
+human-readable format. While numbers like "44470272" are completely
+readable to computers and savants, humans will generally find "44M"
+more meaningful.
+
+"hn" can be used as an alias for "humanize".
+
+The humanize modifier only affects display styles (TEXT and HMTL).
+The "no-humanize" option (See ^LIBXO_OPTIONS^) will block the function of
+the humanize modifier.
+
+There are a number of modifiers that affect details of humanization.
+These are only available in as full names, not single characters. The
+"hn-space" modifier places a space between the number and any
+multiplier symbol, such as "M" or "K" (ex: "44 K"). The "hn-decimal"
+modifier will add a decimal point and a single tenths digit when the number is
+less than 10 (ex: "4.4K"). The "hn-1000" modifier will use 1000 as divisor
+instead of 1024, following the JEDEC-standard instead of the more
+natural binary powers-of-two tradition.
+
+ EXAMPLE:
+ xo_emit("{h:input/%u}, {h,hn-space:output/%u}, "
+ "{h,hn-decimal:errors/%u}, {h,hn-1000:capacity/%u}, "
+ "{h,hn-decimal:remaining/%u}\n",
+ input, output, errors, capacity, remaining);
+ TEXT:
+ 21, 57 K, 96M, 44M, 1.2G
+
+In the HTML style, the original numeric value is rendered in the
+"data-number" attribute on the <div> element:
+
+ <div class="data" data-tag="errors"
+ data-number="100663296">96M</div>
+
**** The Key Modifier ({k:})
The key modifier is used to indicate that a particular field helps
@@ -586,6 +873,27 @@ needed, but often this needs to be controlled by the caller.
JSON:
"fancy": true
+**** The Plural Modifier ({p:}) @plural-modifier@
+
+The plural modifier selects the appropriate plural form of an
+expression based on the most recent number emitted and the current
+language settings. The contents of the field should be the singular
+and plural English values, separated by a comma:
+
+ xo_emit("{:bytes} {Ngp:byte,bytes}\n", bytes);
+
+The plural modifier is meant to work with the gettext modifier ({g:})
+but can work independently. See ^gettext-modifier^.
+
+When used without the gettext modifier or when the message does not
+appear in the message catalog, the first token is chosen when the last
+numeric value is equal to 1; otherwise the second value is used,
+mimicking the simple pluralization rules of English.
+
+When used with the gettext modifier, the ngettext(3) function is
+called to handle the heavy lifting, using the message catalog to
+convert the singular and plural forms into the native language.
+
**** The Quotes Modifier ({q:})
The quotes modifier (and its twin, the 'no-quotes' modifier) affect
@@ -757,8 +1065,8 @@ number of columns to emit. xo_emit uses the precision as the former,
and adds a third value for specifying the maximum number of columns.
In this example, the name field is printed with a minimum of 3 columns
-and a maximum of 6. Up to ten bytes are in used in filling those
-columns.
+and a maximum of 6. Up to ten bytes of data at the location given by
+'name' are in used in filling those columns.
xo_emit("{:name/%3.10.6s}", name);
@@ -782,7 +1090,17 @@ placed in a <div> with class "text".
<div class="data" data-tag="size">extra small</div>
<div class="text">.</div>
-*** "%n" is Not Supported
+*** "%m" Is Supported
+
+libxo supports the '%m' directive, which formats the error message
+associated with the current value of "errno". It is the equivalent
+of "%s" with the argument strerror(errno).
+
+ xo_emit("{:filename} cannot be opened: {:error/%m}", filename);
+ xo_emit("{:filename} cannot be opened: {:error/%s}",
+ filename, strerror(errno));
+
+*** "%n" Is Not Supported
libxo does not support the '%n' directive. It's a bad idea and we
just don't do it.
@@ -799,6 +1117,52 @@ default to "%s".
For padding and labels, the content string is considered the content,
unless a format is given.
+*** Argument Validation @printf-like@
+
+Many compilers and tool chains support validation of printf-like
+arguments. When the format string fails to match the argument list,
+a warning is generated. This is a valuable feature and while the
+formatting strings for libxo differ considerably from printf, many of
+these checks can still provide build-time protection against bugs.
+
+libxo provide variants of functions that provide this ability, if the
+"--enable-printflike" option is passed to the "configure" script.
+These functions use the "_p" suffix, like "xo_emit_p()",
+xo_emit_hp()", etc.
+
+The following are features of libxo formatting strings that are
+incompatible with printf-like testing:
+
+- implicit formats, where "{:tag}" has an implicit "%s";
+- the "max" parameter for strings, where "{:tag/%4.10.6s}" means up to
+ten bytes of data can be inspected to fill a minimum of 4 columns and
+a maximum of 6;
+- percent signs in strings, where "{:filled}%" makes a single,
+trailing percent sign;
+- the "l" and "h" modifiers for strings, where "{:tag/%hs}" means
+locale-based string and "{:tag/%ls}" means a wide character string;
+- distinct encoding formats, where "{:tag/#%s/%s}" means the display
+styles (text and HTML) will use "#%s" where other styles use "%s";
+
+If none of these features are in use by your code, then using the "_p"
+variants might be wise.
+
+|------------------+------------------------|
+| Function | printf-like Equivalent |
+|------------------+------------------------|
+| xo_emit_hv | xo_emit_hvp |
+| xo_emit_h | xo_emit_hp |
+| xo_emit | xo_emit_p |
+| xo_emit_warn_hcv | xo_emit_warn_hcvp |
+| xo_emit_warn_hc | xo_emit_warn_hcp |
+| xo_emit_warn_c | xo_emit_warn_cp |
+| xo_emit_warn | xo_emit_warn_p |
+| xo_emit_warnx_ | xo_emit_warnx_p |
+| xo_emit_err | xo_emit_err_p |
+| xo_emit_errx | xo_emit_errx_p |
+| xo_emit_errc | xo_emit_errc_p |
+|------------------+------------------------|
+
*** Example
In this example, the value for the number of items in stock is emitted:
@@ -844,26 +1208,31 @@ following options are recognised:
Options is a comma-separated list of tokens that correspond to output
styles, flags, or features:
-|-----------+-------------------------------------------------------|
-| Token | Action |
-|-----------+-------------------------------------------------------|
-| dtrt | Enable "Do The Right Thing" mode |
-| html | Emit HTML output |
-| indent=xx | Set the indentation level |
-| info | Add info attributes (HTML) |
-| json | Emit JSON output |
-| keys | Emit the key attribute for keys (XML) |
-| no-locale | Do not initialize the locale setting |
-| no-top | Do not emit a top set of braces (JSON) |
-| not-first | Pretend the 1st output item was not 1st (JSON) |
-| pretty | Emit pretty-printed output |
-| text | Emit TEXT output |
-| units | Add the 'units' (XML) or 'data-units (HTML) attribute |
-| warn | Emit warnings when libxo detects bad calls |
-| warn-xml | Emit warnings in XML |
-| xml | Emit XML output |
-| xpath | Add XPath expressions (HTML) |
-|-----------+-------------------------------------------------------|
+|-------------+-------------------------------------------------------|
+| Token | Action |
+|-------------+-------------------------------------------------------|
+| color | Enable colors/effects for display styles (TEXT, HTML) |
+| dtrt | Enable "Do The Right Thing" mode |
+| html | Emit HTML output |
+| indent=xx | Set the indentation level |
+| info | Add info attributes (HTML) |
+| json | Emit JSON output |
+| keys | Emit the key attribute for keys (XML) |
+| log-gettext | Log (via stderr) each gettext(3) string lookup |
+| log-syslog | Log (via stderr) each syslog message (via xo_syslog) |
+| no-humanize | Ignore the {h:} modifier (TEXT, HTML) |
+| no-locale | Do not initialize the locale setting |
+| no-top | Do not emit a top set of braces (JSON) |
+| not-first | Pretend the 1st output item was not 1st (JSON) |
+| pretty | Emit pretty-printed output |
+| text | Emit TEXT output |
+| underscores | Replace XML-friendly "-"s with JSON friendly "_"s e |
+| units | Add the 'units' (XML) or 'data-units (HTML) attribute |
+| warn | Emit warnings when libxo detects bad calls |
+| warn-xml | Emit warnings in XML |
+| xml | Emit XML output |
+| xpath | Add XPath expressions (HTML) |
+|-------------+-------------------------------------------------------|
The brief options are detailed in ^LIBXO_OPTIONS^.
@@ -1013,7 +1382,7 @@ properly.
xo_close_marker("fish-guts");
}
-** Handles
+** Handles @handles@
libxo uses "handles" to control its rendering functionality. The
handle contains state and buffered data, as well as callback functions
@@ -1164,27 +1533,34 @@ To use the default handle, pass a NULL handle:
The set of valid flags include:
-|-----------------+---------------------------------------|
-| Flag | Description |
-|-----------------+---------------------------------------|
-| XOF_CLOSE_FP | Close file pointer on xo_destroy() |
-| XOF_DTRT | Enable "do the right thing" mode |
-| XOF_INFO | Display info data attributes (HTML) |
-| XOF_KEYS | Emit the key attribute (XML) |
-| XOF_NO_ENV | Do not use the LIBXO_OPTIONS env var |
-| XOF_PRETTY | Make 'pretty printed' output |
-| XOF_UNDERSCORES | Replaces hyphens with underscores |
-| XOF_UNITS | Display units (XML and HMTL) |
-| XOF_WARN | Generate warnings for broken calls |
-| XOF_WARN_XML | Generate warnings in XML on stdout |
-| XOF_XPATH | Emit XPath expressions (HTML) |
-| XOF_COLUMNS | Force xo_emit to return columns used |
-| XOF_FLUSH | Flush output after each xo_emit call |
-|-----------------+---------------------------------------|
+|-------------------+----------------------------------------|
+| Flag | Description |
+|-------------------+----------------------------------------|
+| XOF_CLOSE_FP | Close file pointer on xo_destroy() |
+| XOF_COLOR | Enable color and effects in output |
+| XOF_COLOR_ALLOWED | Allow color/effect for terminal output |
+| XOF_DTRT | Enable "do the right thing" mode |
+| XOF_INFO | Display info data attributes (HTML) |
+| XOF_KEYS | Emit the key attribute (XML) |
+| XOF_NO_ENV | Do not use the LIBXO_OPTIONS env var |
+| XOF_NO_HUMANIZE | Display humanization (TEXT, HTML) |
+| XOF_PRETTY | Make 'pretty printed' output |
+| XOF_UNDERSCORES | Replaces hyphens with underscores |
+| XOF_UNITS | Display units (XML, HMTL) |
+| XOF_WARN | Generate warnings for broken calls |
+| XOF_WARN_XML | Generate warnings in XML on stdout |
+| XOF_XPATH | Emit XPath expressions (HTML) |
+| XOF_COLUMNS | Force xo_emit to return columns used |
+| XOF_FLUSH | Flush output after each xo_emit call |
+|-------------------+----------------------------------------|
The XOF_CLOSE_FP flag will trigger the call of the close_func
(provided via xo_set_writer()) when the handle is destroyed.
+The XOF_COLOR flag enables color and effects in output regardless of
+output device, while the XOF_COLOR_ALLOWED flag allows color and
+effects only if the output device is a terminal.
+
The XOF_PRETTY flag requests 'pretty printing', which will trigger the
addition of indentation and newlines to enhance the readability of
XML, JSON, and HTML output. Text output is not affected.
@@ -1328,12 +1704,15 @@ When the program is ready to exit or close a handle, a call to
xo_finish() is required. This flushes any buffered data, closes
open libxo constructs, and completes any pending operations.
- void xo_finish (void);
- void xo_finish_h (xo_handle_t *xop);
+ int xo_finish (void);
+ int xo_finish_h (xo_handle_t *xop);
+ void xo_finish_atexit (void);
Calling this function is vital to the proper operation of libxo,
especially for the non-TEXT output styles.
+xo_finish_atexit is suitable for use with atexit(3).
+
** Emitting Hierarchy
libxo represents to types of hierarchy: containers and lists. A
@@ -1430,9 +1809,9 @@ style and usage expectations.
}
]
-** Additional Functionality
+** Support Functions
-*** Parsing Command-line Arguments (xo_parse_args)
+*** Parsing Command-line Arguments (xo_parse_args) @xo_parse_args@
The xo_parse_args() function is used to process a program's
arguments. libxo-specific options are processed and removed
@@ -1442,7 +1821,7 @@ is returned. On failure, a message it emitted and -1 is returned.
argc = xo_parse_args(argc, argv);
if (argc < 0)
- exit(1);
+ exit(EXIT_FAILURE);
Following the call to xo_parse_args, the application can process the
remaining arguments in a normal manner. See ^command-line-arguments^
@@ -1539,19 +1918,25 @@ By default, the standard realloc() and free() functions are used.
The environment variable "LIBXO_OPTIONS" can be set to a string of
options:
-|--------+-------------------------------------------|
-| Option | Action |
-|--------+-------------------------------------------|
-| H | Enable HTML output (XO_STYLE_HTML) |
-| I | Enable info output (XOF_INFO) |
-| i<num> | Indent by <number> |
-| J | Enable JSON output (XO_STYLE_JSON) |
-| P | Enable pretty-printed output (XOF_PRETTY) |
-| T | Enable text output (XO_STYLE_TEXT) |
-| W | Enable warnings (XOF_WARN) |
-| X | Enable XML output (XO_STYLE_XML) |
-| x | Enable XPath data (XOF_XPATH) |
-|--------+-------------------------------------------|
+|--------+---------------------------------------------|
+| Option | Action |
+|--------+---------------------------------------------|
+| c | Enable color/effects for TEXT/HTML |
+| F | Force line-buffered flushing |
+| H | Enable HTML output (XO_STYLE_HTML) |
+| I | Enable info output (XOF_INFO) |
+| i<num> | Indent by <number> |
+| J | Enable JSON output (XO_STYLE_JSON) |
+| k | Add keys to XPATH expressions in HTML |
+| n | Disable humanization (TEXT, HTML) |
+| P | Enable pretty-printed output (XOF_PRETTY) |
+| T | Enable text output (XO_STYLE_TEXT) |
+| U | Add units to HTML output |
+| u | Change "-"s to "_"s in element names (JSON) |
+| W | Enable warnings (XOF_WARN) |
+| X | Enable XML output (XO_STYLE_XML) |
+| x | Enable XPath data (XOF_XPATH) |
+|--------+---------------------------------------------|
For example, warnings can be enabled by:
@@ -1619,6 +2004,327 @@ can do so by calling the xo_no_setlocale() function.
void xo_no_setlocale (void);
+** Emitting syslog Messages
+
+syslog is the system logging facility used throughout the unix world.
+Messages are sent from commands, applications, and daemons to a
+hierarchy of servers, where they are filtered, saved, and forwarded
+based on configuration behaviors.
+
+syslog is an older protocol, originally documented only in source
+code. By the time RFC 3164 published, variation and mutation left the
+leading "<pri>" string as only common content. RFC 5424 defines a new
+version (version 1) of syslog and introduces structured data into the
+messages. Structured data is a set of name/value pairs transmitted
+distinctly alongside the traditional text message, allowing filtering
+on precise values instead of regular expressions.
+
+These name/value pairs are scoped by a two-part identifier; an
+enterprise identifier names the party responsible for the message
+catalog and a name identifying that message. Enterprise IDs are
+defined by IANA, the Internet Assigned Numbers Authority:
+
+https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
+
+Use the ^xo_set_syslog_enterprise_id^() function to set the Enterprise
+ID, as needed.
+
+The message name should follow the conventions in ^good-field-names^,
+as should the fields within the message.
+
+ /* Both of these calls are optional */
+ xo_set_syslog_enterprise_id(32473);
+ xo_open_log("my-program", 0, LOG_DAEMON);
+
+ /* Generate a syslog message */
+ xo_syslog(LOG_ERR, "upload-failed",
+ "error <%d> uploading file '{:filename}' "
+ "as '{:target/%s:%s}'",
+ code, filename, protocol, remote);
+
+ xo_syslog(LOG_INFO, "poofd-invalid-state",
+ "state {:current/%u} is invalid {:connection/%u}",
+ state, conn);
+
+The developer should be aware that the message name may be used in the
+future to allow access to further information, including
+documentation. Care should be taken to choose quality, descriptive
+names.
+
+*** Priority, Facility, and Flags @priority@
+
+The xo_syslog, xo_vsyslog, and xo_open_log functions accept a set of
+flags which provide the priority of the message, the source facility,
+and some additional features. These values are OR'd together to
+create a single integer argument:
+
+ xo_syslog(LOG_ERR | LOG_AUTH, "login-failed",
+ "Login failed; user '{:user}' from host '{:address}'",
+ user, addr);
+
+These values are defined in <syslog.h>.
+
+The priority value indicates the importance and potential impact of
+each message.
+
+|-------------+-------------------------------------------------------|
+| Priority | Description |
+|-------------+-------------------------------------------------------|
+| LOG_EMERG | A panic condition, normally broadcast to all users |
+| LOG_ALERT | A condition that should be corrected immediately |
+| LOG_CRIT | Critical conditions |
+| LOG_ERR | Generic errors |
+| LOG_WARNING | Warning messages |
+| LOG_NOTICE | Non-error conditions that might need special handling |
+| LOG_INFO | Informational messages |
+| LOG_DEBUG | Developer-oriented messages |
+|-------------+-------------------------------------------------------|
+
+The facility value indicates the source of message, in fairly generic
+terms.
+
+|---------------+-------------------------------------------------|
+| Facility | Description |
+|---------------+-------------------------------------------------|
+| LOG_AUTH | The authorization system (e.g. login(1)) |
+| LOG_AUTHPRIV | As LOG_AUTH, but logged to a privileged file |
+| LOG_CRON | The cron daemon: cron(8) |
+| LOG_DAEMON | System daemons, not otherwise explicitly listed |
+| LOG_FTP | The file transfer protocol daemons |
+| LOG_KERN | Messages generated by the kernel |
+| LOG_LPR | The line printer spooling system |
+| LOG_MAIL | The mail system |
+| LOG_NEWS | The network news system |
+| LOG_SECURITY | Security subsystems, such as ipfw(4) |
+| LOG_SYSLOG | Messages generated internally by syslogd(8) |
+| LOG_USER | Messages generated by user processes (default) |
+| LOG_UUCP | The uucp system |
+| LOG_LOCAL0..7 | Reserved for local use |
+|---------------+-------------------------------------------------|
+
+In addition to the values listed above, xo_open_log accepts a set of
+addition flags requesting specific behaviors.
+
+|------------+----------------------------------------------------|
+| Flag | Description |
+|------------+----------------------------------------------------|
+| LOG_CONS | If syslogd fails, attempt to write to /dev/console |
+| LOG_NDELAY | Open the connection to syslogd(8) immediately |
+| LOG_PERROR | Write the message also to standard error output |
+| LOG_PID | Log the process id with each message |
+|------------+----------------------------------------------------|
+
+*** xo_syslog
+
+Use the xo_syslog function to generate syslog messages by calling it
+with a log priority and facility, a message name, a format string, and
+a set of arguments. The priority/facility argument are discussed
+above, as is the message name.
+
+The format string follows the same conventions as xo_emit's format
+string, with each field being rendered as an SD-PARAM pair.
+
+ xo_syslog(LOG_ERR, "poofd-missing-file",
+ "'{:filename}' not found: {:error/%m}", filename);
+
+ ... [poofd-missing-file@32473 filename="/etc/poofd.conf"
+ error="Permission denied"] '/etc/poofd.conf' not
+ found: Permission denied
+
+*** Support functions
+
+**** xo_vsyslog
+
+xo_vsyslog is identical in function to xo_syslog, but takes the set of
+arguments using a va_list.
+
+ void my_log (const char *name, const char *fmt, ...)
+ {
+ va_list vap;
+ va_start(vap, fmt);
+ xo_vsyslog(LOG_ERR, name, fmt, vap);
+ va_end(vap);
+ }
+
+**** xo_open_log
+
+xo_open_log functions similar to openlog(3), allowing customization of
+the program name, the log facility number, and the additional option
+flags described in ^priority^.
+
+ void
+ xo_open_log (const char *ident, int logopt, int facility);
+
+**** xo_close_log
+
+xo_close_log functions similar to closelog(3), closing the log file
+and releasing any associated resources.
+
+ void
+ xo_close_log (void);
+
+**** xo_set_logmask
+
+xo_set_logmask function similar to setlogmask(3), restricting the set
+of generated log event to those whose associated bit is set in
+maskpri. Use LOG_MASK(pri) to find the appropriate bit, or
+LOG_UPTO(toppri) to create a mask for all priorities up to and
+including toppri.
+
+ int
+ xo_set_logmask (int maskpri);
+
+ Example:
+ setlogmask(LOG_UPTO(LOG_WARN));
+
+**** xo_set_syslog_enterprise_id
+
+Use the xo_set_syslog_enterprise_id to supply a platform- or
+application-specific enterprise id. This value is used in any
+future syslog messages.
+
+Ideally, the operating system should supply a default value via the
+"kern.syslog.enterprise_id" sysctl value. Lacking that, the
+application should provide a suitable value.
+
+ void
+ xo_set_syslog_enterprise_id (unsigned short eid);
+
+Enterprise IDs are administered by IANA, the Internet Assigned Number
+Authority. The complete list is EIDs on their web site:
+
+ https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
+
+New EIDs can be requested from IANA using the following page:
+
+ http://pen.iana.org/pen/PenApplication.page
+
+Each software development organization that defines a set of syslog
+messages should register their own EID and use that value in their
+software to ensure that messages can be uniquely identified by the
+combination of EID + message name.
+
+** Creating Custom Encoders
+
+The number of encoding schemes in current use is staggering, with new
+and distinct schemes appearing daily. While libxo provide XML, JSON,
+HMTL, and text natively, there are requirements for other encodings.
+
+Rather than bake support for all possible encoders into libxo, the API
+allows them to be defined externally. libxo can then interfaces with
+these encoding modules using a simplistic API. libxo processes all
+functions calls, handles state transitions, performs all formatting,
+and then passes the results as operations to a customized encoding
+function, which implements specific encoding logic as required. This
+means your encoder doesn't need to detect errors with unbalanced
+open/close operations but can rely on libxo to pass correct data.
+
+By making a simple API, libxo internals are not exposed, insulating the
+encoder and the library from future or internal changes.
+
+The three elements of the API are:
+- loading
+- initialization
+- operations
+
+The following sections provide details about these topics.
+
+libxo source contain an encoder for Concise Binary Object
+Representation, aka CBOR (RFC 7049) which can be used as used as an
+example for the API.
+
+*** Loading Encoders
+
+Encoders can be registered statically or discovered dynamically.
+Applications can choose to call the xo_encoder_register()
+function to explicitly register encoders, but more typically they are
+built as shared libraries, placed in the libxo/extensions directory,
+and loaded based on name. libxo looks for a file with the name of the encoder
+and an extension of ".enc". This can be a file or a symlink to the
+shared library file that supports the encoder.
+
+ % ls -1 lib/libxo/extensions/*.enc
+ lib/libxo/extensions/cbor.enc
+ lib/libxo/extensions/test.enc
+
+*** Encoder Initialization
+
+Each encoder must export a symbol used to access the library, which
+must have the following signature:
+
+ int xo_encoder_library_init (XO_ENCODER_INIT_ARGS);
+
+XO_ENCODER_INIT_ARGS is a macro defined in xo_encoder.h that defines
+an argument called "arg", a pointer of the type
+xo_encoder_init_args_t. This structure contains two fields:
+
+- xei_version is the version number of the API as implemented within
+libxo. This version is currently as 1 using XO_ENCODER_VERSION. This
+number can be checked to ensure compatibility. The working assumption
+is that all versions should be backward compatible, but each side may
+need to accurately know the version supported by the other side.
+xo_encoder_library_init can optionally check this value, and must then
+set it to the version number used by the encoder, allowing libxo to
+detect version differences and react accordingly. For example, if
+version 2 adds new operations, then libxo will know that an encoding
+library that set xei_version to 1 cannot be expected to handle those
+new operations.
+
+- xei_handler must be set to a pointer to a function of type
+xo_encoder_func_t, as defined in xo_encoder.h. This function
+takes a set of parameters:
+-- xop is a pointer to the opaque xo_handle_t structure
+-- op is an integer representing the current operation
+-- name is a string whose meaning differs by operation
+-- value is a string whose meaning differs by operation
+-- private is an opaque structure provided by the encoder
+
+Additional arguments may be added in the future, so handler functions
+should use the XO_ENCODER_HANDLER_ARGS macro. An appropriate
+"extern" declaration is provided to help catch errors.
+
+Once the encoder initialization function has completed processing, it
+should return zero to indicate that no error has occurred. A non-zero
+return code will cause the handle initialization to fail.
+
+*** Operations
+
+The encoder API defines a set of operations representing the
+processing model of libxo. Content is formatted within libxo, and
+callbacks are made to the encoder's handler function when data is
+ready to be processed.
+
+|-----------------------+---------------------------------------|
+| Operation | Meaning (Base function) |
+|-----------------------+---------------------------------------|
+| XO_OP_CREATE | Called when the handle is created |
+| XO_OP_OPEN_CONTAINER | Container opened (xo_open_container) |
+| XO_OP_CLOSE_CONTAINER | Container closed (xo_close_container) |
+| XO_OP_OPEN_LIST | List opened (xo_open_list) |
+| XO_OP_CLOSE_LIST | List closed (xo_close_list) |
+| XO_OP_OPEN_LEAF_LIST | Leaf list opened (xo_open_leaf_list) |
+| XO_OP_CLOSE_LEAF_LIST | Leaf list closed (xo_close_leaf_list) |
+| XO_OP_OPEN_INSTANCE | Instance opened (xo_open_instance) |
+| XO_OP_CLOSE_INSTANCE | Instance closed (xo_close_instance) |
+| XO_OP_STRING | Field with Quoted UTF-8 string |
+| XO_OP_CONTENT | Field with content |
+| XO_OP_FINISH | Finish any pending output |
+| XO_OP_FLUSH | Flush any buffered output |
+| XO_OP_DESTROY | Clean up resources |
+| XO_OP_ATTRIBUTE | An attribute name/value pair |
+| XO_OP_VERSION | A version string |
+|-----------------------+---------------------------------------|
+
+For all the open and close operations, the name parameter holds the
+name of the construct. For string, content, and attribute operations,
+the name parameter is the name of the field and the value parameter is
+the value. "string" are differentiated from "content" to allow differing
+treatment of true, false, null, and numbers from real strings, though
+content values are formatted as strings before the handler is called.
+For version operations, the value parameter contains the version.
+
+All strings are encoded in UTF-8.
+
* The "xo" Utility
The "xo" utility allows command line access to the functionality of
@@ -1768,7 +2474,7 @@ The "-V" option does not report errors, but prints a complete list of
all field names, sorted alphabetically. The output can help spot
inconsistencies and spelling errors.
-* xohtml
+* xohtml @xohtml@
xohtml is a tool for turning the output of libxo-enabled commands into
html files suitable for display in modern HTML web browsers. It can
@@ -1794,6 +2500,46 @@ The "-c" option takes a full command with arguments, including
any libxo options needed to generate html ("--libxo=html"). This
value must be quoted if it consists of multiple tokens.
+* xopo
+
+The "xopo" utility filters ".pot" files generated by the "xgettext"
+utility to remove formatting information suitable for use with
+the "{G:}" modifier. This means that when the developer changes the
+formatting portion of the field definitions, or the fields modifiers,
+the string passed to gettext(3) is unchanged, avoiding the expense of
+updating any existing translation files (".po" files).
+
+The syntax for the xopo command is one of two forms; it can be used as
+a filter for processing a .po or .pot file, rewriting the "msgid"
+strings with a simplified message string. In this mode, the input is
+either standard input or a file given by the "-f" option, and the
+output is either standard output or a file given by the "-o" option.
+
+In the second mode, a simple message given using the "-s" option on
+the command, and the simplified version of that message is printed on
+stdout.
+
+|-----------+---------------------------------|
+| Option | Meaning |
+|-----------+---------------------------------|
+| -o <file> | Output file name |
+| -f <file> | Use the given .po file as input |
+| -s <text> | Simplify a format string |
+|-----------+---------------------------------|
+
+ EXAMPLE:
+ % xopo -s "There are {:count/%u} {:event/%.6s} events\n"
+ There are {:count} {:event} events\n
+
+ % xgettext --default-domain=foo --no-wrap \
+ --add-comments --keyword=xo_emit --keyword=xo_emit_h \
+ --keyword=xo_emit_warn -C -E -n --foreign-user \
+ -o foo.pot.raw foo.c
+ % xopo -f foo.pot.raw -o foo.pot
+
+Use of the "--no-wrap" option for xgettext is required to ensure that
+incoming msgid strings are not wrapped across multiple lines.
+
* FAQs
This section contains the set of questions that users typically ask,
@@ -1909,7 +2655,7 @@ be lost.
libxo is a new implementation of these ideas and is distinct from
the previous implementation in JUNOS.
-*** What makes a good field name?
+*** What makes a good field name? @good-field-names@
To make useful, consistent field names, follow these guidelines:
@@ -1937,7 +2683,8 @@ Use "receive-after-window-packets" instead of
Nothing's worse than writing expressions like:
if ($src1/process[pid == $pid]/name ==
- $src2/proc-table/proc/p[process-id == $pid]/proc-name) {
+ $src2/proc-table/proc-list
+ /proc-entry[process-id == $pid]/proc-name) {
...
}
@@ -1945,6 +2692,9 @@ Find someone else who is expressing similar data and follow their
fields and hierarchy. Remember the quote is not "Consistency is the
hobgoblin of little minds", but "A foolish consistency is the
hobgoblin of little minds".
+= Use containment as scoping
+In the previous example, all the names are prefixed with "proc-",
+which is redundant given that they are nested under the process table.
= Think about your users
Have empathy for your users, choosing clear and useful fields that
contain clear and useful data. You may need to augment the display
@@ -1979,6 +2729,331 @@ names to make that difference more obvious.
!!include-file xolint.txt
+* Howtos: Focused Directions
+
+This section provides task-oriented instructions for selected tasks.
+If you have a task that needs instructions, please open a request as
+an enhancement issue on github.
+
+** Howto: Report bugs
+
+libxo uses github to track bugs or request enhancements. Please use
+the following URL:
+
+ https://github.com/Juniper/libxo/issues
+
+** Howto: Install libxo
+
+libxo is open source, under a new BSD license. Source code is
+available on github, as are recent releases. To get the most
+current release, please visit:
+
+ https://github.com/Juniper/libxo/releases
+
+After downloading and untarring the source code, building involves the
+following steps:
+
+ sh bin/setup.sh
+ cd build
+ ../configure
+ make
+ make test
+ sudo make install
+
+libxo uses a distinct "build" directory to keep generated files
+separated from source files.
+
+Use "../configure --help" to display available configuration options,
+which include the following:
+
+ --enable-warnings Turn on compiler warnings
+ --enable-debug Turn on debugging
+ --enable-text-only Turn on text-only rendering
+ --enable-printflike Enable use of GCC __printflike attribute
+ --disable-libxo-options Turn off support for LIBXO_OPTIONS
+ --with-gettext=PFX Specify location of gettext installation
+ --with-libslax-prefix=PFX Specify location of libslax config
+
+Compiler warnings are a very good thing, but recent compiler version
+have added some very pedantic checks. While every attempt is made to
+keep libxo code warning-free, warnings are now optional. If you are
+doing development work on libxo, it is required that you use
+--enable-warnings to keep the code warning free, but most users need
+not use this option.
+
+libxo provides the --enable-text-only option to reduce the footprint
+of the library for smaller installations. XML, JSON, and HTML
+rendering logic is removed.
+
+The gettext library does not provide a simple means of learning its
+location, but libxo will look for it in /usr and /opt/local. If
+installed elsewhere, the installer will need to provide this
+information using the --with-gettext=/dir/path option.
+
+libslax is not required by libxo; it contains the "oxtradoc" program
+used to format documentation.
+
+For additional information, see ^building-libxo^.
+
+** Howto: Convert command line applications
+
+ How do I convert an existing command line application?
+
+There are three basic steps for converting command line application to
+use libxo.
+
+- Setting up the context
+- Converting printf calls
+- Creating hierarchy
+- Converting error functions
+
+*** Setting up the context
+
+To use libxo, you'll need to include the "xo.h" header file in your
+source code files:
+
+ #include <libxo/xo.h>
+
+In your main() function, you'll need to call xo_parse_args to handling
+argument parsing (^xo_parse_args^). This function removes
+libxo-specific arguments the program's argv and returns either the
+number of remaining arguments or -1 to indicate an error.
+
+ int main (int argc, char **argv)
+ {
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ return argc;
+ ....
+ }
+
+At the bottom of your main(), you'll need to call xo_finish() to
+complete output processing for the default handle (^handles^). libxo
+provides the xo_finish_atexit function that is suitable for use with
+the atexit(3) function.
+
+ atexit(xo_finish_atexit);
+
+*** Converting printf Calls
+
+The second task is inspecting code for printf(3) calls and replacing
+them with xo_emit() calls. The format strings are similar in task,
+but libxo format strings wrap output fields in braces. The following
+two calls produce identical text output:
+
+ printf("There are %d %s events\n", count, etype);
+ xo_emit("There are {:count/%d} {:event} events\n", count, etype);
+
+"count" and "event" are used as names for JSON and XML output. The
+"count" field uses the format "%d" and "event" uses the default "%s"
+format. Both are "value" roles, which is the default role.
+
+Since text outside of output fields is passed verbatim, other roles
+are less important, but their proper use can help make output more
+useful. The "note" and "label" roles allow HTML output to recognize
+the relationship between text and the associated values, allowing
+appropriate "hover" and "onclick" behavior. Using the "units" role
+allows the presentation layer to perform conversions when needed. The
+"warning" and "error" roles allows use of color and font to draw
+attention to warnings. The "padding" role makes the use of vital
+whitespace more clear (^padding-role^).
+
+The "title" role indicates the headings of table and sections. This
+allows HTML output to use CSS to make this relationship more obvious.
+
+ printf("Statistics:\n");
+ xo_emit("{T:Statistics}:\n");
+
+The "color" roles controls foreground and background colors, as well
+as effects like bold and underline (see ^color-role^).
+
+ xo_emit("{C:bold}required{C:}\n");
+
+Finally, the start- and stop-anchor roles allow justification and
+padding over multiple fields (see ^anchor-role^).
+
+ snprintf(buf, sizeof(buf), "(%u/%u/%u)", min, ave, max);
+ printf("%30s", buf);
+
+ xo_emit("{[:30}({:minimum/%u}/{:average/%u}/{:maximum/%u}{]:}",
+ min, ave, max);
+
+*** Creating Hierarchy
+
+Text output doesn't have any sort of hierarchy, but XML and JSON
+require this. Typically applications use indentation to represent
+these relationship:
+
+ printf("table %d\n", tnum);
+ for (i = 0; i < tmax; i++) {
+ printf(" %s %d\n", table[i].name, table[i].size);
+ }
+
+ xo_emit("{T:/table %d}\n", tnum);
+ xo_open_list("table");
+ for (i = 0; i < tmax; i++) {
+ xo_open_instance("table");
+ xo_emit("{P: }{k:name} {:size/%d}\n",
+ table[i].name, table[i].size);
+ xo_close_instance("table");
+ }
+ xo_close_list("table");
+
+The open and close list functions are used before and after the list,
+and the open and close instance functions are used before and after
+each instance with in the list.
+
+Typically these developer looks for a "for" loop as an indication of
+where to put these calls.
+
+In addition, the open and close container functions allow for
+organization levels of hierarchy.
+
+ printf("Paging information:\n");
+ printf(" Free: %lu\n", free);
+ printf(" Active: %lu\n", active);
+ printf(" Inactive: %lu\n", inactive);
+
+ xo_open_container("paging-information");
+ xo_emit("{P: }{L:Free: }{:free/%lu}\n", free);
+ xo_emit("{P: }{L:Active: }{:active/%lu}\n", active);
+ xo_emit("{P: }{L:Inactive: }{:inactive/%lu}\n", inactive);
+ xo_close_container("paging-information");
+
+*** Converting Error Functions
+
+libxo provides variants of the standard error and warning functions,
+err(3) and warn(3). There are two variants, one for putting the
+errors on standard error, and the other writes the errors and warnings
+to the handle using the appropriate encoding style:
+
+ err(1, "cannot open output file: %s", file);
+
+ xo_err(1, "cannot open output file: %s", file);
+ xo_emit_err(1, "cannot open output file: {:filename}", file);
+
+** Howto: Use "xo" in Shell Scripts
+
+** Howto: Internationalization (i18n) @howto-i18n@
+
+ How do I use libxo to support internationalization?
+
+libxo allows format and field strings to be used a keys into message
+catalogs to enable translation into a user's native language by
+invoking the standard gettext(3) functions.
+
+gettext setup is a bit complicated: text strings are extracted from
+source files into "portable object template" (.pot) files using the
+"xgettext" command. For each language, this template file is used as
+the source for a message catalog in the "portable object" (.po)
+format, which are translated by hand and compiled into "machine
+object" (.mo) files using the "msgfmt" command. The .mo files are
+then typically installed in the /usr/share/locale or
+/opt/local/share/locale directories. At run time, the user's language
+settings are used to select a .mo file which is searched for matching
+messages. Text strings in the source code are used as keys to look up
+the native language strings in the .mo file.
+
+Since the xo_emit format string is used as the key into the message
+catalog, libxo removes unimportant field formatting and modifiers from
+the format string before use so that minor formatting changes will not
+impact the expensive translation process. We don't want a developer
+change such as changing "/%06d" to "/%08d" to force hand inspection of
+all .po files. The simplified version can be generated for a single
+message using the "xopo -s <text>" command, or an entire .pot can be
+translated using the "xopo -f <input> -o <output>" command.
+
+ EXAMPLE:
+ % xopo -s "There are {:count/%u} {:event/%.6s} events\n"
+ There are {:count} {:event} events\n
+
+ Recommended workflow:
+ # Extract text messages
+ xgettext --default-domain=foo --no-wrap \
+ --add-comments --keyword=xo_emit --keyword=xo_emit_h \
+ --keyword=xo_emit_warn -C -E -n --foreign-user \
+ -o foo.pot.raw foo.c
+
+ # Simplify format strings for libxo
+ xopo -f foo.pot.raw -o foo.pot
+
+ # For a new language, just copy the file
+ cp foo.pot po/LC/my_lang/foo.po
+
+ # For an existing language:
+ msgmerge --no-wrap po/LC/my_lang/foo.po \
+ foo.pot -o po/LC/my_lang/foo.po.new
+
+ # Now the hard part: translate foo.po using tools
+ # like poedit or emacs' po-mode
+
+ # Compile the finished file; Use of msgfmt's "-v" option is
+ # strongly encouraged, so that "fuzzy" entries are reported.
+ msgfmt -v -o po/my_lang/LC_MESSAGES/foo.mo po/my_lang/foo.po
+
+ # Install the .mo file
+ sudo cp po/my_lang/LC_MESSAGES/foo.mo \
+ /opt/local/share/locale/my_lang/LC_MESSAGE/
+
+Once these steps are complete, you can use the "gettext" command to
+test the message catalog:
+
+ gettext -d foo -e "some text"
+
+*** i18n and xo_emit
+
+There are three features used in libxo used to support i18n:
+
+- The "{G:}" role looks for a translation of the format string.
+- The "{g:}" modifier looks for a translation of the field.
+- The "{p:}" modifier looks for a pluralized version of the field.
+
+Together these three flags allows a single function call to give
+native language support, as well as libxo's normal XML, JSON, and HTML
+support.
+
+ printf(gettext("Received %zu %s from {g:server} server\n"),
+ counter, ngettext("byte", "bytes", counter),
+ gettext("web"));
+
+ xo_emit("{G:}Received {:received/%zu} {Ngp:byte,bytes} "
+ "from {g:server} server\n", counter, "web");
+
+libxo will see the "{G:}" role and will first simplify the format
+string, removing field formats and modifiers.
+
+ "Received {:received} {N:byte,bytes} from {:server} server\n"
+
+libxo calls gettext(3) with that string to get a localized version.
+If your language were Pig Latin, the result might look like:
+
+ "Eceivedray {:received} {N:byte,bytes} omfray "
+ "{:server} erversay\n"
+
+Note the field names do not change and they should not be translated.
+The contents of the note ("byte,bytes") should also not be translated,
+since the "g" modifier will need the untranslated value as the key for
+the message catalog.
+
+The field "{g:server}" requests the rendered value of the field be
+translated using gettext(3). In this example, "web" would be used.
+
+The field "{Ngp:byte,bytes}" shows an example of plural form using the
+"p" modifier with the "g" modifier. The base singular and plural
+forms appear inside the field, separated by a comma. At run time,
+libxo uses the previous field's numeric value to decide which form to
+use by calling ngettext(3).
+
+If a domain name is needed, it can be supplied as the content of the
+{G:} role. Domain names remain in use throughout the format string
+until cleared with another domain name.
+
+ printf(dgettext("dns", "Host %s not found: %d(%s)\n"),
+ name, errno, dgettext("strerror", strerror(errno)));
+
+ xo_emit("{G:dns}Host {:hostname} not found: "
+ "%d({G:strerror}{g:%m})\n", name, errno);
+
* Examples
** Unit Test
@@ -2022,7 +3097,7 @@ Here is the unit test example:
argc = xo_parse_args(argc, argv);
if (argc < 0)
- exit(1);
+ exit(EXIT_FAILURE);
xo_set_info(NULL, info, info_count);
diff --git a/contrib/libxo/encoder/Makefile.am b/contrib/libxo/encoder/Makefile.am
new file mode 100644
index 0000000..10d19de
--- /dev/null
+++ b/contrib/libxo/encoder/Makefile.am
@@ -0,0 +1,9 @@
+#
+# Copyright 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.
+
+SUBDIRS = cbor test
diff --git a/contrib/libxo/encoder/cbor/Makefile.am b/contrib/libxo/encoder/cbor/Makefile.am
new file mode 100644
index 0000000..7ce44e0
--- /dev/null
+++ b/contrib/libxo/encoder/cbor/Makefile.am
@@ -0,0 +1,51 @@
+#
+# $Id$
+#
+# Copyright 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.
+
+if LIBXO_WARNINGS_HIGH
+LIBXO_WARNINGS = HIGH
+endif
+if HAVE_GCC
+GCC_WARNINGS = yes
+endif
+include ${top_srcdir}/warnings.mk
+
+enc_cborincdir = ${includedir}/libxo
+
+AM_CFLAGS = \
+ -I${top_srcdir}/libxo \
+ -I${top_builddir}/libxo \
+ ${WARNINGS}
+
+LIBNAME = libenc_cbor
+pkglib_LTLIBRARIES = libenc_cbor.la
+LIBS = \
+ -L${top_builddir}/libxo -lxo
+
+LDADD = ${top_builddir}/libxo/libxo.la
+
+libenc_cbor_la_SOURCES = \
+ enc_cbor.c
+
+pkglibdir = ${XO_ENCODERDIR}
+
+UGLY_NAME = cbor.enc
+
+install-exec-hook:
+ @DLNAME=`sh -c '. ./libenc_cbor.la ; echo $$dlname'` ; \
+ if [ x"$$DLNAME" = x ]; \
+ then DLNAME=${LIBNAME}.${XO_LIBEXT}; fi ; \
+ if [ "$(build_os)" = "cygwin" ]; \
+ then DLNAME="../bin/$$DLNAME"; fi ; \
+ echo Install link $$DLNAME "->" ${UGLY_NAME} "..." ; \
+ mkdir -p ${DESTDIR}${XO_ENCODERDIR} ; \
+ cd ${DESTDIR}${XO_ENCODERDIR} \
+ && chmod +w . \
+ && rm -f ${UGLY_NAME} \
+ && ${LN_S} $$DLNAME ${UGLY_NAME}
diff --git a/contrib/libxo/encoder/cbor/enc_cbor.c b/contrib/libxo/encoder/cbor/enc_cbor.c
new file mode 100644
index 0000000..513063f
--- /dev/null
+++ b/contrib/libxo/encoder/cbor/enc_cbor.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 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, August 2015
+ */
+
+/*
+ * CBOR (RFC 7049) mades a suitable test case for libxo's external
+ * encoder API. It's simple, streaming, well documented, and an
+ * IETF standard.
+ *
+ * This encoder uses the "pretty" flag for diagnostics, which isn't
+ * really kosher, but it's example code.
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "xo.h"
+#include "xo_encoder.h"
+#include "xo_buf.h"
+
+/*
+ * memdump(): dump memory contents in hex/ascii
+0 1 2 3 4 5 6 7
+0123456789012345678901234567890123456789012345678901234567890123456789012345
+XX XX XX XX XX XX XX XX - XX XX XX XX XX XX XX XX abcdefghijklmnop
+ */
+static void
+cbor_memdump (FILE *fp, const char *title, const char *data,
+ size_t len, const char *tag, int indent)
+{
+ enum { MAX_PER_LINE = 16 };
+ char buf[ 80 ];
+ char text[ 80 ];
+ char *bp, *tp;
+ size_t i;
+#if 0
+ static const int ends[ MAX_PER_LINE ] = { 2, 5, 8, 11, 15, 18, 21, 24,
+ 29, 32, 35, 38, 42, 45, 48, 51 };
+#endif
+
+ if (fp == NULL)
+ fp = stdout;
+ if (tag == NULL)
+ tag = "";
+
+ fprintf(fp, "%*s[%s] @ %p (%lx/%lu)\n", indent + 1, tag,
+ title, data, (unsigned long) len, (unsigned long) len);
+
+ while (len > 0) {
+ bp = buf;
+ tp = text;
+
+ for (i = 0; i < MAX_PER_LINE && i < len; i++) {
+ if (i && (i % 4) == 0) *bp++ = ' ';
+ if (i == 8) {
+ *bp++ = '-';
+ *bp++ = ' ';
+ }
+ sprintf(bp, "%02x ", (unsigned char) *data);
+ bp += strlen(bp);
+ *tp++ = (isprint((int) *data) && *data >= ' ') ? *data : '.';
+ data += 1;
+ }
+
+ *tp = 0;
+ *bp = 0;
+ fprintf(fp, "%*s%-54s%s\n", indent + 1, tag, buf, text);
+ len -= i;
+ }
+}
+
+/*
+ * CBOR breaks the first byte into two pieces, the major type in the
+ * top 3 bits and the minor value in the low 5 bits. The value can be
+ * a small value (0 .. 23), an 8-bit value (24), a 16-bit value (25),
+ * a 32-bit value (26), or a 64-bit value (27). A value of 31
+ * represents an unknown length, which we'll use extensively for
+ * streaming our content.
+ */
+#define CBOR_MAJOR_MASK 0xE0
+#define CBOR_MINOR_MASK 0x1F
+#define CBOR_MAJOR_SHIFT 5
+
+#define CBOR_MAJOR(_x) ((_x) & CBOR_MAJOR_MASK)
+#define CBOR_MAJOR_VAL(_x) ((_x) << CBOR_MAJOR_SHIFT)
+#define CBOR_MINOR_VAL(_x) ((_x) & CBOR_MINOR_MASK)
+
+/* Major type codes */
+#define CBOR_UNSIGNED CBOR_MAJOR_VAL(0) /* 0x00 */
+#define CBOR_NEGATIVE CBOR_MAJOR_VAL(1) /* 0x20 */
+#define CBOR_BYTES CBOR_MAJOR_VAL(2) /* 0x40 */
+#define CBOR_STRING CBOR_MAJOR_VAL(3) /* 0x60 */
+#define CBOR_ARRAY CBOR_MAJOR_VAL(4) /* 0x80 */
+#define CBOR_MAP CBOR_MAJOR_VAL(5) /* 0xa0 */
+#define CBOR_SEMANTIC CBOR_MAJOR_VAL(6) /* 0xc0 */
+#define CBOR_SPECIAL CBOR_MAJOR_VAL(7) /* 0xe0 */
+
+#define CBOR_ULIMIT 24 /* Largest unsigned value */
+#define CBOR_NLIMIT 23 /* Largest negative value */
+
+#define CBOR_BREAK 0xFF
+#define CBOR_INDEF 0x1F
+
+#define CBOR_FALSE 0xF4
+#define CBOR_TRUE 0xF5
+#define CBOR_NULL 0xF6
+#define CBOR_UNDEF 0xF7
+
+#define CBOR_LEN8 0x18 /* 24 - 8-bit value */
+#define CBOR_LEN16 0x19 /* 25 - 16-bit value */
+#define CBOR_LEN32 0x1a /* 26 - 32-bit value */
+#define CBOR_LEN64 0x1b /* 27 - 64-bit value */
+#define CBOR_LEN128 0x1c /* 28 - 128-bit value */
+
+typedef struct cbor_private_s {
+ xo_buffer_t c_data; /* Our data buffer */
+ unsigned c_indent; /* Indent level */
+ unsigned c_open_leaf_list; /* Open leaf list construct? */
+} cbor_private_t;
+
+static void
+cbor_encode_uint (xo_buffer_t *xbp, uint64_t minor, unsigned limit)
+{
+ char *bp = xbp->xb_curp;
+ int i, m;
+
+ if (minor > (1UL<<32)) {
+ *bp++ |= CBOR_LEN64;
+ m = 64;
+
+ } else if (minor > (1<<16)) {
+ *bp++ |= CBOR_LEN32;
+ m = 32;
+
+ } else if (minor > (1<<8)) {
+ *bp++ |= CBOR_LEN16;
+ m = 16;
+
+ } else if (minor > limit) {
+ *bp++ |= CBOR_LEN8;
+ m = 8;
+ } else {
+ *bp++ |= minor & CBOR_MINOR_MASK;
+ m = 0;
+ }
+
+ if (m) {
+ for (i = m - 8; i >= 0; i -= 8)
+ *bp++ = minor >> i;
+ }
+
+ xbp->xb_curp = bp;
+}
+
+static void
+cbor_append (xo_handle_t *xop, cbor_private_t *cbor, xo_buffer_t *xbp,
+ unsigned major, unsigned minor, const char *data)
+{
+ if (!xo_buf_has_room(xbp, minor + 2))
+ return;
+
+ unsigned offset = xo_buf_offset(xbp);
+
+ *xbp->xb_curp = major;
+ cbor_encode_uint(xbp, minor, CBOR_ULIMIT);
+ if (data)
+ xo_buf_append(xbp, data, minor);
+
+ if (xo_get_flags(xop) & XOF_PRETTY)
+ cbor_memdump(stdout, "append", xo_buf_data(xbp, offset),
+ xbp->xb_curp - xbp->xb_bufp - offset, "",
+ cbor->c_indent * 2);
+}
+
+static int
+cbor_create (xo_handle_t *xop)
+{
+ cbor_private_t *cbor = xo_realloc(NULL, sizeof(*cbor));
+ if (cbor == NULL)
+ return -1;
+
+ bzero(cbor, sizeof(*cbor));
+ xo_buf_init(&cbor->c_data);
+
+ xo_set_private(xop, cbor);
+
+ cbor_append(xop, cbor, &cbor->c_data, CBOR_MAP | CBOR_INDEF, 0, NULL);
+
+ return 0;
+}
+
+static int
+cbor_content (xo_handle_t *xop, cbor_private_t *cbor, xo_buffer_t *xbp,
+ const char *value)
+{
+ int rc = 0;
+
+ unsigned offset = xo_buf_offset(xbp);
+
+ if (value == NULL || *value == '\0' || strcmp(value, "true") == 0)
+ cbor_append(xop, cbor, &cbor->c_data, CBOR_TRUE, 0, NULL);
+ else if (strcmp(value, "false") == 0)
+ cbor_append(xop, cbor, &cbor->c_data, CBOR_FALSE, 0, NULL);
+ else {
+ int negative = 0;
+ if (*value == '-') {
+ value += 1;
+ negative = 1;
+ }
+
+ char *ep;
+ unsigned long long ival;
+ ival = strtoull(value, &ep, 0);
+ if (ival == ULLONG_MAX) /* Sometimes a string is just a string */
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(value), value);
+ else {
+ *xbp->xb_curp = negative ? CBOR_NEGATIVE : CBOR_UNSIGNED;
+ if (negative)
+ ival -= 1; /* Don't waste a negative zero */
+ cbor_encode_uint(xbp, ival, negative ? CBOR_NLIMIT : CBOR_ULIMIT);
+ }
+ }
+
+ if (xo_get_flags(xop) & XOF_PRETTY)
+ cbor_memdump(stdout, "content", xo_buf_data(xbp, offset),
+ xbp->xb_curp - xbp->xb_bufp - offset, "",
+ cbor->c_indent * 2);
+
+ return rc;
+}
+
+static int
+cbor_handler (XO_ENCODER_HANDLER_ARGS)
+{
+ int rc = 0;
+ cbor_private_t *cbor = private;
+ xo_buffer_t *xbp = cbor ? &cbor->c_data : NULL;
+
+ if (xo_get_flags(xop) & XOF_PRETTY) {
+ printf("%*sop %s: [%s] [%s]\n", cbor ? cbor->c_indent * 2 + 4 : 0, "",
+ xo_encoder_op_name(op), name ?: "", value ?: "");
+ fflush(stdout);
+ }
+
+ /* If we don't have private data, we're sunk */
+ if (cbor == NULL && op != XO_OP_CREATE)
+ return -1;
+
+ switch (op) {
+ case XO_OP_CREATE: /* Called when the handle is init'd */
+ rc = cbor_create(xop);
+ break;
+
+ case XO_OP_OPEN_CONTAINER:
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
+ cbor_append(xop, cbor, xbp, CBOR_MAP | CBOR_INDEF, 0, NULL);
+ cbor->c_indent += 1;
+ break;
+
+ case XO_OP_CLOSE_CONTAINER:
+ cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
+ cbor->c_indent -= 1;
+ break;
+
+ case XO_OP_OPEN_LIST:
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
+ cbor_append(xop, cbor, xbp, CBOR_ARRAY | CBOR_INDEF, 0, NULL);
+ cbor->c_indent += 1;
+ break;
+
+ case XO_OP_CLOSE_LIST:
+ cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
+ cbor->c_indent -= 1;
+ break;
+
+ case XO_OP_OPEN_LEAF_LIST:
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
+ cbor_append(xop, cbor, xbp, CBOR_ARRAY | CBOR_INDEF, 0, NULL);
+ cbor->c_indent += 1;
+ cbor->c_open_leaf_list = 1;
+ break;
+
+ case XO_OP_CLOSE_LEAF_LIST:
+ cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
+ cbor->c_indent -= 1;
+ cbor->c_open_leaf_list = 0;
+ break;
+
+ case XO_OP_OPEN_INSTANCE:
+ cbor_append(xop, cbor, xbp, CBOR_MAP | CBOR_INDEF, 0, NULL);
+ cbor->c_indent += 1;
+ break;
+
+ case XO_OP_CLOSE_INSTANCE:
+ cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
+ cbor->c_indent -= 1;
+ break;
+
+ case XO_OP_STRING: /* Quoted UTF-8 string */
+ if (!cbor->c_open_leaf_list)
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(value), value);
+ break;
+
+ case XO_OP_CONTENT: /* Other content */
+ if (!cbor->c_open_leaf_list)
+ cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
+
+ /*
+ * It's content, not string, so we need to look at the
+ * string and build some content. Turns out we only
+ * care about true, false, null, and numbers.
+ */
+ cbor_content(xop, cbor, xbp, value);
+ break;
+
+ case XO_OP_FINISH: /* Clean up function */
+ cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
+ cbor->c_indent -= 1;
+ break;
+
+ case XO_OP_FLUSH: /* Clean up function */
+ if (xo_get_flags(xop) & XOF_PRETTY)
+ cbor_memdump(stdout, "cbor",
+ xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp,
+ ">", 0);
+ else {
+ rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
+ if (rc > 0)
+ rc = 0;
+ }
+ break;
+
+ case XO_OP_DESTROY: /* Clean up function */
+ break;
+
+ case XO_OP_ATTRIBUTE: /* Attribute name/value */
+ break;
+
+ case XO_OP_VERSION: /* Version string */
+ break;
+
+ }
+
+ return rc;
+}
+
+int
+xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
+{
+ arg->xei_handler = cbor_handler;
+
+ return 0;
+}
diff --git a/contrib/libxo/encoder/test/Makefile.am b/contrib/libxo/encoder/test/Makefile.am
new file mode 100644
index 0000000..1d8518e
--- /dev/null
+++ b/contrib/libxo/encoder/test/Makefile.am
@@ -0,0 +1,51 @@
+#
+# $Id$
+#
+# Copyright 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.
+
+if LIBXO_WARNINGS_HIGH
+LIBXO_WARNINGS = HIGH
+endif
+if HAVE_GCC
+GCC_WARNINGS = yes
+endif
+include ${top_srcdir}/warnings.mk
+
+enc_testincdir = ${includedir}/libxo
+
+AM_CFLAGS = \
+ -I${top_srcdir}/libxo \
+ -I${top_builddir}/libxo \
+ ${WARNINGS}
+
+LIBNAME = libenc_test
+pkglib_LTLIBRARIES = libenc_test.la
+LIBS = \
+ -L${top_builddir}/libxo -lxo
+
+LDADD = ${top_builddir}/libxo/libxo.la
+
+libenc_test_la_SOURCES = \
+ enc_test.c
+
+pkglibdir = ${XO_ENCODERDIR}
+
+UGLY_NAME = test.enc
+
+install-exec-hook:
+ @DLNAME=`sh -c '. ./libenc_test.la ; echo $$dlname'` ; \
+ if [ x"$$DLNAME" = x ]; \
+ then DLNAME=${LIBNAME}.${XO_LIBEXT}; fi ; \
+ if [ "$(build_os)" = "cygwin" ]; \
+ then DLNAME="../bin/$$DLNAME"; fi ; \
+ echo Install link $$DLNAME "->" ${UGLY_NAME} "..." ; \
+ mkdir -p ${DESTDIR}${XO_ENCODERDIR} ; \
+ cd ${DESTDIR}${XO_ENCODERDIR} \
+ && chmod +w . \
+ && rm -f ${UGLY_NAME} \
+ && ${LN_S} $$DLNAME ${UGLY_NAME}
diff --git a/contrib/libxo/encoder/test/enc_test.c b/contrib/libxo/encoder/test/enc_test.c
new file mode 100644
index 0000000..ec49499
--- /dev/null
+++ b/contrib/libxo/encoder/test/enc_test.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 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, August 2015
+ */
+
+#include "xo.h"
+#include "xo_encoder.h"
+
+static int
+test_handler (XO_ENCODER_HANDLER_ARGS)
+{
+ printf("op %s: [%s] [%s]\n", xo_encoder_op_name(op),
+ name ?: "", value ?: "");
+
+ return 0;
+}
+
+int
+xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
+{
+ arg->xei_version = XO_ENCODER_VERSION;
+ arg->xei_handler = test_handler;
+
+ return 0;
+}
diff --git a/contrib/libxo/libxo/Makefile.am b/contrib/libxo/libxo/Makefile.am
index 0047b63..b11c311 100644
--- a/contrib/libxo/libxo/Makefile.am
+++ b/contrib/libxo/libxo/Makefile.am
@@ -16,25 +16,44 @@ include ${top_srcdir}/warnings.mk
libxoincdir = ${includedir}/libxo
-AM_CFLAGS = -I${top_srcdir} ${WARNINGS}
+AM_CFLAGS = \
+ -I${top_srcdir} \
+ ${WARNINGS} \
+ ${GETTEXT_CFLAGS}
+
+AM_CFLAGS += \
+ -DXO_ENCODERDIR=\"${XO_ENCODERDIR}\"
lib_LTLIBRARIES = libxo.la
+LIBS = \
+ ${GETTEXT_LIBS}
+
libxoinc_HEADERS = \
- xo.h
+ xo.h \
+ xo_encoder.h
+
+noinst_HEADERS = \
+ xo_buf.h \
+ xo_humanize.h \
+ xo_wcwidth.h
libxo_la_SOURCES = \
- libxo.c
+ libxo.c \
+ xo_encoder.c \
+ xo_syslog.c
-man_MANS = \
+man3_files = \
libxo.3 \
xo_attr.3 \
xo_create.3 \
xo_emit.3 \
+ xo_emit_err.3 \
xo_err.3 \
+ xo_error.3 \
xo_finish.3 \
xo_flush.3 \
- xo_format.5 \
+ xo_message.3 \
xo_no_setlocale.3 \
xo_open_container.3 \
xo_open_list.3 \
@@ -45,8 +64,26 @@ man_MANS = \
xo_set_info.3 \
xo_set_options.3 \
xo_set_style.3 \
+ xo_set_syslog_enterprise_id.3 \
xo_set_version.3 \
- xo_set_writer.3
+ xo_set_writer.3 \
+ xo_syslog.3
+
+man5_files = \
+ xo_format.5
+
+man_MANS = ${man3_files} ${man5_files}
+
+EXTRA_DIST = \
+ ${man_MANS}
-EXTRA_DIST = ${man_MANS}
+call-graph:
+ ${RM} libxo.o
+ ${MAKE} CC="clang -Xclang -analyze -Xclang \
+ -analyzer-checker=debug.ViewCallGraph" libxo.o
+install-data-hook:
+ for file in ${man3_files}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man3dir}/$$file ; done
+ for file in ${man5_files}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man5dir}/$$file ; done
diff --git a/contrib/libxo/libxo/add.man b/contrib/libxo/libxo/add.man
new file mode 100644
index 0000000..a1e3e11
--- /dev/null
+++ b/contrib/libxo/libxo/add.man
@@ -0,0 +1,29 @@
+.Sh ADDITIONAL DOCUMENTATION
+.Fx
+uses
+.Nm libxo
+version 0.4.3.
+Complete documentation can be found on github:
+.Bd -literal -offset indent
+http://juniper.github.io/libxo/0.4.3/libxo\-manual.html
+.Ed
+.Pp
+.Nm libxo
+lives on github as:
+.Bd -literal -offset indent
+https://github.com/Juniper/libxo
+.Ed
+.Pp
+The latest release of
+.Nm libxo
+is available at:
+.Bd -literal -offset indent
+https://github.com/Juniper/libxo/releases
+.Ed
+.Sh HISTORY
+The
+.Nm libxo
+library was added in
+.Fx 11.0 .
+.Sh AUTHOR
+Phil Shafer
diff --git a/contrib/libxo/libxo/add.man.in b/contrib/libxo/libxo/add.man.in
new file mode 100644
index 0000000..4eae265
--- /dev/null
+++ b/contrib/libxo/libxo/add.man.in
@@ -0,0 +1,29 @@
+.Sh ADDITIONAL DOCUMENTATION
+.Fx
+uses
+.Nm libxo
+version @LIBXO_VERSION@.
+Complete documentation can be found on github:
+.Bd -literal -offset indent
+http://juniper.github.io/libxo/@LIBXO_VERSION@/libxo\-manual.html
+.Ed
+.Pp
+.Nm libxo
+lives on github as:
+.Bd -literal -offset indent
+https://github.com/Juniper/libxo
+.Ed
+.Pp
+The latest release of
+.Nm libxo
+is available at:
+.Bd -literal -offset indent
+https://github.com/Juniper/libxo/releases
+.Ed
+.Sh HISTORY
+The
+.Nm libxo
+library was added in
+.Fx 11.0 .
+.Sh AUTHOR
+Phil Shafer
diff --git a/contrib/libxo/libxo/libxo.3 b/contrib/libxo/libxo/libxo.3
index f9b0e6f8..4e2488c 100644
--- a/contrib/libxo/libxo/libxo.3
+++ b/contrib/libxo/libxo/libxo.3
@@ -66,17 +66,25 @@ output, with attributes that detail how to render the data.
.Pp
There are four encoding styles supported by
.Nm :
-TEXT, HTML, JSON,
-and XML.
-JSON and XML are suitable for encoding data, while TEXT and
-HTML are suited for display to the user.
-TEXT output can be display
-on a terminal session, allowing compatibility with traditional usage.
+.Bl -bullet
+.It
+TEXT output can be display on a terminal session, allowing
+compatibility with traditional command line usage.
+.It
+XML output is suitable for tools like XPath and protocols like
+NETCONF.
+.It
+JSON output can be used for RESTful APIs and integration with
+languages like Javascript and Python.
+.It
HTML can be matched with a small CSS file to permit rendering in any
HTML5 browser.
-XML output is suitable for tools like XPath and
-protocols like NETCONF.
-JSON output can be used for RESTful APIs.
+.El
+.Pp
+In general, XML and JSON are suitable for encoding data, while TEXT is
+suited for terminal output and HTML is suited for display in a web
+browser (see
+.Xr xohtml 1 ).
.Pp
The
.Nm
@@ -177,6 +185,19 @@ formatted output.
See
.Xr xo_format 5
for details.
+.It Fn xo_emit_warn
+.It Fn xo_emit_warnx
+.It Fn xo_emit_warn_c
+.It Fn xo_emit_warn_hc
+.It Fn xo_emit_err
+.It Fn xo_emit_errc
+.It Fn xo_emit_errx
+These functions are mildly compatible with their standard libc
+namesakes, but use the format string defined in
+.Xr xo_format 5 .
+While there is an increased cost for converting the strings, the
+output provided can be richer and more useful. See also
+.Xr xo_err 3
.It Fn xo_warn
.It Fn xo_warnx
.It Fn xo_warn_c
@@ -269,30 +290,13 @@ Instructs
.Nm
to use an alternative set of low-level output functions.
.El
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr xo 1 ,
.Xr xolint 1 ,
.Xr xo_attr 3 ,
.Xr xo_create 3 ,
.Xr xo_emit 3 ,
+.Xr xo_emit_err 3 ,
.Xr xo_err 3 ,
.Xr xo_finish 3 ,
.Xr xo_flush 3 ,
@@ -307,10 +311,3 @@ https://github.com/Juniper/libxo/releases
.Xr xo_set_style 3 ,
.Xr xo_set_writer 3 ,
.Xr xo_format 5
-.Sh HISTORY
-The
-.Nm
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
diff --git a/contrib/libxo/libxo/libxo.c b/contrib/libxo/libxo/libxo.c
index bb4ce2e..b531371 100644
--- a/contrib/libxo/libxo/libxo.c
+++ b/contrib/libxo/libxo/libxo.c
@@ -6,6 +6,24 @@
* 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>
@@ -24,14 +42,82 @@
#include <wctype.h>
#include <getopt.h>
-#include "xoconfig.h"
+#include "xo_config.h"
#include "xo.h"
-#include "xoversion.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;
@@ -40,22 +126,11 @@ const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
#endif /* UNUSED */
#define XO_INDENT_BY 2 /* Amount to indent when pretty printing */
-#define XO_BUFSIZ (8*1024) /* Initial buffer size */
-#define XO_DEPTH 512 /* Default stack depth */
+#define XO_DEPTH 128 /* Default stack depth */
#define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
#define XO_FAILURE_NAME "failure"
-/*
- * xo_buffer_t: a memory buffer that can be grown as needed. We
- * use them for building format strings and output data.
- */
-typedef struct xo_buffer_s {
- char *xb_bufp; /* Buffer memory */
- char *xb_curp; /* Current insertion point */
- int xb_size; /* Size of buffer */
-} xo_buffer_t;
-
/* Flags for the stack frame */
typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
#define XSF_NOT_FIRST (1<<0) /* Not the first element */
@@ -73,17 +148,17 @@ typedef unsigned xo_xsf_flags_t; /* XSF_* 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
+ * 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
+ * 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
+ * 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 their stack frame will be popped.
+ * 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.
*/
@@ -121,7 +196,11 @@ typedef struct xo_stack_s {
char *xs_keys; /* XPath predicate for any key fields */
} xo_stack_t;
-/* "colors" refers to fancy ansi codes */
+/*
+ * 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
@@ -134,20 +213,19 @@ typedef struct xo_stack_s {
#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
+ * 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 asking while making funeral arrangements.
+ * 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!
+ * civilized to do that. Hhhmph!
*/
#define XO_EFF_RESET (1<<0)
#define XO_EFF_NORMAL (1<<1)
@@ -155,7 +233,7 @@ typedef struct xo_stack_s {
#define XO_EFF_UNDERLINE (1<<3)
#define XO_EFF_INVERSE (1<<4)
-#define XO_EFF_CLEAR_BITS XO_EFF_RESET
+#define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
typedef uint8_t xo_effect_t;
typedef uint8_t xo_color_t;
@@ -167,11 +245,13 @@ typedef struct xo_colors_s {
/*
* xo_handle_t: this is the principle data structure for libxo.
- * It's used as a store for state, options, and content.
+ * 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 */
- unsigned short xo_style; /* XO_STYLE_* value */
+ 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 */
@@ -202,18 +282,44 @@ struct xo_handle_s {
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 formats (xml, json) */
+#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 formats (text and html) */
+#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) */
@@ -224,6 +330,17 @@ typedef unsigned long xo_xff_flags_t;
#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
@@ -258,7 +375,6 @@ typedef unsigned long xo_xff_flags_t;
* 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 */
@@ -291,12 +407,39 @@ typedef struct xo_format_s {
} xo_format_t;
/*
- * We keep a default handle to allow callers to avoid having to
+ * 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.
+ * 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 xo_handle_t xo_default_handle;
-static int xo_default_inited;
+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;
@@ -304,8 +447,8 @@ static const char *xo_program;
* To allow libxo to be used in diverse environment, we allow the
* caller to give callbacks for memory allocation.
*/
-static xo_realloc_func_t xo_realloc = realloc;
-static xo_free_func_t xo_free = free;
+xo_realloc_func_t xo_realloc = realloc;
+xo_free_func_t xo_free = free;
/* Forward declarations */
static void
@@ -332,7 +475,7 @@ xo_anchor_clear (xo_handle_t *xop);
* trims our code nicely without needing to trampel perfectly readable
* code with ifdefs.
*/
-static inline unsigned short
+static inline xo_style_t
xo_style (xo_handle_t *xop UNUSED)
{
#ifdef LIBXO_TEXT_ONLY
@@ -376,43 +519,41 @@ xo_flush_file (void *opaque)
}
/*
- * Initialize the contents of an xo_buffer_t.
- */
-static void
-xo_buf_init (xo_buffer_t *xbp)
-{
- xbp->xb_size = XO_BUFSIZ;
- xbp->xb_bufp = xo_realloc(NULL, xbp->xb_size);
- xbp->xb_curp = xbp->xb_bufp;
-}
-
-/*
- * Reset the buffer to empty
+ * Use a rotating stock of buffers to make a printable string
*/
-static void
-xo_buf_reset (xo_buffer_t *xbp)
-{
- xbp->xb_curp = xbp->xb_bufp;
-}
+#define XO_NUMBUFS 8
+#define XO_SMBUFSZ 128
-/*
- * Reset the buffer to empty
- */
-static int
-xo_buf_is_empty (xo_buffer_t *xbp)
-{
- return (xbp->xb_curp == xbp->xb_bufp);
-}
+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;
+ }
-/*
- * Initialize the contents of an xo_buffer_t.
- */
-static void
-xo_buf_cleanup (xo_buffer_t *xbp)
-{
- if (xbp->xb_bufp)
- xo_free(xbp->xb_bufp);
- bzero(xbp, sizeof(*xbp));
+ *cp = '\0';
+ return res;
}
static int
@@ -421,11 +562,12 @@ xo_depth_check (xo_handle_t *xop, int depth)
xo_stack_t *xsp;
if (depth >= xop->xo_stack_size) {
- depth += 16;
+ 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 0;
+ return -1;
}
int count = depth - xop->xo_stack_size;
@@ -480,14 +622,14 @@ xo_init_handle (xo_handle_t *xop)
xop->xo_flush = xo_flush_file;
if (xo_is_line_buffered(stdout))
- xop->xo_flags |= XOF_FLUSH_LINE;
+ 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 ((xop->xo_flags & XOF_COLOR_ALLOWED) && isatty(1))
- xop->xo_flags |= XOF_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.
@@ -504,7 +646,7 @@ xo_init_handle (xo_handle_t *xop)
if (cp == NULL)
cp = getenv("LC_ALL");
if (cp == NULL)
- cp = "UTF-8"; /* Optimistic? */
+ cp = "C"; /* Default for C programs */
(void) setlocale(LC_CTYPE, cp);
}
@@ -515,16 +657,23 @@ xo_init_handle (xo_handle_t *xop)
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 (!(xop->xo_flags & XOF_NO_ENV)) {
+ 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);
}
/*
@@ -541,34 +690,6 @@ xo_default_init (void)
}
/*
- * Does the buffer have room for the given number of bytes of data?
- * If not, realloc the buffer to make room. If that fails, we
- * return 0 to tell the caller they are in trouble.
- */
-static int
-xo_buf_has_room (xo_buffer_t *xbp, int len)
-{
- if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) {
- int sz = xbp->xb_size + XO_BUFSIZ;
- char *bp = xo_realloc(xbp->xb_bufp, sz);
- if (bp == NULL) {
- /*
- * XXX If we wanted to put a stick XOF_ENOMEM on xop,
- * this would be the place to do it. But we'd need
- * to churn the code to pass xop in here....
- */
- return 0;
- }
-
- xbp->xb_curp = bp + (xbp->xb_curp - xbp->xb_bufp);
- xbp->xb_bufp = bp;
- xbp->xb_size = sz;
- }
-
- return 1;
-}
-
-/*
* Cheap convenience function to return either the argument, or
* the internal handle, after it has been initialized. The usage
* is:
@@ -597,9 +718,9 @@ xo_indent (xo_handle_t *xop)
xop = xo_default(xop);
- if (xop->xo_flags & XOF_PRETTY) {
+ if (XOF_ISSET(xop, XOF_PRETTY)) {
rc = xop->xo_indent * xop->xo_indent_by;
- if (xop->xo_flags & XOF_TOP_EMITTED)
+ if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
rc += xop->xo_indent_by;
}
@@ -627,12 +748,13 @@ static char xo_xml_gt[] = "&gt;";
static char xo_xml_quot[] = "&quot;";
static int
-xo_escape_xml (xo_buffer_t *xbp, int len, int attr)
+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 */
@@ -682,7 +804,7 @@ xo_escape_xml (xo_buffer_t *xbp, int len, int attr)
}
static int
-xo_escape_json (xo_buffer_t *xbp, int len)
+xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
{
unsigned delta = 0;
char *cp, *ep, *ip;
@@ -726,31 +848,43 @@ xo_escape_json (xo_buffer_t *xbp, int len)
}
/*
- * Append the given string to the given buffer
+ * PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
+ * ; ']' MUST be escaped.
*/
-static void
-xo_buf_append (xo_buffer_t *xbp, const char *str, int len)
+static int
+xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
{
- if (!xo_buf_has_room(xbp, len))
- return;
+ unsigned delta = 0;
+ char *cp, *ep, *ip;
- memcpy(xbp->xb_curp, str, len);
- xbp->xb_curp += len;
-}
+ for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
+ if (*cp == '\\' || *cp == '"' || *cp == ']')
+ delta += 1;
+ }
-/*
- * 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 (delta == 0) /* Nothing to escape; bail */
+ return len;
- if (!xo_buf_has_room(xbp, len))
- return;
+ if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
+ return 0;
- memcpy(xbp->xb_curp, str, len);
- xbp->xb_curp += len;
+ 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
@@ -765,11 +899,15 @@ xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
switch (xo_style(xop)) {
case XO_STYLE_XML:
case XO_STYLE_HTML:
- len = xo_escape_xml(xbp, len, (flags & XFF_ATTR));
+ len = xo_escape_xml(xbp, len, flags);
break;
case XO_STYLE_JSON:
- len = xo_escape_json(xbp, len);
+ len = xo_escape_json(xbp, len, flags);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ len = xo_escape_sdparams(xbp, len, flags);
break;
}
@@ -789,12 +927,13 @@ xo_write (xo_handle_t *xop)
if (xbp->xb_curp != xbp->xb_bufp) {
xo_buf_append(xbp, "", 1); /* Append ending NUL */
xo_anchor_clear(xop);
- rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
+ 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 */
- xop->xo_flags &= ~(XOF_UNITS_PENDING);
+ XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
return rc;
}
@@ -860,7 +999,7 @@ xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
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;
@@ -1045,7 +1184,7 @@ xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
return 0;
}
- if (xop->xo_flags & XOF_NO_LOCALE) {
+ if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
if (!xo_buf_has_room(xbp, ilen))
return 0;
@@ -1067,7 +1206,7 @@ xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
xbp->xb_curp += len;
}
- return wcwidth(wc);
+ return xo_wcwidth(wc);
}
static void
@@ -1111,9 +1250,9 @@ xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
}
/* Update column values */
- if (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += cols;
/* Before we fall into the basic logic below, we need reset len */
@@ -1123,7 +1262,9 @@ xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
}
/*
- * Append the given string to the given buffer
+ * 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)
@@ -1150,7 +1291,7 @@ 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 && !(xop->xo_flags & XOF_WARN))
+ if (check_warn && !XOF_ISSET(xop, XOF_WARN))
return;
if (fmt == NULL)
@@ -1168,7 +1309,7 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
memcpy(newfmt + plen, fmt, len);
newfmt[len + plen] = '\0';
- if (xop->xo_flags & XOF_WARN_XML) {
+ if (XOF_ISSET(xop, XOF_WARN_XML)) {
static char err_open[] = "<error>";
static char err_close[] = "</error>";
static char msg_open[] = "<message>";
@@ -1184,7 +1325,7 @@ xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
- if (rc > xbp->xb_size) {
+ if (rc >= left) {
if (!xo_buf_has_room(xbp, rc)) {
va_end(va_local);
return;
@@ -1212,7 +1353,7 @@ 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_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
(void) xo_write(xop);
} else {
@@ -1328,7 +1469,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
switch (xo_style(xop)) {
case XO_STYLE_XML:
xbp = &xop->xo_data;
- if (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_buf_indent(xop, xop->xo_indent_by);
xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
@@ -1336,7 +1477,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
- if (rc > xbp->xb_size) {
+ if (rc >= left) {
if (!xo_buf_has_room(xbp, rc)) {
va_end(va_local);
return;
@@ -1350,7 +1491,7 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
}
va_end(va_local);
- rc = xo_escape_xml(xbp, rc, 1);
+ rc = xo_escape_xml(xbp, rc, 0);
xbp->xb_curp += rc;
if (need_nl && code > 0) {
@@ -1361,9 +1502,14 @@ 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_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;
@@ -1399,8 +1545,10 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
break;
case XO_STYLE_JSON:
- /* No meanings of representing messages in JSON */
- break;
+ 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);
@@ -1408,9 +1556,9 @@ xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
* XXX need to handle UTF-8 widths
*/
if (rc > 0) {
- if (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += rc;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += rc;
}
@@ -1450,7 +1598,7 @@ xo_message_c (int code, const char *fmt, ...)
}
void
-xo_message (const char *fmt, ...)
+xo_message_e (const char *fmt, ...)
{
int code = errno;
va_list vap;
@@ -1460,10 +1608,20 @@ xo_message (const char *fmt, ...)
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 (!(xop->xo_flags & XOF_WARN))
+ if (!XOF_ISSET(xop, XOF_WARN))
return;
va_list vap;
@@ -1490,9 +1648,10 @@ xo_create (xo_style_t style, xo_xof_flags_t flags)
if (xop) {
bzero(xop, sizeof(*xop));
- xop->xo_style = style;
- xop->xo_flags = flags;
+ xop->xo_style = style;
+ XOF_SET(xop, flags);
xo_init_handle(xop);
+ xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */
}
return xop;
@@ -1529,7 +1688,9 @@ xo_destroy (xo_handle_t *xop_arg)
{
xo_handle_t *xop = xo_default(xop_arg);
- if (xop->xo_close && (xop->xo_flags & XOF_CLOSE_FP))
+ 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);
@@ -1577,55 +1738,111 @@ xo_name_to_style (const char *name)
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)
{
- if (strcmp(name, "pretty") == 0)
- return XOF_PRETTY;
- if (strcmp(name, "warn") == 0)
- return XOF_WARN;
- if (strcmp(name, "xpath") == 0)
- return XOF_XPATH;
- if (strcmp(name, "info") == 0)
- 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)
- return XOF_DTRT;
- if (strcmp(name, "flush") == 0)
- return XOF_FLUSH;
- if (strcmp(name, "keys") == 0)
- return XOF_KEYS;
- if (strcmp(name, "ignore-close") == 0)
- return XOF_IGNORE_CLOSE;
- if (strcmp(name, "not-first") == 0)
- return XOF_NOT_FIRST;
- if (strcmp(name, "no-locale") == 0)
- return XOF_NO_LOCALE;
- if (strcmp(name, "no-top") == 0)
- return XOF_NO_TOP;
- if (strcmp(name, "units") == 0)
- return XOF_UNITS;
- if (strcmp(name, "underscores") == 0)
- return XOF_UNDERSCORES;
-
- return 0;
+ return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
}
int
@@ -1661,7 +1878,7 @@ xo_set_options (xo_handle_t *xop, const char *input)
#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;
+ XOF_SET(xop, XOF_COLOR_ALLOWED);
#endif /* LIBXO_COLOR_ON_BY_DEFAULT */
/*
@@ -1675,15 +1892,19 @@ xo_set_options (xo_handle_t *xop, const char *input)
for (input++ ; *input; input++) {
switch (*input) {
case 'c':
- xop->xo_flags |= XOF_COLOR_ALLOWED;
+ XOF_SET(xop, XOF_COLOR_ALLOWED);
break;
case 'f':
- xop->xo_flags |= XOF_FLUSH;
+ XOF_SET(xop, XOF_FLUSH);
break;
case 'F':
- xop->xo_flags |= XOF_FLUSH_LINE;
+ XOF_SET(xop, XOF_FLUSH_LINE);
+ break;
+
+ case 'g':
+ XOF_SET(xop, XOF_LOG_GETTEXT);
break;
case 'H':
@@ -1691,7 +1912,7 @@ xo_set_options (xo_handle_t *xop, const char *input)
break;
case 'I':
- xop->xo_flags |= XOF_INFO;
+ XOF_SET(xop, XOF_INFO);
break;
case 'i':
@@ -1702,16 +1923,20 @@ xo_set_options (xo_handle_t *xop, const char *input)
}
break;
+ case 'J':
+ xop->xo_style = XO_STYLE_JSON;
+ break;
+
case 'k':
- xop->xo_flags |= XOF_KEYS;
+ XOF_SET(xop, XOF_KEYS);
break;
- case 'J':
- xop->xo_style = XO_STYLE_JSON;
+ case 'n':
+ XOF_SET(xop, XOF_NO_HUMANIZE);
break;
case 'P':
- xop->xo_flags |= XOF_PRETTY;
+ XOF_SET(xop, XOF_PRETTY);
break;
case 'T':
@@ -1719,15 +1944,15 @@ xo_set_options (xo_handle_t *xop, const char *input)
break;
case 'U':
- xop->xo_flags |= XOF_UNITS;
+ XOF_SET(xop, XOF_UNITS);
break;
case 'u':
- xop->xo_flags |= XOF_UNDERSCORES;
+ XOF_SET(xop, XOF_UNDERSCORES);
break;
case 'W':
- xop->xo_flags |= XOF_WARN;
+ XOF_SET(xop, XOF_WARN);
break;
case 'X':
@@ -1735,7 +1960,7 @@ xo_set_options (xo_handle_t *xop, const char *input)
break;
case 'x':
- xop->xo_flags |= XOF_XPATH;
+ XOF_SET(xop, XOF_XPATH);
break;
}
}
@@ -1760,8 +1985,12 @@ xo_set_options (xo_handle_t *xop, const char *input)
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) {
+ if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
if (style >= 0)
xo_warnx("ignoring multiple styles: '%s'", cp);
else
@@ -1769,14 +1998,27 @@ xo_set_options (xo_handle_t *xop, const char *input)
} else {
new_flag = xo_name_to_flag(cp);
if (new_flag != 0)
- xop->xo_flags |= new_flag;
+ XOF_SET(xop, new_flag);
else {
if (strcmp(cp, "no-color") == 0) {
- xop->xo_flags &= ~XOF_COLOR_ALLOWED;
+ XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
} else if (strcmp(cp, "indent") == 0) {
- xop->xo_indent_by = atoi(vp);
+ 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 option: '%s'", cp);
+ xo_warnx("unknown libxo option value: '%s'", cp);
rc = -1;
}
}
@@ -1801,7 +2043,7 @@ xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
{
xop = xo_default(xop);
- xop->xo_flags |= flags;
+ XOF_SET(xop, flags);
}
xo_xof_flags_t
@@ -1812,6 +2054,24 @@ xo_get_flags (xo_handle_t *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
@@ -1833,11 +2093,7 @@ xo_set_leading_xpath (xo_handle_t *xop, const char *path)
if (path == NULL)
return;
- int len = strlen(path);
- xop->xo_leading_xpath = xo_realloc(NULL, len + 1);
- if (xop->xo_leading_xpath) {
- memcpy(xop->xo_leading_xpath, path, len + 1);
- }
+ xop->xo_leading_xpath = xo_strndup(path, -1);
}
/**
@@ -1890,7 +2146,7 @@ xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
{
xop = xo_default(xop);
- xop->xo_flags &= ~flags;
+ XOF_CLEAR(xop, flags);
}
static const char *
@@ -1926,19 +2182,19 @@ 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 (xop->xo_flags & XOF_DIV_OPEN)
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
return;
if (xo_style(xop) != XO_STYLE_HTML)
return;
- xop->xo_flags |= XOF_DIV_OPEN;
+ 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 (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_data_append(xop, "\n", 1);
}
@@ -1949,13 +2205,13 @@ xo_line_close (xo_handle_t *xop)
switch (xo_style(xop)) {
case XO_STYLE_HTML:
- if (!(xop->xo_flags & XOF_DIV_OPEN))
+ if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
xo_line_ensure_open(xop, 0);
- xop->xo_flags &= ~XOF_DIV_OPEN;
+ XOIF_CLEAR(xop, XOIF_DIV_OPEN);
xo_data_append(xop, div_close, sizeof(div_close) - 1);
- if (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_data_append(xop, "\n", 1);
break;
@@ -2055,7 +2311,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
ilen = xo_utf8_to_wc_len(cp);
if (ilen < 0) {
xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
- return -1;
+ return -1; /* Can't continue; we can't find the end */
}
if (len > 0 && len < ilen) {
@@ -2067,7 +2323,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
if (wc == (wchar_t) -1) {
xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
*cp, ilen);
- return -1;
+ return -1; /* Can't continue; we can't find the end */
}
cp += ilen;
break;
@@ -2080,6 +2336,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
wc = L'?';
ilen = 1;
}
+
if (ilen == 0) { /* Hit a wide NUL character */
len = 0;
continue;
@@ -2101,7 +2358,7 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
* in wide characters, since we lack a mbswidth() function. If
* it doesn't fit
*/
- width = wcwidth(wc);
+ width = xo_wcwidth(wc);
if (width < 0)
width = iswcntrl(wc) ? 0 : 1;
@@ -2152,6 +2409,18 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
*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);
@@ -2175,7 +2444,6 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
if (olen <= 0) {
xo_failure(xop, "could not convert wide char: %lx",
(unsigned long) wc);
- olen = 1;
width = 1;
*xbp->xb_curp++ = '?';
} else
@@ -2191,24 +2459,42 @@ xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
}
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_style(xop) == XO_STYLE_TEXT)
- ? XF_ENC_LOCALE : XF_ENC_UTF8;
+ 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_enc == XF_ENC_WIDE) {
+ 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;
@@ -2224,13 +2510,20 @@ xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
} 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) {
- cp = null;
- len = sizeof(null) - 1;
+ 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;
+ }
}
/*
@@ -2241,7 +2534,8 @@ xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
&& xfp->xf_width[XF_WIDTH_MIN] < 0
&& xfp->xf_width[XF_WIDTH_SIZE] < 0
&& xfp->xf_width[XF_WIDTH_MAX] < 0
- && !(xop->xo_flags & (XOF_ANCHOR | XOF_COLUMNS))) {
+ && !(XOIF_ISSET(xop, XOIF_ANCHOR)
+ || XOF_ISSET(xop, XOF_COLUMNS))) {
len = strlen(cp);
xo_buf_escape(xop, xbp, cp, len, flags);
@@ -2298,9 +2592,9 @@ xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
cols += delta;
}
- if (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += cols;
return rc;
@@ -2310,20 +2604,227 @@ xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
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_data_append_content (xo_handle_t *xop, const char *str, int len,
+ xo_xff_flags_t flags)
{
int cols;
- int need_enc = (xo_style(xop) == XO_STYLE_TEXT)
- ? XF_ENC_LOCALE : XF_ENC_UTF8;
+ 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,
+ 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 (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += cols;
}
@@ -2368,8 +2869,13 @@ xo_trim_ws (xo_buffer_t *xbp, int len)
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_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
+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;
@@ -2377,13 +2883,25 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
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_style(xop) == XO_STYLE_TEXT)
- ? XF_ENC_LOCALE : XF_ENC_UTF8;
-
+ 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)
@@ -2403,9 +2921,9 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
NULL, xp, cp - xp, -1,
need_enc, XF_ENC_UTF8);
- if (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += cols;
}
@@ -2430,18 +2948,18 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
* '*' means there's a "%*.*s" value in vap that
* we want to ignore
*/
- if (!(xop->xo_flags & XOF_NO_VA_ARG))
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
va_arg(xop->xo_vap, int);
}
}
}
/* Hidden fields are only visible to JSON and XML */
- if (xop->xo_flags & XFF_ENCODE_ONLY) {
+ if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
if (style != XO_STYLE_XML
- && xo_style(xop) != XO_STYLE_JSON)
+ && !xo_style_is_encoding(xop))
xf.xf_skip = 1;
- } else if (xop->xo_flags & XFF_DISPLAY_ONLY) {
+ } else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
if (style != XO_STYLE_TEXT
&& xo_style(xop) != XO_STYLE_HTML)
xf.xf_skip = 1;
@@ -2484,7 +3002,7 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
} else if (*cp == '*') {
xf.xf_stars += 1;
xf.xf_star[xf.xf_dots] = 1;
- } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
+ } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
break;
else if (*cp == 'n' || *cp == 'v') {
xo_failure(xop, "unsupported format: '%s'", fmt);
@@ -2498,7 +3016,7 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
xf.xf_fc = *cp;
- if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
if (*cp == 's' || *cp == 'S') {
/* Handle "%*.*.*s" */
int s;
@@ -2543,14 +3061,16 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
* correctly. So we have to handle this ourselves.
*/
if (xop->xo_formatter == NULL
- && (xf.xf_fc == 's' || xf.xf_fc == 'S')) {
- xf.xf_enc = (xf.xf_lflag || (xf.xf_fc == 'S'))
- ? XF_ENC_WIDE : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
+ && (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(xop) == XO_STYLE_XML
- || xo_style(xop) == XO_STYLE_JSON))
+ if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
rc = xo_trim_ws(xbp, rc);
} else {
@@ -2572,19 +3092,31 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
case XO_STYLE_JSON:
if (flags & XFF_TRIM_WS)
columns = rc = xo_trim_ws(xbp, rc);
- rc = xo_escape_json(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 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.
+ * 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 (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += columns;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += columns;
}
@@ -2595,7 +3127,7 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
* Now for the tricky part: we need to move the argument pointer
* along by the amount needed.
*/
- if (!(xop->xo_flags & XOF_NO_VA_ARG)) {
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
/*
@@ -2606,6 +3138,9 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
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++) {
@@ -2664,15 +3199,31 @@ xo_format_data (xo_handle_t *xop, xo_buffer_t *xbp,
cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
NULL, xp, cp - xp, -1,
need_enc, XF_ENC_UTF8);
- if (xop->xo_flags & XOF_COLUMNS)
+
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
+ 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;
}
@@ -2709,6 +3260,107 @@ xo_color_append_html (xo_handle_t *xop)
}
}
+/*
+ * 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,
@@ -2731,7 +3383,7 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
*/
int need_predidate =
(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
- && (xop->xo_flags & XOF_XPATH));
+ && XOF_ISSET(xop, XOF_XPATH));
if (need_predidate) {
va_list va_local;
@@ -2749,7 +3401,7 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
xo_buf_append(pbp, "[", 1);
xo_buf_escape(xop, pbp, name, nlen, 0);
- if (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_buf_append(pbp, " = '", 4);
else
xo_buf_append(pbp, "='", 2);
@@ -2763,7 +3415,9 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
elen = strlen(encoding);
}
- xo_format_data(xop, pbp, encoding, elen, XFF_XML | XFF_ATTR);
+ 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);
@@ -2793,14 +3447,14 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
* work of formatting it to make sure the args are cleared
* from xo_vap.
*/
- xo_format_data(xop, &xop->xo_data, encoding, elen,
+ xo_do_format_field(xop, NULL, encoding, elen,
flags | XFF_NO_OUTPUT);
return;
}
xo_line_ensure_open(xop, 0);
- if (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_buf_indent(xop, xop->xo_indent_by);
xo_data_append(xop, div_start, sizeof(div_start) - 1);
@@ -2823,8 +3477,8 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
/*
* Save the offset at which we'd place units. See xo_format_units.
*/
- if (xop->xo_flags & XOF_UNITS) {
- xop->xo_flags |= XOF_UNITS_PENDING;
+ 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
@@ -2833,10 +3487,8 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
xop->xo_units_offset =
xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
}
- }
- if (name) {
- if (xop->xo_flags & XOF_XPATH) {
+ if (XOF_ISSET(xop, XOF_XPATH)) {
int i;
xo_stack_t *xsp;
@@ -2872,7 +3524,7 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
xo_data_escape(xop, name, nlen);
}
- if ((xop->xo_flags & XOF_INFO) && xop->xo_info) {
+ if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
static char in_type[] = "\" data-type=\"";
static char in_help[] = "\" data-help=\"";
@@ -2889,17 +3541,60 @@ xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
}
}
- if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS))
+ 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_format_data(xop, NULL, value, vlen, 0);
+ 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 (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_data_append(xop, "\n", 1);
}
@@ -2918,9 +3613,14 @@ xo_format_text (xo_handle_t *xop, const char *str, int len)
}
static void
-xo_format_title (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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>";
@@ -2933,12 +3633,14 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
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_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
+ xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
return;
}
@@ -2946,12 +3648,10 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
int start = xbp->xb_curp - xbp->xb_bufp;
int left = xbp->xb_size - start;
int rc;
- int need_enc = XF_ENC_LOCALE;
if (xo_style(xop) == XO_STYLE_HTML) {
- need_enc = XF_ENC_UTF8;
xo_line_ensure_open(xop, 0);
- if (xop->xo_flags & XOF_PRETTY)
+ 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);
@@ -2970,7 +3670,6 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
newstr[len] = '\0';
if (newstr[len - 1] == 's') {
- int cols;
char *bp;
rc = snprintf(NULL, 0, newfmt, newstr);
@@ -2981,20 +3680,14 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
*/
bp = alloca(rc + 1);
rc = snprintf(bp, rc + 1, newfmt, newstr);
- cols = xo_format_string_direct(xop, xbp, 0, NULL, bp, rc, -1,
- need_enc, XF_ENC_UTF8);
- if (cols > 0) {
- if (xop->xo_flags & XOF_COLUMNS)
- xop->xo_columns += cols;
- if (xop->xo_flags & XOF_ANCHOR)
- xop->xo_anchor_columns += cols;
- }
+
+ xo_data_append_content(xop, bp, rc, flags);
}
goto move_along;
} else {
rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
- if (rc > left) {
+ if (rc >= left) {
if (!xo_buf_has_room(xbp, rc))
return;
left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
@@ -3002,17 +3695,17 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
}
if (rc > 0) {
- if (xop->xo_flags & XOF_COLUMNS)
+ if (XOF_ISSET(xop, XOF_COLUMNS))
xop->xo_columns += rc;
- if (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xop->xo_anchor_columns += rc;
}
}
} else {
- xo_format_data(xop, NULL, fmt, flen, 0);
+ xo_do_format_field(xop, NULL, fmt, flen, flags);
- /* xo_format_data moved curp, so we need to reset it */
+ /* 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;
}
@@ -3028,7 +3721,7 @@ xo_format_title (xo_handle_t *xop, const char *str, int len,
move_along:
if (xo_style(xop) == XO_STYLE_HTML) {
xo_data_append(xop, div_close, sizeof(div_close) - 1);
- if (xop->xo_flags & XOF_PRETTY)
+ if (XOF_ISSET(xop, XOF_PRETTY))
xo_data_append(xop, "\n", 1);
}
}
@@ -3038,7 +3731,7 @@ 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) && (xop->xo_flags & XOF_PRETTY))
+ 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;
@@ -3058,12 +3751,11 @@ xo_arg (xo_handle_t *xop)
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)
+ const char *format, int flen,
+ const char *encoding, int elen, xo_xff_flags_t flags)
{
- int pretty = (xop->xo_flags & XOF_PRETTY);
+ int pretty = XOF_ISSET(xop, XOF_PRETTY);
int quote;
- xo_buffer_t *xbp;
/*
* Before we emit a value, we need to know that the frame is ready.
@@ -3134,16 +3826,28 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
}
}
+ 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;
- xo_format_data(xop, NULL, format, flen, flags);
+
+ 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;
@@ -3155,7 +3859,7 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
*/
if (flags & XFF_DISPLAY_ONLY) {
flags |= XFF_NO_OUTPUT;
- xo_format_data(xop, NULL, format, flen, flags);
+ xo_do_format_field(xop, NULL, format, flen, flags);
break;
}
@@ -3194,7 +3898,7 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
* data, it's often useful. Especially when format meta-data is
* difficult to come by.
*/
- if ((flags & XFF_KEY) && (xop->xo_flags & XOF_KEYS)) {
+ if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
static char attr[] = " key=\"key\"";
xo_data_append(xop, attr, sizeof(attr) - 1);
}
@@ -3202,13 +3906,13 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
/*
* Save the offset at which we'd place units. See xo_format_units.
*/
- if (xop->xo_flags & XOF_UNITS) {
- xop->xo_flags |= XOF_UNITS_PENDING;
+ 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_format_data(xop, NULL, format, flen, flags);
+ 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);
@@ -3219,7 +3923,7 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
case XO_STYLE_JSON:
if (flags & XFF_DISPLAY_ONLY) {
flags |= XFF_NO_OUTPUT;
- xo_format_data(xop, NULL, format, flen, flags);
+ xo_do_format_field(xop, NULL, format, flen, flags);
break;
}
@@ -3273,7 +3977,7 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
xo_data_escape(xop, name, nlen);
- if (xop->xo_flags & XOF_UNDERSCORES) {
+ if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
int now = xbp->xb_curp - xbp->xb_bufp;
for ( ; off < now; off++)
if (xbp->xb_bufp[off] == '-')
@@ -3287,25 +3991,145 @@ xo_format_value (xo_handle_t *xop, const char *name, int nlen,
if (quote)
xo_data_append(xop, "\"", 1);
- xo_format_data(xop, NULL, format, flen, flags);
+ 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 *xml_tag, int display_only,
- const char *str, int len, const char *fmt, int flen)
+ 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);
- } else
- xo_format_data(xop, NULL, fmt, flen, 0);
+ 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:
@@ -3314,19 +4138,21 @@ xo_format_content (xo_handle_t *xop, const char *class_name,
len = flen;
}
- xo_buf_append_div(xop, class_name, 0, NULL, 0, str, len, NULL, 0);
+ xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
break;
case XO_STYLE_XML:
- if (xml_tag) {
+ case XO_STYLE_JSON:
+ case XO_STYLE_SDPARAMS:
+ if (tag_name) {
if (len == 0) {
str = fmt;
len = flen;
}
- xo_open_container_h(xop, xml_tag);
- xo_format_value(xop, "message", 7, str, len, NULL, 0, 0);
- xo_close_container_h(xop, xml_tag);
+ 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 {
/*
@@ -3334,21 +4160,15 @@ xo_format_content (xo_handle_t *xop, const char *class_name,
* enough parsing work to skip over the right bits of xo_vap.
*/
if (len == 0)
- xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
+ xo_do_format_field(xop, NULL, fmt, flen,
+ flags | XFF_NO_OUTPUT);
}
break;
- case XO_STYLE_JSON:
- /*
- * 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 (display_only) {
- if (len == 0)
- xo_format_data(xop, NULL, fmt, flen, XFF_NO_OUTPUT);
- break;
- }
- /* XXX need schem for representing errors in JSON */
+ case XO_STYLE_ENCODER:
+ if (len == 0)
+ xo_do_format_field(xop, NULL, fmt, flen,
+ flags | XFF_NO_OUTPUT);
break;
}
}
@@ -3401,7 +4221,7 @@ static const char *xo_effect_on_codes[] = {
/*
* See comment below re: joy of terminal standards. These can
* be use by just adding:
- * if (newp->xoc_effects & bit)
+ * + if (newp->xoc_effects & bit)
* code = xo_effect_on_codes[i];
* + else
* + code = xo_effect_off_codes[i];
@@ -3499,7 +4319,7 @@ xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
continue;
unknown:
- if (xop->xo_flags & XOF_WARN)
+ if (XOF_ISSET(xop, XOF_WARN))
xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
}
}
@@ -3510,7 +4330,7 @@ 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);
+ return XOF_ISSET(xop, XOF_COLOR);
#endif /* LIBXO_TEXT_ONLY */
}
@@ -3547,8 +4367,7 @@ xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
continue;
- if (newp->xoc_effects & bit)
- code = xo_effect_on_codes[i];
+ code = xo_effect_on_codes[i];
cp += snprintf(cp, ep - cp, ";%s", code);
if (cp >= ep)
@@ -3641,14 +4460,17 @@ xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
}
static void
-xo_format_colors (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON))
+ if (len != 0 && xo_style_is_encoding(xop))
return;
xo_buf_init(&xb);
@@ -3656,7 +4478,7 @@ xo_format_colors (xo_handle_t *xop, const char *str, int len,
if (len)
xo_buf_append(&xb, str, len);
else if (flen)
- xo_format_data(xop, &xb, fmt, flen, 0);
+ xo_do_format_field(xop, &xb, fmt, flen, 0);
else
xo_buf_append(&xb, "reset", 6); /* Default if empty */
@@ -3698,6 +4520,8 @@ xo_format_colors (xo_handle_t *xop, const char *str, int len,
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.
@@ -3710,14 +4534,19 @@ xo_format_colors (xo_handle_t *xop, const char *str, int len,
}
static void
-xo_format_units (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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 (!(xop->xo_flags & XOF_UNITS_PENDING)) {
- xo_format_content(xop, "units", NULL, 1, str, len, fmt, flen);
+ if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
+ xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
return;
}
@@ -3733,15 +4562,15 @@ xo_format_units (xo_handle_t *xop, const char *str, int len,
return;
if (len)
- xo_data_append(xop, str, len);
+ xo_data_escape(xop, str, len);
else
- xo_format_data(xop, NULL, fmt, flen, 0);
+ 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 */
+ if (delta <= 0) { /* Strange; no output to move */
xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
return;
}
@@ -3760,9 +4589,13 @@ xo_format_units (xo_handle_t *xop, const char *str, int len,
}
static int
-xo_find_width (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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;
@@ -3781,7 +4614,7 @@ xo_find_width (xo_handle_t *xop, const char *str, int len,
} else if (flen) {
if (flen != 2 || strncmp("%d", fmt, flen) != 0)
xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
- if (!(xop->xo_flags & XOF_NO_VA_ARG))
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
width = va_arg(xop->xo_vap, int);
}
@@ -3791,7 +4624,7 @@ xo_find_width (xo_handle_t *xop, const char *str, int len,
static void
xo_anchor_clear (xo_handle_t *xop)
{
- xop->xo_flags &= ~XOF_ANCHOR;
+ XOIF_CLEAR(xop, XOIF_ANCHOR);
xop->xo_anchor_offset = 0;
xop->xo_anchor_columns = 0;
xop->xo_anchor_min_width = 0;
@@ -3806,16 +4639,15 @@ xo_anchor_clear (xo_handle_t *xop)
* format it when the end anchor tag is seen.
*/
static void
-xo_anchor_start (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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 (xop->xo_flags & XOF_ANCHOR)
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
xo_failure(xop, "the anchor already recording is discarded");
- xop->xo_flags |= XOF_ANCHOR;
+ 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;
@@ -3824,24 +4656,23 @@ xo_anchor_start (xo_handle_t *xop, const char *str, int len,
* 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, str, len, fmt, flen);
+ xop->xo_anchor_min_width = xo_find_width(xop, xfip);
}
static void
-xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
- const char *fmt, int flen)
+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 (!(xop->xo_flags & XOF_ANCHOR)) {
+ if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
xo_failure(xop, "no start anchor");
return;
}
- xop->xo_flags &= ~XOF_UNITS_PENDING;
+ XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
- int width = xo_find_width(xop, str, len, fmt, flen);
+ int width = xo_find_width(xop, xfip);
if (width == 0)
width = xop->xo_anchor_min_width;
@@ -3866,14 +4697,14 @@ xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
/* Make a suitable padding field and emit it */
char *buf = alloca(blen);
memset(buf, ' ', blen);
- xo_format_content(xop, "padding", NULL, 1, buf, blen, NULL, 0);
+ 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 */
+ if (delta <= 0) /* Strange; no output to move */
goto done;
/*
@@ -3894,179 +4725,488 @@ xo_anchor_stop (xo_handle_t *xop, const char *str, int len,
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_do_emit (xo_handle_t *xop, const char *fmt)
+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 = 0;
- 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;
+ int rc = 1;
+ const char *cp;
- xop->xo_columns = 0; /* Always reset it */
+ for (cp = fmt; *cp; cp++)
+ if (*cp == '{' || *cp == '\n')
+ rc += 1;
- for (cp = fmt; *cp; ) {
- if (*cp == '\n') {
- xo_line_close(xop);
- if (flush_line && xo_flush_h(xop) < 0)
- return -1;
- cp += 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;
+ }
- } else if (*cp == '{') {
- if (cp[1] == '{') { /* Start of {{escaped braces}} */
+ if (*sp == ',') {
+ const char *np;
+ for (np = ++sp; *np; np++)
+ if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
+ break;
- 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", fmt);
- return -1;
+ 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);
}
+ }
- xo_format_text(xop, cp, sp - cp);
+ sp = np - 1;
+ continue;
+ }
- /* Move along the string, but don't run off the end */
- if (*sp == '}' && sp[1] == '}')
- sp += 2;
- cp = *sp ? sp + 1 : sp;
- 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;
}
- /* Else fall thru to the code below */
+ ftype = *sp;
+ break;
- } else {
- /* Normal text */
- for (sp = cp; *sp; sp++) {
- if (*sp == '{' || *sp == '\n')
- 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;
}
- xo_format_text(xop, cp, sp - cp);
+ }
+ }
- cp = sp;
+ 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;
}
- basep = cp + 1;
+ fnum += 1;
+ if (fnum >= 63)
+ break;
- /*
- * We are looking at the start of a field definition. The format is:
- * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
- * Modifiers are optional and include the following field types:
- * 'D': decoration; something non-text and non-data (colons, commmas)
- * 'E': error message
- * '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 flags are also supported:
- * 'c': flag: emit a colon after the label
- * 'd': field is only emitted for display formats (text and html)
- * 'e': field is only emitted for encoding formats (xml and json)
- * '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
- * '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'.
- */
- unsigned ftype = 0, flags = 0;
- const char *content = NULL, *format = NULL, *encoding = NULL;
- int clen = 0, flen = 0, elen = 0;
+ if (xfip->xfi_fnum)
+ bits |= 1 << xfip->xfi_fnum;
+ }
- for (sp = basep; sp; sp++) {
- if (*sp == ':' || *sp == '/' || *sp == '}')
- break;
+ max_fields = fnum;
- if (*sp == '\\') {
- if (sp[1] == '\0') {
- xo_failure(xop, "backslash at the end of string");
- return -1;
- }
- sp += 1;
- continue;
- }
+ 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;
+ }
- switch (*sp) {
- case 'C':
- case 'D':
- case 'E':
- 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",
- fmt);
- return -1;
- }
- ftype = *sp;
- break;
+ if (xfip->xfi_fnum != 0)
+ continue;
- case 'c':
- flags |= XFF_COLON;
- break;
+ /* Find the next unassigned field */
+ for (fnum++; bits & (1 << fnum); fnum++)
+ continue;
- case 'd':
- flags |= XFF_DISPLAY_ONLY;
- break;
+ if (fnum > max_fields)
+ break;
- case 'e':
- flags |= XFF_ENCODE_ONLY;
- break;
+ xfip->xfi_fnum = fnum; /* Mark the field number */
+ bits |= 1 << fnum; /* Mark it used */
+ }
+}
- case 'k':
- flags |= XFF_KEY;
- break;
+/*
+ * 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;
+ }
- case 'l':
- flags |= XFF_LEAF_LIST;
- break;
+ 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;
+ }
+ }
- case 'n':
- flags |= XFF_NOQUOTE;
- break;
+ return 0;
+}
- case 'q':
- flags |= XFF_QUOTE;
- break;
+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;
- case 't':
- flags |= XFF_TRIM_WS;
- break;
+ for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
+ xfip->xfi_start = cp;
- case 'w':
- flags |= XFF_WS;
- break;
+ if (*cp == '\n') {
+ xfip->xfi_ftype = XO_ROLE_NEWLINE;
+ xfip->xfi_len = 1;
+ cp += 1;
+ continue;
+ }
- default:
- xo_failure(xop, "field descriptor uses unknown modifier: %s",
- 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.
- */
+ 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 == '/')
@@ -4081,14 +5221,15 @@ xo_do_emit (xo_handle_t *xop, const char *fmt)
}
}
if (ep != sp) {
- clen = sp - ep;
- content = ep;
+ xfip->xfi_clen = sp - ep;
+ xfip->xfi_content = ep;
}
} else {
- xo_failure(xop, "missing content (':'): %s", fmt);
+ 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 == '/')
@@ -4106,95 +5247,696 @@ xo_do_emit (xo_handle_t *xop, const char *fmt)
format = ep;
}
+ /* Looking at encoding format */
if (*sp == '/') {
for (ep = ++sp; *sp; sp++) {
if (*sp == '}')
break;
}
- elen = sp - ep;
- encoding = ep;
+
+ xfip->xfi_encoding = ep;
+ xfip->xfi_elen = sp - ep;
}
- if (*sp == '}') {
- sp += 1;
- } else {
- xo_failure(xop, "missing closing '}': %s", fmt);
+ if (*sp != '}') {
+ xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
return -1;
}
- if (ftype == 0 || ftype == 'V') {
- if (format == NULL) {
- /* Default format for value fields is '%s' */
- format = "%s";
- flen = 2;
+ 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" */
- xo_format_value(xop, content, clen, format, flen,
- encoding, elen, flags);
+ unsigned max_fields = xo_count_fields(xop, fmt), field;
+ xo_field_info_t fields[max_fields], *xfip;
- } else if (ftype == '[')
- xo_anchor_start(xop, content, clen, format, flen);
+ 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, content, clen, format, flen);
+ xo_anchor_stop(xop, xfip);
else if (ftype == 'C')
- xo_format_colors(xop, content, clen, format, flen);
+ 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;
+ }
- else if (clen || format) { /* Need either content or format */
- if (format == NULL) {
- /* Default format for value fields is '%s' */
- format = "%s";
- flen = 2;
+ 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;
- 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 (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, 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);
+ 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, ":", 1, NULL, 0);
- if (ftype != 'U' && (flags & XFF_WS))
- xo_format_content(xop, "padding", NULL, 1, " ", 1, NULL, 0);
+ xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
- cp += sp - basep + 1;
- if (newp) {
- xo_free(newp);
- newp = NULL;
+ 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 && !(xop->xo_flags & XOF_ANCHOR)) {
+ 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)
{
@@ -4243,35 +5985,55 @@ 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 (xo_style(xop) != XO_STYLE_XML)
- return 0;
-
+ int rc = 0;
int nlen = strlen(name);
xo_buffer_t *xbp = &xop->xo_attrs;
+ unsigned name_offset, value_offset;
- if (!xo_buf_has_room(xbp, nlen + extra))
- return -1;
+ 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++ = '"';
+ *xbp->xb_curp++ = ' ';
+ memcpy(xbp->xb_curp, name, nlen);
+ xbp->xb_curp += nlen;
+ *xbp->xb_curp++ = '=';
+ *xbp->xb_curp++ = '"';
- int rc = xo_vsnprintf(xop, xbp, fmt, vap);
+ rc = xo_vsnprintf(xop, xbp, fmt, vap);
- if (rc > 0) {
- rc = xo_escape_xml(xbp, rc, 1);
- xbp->xb_curp += rc;
- }
+ if (rc >= 0) {
+ rc = xo_escape_xml(xbp, rc, 1);
+ xbp->xb_curp += rc;
+ }
- if (!xo_buf_has_room(xbp, 2))
- return -1;
+ if (!xo_buf_has_room(xbp, 2))
+ return -1;
+
+ *xbp->xb_curp++ = '"';
+ *xbp->xb_curp = '\0';
- *xbp->xb_curp++ = '"';
- *xbp->xb_curp = '\0';
+ rc += nlen + extra;
+ break;
- return rc + nlen + extra;
+ 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
@@ -4303,11 +6065,11 @@ xo_attr (const char *name, const char *fmt, ...)
static void
xo_stack_set_flags (xo_handle_t *xop)
{
- if (xop->xo_flags & XOF_NOT_FIRST) {
+ if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
xsp->xs_flags |= XSF_NOT_FIRST;
- xop->xo_flags &= ~XOF_NOT_FIRST;
+ XOF_CLEAR(xop, XOF_NOT_FIRST);
}
}
@@ -4318,7 +6080,7 @@ xo_depth_change (xo_handle_t *xop, const char *name,
if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
indent = 0;
- if (xop->xo_flags & XOF_DTRT)
+ if (XOF_ISSET(xop, XOF_DTRT))
flags |= XSF_DTRT;
if (delta >= 0) { /* Push operation */
@@ -4333,22 +6095,17 @@ xo_depth_change (xo_handle_t *xop, const char *name,
if (name == NULL)
name = XO_FAILURE_NAME;
- int len = strlen(name) + 1;
- char *cp = xo_realloc(NULL, len);
- if (cp) {
- memcpy(cp, name, len);
- xsp->xs_name = cp;
- }
+ xsp->xs_name = xo_strndup(name, -1);
} else { /* Pop operation */
if (xop->xo_depth == 0) {
- if (!(xop->xo_flags & XOF_IGNORE_CLOSE))
+ 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 (xop->xo_flags & XOF_WARN) {
+ 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'",
@@ -4405,7 +6162,7 @@ static void
xo_emit_top (xo_handle_t *xop, const char *ppn)
{
xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
- xop->xo_flags |= XOF_TOP_EMITTED;
+ XOIF_SET(xop, XOIF_TOP_EMITTED);
if (xop->xo_version) {
xo_printf(xop, "%*s\"__version\": \"%s\", %s",
@@ -4419,7 +6176,7 @@ 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 *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
const char *pre_nl = "";
if (name == NULL) {
@@ -4446,17 +6203,24 @@ xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
case XO_STYLE_JSON:
xo_stack_set_flags(xop);
- if (!(xop->xo_flags & XOF_NO_TOP)
- && !(xop->xo_flags & XOF_TOP_EMITTED))
+ 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 = (xop->xo_flags & XOF_PRETTY) ? ",\n" : ", ";
+ 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,
@@ -4501,7 +6265,7 @@ xo_do_close_container (xo_handle_t *xop, const char *name)
xop = xo_default(xop);
int rc = 0;
- const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
const char *pre_nl = "";
if (name == NULL) {
@@ -4527,7 +6291,7 @@ xo_do_close_container (xo_handle_t *xop, const char *name)
break;
case XO_STYLE_JSON:
- pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ 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);
@@ -4539,6 +6303,14 @@ xo_do_close_container (xo_handle_t *xop, const char *name)
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;
@@ -4576,13 +6348,15 @@ xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xop = xo_default(xop);
- if (xo_style(xop) == XO_STYLE_JSON) {
- const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
- const char *pre_nl = "";
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
indent = 1;
- if (!(xop->xo_flags & XOF_NO_TOP)
- && !(xop->xo_flags & XOF_TOP_EMITTED))
+ if (!XOF_ISSET(xop, XOF_NO_TOP)
+ && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
xo_emit_top(xop, ppn);
if (name == NULL) {
@@ -4593,11 +6367,16 @@ xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *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" : ", ";
+ 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,
@@ -4658,18 +6437,26 @@ xo_do_close_list (xo_handle_t *xop, const char *name)
}
}
- if (xo_style(xop) == XO_STYLE_JSON) {
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
- pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ 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;
- } else {
+ 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;
@@ -4707,16 +6494,17 @@ xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
xop = xo_default(xop);
- if (xo_style(xop) == XO_STYLE_JSON) {
- const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
- const char *pre_nl = "";
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
indent = 1;
- if (!(xop->xo_flags & XOF_NO_TOP)) {
- if (!(xop->xo_flags & XOF_TOP_EMITTED)) {
+ if (!XOF_ISSET(xop, XOF_NO_TOP)) {
+ if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
- xop->xo_flags |= XOF_TOP_EMITTED;
+ XOIF_SET(xop, XOIF_TOP_EMITTED);
}
}
@@ -4728,11 +6516,16 @@ xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *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" : ", ";
+ 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,
@@ -4763,18 +6556,25 @@ xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
}
}
- if (xo_style(xop) == XO_STYLE_JSON) {
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
- pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ 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;
- } else {
+ 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;
@@ -4786,7 +6586,7 @@ 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 = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
const char *pre_nl = "";
flags |= xop->xo_flags;
@@ -4814,12 +6614,19 @@ xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *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" : ", ";
+ 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));
@@ -4863,7 +6670,7 @@ xo_do_close_instance (xo_handle_t *xop, const char *name)
xop = xo_default(xop);
int rc = 0;
- const char *ppn = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
const char *pre_nl = "";
if (name == NULL) {
@@ -4889,7 +6696,7 @@ xo_do_close_instance (xo_handle_t *xop, const char *name)
break;
case XO_STYLE_JSON:
- pre_nl = (xop->xo_flags & XOF_PRETTY) ? "\n" : "";
+ 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), "");
@@ -4900,6 +6707,14 @@ xo_do_close_instance (xo_handle_t *xop, const char *name)
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;
@@ -5338,6 +7153,10 @@ 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)
@@ -5367,14 +7186,17 @@ xo_flush_h (xo_handle_t *xop)
switch (xo_style(xop)) {
case XO_STYLE_HTML:
- if (xop->xo_flags & XOF_DIV_OPEN) {
- xop->xo_flags &= ~XOF_DIV_OPEN;
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
+ XOIF_CLEAR(xop, XOIF_DIV_OPEN);
xo_data_append(xop, div_close, sizeof(div_close) - 1);
- if (xop->xo_flags & XOF_PRETTY)
+ 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);
@@ -5397,19 +7219,23 @@ xo_finish_h (xo_handle_t *xop)
const char *cp = "";
xop = xo_default(xop);
- if (!(xop->xo_flags & XOF_NO_CLOSE))
+ if (!XOF_ISSET(xop, 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)
- xop->xo_flags &= ~XOF_TOP_EMITTED; /* Turn off before output */
+ 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);
@@ -5422,6 +7248,16 @@ xo_finish (void)
}
/*
+ * 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
@@ -5452,7 +7288,7 @@ xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
- if (xop->xo_flags & XOF_DIV_OPEN)
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
xo_line_close(xop);
xo_write(xop);
@@ -5472,6 +7308,10 @@ xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
va_end(xop->xo_vap);
bzero(&xop->xo_vap, sizeof(xop->xo_vap));
break;
+
+ case XO_STYLE_SDPARAMS:
+ case XO_STYLE_ENCODER:
+ break;
}
}
@@ -5498,6 +7338,12 @@ xo_error (const char *fmt, ...)
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)
{
@@ -5557,6 +7403,10 @@ xo_parse_args (int argc, char **argv)
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)
{
@@ -5575,6 +7425,9 @@ xo_dump_stack (xo_handle_t *xop)
}
}
+/*
+ * Record the program name used for error messages
+ */
void
xo_set_program (const char *name)
{
@@ -5589,6 +7442,9 @@ xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
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 */
@@ -5596,153 +7452,208 @@ xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
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);
- }
+ /*
+ * 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);
}
-#ifdef UNIT_TEST
-int
-main (int argc, char **argv)
-{
- static char base_grocery[] = "GRO";
- static char base_hardware[] = "HRD";
- struct item {
- const char *i_title;
- int i_sold;
- int i_instock;
- int i_onorder;
- const char *i_sku_base;
- int i_sku_num;
- };
- struct item list[] = {
- { "gum&this&that", 1412, 54, 10, base_grocery, 415 },
- { "<rope>", 85, 4, 2, base_hardware, 212 },
- { "ladder", 0, 2, 1, base_hardware, 517 },
- { "\"bolt\"", 4123, 144, 42, base_hardware, 632 },
- { "water\\blue", 17, 14, 2, base_grocery, 2331 },
- { NULL, 0, 0, 0, NULL, 0 }
- };
- struct item list2[] = {
- { "fish", 1321, 45, 1, base_grocery, 533 },
- { NULL, 0, 0, 0, NULL, 0 }
- };
- struct item *ip;
- xo_info_t info[] = {
- { "in-stock", "number", "Number of items in stock" },
- { "name", "string", "Name of the item" },
- { "on-order", "number", "Number of items on order" },
- { "sku", "string", "Stock Keeping Unit" },
- { "sold", "number", "Number of items sold" },
- { NULL, NULL, NULL },
- };
- int info_count = (sizeof(info) / sizeof(info[0])) - 1;
-
- argc = xo_parse_args(argc, argv);
- if (argc < 0)
- exit(1);
+/*
+ * 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);
- xo_set_info(NULL, info, info_count);
+ if (fmt == NULL)
+ return;
- xo_open_container_h(NULL, "top");
+ xo_open_marker_h(xop, "xo_emit_warn_hcv");
+ xo_open_container_h(xop, as_warning ? "__warning" : "__error");
- xo_open_container("data");
- xo_open_list("item");
+ if (xo_program)
+ xo_emit("{wc:program}", xo_program);
- xo_emit("{T:Item/%-15s}{T:Total Sold/%12s}{T:In Stock/%12s}"
- "{T:On Order/%12s}{T:SKU/%5s}\n");
+ if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
+ va_list ap;
+ xo_handle_t temp;
- for (ip = list; ip->i_title; ip++) {
- xo_open_instance("item");
+ bzero(&temp, sizeof(temp));
+ temp.xo_style = XO_STYLE_TEXT;
+ xo_buf_init(&temp.xo_data);
+ xo_depth_check(&temp, XO_DEPTH);
- xo_emit("{k:name/%-15s/%s}{n:sold/%12u/%u}{:in-stock/%12u/%u}"
- "{:on-order/%12u/%u} {q:sku/%5s-000-%u/%s-000-%u}\n",
- ip->i_title, ip->i_sold, ip->i_instock, ip->i_onorder,
- ip->i_sku_base, ip->i_sku_num);
+ va_copy(ap, vap);
+ (void) xo_emit_hv(&temp, fmt, ap);
+ va_end(ap);
- xo_close_instance("item");
+ 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);
}
- xo_close_list("item");
- xo_close_container("data");
+ (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_emit("\n\n");
+ xo_close_marker_h(xop, "xo_emit_warn_hcv");
+ xo_flush_h(xop);
+}
- xo_open_container("data");
- xo_open_list("item");
+void
+xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
+{
+ va_list vap;
- for (ip = list; ip->i_title; ip++) {
- xo_open_instance("item");
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(xop, 1, code, fmt, vap);
+ va_end(vap);
+}
- xo_attr("fancy", "%s%d", "item", ip - list);
- xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
- xo_emit("{P: }{L:Total sold}: {n:sold/%u%s}{e:percent/%u}\n",
- ip->i_sold, ip->i_sold ? ".0" : "", 44);
- xo_emit("{P: }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
- xo_emit("{P: }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
- xo_emit("{P: }{L:SKU}: {q:sku/%s-000-%u}\n",
- ip->i_sku_base, ip->i_sku_num);
+void
+xo_emit_warn_c (int code, const char *fmt, ...)
+{
+ va_list vap;
- xo_close_instance("item");
- }
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
+ va_end(vap);
+}
- xo_close_list("item");
- xo_close_container("data");
+void
+xo_emit_warn (const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
- xo_open_container("data");
- xo_open_list("item");
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
+ va_end(vap);
+}
- for (ip = list2; ip->i_title; ip++) {
- xo_open_instance("item");
+void
+xo_emit_warnx (const char *fmt, ...)
+{
+ va_list vap;
- xo_emit("{L:Item} '{k:name/%s}':\n", ip->i_title);
- xo_emit("{P: }{L:Total sold}: {n:sold/%u%s}\n",
- ip->i_sold, ip->i_sold ? ".0" : "");
- xo_emit("{P: }{Lcw:In stock}{:in-stock/%u}\n", ip->i_instock);
- xo_emit("{P: }{Lcw:On order}{:on-order/%u}\n", ip->i_onorder);
- xo_emit("{P: }{L:SKU}: {q:sku/%s-000-%u}\n",
- ip->i_sku_base, ip->i_sku_num);
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
+ va_end(vap);
+}
- xo_open_list("month");
+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);
+}
- const char *months[] = { "Jan", "Feb", "Mar", NULL };
- int discounts[] = { 10, 20, 25, 0 };
- int i;
- for (i = 0; months[i]; i++) {
- xo_open_instance("month");
- xo_emit("{P: }"
- "{Lwc:Month}{k:month}, {Lwc:Special}{:discount/%d}\n",
- months[i], discounts[i]);
- xo_close_instance("month");
- }
-
- xo_close_list("month");
+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);
+}
- xo_close_instance("item");
- }
+void
+xo_emit_errx (int eval, const char *fmt, ...)
+{
+ va_list vap;
- xo_close_list("item");
- xo_close_container("data");
+ va_start(vap, fmt);
+ xo_emit_err_v(0, -1, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
- xo_close_container_h(NULL, "top");
+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);
+}
- return 0;
+/*
+ * 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;
}
-#endif /* UNIT_TEST */
diff --git a/contrib/libxo/libxo/xo.h b/contrib/libxo/libxo/xo.h
index c065740..88bcce2 100644
--- a/contrib/libxo/libxo/xo.h
+++ b/contrib/libxo/libxo/xo.h
@@ -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
@@ -12,12 +12,19 @@
* libxo provides a means of generating text, XML, JSON, and HTML output
* using a single set of function calls, maximizing the value of output
* while minimizing the cost/impact on the code.
+ *
+ * Full documentation is available in ./doc/libxo.txt or online at:
+ * http://juniper.github.io/libxo/libxo-manual.html
*/
#ifndef INCLUDE_XO_H
#define INCLUDE_XO_H
+#include <stdio.h>
#include <sys/types.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <errno.h>
#ifdef __dead2
#define NORETURN __dead2
@@ -39,19 +46,21 @@
#endif /* NO_PRINTFLIKE */
/** Formatting types */
-typedef unsigned xo_style_t;
+typedef unsigned short xo_style_t;
#define XO_STYLE_TEXT 0 /** Generate text output */
#define XO_STYLE_XML 1 /** Generate XML output */
#define XO_STYLE_JSON 2 /** Generate JSON output */
#define XO_STYLE_HTML 3 /** Generate HTML output */
+#define XO_STYLE_SDPARAMS 4 /* Generate syslog structured data params */
+#define XO_STYLE_ENCODER 5 /* Generate calls to external encoder */
/** Flags for libxo */
typedef unsigned long long xo_xof_flags_t;
#define XOF_BIT(_n) ((xo_xof_flags_t) 1 << (_n))
#define XOF_CLOSE_FP XOF_BIT(0) /** Close file pointer on xo_close() */
#define XOF_PRETTY XOF_BIT(1) /** Make 'pretty printed' output */
-#define XOF_DIV_OPEN XOF_BIT(2) /** Internal use only: a <div> is open */
-#define XOF_LINE_OPEN XOF_BIT(3) /** Internal use only: <div class="line"> */
+#define XOF_LOG_SYSLOG XOF_BIT(2) /** Log (on stderr) our syslog content */
+#define XOF_RESV3 XOF_BIT(3) /* Unused */
#define XOF_WARN XOF_BIT(4) /** Generate warnings for broken calls */
#define XOF_XPATH XOF_BIT(5) /** Emit XPath attributes in HTML */
@@ -66,12 +75,12 @@ typedef unsigned long long xo_xof_flags_t;
#define XOF_IGNORE_CLOSE XOF_BIT(12) /** Ignore errors on close tags */
#define XOF_NOT_FIRST XOF_BIT(13) /* Not the first item (JSON) */
#define XOF_NO_LOCALE XOF_BIT(14) /** Don't bother with locale */
-#define XOF_TOP_EMITTED XOF_BIT(15) /* The top JSON braces have been emitted */
+#define XOF_RESV15 XOF_BIT(15) /* Unused */
#define XOF_NO_TOP XOF_BIT(16) /** Don't emit the top braces in JSON */
-#define XOF_ANCHOR XOF_BIT(17) /** An anchor is in place */
+#define XOF_RESV17 XOF_BIT(17) /* Unused */
#define XOF_UNITS XOF_BIT(18) /** Encode units in XML */
-#define XOF_UNITS_PENDING XOF_BIT(19) /** We have a units-insertion pending */
+#define XOF_RESV19 XOF_BIT(19) /* Unused */
#define XOF_UNDERSCORES XOF_BIT(20) /** Replace dashes with underscores (JSON)*/
#define XOF_COLUMNS XOF_BIT(21) /** xo_emit should return a column count */
@@ -81,6 +90,10 @@ typedef unsigned long long xo_xof_flags_t;
#define XOF_NO_CLOSE XOF_BIT(24) /** xo_finish won't close open elements */
#define XOF_COLOR_ALLOWED XOF_BIT(25) /** Allow color/effects to be enabled */
#define XOF_COLOR XOF_BIT(26) /** Enable color and effects */
+#define XOF_NO_HUMANIZE XOF_BIT(27) /** Block the {h:} modifier */
+
+#define XOF_LOG_GETTEXT XOF_BIT(28) /** Log (stderr) gettext lookup strings */
+#define XOF_UTF8 XOF_BIT(29) /** Force text output to be UTF8 */
/*
* The xo_info_t structure provides a mapping between names and
@@ -92,6 +105,8 @@ typedef struct xo_info_s {
const char *xi_help; /* Description of field */
} xo_info_t;
+#define XO_INFO_NULL NULL, NULL, NULL /* Use '{ XO_INFO_NULL }' to end lists */
+
struct xo_handle_s; /* Opaque structure forward */
typedef struct xo_handle_s xo_handle_t; /* Handle for XO output */
@@ -165,6 +180,35 @@ xo_emit_h (xo_handle_t *xop, const char *fmt, ...);
int
xo_emit (const char *fmt, ...);
+PRINTFLIKE(2, 0)
+static inline int
+xo_emit_hvp (xo_handle_t *xop, const char *fmt, va_list vap)
+{
+ return xo_emit_hv(xop, fmt, vap);
+}
+
+PRINTFLIKE(2, 3)
+static inline int
+xo_emit_hp (xo_handle_t *xop, const char *fmt, ...)
+{
+ va_list vap;
+ va_start(vap, fmt);
+ int rc = xo_emit_hv(xop, fmt, vap);
+ va_end(vap);
+ return rc;
+}
+
+PRINTFLIKE(1, 2)
+static inline int
+xo_emit_p (const char *fmt, ...)
+{
+ va_list vap;
+ va_start(vap, fmt);
+ int rc = xo_emit_hv(NULL, fmt, vap);
+ va_end(vap);
+ return rc;
+}
+
int
xo_open_container_h (xo_handle_t *xop, const char *name);
@@ -280,6 +324,9 @@ int
xo_finish (void);
void
+xo_finish_atexit (void);
+
+void
xo_set_leading_xpath (xo_handle_t *xop, const char *path);
void
@@ -304,7 +351,7 @@ void
xo_errc (int eval, int code, const char *fmt, ...) NORETURN PRINTFLIKE(3, 4);
void
-xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap);
+xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap) PRINTFLIKE(3, 0);
void
xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...) PRINTFLIKE(3, 4);
@@ -313,9 +360,124 @@ void
xo_message_c (int code, const char *fmt, ...) PRINTFLIKE(2, 3);
void
+xo_message_e (const char *fmt, ...) PRINTFLIKE(1, 2);
+
+void
xo_message (const char *fmt, ...) PRINTFLIKE(1, 2);
void
+xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
+ const char *fmt, va_list vap);
+
+void
+xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...);
+
+void
+xo_emit_warn_c (int code, const char *fmt, ...);
+
+void
+xo_emit_warn (const char *fmt, ...);
+
+void
+xo_emit_warnx (const char *fmt, ...);
+
+void
+xo_emit_err (int eval, const char *fmt, ...) NORETURN;
+
+void
+xo_emit_errx (int eval, const char *fmt, ...) NORETURN;
+
+void
+xo_emit_errc (int eval, int code, const char *fmt, ...) NORETURN;
+
+PRINTFLIKE(4, 0)
+static inline void
+xo_emit_warn_hcvp (xo_handle_t *xop, int as_warning, int code,
+ const char *fmt, va_list vap)
+{
+ xo_emit_warn_hcv(xop, as_warning, code, fmt, vap);
+}
+
+PRINTFLIKE(3, 4)
+static inline void
+xo_emit_warn_hcp (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);
+}
+
+PRINTFLIKE(2, 3)
+static inline void
+xo_emit_warn_cp (int code, const char *fmt, ...)
+{
+ va_list vap;
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
+ va_end(vap);
+}
+
+PRINTFLIKE(1, 2)
+static inline void
+xo_emit_warn_p (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);
+}
+
+PRINTFLIKE(1, 2)
+static inline void
+xo_emit_warnx_p (const char *fmt, ...)
+{
+ va_list vap;
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
+ va_end(vap);
+}
+
+NORETURN PRINTFLIKE(2, 3)
+static inline void
+xo_emit_err_p (int eval, const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
+ va_end(vap);
+
+ exit(eval);
+}
+
+PRINTFLIKE(2, 3)
+static inline void
+xo_emit_errx_p (int eval, const char *fmt, ...)
+{
+ va_list vap;
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 0, -1, fmt, vap);
+ va_end(vap);
+ exit(eval);
+}
+
+PRINTFLIKE(3, 4)
+static inline void
+xo_emit_errc_p (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);
+ exit(eval);
+}
+
+void
+xo_emit_err_v (int eval, int code, const char *fmt, va_list vap) NORETURN PRINTFLIKE(3, 0);
+
+void
xo_no_setlocale (void);
/**
@@ -395,4 +557,40 @@ xo_set_version (const char *version);
void
xo_set_version_h (xo_handle_t *xop, const char *version);
+void
+xo_open_log (const char *ident, int logopt, int facility);
+
+void
+xo_close_log (void);
+
+int
+xo_set_logmask (int maskpri);
+
+void
+xo_set_unit_test_mode (int value);
+
+void
+xo_syslog (int priority, const char *name, const char *message, ...);
+
+void
+xo_vsyslog (int priority, const char *name, const char *message, va_list args);
+
+typedef void (*xo_syslog_open_t)(void);
+typedef void (*xo_syslog_send_t)(const char *full_msg,
+ const char *v0_hdr, const char *text_only);
+typedef void (*xo_syslog_close_t)(void);
+
+void
+xo_set_syslog_handler (xo_syslog_open_t open_func, xo_syslog_send_t send_func,
+ xo_syslog_close_t close_func);
+
+void
+xo_set_syslog_enterprise_id (unsigned short eid);
+
+typedef void (*xo_simplify_field_func_t)(const char *, unsigned, int);
+
+char *
+xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
+ xo_simplify_field_func_t field_cb);
+
#endif /* INCLUDE_XO_H */
diff --git a/contrib/libxo/libxo/xo_attr.3 b/contrib/libxo/libxo/xo_attr.3
index 1c18360..c71377f 100644
--- a/contrib/libxo/libxo/xo_attr.3
+++ b/contrib/libxo/libxo/xo_attr.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_attr , xo_attr_h , xo_attr_hv
+.Nd Add attribute name/value pairs to formatted output
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -55,27 +55,6 @@ parameter as passed to
Since attributes are only emitted in XML, their use should be limited
to meta-data and additional or redundant representations of data
already emitted in other form.
-.Sh ADDITIONAL DOCUMENTATION
-.Pp
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-libxo lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of libxo is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Fa libxo
-library was added in FreeBSD 11.0.
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_buf.h b/contrib/libxo/libxo/xo_buf.h
new file mode 100644
index 0000000..349e9ad
--- /dev/null
+++ b/contrib/libxo/libxo/xo_buf.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 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, August 2015
+ */
+
+/*
+ * This file is an _internal_ part of the libxo plumbing, not suitable
+ * for external use. It is not considered part of the libxo API and
+ * will not be a stable part of that API. Mine, not your's, dude...
+ * The real hope is that something like this will become a standard part
+ * of libc and I can kill this off.
+ */
+
+#ifndef XO_BUF_H
+#define XO_BUF_H
+
+#define XO_BUFSIZ (8*1024) /* Initial buffer size */
+
+/*
+ * xo_buffer_t: a memory buffer that can be grown as needed. We
+ * use them for building format strings and output data.
+ */
+typedef struct xo_buffer_s {
+ char *xb_bufp; /* Buffer memory */
+ char *xb_curp; /* Current insertion point */
+ unsigned xb_size; /* Size of buffer */
+} xo_buffer_t;
+
+/*
+ * Initialize the contents of an xo_buffer_t.
+ */
+static inline void
+xo_buf_init (xo_buffer_t *xbp)
+{
+ xbp->xb_size = XO_BUFSIZ;
+ xbp->xb_bufp = xo_realloc(NULL, xbp->xb_size);
+ xbp->xb_curp = xbp->xb_bufp;
+}
+
+/*
+ * Reset the buffer to empty
+ */
+static inline void
+xo_buf_reset (xo_buffer_t *xbp)
+{
+ xbp->xb_curp = xbp->xb_bufp;
+}
+
+/*
+ * Return the number of bytes left in the buffer
+ */
+static inline int
+xo_buf_left (xo_buffer_t *xbp)
+{
+ return xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+}
+
+/*
+ * See if the buffer to empty
+ */
+static inline int
+xo_buf_is_empty (xo_buffer_t *xbp)
+{
+ return (xbp->xb_curp == xbp->xb_bufp);
+}
+
+/*
+ * Return the current offset
+ */
+static inline unsigned
+xo_buf_offset (xo_buffer_t *xbp)
+{
+ return xbp ? (xbp->xb_curp - xbp->xb_bufp) : 0;
+}
+
+static inline char *
+xo_buf_data (xo_buffer_t *xbp, unsigned offset)
+{
+ if (xbp == NULL)
+ return NULL;
+ return xbp->xb_bufp + offset;
+}
+
+static inline char *
+xo_buf_cur (xo_buffer_t *xbp)
+{
+ if (xbp == NULL)
+ return NULL;
+ return xbp->xb_curp;
+}
+
+/*
+ * Initialize the contents of an xo_buffer_t.
+ */
+static inline void
+xo_buf_cleanup (xo_buffer_t *xbp)
+{
+ if (xbp->xb_bufp)
+ xo_free(xbp->xb_bufp);
+ bzero(xbp, sizeof(*xbp));
+}
+
+/*
+ * Does the buffer have room for the given number of bytes of data?
+ * If not, realloc the buffer to make room. If that fails, we
+ * return 0 to tell the caller they are in trouble.
+ */
+static inline int
+xo_buf_has_room (xo_buffer_t *xbp, int len)
+{
+ if (xbp->xb_curp + len >= xbp->xb_bufp + xbp->xb_size) {
+ int sz = xbp->xb_size + XO_BUFSIZ;
+ char *bp = xo_realloc(xbp->xb_bufp, sz);
+ if (bp == NULL)
+ return 0;
+
+ xbp->xb_curp = bp + (xbp->xb_curp - xbp->xb_bufp);
+ xbp->xb_bufp = bp;
+ xbp->xb_size = sz;
+ }
+
+ return 1;
+}
+
+/*
+ * Append the given string to the given buffer
+ */
+static inline void
+xo_buf_append (xo_buffer_t *xbp, const char *str, int len)
+{
+ if (!xo_buf_has_room(xbp, len))
+ return;
+
+ memcpy(xbp->xb_curp, str, len);
+ xbp->xb_curp += len;
+}
+
+/*
+ * Append the given NUL-terminated string to the given buffer
+ */
+static inline 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;
+}
+
+#endif /* XO_BUF_H */
diff --git a/contrib/libxo/libxo/xoconfig.h b/contrib/libxo/libxo/xo_config.h
index dd1823e..9177962 100644
--- a/contrib/libxo/libxo/xoconfig.h
+++ b/contrib/libxo/libxo/xo_config.h
@@ -1,5 +1,5 @@
-/* libxo/xoconfig.h. Generated from xoconfig.h.in by configure. */
-/* libxo/xoconfig.h.in. Generated from configure.ac by autoheader. */
+/* libxo/xo_config.h. Generated from xo_config.h.in by configure. */
+/* libxo/xo_config.h.in. Generated from configure.ac by autoheader. */
/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
systems. This function is required for `alloca.c' support on those systems.
@@ -28,6 +28,10 @@
/* Define to 1 if you have the <ctype.h> header file. */
#define HAVE_CTYPE_H 1
+/* Define to 1 if you have the declaration of `__isthreaded', and to 0 if you
+ don't. */
+#define HAVE_DECL___ISTHREADED 1
+
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
@@ -46,12 +50,21 @@
/* Define to 1 if you have the `getpass' function. */
#define HAVE_GETPASS 1
+/* Define to 1 if you have the `getprogname' function. */
+#define HAVE_GETPROGNAME 1
+
/* Define to 1 if you have the `getrusage' function. */
#define HAVE_GETRUSAGE 1
+/* gettext(3) */
+/* #undef HAVE_GETTEXT */
+
/* Define to 1 if you have the `gettimeofday' function. */
#define HAVE_GETTIMEOFDAY 1
+/* humanize_number(3) */
+#define HAVE_HUMANIZE_NUMBER 1
+
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
@@ -61,6 +74,9 @@
/* Define to 1 if you have the `m' library (-lm). */
#define HAVE_LIBM 1
+/* Define to 1 if you have the <libutil.h> header file. */
+#define HAVE_LIBUTIL_H 1
+
/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
to 0 otherwise. */
#define HAVE_MALLOC 1
@@ -120,6 +136,9 @@
/* Define to 1 if you have the `strspn' function. */
#define HAVE_STRSPN 1
+/* Have struct sockaddr_un.sun_len */
+#define HAVE_SUN_LEN 1
+
/* Define to 1 if you have the `sysctlbyname' function. */
#define HAVE_SYSCTLBYNAME 1
@@ -138,6 +157,12 @@
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
+/* Define to 1 if you have the <threads.h> header file. */
+#define HAVE_THREADS_H 1
+
+/* thread-local setting */
+#define HAVE_THREAD_LOCAL THREAD_LOCAL_before
+
/* Define to 1 if you have the <tzfile.h> header file. */
/* #undef HAVE_TZFILE_H */
@@ -153,6 +178,21 @@
/* Enable text-only rendering */
/* #undef LIBXO_TEXT_ONLY */
+/* Version number as dotted value */
+#define LIBXO_VERSION "0.4.3"
+
+/* Version number extra information */
+#define LIBXO_VERSION_EXTRA ""
+
+/* Version number as a number */
+#define LIBXO_VERSION_NUMBER 4003
+
+/* Version number as string */
+#define LIBXO_VERSION_STRING "4003"
+
+/* Enable local wcwidth implementation */
+#define LIBXO_WCWIDTH 1
+
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#define LT_OBJDIR ".libs/"
@@ -166,7 +206,7 @@
#define PACKAGE_NAME "libxo"
/* Define to the full name and version of this package. */
-#define PACKAGE_STRING "libxo 0.3.2"
+#define PACKAGE_STRING "libxo 0.4.3"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "libxo"
@@ -175,7 +215,7 @@
#define PACKAGE_URL ""
/* Define to the version of this package. */
-#define PACKAGE_VERSION "0.3.2"
+#define PACKAGE_VERSION "0.4.3"
/* If using the C implementation of alloca, define if you know the
direction of stack growth for your system; otherwise it will be
@@ -189,7 +229,7 @@
#define STDC_HEADERS 1
/* Version number of package */
-#define VERSION "0.3.2"
+#define VERSION "0.4.3"
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
diff --git a/contrib/libxo/libxo/xo_create.3 b/contrib/libxo/libxo/xo_create.3
index b0e8965..bfbadc4 100644
--- a/contrib/libxo/libxo/xo_create.3
+++ b/contrib/libxo/libxo/xo_create.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_create , xo_create_to_file , xo_destroy
+.Nd create and destroy libxo output handles
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -61,31 +61,7 @@ with a
.Dv NULL
handle will release any
resources associated with the default handle.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr xo_emit 3 ,
-.Xr xo_set_options 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_set_options 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_emit.3 b/contrib/libxo/libxo/xo_emit.3
index 7060824..155ea75 100644
--- a/contrib/libxo/libxo/xo_emit.3
+++ b/contrib/libxo/libxo/xo_emit.3
@@ -11,7 +11,7 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
+.Nm xo_emit , xo_emit_h , xo_emit_hv
.Nd emit formatted output based on format string and arguments
.Sh LIBRARY
.Lb libxo
@@ -43,30 +43,62 @@ uses an explicit handle.
accepts a
.Fa va_list
for additional flexibility.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
+.Sh EXAMPLES
+In this example, a set of four values is emitted using the following
+source code:
+.Bd -literal -offset indent
+ xo_emit(" {:lines/%7ju} {:words/%7ju} "
+ "{:characters/%7ju} {d:filename/%s}\n",
+ linect, wordct, charct, file);
.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of libxo is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
+Output can then be generated in various style, using
+the "--libxo" option:
+.Bd -literal -offset indent
+ % wc /etc/motd
+ 25 165 1140 /etc/motd
+ % wc --libxo xml,pretty,warn /etc/motd
+ <wc>
+ <file>
+ <lines>25</lines>
+ <words>165</words>
+ <characters>1140</characters>
+ <filename>/etc/motd</filename>
+ </file>
+ </wc>
+ % wc --libxo json,pretty,warn /etc/motd
+ {
+ "wc": {
+ "file": [
+ {
+ "lines": 25,
+ "words": 165,
+ "characters": 1140,
+ "filename": "/etc/motd"
+ }
+ ]
+ }
+ }
+ % wc --libxo html,pretty,warn /etc/motd
+ <div class="line">
+ <div class="text"> </div>
+ <div class="data" data-tag="lines"> 25</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="words"> 165</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="characters"> 1140</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="filename">/etc/motd</div>
+ </div>
.Ed
+.Sh RETURN CODE
+.Nm
+returns a negative value on error. If the
+.Nm XOF_COLUMNS
+flag has been turned on for the specific handle using
+.Xr xo_set_flags 3 ,
+then the number of display columns consumed by the output will be returned.
.Sh SEE ALSO
.Xr xo_open_container 3 ,
.Xr xo_open_list 3 ,
-.Xr xo_format 5
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_format 5 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_emit_err.3 b/contrib/libxo/libxo/xo_emit_err.3
new file mode 100644
index 0000000..bb1ca64
--- /dev/null
+++ b/contrib/libxo/libxo/xo_emit_err.3
@@ -0,0 +1,72 @@
+.\" #
+.\" # Copyright (c) 2014, Juniper Networks, Inc.
+.\" # All rights reserved.
+.\" # This SOFTWARE is licensed under the LICENSE provided in the
+.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # using the SOFTWARE, you agree to be bound by the terms of that
+.\" # LICENSE.
+.\" # Phil Shafer, July 2014
+.\"
+.Dd December 4, 2014
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_emit_err , xo_emit_errc , xo_emit_errx
+.Nm xo_emit_warn , xo_emit_warnx , xo_emit_warn_c , xo_emit_warn_hc
+.Nd emit errors and warnings in multiple output styles
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_emit_warn "const char *fmt" "..."
+.Ft void
+.Fn xo_emit_warnx "const char *fmt" "..."
+.Ft void
+.Fn xo_emit_warn_c "int code" "const char *fmt" "..."
+.Ft void
+.Fn xo_emit_warn_hc "xo_handle_t *xop" "int code, const char *fmt" "..."
+.Ft void
+.Fn xo_emit_err "int eval" "const char *fmt" "..."
+.Ft void
+.Fn xo_emit_errc "int eval" "int code" "const char *fmt" "..."
+.Ft void
+.Fn xo_emit_errx "int eval" "const char *fmt" "..."
+.Sh DESCRIPTION
+Many programs make use of the standard library functions
+.Xr err 3
+and
+.Xr warn 3
+to generate errors and warnings for the user.
+.Nm libxo
+wants to
+pass that information via the current output style, and provides
+compatible functions to allow this.
+.Pp
+The
+.Fa fmt
+argument is one compatible with
+.Xr xo_emit 3
+which allows these functions make structured data.
+To generate unstructured data,
+use the
+.Xr xo_err 3
+functions.
+.Pp
+These functions display the program name, a colon, a formatted message
+based on the arguments, and then optionally a colon and an error
+message associated with either
+.Fa errno
+or the
+.Fa code
+parameter.
+.Bd -literal -offset indent
+ EXAMPLE:
+ if (open(filename, O_RDONLY) < 0)
+ xo_err(1, "cannot open file '%s'", filename);
+.Ed
+.Sh SEE ALSO
+.Xr xo_emit 3 ,
+.Xr xo_format 5 ,
+.Xr xo_err 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_encoder.c b/contrib/libxo/libxo/xo_encoder.c
new file mode 100644
index 0000000..70195ec
--- /dev/null
+++ b/contrib/libxo/libxo/xo_encoder.c
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 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, August 2015
+ */
+
+/**
+ * libxo includes a number of fixed encoding styles. But other
+ * external encoders are need to deal with new encoders. Rather
+ * than expose a swarm of libxo internals, we create a distinct
+ * API, with a simpler API than we use internally.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <dlfcn.h>
+
+#include "xo_config.h"
+#include "xo.h"
+#include "xo_encoder.h"
+
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#if !defined(HAVE_DLFUNC)
+#define dlfunc(_p, _n) dlsym(_p, _n)
+#endif
+#else /* HAVE_DLFCN_H */
+#define dlopen(_n, _f) NULL /* Fail */
+#define dlsym(_p, _n) NULL /* Fail */
+#define dlfunc(_p, _n) NULL /* Fail */
+#endif /* HAVE_DLFCN_H */
+
+static void xo_encoder_setup (void); /* Forward decl */
+
+/*
+ * Need a simple string collection
+ */
+typedef struct xo_string_node_s {
+ TAILQ_ENTRY(xo_string_node_s) xs_link; /* Next string */
+ char xs_data[0]; /* String data */
+} xo_string_node_t;
+
+typedef TAILQ_HEAD(xo_string_list_s, xo_string_node_s) xo_string_list_t;
+
+static inline void
+xo_string_list_init (xo_string_list_t *listp)
+{
+ if (listp->tqh_last == NULL)
+ TAILQ_INIT(listp);
+}
+
+static inline xo_string_node_t *
+xo_string_add (xo_string_list_t *listp, const char *str)
+{
+ if (listp == NULL || str == NULL)
+ return NULL;
+
+ xo_string_list_init(listp);
+ size_t len = strlen(str);
+ xo_string_node_t *xsp;
+
+ xsp = xo_realloc(NULL, sizeof(*xsp) + len + 1);
+ if (xsp) {
+ memcpy(xsp->xs_data, str, len);
+ xsp->xs_data[len] = '\0';
+ TAILQ_INSERT_TAIL(listp, xsp, xs_link);
+ }
+
+ return xsp;
+}
+
+#define XO_STRING_LIST_FOREACH(_xsp, _listp) \
+ xo_string_list_init(_listp); \
+ TAILQ_FOREACH(_xsp, _listp, xs_link)
+
+static inline void
+xo_string_list_clean (xo_string_list_t *listp)
+{
+ xo_string_node_t *xsp;
+
+ xo_string_list_init(listp);
+
+ for (;;) {
+ xsp = TAILQ_FIRST(listp);
+ if (xsp == NULL)
+ break;
+ TAILQ_REMOVE(listp, xsp, xs_link);
+ xo_free(xsp);
+ }
+}
+
+static xo_string_list_t xo_encoder_path;
+
+void
+xo_encoder_path_add (const char *path)
+{
+ xo_encoder_setup();
+
+ if (path)
+ xo_string_add(&xo_encoder_path, path);
+}
+
+/* ---------------------------------------------------------------------- */
+
+typedef struct xo_encoder_node_s {
+ TAILQ_ENTRY(xo_encoder_node_s) xe_link; /* Next session */
+ char *xe_name; /* Name for this encoder */
+ xo_encoder_func_t xe_handler; /* Callback function */
+ void *xe_dlhandle; /* dlopen handle */
+} xo_encoder_node_t;
+
+typedef TAILQ_HEAD(xo_encoder_list_s, xo_encoder_node_s) xo_encoder_list_t;
+
+#define XO_ENCODER_LIST_FOREACH(_xep, _listp) \
+ xo_encoder_list_init(_listp); \
+ TAILQ_FOREACH(_xep, _listp, xe_link)
+
+static xo_encoder_list_t xo_encoders;
+
+static void
+xo_encoder_list_init (xo_encoder_list_t *listp)
+{
+ if (listp->tqh_last == NULL)
+ TAILQ_INIT(listp);
+}
+
+static xo_encoder_node_t *
+xo_encoder_list_add (const char *name)
+{
+ if (name == NULL)
+ return NULL;
+
+ xo_encoder_node_t *xep = xo_realloc(NULL, sizeof(*xep));
+ if (xep) {
+ int len = strlen(name) + 1;
+ xep->xe_name = xo_realloc(NULL, len);
+ if (xep->xe_name == NULL) {
+ xo_free(xep);
+ return NULL;
+ }
+
+ memcpy(xep->xe_name, name, len);
+
+ TAILQ_INSERT_TAIL(&xo_encoders, xep, xe_link);
+ }
+
+ return xep;
+}
+
+void
+xo_encoders_clean (void)
+{
+ xo_encoder_node_t *xep;
+
+ xo_encoder_setup();
+
+ for (;;) {
+ xep = TAILQ_FIRST(&xo_encoders);
+ if (xep == NULL)
+ break;
+
+ TAILQ_REMOVE(&xo_encoders, xep, xe_link);
+
+ if (xep->xe_dlhandle)
+ dlclose(xep->xe_dlhandle);
+
+ xo_free(xep);
+ }
+
+ xo_string_list_clean(&xo_encoder_path);
+}
+
+static void
+xo_encoder_setup (void)
+{
+ static int initted;
+ if (!initted) {
+ initted = 1;
+
+ xo_string_list_init(&xo_encoder_path);
+ xo_encoder_list_init(&xo_encoders);
+
+ xo_encoder_path_add(XO_ENCODERDIR);
+ }
+}
+
+static xo_encoder_node_t *
+xo_encoder_find (const char *name)
+{
+ xo_encoder_node_t *xep;
+
+ xo_encoder_list_init(&xo_encoders);
+
+ XO_ENCODER_LIST_FOREACH(xep, &xo_encoders) {
+ if (strcmp(xep->xe_name, name) == 0)
+ return xep;
+ }
+
+ return NULL;
+}
+
+static xo_encoder_node_t *
+xo_encoder_discover (const char *name)
+{
+ void *dlp = NULL;
+ char buf[MAXPATHLEN];
+ xo_string_node_t *xsp;
+ xo_encoder_node_t *xep = NULL;
+
+ XO_STRING_LIST_FOREACH(xsp, &xo_encoder_path) {
+ static const char fmt[] = "%s/%s.enc";
+ char *dir = xsp->xs_data;
+ size_t len = snprintf(buf, sizeof(buf), fmt, dir, name);
+
+ if (len > sizeof(buf)) /* Should not occur */
+ continue;
+
+ dlp = dlopen((const char *) buf, RTLD_NOW);
+ if (dlp)
+ break;
+ }
+
+ if (dlp) {
+ /*
+ * If the library exists, find the initializer function and
+ * call it.
+ */
+ xo_encoder_init_func_t func;
+
+ func = (xo_encoder_init_func_t) dlfunc(dlp, XO_ENCODER_INIT_NAME);
+ if (func) {
+ xo_encoder_init_args_t xei;
+
+ bzero(&xei, sizeof(xei));
+
+ xei.xei_version = XO_ENCODER_VERSION;
+ int rc = func(&xei);
+ if (rc == 0 && xei.xei_handler) {
+ xep = xo_encoder_list_add(name);
+ if (xep) {
+ xep->xe_handler = xei.xei_handler;
+ xep->xe_dlhandle = dlp;
+ }
+ }
+ }
+
+ if (xep == NULL)
+ dlclose(dlp);
+ }
+
+ return xep;
+}
+
+void
+xo_encoder_register (const char *name, xo_encoder_func_t func)
+{
+ xo_encoder_setup();
+
+ xo_encoder_node_t *xep = xo_encoder_find(name);
+
+ if (xep) /* "We alla-ready got one" */
+ return;
+
+ xep = xo_encoder_list_add(name);
+ if (xep)
+ xep->xe_handler = func;
+}
+
+void
+xo_encoder_unregister (const char *name)
+{
+ xo_encoder_setup();
+
+ xo_encoder_node_t *xep = xo_encoder_find(name);
+ if (xep) {
+ TAILQ_REMOVE(&xo_encoders, xep, xe_link);
+ xo_free(xep);
+ }
+}
+
+int
+xo_encoder_init (xo_handle_t *xop, const char *name)
+{
+ xo_encoder_setup();
+
+ /* Can't have names containing '/' or ':' */
+ if (strchr(name, '/') != NULL || strchr(name, ':') != NULL)
+ return -1;
+
+ /*
+ * First we look on the list of known (registered) encoders.
+ * If we don't find it, we follow the set of paths to find
+ * the encoding library.
+ */
+ xo_encoder_node_t *xep = xo_encoder_find(name);
+ if (xep == NULL) {
+ xep = xo_encoder_discover(name);
+ if (xep == NULL)
+ return -1;
+ }
+
+ xo_set_encoder(xop, xep->xe_handler);
+
+ return xo_encoder_handle(xop, XO_OP_CREATE, NULL, NULL);
+}
+
+/*
+ * A couple of function varieties here, to allow for multiple
+ * use cases. This varient is for when the main program knows
+ * its own encoder needs.
+ */
+xo_handle_t *
+xo_encoder_create (const char *name, xo_xof_flags_t flags)
+{
+ xo_handle_t *xop;
+
+ xop = xo_create(XO_STYLE_ENCODER, flags);
+ if (xop) {
+ if (xo_encoder_init(xop, name)) {
+ xo_destroy(xop);
+ xop = NULL;
+ }
+ }
+
+ return xop;
+}
+
+int
+xo_encoder_handle (xo_handle_t *xop, xo_encoder_op_t op,
+ const char *name, const char *value)
+{
+ void *private = xo_get_private(xop);
+ xo_encoder_func_t func = xo_get_encoder(xop);
+
+ if (func == NULL)
+ return -1;
+
+ return func(xop, op, name, value, private);
+}
+
+const char *
+xo_encoder_op_name (xo_encoder_op_t op)
+{
+ static const char *names[] = {
+ /* 0 */ "unknown",
+ /* 1 */ "create",
+ /* 2 */ "open_container",
+ /* 3 */ "close_container",
+ /* 4 */ "open_list",
+ /* 5 */ "close_list",
+ /* 6 */ "open_leaf_list",
+ /* 7 */ "close_leaf_list",
+ /* 8 */ "open_instance",
+ /* 9 */ "close_instance",
+ /* 10 */ "string",
+ /* 11 */ "content",
+ /* 12 */ "finish",
+ /* 13 */ "flush",
+ /* 14 */ "destroy",
+ /* 15 */ "attr",
+ /* 16 */ "version",
+ };
+
+ if (op > sizeof(names) / sizeof(names[0]))
+ return "unknown";
+
+ return names[op];
+}
diff --git a/contrib/libxo/libxo/xo_encoder.h b/contrib/libxo/libxo/xo_encoder.h
new file mode 100644
index 0000000..f73552b
--- /dev/null
+++ b/contrib/libxo/libxo/xo_encoder.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 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, August 2015
+ */
+
+/*
+ * NOTE WELL: This file is needed to software that implements an
+ * external encoder for libxo that allows libxo data to be encoded in
+ * new and bizarre formats. General libxo code should _never_
+ * include this header file.
+ */
+
+#ifndef XO_ENCODER_H
+#define XO_ENCODER_H
+
+/*
+ * Expose libxo's memory allocation functions
+ */
+extern xo_realloc_func_t xo_realloc;
+extern xo_free_func_t xo_free;
+
+typedef unsigned xo_encoder_op_t;
+
+/* Encoder operations; names are in xo_encoder.c:xo_encoder_op_name() */
+#define XO_OP_UNKNOWN 0
+#define XO_OP_CREATE 1 /* Called when the handle is init'd */
+#define XO_OP_OPEN_CONTAINER 2
+#define XO_OP_CLOSE_CONTAINER 3
+#define XO_OP_OPEN_LIST 4
+#define XO_OP_CLOSE_LIST 5
+#define XO_OP_OPEN_LEAF_LIST 6
+#define XO_OP_CLOSE_LEAF_LIST 7
+#define XO_OP_OPEN_INSTANCE 8
+#define XO_OP_CLOSE_INSTANCE 9
+#define XO_OP_STRING 10 /* Quoted UTF-8 string */
+#define XO_OP_CONTENT 11 /* Other content */
+#define XO_OP_FINISH 12 /* Finish any pending output */
+#define XO_OP_FLUSH 13 /* Flush any buffered output */
+#define XO_OP_DESTROY 14 /* Clean up function */
+#define XO_OP_ATTRIBUTE 15 /* Attribute name/value */
+#define XO_OP_VERSION 16 /* Version string */
+
+#define XO_ENCODER_HANDLER_ARGS \
+ xo_handle_t *xop __attribute__ ((__unused__)), \
+ xo_encoder_op_t op __attribute__ ((__unused__)), \
+ const char *name __attribute__ ((__unused__)), \
+ const char *value __attribute__ ((__unused__)), \
+ void *private __attribute__ ((__unused__))
+
+typedef int (*xo_encoder_func_t)(XO_ENCODER_HANDLER_ARGS);
+
+typedef struct xo_encoder_init_args_s {
+ unsigned xei_version; /* Current version */
+ xo_encoder_func_t xei_handler; /* Encoding handler */
+} xo_encoder_init_args_t;
+
+#define XO_ENCODER_VERSION 1 /* Current version */
+
+#define XO_ENCODER_INIT_ARGS \
+ xo_encoder_init_args_t *arg __attribute__ ((__unused__))
+
+typedef int (*xo_encoder_init_func_t)(XO_ENCODER_INIT_ARGS);
+/*
+ * Each encoder library must define a function named xo_encoder_init
+ * that takes the arguments defined in XO_ENCODER_INIT_ARGS. It
+ * should return zero for success.
+ */
+#define XO_ENCODER_INIT_NAME_TOKEN xo_encoder_library_init
+#define XO_STRINGIFY(_x) #_x
+#define XO_STRINGIFY2(_x) XO_STRINGIFY(_x)
+#define XO_ENCODER_INIT_NAME XO_STRINGIFY2(XO_ENCODER_INIT_NAME_TOKEN)
+extern int XO_ENCODER_INIT_NAME_TOKEN (XO_ENCODER_INIT_ARGS);
+
+void
+xo_encoder_register (const char *name, xo_encoder_func_t func);
+
+void
+xo_encoder_unregister (const char *name);
+
+void *
+xo_get_private (xo_handle_t *xop);
+
+void
+xo_encoder_path_add (const char *path);
+
+void
+xo_set_private (xo_handle_t *xop, void *opaque);
+
+xo_encoder_func_t
+xo_get_encoder (xo_handle_t *xop);
+
+void
+xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder);
+
+int
+xo_encoder_init (xo_handle_t *xop, const char *name);
+
+xo_handle_t *
+xo_encoder_create (const char *name, xo_xof_flags_t flags);
+
+int
+xo_encoder_handle (xo_handle_t *xop, xo_encoder_op_t op,
+ const char *name, const char *value);
+
+void
+xo_encoders_clean (void);
+
+const char *
+xo_encoder_op_name (xo_encoder_op_t op);
+
+#endif /* XO_ENCODER_H */
diff --git a/contrib/libxo/libxo/xo_err.3 b/contrib/libxo/libxo/xo_err.3
index 5584309..532899a 100644
--- a/contrib/libxo/libxo/xo_err.3
+++ b/contrib/libxo/libxo/xo_err.3
@@ -11,8 +11,9 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_err
-.Nd emit errors and warnings in multiple formats
+.Nm xo_err , xo_errc , xo_errx
+.Nm xo_warn , xo_warnx , xo_warn_c , xo_warn_hc
+.Nd emit errors and warnings in multiple output styles
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -31,14 +32,6 @@
.Fn xo_errc "int eval" "int code" "const char *fmt" "..."
.Ft void
.Fn xo_errx "int eval" "const char *fmt" "..."
-.Ft void
-.Fn xo_message "const char *fmt" "..."
-.Ft void
-.Fn xo_message_c "int code" "const char *fmt" "..."
-.Ft void
-.Fn xo_message_hc "xo_handle_t *xop" "int code, const char *fmt" "..."
-.Ft void
-.Fn xo_message_hcv "xo_handle_t *xop" "int code" "const char *fmt" "va_list vap"
.Sh DESCRIPTION
Many programs make use of the standard library functions
.Xr err 3
@@ -50,6 +43,19 @@ wants to
pass that information via the current output style, and provides
compatible functions to allow this.
.Pp
+The
+.Fa fmt
+argument is one compatible with
+.Xr printf 3
+rather than
+.Xr xo_emit 3
+to aid in simple conversion. This means
+these functions make unstructured data.
+To generate structured data,
+use the
+.Xr xo_emit_err 3
+functions.
+.Pp
These functions display the program name, a colon, a formatted message
based on the arguments, and then optionally a colon and an error
message associated with either
@@ -62,30 +68,7 @@ parameter.
if (open(filename, O_RDONLY) < 0)
xo_err(1, "cannot open file '%s'", filename);
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr xo_emit_err 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_error.3 b/contrib/libxo/libxo/xo_error.3
index 01431cb..e5c99e9 100644
--- a/contrib/libxo/libxo/xo_error.3
+++ b/contrib/libxo/libxo/xo_error.3
@@ -12,7 +12,7 @@
.Os
.Sh NAME
.Nm xo_error
-.Nd generate error messages
+.Nd generate simple error messages in multiple output styles
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -35,31 +35,7 @@ one can replace
calls with
.Fn xo_error
calls.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr printf 3 ,
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_finish.3 b/contrib/libxo/libxo/xo_finish.3
index 421e945..221b1c1 100644
--- a/contrib/libxo/libxo/xo_finish.3
+++ b/contrib/libxo/libxo/xo_finish.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_finish , xo_finish_h
+.Nd finish formatting output
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -34,30 +34,6 @@ Calling this function is
.Em vital
to the proper operation of libxo,
especially for the non-TEXT output styles.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_flush.3 b/contrib/libxo/libxo/xo_flush.3
index b85e9f7..e43bae0 100644
--- a/contrib/libxo/libxo/xo_flush.3
+++ b/contrib/libxo/libxo/xo_flush.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_flush , xo_flush_h
+.Nd flush formatted output from libxo handle
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -30,30 +30,6 @@ caller may wish to flush any data buffered within the library.
The
.Fn xo_flush
function is used for this.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_format.5 b/contrib/libxo/libxo/xo_format.5
index bce5dc5..1db4fc8 100644
--- a/contrib/libxo/libxo/xo_format.5
+++ b/contrib/libxo/libxo/xo_format.5
@@ -2,11 +2,11 @@
.\" # Copyright (c) 2014, Juniper Networks, Inc.
.\" # All rights reserved.
.\" # This SOFTWARE is licensed under the LICENSE provided in the
-.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # ../Copyright file. By downloading, installing, copying, or
.\" # using the SOFTWARE, you agree to be bound by the terms of that
.\" # LICENSE.
.\" # Phil Shafer, July 2014
-.\"
+.\"
.Dd December 4, 2014
.Dt LIBXO 3
.Os
@@ -20,18 +20,18 @@ uses format strings to control the rendering of data into
various output styles, including
.Em text ,
.Em XML ,
-.EM JSON ,
+.Em JSON ,
and
.Em HTML .
Each format string contains a set of zero or more
-.Dq field descriptions ,
+.Dq "field descriptions" ,
which describe independent data fields.
Each field description contains a set of
.Dq modifiers ,
a
-.Dq content string ,
+.Dq "content string" ,
and zero, one, or two
-.Dq format descriptors .
+.Dq "format descriptors" .
The modifiers tell
.Nm libxo
what the field is and how to treat it, while the format descriptors are
@@ -51,7 +51,7 @@ field descriptions within the format string.
.Pp
The field description is given as follows:
.Bd -literal -offset indent
- '{' [ role | modifier ]* ':' [ content ]
+ '{' [ role | modifier ]* [',' long-names ]* ':' [ content ]
[ '/' field-format [ '/' encoding-format ]] '}'
.Ed
.Pp
@@ -61,6 +61,10 @@ The contents, field-format, and
encoding-format are used in varying ways, based on the role.
These are described in the following sections.
.Pp
+Braces can be escaped by using double braces, similar to "%%" in
+.Xr printf 3 .
+The format string "{{braces}}" would emit "{braces}".
+.Pp
In the following example, three field descriptors appear.
The first
is a padding field containing three spaces of padding, the second is a
@@ -70,19 +74,28 @@ passed to the
.Xr xo_emit 3 ,
function as an unsigned integer.
.Bd -literal -offset indent
- xo_emit("{P: }{Lwc:In stock}{:in-stock/%u}\\n", 65);
+ xo_emit("{P: }{Lwc:In stock}{:in-stock/%u}\\n", 65);
.Ed
.Pp
This single line of code can generate text ("In stock: 65\\n"), XML
("<in-stock>65</in-stock>"), JSON ('"in-stock": 65'), or HTML (too
lengthy to be listed here).
-.Ss Modifier Roles
-Modifiers are optional, and indicate the role and formatting of the
+.Pp
+While roles and modifiers typically use single character for brevity,
+there are alternative names for each which allow more verbose
+formatting strings.
+These names must be preceded by a comma, and may follow any
+single-character values:
+.Bd -literal -offset indent
+ xo_emit("{L,white,colon:In stock}{,key:in-stock/%u}\n", 65);
+.Ed
+.Ss "Field Roles"
+Field roles are optional, and indicate the role and formatting of the
content.
The roles are listed below; only one role is permitted:
-.Pp
.Bl -column "M" "Name12341234"
-.It Sy "M Name Description"
+.It Sy "M" "Name " "Description"
+.It C "color " "Field is a color or effect"
.It D "decoration " "Field is non-text (e.g. colon, comma)"
.It E "error " "Field is an error message"
.It L "label " "Field is text that prefixes a value"
@@ -92,38 +105,74 @@ The roles are listed below; only one role is permitted:
.It U "units " "Field is the units for the previous value field"
.It V "value " "Field is the name of field (the default)"
.It W "warning " "Field is a warning message"
-.It \&[ "start anchor" "Begin a section of anchored variable-width text"
-.It \&] "stop anchor " "End a section of anchored variable-width text"
+.It \&[ "start-anchor" "Begin a section of anchored variable-width text"
+.It \&] "stop-anchor " "End a section of anchored variable-width text"
.El
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{L:Free}{D::}{P: }{:free/%u} {U:Blocks}\n",
+ free_blocks);
+.Ed
.Pp
-.Ss The Color Role ({C:})
+When a role is not provided, the "value" role is used as the default.
+.Pp
+Roles and modifiers can also use more verbose names, when preceeded by
+a comma:
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{,label:Free}{,decoration::}{,padding: }"
+ "{,value:free/%u} {,units:Blocks}\n",
+ free_blocks);
+.Ed
+.Ss "The Color Role ({C:})"
Colors and effects control how text values are displayed; they are
used for display styles (TEXT and HTML).
-The color content can be
-either static, when placed directly within the field descriptor, or a
-printf-style format descriptor can be used, if preceded by a slash ("/"):
.Bd -literal -offset indent
- xo_emit("{C:bold}{Lwc:Cost}{:cost/%u}{C:reset}\n", cost);
- xo_emit("{C:/fg-%s,bg-%s}{Lwc:Cost}{:cost/%u}{C:reset}\n",
- fg_color, bg_color, cost);
+ xo_emit("{C:bold}{:value}{C:no-bold}\n", value);
+.Ed
+.Pp
+Colors and effects remain in effect until modified by other "C"-role
+fields.
+.Bd -literal -offset indent
+ xo_emit("{C:bold}{C:inverse}both{C:no-bold}only inverse\n");
+.Ed
+.Pp
+If the content is empty, the "reset" action is performed.
+.Bd -literal -offset indent
+ xo_emit("{C:both,underline}{:value}{C:}\n", value);
.Ed
.Pp
The content should be a comma-separated list of zero or more colors or
display effects.
+.Bd -literal -offset indent
+ xo_emit("{C:bold,underline,inverse}All three{C:no-bold,no-inverse}\n");
+.Ed
.Pp
-Colors and effects remain in effect until modified by other "C" roles.
+The color content can be either static, when placed directly within
+the field descriptor, or a printf-style format descriptor can be used,
+if preceded by a slash ("/"):
+.Bd -literal -offset indent
+ xo_emit("{C:/%s%s}{:value}{C:}", need_bold ? "bold" : "",
+ need_underline ? "underline" : "", value);
+.Ed
.Pp
-If the content is empty, the "reset" action is performed.
+Color names are prefixed with either "fg-" or "bg-" to change the
+foreground and background colors, respectively.
+.Bd -literal -offset indent
+ xo_emit("{C:/fg-%s,bg-%s}{Lwc:Cost}{:cost/%u}{C:reset}\n",
+ fg_color, bg_color, cost);
+.Ed
.Pp
+The following table lists the supported effects:
.Bl -column "no-underline"
-.It Sy "Name Description"
-.It "bg-xxxxx " "Change background color"
+.It Sy "Name " "Description"
+.It "bg\-xxxxx " "Change background color"
.It "bold " "Start bold text effect"
-.It "fg-xxxxx " "Change foreground color"
+.It "fg\-xxxxx " "Change foreground color"
.It "inverse " "Start inverse (aka reverse) text effect"
-.It "no-bold " "Stop bold text effect"
-.It "no-inverse " "Stop inverse (aka reverse) text effect"
-.It "no-underline " "Stop underline text effect"
+.It "no\-bold " "Stop bold text effect"
+.It "no\-inverse " "Stop inverse (aka reverse) text effect"
+.It "no\-underline " "Stop underline text effect"
.It "normal " "Reset effects (only)"
.It "reset " "Reset colors and effects (restore defaults)"
.It "underline " "Start underline text effect"
@@ -142,10 +191,7 @@ The following color names are supported:
.It white
.It yellow
.El
-.Pp
-Color names are prefixed with either "fg-" or "bg-" to change the
-foreground and background colors, respectively.
-.Ss The Decoration Role ({D:})
+.Ss "The Decoration Role ({D:})"
Decorations are typically punctuation marks such as colons,
semi-colons, and commas used to decorate the text and make it simpler
for human readers.
@@ -154,17 +200,53 @@ can use CSS to direct their display parameters.
.Bd -literal -offset indent
xo_emit("{D:((}{:name}{D:))}\\n", name);
.Ed
-.Ss The Label Role ({L:})
+.Ss "The Gettext Role ({G:})"
+.Nm libxo
+supports internationalization (i18n) through its use of
+.Xr gettext 3 .
+Use the "{G:}" role to request that the remaining part of
+the format string, following the "{G:}" field, be handled using
+.Fn gettext .
+Since
+.Fn gettext
+uses the string as the key into the message catalog,
+.Nm libxo
+uses a simplified version of the format string that removes
+unimportant field formatting and modifiers, stopping minor formatting
+changes from impacting the expensive translation process.
+A developer
+change such as changing "/%06d" to "/%08d" should not force hand
+inspection of all .po files.
+.Pp
+The simplified version can be generated for a single message using the
+"xopo -s <text>" command, or an entire .pot can be translated using
+the "xopo -f <input> -o <output>" command.
+.Bd -literal -offset indent
+ xo_emit("{G:}Invalid token\n");
+.Ed
+The {G:} role allows a domain name to be set.
+.Fn gettext
+calls will
+continue to use that domain name until the current format string
+processing is complete, enabling a library function to emit strings
+using it's own catalog.
+The domain name can be either static as the
+content of the field, or a format can be used to get the domain name
+from the arguments.
+.Bd -literal -offset indent
+ xo_emit("{G:libc}Service unavailable in restricted mode\n");
+.Ed
+.Ss "The Label Role ({L:})"
Labels are text that appears before a value.
.Bd -literal -offset indent
xo_emit("{Lwc:Cost}{:cost/%u}\\n", cost);
.Ed
-.Ss The Note Role ({N:})
+.Ss "The Note Role ({N:})"
Notes are text that appears after a value.
.Bd -literal -offset indent
xo_emit("{:cost/%u} {N:per year}\\n", cost);
.Ed
-.Ss The Padding Role ({P:})
+.Ss "The Padding Role ({P:})"
Padding represents whitespace used before and between fields.
The padding content can be either static, when placed directly within
the field descriptor, or a printf-style format descriptor can be used,
@@ -173,7 +255,7 @@ if preceded by a slash ("/"):
xo_emit("{P: }{Lwc:Cost}{:cost/%u}\\n", cost);
xo_emit("{P:/30s}{Lwc:Cost}{:cost/%u}\\n", "", cost);
.Ed
-.Ss The Title Role ({T:})
+.Ss "The Title Role ({T:})"
Titles are heading or column headers that are meant to be displayed to
the user.
The title can be either static, when placed directly within
@@ -183,7 +265,7 @@ if preceded by a slash ("/"):
xo_emit("{T:Interface Statistics}\\n");
xo_emit("{T:/%20.20s}{T:/%6.6s}\\n", "Item Name", "Cost");
.Ed
-.Ss The Units Role ({U:})
+.Ss "The Units Role ({U:})"
Units are the dimension by which values are measured, such as degrees,
miles, bytes, and decibels.
The units field carries this information
@@ -209,7 +291,7 @@ Units can also be rendered in HTML as the "data-units" attribute:
<div class="data" data-tag="distance" data-units="miles"
data-xpath="/top/data/distance">50</div>
.Ed
-.Ss The Value Role ({V:} and {:})
+.Ss "The Value Role ({V:} and {:})"
The value role is used to represent the a data value that is
interesting for the non-display output styles (XML and JSON).
Value
@@ -231,7 +313,7 @@ format descriptors default to "%s".
xo_emit("{:author} wrote \"{:poem}\" in {:year/%4d}\\n,
author, poem, year);
.Ed
-.Ss The Anchor Modifiers ({[:} and {]:})
+.Ss "The Anchor Roles ({[:} and {]:})"
The anchor roles allow a set of strings by be padded as a group,
but still be visible to
.Xr xo_emit 3
@@ -269,20 +351,24 @@ Widths over 8k are considered probable errors and not supported.
If
.Dv XOF_WARN
is set, a warning will be generated.
-.Ss Modifier Flags
-The modifiers can also include the following flags, which modify the
-content emitted for some output styles:
-.Pp
-.Bl -column M "Name12341234"
-.It Sy M "Name Description"
-.It c "colon " "A colon ("":"") is appended after the label"
-.It d "display " "Only emit field for display styles (text/HTML)"
-.It e "encoding " "Only emit for encoding styles (XML/JSON)"
-.It k "key " "Field is a key, suitable for XPath predicates"
-.It l "leaf " "Field is a leaf-list, a list of leaf values"
-.It n "no-quotes " "Do not quote the field when using JSON style"
-.It q "quotes " "Quote the field when using JSON style"
-.It w "white space " "A blank ("" "") is appended after the label"
+.Ss "Field Modifiers"
+Field modifiers are flags which modify the way content emitted for
+particular output styles:
+.Bl -column M "Name123456789"
+.It Sy M "Name " "Description"
+.It c "colon " "A colon ("":"") is appended after the label"
+.It d "display " "Only emit field for display styles (text/HTML)"
+.It e "encoding " "Only emit for encoding styles (XML/JSON)"
+.It h "humanize (hn) " "Format large numbers in human-readable style"
+.It " " "hn-space " "Humanize: Place space between numeric and unit"
+.It " " "hn-decimal " "Humanize: Add a decimal digit, if number < 10"
+.It " " "hn-1000 " "Humanize: Use 1000 as divisor instead of 1024"
+.It k "key " "Field is a key, suitable for XPath predicates"
+.It l "leaf-list " "Field is a leaf-list, a list of leaf values"
+.It n "no-quotes " "Do not quote the field when using JSON style"
+.It q "quotes " "Quote the field when using JSON style"
+.It q "trim " "Trim leading and trailing whitespace"
+.It w "white space " "A blank ("" "") is appended after the label"
.El
.Pp
For example, the modifier string "Lwc" means the field has a label
@@ -291,7 +377,16 @@ colon ('c') and a space ('w').
The modifier string "Vkq" means the
field has a value role, that it is a key for the current instance, and
that the value should be quoted when encoded for JSON.
-.Ss The Colon Modifier ({c:})
+.Pp
+Roles and modifiers can also use more verbose names, when preceeded by
+a comma.
+For example, the modifier string "Lwc" (or "L,white,colon")
+means the field has a label role (text that describes the next field)
+and should be followed by a colon ('c') and a space ('w').
+The modifier string "Vkq" (or ":key,quote") means the field has a value
+role (the default role), that it is a key for the current instance,
+and that the value should be quoted when encoded for JSON.
+.Ss "The Colon Modifier ({c:})"
The colon modifier appends a single colon to the data value:
.Bd -literal -offset indent
EXAMPLE:
@@ -304,7 +399,7 @@ The colon modifier is only used for the TEXT and HTML output
styles.
It is commonly combined with the space modifier ('{w:}').
It is purely a convenience feature.
-.Ss The Display Modifier ({d:})
+.Ss "The Display Modifier ({d:})"
The display modifier indicated the field should only be generated for
the display output styles, TEXT and HTML.
.Bd -literal -offset indent
@@ -318,7 +413,7 @@ the display output styles, TEXT and HTML.
.Pp
The display modifier is the opposite of the encoding modifier, and
they are often used to give to distinct views of the underlying data.
-.Ss The Encoding Modifier ({e:})
+.Ss "The Encoding Modifier ({e:})"
The encoding modifier indicated the field should only be generated for
the encoding output styles, such as JSON and XML.
.Bd -literal -offset indent
@@ -332,7 +427,60 @@ the encoding output styles, such as JSON and XML.
.Pp
The encoding modifier is the opposite of the display modifier, and
they are often used to give to distinct views of the underlying data.
-.Ss The Key Modifier ({k:})
+.Ss "The Humanize Modifier ({h:})"
+The humanize modifier is used to render large numbers as in a
+human-readable format.
+While numbers like "44470272" are completely readable to computers and
+savants, humans will generally find "44M" more meaningful.
+.Pp
+"hn" can be used as an alias for "humanize".
+.Pp
+The humanize modifier only affects display styles (TEXT and HMTL).
+The "no-humanize" option will block the function of the humanize modifier.
+.Pp
+There are a number of modifiers that affect details of humanization.
+These are only available in as full names, not single characters.
+The "hn-space" modifier places a space between the number and any
+multiplier symbol, such as "M" or "K" (ex: "44 K").
+The "hn-decimal" modifier will add a decimal point and a single tenths digit
+when the number is less than 10 (ex: "4.4K").
+The "hn-1000" modifier will use 1000 as divisor instead of 1024, following the
+JEDEC-standard instead of the more natural binary powers-of-two
+tradition.
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{h:input/%u}, {h,hn-space:output/%u}, "
+ "{h,hn-decimal:errors/%u}, {h,hn-1000:capacity/%u}, "
+ "{h,hn-decimal:remaining/%u}\n",
+ input, output, errors, capacity, remaining);
+ TEXT:
+ 21, 57 K, 96M, 44M, 1.2G
+.Ed
+.Pp
+In the HTML style, the original numeric value is rendered in the
+"data-number" attribute on the <div> element:
+.Bd -literal -offset indent
+ <div class="data" data-tag="errors"
+ data-number="100663296">96M</div>
+.Ed
+.Ss "The Gettext Modifier ({g:})"
+The gettext modifier is used to translate individual fields using the
+gettext domain (typically set using the "{G:}" role) and current
+language settings.
+Once libxo renders the field value, it is passed
+to
+.Xr gettext 3 ,
+where it is used as a key to find the native language
+translation.
+.Pp
+In the following example, the strings "State" and "full" are passed
+to
+.Fn gettext
+to find locale-based translated strings.
+.Bd -literal -offset indent
+ xo_emit("{Lgwc:State}{g:state}\n", "full");
+.Ed
+.Ss "The Key Modifier ({k:})"
The key modifier is used to indicate that a particular field helps
uniquely identify an instance of list data.
.Bd -literal -offset indent
@@ -351,7 +499,7 @@ Currently the key modifier is only used when generating XPath values
for the HTML output style when
.Dv XOF_XPATH
is set, but other uses are likely in the near future.
-.Ss The Leaf-List Modifier ({l:})
+.Ss "The Leaf-List Modifier ({l:})"
The leaf-list modifier is used to distinguish lists where each
instance consists of only a single value. In XML, these are
rendered as single elements, where JSON renders them as arrays.
@@ -368,7 +516,7 @@ rendered as single elements, where JSON renders them as arrays.
JSON:
"user": [ "phil", "pallavi" ]
.Ed
-.Ss The No-Quotes Modifier ({n:})
+.Ss "The No-Quotes Modifier ({n:})"
The no-quotes modifier (and its twin, the 'quotes' modifier) affect
the quoting of values in the JSON output style.
JSON uses quotes for
@@ -383,7 +531,29 @@ needed, but often this needs to be controlled by the caller.
JSON:
"fancy": true
.Ed
-.Ss The Quotes Modifier ({q:})
+.Ss "The Plural Modifier ({p:})"
+The plural modifier selects the appropriate plural form of an
+expression based on the most recent number emitted and the current
+language settings.
+The contents of the field should be the singular
+and plural English values, separated by a comma:
+.Bd -literal -offset indent
+ xo_emit("{:bytes} {Ngp:byte,bytes}\n", bytes);
+.Ed
+The plural modifier is meant to work with the gettext modifier ({g:})
+but can work independently.
+.Pp
+When used without the gettext modifier or when the message does not
+appear in the message catalog, the first token is chosen when the last
+numeric value is equal to 1; otherwise the second value is used,
+mimicking the simple pluralization rules of English.
+.Pp
+When used with the gettext modifier, the
+.Xr ngettext 3
+function is
+called to handle the heavy lifting, using the message catalog to
+convert the singular and plural forms into the native language.
+.Ss "The Quotes Modifier ({q:})"
The quotes modifier (and its twin, the 'no-quotes' modifier) affect
the quoting of values in the JSON output style.
JSON uses quotes for
@@ -397,7 +567,7 @@ needed, but often this needs to be controlled by the caller.
JSON:
"year": "2014"
.Ed
-.Ss The White Space Modifier ({w:})
+.Ss "The White Space Modifier ({w:})"
The white space modifier appends a single space to the data value:
.Bd -literal -offset indent
EXAMPLE:
@@ -413,7 +583,7 @@ It is purely a convenience feature.
.Pp
Note that the sense of the 'w' modifier is reversed for the units role
({Uw:}); a blank is added before the contents, rather than after it.
-.Ss Field Formatting
+.Ss "Field Formatting"
The field format is similar to the format string for
.Xr printf 3 .
Its use varies based on the role of the field, but generally is used to
@@ -440,7 +610,7 @@ The format- modifier can be:
.Bl -bullet
.It
a '#' character, indicating the output value should be prefixed with
-'0x', typically to indicate a base 16 (hex) value.
+"0x", typically to indicate a base 16 (hex) value.
.It
a minus sign ('-'), indicating the output value should be padded on
the right instead of the left.
@@ -464,7 +634,7 @@ will never dereference memory beyond the given number of bytes.
.It
a second period followed by one or more digits indicating the maximum
width for a string argument.
-This modifier cannot be given for non-string arguments.
+This modifier cannot be given for non-string arguments.
.It
one or more 'h' characters, indicating shorter input data.
.It
@@ -484,9 +654,8 @@ Note that 'q', 'D', 'O', and 'U' are considered deprecated and will be
removed eventually.
.Pp
The format character is described in the following table:
-.Pp
.Bl -column C "Argument Type12"
-.It Sy "C Argument Type Format"
+.It Sy "C" "Argument Type " "Format"
.It d "int " "base 10 (decimal)"
.It i "int " "base 10 (decimal)"
.It o "int " "base 8 (octal)"
@@ -524,8 +693,7 @@ argument:
.It "z " "size_t " "size_t"
.It "q " "quad_t " "u_quad_t"
.El
-.Pp
-.Ss UTF-8 and Locale Strings
+.Ss "UTF-8 and Locale Strings"
All strings for
.Nm libxo
must be UTF-8.
@@ -542,10 +710,10 @@ Since UTF-8 is compatible with
data, a normal 7-bit
.Em ASCII
string can be used.
-'%ls' expects a
-'wchar_t *' pointer to a wide-character string, encoded as 32-bit
+"%ls" expects a
+"wchar_t *" pointer to a wide-character string, encoded as 32-bit
Unicode values.
-'%hs' expects a 'char *' pointer to a multi-byte
+"%hs" expects a "char *" pointer to a multi-byte
string encoded with the current locale, as given by the
.Ev LC_CTYPE ,
.Ev LANG ,
@@ -612,11 +780,11 @@ and adds a third value for specifying the maximum number of columns.
.Pp
In this example, the name field is printed with a minimum of 3 columns
and a maximum of 6.
-Up to ten bytes are in used in filling those columns.
+Up to ten bytes are in used in filling those columns.
.Bd -literal -offset indent
xo_emit("{:name/%3.10.6s}", name);
.Ed
-.Ss Characters Outside of Field Definitions
+.Ss "Characters Outside of Field Definitions"
Characters in the format string that are not part of a field definition are
copied to the output for the TEXT style, and are ignored for the JSON
and XML styles.
@@ -635,12 +803,12 @@ For HTML, these characters are placed in a <div> with class "text".
<div class="data" data-tag="size">extra small</div>
<div class="text">.</div>
.Ed
-.Ss "%n" is Not Supported
+.Ss "'%n' is Not Supported"
.Nm libxo
does not support the '%n' directive.
It is a bad idea and we
just do not do it.
-.Ss The Encoding Format (eformat)
+.Ss "The Encoding Format (eformat)"
The "eformat" string is the format string used when encoding the field
for JSON and XML.
If not provided, it defaults to the primary format
@@ -655,7 +823,7 @@ In this example, the value for the number of items in stock is emitted:
.Pp
This call will generate the following output:
.Bd -literal -offset indent
- TEXT:
+ TEXT:
In stock: 144
XML:
<in-stock>144</in-stock>
@@ -685,25 +853,24 @@ data, which would expand the penultimate line to:
.Ed
.Sh WHAT MAKES A GOOD FIELD NAME?
To make useful, consistent field names, follow these guidelines:
-.Pp
-.Ss Use lower case, even for TLAs
+.Ss "Use lower case, even for TLAs"
Lower case is more civilized.
Even TLAs should be lower case
to avoid scenarios where the differences between "XPath" and
"Xpath" drive your users crazy.
Using "xpath" is simpler and better.
-.Ss Use hyphens, not underscores
+.Ss "Use hyphens, not underscores"
Use of hyphens is traditional in XML, and the
.Dv XOF_UNDERSCORES
flag can be used to generate underscores in JSON, if desired.
But the raw field name should use hyphens.
-.Ss Use full words
+.Ss "Use full words"
Do not abbreviate especially when the abbreviation is not obvious or
not widely used.
Use "data-size", not "dsz" or "dsize".
Use
"interface" instead of "ifname", "if-name", "iface", "if", or "intf".
-.Ss Use <verb>-<units>
+.Ss "Use <verb>-<units>"
Using the form <verb>-<units> or <verb>-<classifier>-<units> helps in
making consistent, useful names, avoiding the situation where one app
uses "sent-packet" and another "packets-sent" and another
@@ -712,10 +879,10 @@ The <units> can be dropped when it is
obvious, as can obvious words in the classification.
Use "receive-after-window-packets" instead of
"received-packets-of-data-after-window".
-.Ss Reuse existing field names
+.Ss "Reuse existing field names"
Nothing is worse than writing expressions like:
.Bd -literal -offset indent
- if ($src1/process[pid == $pid]/name ==
+ if ($src1/process[pid == $pid]/name ==
$src2/proc-table/proc/p[process-id == $pid]/proc-name) {
...
}
@@ -724,16 +891,16 @@ Nothing is worse than writing expressions like:
Find someone else who is expressing similar data and follow their
fields and hierarchy.
Remember the quote is not
-.Dq Consistency is the hobgoblin of little minds
+.Dq "Consistency is the hobgoblin of little minds"
but
-.Dq A foolish consistency is the hobgoblin of little minds .
-.Ss Think about your users
+.Dq "A foolish consistency is the hobgoblin of little minds" .
+.Ss "Think about your users"
Have empathy for your users, choosing clear and useful fields that
contain clear and useful data.
You may need to augment the display content with
.Xr xo_attr 3
calls or "{e:}" fields to make the data useful.
-.Ss Do not use an arbitrary number postfix
+.Ss "Do not use an arbitrary number postfix"
What does "errors2" mean?
No one will know.
"errors-after-restart" would be a better choice.
@@ -741,7 +908,7 @@ Think of your users, and think of the future.
If you make "errors2", the next guy will happily make
"errors3" and before you know it, someone will be asking what is the
difference between errors37 and errors63.
-.Ss Be consistent, uniform, unsurprising, and predictable
+.Ss "Be consistent, uniform, unsurprising, and predictable"
Think of your field vocabulary as an API.
You want it useful,
expressive, meaningful, direct, and obvious.
@@ -770,31 +937,7 @@ If there is no difference,
use only one of the field names.
If there is a difference, change the
names to make that difference more obvious.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
+.Xr libxo 3 ,
.Xr xolint 1 ,
.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
diff --git a/contrib/libxo/libxo/xo_humanize.h b/contrib/libxo/libxo/xo_humanize.h
new file mode 100644
index 0000000..edf85b8
--- /dev/null
+++ b/contrib/libxo/libxo/xo_humanize.h
@@ -0,0 +1,169 @@
+/* $NetBSD: humanize_number.c,v 1.8 2004/07/27 01:56:24 enami Exp $ */
+
+/*
+ * Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center, by Luke Mewburn and by Tomas Svensson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <stdint.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+/* humanize_number(3) */
+#define HN_DECIMAL 0x01
+#define HN_NOSPACE 0x02
+#define HN_B 0x04
+#define HN_DIVISOR_1000 0x08
+
+#define HN_GETSCALE 0x10
+#define HN_AUTOSCALE 0x20
+
+static int
+xo_humanize_number (char *buf, size_t len, int64_t bytes,
+ const char *suffix, int scale, int flags)
+{
+ const char *prefixes, *sep;
+ int b, i, r, maxscale, s1, s2, sign;
+ int64_t divisor, max;
+ // We multiply bytes by 100 to deal with rounding, so we need something
+ // big enough to hold LLONG_MAX * 100. On 64-bit we can use 128-bit wide
+ // integers with __int128_t, but on 32-bit we have to use long double.
+#ifdef __LP64__
+ __int128_t scalable = (__int128_t)bytes;
+#else
+ long double scalable = (long double)bytes;
+#endif
+ size_t baselen;
+
+ assert(buf != NULL);
+ assert(suffix != NULL);
+ assert(scale >= 0);
+
+ if (flags & HN_DIVISOR_1000) {
+ /* SI for decimal multiplies */
+ divisor = 1000;
+ if (flags & HN_B)
+ prefixes = "B\0k\0M\0G\0T\0P\0E";
+ else
+ prefixes = "\0\0k\0M\0G\0T\0P\0E";
+ } else {
+ /*
+ * binary multiplies
+ * XXX IEC 60027-2 recommends Ki, Mi, Gi...
+ */
+ divisor = 1024;
+ if (flags & HN_B)
+ prefixes = "B\0K\0M\0G\0T\0P\0E";
+ else
+ prefixes = "\0\0K\0M\0G\0T\0P\0E";
+ }
+
+#define SCALE2PREFIX(scale) (&prefixes[(scale) << 1])
+ maxscale = 7;
+
+ if (scale >= maxscale &&
+ (scale & (HN_AUTOSCALE | HN_GETSCALE)) == 0)
+ return (-1);
+
+ if (buf == NULL || suffix == NULL)
+ return (-1);
+
+ if (len > 0)
+ buf[0] = '\0';
+ if (bytes < 0) {
+ sign = -1;
+ scalable *= -100;
+ baselen = 3; /* sign, digit, prefix */
+ } else {
+ sign = 1;
+ scalable *= 100;
+ baselen = 2; /* digit, prefix */
+ }
+ if (flags & HN_NOSPACE)
+ sep = "";
+ else {
+ sep = " ";
+ baselen++;
+ }
+ baselen += strlen(suffix);
+
+ /* Check if enough room for `x y' + suffix + `\0' */
+ if (len < baselen + 1)
+ return (-1);
+
+ if (scale & (HN_AUTOSCALE | HN_GETSCALE)) {
+ /* See if there is additional columns can be used. */
+ for (max = 100, i = len - baselen; i-- > 0;)
+ max *= 10;
+
+ for (i = 0; scalable >= max && i < maxscale; i++)
+ scalable /= divisor;
+
+ if (scale & HN_GETSCALE)
+ return (i);
+ } else
+ for (i = 0; i < scale && i < maxscale; i++)
+ scalable /= divisor;
+
+ /* If a value <= 9.9 after rounding and ... */
+ if (scalable < 995 && i > 0 && flags & HN_DECIMAL) {
+ /* baselen + \0 + .N */
+ if (len < baselen + 1 + 2)
+ return (-1);
+ b = ((int)scalable + 5) / 10;
+ s1 = b / 10;
+ s2 = b % 10;
+ r = snprintf(buf, len, "%s%d%s%d%s%s%s",
+ ((sign == -1) ? "-" : ""),
+ s1, localeconv()->decimal_point, s2,
+ sep, SCALE2PREFIX(i), suffix);
+ } else
+ r = snprintf(buf, len, "%s%lld%s%s%s",
+ /* LONGLONG */
+ ((sign == -1) ? "-" : ""),
+ (long long)((scalable + 50) / 100),
+ sep, SCALE2PREFIX(i), suffix);
+
+ return (r);
+}
diff --git a/contrib/libxo/libxo/xo_message.3 b/contrib/libxo/libxo/xo_message.3
new file mode 100644
index 0000000..36f0148
--- /dev/null
+++ b/contrib/libxo/libxo/xo_message.3
@@ -0,0 +1,68 @@
+.\" #
+.\" # Copyright (c) 2014, Juniper Networks, Inc.
+.\" # All rights reserved.
+.\" # This SOFTWARE is licensed under the LICENSE provided in the
+.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # using the SOFTWARE, you agree to be bound by the terms of that
+.\" # LICENSE.
+.\" # Phil Shafer, July 2014
+.\"
+.Dd December 4, 2014
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_message , xo_message_c , xo_message_hc , xo_message_hcv
+.Nd emit messages in multiple output styles
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_message "const char *fmt" "..."
+.Ft void
+.Fn xo_message_e "const char *fmt" "..."
+.Ft void
+.Fn xo_message_c "int code" "const char *fmt" "..."
+.Ft void
+.Fn xo_message_hc "xo_handle_t *xop" "int code, const char *fmt" "..."
+.Ft void
+.Fn xo_message_hcv "xo_handle_t *xop" "int code" "const char *fmt" "va_list vap"
+.Sh DESCRIPTION
+.Nm xo_message
+generates text message which lack any sort of structure.
+These functions should not be used under normal conditions, since
+they completely defeat the value of using libxo. They are provided
+for scenarios when the output's content is genuinely unknown and
+unusable.
+It is used in converting programs where err/warn where not used,
+and error messages went to
+.Nm stdout ,
+not
+.Nm stderr .
+Use of
+.Nm xo_message
+allows backwards compatibility with that output, but does not put
+the error in a useful form.
+.Pp
+The
+.Nm xo_message
+function generates output strings using the printf-style format string
+and arguments provided.
+If the format string does not end in a newline,
+.Nm xo_message_e
+will appear a colon, a space, and the error associated with the current
+.Nm errno
+value.
+.Nm xo_message_c behaves similarly for the value passed in the
+.Fa code
+parameter.
+.Nm xo_message_hc
+accepts a
+.Fa handle
+as opened by
+.Xr xo_create 3
+and
+.Nm xo_message_hcv accepts a va_list parameter of arguments.
+.Sh SEE ALSO
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_no_setlocale.3 b/contrib/libxo/libxo/xo_no_setlocale.3
index c3f32a4..f91abc0 100644
--- a/contrib/libxo/libxo/xo_no_setlocale.3
+++ b/contrib/libxo/libxo/xo_no_setlocale.3
@@ -12,8 +12,7 @@
.Os
.Sh NAME
.Nm xo_no_setlocale
-.Nd prevent implicit call to
-.Fn setlocale
+.Nd prevent implicit call to setlocale
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -36,33 +35,9 @@ The caller may wish to avoid this behavior, and
can do so by calling the
.Fn xo_no_setlocale
function.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr xo_emit 3 ,
.Xr xo_open_container 3 ,
.Xr xo_open_list 3 ,
-.Xr xo_format 5
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_format 5 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_open_container.3 b/contrib/libxo/libxo/xo_open_container.3
index 3649556..8b016dc 100644
--- a/contrib/libxo/libxo/xo_open_container.3
+++ b/contrib/libxo/libxo/xo_open_container.3
@@ -11,25 +11,13 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_open_container
+.Nm xo_open_container , xo_open_container_h , xo_open_container_hd , xo_open_container_d
+.Nm xo_close_container , xo_close_container_h , xo_close_container_hd , xo_close_container_d
.Nd open (and close) container constructs
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
-.Sh NAME
-.Nm xo_open_container
-.Nm xo_open_container_h
-.Nm xo_open_container_hd
-.Nm xo_open_container_d
-.Nm xo_close_container
-.Nm xo_close_container_h
-.Nm xo_close_container_hd
-.Nm xo_close_container_d
-.Nd open and close containers
-.Sh LIBRARY
-.Lb libxo
-.Sh SYNOPSIS
.Ft int
.Fn xo_open_container "const char *name"
.Ft int
@@ -195,30 +183,6 @@ to track open
containers, lists, and instances.
A warning is generated when the name given to the close function
and the name recorded do not match.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_open_list.3 b/contrib/libxo/libxo/xo_open_list.3
index 047af87..7930dd1 100644
--- a/contrib/libxo/libxo/xo_open_list.3
+++ b/contrib/libxo/libxo/xo_open_list.3
@@ -11,33 +11,15 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
-.Sh LIBRARY
-.Lb libxo
-.Sh SYNOPSIS
-.In libxo/xo.h
-.Sh NAME
-.Nm xo_open_list
-.Nm xo_open_list_h
-.Nm xo_open_list_hd
-.Nm xo_open_list_d
-.Nm xo_open_instance
-.Nm xo_open_instance_h
-.Nm xo_open_instance_hd
-.Nm xo_open_instance_d
-.Nm xo_close_instance
-.Nm xo_close_instance_h
-.Nm xo_close_instance_hd
-.Nm xo_close_instance_d
-.Nm xo_close_list
-.Nm xo_close_list_h
-.Nm xo_close_list_hd
-.Nm xo_close_list_d
+.Nm xo_open_list , xo_open_list_h , xo_open_list_hd , xo_open_list_d
+.Nm xo_open_instance , xo_open_instance_h , xo_open_instance_hd , xo_open_instance_d
+.Nm xo_close_instance , xo_close_instance_h , xo_close_instance_hd , xo_close_instnace_d
+.Nm xo_close_list , xo_close_list_h , xo_close_list_hd , xo_close_list_d
.Nd open and close lists and instances
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
+.In libxo/xo.h
.Ft int
.Fn xo_open_list_h "xo_handle_t *xop" "const char *name"
.Ft int
@@ -171,30 +153,6 @@ are rendered as multiple leaf elements.
<item>hammer</item>
<item>nail</item>
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_open_marker.3 b/contrib/libxo/libxo/xo_open_marker.3
index d7a858c..e7356ba 100644
--- a/contrib/libxo/libxo/xo_open_marker.3
+++ b/contrib/libxo/libxo/xo_open_marker.3
@@ -11,21 +11,12 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_open_marker
+.Nm xo_open_marker , xo_open_marker_h , xo_close_marker , xo_close_marker_h
.Nd prevent and allow closing of open constructs
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
.In libxo/xo.h
-.Sh NAME
-.Nm xo_open_marker
-.Nm xo_open_marker_h
-.Nm xo_close_marker
-.Nm xo_close_marker_h
-.Nd open and close markers
-.Sh LIBRARY
-.Lb libxo
-.Sh SYNOPSIS
.Ft int
.Fn xo_open_marker "const char *name"
.Ft int
@@ -109,30 +100,6 @@ properly.
xo_close_marker("fish-guts");
}
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_parse_args.3 b/contrib/libxo/libxo/xo_parse_args.3
index f66546b..80dceca 100644
--- a/contrib/libxo/libxo/xo_parse_args.3
+++ b/contrib/libxo/libxo/xo_parse_args.3
@@ -11,7 +11,7 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_parse_args
+.Nm xo_parse_args , xo_set_program
.Nd detect, parse, and remove arguments for libxo
.Sh LIBRARY
.Lb libxo
@@ -35,7 +35,7 @@ On failure, a message it emitted and -1 is returned.
.Bd -literal -offset indent
argc = xo_parse_args(argc, argv);
if (argc < 0)
- exit(1);
+ exit(EXIT_FAILURE);
.Ed
.Pp
Following the call to
@@ -72,6 +72,15 @@ Add info attributes (HTML)
Emit JSON output
.It Dv keys
Emit the key attribute for keys (XML)
+.It Dv log-gettext
+Log (via stderr) each
+.Xr gettext 3
+string lookup
+.It Dv log-syslog
+Log (via stderr) each syslog message (via
+.Xr xo_syslog 3 )
+.If Dv no-humanize
+Ignore the {h:} modifier (TEXT, HTML)
.It Dv no-locale
Do not initialize the locale setting
.It Dv no-top
@@ -82,6 +91,8 @@ Pretend the 1st output item was not 1st (JSON)
Emit pretty-printed output
.It Dv text
Emit TEXT output
+.If Dv underscores
+Replace XML-friendly "-"s with JSON friendly "_"s e
.It Dv units
Add the 'units' (XML) or 'data-units (HTML) attribute
.It Dv warn
@@ -132,30 +143,6 @@ Note that the value is not copied, so the memory passed to
.Fn xo_parse_args )
must be maintained by the caller.
.Pp
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_allocator.3 b/contrib/libxo/libxo/xo_set_allocator.3
index 70bfdd5..c20a0f5 100644
--- a/contrib/libxo/libxo/xo_set_allocator.3
+++ b/contrib/libxo/libxo/xo_set_allocator.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_set_allocator
+.Nd set allocation functions for libxo
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -49,30 +49,6 @@ By default, the standard
and
.Xr free 3
functions are used.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_flags.3 b/contrib/libxo/libxo/xo_set_flags.3
index ca66553..52997c5 100644
--- a/contrib/libxo/libxo/xo_set_flags.3
+++ b/contrib/libxo/libxo/xo_set_flags.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_set_flags , xo_clear_flags
+.Nd set operational flags for a libxo handle
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -42,12 +42,21 @@ This flag will trigger the call of the
(provided via
.Xr xo_set_writer 3 )
when the handle is destroyed.
-.It Dv XOF_DTRT
-Enable "do the right thing" mode
+.It Dv XOF_COLOR
+Enable color and effects in output regardless of output device.
+.It Dv XOF_COLOR_ALLOWED
+Allow color and effects if the output device is a terminal.
.It Dv XOF_INFO
Display info data attributes (HTML)
.It Dv XOF_KEYS
Emit the key attribute (XML)
+.It Dv XOF_LOG_GETTEXT
+Log (via stderr) each
+.Xr gettext 3
+string lookup
+.It Dv XOF_LOG_SYSLOG
+Log (via stderr) each syslog message (via
+.Xr xo_syslog 3 )
.It Dv XOF_NO_ENV
Do not use the
.Ev LIBXO_OPTIONS
@@ -125,30 +134,6 @@ The
.Fn xo_clear_flags
function turns off the given flags in a specific
handle.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_info.3 b/contrib/libxo/libxo/xo_set_info.3
index 4f8c587..8ea0657 100644
--- a/contrib/libxo/libxo/xo_set_info.3
+++ b/contrib/libxo/libxo/xo_set_info.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_set_info
+.Nd set the field information data for libxo
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -97,30 +97,6 @@ and "data-help" attributes:
<div class="data" data-tag="sku" data-type="string"
data-help="Stock Keeping Unit">GRO-000-533</div>
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_options.3 b/contrib/libxo/libxo/xo_set_options.3
index bedbf91..5b7c8ed 100644
--- a/contrib/libxo/libxo/xo_set_options.3
+++ b/contrib/libxo/libxo/xo_set_options.3
@@ -12,7 +12,7 @@
.Os
.Sh NAME
.Nm xo_set_options
-.Nd change options used by a handle
+.Nd change options used by a libxo handle
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -26,30 +26,6 @@ function accepts a comma-separated list of styles
and flags and enables them for a specific handle.
The options are identical to those listed in
.Xr xo_parse_args 3 .
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_style.3 b/contrib/libxo/libxo/xo_set_style.3
index f11f190..8e34033 100644
--- a/contrib/libxo/libxo/xo_set_style.3
+++ b/contrib/libxo/libxo/xo_set_style.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_set_style , xo_set_style_name
+.Nd set the output style for a libxo handle
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -48,30 +48,6 @@ The name can be any of the styles: "text", "xml", "json", or "html".
EXAMPLE:
xo_set_style_name(NULL, "html");
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_syslog_enterprise_id.3 b/contrib/libxo/libxo/xo_set_syslog_enterprise_id.3
new file mode 100644
index 0000000..da2eed7
--- /dev/null
+++ b/contrib/libxo/libxo/xo_set_syslog_enterprise_id.3
@@ -0,0 +1,36 @@
+.\" #
+.\" # Copyright (c) 2015, Juniper Networks, Inc.
+.\" # All rights reserved.
+.\" # This SOFTWARE is licensed under the LICENSE provided in the
+.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # using the SOFTWARE, you agree to be bound by the terms of that
+.\" # LICENSE.
+.\" # Phil Shafer, July 2015
+.\"
+.Dd July 20, 2015
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_set_syslog_enterprise_id
+.Nd Set the enterprise identifier for syslog content
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_set_syslog_enterprise_id "unsigned short eid"
+.Ft void
+.Sh DESCRIPTION
+The
+.Fn xo_set_syslog_enterprise_id
+function records an enterprise identifier used for subsequent
+.Xr xo_syslog 3
+calls.
+Enterprise IDs are
+defined by IANA, the Internet Assigned Numbers Authority:
+.Bd -literal -offset indent
+https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
+.Ed
+.Sh SEE ALSO
+.Xr xo_syslog 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_set_version.3 b/contrib/libxo/libxo/xo_set_version.3
index 888aef5..5f9394d 100644
--- a/contrib/libxo/libxo/xo_set_version.3
+++ b/contrib/libxo/libxo/xo_set_version.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_set_version
-.Nd record content version information in encoded output
+.Nm xo_set_version , xo_set_version_h
+.Nd record content-version information in encoded output
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -29,31 +29,6 @@ part of the data for encoding styles (XML and JSON).
This version number is suitable for tracking changes in the content,
allowing a user of the data to discern which version of the data model
is in use.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr xo_emit 3 ,
.Xr libxo 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
diff --git a/contrib/libxo/libxo/xo_set_writer.3 b/contrib/libxo/libxo/xo_set_writer.3
index 942bcc2..2f93bd9 100644
--- a/contrib/libxo/libxo/xo_set_writer.3
+++ b/contrib/libxo/libxo/xo_set_writer.3
@@ -11,8 +11,8 @@
.Dt LIBXO 3
.Os
.Sh NAME
-.Nm xo_emit
-.Nd emit formatted output based on format string and arguments
+.Nm xo_set_writer
+.Nd set custom writer functions for a libxo handle
.Sh LIBRARY
.Lb libxo
.Sh SYNOPSIS
@@ -51,30 +51,6 @@ The
.Fa flush_func
function should
flush any pending data associated with the opaque pointer.
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
-.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_syslog.3 b/contrib/libxo/libxo/xo_syslog.3
new file mode 100644
index 0000000..db92d36
--- /dev/null
+++ b/contrib/libxo/libxo/xo_syslog.3
@@ -0,0 +1,79 @@
+.\" #
+.\" # Copyright (c) 2015, Juniper Networks, Inc.
+.\" # All rights reserved.
+.\" # This SOFTWARE is licensed under the LICENSE provided in the
+.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # using the SOFTWARE, you agree to be bound by the terms of that
+.\" # LICENSE.
+.\" # Phil Shafer, July 2015
+.\"
+.Dd July 20, 2015
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_syslog , xo_vsyslog , xo_open_log , xo_close_log , xo_set_logmask
+.Nd create SYSLOG (RFC5424) log records using libxo formatting
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_syslog "int pri" "const char *name" "const char *fmt" "..."
+.Ft void
+.Fn xo_vsyslog "int pri" "const char *name" "const char *fmt" "va_list vap"
+.Ft void
+.Fn xo_close_log "void"
+.Ft void
+.Fn xo_open_log "const char *ident" "int logstat" "int logfac"
+.Ft int
+.Fn xo_set_logmask "int pmask"
+.Sh DESCRIPTION
+The
+.Fn xo_syslog
+function creates log entries following the standard defined in
+RFC5424.
+These messages are sent to the log
+.Xr syslogd 8
+daemon, where they can be filtered, forwarded, and archived.
+.Nm libxo
+format strings are used to create both the message text and the
+.Nm SD-PARAMS
+content, containing name/value pairs that can be parsed by suitable
+automation software.
+.Pp
+Refer to
+.Xr xo_format 5
+for basic information about formatting strings.
+.Nm xo_syslog
+encodes all value fields at SD-PARAMS within the syslog message.
+An exception is made for fields with the "{d:}" modifier; such fields
+appear in the message text only, with fields with the "{e:}" modifier
+appear as SD-PARAMS, but not in the message text.
+.Pp
+.Fn xo_vsyslog
+accepts a
+.Fa va_list
+for additional flexibility.
+.Pp
+.Fn xo_open_log ,
+.Fn xo_close_log , and
+.Fn xo_set_logmask
+are all analogous to their libs counterparts,
+.Xr openlog 3 ,
+.Xr closelog 3 , and
+.Xr setlogmask 3 .
+The extra underscores in the names are unfortunate, but keep
+consistency in
+.Nm libxo
+function names.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+ xo_syslog(LOG_LOCAL4 | LOG_NOTICE, "ID47",
+ "{e:iut/%u}An {:event-source} {:event-id/%u} log entry",
+ iut, source, id);
+.Ed
+.Sh SEE ALSO
+.Xr xo_syslog 3 ,
+.Xr xo_set_syslog_enterprise_id 3 ,
+.Xr xo_format 5 ,
+.Xr libxo 3
diff --git a/contrib/libxo/libxo/xo_syslog.c b/contrib/libxo/libxo/xo_syslog.c
new file mode 100644
index 0000000..da53e0c
--- /dev/null
+++ b/contrib/libxo/libxo/xo_syslog.c
@@ -0,0 +1,706 @@
+/*
+ * Copyright (c) 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, June 2015
+ */
+
+/*
+ * Portions of this file are:
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include "xo_config.h"
+#include "xo.h"
+#include "xo_encoder.h" /* For xo_realloc */
+#include "xo_buf.h"
+
+/*
+ * SYSLOG (RFC 5424) requires an enterprise identifier. This turns
+ * out to be a fickle little issue. For a single-vendor box, the
+ * system should have a single EID that all software can use. When
+ * VendorX turns FreeBSD into a product, all software (kernel and
+ * utilities) should report VendorX's EID. But when software is
+ * installed on top of an external operating system, the application
+ * should report it's own EID, distinct from the base OS.
+ *
+ * To make this happen, the kernel should support a sysctl to assign a
+ * custom enterprise-id ("kern.syslog.enterprise_id"). libxo then
+ * allows an application to set a custom EID to override that system
+ * wide value, if needed.
+ *
+ * We try to set the stock IANA assigned Enterprise ID value for the
+ * vendors we know about (FreeBSD, macosx), but fallback to the
+ * "example" EID defined by IANA. See:
+ * https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
+ */
+
+#define XO_SYSLOG_ENTERPRISE_ID "kern.syslog.enterprise_id"
+
+#if defined(__FreeBSD__)
+#define XO_DEFAULT_EID 2238
+#elif defined(__macosx__)
+#define XO_DEFAULT_EID 63
+#else
+#define XO_DEFAULT_EID 32473 /* Bail; use "example" number */
+#endif
+
+#ifdef _SC_HOST_NAME_MAX
+#define HOST_NAME_MAX _SC_HOST_NAME_MAX
+#else
+#define HOST_NAME_MAX 255
+#endif /* _SC_HOST_NAME_MAX */
+
+#ifndef UNUSED
+#define UNUSED __attribute__ ((__unused__))
+#endif /* UNUSED */
+
+static int xo_logfile = -1; /* fd for log */
+static int xo_status; /* connection xo_status */
+static int xo_opened; /* have done openlog() */
+static int xo_logstat = 0; /* xo_status bits, set by openlog() */
+static const char *xo_logtag = NULL; /* string to tag the entry with */
+static int xo_logfacility = LOG_USER; /* default facility code */
+static int xo_logmask = 0xff; /* mask of priorities to be logged */
+static pthread_mutex_t xo_syslog_mutex UNUSED = PTHREAD_MUTEX_INITIALIZER;
+static int xo_unit_test; /* Fake data for unit test */
+
+#define REAL_VOID(_x) \
+ do { int really_ignored = _x; if (really_ignored) { }} while (0)
+
+#if !defined(HAVE_DECL___ISTHREADED) || !HAVE_DECL___ISTHREADED
+#define __isthreaded 1
+#endif
+
+#define THREAD_LOCK() \
+ do { \
+ if (__isthreaded) pthread_mutex_lock(&xo_syslog_mutex); \
+ } while(0)
+#define THREAD_UNLOCK() \
+ do { \
+ if (__isthreaded) pthread_mutex_unlock(&xo_syslog_mutex); \
+ } while(0)
+
+static void xo_disconnect_log(void); /* disconnect from syslogd */
+static void xo_connect_log(void); /* (re)connect to syslogd */
+static void xo_open_log_unlocked(const char *, int, int);
+
+enum {
+ NOCONN = 0,
+ CONNDEF,
+ CONNPRIV,
+};
+
+static xo_syslog_open_t xo_syslog_open;
+static xo_syslog_send_t xo_syslog_send;
+static xo_syslog_close_t xo_syslog_close;
+
+static char xo_syslog_enterprise_id[12];
+
+/*
+ * Record an enterprise ID, which functions as a namespace for syslog
+ * messages. The value is pre-formatted into a string. This allows
+ * applications to customize their syslog message set, when needed.
+ */
+void
+xo_set_syslog_enterprise_id (unsigned short eid)
+{
+ snprintf(xo_syslog_enterprise_id, sizeof(xo_syslog_enterprise_id),
+ "%u", eid);
+}
+
+/*
+ * Handle the work of transmitting the syslog message
+ */
+static void
+xo_send_syslog (char *full_msg, char *v0_hdr,
+ char *text_only)
+{
+ if (xo_syslog_send) {
+ xo_syslog_send(full_msg, v0_hdr, text_only);
+ return;
+ }
+
+ int fd;
+ int full_len = strlen(full_msg);
+
+ /* Output to stderr if requested. */
+ if (xo_logstat & LOG_PERROR) {
+ struct iovec iov[3];
+ struct iovec *v = iov;
+ char newline[] = "\n";
+
+ v->iov_base = v0_hdr;
+ v->iov_len = strlen(v0_hdr);
+ v += 1;
+ v->iov_base = text_only;
+ v->iov_len = strlen(text_only);
+ v += 1;
+ v->iov_base = newline;
+ v->iov_len = 1;
+ v += 1;
+ REAL_VOID(writev(STDERR_FILENO, iov, 3));
+ }
+
+ /* Get connected, output the message to the local logger. */
+ if (!xo_opened)
+ xo_open_log_unlocked(xo_logtag, xo_logstat | LOG_NDELAY, 0);
+ xo_connect_log();
+
+ /*
+ * If the send() fails, there are two likely scenarios:
+ * 1) syslogd was restarted
+ * 2) /var/run/log is out of socket buffer space, which
+ * in most cases means local DoS.
+ * If the error does not indicate a full buffer, we address
+ * case #1 by attempting to reconnect to /var/run/log[priv]
+ * and resending the message once.
+ *
+ * If we are working with a privileged socket, the retry
+ * attempts end there, because we don't want to freeze a
+ * critical application like su(1) or sshd(8).
+ *
+ * Otherwise, we address case #2 by repeatedly retrying the
+ * send() to give syslogd a chance to empty its socket buffer.
+ */
+
+ if (send(xo_logfile, full_msg, full_len, 0) < 0) {
+ if (errno != ENOBUFS) {
+ /*
+ * Scenario 1: syslogd was restarted
+ * reconnect and resend once
+ */
+ xo_disconnect_log();
+ xo_connect_log();
+ if (send(xo_logfile, full_msg, full_len, 0) >= 0) {
+ return;
+ }
+ /*
+ * if the resend failed, fall through to
+ * possible scenario 2
+ */
+ }
+ while (errno == ENOBUFS) {
+ /*
+ * Scenario 2: out of socket buffer space
+ * possible DoS, fail fast on a privileged
+ * socket
+ */
+ if (xo_status == CONNPRIV)
+ break;
+ usleep(1);
+ if (send(xo_logfile, full_msg, full_len, 0) >= 0) {
+ return;
+ }
+ }
+ } else {
+ return;
+ }
+
+ /*
+ * Output the message to the console; try not to block
+ * as a blocking console should not stop other processes.
+ * Make sure the error reported is the one from the syslogd failure.
+ */
+ int flags = O_WRONLY | O_NONBLOCK;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif /* O_CLOEXEC */
+
+ if (xo_logstat & LOG_CONS
+ && (fd = open(_PATH_CONSOLE, flags, 0)) >= 0) {
+ struct iovec iov[2];
+ struct iovec *v = iov;
+ char crnl[] = "\r\n";
+ char *p;
+
+ p = strchr(full_msg, '>') + 1;
+ v->iov_base = p;
+ v->iov_len = full_len - (p - full_msg);
+ ++v;
+ v->iov_base = crnl;
+ v->iov_len = 2;
+ REAL_VOID(writev(fd, iov, 2));
+ (void) close(fd);
+ }
+}
+
+/* Should be called with mutex acquired */
+static void
+xo_disconnect_log (void)
+{
+ if (xo_syslog_close) {
+ xo_syslog_close();
+ return;
+ }
+
+ /*
+ * If the user closed the FD and opened another in the same slot,
+ * that's their problem. They should close it before calling on
+ * system services.
+ */
+ if (xo_logfile != -1) {
+ close(xo_logfile);
+ xo_logfile = -1;
+ }
+ xo_status = NOCONN; /* retry connect */
+}
+
+/* Should be called with mutex acquired */
+static void
+xo_connect_log (void)
+{
+ if (xo_syslog_open) {
+ xo_syslog_open();
+ return;
+ }
+
+ struct sockaddr_un saddr; /* AF_UNIX address of local logger */
+
+ if (xo_logfile == -1) {
+ int flags = SOCK_DGRAM;
+#ifdef SOCK_CLOEXEC
+ flags |= SOCK_CLOEXEC;
+#endif /* SOCK_CLOEXEC */
+ if ((xo_logfile = socket(AF_UNIX, flags, 0)) == -1)
+ return;
+ }
+ if (xo_logfile != -1 && xo_status == NOCONN) {
+#ifdef HAVE_SUN_LEN
+ saddr.sun_len = sizeof(saddr);
+#endif /* HAVE_SUN_LEN */
+ saddr.sun_family = AF_UNIX;
+
+ /*
+ * First try privileged socket. If no success,
+ * then try default socket.
+ */
+
+#ifdef _PATH_LOG_PRIV
+ (void) strncpy(saddr.sun_path, _PATH_LOG_PRIV,
+ sizeof saddr.sun_path);
+ if (connect(xo_logfile, (struct sockaddr *) &saddr,
+ sizeof(saddr)) != -1)
+ xo_status = CONNPRIV;
+#endif /* _PATH_LOG_PRIV */
+
+#ifdef _PATH_LOG
+ if (xo_status == NOCONN) {
+ (void) strncpy(saddr.sun_path, _PATH_LOG,
+ sizeof saddr.sun_path);
+ if (connect(xo_logfile, (struct sockaddr *)&saddr,
+ sizeof(saddr)) != -1)
+ xo_status = CONNDEF;
+ }
+#endif /* _PATH_LOG */
+
+#ifdef _PATH_OLDLOG
+ if (xo_status == NOCONN) {
+ /*
+ * Try the old "/dev/log" path, for backward
+ * compatibility.
+ */
+ (void) strncpy(saddr.sun_path, _PATH_OLDLOG,
+ sizeof saddr.sun_path);
+ if (connect(xo_logfile, (struct sockaddr *)&saddr,
+ sizeof(saddr)) != -1)
+ xo_status = CONNDEF;
+ }
+#endif /* _PATH_OLDLOG */
+
+ if (xo_status == NOCONN) {
+ (void) close(xo_logfile);
+ xo_logfile = -1;
+ }
+ }
+}
+
+static void
+xo_open_log_unlocked (const char *ident, int logstat, int logfac)
+{
+ if (ident != NULL)
+ xo_logtag = ident;
+ xo_logstat = logstat;
+ if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
+ xo_logfacility = logfac;
+
+ if (xo_logstat & LOG_NDELAY) /* open immediately */
+ xo_connect_log();
+
+ xo_opened = 1; /* ident and facility has been set */
+}
+
+void
+xo_open_log (const char *ident, int logstat, int logfac)
+{
+ THREAD_LOCK();
+ xo_open_log_unlocked(ident, logstat, logfac);
+ THREAD_UNLOCK();
+}
+
+
+void
+xo_close_log (void)
+{
+ THREAD_LOCK();
+ if (xo_logfile != -1) {
+ (void) close(xo_logfile);
+ xo_logfile = -1;
+ }
+ xo_logtag = NULL;
+ xo_status = NOCONN;
+ THREAD_UNLOCK();
+}
+
+/* xo_set_logmask -- set the log mask level */
+int
+xo_set_logmask (int pmask)
+{
+ int omask;
+
+ THREAD_LOCK();
+ omask = xo_logmask;
+ if (pmask != 0)
+ xo_logmask = pmask;
+ THREAD_UNLOCK();
+ return (omask);
+}
+
+void
+xo_set_syslog_handler (xo_syslog_open_t open_func,
+ xo_syslog_send_t send_func,
+ xo_syslog_close_t close_func)
+{
+ xo_syslog_open = open_func;
+ xo_syslog_send = send_func;
+ xo_syslog_close = close_func;
+}
+
+static size_t
+xo_snprintf (char *out, size_t outsize, const char *fmt, ...)
+{
+ int status;
+ size_t retval = 0;
+ va_list ap;
+ if (out && outsize) {
+ va_start(ap, fmt);
+ status = vsnprintf(out, outsize, fmt, ap);
+ if (status < 0) { /* this should never happen, */
+ *out = 0; /* handle it in the safest way possible if it does */
+ retval = 0;
+ } else {
+ retval = status;
+ retval = retval > outsize ? outsize : retval;
+ }
+ va_end(ap);
+ }
+ return retval;
+}
+
+static int
+xo_syslog_handle_write (void *opaque, const char *data)
+{
+ xo_buffer_t *xbp = opaque;
+ int len = strlen(data);
+ int left = xo_buf_left(xbp);
+
+ if (len > left - 1)
+ len = left - 1;
+
+ memcpy(xbp->xb_curp, data, len);
+ xbp->xb_curp += len;
+ *xbp->xb_curp = '\0';
+
+ return len;
+}
+
+static void
+xo_syslog_handle_close (void *opaque UNUSED)
+{
+}
+
+static int
+xo_syslog_handle_flush (void *opaque UNUSED)
+{
+ return 0;
+}
+
+void
+xo_set_unit_test_mode (int value)
+{
+ xo_unit_test = value;
+}
+
+void
+xo_vsyslog (int pri, const char *name, const char *fmt, va_list vap)
+{
+ int saved_errno = errno;
+ char tbuf[2048];
+ char *tp = NULL, *ep = NULL;
+ unsigned start_of_msg = 0;
+ char *v0_hdr = NULL;
+ xo_buffer_t xb;
+ static pid_t my_pid;
+ unsigned log_offset;
+
+ if (my_pid == 0)
+ my_pid = xo_unit_test ? 222 : getpid();
+
+ /* Check for invalid bits */
+ if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
+ xo_syslog(LOG_ERR | LOG_CONS | LOG_PERROR | LOG_PID,
+ "syslog-unknown-priority",
+ "syslog: unknown facility/priority: %#x", pri);
+ pri &= LOG_PRIMASK|LOG_FACMASK;
+ }
+
+ THREAD_LOCK();
+
+ /* Check priority against setlogmask values. */
+ if (!(LOG_MASK(LOG_PRI(pri)) & xo_logmask)) {
+ THREAD_UNLOCK();
+ return;
+ }
+
+ /* Set default facility if none specified. */
+ if ((pri & LOG_FACMASK) == 0)
+ pri |= xo_logfacility;
+
+ /* Create the primary stdio hook */
+ xb.xb_bufp = tbuf;
+ xb.xb_curp = tbuf;
+ xb.xb_size = sizeof(tbuf);
+
+ xo_handle_t *xop = xo_create(XO_STYLE_SDPARAMS, 0);
+ if (xop == NULL) {
+ THREAD_UNLOCK();
+ return;
+ }
+
+#ifdef HAVE_GETPROGNAME
+ if (xo_logtag == NULL)
+ xo_logtag = getprogname();
+#endif /* HAVE_GETPROGNAME */
+
+ xo_set_writer(xop, &xb, xo_syslog_handle_write, xo_syslog_handle_close,
+ xo_syslog_handle_flush);
+
+ /* Build the message; start by getting the time */
+ struct tm tm;
+ struct timeval tv;
+
+ /* Unit test hack: fake a fixed time */
+ if (xo_unit_test) {
+ tv.tv_sec = 1435085229;
+ tv.tv_usec = 123456;
+ } else
+ gettimeofday(&tv, NULL);
+
+ (void) localtime_r(&tv.tv_sec, &tm);
+
+ if (xo_logstat & LOG_PERROR) {
+ /*
+ * For backwards compatibility, we need to make the old-style
+ * message. This message can be emitted to the console/tty.
+ */
+ v0_hdr = alloca(2048);
+ tp = v0_hdr;
+ ep = v0_hdr + 2048;
+
+ if (xo_logtag != NULL)
+ tp += xo_snprintf(tp, ep - tp, "%s", xo_logtag);
+ if (xo_logstat & LOG_PID)
+ tp += xo_snprintf(tp, ep - tp, "[%d]", my_pid);
+ if (xo_logtag)
+ tp += xo_snprintf(tp, ep - tp, ": ");
+ }
+
+ log_offset = xb.xb_curp - xb.xb_bufp;
+
+ /* Add PRI, PRIVAL, and VERSION */
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "<%d>1 ", pri);
+
+ /* Add TIMESTAMP with milliseconds and TZOFFSET */
+ xb.xb_curp += strftime(xb.xb_curp, xo_buf_left(&xb), "%FT%T", &tm);
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb),
+ ".%03.3u", tv.tv_usec / 1000);
+ xb.xb_curp += strftime(xb.xb_curp, xo_buf_left(&xb), "%z ", &tm);
+
+ /*
+ * Add HOSTNAME; we rely on gethostname and don't fluff with
+ * ip addresses. Might need to revisit.....
+ */
+ char hostname[HOST_NAME_MAX];
+ hostname[0] = '\0';
+ if (xo_unit_test)
+ strcpy(hostname, "worker-host");
+ else
+ (void) gethostname(hostname, sizeof(hostname));
+
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s ",
+ hostname[0] ? hostname : "-");
+
+ /* Add APP-NAME */
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s ",
+ xo_logtag ?: "-");
+
+ /* Add PROCID */
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%d ", my_pid);
+
+ /*
+ * Add MSGID. The user should provide us with a name, which we
+ * prefix with the current enterprise ID, as learned from the kernel.
+ * If the kernel won't tell us, we use the stock/builtin number.
+ */
+ char *buf UNUSED = NULL;
+ const char *eid = xo_syslog_enterprise_id;
+ const char *at_sign = "@";
+
+ if (name == NULL) {
+ name = "-";
+ eid = at_sign = "";
+
+ } else if (*name == '@') {
+ /* Our convention is to prefix IANA-defined names with an "@" */
+ name += 1;
+ eid = at_sign = "";
+
+ } else if (eid[0] == '\0') {
+#ifdef HAVE_SYSCTLBYNAME
+ /*
+ * See if the kernel knows the sysctl for the enterprise ID
+ */
+ size_t size = 0;
+ if (sysctlbyname(XO_SYSLOG_ENTERPRISE_ID, NULL, &size, NULL, 0) == 0
+ && size > 0) {
+ buf = alloca(size);
+ if (sysctlbyname(XO_SYSLOG_ENTERPRISE_ID, buf, &size, NULL, 0) == 0
+ && size > 0)
+ eid = buf;
+ }
+#endif /* HAVE_SYSCTLBYNAME */
+
+ if (eid[0] == '\0') {
+ /* Fallback to our base default */
+ xo_set_syslog_enterprise_id(XO_DEFAULT_EID);
+ eid = xo_syslog_enterprise_id;
+ }
+ }
+
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s [%s%s%s ",
+ name, name, at_sign, eid);
+
+ /*
+ * Now for the real content. We make two distinct passes thru the
+ * xo_emit engine, first for the SD-PARAMS and then for the text
+ * message.
+ */
+ va_list ap;
+ va_copy(ap, vap);
+
+ errno = saved_errno; /* Restore saved error value */
+ xo_emit_hv(xop, fmt, ap);
+ xo_flush_h(xop);
+
+ va_end(ap);
+
+ /* Trim trailing space */
+ if (xb.xb_curp[-1] == ' ')
+ xb.xb_curp -= 1;
+
+ /* Close the structured data (SD-ELEMENT) */
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "] ");
+
+ /*
+ * Since our MSG is known to be UTF-8, we MUST prefix it with
+ * that most-annoying-of-all-UTF-8 features, the BOM (0xEF.BB.BF).
+ */
+ xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb),
+ "%c%c%c", 0xEF, 0xBB, 0xBF);
+
+ /* Save the start of the message */
+ if (xo_logstat & LOG_PERROR)
+ start_of_msg = xb.xb_curp - xb.xb_bufp;
+
+ xo_set_style(xop, XO_STYLE_TEXT);
+ xo_set_flags(xop, XOF_UTF8);
+
+ errno = saved_errno; /* Restore saved error value */
+ xo_emit_hv(xop, fmt, ap);
+ xo_flush_h(xop);
+
+ /* Remove a trailing newline */
+ if (xb.xb_curp[-1] == '\n')
+ *--xb.xb_curp = '\0';
+
+ if (xo_get_flags(xop) & XOF_LOG_SYSLOG)
+ fprintf(stderr, "xo: syslog: %s\n", xb.xb_bufp + log_offset);
+
+ xo_send_syslog(xb.xb_bufp, v0_hdr, xb.xb_bufp + start_of_msg);
+
+ xo_destroy(xop);
+
+ THREAD_UNLOCK();
+}
+
+/*
+ * syslog - print message on log file; output is intended for syslogd(8).
+ */
+void
+xo_syslog (int pri, const char *name, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ xo_vsyslog(pri, name, fmt, ap);
+ va_end(ap);
+}
diff --git a/contrib/libxo/libxo/xo_wcwidth.h b/contrib/libxo/libxo/xo_wcwidth.h
new file mode 100644
index 0000000..773307f
--- /dev/null
+++ b/contrib/libxo/libxo/xo_wcwidth.h
@@ -0,0 +1,313 @@
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <wchar.h>
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int
+xo_bisearch (wchar_t ucs, const struct interval *table, int max)
+{
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static int
+xo_wcwidth (wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (xo_bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+
+#if UNUSED_CODE
+static int xo_wcswidth(const wchar_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcswidth(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+int mk_wcwidth_cjk(wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
+ static const struct interval ambiguous[] = {
+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
+ { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
+ { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
+ { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
+ { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
+ { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
+ { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
+ { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (xo_bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+
+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+#endif /* UNUSED_CODE */
diff --git a/contrib/libxo/libxo/xoconfig.h.in b/contrib/libxo/libxo/xoconfig.h.in
deleted file mode 100644
index ad992f3..0000000
--- a/contrib/libxo/libxo/xoconfig.h.in
+++ /dev/null
@@ -1,206 +0,0 @@
-/* libxo/xoconfig.h.in. Generated from configure.ac by autoheader. */
-
-/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
- systems. This function is required for `alloca.c' support on those systems.
- */
-#undef CRAY_STACKSEG_END
-
-/* Define to 1 if using `alloca.c'. */
-#undef C_ALLOCA
-
-/* Define to 1 if you have `alloca', as a function or macro. */
-#undef HAVE_ALLOCA
-
-/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
- */
-#undef HAVE_ALLOCA_H
-
-/* Define to 1 if you have the `asprintf' function. */
-#undef HAVE_ASPRINTF
-
-/* Define to 1 if you have the `bzero' function. */
-#undef HAVE_BZERO
-
-/* Define to 1 if you have the `ctime' function. */
-#undef HAVE_CTIME
-
-/* Define to 1 if you have the <ctype.h> header file. */
-#undef HAVE_CTYPE_H
-
-/* Define to 1 if you have the <dlfcn.h> header file. */
-#undef HAVE_DLFCN_H
-
-/* Define to 1 if you have the `dlfunc' function. */
-#undef HAVE_DLFUNC
-
-/* Define to 1 if you have the <errno.h> header file. */
-#undef HAVE_ERRNO_H
-
-/* Define to 1 if you have the `fdopen' function. */
-#undef HAVE_FDOPEN
-
-/* Define to 1 if you have the `flock' function. */
-#undef HAVE_FLOCK
-
-/* Define to 1 if you have the `getpass' function. */
-#undef HAVE_GETPASS
-
-/* Define to 1 if you have the `getrusage' function. */
-#undef HAVE_GETRUSAGE
-
-/* Define to 1 if you have the `gettimeofday' function. */
-#undef HAVE_GETTIMEOFDAY
-
-/* Define to 1 if you have the <inttypes.h> header file. */
-#undef HAVE_INTTYPES_H
-
-/* Define to 1 if you have the `crypto' library (-lcrypto). */
-#undef HAVE_LIBCRYPTO
-
-/* Define to 1 if you have the `m' library (-lm). */
-#undef HAVE_LIBM
-
-/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
- to 0 otherwise. */
-#undef HAVE_MALLOC
-
-/* Define to 1 if you have the `memmove' function. */
-#undef HAVE_MEMMOVE
-
-/* Define to 1 if you have the <memory.h> header file. */
-#undef HAVE_MEMORY_H
-
-/* Support printflike */
-#undef HAVE_PRINTFLIKE
-
-/* Define to 1 if your system has a GNU libc compatible `realloc' function,
- and to 0 otherwise. */
-#undef HAVE_REALLOC
-
-/* Define to 1 if you have the `srand' function. */
-#undef HAVE_SRAND
-
-/* Define to 1 if you have the `sranddev' function. */
-#undef HAVE_SRANDDEV
-
-/* Define to 1 if you have the <stdint.h> header file. */
-#undef HAVE_STDINT_H
-
-/* Define to 1 if you have the <stdio_ext.h> header file. */
-#undef HAVE_STDIO_EXT_H
-
-/* Define to 1 if you have the <stdio.h> header file. */
-#undef HAVE_STDIO_H
-
-/* Define to 1 if you have the <stdlib.h> header file. */
-#undef HAVE_STDLIB_H
-
-/* Define to 1 if you have the <stdtime/tzfile.h> header file. */
-#undef HAVE_STDTIME_TZFILE_H
-
-/* Define to 1 if you have the `strchr' function. */
-#undef HAVE_STRCHR
-
-/* Define to 1 if you have the `strcspn' function. */
-#undef HAVE_STRCSPN
-
-/* Define to 1 if you have the `strerror' function. */
-#undef HAVE_STRERROR
-
-/* Define to 1 if you have the <strings.h> header file. */
-#undef HAVE_STRINGS_H
-
-/* Define to 1 if you have the <string.h> header file. */
-#undef HAVE_STRING_H
-
-/* Define to 1 if you have the `strlcpy' function. */
-#undef HAVE_STRLCPY
-
-/* Define to 1 if you have the `strspn' function. */
-#undef HAVE_STRSPN
-
-/* Define to 1 if you have the `sysctlbyname' function. */
-#undef HAVE_SYSCTLBYNAME
-
-/* Define to 1 if you have the <sys/param.h> header file. */
-#undef HAVE_SYS_PARAM_H
-
-/* Define to 1 if you have the <sys/stat.h> header file. */
-#undef HAVE_SYS_STAT_H
-
-/* Define to 1 if you have the <sys/sysctl.h> header file. */
-#undef HAVE_SYS_SYSCTL_H
-
-/* Define to 1 if you have the <sys/time.h> header file. */
-#undef HAVE_SYS_TIME_H
-
-/* Define to 1 if you have the <sys/types.h> header file. */
-#undef HAVE_SYS_TYPES_H
-
-/* Define to 1 if you have the <tzfile.h> header file. */
-#undef HAVE_TZFILE_H
-
-/* Define to 1 if you have the <unistd.h> header file. */
-#undef HAVE_UNISTD_H
-
-/* Define to 1 if you have the `__flbf' function. */
-#undef HAVE___FLBF
-
-/* Enable debugging */
-#undef LIBXO_DEBUG
-
-/* Enable text-only rendering */
-#undef LIBXO_TEXT_ONLY
-
-/* Define to the sub-directory where libtool stores uninstalled libraries. */
-#undef LT_OBJDIR
-
-/* Name of package */
-#undef PACKAGE
-
-/* Define to the address where bug reports for this package should be sent. */
-#undef PACKAGE_BUGREPORT
-
-/* Define to the full name of this package. */
-#undef PACKAGE_NAME
-
-/* Define to the full name and version of this package. */
-#undef PACKAGE_STRING
-
-/* Define to the one symbol short name of this package. */
-#undef PACKAGE_TARNAME
-
-/* Define to the home page for this package. */
-#undef PACKAGE_URL
-
-/* Define to the version of this package. */
-#undef PACKAGE_VERSION
-
-/* If using the C implementation of alloca, define if you know the
- direction of stack growth for your system; otherwise it will be
- automatically deduced at runtime.
- STACK_DIRECTION > 0 => grows toward higher addresses
- STACK_DIRECTION < 0 => grows toward lower addresses
- STACK_DIRECTION = 0 => direction of growth unknown */
-#undef STACK_DIRECTION
-
-/* Define to 1 if you have the ANSI C header files. */
-#undef STDC_HEADERS
-
-/* Version number of package */
-#undef VERSION
-
-/* Define to `__inline__' or `__inline' if that's what the C compiler
- calls it, or to nothing if 'inline' is not supported under any name. */
-#ifndef __cplusplus
-#undef inline
-#endif
-
-/* Define to rpl_malloc if the replacement function should be used. */
-#undef malloc
-
-/* Define to rpl_realloc if the replacement function should be used. */
-#undef realloc
-
-/* Define to `unsigned int' if <sys/types.h> does not define. */
-#undef size_t
diff --git a/contrib/libxo/libxo/xoversion.h b/contrib/libxo/libxo/xoversion.h
deleted file mode 100644
index 6a60596..0000000
--- a/contrib/libxo/libxo/xoversion.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * $Id$
- *
- * Copyright (c) 2014, 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.
- *
- * xoversion.h -- compile time constants for libxo
- * NOTE: This file is generated from xoversion.h.in.
- */
-
-#ifndef LIBXO_XOVERSION_H
-#define LIBXO_XOVERSION_H
-
-/**
- * The version string
- */
-#define LIBXO_VERSION "0.3.2"
-
-/**
- * The version number
- */
-#define LIBXO_VERSION_NUMBER 3002
-
-/**
- * The version number as a string
- */
-#define LIBXO_VERSION_STRING "3002"
-
-/**
- * The version number extra info as a string
- */
-#define LIBXO_VERSION_EXTRA ""
-
-#endif /* LIBXO_XOVERSION_H */
diff --git a/contrib/libxo/libxo/xoversion.h.in b/contrib/libxo/libxo/xoversion.h.in
deleted file mode 100644
index 777e83e..0000000
--- a/contrib/libxo/libxo/xoversion.h.in
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * $Id$
- *
- * Copyright (c) 2014, 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.
- *
- * xoversion.h -- compile time constants for libxo
- * NOTE: This file is generated from xoversion.h.in.
- */
-
-#ifndef LIBXO_XOVERSION_H
-#define LIBXO_XOVERSION_H
-
-/**
- * The version string
- */
-#define LIBXO_VERSION "@PACKAGE_VERSION@"
-
-/**
- * The version number
- */
-#define LIBXO_VERSION_NUMBER @LIBXO_VERSION_NUMBER@
-
-/**
- * The version number as a string
- */
-#define LIBXO_VERSION_STRING "@LIBXO_VERSION_NUMBER@"
-
-/**
- * The version number extra info as a string
- */
-#define LIBXO_VERSION_EXTRA "@LIBXO_VERSION_EXTRA@"
-
-#endif /* LIBXO_XOVERSION_H */
diff --git a/contrib/libxo/tests/Makefile.am b/contrib/libxo/tests/Makefile.am
index c69d511..b6d3e71 100644
--- a/contrib/libxo/tests/Makefile.am
+++ b/contrib/libxo/tests/Makefile.am
@@ -6,7 +6,11 @@
# using the SOFTWARE, you agree to be bound by the terms of that
# LICENSE.
-SUBDIRS=core xo
+SUBDIRS = core xo
+
+if HAVE_GETTEXT
+SUBDIRS += gettext
+endif
test tests:
@(cur=`pwd` ; for dir in $(SUBDIRS) ; do \
diff --git a/contrib/libxo/tests/core/Makefile.am b/contrib/libxo/tests/core/Makefile.am
index 92f5f36..0131a6f 100644
--- a/contrib/libxo/tests/core/Makefile.am
+++ b/contrib/libxo/tests/core/Makefile.am
@@ -21,7 +21,8 @@ test_06.c \
test_07.c \
test_08.c \
test_09.c \
-test_10.c
+test_10.c \
+test_11.c
test_01_test_SOURCES = test_01.c
test_02_test_SOURCES = test_02.c
@@ -33,6 +34,7 @@ test_07_test_SOURCES = test_07.c
test_08_test_SOURCES = test_08.c
test_09_test_SOURCES = test_09.c
test_10_test_SOURCES = test_10.c
+test_11_test_SOURCES = test_11.c
# TEST_CASES := $(shell cd ${srcdir} ; echo *.c )
@@ -41,6 +43,10 @@ noinst_PROGRAMS = ${TEST_CASES:.c=.test}
LDADD = \
${top_builddir}/libxo/libxo.la
+if HAVE_HUMANIZE_NUMBER
+LDADD += -lutil
+endif
+
EXTRA_DIST = \
${TEST_CASES} \
${addprefix saved/, ${TEST_CASES:.c=.T.err}} \
@@ -70,13 +76,18 @@ valgrind:
#TEST_TRACE = set -x ;
-TEST_ONE = \
- LIBXO_OPTIONS=:W$$fmt \
+TEST_JIG = \
${CHECKER} ./$$base.test ${TEST_OPTS} \
> out/$$base.$$fmt.out 2> out/$$base.$$fmt.err ; \
${DIFF} -Nu ${srcdir}/saved/$$base.$$fmt.out out/$$base.$$fmt.out ${S2O} ; \
${DIFF} -Nu ${srcdir}/saved/$$base.$$fmt.err out/$$base.$$fmt.err ${S2O}
+TEST_ONE = \
+ LIBXO_OPTIONS=:W$$fmt ${TEST_JIG}
+
+TEST_TWO = \
+ LIBXO_OPTIONS=warn,encoder=test ${TEST_JIG}
+
TEST_FORMATS = T XP JP HP X J H HIPx
test tests: ${bin_PROGRAMS}
@@ -87,6 +98,11 @@ test tests: ${bin_PROGRAMS}
echo "... $$test ... $$fmt ..."; \
${TEST_ONE}; \
true; \
+ done) ; \
+ (for fmt in E; do \
+ echo "... $$test ... $$fmt ..."; \
+ ${TEST_TWO}; \
+ true; \
done) \
done)
@@ -96,7 +112,7 @@ one:
accept:
-@(for test in ${TEST_CASES} ; do \
base=`${BASENAME} $$test .c` ; \
- (for fmt in ${TEST_FORMATS}; do \
+ (for fmt in ${TEST_FORMATS} E; do \
echo "... $$test ... $$fmt ..."; \
${CP} out/$$base.$$fmt.out ${srcdir}/saved/$$base.$$fmt.out ; \
${CP} out/$$base.$$fmt.err ${srcdir}/saved/$$base.$$fmt.err ; \
diff --git a/contrib/libxo/tests/core/saved/test_01.E.err b/contrib/libxo/tests/core/saved/test_01.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_01.E.err
diff --git a/contrib/libxo/tests/core/saved/test_01.E.out b/contrib/libxo/tests/core/saved/test_01.E.out
new file mode 100644
index 0000000..296a34e
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_01.E.out
@@ -0,0 +1,119 @@
+op create: [] []
+op open_container: [top] []
+op string: [host] [my-box]
+op string: [domain] [example.com]
+op attr: [test] [value]
+op open_container: [data] []
+op open_list: [item] []
+op attr: [test2] [value2]
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [GRO-000-415]
+op string: [name] [gum]
+op content: [sold] [1412]
+op content: [in-stock] [54]
+op content: [on-order] [10]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-212]
+op string: [name] [rope]
+op content: [sold] [85]
+op content: [in-stock] [4]
+op content: [on-order] [2]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-517]
+op string: [name] [ladder]
+op content: [sold] [0]
+op content: [in-stock] [2]
+op content: [on-order] [1]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-632]
+op string: [name] [bolt]
+op content: [sold] [4123]
+op content: [in-stock] [144]
+op content: [on-order] [42]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [GRO-000-2331]
+op string: [name] [water]
+op content: [sold] [17]
+op content: [in-stock] [14]
+op content: [on-order] [2]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data] []
+op open_container: [data2] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-415]
+op string: [name] [gum]
+op content: [sold] [1412.0]
+op content: [in-stock] [54]
+op content: [on-order] [10]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-212]
+op string: [name] [rope]
+op content: [sold] [85.0]
+op content: [in-stock] [4]
+op content: [on-order] [2]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-517]
+op string: [name] [ladder]
+op content: [sold] [0]
+op content: [in-stock] [2]
+op content: [on-order] [1]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-632]
+op string: [name] [bolt]
+op content: [sold] [4123.0]
+op content: [in-stock] [144]
+op content: [on-order] [42]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-2331]
+op string: [name] [water]
+op content: [sold] [17.0]
+op content: [in-stock] [14]
+op content: [on-order] [2]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data2] []
+op open_container: [data3] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-533]
+op string: [name] [fish]
+op content: [sold] [1321.0]
+op content: [in-stock] [45]
+op content: [on-order] [1]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data3] []
+op open_container: [data4] []
+op open_list: [item] []
+op attr: [test4] [value4]
+op string: [item] [gum]
+op attr: [test4] [value4]
+op string: [item] [rope]
+op attr: [test4] [value4]
+op string: [item] [ladder]
+op attr: [test4] [value4]
+op string: [item] [bolt]
+op attr: [test4] [value4]
+op string: [item] [water]
+op close_list: [item] []
+op close_container: [data4] []
+op content: [cost] [425]
+op content: [cost] [455]
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_01.H.out b/contrib/libxo/tests/core/saved/test_01.H.out
index 4d4f2f1..ead320e 100644
--- a/contrib/libxo/tests/core/saved/test_01.H.out
+++ b/contrib/libxo/tests/core/saved/test_01.H.out
@@ -1 +1 @@
-<div class="line"><div class="title">Item </div><div class="title"> Total Sold</div><div class="title"> In Stock</div><div class="title"> On Order</div><div class="title"> SKU</div></div><div class="line"><div class="data" data-tag="name" data-key="key">gum </div><div class="data" data-tag="sold"> 1412</div><div class="data" data-tag="in-stock"> 54</div><div class="data" data-tag="on-order"> 10</div><div class="data" data-tag="sku" data-key="key"> GRO-000-415</div></div><div class="line"><div class="data" data-tag="name" data-key="key">rope </div><div class="data" data-tag="sold"> 85</div><div class="data" data-tag="in-stock"> 4</div><div class="data" data-tag="on-order"> 2</div><div class="data" data-tag="sku" data-key="key"> HRD-000-212</div></div><div class="line"><div class="data" data-tag="name" data-key="key">ladder </div><div class="data" data-tag="sold"> 0</div><div class="data" data-tag="in-stock"> 2</div><div class="data" data-tag="on-order"> 1</div><div class="data" data-tag="sku" data-key="key"> HRD-000-517</div></div><div class="line"><div class="data" data-tag="name" data-key="key">bolt </div><div class="data" data-tag="sold"> 4123</div><div class="data" data-tag="in-stock"> 144</div><div class="data" data-tag="on-order"> 42</div><div class="data" data-tag="sku" data-key="key"> HRD-000-632</div></div><div class="line"><div class="data" data-tag="name" data-key="key">water </div><div class="data" data-tag="sold"> 17</div><div class="data" data-tag="in-stock"> 14</div><div class="data" data-tag="on-order"> 2</div><div class="data" data-tag="sku" data-key="key"> GRO-000-2331</div></div><div class="line"></div><div class="line"></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">gum</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">1412.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">54</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">10</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-415</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">rope</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">85.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">4</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">2</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-212</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">ladder</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">2</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">1</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-517</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">bolt</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">4123.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">144</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">42</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-632</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">water</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">17.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">14</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">2</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-2331</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">fish</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">1321.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">45</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">1</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-533</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">gum</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">rope</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">ladder</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">bolt</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">water</div></div><div class="line"><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div></div><div class="line"><div class="text">X</div><div class="padding"> </div><div class="text">X</div><div class="label">Cost</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="cost">425</div></div><div class="line"><div class="text">X</div><div class="padding"> </div><div class="text">X</div><div class="label">Cost</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="cost">455</div></div> \ No newline at end of file
+<div class="line"><div class="text">Connecting to </div><div class="data" data-tag="host">my-box</div><div class="text">.</div><div class="data" data-tag="domain">example.com</div><div class="text">...</div></div><div class="line"><div class="title">Item </div><div class="title"> Total Sold</div><div class="title"> In Stock</div><div class="title"> On Order</div><div class="title"> SKU</div></div><div class="line"><div class="data" data-tag="name" data-key="key">gum </div><div class="data" data-tag="sold"> 1412</div><div class="data" data-tag="in-stock"> 54</div><div class="data" data-tag="on-order"> 10</div><div class="data" data-tag="sku" data-key="key"> GRO-000-415</div></div><div class="line"><div class="data" data-tag="name" data-key="key">rope </div><div class="data" data-tag="sold"> 85</div><div class="data" data-tag="in-stock"> 4</div><div class="data" data-tag="on-order"> 2</div><div class="data" data-tag="sku" data-key="key"> HRD-000-212</div></div><div class="line"><div class="data" data-tag="name" data-key="key">ladder </div><div class="data" data-tag="sold"> 0</div><div class="data" data-tag="in-stock"> 2</div><div class="data" data-tag="on-order"> 1</div><div class="data" data-tag="sku" data-key="key"> HRD-000-517</div></div><div class="line"><div class="data" data-tag="name" data-key="key">bolt </div><div class="data" data-tag="sold"> 4123</div><div class="data" data-tag="in-stock"> 144</div><div class="data" data-tag="on-order"> 42</div><div class="data" data-tag="sku" data-key="key"> HRD-000-632</div></div><div class="line"><div class="data" data-tag="name" data-key="key">water </div><div class="data" data-tag="sold"> 17</div><div class="data" data-tag="in-stock"> 14</div><div class="data" data-tag="on-order"> 2</div><div class="data" data-tag="sku" data-key="key"> GRO-000-2331</div></div><div class="line"></div><div class="line"></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">gum</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">1412.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">54</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">10</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-415</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">rope</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">85.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">4</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">2</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-212</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">ladder</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">2</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">1</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-517</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">bolt</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">4123.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">144</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">42</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">HRD-000-632</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">water</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">17.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">14</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">2</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-2331</div></div><div class="line"><div class="label">Item</div><div class="text"> '</div><div class="data" data-tag="name" data-key="key">fish</div><div class="text">':</div></div><div class="line"><div class="padding"> </div><div class="label">Total sold</div><div class="text">: </div><div class="data" data-tag="sold">1321.0</div></div><div class="line"><div class="padding"> </div><div class="label">In stock</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="in-stock">45</div></div><div class="line"><div class="padding"> </div><div class="label">On order</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="on-order">1</div></div><div class="line"><div class="padding"> </div><div class="label">SKU</div><div class="text">: </div><div class="data" data-tag="sku" data-key="key">GRO-000-533</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">gum</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">rope</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">ladder</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">bolt</div></div><div class="line"><div class="label">Item</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="item">water</div></div><div class="line"><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div><div class="text">X</div></div><div class="line"><div class="text">X</div><div class="padding"> </div><div class="text">X</div><div class="label">Cost</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="cost">425</div></div><div class="line"><div class="text">X</div><div class="padding"> </div><div class="text">X</div><div class="label">Cost</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="cost">455</div></div> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_01.HIPx.out b/contrib/libxo/tests/core/saved/test_01.HIPx.out
index 2bafff9..2b8e296 100644
--- a/contrib/libxo/tests/core/saved/test_01.HIPx.out
+++ b/contrib/libxo/tests/core/saved/test_01.HIPx.out
@@ -1,4 +1,11 @@
<div class="line">
+ <div class="text">Connecting to </div>
+ <div class="data" data-tag="host" data-xpath="/top/host">my-box</div>
+ <div class="text">.</div>
+ <div class="data" data-tag="domain" data-xpath="/top/domain">example.com</div>
+ <div class="text">...</div>
+</div>
+<div class="line">
<div class="title">Item </div>
<div class="title"> Total Sold</div>
<div class="title"> In Stock</div>
@@ -47,224 +54,224 @@
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">gum</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/item/name" data-type="string" data-help="Name of the item" data-key="key">gum</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'GRO-000-415'][name = 'gum']/sold" data-type="number" data-help="Number of items sold">1412.0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data2/item[sku = 'GRO-000-415'][name = 'gum']/sold" data-type="number" data-help="Number of items sold">1412.0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'GRO-000-415'][name = 'gum']/in-stock" data-type="number" data-help="Number of items in stock">54</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data2/item[sku = 'GRO-000-415'][name = 'gum']/in-stock" data-type="number" data-help="Number of items in stock">54</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'GRO-000-415'][name = 'gum']/on-order" data-type="number" data-help="Number of items on order">10</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data2/item[sku = 'GRO-000-415'][name = 'gum']/on-order" data-type="number" data-help="Number of items on order">10</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-415</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data2/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-415</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">rope</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/item/name" data-type="string" data-help="Name of the item" data-key="key">rope</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'HRD-000-212'][name = 'rope']/sold" data-type="number" data-help="Number of items sold">85.0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data2/item[sku = 'HRD-000-212'][name = 'rope']/sold" data-type="number" data-help="Number of items sold">85.0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'HRD-000-212'][name = 'rope']/in-stock" data-type="number" data-help="Number of items in stock">4</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data2/item[sku = 'HRD-000-212'][name = 'rope']/in-stock" data-type="number" data-help="Number of items in stock">4</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'HRD-000-212'][name = 'rope']/on-order" data-type="number" data-help="Number of items on order">2</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data2/item[sku = 'HRD-000-212'][name = 'rope']/on-order" data-type="number" data-help="Number of items on order">2</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-212</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data2/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-212</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">ladder</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/item/name" data-type="string" data-help="Name of the item" data-key="key">ladder</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'HRD-000-517'][name = 'ladder']/sold" data-type="number" data-help="Number of items sold">0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data2/item[sku = 'HRD-000-517'][name = 'ladder']/sold" data-type="number" data-help="Number of items sold">0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'HRD-000-517'][name = 'ladder']/in-stock" data-type="number" data-help="Number of items in stock">2</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data2/item[sku = 'HRD-000-517'][name = 'ladder']/in-stock" data-type="number" data-help="Number of items in stock">2</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'HRD-000-517'][name = 'ladder']/on-order" data-type="number" data-help="Number of items on order">1</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data2/item[sku = 'HRD-000-517'][name = 'ladder']/on-order" data-type="number" data-help="Number of items on order">1</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-517</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data2/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-517</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">bolt</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/item/name" data-type="string" data-help="Name of the item" data-key="key">bolt</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'HRD-000-632'][name = 'bolt']/sold" data-type="number" data-help="Number of items sold">4123.0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data2/item[sku = 'HRD-000-632'][name = 'bolt']/sold" data-type="number" data-help="Number of items sold">4123.0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'HRD-000-632'][name = 'bolt']/in-stock" data-type="number" data-help="Number of items in stock">144</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data2/item[sku = 'HRD-000-632'][name = 'bolt']/in-stock" data-type="number" data-help="Number of items in stock">144</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'HRD-000-632'][name = 'bolt']/on-order" data-type="number" data-help="Number of items on order">42</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data2/item[sku = 'HRD-000-632'][name = 'bolt']/on-order" data-type="number" data-help="Number of items on order">42</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-632</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data2/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">HRD-000-632</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">water</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/item/name" data-type="string" data-help="Name of the item" data-key="key">water</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'GRO-000-2331'][name = 'water']/sold" data-type="number" data-help="Number of items sold">17.0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data2/item[sku = 'GRO-000-2331'][name = 'water']/sold" data-type="number" data-help="Number of items sold">17.0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'GRO-000-2331'][name = 'water']/in-stock" data-type="number" data-help="Number of items in stock">14</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data2/item[sku = 'GRO-000-2331'][name = 'water']/in-stock" data-type="number" data-help="Number of items in stock">14</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'GRO-000-2331'][name = 'water']/on-order" data-type="number" data-help="Number of items on order">2</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data2/item[sku = 'GRO-000-2331'][name = 'water']/on-order" data-type="number" data-help="Number of items on order">2</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-2331</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data2/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-2331</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
- <div class="data" data-tag="name" data-xpath="/top/data/item/name" data-type="string" data-help="Name of the item" data-key="key">fish</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/item/name" data-type="string" data-help="Name of the item" data-key="key">fish</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">Total sold</div>
<div class="text">: </div>
- <div class="data" data-tag="sold" data-xpath="/top/data/item[sku = 'GRO-000-533'][name = 'fish']/sold" data-type="number" data-help="Number of items sold">1321.0</div>
+ <div class="data" data-tag="sold" data-xpath="/top/data3/item[sku = 'GRO-000-533'][name = 'fish']/sold" data-type="number" data-help="Number of items sold">1321.0</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">In stock</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="in-stock" data-xpath="/top/data/item[sku = 'GRO-000-533'][name = 'fish']/in-stock" data-type="number" data-help="Number of items in stock">45</div>
+ <div class="data" data-tag="in-stock" data-xpath="/top/data3/item[sku = 'GRO-000-533'][name = 'fish']/in-stock" data-type="number" data-help="Number of items in stock">45</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">On order</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="on-order" data-xpath="/top/data/item[sku = 'GRO-000-533'][name = 'fish']/on-order" data-type="number" data-help="Number of items on order">1</div>
+ <div class="data" data-tag="on-order" data-xpath="/top/data3/item[sku = 'GRO-000-533'][name = 'fish']/on-order" data-type="number" data-help="Number of items on order">1</div>
</div>
<div class="line">
<div class="padding"> </div>
<div class="label">SKU</div>
<div class="text">: </div>
- <div class="data" data-tag="sku" data-xpath="/top/data/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-533</div>
+ <div class="data" data-tag="sku" data-xpath="/top/data3/item/sku" data-type="string" data-help="Stock Keeping Unit" data-key="key">GRO-000-533</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="item" data-xpath="/top/data/item">gum</div>
+ <div class="data" data-tag="item" data-xpath="/top/data4/item">gum</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="item" data-xpath="/top/data/item">rope</div>
+ <div class="data" data-tag="item" data-xpath="/top/data4/item">rope</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="item" data-xpath="/top/data/item">ladder</div>
+ <div class="data" data-tag="item" data-xpath="/top/data4/item">ladder</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="item" data-xpath="/top/data/item">bolt</div>
+ <div class="data" data-tag="item" data-xpath="/top/data4/item">bolt</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="item" data-xpath="/top/data/item">water</div>
+ <div class="data" data-tag="item" data-xpath="/top/data4/item">water</div>
</div>
<div class="line">
<div class="text">X</div>
diff --git a/contrib/libxo/tests/core/saved/test_01.HP.out b/contrib/libxo/tests/core/saved/test_01.HP.out
index a007778..c8f2dbc 100644
--- a/contrib/libxo/tests/core/saved/test_01.HP.out
+++ b/contrib/libxo/tests/core/saved/test_01.HP.out
@@ -1,4 +1,11 @@
<div class="line">
+ <div class="text">Connecting to </div>
+ <div class="data" data-tag="host">my-box</div>
+ <div class="text">.</div>
+ <div class="data" data-tag="domain">example.com</div>
+ <div class="text">...</div>
+</div>
+<div class="line">
<div class="title">Item </div>
<div class="title"> Total Sold</div>
<div class="title"> In Stock</div>
diff --git a/contrib/libxo/tests/core/saved/test_01.J.out b/contrib/libxo/tests/core/saved/test_01.J.out
index 6fcdbd4..69e3faa 100644
--- a/contrib/libxo/tests/core/saved/test_01.J.out
+++ b/contrib/libxo/tests/core/saved/test_01.J.out
@@ -1,2 +1,2 @@
-{"top": {"data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455}
+{"top": {"host":"my-box","domain":"example.com", "data": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17,"in-stock":14,"on-order":2}]}, "data2": {"item": [{"sku":"GRO-000-415","name":"gum","sold":1412.0,"in-stock":54,"on-order":10}, {"sku":"HRD-000-212","name":"rope","sold":85.0,"in-stock":4,"on-order":2}, {"sku":"HRD-000-517","name":"ladder","sold":0,"in-stock":2,"on-order":1}, {"sku":"HRD-000-632","name":"bolt","sold":4123.0,"in-stock":144,"on-order":42}, {"sku":"GRO-000-2331","name":"water","sold":17.0,"in-stock":14,"on-order":2}]}, "data3": {"item": [{"sku":"GRO-000-533","name":"fish","sold":1321.0,"in-stock":45,"on-order":1}]}, "data4": {"item": ["gum","rope","ladder","bolt","water"]},"cost":425,"cost":455}
}
diff --git a/contrib/libxo/tests/core/saved/test_01.JP.out b/contrib/libxo/tests/core/saved/test_01.JP.out
index 4790065..e65897f 100644
--- a/contrib/libxo/tests/core/saved/test_01.JP.out
+++ b/contrib/libxo/tests/core/saved/test_01.JP.out
@@ -1,5 +1,7 @@
{
"top": {
+ "host": "my-box",
+ "domain": "example.com",
"data": {
"item": [
{
@@ -39,7 +41,7 @@
}
]
},
- "data": {
+ "data2": {
"item": [
{
"sku": "GRO-000-415",
@@ -78,7 +80,7 @@
}
]
},
- "data": {
+ "data3": {
"item": [
{
"sku": "GRO-000-533",
@@ -89,7 +91,7 @@
}
]
},
- "data": {
+ "data4": {
"item": [
"gum",
"rope",
diff --git a/contrib/libxo/tests/core/saved/test_01.T.out b/contrib/libxo/tests/core/saved/test_01.T.out
index c45b130..2ecf537 100644
--- a/contrib/libxo/tests/core/saved/test_01.T.out
+++ b/contrib/libxo/tests/core/saved/test_01.T.out
@@ -1,3 +1,4 @@
+Connecting to my-box.example.com...
Item Total Sold In Stock On Order SKU
gum 1412 54 10 GRO-000-415
rope 85 4 2 HRD-000-212
diff --git a/contrib/libxo/tests/core/saved/test_01.X.out b/contrib/libxo/tests/core/saved/test_01.X.out
index ce27192..46f501e2 100644
--- a/contrib/libxo/tests/core/saved/test_01.X.out
+++ b/contrib/libxo/tests/core/saved/test_01.X.out
@@ -1 +1 @@
-<top><data test="value"><item test2="value2"><sku test3="value3" key="key">GRO-000-415</sku><name key="key">gum</name><sold>1412</sold><in-stock>54</in-stock><on-order>10</on-order></item><item><sku test3="value3" key="key">HRD-000-212</sku><name key="key">rope</name><sold>85</sold><in-stock>4</in-stock><on-order>2</on-order></item><item><sku test3="value3" key="key">HRD-000-517</sku><name key="key">ladder</name><sold>0</sold><in-stock>2</in-stock><on-order>1</on-order></item><item><sku test3="value3" key="key">HRD-000-632</sku><name key="key">bolt</name><sold>4123</sold><in-stock>144</in-stock><on-order>42</on-order></item><item><sku test3="value3" key="key">GRO-000-2331</sku><name key="key">water</name><sold>17</sold><in-stock>14</in-stock><on-order>2</on-order></item></data><data><item><sku key="key">GRO-000-415</sku><name key="key">gum</name><sold>1412.0</sold><in-stock>54</in-stock><on-order>10</on-order></item><item><sku key="key">HRD-000-212</sku><name key="key">rope</name><sold>85.0</sold><in-stock>4</in-stock><on-order>2</on-order></item><item><sku key="key">HRD-000-517</sku><name key="key">ladder</name><sold>0</sold><in-stock>2</in-stock><on-order>1</on-order></item><item><sku key="key">HRD-000-632</sku><name key="key">bolt</name><sold>4123.0</sold><in-stock>144</in-stock><on-order>42</on-order></item><item><sku key="key">GRO-000-2331</sku><name key="key">water</name><sold>17.0</sold><in-stock>14</in-stock><on-order>2</on-order></item></data><data><item><sku key="key">GRO-000-533</sku><name key="key">fish</name><sold>1321.0</sold><in-stock>45</in-stock><on-order>1</on-order></item></data><data><item test4="value4">gum</item><item test4="value4">rope</item><item test4="value4">ladder</item><item test4="value4">bolt</item><item test4="value4">water</item></data><cost>425</cost><cost>455</cost></top> \ No newline at end of file
+<top><host>my-box</host><domain>example.com</domain><data test="value"><item test2="value2"><sku test3="value3" key="key">GRO-000-415</sku><name key="key">gum</name><sold>1412</sold><in-stock>54</in-stock><on-order>10</on-order></item><item><sku test3="value3" key="key">HRD-000-212</sku><name key="key">rope</name><sold>85</sold><in-stock>4</in-stock><on-order>2</on-order></item><item><sku test3="value3" key="key">HRD-000-517</sku><name key="key">ladder</name><sold>0</sold><in-stock>2</in-stock><on-order>1</on-order></item><item><sku test3="value3" key="key">HRD-000-632</sku><name key="key">bolt</name><sold>4123</sold><in-stock>144</in-stock><on-order>42</on-order></item><item><sku test3="value3" key="key">GRO-000-2331</sku><name key="key">water</name><sold>17</sold><in-stock>14</in-stock><on-order>2</on-order></item></data><data2><item><sku key="key">GRO-000-415</sku><name key="key">gum</name><sold>1412.0</sold><in-stock>54</in-stock><on-order>10</on-order></item><item><sku key="key">HRD-000-212</sku><name key="key">rope</name><sold>85.0</sold><in-stock>4</in-stock><on-order>2</on-order></item><item><sku key="key">HRD-000-517</sku><name key="key">ladder</name><sold>0</sold><in-stock>2</in-stock><on-order>1</on-order></item><item><sku key="key">HRD-000-632</sku><name key="key">bolt</name><sold>4123.0</sold><in-stock>144</in-stock><on-order>42</on-order></item><item><sku key="key">GRO-000-2331</sku><name key="key">water</name><sold>17.0</sold><in-stock>14</in-stock><on-order>2</on-order></item></data2><data3><item><sku key="key">GRO-000-533</sku><name key="key">fish</name><sold>1321.0</sold><in-stock>45</in-stock><on-order>1</on-order></item></data3><data4><item test4="value4">gum</item><item test4="value4">rope</item><item test4="value4">ladder</item><item test4="value4">bolt</item><item test4="value4">water</item></data4><cost>425</cost><cost>455</cost></top> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_01.XP.out b/contrib/libxo/tests/core/saved/test_01.XP.out
index e5ea3e0..c7f4bfe8 100644
--- a/contrib/libxo/tests/core/saved/test_01.XP.out
+++ b/contrib/libxo/tests/core/saved/test_01.XP.out
@@ -1,4 +1,6 @@
<top>
+ <host>my-box</host>
+ <domain>example.com</domain>
<data test="value">
<item test2="value2">
<sku test3="value3" key="key">GRO-000-415</sku>
@@ -36,7 +38,7 @@
<on-order>2</on-order>
</item>
</data>
- <data>
+ <data2>
<item>
<sku key="key">GRO-000-415</sku>
<name key="key">gum</name>
@@ -72,8 +74,8 @@
<in-stock>14</in-stock>
<on-order>2</on-order>
</item>
- </data>
- <data>
+ </data2>
+ <data3>
<item>
<sku key="key">GRO-000-533</sku>
<name key="key">fish</name>
@@ -81,14 +83,14 @@
<in-stock>45</in-stock>
<on-order>1</on-order>
</item>
- </data>
- <data>
+ </data3>
+ <data4>
<item test4="value4">gum</item>
<item test4="value4">rope</item>
<item test4="value4">ladder</item>
<item test4="value4">bolt</item>
<item test4="value4">water</item>
- </data>
+ </data4>
<cost>425</cost>
<cost>455</cost>
</top>
diff --git a/contrib/libxo/tests/core/saved/test_02.E.err b/contrib/libxo/tests/core/saved/test_02.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_02.E.err
diff --git a/contrib/libxo/tests/core/saved/test_02.E.out b/contrib/libxo/tests/core/saved/test_02.E.out
new file mode 100644
index 0000000..9e1ec96
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_02.E.out
@@ -0,0 +1,68 @@
+op create: [] []
+op open_container: [top] []
+op open_container: [data] []
+op string: [what] [braces]
+op string: [length] [abcdef]
+op content: [fd] [-1]
+op string: [error] [Bad file descriptor]
+op string: [test] [good]
+op content: [fd] [-1]
+op string: [error] [Bad fi]
+op string: [test] [good]
+op content: [lines] [20]
+op content: [words] [30]
+op content: [characters] [40]
+op open_leaf_list: [bytes] []
+op content: [bytes] [0]
+op content: [bytes] [1]
+op content: [bytes] [2]
+op content: [bytes] [3]
+op content: [bytes] [4]
+op close_leaf_list: [bytes] []
+op content: [mbuf-current] [10]
+op content: [mbuf-cache] [20]
+op content: [mbuf-total] [30]
+op content: [distance] [50]
+op string: [location] [Boston]
+op content: [memory] [64]
+op content: [total] [640]
+op content: [memory] [64]
+op content: [total] [640]
+op content: [ten] [10]
+op content: [eleven] [11]
+op content: [unknown] [1010]
+op content: [unknown] [1010]
+op content: [min] [15]
+op content: [cur] [20]
+op content: [max] [30]
+op content: [min] [15]
+op content: [cur] [20]
+op content: [max] [125]
+op content: [min] [15]
+op content: [cur] [20]
+op content: [max] [125]
+op content: [min] [15]
+op content: [cur] [20]
+op content: [max] [125]
+op content: [val1] [21]
+op content: [val2] [58368]
+op content: [val3] [100663296]
+op content: [val4] [44470272]
+op content: [val5] [1342172800]
+op open_list: [flag] []
+op string: [flag] [one]
+op string: [flag] [two]
+op string: [flag] [three]
+op close_list: [flag] []
+op content: [works] [null]
+op content: [empty-tag] [true]
+op string: [t1] [1000]
+op string: [t2] [test5000]
+op string: [t3] [ten-longx]
+op string: [t4] [xtest]
+op content: [count] [10]
+op content: [test] [4]
+op close_container: [data] []
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_02.H.out b/contrib/libxo/tests/core/saved/test_02.H.out
index 60350b5..f1387a6 100644
--- a/contrib/libxo/tests/core/saved/test_02.H.out
+++ b/contrib/libxo/tests/core/saved/test_02.H.out
@@ -1,3 +1,7 @@
-<div class="line"><div class="data" data-tag="mbuf-current">10</div><div class="text">/</div><div class="data" data-tag="mbuf-cache">20</div><div class="text">/</div><div class="data" data-tag="mbuf-total">30</div><div class="text"> </div><div class="note">mbufs &lt;&amp;&gt; in use (current/cache/total)</div></div><div class="line"><div class="data" data-tag="distance" data-units="miles">50</div><div class="padding"> </div><div class="text"> from </div><div class="data" data-tag="location">Boston</div></div><div class="line"><div class="data" data-tag="memory" data-units="k">64</div><div class="text"> left out of </div><div class="data" data-tag="total" data-units="kb">640</div></div><div class="line"><div class="data" data-tag="memory" data-units="k">64</div><div class="text"> left out of </div><div class="data" data-tag="total" data-units="kilobytes">640</div></div><div class="line"><div class="title">beforeworkingafter:</div></div><div class="line"><div class="data" data-tag="some">string</div><div class="data" data-tag="ten">10</div><div class="data" data-tag="eleven">11</div></div><div class="line"><div class="data" data-tag="unknown">1010</div><div class="text"> </div><div class="note">packets here/there/everywhere</div></div><div class="line"><div class="text">(</div><div class="padding"> </div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="padding"> </div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="padding"> </div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="padding"> </div><div class="text">)</div></div><div class="line"><div class="data" data-tag="flag">one</div><div class="text"> </div><div class="data" data-tag="flag">two</div><div class="text"> </div><div class="data" data-tag="flag">three</div></div><div class="line"><div class="text">1:</div><div class="data" data-tag="t1"> 1000</div><div class="text"> 2:</div><div class="data" data-tag="t2">test5000 </div><div class="text"> 3:</div><div class="data" data-tag="t3"> ten-longx</div><div class="text"> 4:</div><div class="data" data-tag="t4">xtest </div></div><div class="line"><div class="error">this is an error</div></div><div class="line"><div class="error">two more errors</div></div><div class="line"><div class="warning">this is an warning</div></div><div class="line"><div class="warning">two more warnings</div></div><div class="line"><div class="label">V1/V2 packets</div><div class="text">: </div><div class="data" data-tag="count">10</div></div><div class="line"><div class="data" data-tag="test">0004</div><div class="text"> </div><div class="label">tries</div></div><div class="line"><div class="message">improper use of profanity; ten yard penalty; first down
+<div class="line"><div class="text">We are </div><div class="text">{emit}</div><div class="text">{ting}</div><div class="text"> some </div><div class="data" data-tag="what">braces</div></div><div class="line"><div class="message">abcdef
+</div></div><div class="line"><div class="message">abcdef: Bad file descriptor
+</div></div><div class="line"><div class="message">improper use of profanity; ten yard penalty; first down
+</div></div><div class="line"><div class="text">length </div><div class="data" data-tag="length">abcdef</div></div><div class="line"><div class="text">close </div><div class="data" data-tag="fd">-1</div><div class="text"> returned </div><div class="data" data-tag="error">Bad file descriptor</div><div class="text"> </div><div class="data" data-tag="test">good</div></div><div class="line"><div class="text">close </div><div class="data" data-tag="fd">-1</div><div class="text"> returned </div><div class="data" data-tag="error">Bad fi</div><div class="text"> </div><div class="data" data-tag="test">good</div></div><div class="line"><div class="message">improper use of profanity; ten yard penalty; first down
+</div></div><div class="line"><div class="text"> </div><div class="data" data-tag="lines"> 20</div><div class="text"> </div><div class="data" data-tag="words"> 30</div><div class="text"> </div><div class="data" data-tag="characters"> 40</div><div class="text"> </div><div class="data" data-tag="filename">file</div></div><div class="line"><div class="data" data-tag="bytes">0</div><div class="padding"> </div><div class="note">bytes</div></div><div class="line"><div class="data" data-tag="bytes">1</div><div class="padding"> </div><div class="note">byte</div></div><div class="line"><div class="data" data-tag="bytes">2</div><div class="padding"> </div><div class="note">bytes</div></div><div class="line"><div class="data" data-tag="bytes">3</div><div class="padding"> </div><div class="note">bytes</div></div><div class="line"><div class="data" data-tag="bytes">4</div><div class="padding"> </div><div class="note">bytes</div></div><div class="line"><div class="data" data-tag="mbuf-current">10</div><div class="text">/</div><div class="data" data-tag="mbuf-cache">20</div><div class="text">/</div><div class="data" data-tag="mbuf-total">30</div><div class="text"> </div><div class="note">mbufs &lt;&amp;&gt; in use (current/cache/total)</div></div><div class="line"><div class="data" data-tag="distance" data-units="miles">50</div><div class="padding"> </div><div class="text"> from </div><div class="data" data-tag="location">Boston</div></div><div class="line"><div class="data" data-tag="memory" data-units="k">64</div><div class="text"> left out of </div><div class="data" data-tag="total" data-units="kb">640</div></div><div class="line"><div class="data" data-tag="memory" data-units="k">64</div><div class="text"> left out of </div><div class="data" data-tag="total" data-units="kilobytes">640</div></div><div class="line"><div class="title">beforeworkingafter:</div></div><div class="line"><div class="data" data-tag="some">string</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="ten">10</div><div class="data" data-tag="eleven">11</div></div><div class="line"><div class="data" data-tag="unknown">1010</div><div class="text"> </div><div class="note">packets here/there/everywhere</div></div><div class="line"><div class="data" data-tag="unknown">1010</div><div class="text"> </div><div class="note">packets here/there/everywhere</div></div><div class="line"><div class="text">(</div><div class="padding"> </div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="padding"> </div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="padding"> </div><div class="text">)</div></div><div class="line"><div class="text">(</div><div class="data" data-tag="min">15</div><div class="text">/</div><div class="data" data-tag="cur">20</div><div class="text">/</div><div class="data" data-tag="max">125</div><div class="padding"> </div><div class="text">)</div></div><div class="line"><div class="text">Humanize: </div><div class="data" data-tag="val1" data-number="21">21</div><div class="text">, </div><div class="data" data-tag="val2" data-number="58368">57 K</div><div class="text">, </div><div class="data" data-tag="val3" data-number="100663296">96M</div><div class="text">, </div><div class="data" data-tag="val4" data-number="44470272">44M</div><div class="text">, </div><div class="data" data-tag="val5" data-number="1342172800">1.2G</div></div><div class="line"><div class="data" data-tag="flag">one</div><div class="text"> </div><div class="data" data-tag="flag">two</div><div class="text"> </div><div class="data" data-tag="flag">three</div></div><div class="line"><div class="data" data-tag="works">(null)</div></div><div class="line"><div class="text">1:</div><div class="data" data-tag="t1"> 1000</div><div class="text"> 2:</div><div class="data" data-tag="t2">test5000 </div><div class="text"> 3:</div><div class="data" data-tag="t3"> ten-longx</div><div class="text"> 4:</div><div class="data" data-tag="t4">xtest </div></div><div class="line"><div class="error">this is an error</div></div><div class="line"><div class="error">two more errors</div></div><div class="line"><div class="warning">this is an warning</div></div><div class="line"><div class="warning">two more warnings</div></div><div class="line"><div class="label">V1/V2 packets</div><div class="text">: </div><div class="data" data-tag="count">10</div></div><div class="line"><div class="data" data-tag="test">0004</div><div class="text"> </div><div class="label">tries</div></div><div class="line"><div class="message">improper use of profanity; ten yard penalty; first down
</div></div><div class="line"><div class="error">Shut 'er down, Clancey! She's a-pumpin' mud! &lt;&gt;!,"!&lt;&gt;
</div></div> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_02.HIPx.out b/contrib/libxo/tests/core/saved/test_02.HIPx.out
index f4264f7..7af26b3 100644
--- a/contrib/libxo/tests/core/saved/test_02.HIPx.out
+++ b/contrib/libxo/tests/core/saved/test_02.HIPx.out
@@ -1,4 +1,82 @@
<div class="line">
+ <div class="text">We are </div>
+ <div class="text">{emit}</div>
+ <div class="text">{ting}</div>
+ <div class="text"> some </div>
+ <div class="data" data-tag="what" data-xpath="/top/data/what">braces</div>
+</div>
+<div class="line">
+ <div class="message">abcdef
+</div>
+</div>
+<div class="line">
+ <div class="message">abcdef: Bad file descriptor
+</div>
+</div>
+<div class="line">
+ <div class="message">improper use of profanity; ten yard penalty; first down
+</div>
+</div>
+<div class="line">
+ <div class="text">length </div>
+ <div class="data" data-tag="length" data-xpath="/top/data/length">abcdef</div>
+</div>
+<div class="line">
+ <div class="text">close </div>
+ <div class="data" data-tag="fd" data-xpath="/top/data/fd">-1</div>
+ <div class="text"> returned </div>
+ <div class="data" data-tag="error" data-xpath="/top/data/error">Bad file descriptor</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="test" data-xpath="/top/data/test">good</div>
+</div>
+<div class="line">
+ <div class="text">close </div>
+ <div class="data" data-tag="fd" data-xpath="/top/data/fd">-1</div>
+ <div class="text"> returned </div>
+ <div class="data" data-tag="error" data-xpath="/top/data/error">Bad fi</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="test" data-xpath="/top/data/test">good</div>
+</div>
+<div class="line">
+ <div class="message">improper use of profanity; ten yard penalty; first down
+</div>
+</div>
+<div class="line">
+ <div class="text"> </div>
+ <div class="data" data-tag="lines" data-xpath="/top/data/lines"> 20</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="words" data-xpath="/top/data/words"> 30</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="characters" data-xpath="/top/data/characters"> 40</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="filename" data-xpath="/top/data/filename">file</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/data/bytes">0</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/data/bytes">1</div>
+ <div class="padding"> </div>
+ <div class="note">byte</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/data/bytes">2</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/data/bytes">3</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/data/bytes">4</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
<div class="data" data-tag="mbuf-current" data-xpath="/top/data/mbuf-current">10</div>
<div class="text">/</div>
<div class="data" data-tag="mbuf-cache" data-xpath="/top/data/mbuf-cache">20</div>
@@ -28,6 +106,8 @@
</div>
<div class="line">
<div class="data" data-tag="some" data-xpath="/top/data/some">string</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
<div class="data" data-tag="ten" data-xpath="/top/data/ten">10</div>
<div class="data" data-tag="eleven" data-xpath="/top/data/eleven">11</div>
</div>
@@ -37,6 +117,11 @@
<div class="note">packets here/there/everywhere</div>
</div>
<div class="line">
+ <div class="data" data-tag="unknown" data-xpath="/top/data/unknown">1010</div>
+ <div class="text"> </div>
+ <div class="note">packets here/there/everywhere</div>
+</div>
+<div class="line">
<div class="text">(</div>
<div class="padding"> </div>
<div class="data" data-tag="min" data-xpath="/top/data/min">15</div>
@@ -77,6 +162,18 @@
<div class="text">)</div>
</div>
<div class="line">
+ <div class="text">Humanize: </div>
+ <div class="data" data-tag="val1" data-xpath="/top/data/val1" data-number="21">21</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val2" data-xpath="/top/data/val2" data-number="58368">57 K</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val3" data-xpath="/top/data/val3" data-number="100663296">96M</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val4" data-xpath="/top/data/val4" data-number="44470272">44M</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val5" data-xpath="/top/data/val5" data-number="1342172800">1.2G</div>
+</div>
+<div class="line">
<div class="data" data-tag="flag" data-xpath="/top/data/flag">one</div>
<div class="text"> </div>
<div class="data" data-tag="flag" data-xpath="/top/data/flag">two</div>
@@ -84,6 +181,9 @@
<div class="data" data-tag="flag" data-xpath="/top/data/flag">three</div>
</div>
<div class="line">
+ <div class="data" data-tag="works" data-xpath="/top/data/works">(null)</div>
+</div>
+<div class="line">
<div class="text">1:</div>
<div class="data" data-tag="t1" data-xpath="/top/data/t1"> 1000</div>
<div class="text"> 2:</div>
diff --git a/contrib/libxo/tests/core/saved/test_02.HP.out b/contrib/libxo/tests/core/saved/test_02.HP.out
index 1ccf369..f7c9d9b 100644
--- a/contrib/libxo/tests/core/saved/test_02.HP.out
+++ b/contrib/libxo/tests/core/saved/test_02.HP.out
@@ -1,4 +1,82 @@
<div class="line">
+ <div class="text">We are </div>
+ <div class="text">{emit}</div>
+ <div class="text">{ting}</div>
+ <div class="text"> some </div>
+ <div class="data" data-tag="what">braces</div>
+</div>
+<div class="line">
+ <div class="message">abcdef
+</div>
+</div>
+<div class="line">
+ <div class="message">abcdef: Bad file descriptor
+</div>
+</div>
+<div class="line">
+ <div class="message">improper use of profanity; ten yard penalty; first down
+</div>
+</div>
+<div class="line">
+ <div class="text">length </div>
+ <div class="data" data-tag="length">abcdef</div>
+</div>
+<div class="line">
+ <div class="text">close </div>
+ <div class="data" data-tag="fd">-1</div>
+ <div class="text"> returned </div>
+ <div class="data" data-tag="error">Bad file descriptor</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="test">good</div>
+</div>
+<div class="line">
+ <div class="text">close </div>
+ <div class="data" data-tag="fd">-1</div>
+ <div class="text"> returned </div>
+ <div class="data" data-tag="error">Bad fi</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="test">good</div>
+</div>
+<div class="line">
+ <div class="message">improper use of profanity; ten yard penalty; first down
+</div>
+</div>
+<div class="line">
+ <div class="text"> </div>
+ <div class="data" data-tag="lines"> 20</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="words"> 30</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="characters"> 40</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="filename">file</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">0</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">1</div>
+ <div class="padding"> </div>
+ <div class="note">byte</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">2</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">3</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">4</div>
+ <div class="padding"> </div>
+ <div class="note">bytes</div>
+</div>
+<div class="line">
<div class="data" data-tag="mbuf-current">10</div>
<div class="text">/</div>
<div class="data" data-tag="mbuf-cache">20</div>
@@ -28,6 +106,8 @@
</div>
<div class="line">
<div class="data" data-tag="some">string</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
<div class="data" data-tag="ten">10</div>
<div class="data" data-tag="eleven">11</div>
</div>
@@ -37,6 +117,11 @@
<div class="note">packets here/there/everywhere</div>
</div>
<div class="line">
+ <div class="data" data-tag="unknown">1010</div>
+ <div class="text"> </div>
+ <div class="note">packets here/there/everywhere</div>
+</div>
+<div class="line">
<div class="text">(</div>
<div class="padding"> </div>
<div class="data" data-tag="min">15</div>
@@ -77,6 +162,18 @@
<div class="text">)</div>
</div>
<div class="line">
+ <div class="text">Humanize: </div>
+ <div class="data" data-tag="val1" data-number="21">21</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val2" data-number="58368">57 K</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val3" data-number="100663296">96M</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val4" data-number="44470272">44M</div>
+ <div class="text">, </div>
+ <div class="data" data-tag="val5" data-number="1342172800">1.2G</div>
+</div>
+<div class="line">
<div class="data" data-tag="flag">one</div>
<div class="text"> </div>
<div class="data" data-tag="flag">two</div>
@@ -84,6 +181,9 @@
<div class="data" data-tag="flag">three</div>
</div>
<div class="line">
+ <div class="data" data-tag="works">(null)</div>
+</div>
+<div class="line">
<div class="text">1:</div>
<div class="data" data-tag="t1"> 1000</div>
<div class="text"> 2:</div>
diff --git a/contrib/libxo/tests/core/saved/test_02.J.out b/contrib/libxo/tests/core/saved/test_02.J.out
index 5b4502a..5578389 100644
--- a/contrib/libxo/tests/core/saved/test_02.J.out
+++ b/contrib/libxo/tests/core/saved/test_02.J.out
@@ -1,2 +1,2 @@
-{"top": {"data": {"mbuf-current":10,"mbuf-cache":20,"mbuf-total":30,"distance":50,"location":"Boston","memory":64,"total":640,"memory":64,"total":640,"ten":10,"eleven":11,"unknown":1010,"min":15,"cur":20,"max":30,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125, "flag": ["one","two","three"],"empty-tag":true,"t1":"1000","t2":"test5000","t3":"ten-longx","t4":"xtest","count":10,"test":4, "error": {"message":"Shut 'er down, Clancey! She's a-pumpin' mud! <>!,\"!<>\n"}}}
+{"top": {"data": {"what":"braces","length":"abcdef","fd":-1,"error":"Bad file descriptor","test":"good","fd":-1,"error":"Bad fi","test":"good","lines":20,"words":30,"characters":40, "bytes": [0,1,2,3,4],"mbuf-current":10,"mbuf-cache":20,"mbuf-total":30,"distance":50,"location":"Boston","memory":64,"total":640,"memory":64,"total":640,"ten":10,"eleven":11,"unknown":1010,"unknown":1010,"min":15,"cur":20,"max":30,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"min":15,"cur":20,"max":125,"val1":21,"val2":58368,"val3":100663296,"val4":44470272,"val5":1342172800, "flag": ["one","two","three"],"works":null,"empty-tag":true,"t1":"1000","t2":"test5000","t3":"ten-longx","t4":"xtest", "__error": {"message":"this is an error"}, "__error": {"message":"two more errors"}, "__warning": {"message":this is an warning}, "__warning": {"message":"two more warnings"},"count":10,"test":4, "error": {"message":"Shut 'er down, Clancey! She's a-pumpin' mud! <>!,\"!<>\n"}}}
}
diff --git a/contrib/libxo/tests/core/saved/test_02.JP.out b/contrib/libxo/tests/core/saved/test_02.JP.out
index ade2dc2..fd910aa 100644
--- a/contrib/libxo/tests/core/saved/test_02.JP.out
+++ b/contrib/libxo/tests/core/saved/test_02.JP.out
@@ -1,6 +1,24 @@
{
"top": {
"data": {
+ "what": "braces",
+ "length": "abcdef",
+ "fd": -1,
+ "error": "Bad file descriptor",
+ "test": "good",
+ "fd": -1,
+ "error": "Bad fi",
+ "test": "good",
+ "lines": 20,
+ "words": 30,
+ "characters": 40,
+ "bytes": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4
+ ],
"mbuf-current": 10,
"mbuf-cache": 20,
"mbuf-total": 30,
@@ -13,6 +31,7 @@
"ten": 10,
"eleven": 11,
"unknown": 1010,
+ "unknown": 1010,
"min": 15,
"cur": 20,
"max": 30,
@@ -25,16 +44,34 @@
"min": 15,
"cur": 20,
"max": 125,
+ "val1": 21,
+ "val2": 58368,
+ "val3": 100663296,
+ "val4": 44470272,
+ "val5": 1342172800,
"flag": [
"one",
"two",
"three"
],
+ "works": null,
"empty-tag": true,
"t1": "1000",
"t2": "test5000",
"t3": "ten-longx",
"t4": "xtest",
+ "__error": {
+ "message": "this is an error"
+ },
+ "__error": {
+ "message": "two more errors"
+ },
+ "__warning": {
+ "message": this is an warning
+ },
+ "__warning": {
+ "message": "two more warnings"
+ },
"count": 10,
"test": 4,
"error": {
diff --git a/contrib/libxo/tests/core/saved/test_02.T.out b/contrib/libxo/tests/core/saved/test_02.T.out
index b37ba00..d65e7c5 100644
--- a/contrib/libxo/tests/core/saved/test_02.T.out
+++ b/contrib/libxo/tests/core/saved/test_02.T.out
@@ -1,15 +1,32 @@
+We are {emit}{ting} some braces
+abcdef
+abcdef: Bad file descriptor
+improper use of profanity; ten yard penalty; first down
+length abcdef
+close -1 returned Bad file descriptor good
+close -1 returned Bad fi good
+improper use of profanity; ten yard penalty; first down
+ 20 30 40 file
+0 bytes
+1 byte
+2 bytes
+3 bytes
+4 bytes
10/20/30 mbufs <&> in use (current/cache/total)
50 miles from Boston
64k left out of 640kb
64k left out of 640kilobytes
beforeworkingafter:
-string1011
+string: 1011
+1010 packets here/there/everywhere
1010 packets here/there/everywhere
( 15/20/125)
( 15/20/125)
(15/20/125 )
(15/20/125 )
+Humanize: 21, 57 K, 96M, 44M, 1.2G
one two three
+(null)
1: 1000 2:test5000 3: ten-longx 4:xtest
this is an error
two more errors
diff --git a/contrib/libxo/tests/core/saved/test_02.X.out b/contrib/libxo/tests/core/saved/test_02.X.out
index ff6d40c..30421ea 100644
--- a/contrib/libxo/tests/core/saved/test_02.X.out
+++ b/contrib/libxo/tests/core/saved/test_02.X.out
@@ -1,3 +1,7 @@
-<top><data><mbuf-current>10</mbuf-current><mbuf-cache>20</mbuf-cache><mbuf-total>30</mbuf-total><distance units="miles">50</distance><location>Boston</location><memory units="k">64</memory><total units="kb">640</total><memory units="k">64</memory><total units="kilobytes">640</total><ten>10</ten><eleven>11</eleven><unknown>1010</unknown><min>15</min><cur>20</cur><max>30</max><min>15</min><cur>20</cur><max>125</max><min>15</min><cur>20</cur><max>125</max><min>15</min><cur>20</cur><max>125</max><flag>one</flag><flag>two</flag><flag>three</flag><empty-tag></empty-tag><t1>1000</t1><t2>test5000</t2><t3>ten-longx</t3><t4>xtest</t4><error><message>this is an error</message></error><error><message>two more errors</message></error><warning><message>this is an warning</message></warning><warning><message>two more warnings</message></warning><count>10</count><test>4</test><message>improper use of profanity; ten yard penalty; first down</message>
-<error><message>Shut 'er down, Clancey! She's a-pumpin' mud! &lt;&gt;!,"!&lt;&gt;
+<top><data><what>braces</what><message>abcdef
+</message><message>abcdef: Bad file descriptor
+</message><message>improper use of profanity; ten yard penalty; first down
+</message><length>abcdef</length><fd>-1</fd><error>Bad file descriptor</error><test>good</test><fd>-1</fd><error>Bad fi</error><test>good</test><message>improper use of profanity; ten yard penalty; first down
+</message><lines>20</lines><words>30</words><characters>40</characters><bytes>0</bytes><bytes>1</bytes><bytes>2</bytes><bytes>3</bytes><bytes>4</bytes><mbuf-current>10</mbuf-current><mbuf-cache>20</mbuf-cache><mbuf-total>30</mbuf-total><distance units="miles">50</distance><location>Boston</location><memory units="k">64</memory><total units="kb">640</total><memory units="k">64</memory><total units="kilobytes">640</total><ten>10</ten><eleven>11</eleven><unknown>1010</unknown><unknown>1010</unknown><min>15</min><cur>20</cur><max>30</max><min>15</min><cur>20</cur><max>125</max><min>15</min><cur>20</cur><max>125</max><min>15</min><cur>20</cur><max>125</max><val1>21</val1><val2>58368</val2><val3>100663296</val3><val4>44470272</val4><val5>1342172800</val5><flag>one</flag><flag>two</flag><flag>three</flag><works>null</works><empty-tag></empty-tag><t1>1000</t1><t2>test5000</t2><t3>ten-longx</t3><t4>xtest</t4><__error><message>this is an error</message></__error><__error><message>two more errors</message></__error><__warning><message>this is an warning</message></__warning><__warning><message>two more warnings</message></__warning><count>10</count><test>4</test><message>improper use of profanity; ten yard penalty; first down
+</message><error><message>Shut 'er down, Clancey! She's a-pumpin' mud! &lt;&gt;!,"!&lt;&gt;
</message></error></data></top> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_02.XP.out b/contrib/libxo/tests/core/saved/test_02.XP.out
index d32c730..e70e6ef 100644
--- a/contrib/libxo/tests/core/saved/test_02.XP.out
+++ b/contrib/libxo/tests/core/saved/test_02.XP.out
@@ -1,5 +1,29 @@
<top>
<data>
+ <what>braces</what>
+ <message>abcdef
+</message>
+ <message>abcdef: Bad file descriptor
+</message>
+ <message>improper use of profanity; ten yard penalty; first down
+</message>
+ <length>abcdef</length>
+ <fd>-1</fd>
+ <error>Bad file descriptor</error>
+ <test>good</test>
+ <fd>-1</fd>
+ <error>Bad fi</error>
+ <test>good</test>
+ <message>improper use of profanity; ten yard penalty; first down
+</message>
+ <lines>20</lines>
+ <words>30</words>
+ <characters>40</characters>
+ <bytes>0</bytes>
+ <bytes>1</bytes>
+ <bytes>2</bytes>
+ <bytes>3</bytes>
+ <bytes>4</bytes>
<mbuf-current>10</mbuf-current>
<mbuf-cache>20</mbuf-cache>
<mbuf-total>30</mbuf-total>
@@ -12,6 +36,7 @@
<ten>10</ten>
<eleven>11</eleven>
<unknown>1010</unknown>
+ <unknown>1010</unknown>
<min>15</min>
<cur>20</cur>
<max>30</max>
@@ -24,29 +49,36 @@
<min>15</min>
<cur>20</cur>
<max>125</max>
+ <val1>21</val1>
+ <val2>58368</val2>
+ <val3>100663296</val3>
+ <val4>44470272</val4>
+ <val5>1342172800</val5>
<flag>one</flag>
<flag>two</flag>
<flag>three</flag>
+ <works>null</works>
<empty-tag></empty-tag>
<t1>1000</t1>
<t2>test5000</t2>
<t3>ten-longx</t3>
<t4>xtest</t4>
- <error>
+ <__error>
<message>this is an error</message>
- </error>
- <error>
+ </__error>
+ <__error>
<message>two more errors</message>
- </error>
- <warning>
+ </__error>
+ <__warning>
<message>this is an warning</message>
- </warning>
- <warning>
+ </__warning>
+ <__warning>
<message>two more warnings</message>
- </warning>
+ </__warning>
<count>10</count>
<test>4</test>
- <message>improper use of profanity; ten yard penalty; first down</message>
+ <message>improper use of profanity; ten yard penalty; first down
+</message>
<error>
<message>Shut 'er down, Clancey! She's a-pumpin' mud! &lt;&gt;!,"!&lt;&gt;
</message>
diff --git a/contrib/libxo/tests/core/saved/test_03.E.err b/contrib/libxo/tests/core/saved/test_03.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_03.E.err
diff --git a/contrib/libxo/tests/core/saved/test_03.E.out b/contrib/libxo/tests/core/saved/test_03.E.out
new file mode 100644
index 0000000..3965783
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_03.E.out
@@ -0,0 +1,22 @@
+op create: [] []
+op open_container: [employees] []
+op open_list: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Terry]
+op string: [last-name] [Jones]
+op content: [department] [660]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Leslie]
+op string: [last-name] [Patterson]
+op content: [department] [341]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Ashley]
+op string: [last-name] [Smith]
+op content: [department] [1440]
+op close_instance: [employee] []
+op close_list: [employee] []
+op close_container: [employees] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_04.E.err b/contrib/libxo/tests/core/saved/test_04.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_04.E.err
diff --git a/contrib/libxo/tests/core/saved/test_04.E.out b/contrib/libxo/tests/core/saved/test_04.E.out
new file mode 100644
index 0000000..3965783
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_04.E.out
@@ -0,0 +1,22 @@
+op create: [] []
+op open_container: [employees] []
+op open_list: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Terry]
+op string: [last-name] [Jones]
+op content: [department] [660]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Leslie]
+op string: [last-name] [Patterson]
+op content: [department] [341]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Ashley]
+op string: [last-name] [Smith]
+op content: [department] [1440]
+op close_instance: [employee] []
+op close_list: [employee] []
+op close_container: [employees] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_05.E.err b/contrib/libxo/tests/core/saved/test_05.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_05.E.err
diff --git a/contrib/libxo/tests/core/saved/test_05.E.out b/contrib/libxo/tests/core/saved/test_05.E.out
new file mode 100644
index 0000000..0ca15f4
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_05.E.out
@@ -0,0 +1,96 @@
+op create: [] []
+op open_container: [indian-languages] []
+op string: [gurmukhi] [ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।]
+op string: [shahmukhi] [لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔]
+op string: [tranliteration] [lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.]
+op close_container: [indian-languages] []
+op open_container: [employees] []
+op open_leaf_list: [wc] []
+op string: [wc] [෴ - 0xdf4 - 1]
+op string: [wc] [ණ - 0xdab - 1]
+op string: [wc] [් - 0xdca - 0]
+op string: [wc] [ණ - 0xdab - 1]
+op string: [wc] [្ - 0x17d2 - 0]
+op string: [wc] [෴ - 0xdf4 - 1]
+op string: [wc] [1 - 0x31 - 1]
+op string: [wc] [͏ - 0x34f - 0]
+op string: [wc] [2 - 0x32 - 1]
+op string: [wc] [⃝ - 0x20dd - 0]
+op close_leaf_list: [wc] []
+op string: [fancy] [1͏2⃝]
+op string: [v1] [γιγνώσκειν]
+op string: [v2] [ὦ ἄνδρες ᾿Αθηναῖοι]
+op string: [v1] [ახლავე გაიაროთ რეგისტრაცია]
+op string: [v2] [Unicode-ის მეათე საერთაშორისო]
+op content: [width] [55]
+op string: [sinhala] [෴ණ්ණ෴]
+op content: [width] [4]
+op string: [sinhala] [෴]
+op content: [width] [1]
+op string: [sinhala] [෴ණ්ණ෴෴ණ්ණ෴]
+op content: [width] [8]
+op string: [not-sinhala] [123456]
+op string: [tag] [ර්‍ඝ]
+op content: [width] [2]
+op open_list: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Jim]
+op string: [nic-name] ["რეგტ"]
+op string: [last-name] [გთხოვთ ახ]
+op content: [department] [431]
+op content: [percent-time] [90]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Terry]
+op string: [nic-name] ["<one"]
+op string: [last-name] [Οὐχὶ ταὐτὰ παρίσταταί μοι Jones]
+op content: [department] [660]
+op content: [percent-time] [90]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Leslie]
+op string: [nic-name] ["Les"]
+op string: [last-name] [Patterson]
+op content: [department] [341]
+op content: [percent-time] [60]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Ashley]
+op string: [nic-name] ["Ash"]
+op string: [last-name] [Meter & Smith]
+op content: [department] [1440]
+op content: [percent-time] [40]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [0123456789]
+op string: [nic-name] ["0123456789"]
+op string: [last-name] [012345678901234567890]
+op content: [department] [1440]
+op content: [percent-time] [40]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [ახლა]
+op string: [nic-name] ["გაიარო"]
+op string: [last-name] [საერთაშორისო]
+op content: [department] [123]
+op content: [percent-time] [90]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [෴ණ්ණ෴෴ණ්ණ෴]
+op string: [nic-name] ["Mick"]
+op string: [last-name] [෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴]
+op content: [department] [110]
+op content: [percent-time] [20]
+op close_instance: [employee] []
+op close_list: [employee] []
+op close_container: [employees] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_05.H.out b/contrib/libxo/tests/core/saved/test_05.H.out
index 136b956..e8fd6d1 100644
--- a/contrib/libxo/tests/core/saved/test_05.H.out
+++ b/contrib/libxo/tests/core/saved/test_05.H.out
@@ -1 +1 @@
-<div class="line"><div class="text">Οὐχὶ ταὐτὰ παρίσταταί μοι </div><div class="data" data-tag="v1">γιγνώσκειν</div><div class="text">, </div><div class="data" data-tag="v2">ὦ ἄνδρες ᾿Αθηναῖοι</div></div><div class="line"><div class="text">გთხოვთ </div><div class="data" data-tag="v1">ახლავე გაიაროთ რეგისტრაცია</div><div class="text"> </div><div class="data" data-tag="v2">Unicode-ის მეათე საერთაშორისო</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">63</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴ණ්ණ෴</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">7</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">3</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴ණ්ණ</div><div class="text">]</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="not-sinhala">1234</div><div class="text">]</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="tag">ර්‍ඝ</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">5</div></div><div class="line"><div class="title">First Name </div><div class="title">Last Name </div><div class="title">Department </div><div class="title">Time (%)</div></div><div class="line"><div class="data" data-tag="first-name">Jim</div><div class="text"> (</div><div class="data" data-tag="nic-name">"რეგტ"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">გთხოვთ ახ </div><div class="data" data-tag="department"> 431</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">Terry</div><div class="text"> (</div><div class="data" data-tag="nic-name">"&lt;one"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Οὐχὶ ταὐτὰ παρ</div><div class="data" data-tag="department"> 660</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">Leslie</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Les"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Patterson </div><div class="data" data-tag="department"> 341</div><div class="data" data-tag="percent-time"> 60</div></div><div class="line"><div class="data" data-tag="first-name">Ashley</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Ash"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Meter &amp; Smith </div><div class="data" data-tag="department"> 1440</div><div class="data" data-tag="percent-time"> 40</div></div><div class="line"><div class="data" data-tag="first-name">0123456789</div><div class="text"> (</div><div class="data" data-tag="nic-name">"0123456789"</div><div class="text">)</div><div class="data" data-tag="last-name">01234567890123</div><div class="data" data-tag="department"> 1440</div><div class="data" data-tag="percent-time"> 40</div></div><div class="line"><div class="data" data-tag="first-name">ახლა</div><div class="text"> (</div><div class="data" data-tag="nic-name">"გაიარო"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">საერთაშორისო </div><div class="data" data-tag="department"> 123</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">෴ණ්ණ෴෴ණ්ණ෴</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Mick"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ</div><div class="data" data-tag="department"> 110</div><div class="data" data-tag="percent-time"> 20</div></div> \ No newline at end of file
+<div class="line"><div class="title">Sample text</div></div><div class="line"><div class="text">This sample text was taken from the Punjabi Wikipedia article on Lahore and transliterated into the Latin script.</div></div><div class="line"><div class="title">Gurmukhi:</div></div><div class="line"><div class="data" data-tag="gurmukhi">ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।</div></div><div class="line"><div class="title">Shahmukhi:</div></div><div class="line"><div class="data" data-tag="shahmukhi">لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔</div></div><div class="line"><div class="title">Transliteration</div><div class="text">:</div></div><div class="line"><div class="data" data-tag="tranliteration">lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">෴ - 0xdf4 - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">ණ - 0xdab - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">් - 0xdca - 0</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">ණ - 0xdab - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">្ - 0x17d2 - 0</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">෴ - 0xdf4 - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">1 - 0x31 - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">͏ - 0x34f - 0</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">2 - 0x32 - 1</div></div><div class="line"><div class="text">Wide char: </div><div class="data" data-tag="wc">⃝ - 0x20dd - 0</div></div><div class="line"><div class="text">Cool: [</div><div class="data" data-tag="fancy">1͏2⃝</div><div class="text">]</div></div><div class="line"><div class="text">Οὐχὶ ταὐτὰ παρίσταταί μοι </div><div class="data" data-tag="v1">γιγνώσκειν</div><div class="text">, </div><div class="data" data-tag="v2">ὦ ἄνδρες ᾿Αθηναῖοι</div></div><div class="line"><div class="text">გთხოვთ </div><div class="data" data-tag="v1">ახლავე გაიაროთ რეგისტრაცია</div><div class="text"> </div><div class="data" data-tag="v2">Unicode-ის მეათე საერთაშორისო</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">63</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴ණ්ණ෴</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">6</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">3</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="sinhala">෴ණ්ණ෴</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">6</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="not-sinhala">1234</div><div class="text">]</div></div><div class="line"><div class="text">[</div><div class="data" data-tag="tag">ර්‍ඝ</div><div class="text">]</div></div><div class="line"><div class="title">Width</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="width">4</div></div><div class="line"><div class="title">First Name </div><div class="title">Last Name </div><div class="title">Department </div><div class="title">Time (%)</div></div><div class="line"><div class="data" data-tag="first-name">Jim</div><div class="text"> (</div><div class="data" data-tag="nic-name">"რეგტ"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">გთხოვთ ახ </div><div class="data" data-tag="department"> 431</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">Terry</div><div class="text"> (</div><div class="data" data-tag="nic-name">"&lt;one"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Οὐχὶ ταὐτὰ παρ</div><div class="data" data-tag="department"> 660</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">Leslie</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Les"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Patterson </div><div class="data" data-tag="department"> 341</div><div class="data" data-tag="percent-time"> 60</div></div><div class="line"><div class="data" data-tag="first-name">Ashley</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Ash"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">Meter &amp; Smith </div><div class="data" data-tag="department"> 1440</div><div class="data" data-tag="percent-time"> 40</div></div><div class="line"><div class="data" data-tag="first-name">0123456789</div><div class="text"> (</div><div class="data" data-tag="nic-name">"0123456789"</div><div class="text">)</div><div class="data" data-tag="last-name">01234567890123</div><div class="data" data-tag="department"> 1440</div><div class="data" data-tag="percent-time"> 40</div></div><div class="line"><div class="data" data-tag="first-name">ახლა</div><div class="text"> (</div><div class="data" data-tag="nic-name">"გაიარო"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">საერთაშორისო </div><div class="data" data-tag="department"> 123</div><div class="data" data-tag="percent-time"> 90</div></div><div class="line"><div class="data" data-tag="first-name">෴ණ්ණ෴෴ණ්ණ෴</div><div class="text"> (</div><div class="data" data-tag="nic-name">"Mick"</div><div class="text">)</div><div class="padding"> </div><div class="data" data-tag="last-name">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴</div><div class="data" data-tag="department"> 110</div><div class="data" data-tag="percent-time"> 20</div></div> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_05.HIPx.out b/contrib/libxo/tests/core/saved/test_05.HIPx.out
index 105f848..4389b85 100644
--- a/contrib/libxo/tests/core/saved/test_05.HIPx.out
+++ b/contrib/libxo/tests/core/saved/test_05.HIPx.out
@@ -1,4 +1,74 @@
<div class="line">
+ <div class="title">Sample text</div>
+</div>
+<div class="line">
+ <div class="text">This sample text was taken from the Punjabi Wikipedia article on Lahore and transliterated into the Latin script.</div>
+</div>
+<div class="line">
+ <div class="title">Gurmukhi:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="gurmukhi" data-xpath="/indian-languages/gurmukhi">ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।</div>
+</div>
+<div class="line">
+ <div class="title">Shahmukhi:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="shahmukhi" data-xpath="/indian-languages/shahmukhi">لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔</div>
+</div>
+<div class="line">
+ <div class="title">Transliteration</div>
+ <div class="text">:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="tranliteration" data-xpath="/indian-languages/tranliteration">lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">෴ - 0xdf4 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">ණ - 0xdab - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">් - 0xdca - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">ණ - 0xdab - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">្ - 0x17d2 - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">෴ - 0xdf4 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">1 - 0x31 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">͏ - 0x34f - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">2 - 0x32 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc" data-xpath="/employees/wc">⃝ - 0x20dd - 0</div>
+</div>
+<div class="line">
+ <div class="text">Cool: [</div>
+ <div class="data" data-tag="fancy" data-xpath="/employees/fancy">1͏2⃝</div>
+ <div class="text">]</div>
+</div>
+<div class="line">
<div class="text">Οὐχὶ ταὐτὰ παρίσταταί μοι </div>
<div class="data" data-tag="v1" data-xpath="/employees/v1">γιγνώσκειν</div>
<div class="text">, </div>
@@ -25,7 +95,7 @@
<div class="title">Width</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="width" data-xpath="/employees/width">7</div>
+ <div class="data" data-tag="width" data-xpath="/employees/width">6</div>
</div>
<div class="line">
<div class="text">[</div>
@@ -40,10 +110,16 @@
</div>
<div class="line">
<div class="text">[</div>
- <div class="data" data-tag="sinhala" data-xpath="/employees/sinhala">෴ණ්ණ</div>
+ <div class="data" data-tag="sinhala" data-xpath="/employees/sinhala">෴ණ්ණ෴</div>
<div class="text">]</div>
</div>
<div class="line">
+ <div class="title">Width</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="width" data-xpath="/employees/width">6</div>
+</div>
+<div class="line">
<div class="text">[</div>
<div class="data" data-tag="not-sinhala" data-xpath="/employees/not-sinhala">1234</div>
<div class="text">]</div>
@@ -57,7 +133,7 @@
<div class="title">Width</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="width" data-xpath="/employees/width">5</div>
+ <div class="data" data-tag="width" data-xpath="/employees/width">4</div>
</div>
<div class="line">
<div class="title">First Name </div>
@@ -129,8 +205,8 @@
<div class="text"> (</div>
<div class="data" data-tag="nic-name" data-xpath="/employees/employee/nic-name">"Mick"</div>
<div class="text">)</div>
- <div class="padding"> </div>
- <div class="data" data-tag="last-name" data-xpath="/employees/employee/last-name" data-type="string" data-help="Last name of employee">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="last-name" data-xpath="/employees/employee/last-name" data-type="string" data-help="Last name of employee">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴</div>
<div class="data" data-tag="department" data-xpath="/employees/employee/department"> 110</div>
<div class="data" data-tag="percent-time" data-xpath="/employees/employee/percent-time" data-type="number" data-help="Percentage of full &amp; part time (%)"> 20</div>
</div>
diff --git a/contrib/libxo/tests/core/saved/test_05.HP.out b/contrib/libxo/tests/core/saved/test_05.HP.out
index e66cf2b..e3fcdda 100644
--- a/contrib/libxo/tests/core/saved/test_05.HP.out
+++ b/contrib/libxo/tests/core/saved/test_05.HP.out
@@ -1,4 +1,74 @@
<div class="line">
+ <div class="title">Sample text</div>
+</div>
+<div class="line">
+ <div class="text">This sample text was taken from the Punjabi Wikipedia article on Lahore and transliterated into the Latin script.</div>
+</div>
+<div class="line">
+ <div class="title">Gurmukhi:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="gurmukhi">ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।</div>
+</div>
+<div class="line">
+ <div class="title">Shahmukhi:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="shahmukhi">لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔</div>
+</div>
+<div class="line">
+ <div class="title">Transliteration</div>
+ <div class="text">:</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="tranliteration">lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">෴ - 0xdf4 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">ණ - 0xdab - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">් - 0xdca - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">ණ - 0xdab - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">្ - 0x17d2 - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">෴ - 0xdf4 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">1 - 0x31 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">͏ - 0x34f - 0</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">2 - 0x32 - 1</div>
+</div>
+<div class="line">
+ <div class="text">Wide char: </div>
+ <div class="data" data-tag="wc">⃝ - 0x20dd - 0</div>
+</div>
+<div class="line">
+ <div class="text">Cool: [</div>
+ <div class="data" data-tag="fancy">1͏2⃝</div>
+ <div class="text">]</div>
+</div>
+<div class="line">
<div class="text">Οὐχὶ ταὐτὰ παρίσταταί μοι </div>
<div class="data" data-tag="v1">γιγνώσκειν</div>
<div class="text">, </div>
@@ -25,7 +95,7 @@
<div class="title">Width</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="width">7</div>
+ <div class="data" data-tag="width">6</div>
</div>
<div class="line">
<div class="text">[</div>
@@ -40,10 +110,16 @@
</div>
<div class="line">
<div class="text">[</div>
- <div class="data" data-tag="sinhala">෴ණ්ණ</div>
+ <div class="data" data-tag="sinhala">෴ණ්ණ෴</div>
<div class="text">]</div>
</div>
<div class="line">
+ <div class="title">Width</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="width">6</div>
+</div>
+<div class="line">
<div class="text">[</div>
<div class="data" data-tag="not-sinhala">1234</div>
<div class="text">]</div>
@@ -57,7 +133,7 @@
<div class="title">Width</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="width">5</div>
+ <div class="data" data-tag="width">4</div>
</div>
<div class="line">
<div class="title">First Name </div>
@@ -129,8 +205,8 @@
<div class="text"> (</div>
<div class="data" data-tag="nic-name">"Mick"</div>
<div class="text">)</div>
- <div class="padding"> </div>
- <div class="data" data-tag="last-name">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="last-name">෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴</div>
<div class="data" data-tag="department"> 110</div>
<div class="data" data-tag="percent-time"> 20</div>
</div>
diff --git a/contrib/libxo/tests/core/saved/test_05.J.out b/contrib/libxo/tests/core/saved/test_05.J.out
index 25d13ea..3525c43 100644
--- a/contrib/libxo/tests/core/saved/test_05.J.out
+++ b/contrib/libxo/tests/core/saved/test_05.J.out
@@ -1,2 +1,3 @@
-{"employees": {"v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","width":55,"sinhala":"෴ණ්ණ෴","width":5,"sinhala":"෴","width":1,"sinhala":"෴ණ්ණ෴෴ණ්ණ෴","not-sinhala":"123456","tag":"ර්‍ඝ","width":3, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"benefits":"full"}, {"first-name":"෴ණ්ණ෴෴ණ්ණ෴","nic-name":"\"Mick\"","last-name":"෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴","department":110,"percent-time":20}]}
+{"indian-languages": {"gurmukhi":"ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।","shahmukhi":"لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔","tranliteration":"lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē."}
+, "employees": {"wc": ["෴ - 0xdf4 - 1","ණ - 0xdab - 1","් - 0xdca - 0","ණ - 0xdab - 1","្ - 0x17d2 - 0","෴ - 0xdf4 - 1","1 - 0x31 - 1","͏ - 0x34f - 0","2 - 0x32 - 1","⃝ - 0x20dd - 0"],"fancy":"1͏2⃝","v1":"γιγνώσκειν","v2":"ὦ ἄνδρες ᾿Αθηναῖοι","v1":"ახლავე გაიაროთ რეგისტრაცია","v2":"Unicode-ის მეათე საერთაშორისო","width":55,"sinhala":"෴ණ්ණ෴","width":4,"sinhala":"෴","width":1,"sinhala":"෴ණ්ණ෴෴ණ්ණ෴","width":8,"not-sinhala":"123456","tag":"ර්‍ඝ","width":2, "employee": [{"first-name":"Jim","nic-name":"\"რეგტ\"","last-name":"გთხოვთ ახ","department":431,"percent-time":90,"benefits":"full"}, {"first-name":"Terry","nic-name":"\"<one\"","last-name":"Οὐχὶ ταὐτὰ παρίσταταί μοι Jones","department":660,"percent-time":90,"benefits":"full"}, {"first-name":"Leslie","nic-name":"\"Les\"","last-name":"Patterson","department":341,"percent-time":60,"benefits":"full"}, {"first-name":"Ashley","nic-name":"\"Ash\"","last-name":"Meter & Smith","department":1440,"percent-time":40}, {"first-name":"0123456789","nic-name":"\"0123456789\"","last-name":"012345678901234567890","department":1440,"percent-time":40}, {"first-name":"ახლა","nic-name":"\"გაიარო\"","last-name":"საერთაშორისო","department":123,"percent-time":90,"benefits":"full"}, {"first-name":"෴ණ්ණ෴෴ණ්ණ෴","nic-name":"\"Mick\"","last-name":"෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴","department":110,"percent-time":20}]}
}
diff --git a/contrib/libxo/tests/core/saved/test_05.JP.out b/contrib/libxo/tests/core/saved/test_05.JP.out
index c321917..e027034 100644
--- a/contrib/libxo/tests/core/saved/test_05.JP.out
+++ b/contrib/libxo/tests/core/saved/test_05.JP.out
@@ -1,18 +1,38 @@
{
+ "indian-languages": {
+ "gurmukhi": "ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।",
+ "shahmukhi": "لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔",
+ "tranliteration": "lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē."
+ }
+,
"employees": {
+ "wc": [
+ "෴ - 0xdf4 - 1",
+ "ණ - 0xdab - 1",
+ "් - 0xdca - 0",
+ "ණ - 0xdab - 1",
+ "្ - 0x17d2 - 0",
+ "෴ - 0xdf4 - 1",
+ "1 - 0x31 - 1",
+ "͏ - 0x34f - 0",
+ "2 - 0x32 - 1",
+ "⃝ - 0x20dd - 0"
+ ],
+ "fancy": "1͏2⃝",
"v1": "γιγνώσκειν",
"v2": "ὦ ἄνδρες ᾿Αθηναῖοι",
"v1": "ახლავე გაიაროთ რეგისტრაცია",
"v2": "Unicode-ის მეათე საერთაშორისო",
"width": 55,
"sinhala": "෴ණ්ණ෴",
- "width": 5,
+ "width": 4,
"sinhala": "෴",
"width": 1,
"sinhala": "෴ණ්ණ෴෴ණ්ණ෴",
+ "width": 8,
"not-sinhala": "123456",
"tag": "ර්‍ඝ",
- "width": 3,
+ "width": 2,
"employee": [
{
"first-name": "Jim",
diff --git a/contrib/libxo/tests/core/saved/test_05.T.out b/contrib/libxo/tests/core/saved/test_05.T.out
index db713a6..397a64e 100644
--- a/contrib/libxo/tests/core/saved/test_05.T.out
+++ b/contrib/libxo/tests/core/saved/test_05.T.out
@@ -1,14 +1,34 @@
+Sample text
+This sample text was taken from the Punjabi Wikipedia article on Lahore and transliterated into the Latin script.
+Gurmukhi:
+ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।
+Shahmukhi:
+لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔
+Transliteration:
+lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.
+Wide char: ෴ - 0xdf4 - 1
+Wide char: ණ - 0xdab - 1
+Wide char: ් - 0xdca - 0
+Wide char: ණ - 0xdab - 1
+Wide char: ្ - 0x17d2 - 0
+Wide char: ෴ - 0xdf4 - 1
+Wide char: 1 - 0x31 - 1
+Wide char: ͏ - 0x34f - 0
+Wide char: 2 - 0x32 - 1
+Wide char: ⃝ - 0x20dd - 0
+Cool: [1͏2⃝]
Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι
გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო
Width: 63
[෴ණ්ණ෴]
-Width: 7
+Width: 6
[෴]
Width: 3
-[෴ණ්ණ]
+[෴ණ්ණ෴]
+Width: 6
[1234]
[ර්‍ඝ]
-Width: 5
+Width: 4
First Name Last Name Department Time (%)
Jim ("რეგტ") გთხოვთ ახ 431 90
Terry ("<one") Οὐχὶ ταὐτὰ παρ 660 90
@@ -16,4 +36,4 @@ Leslie ("Les") Patterson 341 60
Ashley ("Ash") Meter & Smith 1440 40
0123456789 ("0123456789")01234567890123 1440 40
ახლა ("გაიარო") საერთაშორისო 123 90
-෴ණ්ණ෴෴ණ්ණ෴ ("Mick") ෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ 110 20
+෴ණ්ණ෴෴ණ්ණ෴ ("Mick") ෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴ 110 20
diff --git a/contrib/libxo/tests/core/saved/test_05.X.out b/contrib/libxo/tests/core/saved/test_05.X.out
index 7cd2926..2a40010 100644
--- a/contrib/libxo/tests/core/saved/test_05.X.out
+++ b/contrib/libxo/tests/core/saved/test_05.X.out
@@ -1 +1 @@
-<employees><v1>γιγνώσκειν</v1><v2>ὦ ἄνδρες ᾿Αθηναῖοι</v2><v1>ახლავე გაიაროთ რეგისტრაცია</v1><v2>Unicode-ის მეათე საერთაშორისო</v2><width>55</width><sinhala>෴ණ්ණ෴</sinhala><width>5</width><sinhala>෴</sinhala><width>1</width><sinhala>෴ණ්ණ෴෴ණ්ණ෴</sinhala><not-sinhala>123456</not-sinhala><tag>ර්‍ඝ</tag><width>3</width><employee><first-name>Jim</first-name><nic-name>"რეგტ"</nic-name><last-name>გთხოვთ ახ</last-name><department>431</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Terry</first-name><nic-name>"&lt;one"</nic-name><last-name>Οὐχὶ ταὐτὰ παρίσταταί μοι Jones</last-name><department>660</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Leslie</first-name><nic-name>"Les"</nic-name><last-name>Patterson</last-name><department>341</department><percent-time>60</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Ashley</first-name><nic-name>"Ash"</nic-name><last-name>Meter &amp; Smith</last-name><department>1440</department><percent-time>40</percent-time></employee><employee><first-name>0123456789</first-name><nic-name>"0123456789"</nic-name><last-name>012345678901234567890</last-name><department>1440</department><percent-time>40</percent-time></employee><employee><first-name>ახლა</first-name><nic-name>"გაიარო"</nic-name><last-name>საერთაშორისო</last-name><department>123</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>෴ණ්ණ෴෴ණ්ණ෴</first-name><nic-name>"Mick"</nic-name><last-name>෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴</last-name><department>110</department><percent-time>20</percent-time></employee></employees> \ No newline at end of file
+<indian-languages><gurmukhi>ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।</gurmukhi><shahmukhi>لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔</shahmukhi><tranliteration>lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.</tranliteration></indian-languages><employees><wc>෴ - 0xdf4 - 1</wc><wc>ණ - 0xdab - 1</wc><wc>් - 0xdca - 0</wc><wc>ණ - 0xdab - 1</wc><wc>្ - 0x17d2 - 0</wc><wc>෴ - 0xdf4 - 1</wc><wc>1 - 0x31 - 1</wc><wc>͏ - 0x34f - 0</wc><wc>2 - 0x32 - 1</wc><wc>⃝ - 0x20dd - 0</wc><fancy>1͏2⃝</fancy><v1>γιγνώσκειν</v1><v2>ὦ ἄνδρες ᾿Αθηναῖοι</v2><v1>ახლავე გაიაროთ რეგისტრაცია</v1><v2>Unicode-ის მეათე საერთაშორისო</v2><width>55</width><sinhala>෴ණ්ණ෴</sinhala><width>4</width><sinhala>෴</sinhala><width>1</width><sinhala>෴ණ්ණ෴෴ණ්ණ෴</sinhala><width>8</width><not-sinhala>123456</not-sinhala><tag>ර්‍ඝ</tag><width>2</width><employee><first-name>Jim</first-name><nic-name>"რეგტ"</nic-name><last-name>გთხოვთ ახ</last-name><department>431</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Terry</first-name><nic-name>"&lt;one"</nic-name><last-name>Οὐχὶ ταὐτὰ παρίσταταί μοι Jones</last-name><department>660</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Leslie</first-name><nic-name>"Les"</nic-name><last-name>Patterson</last-name><department>341</department><percent-time>60</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>Ashley</first-name><nic-name>"Ash"</nic-name><last-name>Meter &amp; Smith</last-name><department>1440</department><percent-time>40</percent-time></employee><employee><first-name>0123456789</first-name><nic-name>"0123456789"</nic-name><last-name>012345678901234567890</last-name><department>1440</department><percent-time>40</percent-time></employee><employee><first-name>ახლა</first-name><nic-name>"გაიარო"</nic-name><last-name>საერთაშორისო</last-name><department>123</department><percent-time>90</percent-time><benefits full-time="honest &amp; for true">full</benefits></employee><employee><first-name>෴ණ්ණ෴෴ණ්ණ෴</first-name><nic-name>"Mick"</nic-name><last-name>෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴</last-name><department>110</department><percent-time>20</percent-time></employee></employees> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_05.XP.out b/contrib/libxo/tests/core/saved/test_05.XP.out
index 6ef573f..cf48b5b 100644
--- a/contrib/libxo/tests/core/saved/test_05.XP.out
+++ b/contrib/libxo/tests/core/saved/test_05.XP.out
@@ -1,17 +1,34 @@
+<indian-languages>
+ <gurmukhi>ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।</gurmukhi>
+ <shahmukhi>لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔</shahmukhi>
+ <tranliteration>lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.</tranliteration>
+</indian-languages>
<employees>
+ <wc>෴ - 0xdf4 - 1</wc>
+ <wc>ණ - 0xdab - 1</wc>
+ <wc>් - 0xdca - 0</wc>
+ <wc>ණ - 0xdab - 1</wc>
+ <wc>្ - 0x17d2 - 0</wc>
+ <wc>෴ - 0xdf4 - 1</wc>
+ <wc>1 - 0x31 - 1</wc>
+ <wc>͏ - 0x34f - 0</wc>
+ <wc>2 - 0x32 - 1</wc>
+ <wc>⃝ - 0x20dd - 0</wc>
+ <fancy>1͏2⃝</fancy>
<v1>γιγνώσκειν</v1>
<v2>ὦ ἄνδρες ᾿Αθηναῖοι</v2>
<v1>ახლავე გაიაროთ რეგისტრაცია</v1>
<v2>Unicode-ის მეათე საერთაშორისო</v2>
<width>55</width>
<sinhala>෴ණ්ණ෴</sinhala>
- <width>5</width>
+ <width>4</width>
<sinhala>෴</sinhala>
<width>1</width>
<sinhala>෴ණ්ණ෴෴ණ්ණ෴</sinhala>
+ <width>8</width>
<not-sinhala>123456</not-sinhala>
<tag>ර්‍ඝ</tag>
- <width>3</width>
+ <width>2</width>
<employee>
<first-name>Jim</first-name>
<nic-name>"რეგტ"</nic-name>
diff --git a/contrib/libxo/tests/core/saved/test_06.E.err b/contrib/libxo/tests/core/saved/test_06.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_06.E.err
diff --git a/contrib/libxo/tests/core/saved/test_06.E.out b/contrib/libxo/tests/core/saved/test_06.E.out
new file mode 100644
index 0000000..3965783
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_06.E.out
@@ -0,0 +1,22 @@
+op create: [] []
+op open_container: [employees] []
+op open_list: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Terry]
+op string: [last-name] [Jones]
+op content: [department] [660]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Leslie]
+op string: [last-name] [Patterson]
+op content: [department] [341]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Ashley]
+op string: [last-name] [Smith]
+op content: [department] [1440]
+op close_instance: [employee] []
+op close_list: [employee] []
+op close_container: [employees] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_07.E.err b/contrib/libxo/tests/core/saved/test_07.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_07.E.err
diff --git a/contrib/libxo/tests/core/saved/test_07.E.out b/contrib/libxo/tests/core/saved/test_07.E.out
new file mode 100644
index 0000000..45e4b69
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_07.E.out
@@ -0,0 +1,76 @@
+op create: [] []
+op open_container: [employees] []
+op open_list: [test] []
+op open_instance: [test] []
+op string: [filename] [(null)]
+op close_instance: [test] []
+op close_list: [test] []
+op string: [v1] [γιγνώσκειν]
+op string: [v2] [ὦ ἄνδρες ᾿Αθηναῖοι]
+op content: [columns] [28]
+op content: [columns] [2]
+op string: [v1] [ახლავე გაიაროთ რეგისტრაცია]
+op string: [v2] [Unicode-ის მეათე საერთაშორისო]
+op content: [columns] [55]
+op content: [columns] [0]
+op open_list: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Jim]
+op string: [nic-name] ["რეგტ"]
+op string: [last-name] [გთხოვთ ახ]
+op content: [department] [431]
+op content: [percent-time] [90]
+op content: [columns] [23]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Terry]
+op string: [nic-name] ["<one"]
+op string: [last-name] [Οὐχὶ ταὐτὰ παρίσταταί μοι Jones]
+op content: [department] [660]
+op content: [percent-time] [90]
+op content: [columns] [47]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Leslie]
+op string: [nic-name] ["Les"]
+op string: [last-name] [Patterson]
+op content: [department] [341]
+op content: [percent-time] [60]
+op content: [columns] [25]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [Ashley]
+op string: [nic-name] ["Ash"]
+op string: [last-name] [Meter & Smith]
+op content: [department] [1440]
+op content: [percent-time] [40]
+op content: [columns] [30]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [0123456789]
+op string: [nic-name] ["0123456789"]
+op string: [last-name] [012345678901234567890]
+op content: [department] [1440]
+op content: [percent-time] [40]
+op content: [columns] [49]
+op close_instance: [employee] []
+op open_instance: [employee] []
+op string: [first-name] [ახლა]
+op string: [nic-name] ["გაიარო"]
+op string: [last-name] [საერთაშორისო]
+op content: [department] [123]
+op content: [percent-time] [90]
+op content: [columns] [29]
+op attr: [full-time] [honest & for true]
+op string: [benefits] [full]
+op close_instance: [employee] []
+op close_list: [employee] []
+op close_container: [employees] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_08.E.err b/contrib/libxo/tests/core/saved/test_08.E.err
new file mode 100644
index 0000000..011858c
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_08.E.err
@@ -0,0 +1,18 @@
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.E.out b/contrib/libxo/tests/core/saved/test_08.E.out
new file mode 100644
index 0000000..c00f4b8
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_08.E.out
@@ -0,0 +1,186 @@
+op create: [] []
+op open_container: [top] []
+op open_container: [data] []
+op open_container: [contents] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [name] [gum]
+op content: [count] [1412]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [rope]
+op content: [count] [85]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [ladder]
+op content: [count] [0]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [bolt]
+op content: [count] [4123]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [water]
+op content: [count] [17]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [contents] []
+op close_container: [data] []
+op open_container: [data2] []
+op open_container: [contents] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [name] [gum]
+op content: [count] [1412]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [rope]
+op content: [count] [85]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [ladder]
+op content: [count] [0]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [bolt]
+op content: [count] [4123]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [water]
+op content: [count] [17]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [contents] []
+op close_container: [data2] []
+op open_container: [data3] []
+op open_container: [contents] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [name] [gum]
+op content: [count] [1412]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [rope]
+op content: [count] [85]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [ladder]
+op content: [count] [0]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [bolt]
+op content: [count] [4123]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [water]
+op content: [count] [17]
+op string: [test] [one]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [contents] []
+op close_container: [data3] []
+op open_container: [data4] []
+op open_container: [contents] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [name] [gum]
+op content: [count] [1412]
+op open_list: [sub] []
+op open_instance: [sub] []
+op content: [name] [0]
+op content: [next] [1]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [1]
+op content: [next] [2]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [2]
+op content: [next] [3]
+op close_instance: [sub] []
+op close_list: [sub] []
+op content: [last] [3]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [rope]
+op content: [count] [85]
+op open_list: [sub] []
+op open_instance: [sub] []
+op content: [name] [0]
+op content: [next] [1]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [1]
+op content: [next] [2]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [2]
+op content: [next] [3]
+op close_instance: [sub] []
+op close_list: [sub] []
+op content: [last] [3]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [ladder]
+op content: [count] [0]
+op open_list: [sub] []
+op open_instance: [sub] []
+op content: [name] [0]
+op content: [next] [1]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [1]
+op content: [next] [2]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [2]
+op content: [next] [3]
+op close_instance: [sub] []
+op close_list: [sub] []
+op content: [last] [3]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [bolt]
+op content: [count] [4123]
+op open_list: [sub] []
+op open_instance: [sub] []
+op content: [name] [0]
+op content: [next] [1]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [1]
+op content: [next] [2]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [2]
+op content: [next] [3]
+op close_instance: [sub] []
+op close_list: [sub] []
+op content: [last] [3]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [name] [water]
+op content: [count] [17]
+op open_list: [sub] []
+op open_instance: [sub] []
+op content: [name] [0]
+op content: [next] [1]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [1]
+op content: [next] [2]
+op close_instance: [sub] []
+op open_instance: [sub] []
+op content: [name] [2]
+op content: [next] [3]
+op close_instance: [sub] []
+op close_list: [sub] []
+op content: [last] [3]
+op string: [test] [one]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [contents] []
+op close_container: [data4] []
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_08.H.err b/contrib/libxo/tests/core/saved/test_08.H.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.H.err
+++ b/contrib/libxo/tests/core/saved/test_08.H.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.HIPx.err b/contrib/libxo/tests/core/saved/test_08.HIPx.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.HIPx.err
+++ b/contrib/libxo/tests/core/saved/test_08.HIPx.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.HIPx.out b/contrib/libxo/tests/core/saved/test_08.HIPx.out
index 87bfbed..2263347 100644
--- a/contrib/libxo/tests/core/saved/test_08.HIPx.out
+++ b/contrib/libxo/tests/core/saved/test_08.HIPx.out
@@ -31,24 +31,24 @@
<div class="title"> Count</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/contents/item/name" data-key="key">gum </div>
- <div class="data" data-tag="count" data-xpath="/top/data/contents/item[name = 'gum']/count"> 1412</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/contents/item/name" data-key="key">gum </div>
+ <div class="data" data-tag="count" data-xpath="/top/data2/contents/item[name = 'gum']/count"> 1412</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/contents/item/name" data-key="key">rope </div>
- <div class="data" data-tag="count" data-xpath="/top/data/contents/item[name = 'rope']/count"> 85</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/contents/item/name" data-key="key">rope </div>
+ <div class="data" data-tag="count" data-xpath="/top/data2/contents/item[name = 'rope']/count"> 85</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/contents/item/name" data-key="key">ladder </div>
- <div class="data" data-tag="count" data-xpath="/top/data/contents/item[name = 'ladder']/count"> 0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/contents/item/name" data-key="key">ladder </div>
+ <div class="data" data-tag="count" data-xpath="/top/data2/contents/item[name = 'ladder']/count"> 0</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/contents/item/name" data-key="key">bolt </div>
- <div class="data" data-tag="count" data-xpath="/top/data/contents/item[name = 'bolt']/count"> 4123</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/contents/item/name" data-key="key">bolt </div>
+ <div class="data" data-tag="count" data-xpath="/top/data2/contents/item[name = 'bolt']/count"> 4123</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/contents/item/name" data-key="key">water </div>
- <div class="data" data-tag="count" data-xpath="/top/data/contents/item[name = 'water']/count"> 17</div>
+ <div class="data" data-tag="name" data-xpath="/top/data2/contents/item/name" data-key="key">water </div>
+ <div class="data" data-tag="count" data-xpath="/top/data2/contents/item[name = 'water']/count"> 17</div>
</div>
<div class="line">
</div>
@@ -59,27 +59,27 @@
<div class="title"> Count</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">gum </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'gum']/count"> 1412</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/m1/contents/item/name" data-key="key">gum </div>
+ <div class="data" data-tag="count" data-xpath="/top/data3/m1/contents/item[name = 'gum']/count"> 1412</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">rope </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'rope']/count"> 85</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/m1/contents/item/name" data-key="key">rope </div>
+ <div class="data" data-tag="count" data-xpath="/top/data3/m1/contents/item[name = 'rope']/count"> 85</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">ladder </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'ladder']/count"> 0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/m1/contents/item/name" data-key="key">ladder </div>
+ <div class="data" data-tag="count" data-xpath="/top/data3/m1/contents/item[name = 'ladder']/count"> 0</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">bolt </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'bolt']/count"> 4123</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/m1/contents/item/name" data-key="key">bolt </div>
+ <div class="data" data-tag="count" data-xpath="/top/data3/m1/contents/item[name = 'bolt']/count"> 4123</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">water </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'water']/count"> 17</div>
+ <div class="data" data-tag="name" data-xpath="/top/data3/m1/contents/item/name" data-key="key">water </div>
+ <div class="data" data-tag="count" data-xpath="/top/data3/m1/contents/item[name = 'water']/count"> 17</div>
</div>
<div class="line">
- <div class="data" data-tag="test" data-xpath="/top/data/m1/contents/item[name = 'water']/test">one</div>
+ <div class="data" data-tag="test" data-xpath="/top/data3/m1/contents/item[name = 'water']/test">one</div>
</div>
<div class="line">
</div>
@@ -88,177 +88,177 @@
<div class="title"> Count</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">gum </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'gum']/count"> 1412</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item/name" data-key="key">gum </div>
+ <div class="data" data-tag="count" data-xpath="/top/data4/m1/contents/item[name = 'gum']/count"> 1412</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/name">0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/name">0</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/next">1</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/next">1</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/name">1</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/name">1</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/next">2</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/next">2</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/name">2</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/name">2</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'gum']/m2/sub/next">3</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'gum']/m2/sub/next">3</div>
</div>
<div class="line">
<div class="label">Last</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="last" data-xpath="/top/data/m1/contents/item[name = 'gum']/last">3</div>
+ <div class="data" data-tag="last" data-xpath="/top/data4/m1/contents/item[name = 'gum']/last">3</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">rope </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'rope']/count"> 85</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item/name" data-key="key">rope </div>
+ <div class="data" data-tag="count" data-xpath="/top/data4/m1/contents/item[name = 'rope']/count"> 85</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/name">0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/name">0</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/next">1</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/next">1</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/name">1</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/name">1</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/next">2</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/next">2</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/name">2</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/name">2</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'rope']/m2/sub/next">3</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'rope']/m2/sub/next">3</div>
</div>
<div class="line">
<div class="label">Last</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="last" data-xpath="/top/data/m1/contents/item[name = 'rope']/last">3</div>
+ <div class="data" data-tag="last" data-xpath="/top/data4/m1/contents/item[name = 'rope']/last">3</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">ladder </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'ladder']/count"> 0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item/name" data-key="key">ladder </div>
+ <div class="data" data-tag="count" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/count"> 0</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/name">0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/name">0</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/next">1</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/next">1</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/name">1</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/name">1</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/next">2</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/next">2</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/name">2</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/name">2</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'ladder']/m2/sub/next">3</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/m2/sub/next">3</div>
</div>
<div class="line">
<div class="label">Last</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="last" data-xpath="/top/data/m1/contents/item[name = 'ladder']/last">3</div>
+ <div class="data" data-tag="last" data-xpath="/top/data4/m1/contents/item[name = 'ladder']/last">3</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">bolt </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'bolt']/count"> 4123</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item/name" data-key="key">bolt </div>
+ <div class="data" data-tag="count" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/count"> 4123</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/name">0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/name">0</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/next">1</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/next">1</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/name">1</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/name">1</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/next">2</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/next">2</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/name">2</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/name">2</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'bolt']/m2/sub/next">3</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/m2/sub/next">3</div>
</div>
<div class="line">
<div class="label">Last</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="last" data-xpath="/top/data/m1/contents/item[name = 'bolt']/last">3</div>
+ <div class="data" data-tag="last" data-xpath="/top/data4/m1/contents/item[name = 'bolt']/last">3</div>
</div>
<div class="line">
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item/name" data-key="key">water </div>
- <div class="data" data-tag="count" data-xpath="/top/data/m1/contents/item[name = 'water']/count"> 17</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item/name" data-key="key">water </div>
+ <div class="data" data-tag="count" data-xpath="/top/data4/m1/contents/item[name = 'water']/count"> 17</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/name">0</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/name">0</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/next">1</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/next">1</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/name">1</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/name">1</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/next">2</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/next">2</div>
</div>
<div class="line">
<div class="label">Name</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="name" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/name">2</div>
+ <div class="data" data-tag="name" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/name">2</div>
<div class="text"> + 1 = </div>
- <div class="data" data-tag="next" data-xpath="/top/data/m1/contents/item[name = 'water']/m2/sub/next">3</div>
+ <div class="data" data-tag="next" data-xpath="/top/data4/m1/contents/item[name = 'water']/m2/sub/next">3</div>
</div>
<div class="line">
<div class="label">Last</div>
<div class="decoration">:</div>
<div class="padding"> </div>
- <div class="data" data-tag="last" data-xpath="/top/data/m1/contents/item[name = 'water']/last">3</div>
+ <div class="data" data-tag="last" data-xpath="/top/data4/m1/contents/item[name = 'water']/last">3</div>
</div>
<div class="line">
- <div class="data" data-tag="test" data-xpath="/top/data/m1/contents/item[name = 'water']/test">one</div>
+ <div class="data" data-tag="test" data-xpath="/top/data4/m1/contents/item[name = 'water']/test">one</div>
</div>
<div class="line">
</div>
diff --git a/contrib/libxo/tests/core/saved/test_08.HP.err b/contrib/libxo/tests/core/saved/test_08.HP.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.HP.err
+++ b/contrib/libxo/tests/core/saved/test_08.HP.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.J.err b/contrib/libxo/tests/core/saved/test_08.J.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.J.err
+++ b/contrib/libxo/tests/core/saved/test_08.J.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.J.out b/contrib/libxo/tests/core/saved/test_08.J.out
index cbce091..9d89755 100644
--- a/contrib/libxo/tests/core/saved/test_08.J.out
+++ b/contrib/libxo/tests/core/saved/test_08.J.out
@@ -1,2 +1,2 @@
-{"top": {"data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17,"test":"one"}]}}, "data": {"contents": {"item": [{"name":"gum","count":1412, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"rope","count":85, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"ladder","count":0, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"bolt","count":4123, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"water","count":17, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3,"test":"one"}]}}}
+{"top": {"data": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data2": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17}]}}, "data3": {"contents": {"item": [{"name":"gum","count":1412}, {"name":"rope","count":85}, {"name":"ladder","count":0}, {"name":"bolt","count":4123}, {"name":"water","count":17,"test":"one"}]}}, "data4": {"contents": {"item": [{"name":"gum","count":1412, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"rope","count":85, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"ladder","count":0, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"bolt","count":4123, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3}, {"name":"water","count":17, "sub": [{"name":0,"next":1}, {"name":1,"next":2}, {"name":2,"next":3}],"last":3,"test":"one"}]}}}
}
diff --git a/contrib/libxo/tests/core/saved/test_08.JP.err b/contrib/libxo/tests/core/saved/test_08.JP.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.JP.err
+++ b/contrib/libxo/tests/core/saved/test_08.JP.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.JP.out b/contrib/libxo/tests/core/saved/test_08.JP.out
index 932d6a1..46f3de5 100644
--- a/contrib/libxo/tests/core/saved/test_08.JP.out
+++ b/contrib/libxo/tests/core/saved/test_08.JP.out
@@ -26,7 +26,7 @@
]
}
},
- "data": {
+ "data2": {
"contents": {
"item": [
{
@@ -52,7 +52,7 @@
]
}
},
- "data": {
+ "data3": {
"contents": {
"item": [
{
@@ -79,7 +79,7 @@
]
}
},
- "data": {
+ "data4": {
"contents": {
"item": [
{
diff --git a/contrib/libxo/tests/core/saved/test_08.T.err b/contrib/libxo/tests/core/saved/test_08.T.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.T.err
+++ b/contrib/libxo/tests/core/saved/test_08.T.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.X.err b/contrib/libxo/tests/core/saved/test_08.X.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.X.err
+++ b/contrib/libxo/tests/core/saved/test_08.X.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.X.out b/contrib/libxo/tests/core/saved/test_08.X.out
index 5eb72b2..b8ee392 100644
--- a/contrib/libxo/tests/core/saved/test_08.X.out
+++ b/contrib/libxo/tests/core/saved/test_08.X.out
@@ -1 +1 @@
-<top><data><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count></item></contents></data><data><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count></item></contents></data><data><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count><test>one</test></item></contents></data><data><contents><item><name key="key">gum</name><count>1412</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">rope</name><count>85</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">ladder</name><count>0</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">bolt</name><count>4123</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">water</name><count>17</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last><test>one</test></item></contents></data></top> \ No newline at end of file
+<top><data><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count></item></contents></data><data2><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count></item></contents></data2><data3><contents><item><name key="key">gum</name><count>1412</count></item><item><name key="key">rope</name><count>85</count></item><item><name key="key">ladder</name><count>0</count></item><item><name key="key">bolt</name><count>4123</count></item><item><name key="key">water</name><count>17</count><test>one</test></item></contents></data3><data4><contents><item><name key="key">gum</name><count>1412</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">rope</name><count>85</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">ladder</name><count>0</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">bolt</name><count>4123</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last></item><item><name key="key">water</name><count>17</count><sub><name>0</name><next>1</next></sub><sub><name>1</name><next>2</next></sub><sub><name>2</name><next>3</next></sub><last>3</last><test>one</test></item></contents></data4></top> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_08.XP.err b/contrib/libxo/tests/core/saved/test_08.XP.err
index 445bfb7..011858c 100644
--- a/contrib/libxo/tests/core/saved/test_08.XP.err
+++ b/contrib/libxo/tests/core/saved/test_08.XP.err
@@ -1,18 +1,18 @@
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm2'; not found 'data'
-test: close (xo_close_container) fails at marker 'm1'; not found 'data'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data3'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm2'; not found 'data4'
+test: close (xo_close_container) fails at marker 'm1'; not found 'data4'
test: close (xo_close_container) fails at marker 'm1'; not found 'top'
diff --git a/contrib/libxo/tests/core/saved/test_08.XP.out b/contrib/libxo/tests/core/saved/test_08.XP.out
index 99520c0..1d9b70f 100644
--- a/contrib/libxo/tests/core/saved/test_08.XP.out
+++ b/contrib/libxo/tests/core/saved/test_08.XP.out
@@ -23,7 +23,7 @@
</item>
</contents>
</data>
- <data>
+ <data2>
<contents>
<item>
<name key="key">gum</name>
@@ -46,8 +46,8 @@
<count>17</count>
</item>
</contents>
- </data>
- <data>
+ </data2>
+ <data3>
<contents>
<item>
<name key="key">gum</name>
@@ -71,8 +71,8 @@
<test>one</test>
</item>
</contents>
- </data>
- <data>
+ </data3>
+ <data4>
<contents>
<item>
<name key="key">gum</name>
@@ -161,5 +161,5 @@
<test>one</test>
</item>
</contents>
- </data>
+ </data4>
</top>
diff --git a/contrib/libxo/tests/core/saved/test_09.E.err b/contrib/libxo/tests/core/saved/test_09.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_09.E.err
diff --git a/contrib/libxo/tests/core/saved/test_09.E.out b/contrib/libxo/tests/core/saved/test_09.E.out
new file mode 100644
index 0000000..bb808d5
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_09.E.out
@@ -0,0 +1,40 @@
+op create: [] []
+op open_container: [top] []
+op open_container: [data] []
+op open_container: [contents] []
+op open_leaf_list: [name] []
+op string: [name] [gum]
+op string: [name] [rope]
+op string: [name] [ladder]
+op string: [name] [bolt]
+op string: [name] [water]
+op close_leaf_list: [name] []
+op close_container: [contents] []
+op open_container: [contents] []
+op open_leaf_list: [item] []
+op string: [item] [gum]
+op string: [item] [rope]
+op string: [item] [ladder]
+op string: [item] [bolt]
+op string: [item] [water]
+op close_leaf_list: [item] []
+op close_container: [contents] []
+op open_container: [contents] []
+op open_list: [item] []
+op string: [item] [gum]
+op string: [item] [rope]
+op string: [item] [ladder]
+op string: [item] [bolt]
+op string: [item] [water]
+op close_list: [item] []
+op string: [total] [six]
+op string: [one] [one]
+op open_leaf_list: [two] []
+op string: [two] [two]
+op close_leaf_list: [two] []
+op string: [three] [three]
+op close_container: [contents] []
+op close_container: [data] []
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_10.E.err b/contrib/libxo/tests/core/saved/test_10.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_10.E.err
diff --git a/contrib/libxo/tests/core/saved/test_10.E.out b/contrib/libxo/tests/core/saved/test_10.E.out
new file mode 100644
index 0000000..4f21cda
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_10.E.out
@@ -0,0 +1,126 @@
+op create: [] []
+op version: [] [3.1.4]
+op open_container: [top] []
+op attr: [test] [value]
+op open_container: [data] []
+op open_list: [item] []
+op attr: [test2] [value2]
+op close_list: [item] []
+op string: [data] [bold]
+op string: [data] [bold-ul]
+op string: [data] [triple]
+op string: [data] [inv-ul]
+op string: [data] [underline]
+op string: [data] [plain]
+op open_list: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [GRO-000-415]
+op string: [name] [gum]
+op content: [sold] [1412]
+op content: [in-stock] [54]
+op content: [on-order] [10]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-212]
+op string: [name] [rope]
+op content: [sold] [85]
+op content: [in-stock] [4]
+op content: [on-order] [2]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-517]
+op string: [name] [ladder]
+op content: [sold] [0]
+op content: [in-stock] [2]
+op content: [on-order] [1]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [HRD-000-632]
+op string: [name] [bolt]
+op content: [sold] [4123]
+op content: [in-stock] [144]
+op content: [on-order] [42]
+op close_instance: [item] []
+op open_instance: [item] []
+op attr: [test3] [value3]
+op string: [sku] [GRO-000-2331]
+op string: [name] [water]
+op content: [sold] [17]
+op content: [in-stock] [14]
+op content: [on-order] [2]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data] []
+op open_container: [data] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-415]
+op string: [name] [gum]
+op content: [sold] [1412.0]
+op content: [in-stock] [54]
+op content: [on-order] [10]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-212]
+op string: [name] [rope]
+op content: [sold] [85.0]
+op content: [in-stock] [4]
+op content: [on-order] [2]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-517]
+op string: [name] [ladder]
+op content: [sold] [0]
+op content: [in-stock] [2]
+op content: [on-order] [1]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [HRD-000-632]
+op string: [name] [bolt]
+op content: [sold] [4123.0]
+op content: [in-stock] [144]
+op content: [on-order] [42]
+op close_instance: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-2331]
+op string: [name] [water]
+op content: [sold] [17.0]
+op content: [in-stock] [14]
+op content: [on-order] [2]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data] []
+op open_container: [data] []
+op open_list: [item] []
+op open_instance: [item] []
+op string: [sku] [GRO-000-533]
+op string: [name] [fish]
+op content: [sold] [1321.0]
+op content: [in-stock] [45]
+op content: [on-order] [1]
+op close_instance: [item] []
+op close_list: [item] []
+op close_container: [data] []
+op open_container: [data] []
+op open_list: [item] []
+op attr: [test4] [value4]
+op string: [item] [gum]
+op attr: [test4] [value4]
+op string: [item] [rope]
+op attr: [test4] [value4]
+op string: [item] [ladder]
+op attr: [test4] [value4]
+op string: [item] [bolt]
+op attr: [test4] [value4]
+op string: [item] [water]
+op close_list: [item] []
+op close_container: [data] []
+op content: [cost] [425]
+op content: [cost] [455]
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_11.E.err b/contrib/libxo/tests/core/saved/test_11.E.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.E.err
diff --git a/contrib/libxo/tests/core/saved/test_11.E.out b/contrib/libxo/tests/core/saved/test_11.E.out
new file mode 100644
index 0000000..9fd70fa
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.E.out
@@ -0,0 +1,26 @@
+op create: [] []
+op version: [] [3.1.4]
+op open_container: [top] []
+op create: [] []
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+op create: [] []
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+op create: [] []
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+op create: [] []
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
+op close_container: [top] []
+op finish: [] []
+op flush: [] []
diff --git a/contrib/libxo/tests/core/saved/test_11.H.err b/contrib/libxo/tests/core/saved/test_11.H.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.H.err
diff --git a/contrib/libxo/tests/core/saved/test_11.H.out b/contrib/libxo/tests/core/saved/test_11.H.out
new file mode 100644
index 0000000..0786c34
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.H.out
@@ -0,0 +1,16 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
diff --git a/contrib/libxo/tests/core/saved/test_11.HIPx.err b/contrib/libxo/tests/core/saved/test_11.HIPx.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.HIPx.err
diff --git a/contrib/libxo/tests/core/saved/test_11.HIPx.out b/contrib/libxo/tests/core/saved/test_11.HIPx.out
new file mode 100644
index 0000000..8b726f4
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.HIPx.out
@@ -0,0 +1,16 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="" state=""] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="" pet=""] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="" animal="" quote=""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="" event-source="" event-id=""] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
diff --git a/contrib/libxo/tests/core/saved/test_11.HP.err b/contrib/libxo/tests/core/saved/test_11.HP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.HP.err
diff --git a/contrib/libxo/tests/core/saved/test_11.HP.out b/contrib/libxo/tests/core/saved/test_11.HP.out
new file mode 100644
index 0000000..0786c34
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.HP.out
@@ -0,0 +1,16 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
diff --git a/contrib/libxo/tests/core/saved/test_11.J.err b/contrib/libxo/tests/core/saved/test_11.J.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.J.err
diff --git a/contrib/libxo/tests/core/saved/test_11.J.out b/contrib/libxo/tests/core/saved/test_11.J.out
new file mode 100644
index 0000000..4eb7105
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.J.out
@@ -0,0 +1,18 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
+{"__version": "3.1.4", "top": {}
+}
diff --git a/contrib/libxo/tests/core/saved/test_11.JP.err b/contrib/libxo/tests/core/saved/test_11.JP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.JP.err
diff --git a/contrib/libxo/tests/core/saved/test_11.JP.out b/contrib/libxo/tests/core/saved/test_11.JP.out
new file mode 100644
index 0000000..f82139b
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.JP.out
@@ -0,0 +1,22 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
+{
+ "__version": "3.1.4",
+ "top": {
+
+ }
+}
diff --git a/contrib/libxo/tests/core/saved/test_11.T.err b/contrib/libxo/tests/core/saved/test_11.T.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.T.err
diff --git a/contrib/libxo/tests/core/saved/test_11.T.out b/contrib/libxo/tests/core/saved/test_11.T.out
new file mode 100644
index 0000000..0786c34
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.T.out
@@ -0,0 +1,16 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
diff --git a/contrib/libxo/tests/core/saved/test_11.X.err b/contrib/libxo/tests/core/saved/test_11.X.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.X.err
diff --git a/contrib/libxo/tests/core/saved/test_11.X.out b/contrib/libxo/tests/core/saved/test_11.X.out
new file mode 100644
index 0000000..bb73a42
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.X.out
@@ -0,0 +1,17 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
+<top __version="3.1.4"></top> \ No newline at end of file
diff --git a/contrib/libxo/tests/core/saved/test_11.XP.err b/contrib/libxo/tests/core/saved/test_11.XP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.XP.err
diff --git a/contrib/libxo/tests/core/saved/test_11.XP.out b/contrib/libxo/tests/core/saved/test_11.XP.out
new file mode 100644
index 0000000..46b5dd0
--- /dev/null
+++ b/contrib/libxo/tests/core/saved/test_11.XP.out
@@ -0,0 +1,18 @@
+{{<14>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-status [animal-status@42 animal="snake" state="loose"] The snake is loose}}
+{{test-program: }}
+{{The snake is loose}}
+
+{{<22>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-consumed [animal-consumed@42 animal="snake" pet="hamster"] My snake ate your hamster}}
+{{test-program: }}
+{{My snake ate your hamster}}
+
+{{<29>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 animal-talk [animal-talk@42 count="1" animal="owl" quote="\"e=m\\c[2\]\""] 1 owl said "e=m\c[2]"}}
+{{test-program: }}
+{{1 owl said "e=m\c[2]"}}
+
+{{<165>1 2015-06-23T13:47:09.123-0500 worker-host test-program 222 ID47 [ID47@32473 iut="3" event-source="application" event-id="1011"] An application 1011 log entry}}
+{{test-program: }}
+{{An application 1011 log entry}}
+
+<top __version="3.1.4">
+</top>
diff --git a/contrib/libxo/tests/core/test_01.c b/contrib/libxo/tests/core/test_01.c
index 9a9ed2c..f7fe61e 100644
--- a/contrib/libxo/tests/core/test_01.c
+++ b/contrib/libxo/tests/core/test_01.c
@@ -8,11 +8,9 @@
* Phil Shafer, July 2014
*/
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <errno.h>
#include "xo.h"
@@ -48,9 +46,8 @@ main (int argc, char **argv)
{ "on-order", "number", "Number of items on order" },
{ "sku", "string", "Stock Keeping Unit" },
{ "sold", "number", "Number of items sold" },
- { NULL, NULL, NULL },
+ { XO_INFO_NULL },
};
- int info_count = (sizeof(info) / sizeof(info[0])) - 1;
argc = xo_parse_args(argc, argv);
if (argc < 0)
@@ -77,11 +74,13 @@ main (int argc, char **argv)
}
}
- xo_set_info(NULL, info, info_count);
+ xo_set_info(NULL, info, -1);
xo_set_flags(NULL, XOF_KEYS);
xo_open_container_h(NULL, "top");
+ xo_emit("Connecting to {:host}.{:domain}...\n", "my-box", "example.com");
+
xo_attr("test", "value");
xo_open_container("data");
xo_open_list("item");
@@ -109,7 +108,7 @@ main (int argc, char **argv)
xo_emit("\n\n");
- xo_open_container("data");
+ xo_open_container("data2");
xo_open_list("item");
for (ip = list; ip->i_title; ip++) {
@@ -128,9 +127,9 @@ main (int argc, char **argv)
}
xo_close_list("item");
- xo_close_container("data");
+ xo_close_container("data2");
- xo_open_container("data");
+ xo_open_container("data3");
xo_open_list("item");
for (ip = list2; ip->i_title; ip++) {
@@ -149,9 +148,9 @@ main (int argc, char **argv)
}
xo_close_list("item");
- xo_close_container("data");
+ xo_close_container("data3");
- xo_open_container("data");
+ xo_open_container("data4");
xo_open_list("item");
for (ip = list; ip->i_title; ip++) {
@@ -160,7 +159,7 @@ main (int argc, char **argv)
}
xo_close_list("item");
- xo_close_container("data");
+ xo_close_container("data4");
xo_emit("X{P:}X", "epic fail");
xo_emit("X{T:}X", "epic fail");
diff --git a/contrib/libxo/tests/core/test_02.c b/contrib/libxo/tests/core/test_02.c
index abcb14b..abddcf2 100644
--- a/contrib/libxo/tests/core/test_02.c
+++ b/contrib/libxo/tests/core/test_02.c
@@ -15,6 +15,8 @@
#include "xo.h"
+#include "xo_humanize.h"
+
int
main (int argc, char **argv)
{
@@ -45,6 +47,35 @@ main (int argc, char **argv)
xo_open_container("data");
+ xo_emit("We are {{emit}}{{ting}} some {:what}\n", "braces");
+
+ xo_message("abcdef");
+ close(-1);
+ xo_message_e("abcdef");
+
+ xo_message("improper use of profanity; %s; %s",
+ "ten yard penalty", "first down");
+
+ xo_emit("length {:length/%6.6s}\n", "abcdefghijklmnopqrstuvwxyz");
+
+ close(-1);
+ xo_emit("close {:fd/%d} returned {:error/%m} {:test}\n", -1, "good");
+ close(-1);
+ xo_emit("close {:fd/%d} returned {:error/%6.6m} {:test}\n", -1, "good");
+
+
+ xo_message("improper use of profanity; %s; %s",
+ "ten yard penalty", "first down");
+
+ xo_emit(" {:lines/%7ju} {:words/%7ju} "
+ "{:characters/%7ju} {d:filename/%s}\n",
+ 20, 30, 40, "file");
+
+ int i;
+ for (i = 0; i < 5; i++)
+ xo_emit("{lw:bytes/%d}{Np:byte,bytes}\n", i);
+
+
xo_emit("{:mbuf-current/%u}/{:mbuf-cache/%u}/{:mbuf-total/%u} "
"{N:mbufs <&> in use (current\\/cache\\/total)}\n",
10, 20, 30);
@@ -54,26 +85,43 @@ main (int argc, char **argv)
xo_emit("{:memory/%u}{U:/%s} left out of {:total/%u}{U:/%s}\n",
64, "k", 640, "kilobytes");
- xo_emit("{T:/before%safter:}\n", "working");
+ xo_emit("{,title:/before%safter:}\n", "working");
- xo_emit("{d:some/%s}{:ten/%ju}{:eleven/%ju}\n",
+ xo_emit("{,display,white,colon:some/%s}"
+ "{,value:ten/%ju}{,value:eleven/%ju}\n",
"string", (uintmax_t) 10, (uintmax_t) 11);
xo_emit("{:unknown/%u} "
"{N:/packet%s here\\/there\\/everywhere}\n",
1010, "s");
+ xo_emit("{:unknown/%u} "
+ "{,note:/packet%s here\\/there\\/everywhere}\n",
+ 1010, "s");
+
xo_emit("({[:/%d}{n:min/15}/{n:cur/20}/{:max/%d}{]:})\n", 30, 125);
xo_emit("({[:30}{:min/%u}/{:cur/%u}/{:max/%u}{]:})\n", 15, 20, 125);
xo_emit("({[:-30}{n:min/15}/{n:cur/20}/{n:max/125}{]:})\n");
xo_emit("({[:}{:min/%u}/{:cur/%u}/{:max/%u}{]:/%d})\n", 15, 20, 125, -30);
+ xo_emit("Humanize: {h:val1/%u}, {h,hn-space:val2/%u}, "
+ "{h,hn-decimal:val3/%u}, {h,hn-1000:val4/%u}, "
+ "{h,hn-decimal:val5/%u}\n",
+ 21,
+ 57 * 1024,
+ 96 * 1024 * 1024,
+ (42 * 1024 + 420) * 1024,
+ 1342172800);
+
xo_open_list("flag");
xo_emit("{lq:flag/one} {lq:flag/two} {lq:flag/three}\n");
xo_close_list("flag");
+ xo_emit("{n:works/%s}\n", NULL);
+
xo_emit("{e:empty-tag/}");
- xo_emit("1:{qt:t1/%*d} 2:{qt:t2/test%-*u} 3:{qt:t3/%10sx} 4:{qt:t4/x%-*.*s}\n",
+ xo_emit("1:{qt:t1/%*d} 2:{qt:t2/test%-*u} "
+ "3:{qt:t3/%10sx} 4:{qt:t4/x%-*.*s}\n",
6, 1000, 8, 5000, "ten-long", 10, 10, "test");
xo_emit("{E:this is an error}\n");
xo_emit("{E:/%s more error%s}\n", "two", "s" );
diff --git a/contrib/libxo/tests/core/test_05.c b/contrib/libxo/tests/core/test_05.c
index a883a88..07de037 100644
--- a/contrib/libxo/tests/core/test_05.c
+++ b/contrib/libxo/tests/core/test_05.c
@@ -11,8 +11,16 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <wchar.h>
#include "xo.h"
+#include "xo_config.h"
+
+#ifdef LIBXO_WCWIDTH
+#include "xo_wcwidth.h"
+#else /* LIBXO_WCWIDTH */
+#define xo_wcwidth(_x) wcwidth(_x)
+#endif /* LIBXO_WCWIDTH */
xo_info_t info[] = {
{ "employee", "object", "Employee data" },
@@ -43,7 +51,7 @@ main (int argc, char **argv)
"෴ණ්ණ෴෴ණ්ණ෴෴ණ්ණ෴෴෴", 110, 20 },
{ NULL, NULL }
}, *ep = employees;
- int rc;
+ int rc, i;
argc = xo_parse_args(argc, argv);
if (argc < 0)
@@ -52,8 +60,40 @@ main (int argc, char **argv)
xo_set_info(NULL, info, info_count);
xo_set_flags(NULL, XOF_COLUMNS);
+ xo_open_container("indian-languages");
+
+ xo_emit("{T:Sample text}\n");
+ xo_emit("This sample text was taken from the Punjabi Wikipedia "
+ "article on Lahore and transliterated into the Latin script.\n");
+
+ xo_emit("{T:Gurmukhi:}\n");
+ xo_emit("{:gurmukhi}\n",
+ "ਲਹੌਰ ਪਾਕਿਸਤਾਨੀ ਪੰਜਾਬ ਦੀ ਰਾਜਧਾਨੀ ਹੈ । ਲੋਕ ਗਿਣਤੀ ਦੇ ਨਾਲ ਕਰਾਚੀ ਤੋਂ ਬਾਅਦ ਲਹੌਰ ਦੂਜਾ ਸਭ ਤੋਂ ਵੱਡਾ ਸ਼ਹਿਰ ਹੈ । ਲਹੌਰ ਪਾਕਿਸਤਾਨ ਦਾ ਸਿਆਸੀ, ਰਹਤਲੀ ਤੇ ਪੜ੍ਹਾਈ ਦਾ ਗੜ੍ਹ ਹੈ ਅਤੇ ਇਸ ਲਈ ਇਹਨੂੰ ਪਾਕਿਸਤਾਨ ਦਾ ਦਿਲ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ । ਲਹੌਰ ਦਰਿਆ-ਏ-ਰਾਵੀ ਦੇ ਕੰਢੇ ਤੇ ਵਸਦਾ ਹੈ ਤੇ ਇਸਦੀ ਲੋਕ ਗਿਣਤੀ ਇੱਕ ਕਰੋੜ ਦੇ ਨੇੜੇ ਹੈ ।");
+
+
+ xo_emit("{T:Shahmukhi:}\n");
+ xo_emit("{:shahmukhi}\n",
+ "لہور پاکستانی پنجاب دا دارالحکومت اے۔ لوک گنتی دے نال کراچی توں بعد لہور دوجا سبھ توں وڈا شہر اے۔ لہور پاکستان دا سیاسی، رہتلی تے پڑھائی دا گڑھ اے تے اس لئی ایھنوں پاکستان دا دل وی کیھا جاندا اے۔ لہور دریاۓ راوی دے کنڈھے تے وسدا اے اسدی لوک گنتی اک کروڑ دے نیڑے اے ۔");
+
+ xo_emit("{T:Transliteration}:\n");
+ xo_emit("{:tranliteration}\n",
+ "lahor pākistān panjāb dā dārul hakūmat ē. lōk giṇtī dē nāḷ karācī tō᷈ bāad lahor dūjā sab tō᷈ vaḍḍā shahr ē. lahor pākistān dā siāsī, rahtalī tē paṛā̀ī dā gā́ṛ ē tē is laī ihnū᷈ pākistān dā dil vī kehā jāndā ē. lahor dariāē rāvī dē kanḍē tē vasdā ē. isdī lōk giṇtī ikk karōṛ dē nēṛē ē.");
+
+ xo_close_container("indian-languages");
+
xo_open_container("employees");
+ wchar_t wc[] = { L'෴', L'ණ', L'්', L'ණ', 0x17D2, L'෴', 0 };
+ for (i = 0; wc[i]; i++)
+ xo_emit("Wide char: {lq:wc/%lc - %#lx - %d}\n",
+ wc[i], (unsigned long) wc[i], xo_wcwidth(wc[i]));
+
+ wchar_t msg[] = { L'1', 0x034f, L'2', 0x20dd, 0 };
+ for (i = 0; msg[i]; i++)
+ xo_emit("Wide char: {lq:wc/%lc - %#lx - %d}\n",
+ msg[i], (unsigned long) msg[i], xo_wcwidth((int) msg[i]));
+ xo_emit("Cool: [{:fancy/%ls}]\n", msg);
+
xo_emit("Οὐχὶ ταὐτὰ παρίσταταί μοι {:v1/%s}, {:v2/%s}\n",
"γιγνώσκειν", "ὦ ἄνδρες ᾿Αθηναῖοι");
@@ -65,10 +105,15 @@ main (int argc, char **argv)
/* Okay, Sinhala is uber cool ... */
rc = xo_emit("[{:sinhala}]\n", "෴ණ්ණ෴");
xo_emit("{Twc:Width}{:width/%d}\n", rc);
+
rc = xo_emit("[{:sinhala}]\n", "෴");
xo_emit("{Twc:Width}{:width/%d}\n", rc);
+
rc = xo_emit("[{:sinhala/%-4..4s/%s}]\n", "෴ණ්ණ෴෴ණ්ණ෴");
+ xo_emit("{Twc:Width}{:width/%d}\n", rc);
+
xo_emit("[{:not-sinhala/%-4..4s/%s}]\n", "123456");
+
rc = xo_emit("[{:tag/%s}]\n", "ර්‍ඝ");
xo_emit("{Twc:Width}{:width/%d}\n", rc);
@@ -80,7 +125,7 @@ main (int argc, char **argv)
xo_open_instance("employee");
xo_emit("{[:-25}{:first-name/%s} ({:nic-name/\"%s\"}){]:}"
"{:last-name/%-14..14s/%s}"
- "{:department/%8u/%u}{:percent-time/%8u/%u}\n",
+ "{:department/%8u}{:percent-time/%8u}\n",
ep->e_first, ep->e_nic, ep->e_last, ep->e_dept, ep->e_percent);
if (ep->e_percent > 50) {
xo_attr("full-time", "%s", "honest & for true");
diff --git a/contrib/libxo/tests/core/test_08.c b/contrib/libxo/tests/core/test_08.c
index eb3776d..7e19ebe 100644
--- a/contrib/libxo/tests/core/test_08.c
+++ b/contrib/libxo/tests/core/test_08.c
@@ -85,7 +85,7 @@ main (int argc, char **argv)
xo_emit("\n\n");
- xo_open_container("data");
+ xo_open_container("data2");
xo_open_container("contents");
xo_emit("{T:Item/%-10s}{T:Count/%12s}\n");
@@ -97,11 +97,11 @@ main (int argc, char **argv)
ip->i_title, ip->i_count);
}
- xo_close_container("data");
+ xo_close_container("data2");
xo_emit("\n\n");
- xo_open_container("data");
+ xo_open_container("data3");
xo_open_marker("m1");
xo_open_container("contents");
@@ -114,15 +114,15 @@ main (int argc, char **argv)
ip->i_title, ip->i_count);
}
- xo_close_container("data"); /* Should be a noop */
+ xo_close_container("data3"); /* Should be a noop */
xo_emit("{:test}", "one");
xo_close_marker("m1");
- xo_close_container("data"); /* Should be a noop */
+ xo_close_container("data3"); /* Should be a noop */
xo_emit("\n\n");
- xo_open_container("data");
+ xo_open_container("data4");
xo_open_marker("m1");
xo_open_container("contents");
@@ -138,13 +138,13 @@ main (int argc, char **argv)
for (i = 0; i < 3; i++) {
xo_open_instance("sub");
xo_emit("{Lwc:/Name}{:name/%d} + 1 = {:next/%d}\n", i, i + 1);
- xo_close_container("data");
+ xo_close_container("data4");
}
xo_close_marker("m2");
xo_emit("{Lwc:/Last}{:last/%d}\n", i);
}
- xo_close_container("data"); /* Should be a noop */
+ xo_close_container("data4"); /* Should be a noop */
xo_emit("{:test}", "one");
xo_emit("\n\n");
diff --git a/contrib/libxo/tests/core/test_11.c b/contrib/libxo/tests/core/test_11.c
new file mode 100644
index 0000000..60851df
--- /dev/null
+++ b/contrib/libxo/tests/core/test_11.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2014, 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
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <ctype.h>
+#include <syslog.h>
+
+#include "xo.h"
+
+void
+test_syslog_open (void)
+{
+ printf("syslog open\n");
+}
+
+void
+test_syslog_close (void)
+{
+ printf("syslog close\n");
+}
+
+void
+test_syslog_send (const char *full_msg, const char *v0_hdr,
+ const char *text_only)
+{
+ printf("{{%s}}\n{{%s}}\n{{%s}}\n\n", full_msg, v0_hdr, text_only);
+}
+
+int
+main (int argc, char **argv)
+{
+ int unit_test = 1;
+ int fire = 0;
+ const char *tzone = "EST";
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ return 1;
+
+ for (argc = 1; argv[argc]; argc++) {
+ if (strcmp(argv[argc], "full") == 0)
+ unit_test = 0;
+ else if (strcmp(argv[argc], "fire") == 0)
+ fire = 1;
+ else if (strcmp(argv[argc], "tz") == 0)
+ tzone = argv[++argc];
+ }
+
+ setenv("TZ", tzone, 1);
+ tzset();
+
+ if (!fire) {
+ xo_set_syslog_handler(test_syslog_open, test_syslog_send,
+ test_syslog_close);
+ }
+
+ if (unit_test) {
+ xo_set_unit_test_mode(1);
+ xo_open_log("test-program", LOG_PERROR, 0);
+ }
+
+ xo_set_version("3.1.4");
+ xo_set_syslog_enterprise_id(42); /* SunOs */
+
+ xo_open_container_h(NULL, "top");
+
+ xo_syslog(LOG_INFO | LOG_KERN, "animal-status",
+ "The {:animal} is {:state}", "snake", "loose");
+ xo_syslog(LOG_INFO | LOG_MAIL, "animal-consumed",
+ "My {:animal} ate your {:pet}", "snake", "hamster");
+ xo_syslog(LOG_NOTICE | LOG_DAEMON, "animal-talk",
+ "{:count/%d} {:animal} said {:quote}", 1, "owl", "\"e=m\\c[2]\"");
+
+ /*
+ <165>1 2003-10-11T22:14:15.003Z mymachine.example.com
+ evntslog - ID47 [exampleSDID@32473 iut="3" eventSource=
+ "Application" eventID="1011"] BOMAn application
+ event log entry...
+
+ This example is modeled after Example 1. However, this time it
+ contains STRUCTURED-DATA, a single element with the value
+ "[exampleSDID@32473 iut="3" eventSource="Application"
+ eventID="1011"]". The MSG itself is "An application event log
+ entry..." The BOM at the beginning of MSG indicates UTF-8 encoding.
+ */
+
+ xo_set_syslog_enterprise_id(32473);
+ xo_syslog(LOG_LOCAL4 | LOG_NOTICE, "ID47",
+ "{e:iut/%u}An {:event-source} {:event-id/%u} log entry",
+ 3, "application", 1011);
+
+ xo_close_container_h(NULL, "top");
+
+ xo_finish();
+
+ return 0;
+}
diff --git a/contrib/libxo/tests/gettext/Makefile.am b/contrib/libxo/tests/gettext/Makefile.am
new file mode 100644
index 0000000..fb70142
--- /dev/null
+++ b/contrib/libxo/tests/gettext/Makefile.am
@@ -0,0 +1,224 @@
+#
+# $Id$
+#
+# Copyright 2014, 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.
+
+AM_CFLAGS = \
+ -I${top_srcdir} \
+ -I${top_srcdir}/libxo \
+ ${GETTEXT_CFLAGS}
+
+# Ick: maintained by hand!
+TEST_CASES = \
+gt_01.c
+
+gt_01_test_SOURCES = gt_01.c
+
+# TEST_CASES := $(shell cd ${srcdir} ; echo *.c )
+
+noinst_PROGRAMS = ${TEST_CASES:.c=.test}
+
+LDADD = \
+ ${top_builddir}/libxo/libxo.la
+
+if HAVE_HUMANIZE_NUMBER
+LDADD += -lutil
+endif
+
+EXTRA_DIST = \
+ ${TEST_CASES} \
+ ${addprefix saved/, ${TEST_CASES:.c=.T.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.T.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.XP.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.XP.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.JP.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.JP.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.HP.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.HP.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.X.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.X.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.J.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.J.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.H.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.H.out}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.HIPx.err}} \
+ ${addprefix saved/, ${TEST_CASES:.c=.HIPx.out}}
+
+POT_FILES = \
+ gt_01.pot \
+ ldns.pot \
+ strerror.pot
+
+PO_FILES = \
+ po/pig_latin/gt_01.po \
+ po/pig_latin/ldns.po \
+ po/pig_latin/strerror.po
+
+EXTRA_DIST += ${POT_FILES} ${PO_FILES}
+
+S2O = | ${SED} '1,/@@/d'
+
+all:
+
+valgrind:
+ @echo '## Running the regression tests under Valgrind'
+ ${MAKE} CHECKER='valgrind -q' tests
+
+#TEST_TRACE = set -x ;
+
+TEST_ONE = \
+ LIBXO_OPTIONS=:W$$fmt \
+ ${CHECKER} ./$$base.test ${TEST_OPTS} \
+ > out/$$base.$$fmt.out 2> out/$$base.$$fmt.err ; \
+ ${DIFF} -Nu ${srcdir}/saved/$$base.$$fmt.out out/$$base.$$fmt.out ${S2O} ; \
+ ${DIFF} -Nu ${srcdir}/saved/$$base.$$fmt.err out/$$base.$$fmt.err ${S2O}
+
+TEST_FORMATS = T XP JP HP X J H HIPx
+
+test tests: ${bin_PROGRAMS} build-mo-files
+ @${MKDIR} -p out
+ -@ ${TEST_TRACE} (for test in ${TEST_CASES} ; do \
+ base=`${BASENAME} $$test .c` ; \
+ (for fmt in ${TEST_FORMATS}; do \
+ echo "... $$test ... $$fmt ..."; \
+ ${TEST_ONE}; \
+ true; \
+ done) \
+ done)
+
+one:
+ -@(test=${TEST_CASE}; data=${TEST_DATA}; ${TEST_ONE} ; true)
+
+accept:
+ -@(for test in ${TEST_CASES} ; do \
+ base=`${BASENAME} $$test .c` ; \
+ (for fmt in ${TEST_FORMATS}; do \
+ echo "... $$test ... $$fmt ..."; \
+ ${CP} out/$$base.$$fmt.out ${srcdir}/saved/$$base.$$fmt.out ; \
+ ${CP} out/$$base.$$fmt.err ${srcdir}/saved/$$base.$$fmt.err ; \
+ done) \
+ done)
+
+.c.test:
+ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -o $@ $<
+
+CLEANFILES = ${TEST_CASES:.c=.test}
+CLEANDIRS = out
+
+clean-local:
+ rm -rf ${CLEANDIRS}
+
+XGETTEXT = ${GETTEXT_BINDIR}/xgettext
+MSGFMT = ${GETTEXT_BINDIR}/msgfmt -v
+MSGMERGE = ${GETTEXT_BINDIR}/msgmerge
+ECHO = echo
+DB=set -x;
+XOMSGMERGE = ${MSGMERGE} --no-wrap
+XODIFF = ${DIFF} -bu
+
+LANGUAGES = \
+ es \
+ fr \
+ pig_latin
+
+# ldns is fake; used only for a gettext domain
+FAKE_FILES = ldns strerror
+MO_BASE_FILES = ${TEST_CASES:.c=} ${FAKE_FILES}
+
+build-pot-files:
+ for file in ${TEST_CASES} ; do set -x ;\
+ base=`${BASENAME} $$file .c` ; \
+ ${XGETTEXT} --default-domain=$$base \
+ --directory=${srcdir} --no-wrap \
+ --add-comments --keyword=xo_emit --keyword=xo_emit_h \
+ --keyword=xo_emit_warn \
+ -C -E -n --foreign-user \
+ -o $$base.pot.new $$base.c ; \
+ done
+
+accept-pot-files:
+ for base in ${MO_BASE_FILES} ; do set -x ;\
+ ${CP} $$base.pot.new ${srcdir}/$$base.pot ; \
+ done
+
+merge-po-files:
+ for base in ${MO_BASE_FILES} ; do set -x ;\
+ for lang in ${LANGUAGES} ; do \
+ if [ -f po/$$lang/$$base.po ]; then \
+ ${ECHO} "merging $$base.pot into po/$$lang/$$base.po ..." ; \
+ if ${XOMSGMERGE} po/$$lang/$$base.po \
+ ${srcdir}/$$base.pot \
+ -o po/$$lang/$$base.new.po; then \
+ ${MV} po/$$lang/$$base.po \
+ po/$$lang/$$base.po.old ; \
+ ${MV} po/$$lang/$$base.new.po \
+ po/$$lang/$$base.po ; \
+ else \
+ echo "error: msgmerge for $$base failed"; \
+ fi ; \
+ elif [ -f ${srcdir}/po/$$lang/$$base.po ]; then \
+ ${ECHO} "merging (srcdir) $$base.pot into po/$$lang/$$base.po ..." ; \
+ if ${XOMSGMERGE} ${srcdir}/po/$$lang/$$base.po \
+ ${srcdir}/$$base.pot \
+ -o po/$$lang/$$base.new.po; then \
+ ${MV} po/$$lang/$$base.po \
+ po/$$lang/$$base.po.old ; \
+ ${MV} po/$$lang/$$base.new.po \
+ po/$$lang/$$base.po ; \
+ else \
+ echo "error: msgmerge for $$base failed"; \
+ fi ; \
+ fi ; \
+ done ; \
+ done
+
+accept-po-files:
+ @(for base in ${MO_BASE_FILES} ; do \
+ for lang in ${LANGUAGES} ; do \
+ if [ -f po/$$lang/$$base.po ]; then \
+ ${MKDIR} -p ${srcdir}/po/$$lang ; \
+ (${DB} ${CP} po/$$lang/$$base.po ${srcdir}/po/$$lang/$$base.po ); \
+ fi ; \
+ done ; \
+ done)
+
+new-po-file:
+ @(for base in ${MO_BASE_FILES} ; do set -x ;\
+ for lang in ${LANGUAGES} ; do \
+ if [ ! -f po/$$lang/$$base.po ]; then \
+ ${MKDIR} -p po/$$lang ; \
+ (${DB} ${CP} $$base.pot po/$$lang/$$base.po ); \
+ fi ; \
+ done ; \
+ done)
+
+diff:
+ @(for base in ${MO_BASE_FILES} ; do \
+ if [ -f $$base.pot.new ] ; then \
+ ${XODIFF} ${srcdir}/$$base.pot $$base.pot.new ; \
+ fi ; \
+ for lang in ${LANGUAGES} ; do \
+ if [ -f po/$$lang/$$base.po ] ; then \
+ ${XODIFF} ${srcdir}/po/$$lang/$$base.po po/$$lang/$$base.po ; \
+ fi ; \
+ done ; \
+ done)
+
+mo build-mo-files:
+ @(for base in ${MO_BASE_FILES} ; do \
+ for lang in ${LANGUAGES} ; do \
+ ${MKDIR} -p po/$$lang/LC_MESSAGES ; \
+ if [ -f po/$$lang/$$base.po ] ; then \
+ (${DB} ${MSGFMT} -o po/$$lang/LC_MESSAGES/$$base.mo \
+ po/$$lang/$$base.po ); \
+ elif [ -f ${srcdir}/po/$$lang/$$base.po ]; then \
+ (${DB} ${MSGFMT} -o po/$$lang/LC_MESSAGES/$$base.mo \
+ ${srcdir}/po/$$lang/$$base.po ;) \
+ fi ; \
+ done ; \
+ done)
diff --git a/contrib/libxo/tests/gettext/gt_01.c b/contrib/libxo/tests/gettext/gt_01.c
new file mode 100644
index 0000000..a0200c2
--- /dev/null
+++ b/contrib/libxo/tests/gettext/gt_01.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 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, June 2015
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <locale.h>
+#include <libintl.h>
+
+#include "xo.h"
+
+int
+main (int argc, char **argv)
+{
+ static char domainname[] = "gt_01";
+ char path[MAXPATHLEN];
+ const char *tzone = "EST";
+ const char *lang = "pig_latin";
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ return 1;
+
+ for (argc = 1; argv[argc]; argc++) {
+ if (strcmp(argv[argc], "tz") == 0)
+ tzone = argv[++argc];
+ else if (strcmp(argv[argc], "lang") == 0)
+ lang = argv[++argc];
+ else if (strcmp(argv[argc], "po") == 0)
+ strlcpy(path, argv[++argc], sizeof(path));
+ }
+
+ setenv("LANG", lang, 1);
+ setenv("TZ", tzone, 1);
+
+ if (path[0] == 0) {
+ getcwd(path, sizeof(path));
+ strlcat(path, "/po", sizeof(path));
+ }
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(domainname, path);
+ bindtextdomain("ldns", path);
+ bindtextdomain("strerror", path);
+ textdomain(domainname);
+ tzset();
+
+ xo_open_container("top");
+
+ xo_emit("{G:}Your {qg:adjective} {g:noun} is {g:verb} {qg:owner} {g:target}\n",
+ "flaming", "sword", "burning", "my", "couch");
+
+ xo_emit("{G:}The {qg:adjective} {g:noun} was {g:verb} {qg:owner} {g:target}\n",
+ "flaming", "sword", "burning", "my", "couch");
+
+
+ int i;
+ for (i = 0; i < 5; i++)
+ xo_emit("{lw:bytes/%d}{Ngp:byte,bytes}\n", i);
+
+ xo_emit("{G:}{L:total} {:total/%u}\n", 1234);
+
+ xo_emit("{G:ldns}Received {:received/%zu} {Ngp:byte,bytes} "
+ "from {:from/%s}#{:port/%d} in {:time/%d} ms\n",
+ (size_t) 1234, "foop", 4321, 32);
+
+ xo_emit("{G:}Received {:received/%zu} {Ngp:byte,bytes} "
+ "from {:from/%s}#{:port/%d} in {:time/%d} ms\n",
+ (size_t) 1234, "foop", 4321, 32);
+
+ xo_emit("{G:/%s}Received {:received/%zu} {Ngp:byte,bytes} "
+ "from {:from/%s}#{:port/%d} in {:time/%d} ms\n",
+ "ldns", (size_t) 1234, "foop", 4321, 32);
+
+ struct timeval tv;
+ tv.tv_sec = 1435085229;
+ tv.tv_usec = 123456;
+
+ struct tm tm;
+ (void) gmtime_r(&tv.tv_sec, &tm);
+
+ char date[64];
+ strftime(date, sizeof(date), "%+", &tm);
+
+ xo_emit("{G:}Only {:marzlevanes/%d} {Ngp:marzlevane,marzlevanes} "
+ "are functioning correctly\n", 3);
+
+ xo_emit("{G:}Version {:version} {:date}\n", "1.2.3", date);
+
+ errno = EACCES;
+ xo_emit_warn("{G:}Unable to {g:verb/objectulate} forward velociping");
+ xo_emit_warn("{G:}{g:style/automatic} synchronization of {g:type/cardinal} "
+ "{g:target/grammeters} failed");
+ xo_emit("{G:}{Lwcg:hydrocoptic marzlevanes}{:marzlevanes/%d}\n", 6);
+
+ xo_emit("{G:}{Lwcg:Windings}{g:windings}\n", "lotus-o-delta");
+
+ xo_close_container("top");
+ xo_finish();
+
+ return 0;
+}
diff --git a/contrib/libxo/tests/gettext/gt_01.pot b/contrib/libxo/tests/gettext/gt_01.pot
new file mode 100644
index 0000000..1d09223
--- /dev/null
+++ b/contrib/libxo/tests/gettext/gt_01.pot
@@ -0,0 +1,105 @@
+# SOME DESCRIPTIVE TITLE.
+# This file is put in the public domain.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gt_01.c:42
+#, c-format
+msgid "{:bytes}{N:byte,bytes}\n"
+msgstr ""
+
+#: gt_01.c:44
+#, c-format
+msgid "{L:total} {:total}\n"
+msgstr ""
+
+#: gt_01.c:60
+#, c-format
+msgid "Only {:marzlevanes} {N:marzlevane,marzlevanes} are functioning correctly\n"
+msgstr ""
+
+#: gt_01.c:63
+msgid "Version {:version} {:date}\n"
+msgstr ""
+
+#: gt_01.c:66
+msgid "Unable to {:verb} forward velociping"
+msgstr ""
+
+#: gt_01.c:67
+msgid "{:style} synchronization of {:type} {:target} failed"
+msgstr ""
+
+#: gt_01.c:69
+#, c-format
+msgid "{L:hydrocoptic marzlevanes}{:marzlevanes}\n"
+msgstr ""
+
+#: gt_01.c:71
+msgid "{L:Windings}{:windings}\n"
+msgstr ""
+
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "marzlevane"
+msgid_plural "marzlevanes"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "lotus-o-delta"
+msgstr ""
+
+msgid "cardinal"
+msgstr ""
+
+msgid "automatic"
+msgstr ""
+
+msgid "grammeters"
+msgstr ""
+
+msgid "objectulate"
+msgstr ""
+
+msgid "hydrocoptic marzlevanes"
+msgstr ""
+
+msgid "Windings"
+msgstr ""
+
+msgid "Your {:adjective} {:noun} is {:verb} {:owner} {:target}\n"
+msgstr ""
+
+msgid "The {:adjective} {:noun} was {:verb} {:owner} {:target}\n"
+msgstr ""
+
+msgid "flaming"
+msgstr ""
+
+msgid "sword"
+msgstr ""
+
+msgid "burning"
+msgstr ""
+
+msgid "my"
+msgstr ""
+
+msgid "couch"
+msgstr ""
diff --git a/contrib/libxo/tests/gettext/ldns.pot b/contrib/libxo/tests/gettext/ldns.pot
new file mode 100644
index 0000000..6e3df20
--- /dev/null
+++ b/contrib/libxo/tests/gettext/ldns.pot
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# This file is put in the public domain.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gt_01.c:46
+#, c-format
+msgid "Received {:received} {N:byte,bytes} from {:from}#{:port} in {:time} ms\n"
+msgstr ""
+
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] ""
+msgstr[1] ""
+
diff --git a/contrib/libxo/tests/gettext/po/pig_latin/gt_01.po b/contrib/libxo/tests/gettext/po/pig_latin/gt_01.po
new file mode 100644
index 0000000..269bad5
--- /dev/null
+++ b/contrib/libxo/tests/gettext/po/pig_latin/gt_01.po
@@ -0,0 +1,109 @@
+# SOME DESCRIPTIVE TITLE.
+# This file is put in the public domain.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: libxo unit test\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: 2015-07-09 13:43-0400\n"
+"Last-Translator: P.S. <ps@example.com>\n"
+"Language-Team: Self-inflicted <test@example.com>\n"
+"Language: teo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.1\n"
+"Plural-Forms: nplurals=3; plural=(n==0) ? 0 : (n==1) ? 1 : 2;\n"
+"X-Poedit-SourceCharset: iso-8859-1\n"
+
+#: gt_01.c:42
+#, c-format
+msgid "{:bytes}{N:byte,bytes}\n"
+msgstr "{:bytes}{N:ytebay,ytesbay}\n"
+
+#: gt_01.c:44
+#, c-format
+msgid "{L:total} {:total}\n"
+msgstr "{L:otaltay} {:total}\n"
+
+#: gt_01.c:60
+#, c-format
+msgid "Only {:marzlevanes} {N:marzlevane,marzlevanes} are functioning correctly\n"
+msgstr "Onlyay {:marzlevanes} {N:marzlevane,marzlevanes} areyay unctioningfay orrectlycay\n"
+
+#: gt_01.c:63
+msgid "Version {:version} {:date}\n"
+msgstr "Ersionvay {:date} {:version}\n"
+
+#: gt_01.c:66
+msgid "Unable to {:verb} forward velociping"
+msgstr "Nableuay otay {:verb} orwardfay elocipingvay"
+
+#: gt_01.c:67
+msgid "{:style} synchronization of {:type} {:target} failed"
+msgstr "{:style} ynchronizationsay ofyay {:type} {:target} ailedfay"
+
+#: gt_01.c:69
+#, c-format
+msgid "{L:hydrocoptic marzlevanes}{:marzlevanes}\n"
+msgstr "{L:ydrocoptichay arzlevanesmay}{:marzlevanes}\n"
+
+#: gt_01.c:71
+msgid "{L:Windings}{:windings}\n"
+msgstr "Dude, {L:Windings}{:windings}\n"
+
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "yebay"
+msgstr[1] "yesbay"
+msgstr[2] "yezbay"
+
+msgid "marzlevane"
+msgid_plural "marzlevanes"
+msgstr[0] "arzlevanemay"
+msgstr[1] "arzlevanesmay"
+msgstr[2] "arzlevanezmay"
+
+msgid "lotus-o-delta"
+msgstr "otuslay-oyay-eltayay"
+
+msgid "cardinal"
+msgstr "ardinalyay"
+
+msgid "automatic"
+msgstr "automaticyay"
+
+msgid "grammeters"
+msgstr "ammetersgray"
+
+msgid "objectulate"
+msgstr "ectulatobjay"
+
+msgid "hydrocoptic marzlevanes"
+msgstr "ydrocoptichay arzlevanesmay"
+
+msgid "Windings"
+msgstr "Indingsway"
+
+msgid "Your {:adjective} {:noun} is {:verb} {:owner} {:target}\n"
+msgstr "Ouryay {:noun} {:adjective} isyay {:owner}{:target} bubbly-bubbly {:verb}\n"
+
+msgid "The {:adjective} {:noun} was {:verb} {:owner} {:target}\n"
+msgstr "Ethay {:noun} asway '{:owner}{:adjective}{:target}' {:verb}\n"
+
+msgid "flaming"
+msgstr "amingflay"
+
+msgid "sword"
+msgstr "ordsway"
+
+msgid "burning"
+msgstr "urningbay"
+
+msgid "my"
+msgstr "ymay"
+
+msgid "couch"
+msgstr "ouchcay"
diff --git a/contrib/libxo/tests/gettext/po/pig_latin/ldns.po b/contrib/libxo/tests/gettext/po/pig_latin/ldns.po
new file mode 100644
index 0000000..83d5aee
--- /dev/null
+++ b/contrib/libxo/tests/gettext/po/pig_latin/ldns.po
@@ -0,0 +1,30 @@
+# SOME DESCRIPTIVE TITLE.
+# This file is put in the public domain.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: libxo unit test\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: 2015-07-01 18:47-0500\n"
+"Last-Translator: P.S. <ps@example.com>\n"
+"Language-Team: Self-inflicted <test@example.com>\n"
+"Language: teo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.1\n"
+"Plural-Forms: nplurals=3; plural=(n==0) ? 0 : (n==1) ? 1 : 2;\n"
+"X-Poedit-SourceCharset: iso-8859-1\n"
+
+#: gt_01.c:46
+#, c-format
+msgid "Received {:received} {N:byte,bytes} from {:from}#{:port} in {:time} ms\n"
+msgstr "Eceivedray {:received} {N:byte,bytes} omfray {:from}#{:port} inyay {:time} msyay\n"
+
+msgid "byte"
+msgid_plural "bytes"
+msgstr[0] "ldb0"
+msgstr[1] "ldb1"
+msgstr[2] "ldb2"
diff --git a/contrib/libxo/tests/gettext/po/pig_latin/strerror.po b/contrib/libxo/tests/gettext/po/pig_latin/strerror.po
new file mode 100644
index 0000000..8b41c0a
--- /dev/null
+++ b/contrib/libxo/tests/gettext/po/pig_latin/strerror.po
@@ -0,0 +1,459 @@
+#
+# Copyright (c) 1982, 1985, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# List of system errors ala strerror() and sys_errlist
+# Phil Shafer <phil@juniper.net>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: libxo test\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: 2015-07-02 00:37-0500\n"
+"Language: teo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==0) ? 0 : (n==1) ? 1 : 2;\n"
+"Last-Translator: P.S. <ps@example.com>\n"
+"Language-Team: self inflicted <test@example.com>\n"
+"X-Generator: Poedit 1.8.1\n"
+"X-Poedit-SourceCharset: iso-8859-1\n"
+
+# 0 - ENOERROR
+msgid "No error: 0"
+msgstr "Onyay erroryay"
+
+# 1 - EPERM
+msgid "Operation not permitted"
+msgstr "Operationyay otnay ermittedpay"
+
+# 2 - ENOENT
+msgid "No such file or directory"
+msgstr "Onay uchsay ilefay oryay irectoryday"
+
+# 3 - ESRCH
+msgid "No such process"
+msgstr "Onay uchsay ocesspray"
+
+# 4 - EINTR
+msgid "Interrupted system call"
+msgstr "Interruptedyay ystemsay allcay"
+
+# 5 - EIO
+msgid "Input/output error"
+msgstr "Input/outputyay erroryay"
+
+# 6 - ENXIO
+msgid "Device not configured"
+msgstr "Eviceday otnay onfiguredcay"
+
+# 7 - E2BIG
+msgid "Argument list too long"
+msgstr "Argumentyay istlay ootay onglay"
+
+# 8 - ENOEXEC
+msgid "Exec format error"
+msgstr "Execway ormatfay errorway"
+
+# 9 - EBADF
+msgid "Bad file descriptor"
+msgstr "Adbay ilefay escriptorday"
+
+# 10 - ECHILD
+msgid "No child processes"
+msgstr "Onay ildchay ocessespray"
+
+# 11 - EDEADLK
+msgid "Resource deadlock avoided"
+msgstr "Esourceray eadlockday avoidedway"
+
+# 12 - ENOMEM
+msgid "Cannot allocate memory"
+msgstr "Annotcay allocateway emorymay"
+
+# 13 - EACCES
+msgid "Permission denied"
+msgstr "Ermissionpay eniedday"
+
+# 14 - EFAULT
+msgid "Bad address"
+msgstr "Adbay addressway"
+
+# 15 - ENOTBLK
+msgid "Block device required"
+msgstr "Ockblay eviceday equiredray"
+
+# 16 - EBUSY
+msgid "Device busy"
+msgstr "Eviceday usybay"
+
+# 17 - EEXIST
+msgid "File exists"
+msgstr "Ilefay existsway"
+
+# 18 - EXDEV
+msgid "Cross-device link"
+msgstr "Osscray-eviceday inklay"
+
+# 19 - ENODEV
+msgid "Operation not supported by device"
+msgstr "Operationway otnay upportedsay ybay eviceday"
+
+# 20 - ENOTDIR
+msgid "Not a directory"
+msgstr "Otnay away irectoryday"
+
+# 21 - EISDIR
+msgid "Is a directory"
+msgstr "Isway away irectoryday"
+
+# 22 - EINVAL
+msgid "Invalid argument"
+msgstr "Invalidway argumentway"
+
+# 23 - ENFILE
+msgid "Too many open files in system"
+msgstr "Ootay anymay openway ilesfay inway ystemsay"
+
+# 24 - EMFILE
+msgid "Too many open files"
+msgstr "Ootay anymay openway ilesfay"
+
+# 25 - ENOTTY
+msgid "Inappropriate ioctl for device"
+msgstr "Inappropriateway ioctlway orfay eviceday"
+
+# 26 - ETXTBSY
+msgid "Text file busy"
+msgstr "Exttay ilefay usybay"
+
+# 27 - EFBIG
+msgid "File too large"
+msgstr "Ilefay ootay argelay"
+
+# 28 - ENOSPC
+msgid "No space left on device"
+msgstr "Onay acespay eftlay onway eviceday"
+
+# 29 - ESPIPE
+msgid "Illegal seek"
+msgstr "Illegalway eeksay"
+
+# 30 - EROFS
+msgid "Read-only file system"
+msgstr "Eadray-onlyway ilefay ystemsay"
+
+# 31 - EMLINK
+msgid "Too many links"
+msgstr "Ootay anymay inkslay"
+
+# 32 - EPIPE
+msgid "Broken pipe"
+msgstr "Okenbray ipepay"
+
+#
+# math software
+#
+# 33 - EDOM
+msgid "Numerical argument out of domain"
+msgstr "Umericalnay argumentway outway ofway omainday"
+
+# 34 - ERANGE
+msgid "Result too large"
+msgstr "Esultray ootay argelay"
+
+#
+# non-blocking and interrupt i/o
+#
+# 35 - EAGAIN
+# 35 - EWOULDBLOCK
+msgid "Resource temporarily unavailable"
+msgstr "Esourceray emporarilytay unavailableway"
+
+# 36 - EINPROGRESS
+msgid "Operation now in progress"
+msgstr "Operationway ownay inway ogresspray"
+
+# 37 - EALREADY
+msgid "Operation already in progress"
+msgstr "Operationway alreadyway inway ogresspray"
+
+#
+# ipc/network software -- argument errors
+#
+# 38 - ENOTSOCK
+msgid "Socket operation on non-socket"
+msgstr "Ocketsay operationway onway onnay-ocketsay"
+
+# 39 - EDESTADDRREQ
+msgid "Destination address required"
+msgstr "Estinationday addressway equiredray"
+
+# 40 - EMSGSIZE
+msgid "Message too long"
+msgstr "Essagemay ootay onglay"
+
+# 41 - EPROTOTYPE
+msgid "Protocol wrong type for socket"
+msgstr "Otocolpray ongwray ypetay orfay ocketsay"
+
+# 42 - ENOPROTOOPT
+msgid "Protocol not available"
+msgstr "Otocolpray otnay availableway"
+
+# 43 - EPROTONOSUPPORT
+msgid "Protocol not supported"
+msgstr "Otocolpray otnay upportedsay"
+
+# 44 - ESOCKTNOSUPPORT
+msgid "Socket type not supported"
+msgstr "Ocketsay ypetay otnay upportedsay"
+
+# 45 - EOPNOTSUPP
+msgid "Operation not supported"
+msgstr "Operationway otnay upportedsay"
+
+# 46 - EPFNOSUPPORT
+msgid "Protocol family not supported"
+msgstr "Otocolpray amilyfay otnay upportedsay"
+
+# 47 - EAFNOSUPPORT
+msgid "Address family not supported by protocol family"
+msgstr "Addressway amilyfay otnay upportedsay ybay otocolpray amilyfay"
+
+# 48 - EADDRINUSE
+msgid "Address already in use"
+msgstr "Addressway alreadyway inway useway"
+
+# 49 - EADDRNOTAVAIL
+msgid "Can't assign requested address"
+msgstr "An'tcay assignway equestedray addressway"
+
+#
+# ipc/network software -- operational errors
+#
+# 50 - ENETDOWN
+msgid "Network is down"
+msgstr "Etworknay isway ownday"
+
+# 51 - ENETUNREACH
+msgid "Network is unreachable"
+msgstr "Etworknay isway unreachableway"
+
+# 52 - ENETRESET
+msgid "Network dropped connection on reset"
+msgstr "Etworknay oppeddray onnectioncay onway esetray"
+
+# 53 - ECONNABORTED
+msgid "Software caused connection abort"
+msgstr "Oftwaresay ausedcay onnectioncay abortway"
+
+# 54 - ECONNRESET
+msgid "Connection reset by peer"
+msgstr "Onnectioncay esetray ybay eerpay"
+
+# 55 - ENOBUFS
+msgid "No buffer space available"
+msgstr "Onay ufferbay acespay availableway"
+
+# 56 - EISCONN
+msgid "Socket is already connected"
+msgstr "Ocketsay isway alreadyway onnectedcay"
+
+# 57 - ENOTCONN
+msgid "Socket is not connected"
+msgstr "Ocketsay isway otnay onnectedcay"
+
+# 58 - ESHUTDOWN
+msgid "Can't send after socket shutdown"
+msgstr "An'tcay endsay afterway ocketsay utdownshay"
+
+# 59 - ETOOMANYREFS
+msgid "Too many references: can't splice"
+msgstr "Ootay anymay eferencesray: an'tcay icesplay"
+
+# 60 - ETIMEDOUT
+msgid "Operation timed out"
+msgstr "Operationway imedtay outway"
+
+# 61 - ECONNREFUSED
+msgid "Connection refused"
+msgstr "Onnectioncay efusedray"
+
+# 62 - ELOOP
+msgid "Too many levels of symbolic links"
+msgstr "Ootay anymay evelslay ofway ymbolicsay inkslay"
+
+# 63 - ENAMETOOLONG
+msgid "File name too long"
+msgstr "Ilefay amenay ootay onglay"
+
+#
+# should be rearranged
+#
+# 64 - EHOSTDOWN
+msgid "Host is down"
+msgstr "Osthay isway ownday"
+
+# 65 - EHOSTUNREACH
+msgid "No route to host"
+msgstr "Onay outeray otay osthay"
+
+# 66 - ENOTEMPTY
+msgid "Directory not empty"
+msgstr "Irectoryday otnay emptyway"
+
+#
+# quotas & mush
+#
+# 67 - EPROCLIM
+msgid "Too many processes"
+msgstr "Ootay anymay ocessespray"
+
+# 68 - EUSERS
+msgid "Too many users"
+msgstr "Ootay anymay usersway"
+
+# 69 - EDQUOT
+msgid "Disc quota exceeded"
+msgstr "Iscday otaquay exceededway"
+
+#
+# Network File System
+#
+# 70 - ESTALE
+msgid "Stale NFS file handle"
+msgstr "Alestay NFSAY ilefay andlehay"
+
+# 71 - EREMOTE
+msgid "Too many levels of remote in path"
+msgstr "Ootay anymay evelslay ofway emoteray inway athpay"
+
+# 72 - EBADRPC
+msgid "RPC struct is bad"
+msgstr "RPCAY uctstray isway adbay"
+
+# 73 - ERPCMISMATCH
+msgid "RPC version wrong"
+msgstr "RPCAY ersionvay ongwray"
+
+# 74 - EPROGUNAVAIL
+msgid "RPC prog. not avail"
+msgstr "RPCAY ogpray. otnay availway"
+
+# 75 - EPROGMISMATCH
+msgid "Program version wrong"
+msgstr "Ogrampray ersionvay ongwray"
+
+# 76 - EPROCUNAVAIL
+msgid "Bad procedure for program"
+msgstr "Adbay ocedurepray orfay ogrampray"
+
+# 77 - ENOLCK
+msgid "No locks available"
+msgstr "Onay ockslay availableway"
+
+# 78 - ENOSYS
+msgid "Function not implemented"
+msgstr "Unctionfay otnay implementedway"
+
+# 79 - EFTYPE
+msgid "Inappropriate file type or format"
+msgstr "Inappropriateway ilefay ypetay orway ormatfay"
+
+# 80 - EAUTH
+msgid "Authentication error"
+msgstr "Authenticationway errorway"
+
+# 81 - ENEEDAUTH
+msgid "Need authenticator"
+msgstr "Eednay authenticatorway"
+
+# 82 - EIDRM
+msgid "Identifier removed"
+msgstr "Identifierway emovedray"
+
+# 83 - ENOMSG
+msgid "No message of desired type"
+msgstr "Onay essagemay ofway esiredday ypetay"
+
+# 84 - EOVERFLOW
+msgid "Value too large to be stored in data type"
+msgstr "Aluevay ootay argelay otay ebay oredstay inway ataday ypetay"
+
+# 85 - ECANCELED
+msgid "Operation canceled"
+msgstr "Operationway anceledcay"
+
+# 86 - EILSEQ
+msgid "Illegal byte sequence"
+msgstr "Illegalway ytebay equencesay"
+
+# 87 - ENOATTR
+msgid "Attribute not found"
+msgstr "Attributeway otnay oundfay"
+
+#
+# General
+#
+# 88 - EDOOFUS
+msgid "Programming error"
+msgstr "Ogrammingpray errorway"
+
+# 89 - EBADMSG
+msgid "Bad message"
+msgstr "Adbay essagemay"
+
+# 90 - EMULTIHOP
+msgid "Multihop attempted"
+msgstr "Ultihopmay attemptedway"
+
+# 91 - ENOLINK
+msgid "Link has been severed"
+msgstr "Inklay ashay eenbay everedsay"
+
+# 92 - EPROTO
+msgid "Protocol error"
+msgstr "Otocolpray errorway"
+
+# 93 - ENOTCAPABLE
+msgid "Capabilities insufficient"
+msgstr "Apabilitiescay insufficientway"
+
+# 94 - ECAPMODE
+msgid "Not permitted in capability mode"
+msgstr "Otnay ermittedpay inway apabilitycay odemay"
+
+# 95 - ENOTRECOVERABLE
+msgid "State not recoverable"
+msgstr "Atestay otnay ecoverableray"
+
+# 96 - EOWNERDEAD
+msgid "Previous owner died"
+msgstr "Eviouspray ownerway iedday"
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.H.err b/contrib/libxo/tests/gettext/saved/gt_01.H.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.H.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.H.out b/contrib/libxo/tests/gettext/saved/gt_01.H.out
new file mode 100644
index 0000000..13606bf
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.H.out
@@ -0,0 +1 @@
+<div class="line"><div class="text">Ouryay </div><div class="data" data-tag="noun">ordsway</div><div class="text"> </div><div class="data" data-tag="adjective">amingflay</div><div class="text"> isyay </div><div class="data" data-tag="owner">ymay</div><div class="data" data-tag="target">ouchcay</div><div class="text"> bubbly-bubbly </div><div class="data" data-tag="verb">urningbay</div></div><div class="line"><div class="text">Ethay </div><div class="data" data-tag="noun">ordsway</div><div class="text"> asway '</div><div class="data" data-tag="owner">ymay</div><div class="data" data-tag="adjective">amingflay</div><div class="data" data-tag="target">ouchcay</div><div class="text">' </div><div class="data" data-tag="verb">urningbay</div></div><div class="line"><div class="data" data-tag="bytes">0</div><div class="padding"> </div><div class="note">yebay</div></div><div class="line"><div class="data" data-tag="bytes">1</div><div class="padding"> </div><div class="note">yesbay</div></div><div class="line"><div class="data" data-tag="bytes">2</div><div class="padding"> </div><div class="note">yezbay</div></div><div class="line"><div class="data" data-tag="bytes">3</div><div class="padding"> </div><div class="note">yezbay</div></div><div class="line"><div class="data" data-tag="bytes">4</div><div class="padding"> </div><div class="note">yezbay</div></div><div class="line"><div class="label">otaltay</div><div class="text"> </div><div class="data" data-tag="total">1234</div></div><div class="line"><div class="text">Eceivedray </div><div class="data" data-tag="received">1234</div><div class="text"> </div><div class="note">ldb2</div><div class="text"> omfray </div><div class="data" data-tag="from">foop</div><div class="text">#</div><div class="data" data-tag="port">4321</div><div class="text"> inyay </div><div class="data" data-tag="time">32</div><div class="text"> msyay</div></div><div class="line"><div class="text">Received </div><div class="data" data-tag="received">1234</div><div class="text"> </div><div class="note">yezbay</div><div class="text"> from </div><div class="data" data-tag="from">foop</div><div class="text">#</div><div class="data" data-tag="port">4321</div><div class="text"> in </div><div class="data" data-tag="time">32</div><div class="text"> ms</div></div><div class="line"><div class="text">Eceivedray </div><div class="data" data-tag="received">1234</div><div class="text"> </div><div class="note">ldb2</div><div class="text"> omfray </div><div class="data" data-tag="from">foop</div><div class="text">#</div><div class="data" data-tag="port">4321</div><div class="text"> inyay </div><div class="data" data-tag="time">32</div><div class="text"> msyay</div></div><div class="line"><div class="text">Onlyay </div><div class="data" data-tag="marzlevanes">3</div><div class="text"> </div><div class="note">arzlevanezmay</div><div class="text"> areyay unctioningfay orrectlycay</div></div><div class="line"><div class="text">Ersionvay </div><div class="data" data-tag="date">Tue Jun 23 18:47:09 UTC 2015</div><div class="text"> </div><div class="data" data-tag="version">1.2.3</div></div><div class="line"><div class="data" data-tag="program">gt_01.test</div><div class="decoration">:</div><div class="padding"> </div><div class="text">Nableuay otay </div><div class="data" data-tag="verb">ectulatobjay</div><div class="text"> orwardfay elocipingvay</div><div class="text">: </div><div class="data" data-tag="error">Ermissionpay eniedday</div></div><div class="line"><div class="data" data-tag="program">gt_01.test</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="style">automaticyay</div><div class="text"> ynchronizationsay ofyay </div><div class="data" data-tag="type">ardinalyay</div><div class="text"> </div><div class="data" data-tag="target">ammetersgray</div><div class="text"> ailedfay</div><div class="text">: </div><div class="data" data-tag="error">Ermissionpay eniedday</div></div><div class="line"><div class="label">ydrocoptichay arzlevanesmay</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="marzlevanes">6</div></div><div class="line"><div class="text">Dude, </div><div class="label">Indingsway</div><div class="decoration">:</div><div class="padding"> </div><div class="data" data-tag="windings">otuslay-oyay-eltayay</div></div> \ No newline at end of file
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.HIPx.err b/contrib/libxo/tests/gettext/saved/gt_01.HIPx.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.HIPx.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.HIPx.out b/contrib/libxo/tests/gettext/saved/gt_01.HIPx.out
new file mode 100644
index 0000000..06b6a3c
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.HIPx.out
@@ -0,0 +1,139 @@
+<div class="line">
+ <div class="text">Ouryay </div>
+ <div class="data" data-tag="noun" data-xpath="/top/noun">ordsway</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="adjective" data-xpath="/top/adjective">amingflay</div>
+ <div class="text"> isyay </div>
+ <div class="data" data-tag="owner" data-xpath="/top/owner">ymay</div>
+ <div class="data" data-tag="target" data-xpath="/top/target">ouchcay</div>
+ <div class="text"> bubbly-bubbly </div>
+ <div class="data" data-tag="verb" data-xpath="/top/verb">urningbay</div>
+</div>
+<div class="line">
+ <div class="text">Ethay </div>
+ <div class="data" data-tag="noun" data-xpath="/top/noun">ordsway</div>
+ <div class="text"> asway '</div>
+ <div class="data" data-tag="owner" data-xpath="/top/owner">ymay</div>
+ <div class="data" data-tag="adjective" data-xpath="/top/adjective">amingflay</div>
+ <div class="data" data-tag="target" data-xpath="/top/target">ouchcay</div>
+ <div class="text">' </div>
+ <div class="data" data-tag="verb" data-xpath="/top/verb">urningbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/bytes">0</div>
+ <div class="padding"> </div>
+ <div class="note">yebay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/bytes">1</div>
+ <div class="padding"> </div>
+ <div class="note">yesbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/bytes">2</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/bytes">3</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes" data-xpath="/top/bytes">4</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="label">otaltay</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="total" data-xpath="/top/total">1234</div>
+</div>
+<div class="line">
+ <div class="text">Eceivedray </div>
+ <div class="data" data-tag="received" data-xpath="/top/received">1234</div>
+ <div class="text"> </div>
+ <div class="note">ldb2</div>
+ <div class="text"> omfray </div>
+ <div class="data" data-tag="from" data-xpath="/top/from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port" data-xpath="/top/port">4321</div>
+ <div class="text"> inyay </div>
+ <div class="data" data-tag="time" data-xpath="/top/time">32</div>
+ <div class="text"> msyay</div>
+</div>
+<div class="line">
+ <div class="text">Received </div>
+ <div class="data" data-tag="received" data-xpath="/top/received">1234</div>
+ <div class="text"> </div>
+ <div class="note">yezbay</div>
+ <div class="text"> from </div>
+ <div class="data" data-tag="from" data-xpath="/top/from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port" data-xpath="/top/port">4321</div>
+ <div class="text"> in </div>
+ <div class="data" data-tag="time" data-xpath="/top/time">32</div>
+ <div class="text"> ms</div>
+</div>
+<div class="line">
+ <div class="text">Eceivedray </div>
+ <div class="data" data-tag="received" data-xpath="/top/received">1234</div>
+ <div class="text"> </div>
+ <div class="note">ldb2</div>
+ <div class="text"> omfray </div>
+ <div class="data" data-tag="from" data-xpath="/top/from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port" data-xpath="/top/port">4321</div>
+ <div class="text"> inyay </div>
+ <div class="data" data-tag="time" data-xpath="/top/time">32</div>
+ <div class="text"> msyay</div>
+</div>
+<div class="line">
+ <div class="text">Onlyay </div>
+ <div class="data" data-tag="marzlevanes" data-xpath="/top/marzlevanes">3</div>
+ <div class="text"> </div>
+ <div class="note">arzlevanezmay</div>
+ <div class="text"> areyay unctioningfay orrectlycay</div>
+</div>
+<div class="line">
+ <div class="text">Ersionvay </div>
+ <div class="data" data-tag="date" data-xpath="/top/date">Tue Jun 23 18:47:09 UTC 2015</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="version" data-xpath="/top/version">1.2.3</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="program" data-xpath="/top/xo_emit_warn_hcv/__warning/program">gt_01.test</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="text">Nableuay otay </div>
+ <div class="data" data-tag="verb" data-xpath="/top/xo_emit_warn_hcv/__warning/verb">ectulatobjay</div>
+ <div class="text"> orwardfay elocipingvay</div>
+ <div class="text">: </div>
+ <div class="data" data-tag="error" data-xpath="/top/xo_emit_warn_hcv/__warning/error">Ermissionpay eniedday</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="program" data-xpath="/top/xo_emit_warn_hcv/__warning/program">gt_01.test</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="style" data-xpath="/top/xo_emit_warn_hcv/__warning/style">automaticyay</div>
+ <div class="text"> ynchronizationsay ofyay </div>
+ <div class="data" data-tag="type" data-xpath="/top/xo_emit_warn_hcv/__warning/type">ardinalyay</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="target" data-xpath="/top/xo_emit_warn_hcv/__warning/target">ammetersgray</div>
+ <div class="text"> ailedfay</div>
+ <div class="text">: </div>
+ <div class="data" data-tag="error" data-xpath="/top/xo_emit_warn_hcv/__warning/error">Ermissionpay eniedday</div>
+</div>
+<div class="line">
+ <div class="label">ydrocoptichay arzlevanesmay</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="marzlevanes" data-xpath="/top/marzlevanes">6</div>
+</div>
+<div class="line">
+ <div class="text">Dude, </div>
+ <div class="label">Indingsway</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="windings" data-xpath="/top/windings">otuslay-oyay-eltayay</div>
+</div>
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.HP.err b/contrib/libxo/tests/gettext/saved/gt_01.HP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.HP.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.HP.out b/contrib/libxo/tests/gettext/saved/gt_01.HP.out
new file mode 100644
index 0000000..573d7b3
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.HP.out
@@ -0,0 +1,139 @@
+<div class="line">
+ <div class="text">Ouryay </div>
+ <div class="data" data-tag="noun">ordsway</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="adjective">amingflay</div>
+ <div class="text"> isyay </div>
+ <div class="data" data-tag="owner">ymay</div>
+ <div class="data" data-tag="target">ouchcay</div>
+ <div class="text"> bubbly-bubbly </div>
+ <div class="data" data-tag="verb">urningbay</div>
+</div>
+<div class="line">
+ <div class="text">Ethay </div>
+ <div class="data" data-tag="noun">ordsway</div>
+ <div class="text"> asway '</div>
+ <div class="data" data-tag="owner">ymay</div>
+ <div class="data" data-tag="adjective">amingflay</div>
+ <div class="data" data-tag="target">ouchcay</div>
+ <div class="text">' </div>
+ <div class="data" data-tag="verb">urningbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">0</div>
+ <div class="padding"> </div>
+ <div class="note">yebay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">1</div>
+ <div class="padding"> </div>
+ <div class="note">yesbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">2</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">3</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="bytes">4</div>
+ <div class="padding"> </div>
+ <div class="note">yezbay</div>
+</div>
+<div class="line">
+ <div class="label">otaltay</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="total">1234</div>
+</div>
+<div class="line">
+ <div class="text">Eceivedray </div>
+ <div class="data" data-tag="received">1234</div>
+ <div class="text"> </div>
+ <div class="note">ldb2</div>
+ <div class="text"> omfray </div>
+ <div class="data" data-tag="from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port">4321</div>
+ <div class="text"> inyay </div>
+ <div class="data" data-tag="time">32</div>
+ <div class="text"> msyay</div>
+</div>
+<div class="line">
+ <div class="text">Received </div>
+ <div class="data" data-tag="received">1234</div>
+ <div class="text"> </div>
+ <div class="note">yezbay</div>
+ <div class="text"> from </div>
+ <div class="data" data-tag="from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port">4321</div>
+ <div class="text"> in </div>
+ <div class="data" data-tag="time">32</div>
+ <div class="text"> ms</div>
+</div>
+<div class="line">
+ <div class="text">Eceivedray </div>
+ <div class="data" data-tag="received">1234</div>
+ <div class="text"> </div>
+ <div class="note">ldb2</div>
+ <div class="text"> omfray </div>
+ <div class="data" data-tag="from">foop</div>
+ <div class="text">#</div>
+ <div class="data" data-tag="port">4321</div>
+ <div class="text"> inyay </div>
+ <div class="data" data-tag="time">32</div>
+ <div class="text"> msyay</div>
+</div>
+<div class="line">
+ <div class="text">Onlyay </div>
+ <div class="data" data-tag="marzlevanes">3</div>
+ <div class="text"> </div>
+ <div class="note">arzlevanezmay</div>
+ <div class="text"> areyay unctioningfay orrectlycay</div>
+</div>
+<div class="line">
+ <div class="text">Ersionvay </div>
+ <div class="data" data-tag="date">Tue Jun 23 18:47:09 UTC 2015</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="version">1.2.3</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="program">gt_01.test</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="text">Nableuay otay </div>
+ <div class="data" data-tag="verb">ectulatobjay</div>
+ <div class="text"> orwardfay elocipingvay</div>
+ <div class="text">: </div>
+ <div class="data" data-tag="error">Ermissionpay eniedday</div>
+</div>
+<div class="line">
+ <div class="data" data-tag="program">gt_01.test</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="style">automaticyay</div>
+ <div class="text"> ynchronizationsay ofyay </div>
+ <div class="data" data-tag="type">ardinalyay</div>
+ <div class="text"> </div>
+ <div class="data" data-tag="target">ammetersgray</div>
+ <div class="text"> ailedfay</div>
+ <div class="text">: </div>
+ <div class="data" data-tag="error">Ermissionpay eniedday</div>
+</div>
+<div class="line">
+ <div class="label">ydrocoptichay arzlevanesmay</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="marzlevanes">6</div>
+</div>
+<div class="line">
+ <div class="text">Dude, </div>
+ <div class="label">Indingsway</div>
+ <div class="decoration">:</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="windings">otuslay-oyay-eltayay</div>
+</div>
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.J.err b/contrib/libxo/tests/gettext/saved/gt_01.J.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.J.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.J.out b/contrib/libxo/tests/gettext/saved/gt_01.J.out
new file mode 100644
index 0000000..86527d1
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.J.out
@@ -0,0 +1,2 @@
+{"top": {"adjective":"amingflay","noun":"ordsway","verb":"urningbay","owner":"ymay","target":"ouchcay","adjective":"amingflay","noun":"ordsway","verb":"urningbay","owner":"ymay","target":"ouchcay", "bytes": [0,1,2,3,4],"total":1234,"received":1234,"from":"foop","port":4321,"time":32,"received":1234,"from":"foop","port":4321,"time":32,"received":1234,"from":"foop","port":4321,"time":32,"marzlevanes":3,"version":"1.2.3","date":"Tue Jun 23 18:47:09 UTC 2015", "__warning": {"program":"gt_01.test","message":"Nableuay otay ectulatobjay orwardfay elocipingvay","verb":ectulatobjay,"error":"Ermissionpay eniedday"}, "__warning": {"program":"gt_01.test","message":"automaticyay ynchronizationsay ofyay ardinalyay ammetersgray ailedfay","style":automaticyay,"type":"ardinalyay","target":"ammetersgray","error":"Ermissionpay eniedday"},"marzlevanes":6,"windings":"otuslay-oyay-eltayay"}
+}
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.JP.err b/contrib/libxo/tests/gettext/saved/gt_01.JP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.JP.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.JP.out b/contrib/libxo/tests/gettext/saved/gt_01.JP.out
new file mode 100644
index 0000000..537ab21
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.JP.out
@@ -0,0 +1,53 @@
+{
+ "top": {
+ "adjective": "amingflay",
+ "noun": "ordsway",
+ "verb": "urningbay",
+ "owner": "ymay",
+ "target": "ouchcay",
+ "adjective": "amingflay",
+ "noun": "ordsway",
+ "verb": "urningbay",
+ "owner": "ymay",
+ "target": "ouchcay",
+ "bytes": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4
+ ],
+ "total": 1234,
+ "received": 1234,
+ "from": "foop",
+ "port": 4321,
+ "time": 32,
+ "received": 1234,
+ "from": "foop",
+ "port": 4321,
+ "time": 32,
+ "received": 1234,
+ "from": "foop",
+ "port": 4321,
+ "time": 32,
+ "marzlevanes": 3,
+ "version": "1.2.3",
+ "date": "Tue Jun 23 18:47:09 UTC 2015",
+ "__warning": {
+ "program": "gt_01.test",
+ "message": "Nableuay otay ectulatobjay orwardfay elocipingvay",
+ "verb": ectulatobjay,
+ "error": "Ermissionpay eniedday"
+ },
+ "__warning": {
+ "program": "gt_01.test",
+ "message": "automaticyay ynchronizationsay ofyay ardinalyay ammetersgray ailedfay",
+ "style": automaticyay,
+ "type": "ardinalyay",
+ "target": "ammetersgray",
+ "error": "Ermissionpay eniedday"
+ },
+ "marzlevanes": 6,
+ "windings": "otuslay-oyay-eltayay"
+ }
+}
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.T.err b/contrib/libxo/tests/gettext/saved/gt_01.T.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.T.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.T.out b/contrib/libxo/tests/gettext/saved/gt_01.T.out
new file mode 100644
index 0000000..440d9a5
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.T.out
@@ -0,0 +1,17 @@
+Ouryay ordsway amingflay isyay ymayouchcay bubbly-bubbly urningbay
+Ethay ordsway asway 'ymayamingflayouchcay' urningbay
+0 yebay
+1 yesbay
+2 yezbay
+3 yezbay
+4 yezbay
+otaltay 1234
+Eceivedray 1234 ldb2 omfray foop#4321 inyay 32 msyay
+Received 1234 yezbay from foop#4321 in 32 ms
+Eceivedray 1234 ldb2 omfray foop#4321 inyay 32 msyay
+Onlyay 3 arzlevanezmay areyay unctioningfay orrectlycay
+Ersionvay Tue Jun 23 18:47:09 UTC 2015 1.2.3
+gt_01.test: Nableuay otay ectulatobjay orwardfay elocipingvay: Ermissionpay eniedday
+gt_01.test: automaticyay ynchronizationsay ofyay ardinalyay ammetersgray ailedfay: Ermissionpay eniedday
+ydrocoptichay arzlevanesmay: 6
+Dude, Indingsway: otuslay-oyay-eltayay
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.X.err b/contrib/libxo/tests/gettext/saved/gt_01.X.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.X.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.X.out b/contrib/libxo/tests/gettext/saved/gt_01.X.out
new file mode 100644
index 0000000..4eb4622
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.X.out
@@ -0,0 +1 @@
+<top><adjective>amingflay</adjective><noun>ordsway</noun><verb>urningbay</verb><owner>ymay</owner><target>ouchcay</target><adjective>amingflay</adjective><noun>ordsway</noun><verb>urningbay</verb><owner>ymay</owner><target>ouchcay</target><bytes>0</bytes><bytes>1</bytes><bytes>2</bytes><bytes>3</bytes><bytes>4</bytes><total>1234</total><received>1234</received><from>foop</from><port>4321</port><time>32</time><received>1234</received><from>foop</from><port>4321</port><time>32</time><received>1234</received><from>foop</from><port>4321</port><time>32</time><marzlevanes>3</marzlevanes><version>1.2.3</version><date>Tue Jun 23 18:47:09 UTC 2015</date><__warning><program>gt_01.test</program><message>Nableuay otay ectulatobjay orwardfay elocipingvay</message><verb>ectulatobjay</verb><error>Ermissionpay eniedday</error></__warning><__warning><program>gt_01.test</program><message>automaticyay ynchronizationsay ofyay ardinalyay ammetersgray ailedfay</message><style>automaticyay</style><type>ardinalyay</type><target>ammetersgray</target><error>Ermissionpay eniedday</error></__warning><marzlevanes>6</marzlevanes><windings>otuslay-oyay-eltayay</windings></top> \ No newline at end of file
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.XP.err b/contrib/libxo/tests/gettext/saved/gt_01.XP.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.XP.err
diff --git a/contrib/libxo/tests/gettext/saved/gt_01.XP.out b/contrib/libxo/tests/gettext/saved/gt_01.XP.out
new file mode 100644
index 0000000..eac4284
--- /dev/null
+++ b/contrib/libxo/tests/gettext/saved/gt_01.XP.out
@@ -0,0 +1,49 @@
+<top>
+ <adjective>amingflay</adjective>
+ <noun>ordsway</noun>
+ <verb>urningbay</verb>
+ <owner>ymay</owner>
+ <target>ouchcay</target>
+ <adjective>amingflay</adjective>
+ <noun>ordsway</noun>
+ <verb>urningbay</verb>
+ <owner>ymay</owner>
+ <target>ouchcay</target>
+ <bytes>0</bytes>
+ <bytes>1</bytes>
+ <bytes>2</bytes>
+ <bytes>3</bytes>
+ <bytes>4</bytes>
+ <total>1234</total>
+ <received>1234</received>
+ <from>foop</from>
+ <port>4321</port>
+ <time>32</time>
+ <received>1234</received>
+ <from>foop</from>
+ <port>4321</port>
+ <time>32</time>
+ <received>1234</received>
+ <from>foop</from>
+ <port>4321</port>
+ <time>32</time>
+ <marzlevanes>3</marzlevanes>
+ <version>1.2.3</version>
+ <date>Tue Jun 23 18:47:09 UTC 2015</date>
+ <__warning>
+ <program>gt_01.test</program>
+ <message>Nableuay otay ectulatobjay orwardfay elocipingvay</message>
+ <verb>ectulatobjay</verb>
+ <error>Ermissionpay eniedday</error>
+ </__warning>
+ <__warning>
+ <program>gt_01.test</program>
+ <message>automaticyay ynchronizationsay ofyay ardinalyay ammetersgray ailedfay</message>
+ <style>automaticyay</style>
+ <type>ardinalyay</type>
+ <target>ammetersgray</target>
+ <error>Ermissionpay eniedday</error>
+ </__warning>
+ <marzlevanes>6</marzlevanes>
+ <windings>otuslay-oyay-eltayay</windings>
+</top>
diff --git a/contrib/libxo/tests/gettext/strerror.pot b/contrib/libxo/tests/gettext/strerror.pot
new file mode 100644
index 0000000..c63e6bd
--- /dev/null
+++ b/contrib/libxo/tests/gettext/strerror.pot
@@ -0,0 +1,468 @@
+#
+# Copyright (c) 1982, 1985, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# List of system errors ala strerror() and sys_errlist
+# Phil Shafer <phil@juniper.net>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-01 16:15-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Received {:received} {N:byte,bytes} from {:from}#{:port} in {:time} ms\n"
+msgstr ""
+
+# 0 - ENOERROR
+msgid "No error: 0"
+msgstr ""
+
+# 1 - EPERM
+msgid "Operation not permitted"
+msgstr ""
+
+# 2 - ENOENT
+msgid "No such file or directory"
+msgstr ""
+
+# 3 - ESRCH
+msgid "No such process"
+msgstr ""
+
+# 4 - EINTR
+msgid "Interrupted system call"
+msgstr ""
+
+# 5 - EIO
+msgid "Input/output error"
+msgstr ""
+
+# 6 - ENXIO
+msgid "Device not configured"
+msgstr ""
+
+# 7 - E2BIG
+msgid "Argument list too long"
+msgstr ""
+
+# 8 - ENOEXEC
+msgid "Exec format error"
+msgstr ""
+
+# 9 - EBADF
+msgid "Bad file descriptor"
+msgstr ""
+
+# 10 - ECHILD
+msgid "No child processes"
+msgstr ""
+
+# 11 - EDEADLK
+msgid "Resource deadlock avoided"
+msgstr ""
+
+# 12 - ENOMEM
+msgid "Cannot allocate memory"
+msgstr ""
+
+# 13 - EACCES
+msgid "Permission denied"
+msgstr ""
+
+# 14 - EFAULT
+msgid "Bad address"
+msgstr ""
+
+# 15 - ENOTBLK
+msgid "Block device required"
+msgstr ""
+
+# 16 - EBUSY
+msgid "Device busy"
+msgstr ""
+
+# 17 - EEXIST
+msgid "File exists"
+msgstr ""
+
+# 18 - EXDEV
+msgid "Cross-device link"
+msgstr ""
+
+# 19 - ENODEV
+msgid "Operation not supported by device"
+msgstr ""
+
+# 20 - ENOTDIR
+msgid "Not a directory"
+msgstr ""
+
+# 21 - EISDIR
+msgid "Is a directory"
+msgstr ""
+
+# 22 - EINVAL
+msgid "Invalid argument"
+msgstr ""
+
+# 23 - ENFILE
+msgid "Too many open files in system"
+msgstr ""
+
+# 24 - EMFILE
+msgid "Too many open files"
+msgstr ""
+
+# 25 - ENOTTY
+msgid "Inappropriate ioctl for device"
+msgstr ""
+
+# 26 - ETXTBSY
+msgid "Text file busy"
+msgstr ""
+
+# 27 - EFBIG
+msgid "File too large"
+msgstr ""
+
+# 28 - ENOSPC
+msgid "No space left on device"
+msgstr ""
+
+# 29 - ESPIPE
+msgid "Illegal seek"
+msgstr ""
+
+# 30 - EROFS
+msgid "Read-only file system"
+msgstr ""
+
+# 31 - EMLINK
+msgid "Too many links"
+msgstr ""
+
+# 32 - EPIPE
+msgid "Broken pipe"
+msgstr ""
+
+#
+# math software
+#
+
+# 33 - EDOM
+msgid "Numerical argument out of domain"
+msgstr ""
+
+# 34 - ERANGE
+msgid "Result too large"
+msgstr ""
+
+#
+# non-blocking and interrupt i/o
+#
+
+# 35 - EAGAIN
+# 35 - EWOULDBLOCK
+msgid "Resource temporarily unavailable"
+msgstr ""
+
+# 36 - EINPROGRESS
+msgid "Operation now in progress"
+msgstr ""
+
+# 37 - EALREADY
+msgid "Operation already in progress"
+msgstr ""
+
+
+#
+# ipc/network software -- argument errors
+#
+
+# 38 - ENOTSOCK
+msgid "Socket operation on non-socket"
+msgstr ""
+
+# 39 - EDESTADDRREQ
+msgid "Destination address required"
+msgstr ""
+
+# 40 - EMSGSIZE
+msgid "Message too long"
+msgstr ""
+
+# 41 - EPROTOTYPE
+msgid "Protocol wrong type for socket"
+msgstr ""
+
+# 42 - ENOPROTOOPT
+msgid "Protocol not available"
+msgstr ""
+
+# 43 - EPROTONOSUPPORT
+msgid "Protocol not supported"
+msgstr ""
+
+# 44 - ESOCKTNOSUPPORT
+msgid "Socket type not supported"
+msgstr ""
+
+# 45 - EOPNOTSUPP
+msgid "Operation not supported"
+msgstr ""
+
+# 46 - EPFNOSUPPORT
+msgid "Protocol family not supported"
+msgstr ""
+
+# 47 - EAFNOSUPPORT
+msgid "Address family not supported by protocol family"
+msgstr ""
+
+# 48 - EADDRINUSE
+msgid "Address already in use"
+msgstr ""
+
+# 49 - EADDRNOTAVAIL
+msgid "Can't assign requested address"
+msgstr ""
+
+#
+# ipc/network software -- operational errors
+#
+
+# 50 - ENETDOWN
+msgid "Network is down"
+msgstr ""
+
+# 51 - ENETUNREACH
+msgid "Network is unreachable"
+msgstr ""
+
+# 52 - ENETRESET
+msgid "Network dropped connection on reset"
+msgstr ""
+
+# 53 - ECONNABORTED
+msgid "Software caused connection abort"
+msgstr ""
+
+# 54 - ECONNRESET
+msgid "Connection reset by peer"
+msgstr ""
+
+# 55 - ENOBUFS
+msgid "No buffer space available"
+msgstr ""
+
+# 56 - EISCONN
+msgid "Socket is already connected"
+msgstr ""
+
+# 57 - ENOTCONN
+msgid "Socket is not connected"
+msgstr ""
+
+# 58 - ESHUTDOWN
+msgid "Can't send after socket shutdown"
+msgstr ""
+
+# 59 - ETOOMANYREFS
+msgid "Too many references: can't splice"
+msgstr ""
+
+# 60 - ETIMEDOUT
+msgid "Operation timed out"
+msgstr ""
+
+# 61 - ECONNREFUSED
+msgid "Connection refused"
+msgstr ""
+
+# 62 - ELOOP
+msgid "Too many levels of symbolic links"
+msgstr ""
+
+# 63 - ENAMETOOLONG
+msgid "File name too long"
+msgstr ""
+
+#
+# should be rearranged
+#
+
+# 64 - EHOSTDOWN
+msgid "Host is down"
+msgstr ""
+
+# 65 - EHOSTUNREACH
+msgid "No route to host"
+msgstr ""
+
+# 66 - ENOTEMPTY
+msgid "Directory not empty"
+msgstr ""
+
+#
+# quotas & mush
+#
+
+# 67 - EPROCLIM
+msgid "Too many processes"
+msgstr ""
+
+# 68 - EUSERS
+msgid "Too many users"
+msgstr ""
+
+# 69 - EDQUOT
+msgid "Disc quota exceeded"
+msgstr ""
+
+#
+# Network File System
+#
+
+# 70 - ESTALE
+msgid "Stale NFS file handle"
+msgstr ""
+
+# 71 - EREMOTE
+msgid "Too many levels of remote in path"
+msgstr ""
+
+# 72 - EBADRPC
+msgid "RPC struct is bad"
+msgstr ""
+
+# 73 - ERPCMISMATCH
+msgid "RPC version wrong"
+msgstr ""
+
+# 74 - EPROGUNAVAIL
+msgid "RPC prog. not avail"
+msgstr ""
+
+# 75 - EPROGMISMATCH
+msgid "Program version wrong"
+msgstr ""
+
+# 76 - EPROCUNAVAIL
+msgid "Bad procedure for program"
+msgstr ""
+
+# 77 - ENOLCK
+msgid "No locks available"
+msgstr ""
+
+# 78 - ENOSYS
+msgid "Function not implemented"
+msgstr ""
+
+# 79 - EFTYPE
+msgid "Inappropriate file type or format"
+msgstr ""
+
+# 80 - EAUTH
+msgid "Authentication error"
+msgstr ""
+
+# 81 - ENEEDAUTH
+msgid "Need authenticator"
+msgstr ""
+
+# 82 - EIDRM
+msgid "Identifier removed"
+msgstr ""
+
+# 83 - ENOMSG
+msgid "No message of desired type"
+msgstr ""
+
+# 84 - EOVERFLOW
+msgid "Value too large to be stored in data type"
+msgstr ""
+
+# 85 - ECANCELED
+msgid "Operation canceled"
+msgstr ""
+
+# 86 - EILSEQ
+msgid "Illegal byte sequence"
+msgstr ""
+
+# 87 - ENOATTR
+msgid "Attribute not found"
+msgstr ""
+
+#
+# General
+#
+
+# 88 - EDOOFUS
+msgid "Programming error"
+msgstr ""
+
+# 89 - EBADMSG
+msgid "Bad message"
+msgstr ""
+
+# 90 - EMULTIHOP
+msgid "Multihop attempted"
+msgstr ""
+
+# 91 - ENOLINK
+msgid "Link has been severed"
+msgstr ""
+
+# 92 - EPROTO
+msgid "Protocol error"
+msgstr ""
+
+# 93 - ENOTCAPABLE
+msgid "Capabilities insufficient"
+msgstr ""
+
+# 94 - ECAPMODE
+msgid "Not permitted in capability mode"
+msgstr ""
+
+# 95 - ENOTRECOVERABLE
+msgid "State not recoverable"
+msgstr ""
+
+# 96 - EOWNERDEAD
+msgid "Previous owner died"
+msgstr ""
diff --git a/contrib/libxo/xo/Makefile.am b/contrib/libxo/xo/Makefile.am
index 247ef3b..ca01de3 100644
--- a/contrib/libxo/xo/Makefile.am
+++ b/contrib/libxo/xo/Makefile.am
@@ -30,6 +30,14 @@ xo_SOURCES = xo.c
LDADD = \
${top_builddir}/libxo/libxo.la
+if HAVE_HUMANIZE_NUMBER
+LDADD += -lutil
+endif
+
man_MANS = xo.1
EXTRA_DIST = xo.1
+
+install-data-hook:
+ for file in ${man_MANS}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man1dir}/$$file ; done
diff --git a/contrib/libxo/xo/xo.1 b/contrib/libxo/xo/xo.1
index 9dcae85..54a97e7 100644
--- a/contrib/libxo/xo/xo.1
+++ b/contrib/libxo/xo/xo.1
@@ -168,31 +168,6 @@ prepend data to the XPath values used for HTML output style.
<product>stereo</product>
<status>in route</status>
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr libxo 3 ,
.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
diff --git a/contrib/libxo/xo/xo.c b/contrib/libxo/xo/xo.c
index c364539..ce758af 100644
--- a/contrib/libxo/xo/xo.c
+++ b/contrib/libxo/xo/xo.c
@@ -13,9 +13,8 @@
#include <stdarg.h>
#include <string.h>
-#include "xoconfig.h"
+#include "xo_config.h"
#include "xo.h"
-#include "xoversion.h"
#include <getopt.h> /* Include after xo.h for testing */
@@ -95,7 +94,8 @@ static int
formatter (xo_handle_t *xop, char *buf, int bufsiz,
const char *fmt, va_list vap UNUSED)
{
- int lflag = 0, hflag = 0, jflag = 0, tflag = 0,
+ int lflag UNUSED = 0; /* Parse long flag, though currently ignored */
+ int hflag = 0, jflag = 0, tflag = 0,
zflag = 0, qflag = 0, star1 = 0, star2 = 0;
int rc = 0;
int w1 = 0, w2 = 0;
@@ -201,6 +201,7 @@ print_help (void)
" --leading-xpath <path> OR -l <path> "
"Add a prefix to generated XPaths (HTML)\n"
" --open <path> Open tags for the given path\n"
+" --option <opts> -or -O <opts> Give formatting options\n"
" --pretty OR -p Make 'pretty' output (add indent, newlines)\n"
" --style <style> OR -s <style> "
"Generate given style (xml, json, text, html)\n"
@@ -259,7 +260,7 @@ main (int argc UNUSED, char **argv)
if (argc < 0)
return 1;
- while ((rc = getopt_long(argc, argv, "c:HJl:ps:TXW",
+ while ((rc = getopt_long(argc, argv, "c:HJl:O:o:ps:TXW",
long_opts, NULL)) != -1) {
switch (rc) {
case 'c':
diff --git a/contrib/libxo/xohtml/Makefile.am b/contrib/libxo/xohtml/Makefile.am
index 49dffed..cd4cac2 100644
--- a/contrib/libxo/xohtml/Makefile.am
+++ b/contrib/libxo/xohtml/Makefile.am
@@ -36,3 +36,7 @@ uninstall-hook:
rm ${DESTDIR}${XO_SHAREDIR}/$$file ; done
rmdir ${DESTDIR}${XO_SHAREDIR}/external
rm -f ${DESTDIR}${bindir}/xohtml
+
+install-data-hook:
+ for file in ${man_MANS}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man1dir}/$$file ; done
diff --git a/contrib/libxo/xohtml/xohtml.1 b/contrib/libxo/xohtml/xohtml.1
index d520cb3..723225b 100644
--- a/contrib/libxo/xohtml/xohtml.1
+++ b/contrib/libxo/xohtml/xohtml.1
@@ -94,32 +94,6 @@ the output to /tmp/src.html:
xohtml -c "du --libxo=html,warn ~/src" -f /tmp/src.html
.Ed
.Pp
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr libxo 3 ,
.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
-
diff --git a/contrib/libxo/xolint/Makefile.am b/contrib/libxo/xolint/Makefile.am
index ec5c36d..10e9759 100644
--- a/contrib/libxo/xolint/Makefile.am
+++ b/contrib/libxo/xolint/Makefile.am
@@ -15,3 +15,7 @@ install-exec-hook:
uninstall-hook:
rm -f ${DESTDIR}${bindir}/xolint
+
+install-data-hook:
+ for file in ${man_MANS}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man1dir}/$$file ; done
diff --git a/contrib/libxo/xolint/xolint.1 b/contrib/libxo/xolint/xolint.1
index 16a59fc..11b5975 100644
--- a/contrib/libxo/xolint/xolint.1
+++ b/contrib/libxo/xolint/xolint.1
@@ -74,31 +74,6 @@ line that contains the error:
xolint.c: 16: error: anchor format should be "%d"
16 xo_emit("{[:/%s}");
.Ed
-.Sh ADDITIONAL DOCUMENTATION
-Complete documentation can be found on github:
-.Bd -literal -offset indent
-http://juniper.github.io/libxo/libxo-manual.html
-.Ed
-.Pp
-.Nm libxo
-lives on github as:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo
-.Ed
-.Pp
-The latest release of
-.Nm libxo
-is available at:
-.Bd -literal -offset indent
-https://github.com/Juniper/libxo/releases
-.Ed
.Sh SEE ALSO
.Xr libxo 3 ,
.Xr xo_emit 3
-.Sh HISTORY
-The
-.Nm libxo
-library was added in
-.Fx 11.0 .
-.Sh AUTHOR
-Phil Shafer
diff --git a/contrib/libxo/xolint/xolint.pl b/contrib/libxo/xolint/xolint.pl
index 515f7fa..eb72511 100755
--- a/contrib/libxo/xolint/xolint.pl
+++ b/contrib/libxo/xolint/xolint.pl
@@ -310,10 +310,68 @@ sub check_text {
info("a percent sign appearing in text is a literal") if $text =~ /%/;
}
+%short = (
+ # Roles
+ "color" => "C",
+ "decoration" => "D",
+ "error" => "E",
+ "label" => "L",
+ "note" => "N",
+ "padding" => "P",
+ "title" => "T",
+ "units" => "U",
+ "value" => "V",
+ "warning" => "W",
+ "start-anchor" => "[",
+ "stop-anchor" => "]",
+ # Modifiers
+ "colon" => "c",
+ "display" => "d",
+ "encoding" => "e",
+ "hn" => "h",
+ "hn-decimal" => "@",
+ "hn-space" => "@",
+ "hn-1000" => "@",
+ "humanize" => "h",
+ "key" => "k",
+ "leaf-list" => "l",
+ "no-quotes" => "n",
+ "quotes" => "q",
+ "trim" => "t",
+ "white" => "w",
+ );
+
sub check_field {
my(@field) = @_;
print "checking field: [" . join("][", @field) . "]\n" if $opt_debug;
+ if ($field[0] =~ /,/) {
+ # We have long names; deal with it by turning them into short names
+ my @parts = split(/,/, $field[0]);
+ my $new = "";
+ for (my $i = 1; $i <= $#parts; $i++) {
+ my $v = $parts[$i];
+ $v =~ s/^\s+//;
+ $v =~ s/\s+$//;
+ if ($short{$v} eq "@") {
+ # ignore; has no short version
+ } elsif ($short{$v}) {
+ $new .= $short{$v};
+ } else {
+ #@ Unknown long name for role/modifier
+ #@ xo_emit("{,humanization:value}", value);
+ #@ Should be:
+ #@ xo_emit("{,humanize:value}", value);
+ #@ The hn-* modifiers (hn-decimal, hn-space, hn-1000)
+ #@ are only valid for fields with the {h:} modifier.
+ error("Unknown long name for role/modifier ($v)");
+ }
+ }
+
+ $field[4] = substr($field[0], index($field[0], ","));
+ $field[0] = $parts[0] . $new;
+ }
+
if ($opt_vocabulary) {
$vocabulary{$field[1]} = 1
if $field[1] && $field[0] !~ /[DELNPTUW\[\]]/;
@@ -426,6 +484,32 @@ sub check_field {
}
}
+ # Humanized field
+ if ($field[0] =~ /h/) {
+ if (length($field[2]) == 0) {
+ #@ Field has humanize modifier but no format string
+ #@ xo_emit("{h:value}", value);
+ #@ Should be:
+ #@ xo_emit("{h:value/%d}", value);
+ #@ Humanization is only value for numbers, which are not
+ #@ likely to use the default format ("%s").
+ error("Field has humanize modifier but no format string");
+ }
+ }
+
+ # hn-* on non-humanize field
+ if ($field[0] !~ /h/) {
+ if ($field[4] =~ /,hn-/) {
+ #@ Field has hn-* modifier but not 'h' modifier
+ #@ xo_emit("{,hn-1000:value}", value);
+ #@ Should be:
+ #@ xo_emit("{h,hn-1000:value}", value);
+ #@ The hn-* modifiers (hn-decimal, hn-space, hn-1000)
+ #@ are only valid for fields with the {h:} modifier.
+ error("Field has hn-* modifier but not 'h' modifier");
+ }
+ }
+
# A value field
if (length($field[0]) == 0 || $field[0] =~ /V/) {
diff --git a/contrib/libxo/xopo/Makefile.am b/contrib/libxo/xopo/Makefile.am
new file mode 100644
index 0000000..192d308
--- /dev/null
+++ b/contrib/libxo/xopo/Makefile.am
@@ -0,0 +1,43 @@
+#
+# Copyright 2014, 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.
+
+if LIBXO_WARNINGS_HIGH
+LIBXO_WARNINGS = HIGH
+endif
+include ${top_srcdir}/warnings.mk
+
+AM_CFLAGS = \
+ -DLIBXO_XMLSOFT_NEED_PRIVATE \
+ -I${top_builddir} \
+ -I${top_srcdir} \
+ -I${top_srcdir}/libxo \
+ ${WARNINGS}
+
+LIBS = \
+ ${LIBXO_LIBS}
+
+bin_PROGRAMS = xopo
+
+xopo_SOURCES = xopo.c
+#xo_LDADD = ../libxo/libxo.la
+#xo_LDFLAGS = -static
+
+LDADD = \
+ ${top_builddir}/libxo/libxo.la
+
+if HAVE_HUMANIZE_NUMBER
+LDADD += -lutil
+endif
+
+man_MANS = xopo.1
+
+EXTRA_DIST = xopo.1
+
+install-data-hook:
+ for file in ${man_MANS}; do \
+ cat ../libxo/add.man >> ${DESTDIR}${man1dir}/$$file ; done
diff --git a/contrib/libxo/xopo/xopo.1 b/contrib/libxo/xopo/xopo.1
new file mode 100644
index 0000000..c1a9ae8
--- /dev/null
+++ b/contrib/libxo/xopo/xopo.1
@@ -0,0 +1,77 @@
+.\" #
+.\" # Copyright (c) 2015, Juniper Networks, Inc.
+.\" # All rights reserved.
+.\" # This SOFTWARE is licensed under the LICENSE provided in the
+.\" # ../Copyright file. By downloading, installing, copying, or
+.\" # using the SOFTWARE, you agree to be bound by the terms of that
+.\" # LICENSE.
+.\" # Phil Shafer, July 2015
+.\"
+.Dd July 9, 2015
+.Dt XOPO 1
+.Os
+.Sh NAME
+.Nm xopo
+.Nd turn libxo format strings into simplified form
+.Sh SYNOPSIS
+.Nm
+.Op Fl options
+.Sh DESCRIPTION
+The
+.Nm
+utility simplifies libxo format strings into the form used for
+.Xr gettext
+lookups by the
+.Nm libxo
+library.
+Using
+.Nm ,
+users can turn
+.Em .pot
+files generated by
+.Xr xgettext
+into data useful for libxo-enabled applications.
+.Pp
+Since
+.Xr gettext
+uses the string as the key into the message catalog,
+.Nm libxo
+uses a simplified version of the format string that removes
+unimportant field formatting and modifiers, stopping minor formatting
+changes from impacting the expensive translation process.
+A developer
+change such as changing "/%06d" to "/%08d" should not force hand
+inspection of all .po files.
+.Pp
+.Nm
+inspects the input file, looking for lines that begin with "msgid"
+which carry format strings as the remainder of the input line.
+These strings are passed to
+.Nm libxo
+for simplification and the resulting strings are replaced into the
+output stream, allowing
+.Nm
+to operated as a filter.
+.Pp
+.Bl -tag -width "12345678901234567"
+.It Fl "-simplify OR -s <text>"
+Generate the simplified version of a single text string.
+.It Fl "-po OR -f <pofile>"
+Use the given po file for input.
+.It Fl "-help"
+Display this help text
+.It Fl "-output OR -o <file>"
+Write output content to the given file
+.It Fl "-warn OR -W"
+Generate warnings while parsing the format strings
+.It Fl "-version"
+Display version information
+.El
+.Pp
+.Sh EXAMPLE
+.Bd -literal
+ % xopo -f foo.pot -o foo.pot.new
+.Ed
+.Sh SEE ALSO
+.Xr libxo 3 ,
+.Xr xo_format 5
diff --git a/contrib/libxo/xopo/xopo.c b/contrib/libxo/xopo/xopo.c
new file mode 100644
index 0000000..991b757
--- /dev/null
+++ b/contrib/libxo/xopo/xopo.c
@@ -0,0 +1,292 @@
+/*
+ * 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 2015
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/queue.h>
+
+#include "xo_config.h"
+#include "xo.h"
+
+#include <getopt.h> /* Include after xo.h for testing */
+
+#ifndef UNUSED
+#define UNUSED __attribute__ ((__unused__))
+#endif /* UNUSED */
+
+static int opt_warn; /* Enable warnings */
+static int opt_numbers; /* Number our fields */
+
+typedef struct xopo_msg_s {
+ TAILQ_ENTRY(xopo_msg_s) xm_link;
+ char *xm_plural; /* If plural, points to the second part */
+ char xm_data[0]; /* Start of data */
+} xopo_msg_t;
+
+typedef TAILQ_HEAD(xopo_msg_list_s, xopo_msg_s) xopo_msg_list_t;
+
+static xopo_msg_list_t field_list;
+
+static void
+xopo_msg_cb (const char *str, unsigned len, int plural)
+{
+ int sz = sizeof(xopo_msg_t) + len + 1;
+ xopo_msg_t *xmp = malloc(sz);
+ if (xmp == NULL)
+ return;
+
+ bzero(xmp, sz);
+ memcpy(xmp->xm_data, str, len);
+ xmp->xm_data[len] = '\0';
+
+ if (plural) {
+ char *cp = strchr(xmp->xm_data, ',');
+ if (cp) {
+ *cp++ = '\0';
+ xmp->xm_plural = cp;
+ }
+ }
+
+ xopo_msg_t *xmp2;
+
+ TAILQ_FOREACH(xmp2, &field_list, xm_link) {
+ if (strcmp(xmp->xm_data, xmp2->xm_data) == 0) {
+ /* Houston, we have a negative on that trajectory */
+ free(xmp);
+ return;
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&field_list, xmp, xm_link);
+}
+
+static void
+print_version (void)
+{
+ fprintf(stderr, "libxo version %s%s\n",
+ xo_version, xo_version_extra);
+ fprintf(stderr, "xopo version %s%s\n",
+ LIBXO_VERSION, LIBXO_VERSION_EXTRA);
+}
+
+static void
+print_help (void)
+{
+ fprintf(stderr,
+"Usage: xopo [options] format [fields]\n"
+" --help Display this help text\n"
+" --option <opts> -or -O <opts> Give formatting options\n"
+" --output <file> -or -o <file> Use file as output destination\n"
+" --po <file> or -f <file> Generate new msgid's for a po file\n"
+" --simplify <text> OR -s <text> Show simplified form of the format string\n"
+" --version Display version information\n"
+" --warn OR -W Display warnings in text on stderr\n"
+);
+}
+
+static struct opts {
+ int o_help;
+ int o_version;
+} opts;
+
+static struct option long_opts[] = {
+ { "help", no_argument, &opts.o_help, 1 },
+ { "number", no_argument, NULL, 'n' },
+ { "option", required_argument, NULL, 'O' },
+ { "output", required_argument, NULL, 'o' },
+ { "po", required_argument, NULL, 'f' },
+ { "simplify", no_argument, NULL, 'S' },
+ { "warn", no_argument, NULL, 'W' },
+ { NULL, 0, NULL, 0 }
+};
+
+int
+main (int argc UNUSED, char **argv)
+{
+ char *fmt = NULL;
+ char *opt_options = NULL;
+ char *opt_input = NULL;
+ char *opt_output = NULL;
+ char *opt_simplify = NULL;
+ int rc;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ return 1;
+
+ while ((rc = getopt_long(argc, argv, "f:no:O:s:W",
+ long_opts, NULL)) != -1) {
+ switch (rc) {
+ case 'f':
+ opt_input = optarg;
+ break;
+
+ case 'n':
+ opt_numbers = 1;
+ break;
+
+ case 'o':
+ opt_output = optarg;
+ break;
+
+ case 'O':
+ opt_options = optarg;
+ break;
+
+ case 's':
+ opt_simplify = optarg;
+ break;
+
+ case 'W':
+ opt_warn = 1;
+ xo_set_flags(NULL, XOF_WARN);
+ break;
+
+ case ':':
+ xo_errx(1, "missing argument");
+ break;
+
+ case 0:
+ if (opts.o_help) {
+ print_help();
+ return 1;
+
+ } else if (opts.o_version) {
+ print_version();
+ return 0;
+
+ } else {
+ print_help();
+ return 1;
+ }
+
+ bzero(&opts, sizeof(opts)); /* Reset all the options */
+ break;
+
+ default:
+ print_help();
+ return 1;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (opt_options) {
+ rc = xo_set_options(NULL, opt_options);
+ if (rc < 0)
+ xo_errx(1, "invalid options: %s", opt_options);
+ }
+
+ fmt = *argv++;
+
+ if (opt_simplify) {
+ fmt = xo_simplify_format(NULL, opt_simplify, opt_numbers, NULL);
+ if (fmt) {
+ xo_emit("{:format}\n", fmt);
+ free(fmt);
+ }
+ exit(0);
+ }
+
+ static char msgid[] = "msgid ";
+ char buf[BUFSIZ], *cp, *ep;
+ FILE *infile;
+ FILE *outfile;
+ TAILQ_INIT(&field_list);
+
+ if (opt_input) {
+ infile = fopen(opt_input, "r");
+ if (infile == NULL)
+ xo_emit_err(1, "count not open input file: '{:filename}'",
+ opt_input);
+ } else
+ infile = stdin;
+
+ if (opt_output) {
+ unlink(opt_output);
+ outfile = fopen(opt_output, "w");
+ if (outfile == NULL)
+ xo_emit_err(1, "count not open output file: '{:filename}'",
+ opt_output);
+ } else
+ outfile = stdout;
+
+ int blank = 0, line;
+
+ for (line = 1;; line++) {
+ if (fgets(buf, sizeof(buf), infile) == NULL)
+ break;
+
+ if (buf[0] == '#' && buf[1] == '\n')
+ continue;
+
+ blank = (buf[0] == '\n' && buf[1] == '\0');
+
+ if (strncmp(buf, msgid, sizeof(msgid) - 1) != 0) {
+ fprintf(outfile, "%s", buf);
+ continue;
+ }
+
+ for (cp = buf + sizeof(msgid); *cp; cp++)
+ if (!isspace((int) *cp))
+ break;
+
+ if (*cp == '"')
+ cp += 1;
+
+ ep = cp + strlen(cp);
+ if (ep > cp)
+ ep -= 1;
+
+ while (isspace((int) *ep) && ep > cp)
+ ep -= 1;
+
+ if (*ep != '"')
+ *ep += 1;
+
+ *ep = '\0';
+
+ cp = xo_simplify_format(NULL, cp, opt_numbers, xopo_msg_cb);
+ if (cp) {
+ fprintf(outfile, "msgid \"%s\"\n", cp);
+ free(cp);
+ }
+ }
+
+ if (!blank)
+ fprintf(outfile, "\n");
+
+ xopo_msg_t *xmp;
+ TAILQ_FOREACH(xmp, &field_list, xm_link) {
+ if (xmp->xm_plural) {
+ fprintf(outfile, "msgid \"%s\"\n"
+ "msgid_plural \"%s\"\n"
+ "msgstr[0] \"\"\n"
+ "msgstr[1] \"\"\n\n",
+ xmp->xm_data, xmp->xm_plural);
+ } else {
+ fprintf(outfile, "msgid \"%s\"\nmsgstr \"\"\n\n", xmp->xm_data);
+ }
+ }
+
+ if (infile != stdin)
+ fclose(infile);
+ if (outfile != stdout)
+ fclose(outfile);
+
+ xo_finish();
+
+ return 0;
+}
diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist
index e507f54..9ca7ade 100644
--- a/etc/mtree/BSD.usr.dist
+++ b/etc/mtree/BSD.usr.dist
@@ -38,6 +38,10 @@
..
i18n
..
+ libxo
+ encoder
+ ..
+ ..
..
lib32
dtrace
diff --git a/lib/libxo/Makefile b/lib/libxo/Makefile
index 62fface..8ca1292 100644
--- a/lib/libxo/Makefile
+++ b/lib/libxo/Makefile
@@ -9,33 +9,40 @@ SHLIB_MAJOR=0
SHLIBDIR?= /lib
-SRCS= libxo.c
+SRCS= libxo.c xo_encoder.c xo_syslog.c
CFLAGS+=-I${LIBXO}/libxo
+CFLAGS+=-DXO_ENCODERDIR=\"/usr/lib/libxo/encoder\"
-INCS= xo.h
+INCS= xo.h xo_encoder.h
INCSDIR=${INCLUDEDIR}/libxo
+WARNS?= 5
+
MAN+= libxo.3
MAN+= xo_attr.3 \
xo_create.3 \
xo_emit.3 \
+ xo_emit_err.3 \
xo_err.3 \
xo_error.3 \
xo_finish.3 \
xo_flush.3 \
+ xo_message.3 \
xo_no_setlocale.3 \
xo_open_container.3 \
- xo_open_marker.3 \
xo_open_list.3 \
+ xo_open_marker.3 \
xo_parse_args.3 \
xo_set_allocator.3 \
xo_set_flags.3 \
xo_set_info.3 \
xo_set_options.3 \
xo_set_style.3 \
+ xo_set_syslog_enterprise_id.3 \
xo_set_version.3 \
- xo_set_writer.3
+ xo_set_writer.3 \
+ xo_syslog.3
MAN+= xo_format.5
MLINKS= xo_attr.3 xo_attr_h.3 \
@@ -44,18 +51,23 @@ MLINKS= xo_attr.3 xo_attr_h.3 \
xo_create.3 xo_destroy.3 \
xo_emit.3 xo_emit_h.3 \
xo_emit.3 xo_emit_hv.3 \
+ xo_emit_err.3 xo_emit_errc.3 \
+ xo_emit_err.3 xo_emit_errx.3 \
+ xo_emit_err.3 xo_emit_warn.3 \
+ xo_emit_err.3 xo_emit_warnx.3 \
+ xo_emit_err.3 xo_emit_warn_c.3 \
+ xo_emit_err.3 xo_emit_warn_hc.3 \
+ xo_err.3 xo_errc.3 \
+ xo_err.3 xo_errx.3 \
xo_err.3 xo_warn.3 \
xo_err.3 xo_warnx.3 \
xo_err.3 xo_warn_c.3 \
xo_err.3 xo_warn_hc.3 \
- xo_err.3 xo_errc.3 \
- xo_err.3 xo_errx.3 \
- xo_err.3 xo_message.3 \
- xo_err.3 xo_message_c.3 \
- xo_err.3 xo_message_hc.3 \
- xo_err.3 xo_message_hcv.3 \
xo_finish.3 xo_finish_h.3 \
xo_flush.3 xo_flush_h.3 \
+ xo_message.3 xo_message_c.3 \
+ xo_message.3 xo_message_hc.3 \
+ xo_message.3 xo_message_hcv.3 \
xo_open_container.3 xo_open_container_h.3 \
xo_open_container.3 xo_open_container_hd.3 \
xo_open_container.3 xo_open_container_d.3 \
@@ -78,7 +90,16 @@ MLINKS= xo_attr.3 xo_attr_h.3 \
xo_open_list.3 xo_close_list_h.3 \
xo_open_list.3 xo_close_list_hd.3 \
xo_open_list.3 xo_close_list_d.3 \
+ xo_open_marker.3 xo_open_marker_h.3 \
+ xo_open_marker.3 xo_close_marker.3 \
+ xo_open_marker.3 xo_close_marker_h.3 \
+ xo_parse_args.3 xo_set_program.3 \
xo_set_flags.3 xo_clear_flags.3 \
- xo_set_style.3 xo_set_style_name.3
+ xo_set_style.3 xo_set_style_name.3 \
+ xo_set_version.3 xo_set_version_h.3 \
+ xo_syslog.3 xo_close_log.3 \
+ xo_syslog.3 xo_open_log.3 \
+ xo_syslog.3 xo_set_logmask.3 \
+ xo_syslog.3 xo_vsyslog.3
.include <bsd.lib.mk>
diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile
index 5089a12..b67979e 100644
--- a/libexec/ftpd/Makefile
+++ b/libexec/ftpd/Makefile
@@ -13,7 +13,7 @@ YFLAGS=
WARNS?= 2
WFORMAT=0
-LIBADD= util crypt xo
+LIBADD= crypt xo util
# XXX Kluge! Conversation mechanism needs to be fixed.
LIBADD+= opie md
diff --git a/sbin/savecore/Makefile b/sbin/savecore/Makefile
index 5134e4c..e46496f 100644
--- a/sbin/savecore/Makefile
+++ b/sbin/savecore/Makefile
@@ -1,7 +1,7 @@
# $FreeBSD$
PROG= savecore
-LIBADD= z xo
+LIBADD= z xo util
MAN= savecore.8
.include <bsd.prog.mk>
diff --git a/usr.bin/iscsictl/Makefile b/usr.bin/iscsictl/Makefile
index 4437e2d..9b2e1f1 100644
--- a/usr.bin/iscsictl/Makefile
+++ b/usr.bin/iscsictl/Makefile
@@ -6,7 +6,7 @@ CFLAGS+= -I${.CURDIR}
CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
MAN= iscsi.conf.5 iscsictl.8
-LIBADD= xo
+LIBADD= xo util
YFLAGS+= -v
LFLAGS+= -i
diff --git a/usr.bin/netstat/Makefile b/usr.bin/netstat/Makefile
index 9c70eb4..0572a03 100644
--- a/usr.bin/netstat/Makefile
+++ b/usr.bin/netstat/Makefile
@@ -33,7 +33,7 @@ CFLAGS+=-DPF
BINGRP= kmem
BINMODE=2555
-LIBADD= kvm memstat util
+LIBADD= kvm memstat xo util
.if ${MK_NETGRAPH_SUPPORT} != "no"
SRCS+= netgraph.c
@@ -41,6 +41,4 @@ LIBADD+= netgraph
CFLAGS+=-DNETGRAPH
.endif
-LIBADD+= xo
-
.include <bsd.prog.mk>
diff --git a/usr.bin/w/Makefile b/usr.bin/w/Makefile
index 0c51463..0bd09ff 100644
--- a/usr.bin/w/Makefile
+++ b/usr.bin/w/Makefile
@@ -4,7 +4,7 @@
PROG= w
SRCS= fmt.c pr_time.c proc_compare.c w.c
MAN= w.1 uptime.1
-LIBADD= kvm sbuf util xo
+LIBADD= kvm sbuf xo util
#BINGRP= kmem
#BINMODE=2555
LINKS= ${BINDIR}/w ${BINDIR}/uptime
diff --git a/usr.bin/wc/Makefile b/usr.bin/wc/Makefile
index 540e33d..6fa21fa 100644
--- a/usr.bin/wc/Makefile
+++ b/usr.bin/wc/Makefile
@@ -2,6 +2,6 @@
# $FreeBSD$
PROG= wc
-LIBADD= xo
+LIBADD= xo util
.include <bsd.prog.mk>
diff --git a/usr.bin/xo/Makefile b/usr.bin/xo/Makefile
index b199207..be22618 100644
--- a/usr.bin/xo/Makefile
+++ b/usr.bin/xo/Makefile
@@ -10,6 +10,6 @@ MAN= xo.1
# XXX For xoversion.h
CFLAGS+=-I${LIBXO}/libxo
-LIBADD= xo
+LIBADD= xo util
.include <bsd.prog.mk>
OpenPOWER on IntegriCloud