aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid E. O'Brien <obrien@FreeBSD.org>2016-03-16 23:06:34 +0000
committerDavid E. O'Brien <obrien@FreeBSD.org>2016-03-16 23:06:34 +0000
commit6c5bdc21e16419f8486fea593e7d38265d341ee9 (patch)
tree20c57dd873e619f9a0ea7759268d7ccf0957167a
downloadsrc-6c5bdc21e16419f8486fea593e7d38265d341ee9.tar.gz
src-6c5bdc21e16419f8486fea593e7d38265d341ee9.zip
Moving libxo to properly tracked, 3rd-Party imported handling.
Reviewed by: phil, sjg
Notes
Notes: svn path=/vendor/Juniper/libxo/dist/; revision=296962
-rw-r--r--.travis.yml12
-rw-r--r--Copyright23
-rw-r--r--INSTALL.md15
-rw-r--r--LICENSE23
-rw-r--r--Makefile.am102
-rw-r--r--README.md64
-rw-r--r--bin/Makefile.am29
-rw-r--r--bin/Zaliases29
-rwxr-xr-xbin/setup.sh33
-rw-r--r--build/.create0
-rw-r--r--configure.ac452
-rw-r--r--doc/Makefile.am70
-rw-r--r--doc/libxo.txt3757
-rw-r--r--encoder/Makefile.am9
-rw-r--r--encoder/cbor/Makefile.am51
-rw-r--r--encoder/cbor/enc_cbor.c365
-rw-r--r--encoder/test/Makefile.am51
-rw-r--r--encoder/test/enc_test.c30
-rwxr-xr-xinstall-sh527
-rw-r--r--libxo-config.in119
-rw-r--r--libxo/Makefile.am89
-rw-r--r--libxo/add.man29
-rw-r--r--libxo/add.man.in29
-rwxr-xr-xlibxo/gen-wide.sh76
-rw-r--r--libxo/libxo.3313
-rw-r--r--libxo/libxo.c7659
-rw-r--r--libxo/xo.h596
-rw-r--r--libxo/xo_attr.360
-rw-r--r--libxo/xo_buf.h158
-rw-r--r--libxo/xo_config.h247
-rw-r--r--libxo/xo_create.367
-rw-r--r--libxo/xo_emit.3104
-rw-r--r--libxo/xo_emit_err.372
-rw-r--r--libxo/xo_encoder.c375
-rw-r--r--libxo/xo_encoder.h116
-rw-r--r--libxo/xo_err.374
-rw-r--r--libxo/xo_error.341
-rw-r--r--libxo/xo_finish.339
-rw-r--r--libxo/xo_flush.335
-rw-r--r--libxo/xo_format.5943
-rw-r--r--libxo/xo_humanize.h169
-rw-r--r--libxo/xo_message.368
-rw-r--r--libxo/xo_no_setlocale.343
-rw-r--r--libxo/xo_open_container.3188
-rw-r--r--libxo/xo_open_list.3158
-rw-r--r--libxo/xo_open_marker.3105
-rw-r--r--libxo/xo_parse_args.3148
-rw-r--r--libxo/xo_set_allocator.354
-rw-r--r--libxo/xo_set_flags.3139
-rw-r--r--libxo/xo_set_info.3102
-rw-r--r--libxo/xo_set_options.331
-rw-r--r--libxo/xo_set_style.353
-rw-r--r--libxo/xo_set_syslog_enterprise_id.336
-rw-r--r--libxo/xo_set_version.334
-rw-r--r--libxo/xo_set_writer.356
-rw-r--r--libxo/xo_syslog.379
-rw-r--r--libxo/xo_syslog.c706
-rw-r--r--libxo/xo_wcwidth.h313
-rw-r--r--m4/libtool.m48369
-rw-r--r--m4/ltoptions.m4437
-rw-r--r--m4/ltsugar.m4123
-rw-r--r--m4/ltversion.m423
-rw-r--r--m4/lt~obsolete.m498
-rw-r--r--packaging/libxo.pc.in11
-rw-r--r--packaging/libxo.rb.base.in20
-rw-r--r--packaging/libxo.spec.in44
-rw-r--r--tests/Makefile.am32
-rw-r--r--tests/core/Makefile.am129
-rw-r--r--tests/core/saved/test_01.E.err0
-rw-r--r--tests/core/saved/test_01.E.out119
-rw-r--r--tests/core/saved/test_01.H.err0
-rw-r--r--tests/core/saved/test_01.H.out1
-rw-r--r--tests/core/saved/test_01.HIPx.err0
-rw-r--r--tests/core/saved/test_01.HIPx.out303
-rw-r--r--tests/core/saved/test_01.HP.err0
-rw-r--r--tests/core/saved/test_01.HP.out303
-rw-r--r--tests/core/saved/test_01.J.err0
-rw-r--r--tests/core/saved/test_01.J.out2
-rw-r--r--tests/core/saved/test_01.JP.err0
-rw-r--r--tests/core/saved/test_01.JP.out106
-rw-r--r--tests/core/saved/test_01.T.err0
-rw-r--r--tests/core/saved/test_01.T.out47
-rw-r--r--tests/core/saved/test_01.X.err0
-rw-r--r--tests/core/saved/test_01.X.out1
-rw-r--r--tests/core/saved/test_01.XP.err0
-rw-r--r--tests/core/saved/test_01.XP.out96
-rw-r--r--tests/core/saved/test_01.err0
-rw-r--r--tests/core/saved/test_01.out38
-rw-r--r--tests/core/saved/test_02.E.err0
-rw-r--r--tests/core/saved/test_02.E.out68
-rw-r--r--tests/core/saved/test_02.H.err0
-rw-r--r--tests/core/saved/test_02.H.out7
-rw-r--r--tests/core/saved/test_02.HIPx.err0
-rw-r--r--tests/core/saved/test_02.HIPx.out225
-rw-r--r--tests/core/saved/test_02.HP.err0
-rw-r--r--tests/core/saved/test_02.HP.out225
-rw-r--r--tests/core/saved/test_02.J.err0
-rw-r--r--tests/core/saved/test_02.J.out2
-rw-r--r--tests/core/saved/test_02.JP.err0
-rw-r--r--tests/core/saved/test_02.JP.out82
-rw-r--r--tests/core/saved/test_02.T.err1
-rw-r--r--tests/core/saved/test_02.T.out37
-rw-r--r--tests/core/saved/test_02.X.err0
-rw-r--r--tests/core/saved/test_02.X.out7
-rw-r--r--tests/core/saved/test_02.XP.err0
-rw-r--r--tests/core/saved/test_02.XP.out87
-rw-r--r--tests/core/saved/test_02.err0
-rw-r--r--tests/core/saved/test_02.out38
-rw-r--r--tests/core/saved/test_03.E.err0
-rw-r--r--tests/core/saved/test_03.E.out22
-rw-r--r--tests/core/saved/test_03.H.err0
-rw-r--r--tests/core/saved/test_03.H.out1
-rw-r--r--tests/core/saved/test_03.HIPx.err0
-rw-r--r--tests/core/saved/test_03.HIPx.out21
-rw-r--r--tests/core/saved/test_03.HP.err0
-rw-r--r--tests/core/saved/test_03.HP.out21
-rw-r--r--tests/core/saved/test_03.J.err0
-rw-r--r--tests/core/saved/test_03.J.out2
-rw-r--r--tests/core/saved/test_03.JP.err0
-rw-r--r--tests/core/saved/test_03.JP.out21
-rw-r--r--tests/core/saved/test_03.T.err0
-rw-r--r--tests/core/saved/test_03.T.out3
-rw-r--r--tests/core/saved/test_03.X.err0
-rw-r--r--tests/core/saved/test_03.X.out1
-rw-r--r--tests/core/saved/test_03.XP.err0
-rw-r--r--tests/core/saved/test_03.XP.out17
-rw-r--r--tests/core/saved/test_03.err0
-rw-r--r--tests/core/saved/test_03.out3
-rw-r--r--tests/core/saved/test_04.E.err0
-rw-r--r--tests/core/saved/test_04.E.out22
-rw-r--r--tests/core/saved/test_04.H.err0
-rw-r--r--tests/core/saved/test_04.H.out1
-rw-r--r--tests/core/saved/test_04.HIPx.err0
-rw-r--r--tests/core/saved/test_04.HIPx.out20
-rw-r--r--tests/core/saved/test_04.HP.err0
-rw-r--r--tests/core/saved/test_04.HP.out20
-rw-r--r--tests/core/saved/test_04.J.err0
-rw-r--r--tests/core/saved/test_04.J.out2
-rw-r--r--tests/core/saved/test_04.JP.err0
-rw-r--r--tests/core/saved/test_04.JP.out21
-rw-r--r--tests/core/saved/test_04.T.err0
-rw-r--r--tests/core/saved/test_04.T.out4
-rw-r--r--tests/core/saved/test_04.X.err0
-rw-r--r--tests/core/saved/test_04.X.out1
-rw-r--r--tests/core/saved/test_04.XP.err0
-rw-r--r--tests/core/saved/test_04.XP.out17
-rw-r--r--tests/core/saved/test_05.E.err0
-rw-r--r--tests/core/saved/test_05.E.out96
-rw-r--r--tests/core/saved/test_05.H.err0
-rw-r--r--tests/core/saved/test_05.H.out1
-rw-r--r--tests/core/saved/test_05.HIPx.err0
-rw-r--r--tests/core/saved/test_05.HIPx.out212
-rw-r--r--tests/core/saved/test_05.HP.err0
-rw-r--r--tests/core/saved/test_05.HP.out212
-rw-r--r--tests/core/saved/test_05.J.err0
-rw-r--r--tests/core/saved/test_05.J.out3
-rw-r--r--tests/core/saved/test_05.JP.err0
-rw-r--r--tests/core/saved/test_05.JP.out92
-rw-r--r--tests/core/saved/test_05.T.err0
-rw-r--r--tests/core/saved/test_05.T.out39
-rw-r--r--tests/core/saved/test_05.X.err0
-rw-r--r--tests/core/saved/test_05.X.out1
-rw-r--r--tests/core/saved/test_05.XP.err0
-rw-r--r--tests/core/saved/test_05.XP.out85
-rw-r--r--tests/core/saved/test_06.E.err0
-rw-r--r--tests/core/saved/test_06.E.out22
-rw-r--r--tests/core/saved/test_06.H.err0
-rw-r--r--tests/core/saved/test_06.H.out1
-rw-r--r--tests/core/saved/test_06.HIPx.err0
-rw-r--r--tests/core/saved/test_06.HIPx.out21
-rw-r--r--tests/core/saved/test_06.HP.err0
-rw-r--r--tests/core/saved/test_06.HP.out21
-rw-r--r--tests/core/saved/test_06.J.err0
-rw-r--r--tests/core/saved/test_06.J.out2
-rw-r--r--tests/core/saved/test_06.JP.err0
-rw-r--r--tests/core/saved/test_06.JP.out21
-rw-r--r--tests/core/saved/test_06.T.err0
-rw-r--r--tests/core/saved/test_06.T.out3
-rw-r--r--tests/core/saved/test_06.X.err0
-rw-r--r--tests/core/saved/test_06.X.out1
-rw-r--r--tests/core/saved/test_06.XP.err0
-rw-r--r--tests/core/saved/test_06.XP.out17
-rw-r--r--tests/core/saved/test_07.E.err0
-rw-r--r--tests/core/saved/test_07.E.out76
-rw-r--r--tests/core/saved/test_07.H.err0
-rw-r--r--tests/core/saved/test_07.H.out1
-rw-r--r--tests/core/saved/test_07.HIPx.err0
-rw-r--r--tests/core/saved/test_07.HIPx.out107
-rw-r--r--tests/core/saved/test_07.HP.err0
-rw-r--r--tests/core/saved/test_07.HP.out107
-rw-r--r--tests/core/saved/test_07.J.err0
-rw-r--r--tests/core/saved/test_07.J.out2
-rw-r--r--tests/core/saved/test_07.JP.err0
-rw-r--r--tests/core/saved/test_07.JP.out71
-rw-r--r--tests/core/saved/test_07.T.err0
-rw-r--r--tests/core/saved/test_07.T.out19
-rw-r--r--tests/core/saved/test_07.X.err0
-rw-r--r--tests/core/saved/test_07.X.out1
-rw-r--r--tests/core/saved/test_07.XP.err0
-rw-r--r--tests/core/saved/test_07.XP.out65
-rw-r--r--tests/core/saved/test_08.E.err18
-rw-r--r--tests/core/saved/test_08.E.out186
-rw-r--r--tests/core/saved/test_08.H.err18
-rw-r--r--tests/core/saved/test_08.H.out1
-rw-r--r--tests/core/saved/test_08.HIPx.err18
-rw-r--r--tests/core/saved/test_08.HIPx.out264
-rw-r--r--tests/core/saved/test_08.HP.err18
-rw-r--r--tests/core/saved/test_08.HP.out264
-rw-r--r--tests/core/saved/test_08.J.err18
-rw-r--r--tests/core/saved/test_08.J.out2
-rw-r--r--tests/core/saved/test_08.JP.err18
-rw-r--r--tests/core/saved/test_08.JP.out185
-rw-r--r--tests/core/saved/test_08.T.err18
-rw-r--r--tests/core/saved/test_08.T.out52
-rw-r--r--tests/core/saved/test_08.X.err18
-rw-r--r--tests/core/saved/test_08.X.out1
-rw-r--r--tests/core/saved/test_08.XP.err18
-rw-r--r--tests/core/saved/test_08.XP.out165
-rw-r--r--tests/core/saved/test_09.E.err0
-rw-r--r--tests/core/saved/test_09.E.out40
-rw-r--r--tests/core/saved/test_09.H.err0
-rw-r--r--tests/core/saved/test_09.H.out1
-rw-r--r--tests/core/saved/test_09.HIPx.err0
-rw-r--r--tests/core/saved/test_09.HIPx.out93
-rw-r--r--tests/core/saved/test_09.HP.err0
-rw-r--r--tests/core/saved/test_09.HP.out93
-rw-r--r--tests/core/saved/test_09.J.err0
-rw-r--r--tests/core/saved/test_09.J.out2
-rw-r--r--tests/core/saved/test_09.JP.err0
-rw-r--r--tests/core/saved/test_09.JP.out39
-rw-r--r--tests/core/saved/test_09.T.err0
-rw-r--r--tests/core/saved/test_09.T.out25
-rw-r--r--tests/core/saved/test_09.X.err0
-rw-r--r--tests/core/saved/test_09.X.out1
-rw-r--r--tests/core/saved/test_09.XP.err0
-rw-r--r--tests/core/saved/test_09.XP.out29
-rw-r--r--tests/core/saved/test_10.E.err0
-rw-r--r--tests/core/saved/test_10.E.out126
-rw-r--r--tests/core/saved/test_10.H.err0
-rw-r--r--tests/core/saved/test_10.H.out1
-rw-r--r--tests/core/saved/test_10.HIPx.err0
-rw-r--r--tests/core/saved/test_10.HIPx.out316
-rw-r--r--tests/core/saved/test_10.HP.err0
-rw-r--r--tests/core/saved/test_10.HP.out316
-rw-r--r--tests/core/saved/test_10.J.err0
-rw-r--r--tests/core/saved/test_10.J.out2
-rw-r--r--tests/core/saved/test_10.JP.err0
-rw-r--r--tests/core/saved/test_10.JP.out113
-rw-r--r--tests/core/saved/test_10.T.err0
-rw-r--r--tests/core/saved/test_10.T.out48
-rw-r--r--tests/core/saved/test_10.X.err0
-rw-r--r--tests/core/saved/test_10.X.out1
-rw-r--r--tests/core/saved/test_10.XP.err0
-rw-r--r--tests/core/saved/test_10.XP.out100
-rw-r--r--tests/core/saved/test_10.err0
-rw-r--r--tests/core/saved/test_10.out38
-rw-r--r--tests/core/saved/test_11.E.err0
-rw-r--r--tests/core/saved/test_11.E.out26
-rw-r--r--tests/core/saved/test_11.H.err0
-rw-r--r--tests/core/saved/test_11.H.out16
-rw-r--r--tests/core/saved/test_11.HIPx.err0
-rw-r--r--tests/core/saved/test_11.HIPx.out16
-rw-r--r--tests/core/saved/test_11.HP.err0
-rw-r--r--tests/core/saved/test_11.HP.out16
-rw-r--r--tests/core/saved/test_11.J.err0
-rw-r--r--tests/core/saved/test_11.J.out18
-rw-r--r--tests/core/saved/test_11.JP.err0
-rw-r--r--tests/core/saved/test_11.JP.out22
-rw-r--r--tests/core/saved/test_11.T.err0
-rw-r--r--tests/core/saved/test_11.T.out16
-rw-r--r--tests/core/saved/test_11.X.err0
-rw-r--r--tests/core/saved/test_11.X.out17
-rw-r--r--tests/core/saved/test_11.XP.err0
-rw-r--r--tests/core/saved/test_11.XP.out18
-rw-r--r--tests/core/test_01.c177
-rw-r--r--tests/core/test_02.c147
-rw-r--r--tests/core/test_03.c61
-rw-r--r--tests/core/test_04.c63
-rw-r--r--tests/core/test_05.c143
-rw-r--r--tests/core/test_06.c63
-rw-r--r--tests/core/test_07.c97
-rw-r--r--tests/core/test_08.c157
-rw-r--r--tests/core/test_09.c114
-rw-r--r--tests/core/test_10.c212
-rw-r--r--tests/core/test_11.c109
-rw-r--r--tests/gettext/Makefile.am224
-rw-r--r--tests/gettext/gt_01.c115
-rw-r--r--tests/gettext/gt_01.pot105
-rw-r--r--tests/gettext/ldns.pot28
-rw-r--r--tests/gettext/po/pig_latin/gt_01.po109
-rw-r--r--tests/gettext/po/pig_latin/ldns.po30
-rw-r--r--tests/gettext/po/pig_latin/strerror.po459
-rw-r--r--tests/gettext/saved/gt_01.H.err0
-rw-r--r--tests/gettext/saved/gt_01.H.out1
-rw-r--r--tests/gettext/saved/gt_01.HIPx.err0
-rw-r--r--tests/gettext/saved/gt_01.HIPx.out139
-rw-r--r--tests/gettext/saved/gt_01.HP.err0
-rw-r--r--tests/gettext/saved/gt_01.HP.out139
-rw-r--r--tests/gettext/saved/gt_01.J.err0
-rw-r--r--tests/gettext/saved/gt_01.J.out2
-rw-r--r--tests/gettext/saved/gt_01.JP.err0
-rw-r--r--tests/gettext/saved/gt_01.JP.out53
-rw-r--r--tests/gettext/saved/gt_01.T.err0
-rw-r--r--tests/gettext/saved/gt_01.T.out17
-rw-r--r--tests/gettext/saved/gt_01.X.err0
-rw-r--r--tests/gettext/saved/gt_01.X.out1
-rw-r--r--tests/gettext/saved/gt_01.XP.err0
-rw-r--r--tests/gettext/saved/gt_01.XP.out49
-rw-r--r--tests/gettext/strerror.pot468
-rw-r--r--tests/xo/Makefile.am90
-rw-r--r--tests/xo/saved/xo_01.H.err0
-rw-r--r--tests/xo/saved/xo_01.H.out1
-rw-r--r--tests/xo/saved/xo_01.HIPx.err0
-rw-r--r--tests/xo/saved/xo_01.HIPx.out52
-rw-r--r--tests/xo/saved/xo_01.HP.err0
-rw-r--r--tests/xo/saved/xo_01.HP.out52
-rw-r--r--tests/xo/saved/xo_01.J.err0
-rw-r--r--tests/xo/saved/xo_01.J.out1
-rw-r--r--tests/xo/saved/xo_01.JP.err0
-rw-r--r--tests/xo/saved/xo_01.JP.out22
-rw-r--r--tests/xo/saved/xo_01.T.err0
-rw-r--r--tests/xo/saved/xo_01.T.out4
-rw-r--r--tests/xo/saved/xo_01.X.err0
-rw-r--r--tests/xo/saved/xo_01.X.out1
-rw-r--r--tests/xo/saved/xo_01.XP.err0
-rw-r--r--tests/xo/saved/xo_01.XP.out22
-rwxr-xr-xtests/xo/xo_01.sh27
-rw-r--r--warnings.mk57
-rw-r--r--xo/Makefile.am43
-rw-r--r--xo/xo.1173
-rw-r--r--xo/xo.c440
-rw-r--r--xohtml/Makefile.am42
-rw-r--r--xohtml/external/jquery.js9300
-rw-r--r--xohtml/external/jquery.qtip.css599
-rw-r--r--xohtml/external/jquery.qtip.js2656
-rw-r--r--xohtml/xohtml.199
-rw-r--r--xohtml/xohtml.css1040
-rw-r--r--xohtml/xohtml.js54
-rw-r--r--xohtml/xohtml.sh.in75
-rw-r--r--xolint/Makefile.am21
-rw-r--r--xolint/xolint.179
-rwxr-xr-xxolint/xolint.pl699
-rw-r--r--xopo/Makefile.am43
-rw-r--r--xopo/xopo.177
-rw-r--r--xopo/xopo.c292
345 files changed, 54139 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000000..1173578bbd5d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: c
+
+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:
+ - libslax-noise@googlegroups.com
+
+branches:
+ only:
+ - master
+ - develop
diff --git a/Copyright b/Copyright
new file mode 100644
index 000000000000..94ba75e4ffa2
--- /dev/null
+++ b/Copyright
@@ -0,0 +1,23 @@
+Copyright (c) 2014 Juniper Networks, Inc.
+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.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 000000000000..70b80bcea926
--- /dev/null
+++ b/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/LICENSE b/LICENSE
new file mode 100644
index 000000000000..874da7b2d8a8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2014, Juniper Networks
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* 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.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 000000000000..e050bc46f339
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,102 @@
+#
+# $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.
+
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = libxo xo xopo xolint xohtml tests doc encoder
+bin_SCRIPTS=libxo-config
+dist_doc_DATA = Copyright
+
+EXTRA_DIST = \
+ libxo-config.in \
+ warnings.mk \
+ README.md \
+ INSTALL.md \
+ packaging/libxo.spec
+
+.PHONY: test tests
+
+test tests:
+ @(cd tests ; ${MAKE} test)
+
+errors:
+ @(cd tests/errors ; ${MAKE} test)
+
+docs:
+ @(cd doc ; ${MAKE} docs)
+
+
+DIST_FILES_DIR = ~/Dropbox/dist-files/
+GH_PAGES_DIR = gh-pages/
+GH_PAGES_DIR_VER = gh-pages/${PACKAGE_VERSION}
+PACKAGE_FILE = ${PACKAGE_TARNAME}-${PACKAGE_VERSION}.tar.gz
+
+upload: dist upload-docs
+ @echo "Remember to run:"
+ @echo " gt tag ${PACKAGE_VERSION}"
+
+upload-docs: docs
+ @echo "Uploading libxo-manual.html ... "
+ @-[ -d ${GH_PAGES_DIR} ] \
+ && echo "Updating manual on gh-pages ..." \
+ && mkdir -p ${GH_PAGES_DIR_VER} \
+ && cp doc/libxo-manual.html ${GH_PAGES_DIR} \
+ && cp doc/libxo-manual.html ${GH_PAGES_DIR_VER} \
+ && (cd ${GH_PAGES_DIR} \
+ && git add ${PACKAGE_VERSION} \
+ && git add libxo-manual.html \
+ && git commit -m 'new docs' \
+ libxo-manual.html ${PACKAGE_VERSION} \
+ && git push origin gh-pages ) ; true
+
+pkgconfigdir=$(libdir)/pkgconfig
+pkgconfig_DATA = packaging/${PACKAGE_NAME}.pc
+
+get-wiki:
+ git clone https://github.com/Juniper/${PACKAGE_NAME}.wiki.git wiki
+
+get-gh-pages:
+ git clone https://github.com/Juniper/${PACKAGE_NAME}.git \
+ gh-pages -b gh-pages
+
+UPDATE_PACKAGE_FILE = \
+ -e "s;__SHA1__;$$SHA1;" \
+ -e "s;__SHA256__;SHA256 (textproc/${PACKAGE_FILE}) = $$SHA256;" \
+ -e "s;__SIZE__;SIZE (textproc/${PACKAGE_FILE}) = $$SIZE;"
+
+GH_PACKAGING_DIR = ${PACKAGE_VERSION}/packaging
+GH_PAGES_PACKAGE_DIR = ${GH_PAGES_DIR}/${GH_PACKAGING_DIR}
+
+packages:
+ @-[ -d ${GH_PAGES_DIR} ] && set -x \
+ && echo "Updating packages on gh-pages ..." \
+ && SHA1="`openssl sha1 ${PACKAGE_FILE} | awk '{print $$2}'`" \
+ && SHA256="`openssl sha256 ${PACKAGE_FILE} | awk '{print $$2}'`" \
+ && SIZE="`ls -l ${PACKAGE_FILE} | awk '{print $$5}'`" \
+ && echo "... ${GH_PAGES_PACKAGE_DIR}/${PACKAGE_NAME}.rb ..." \
+ && sed ${UPDATE_PACKAGE_FILE} \
+ packaging/${PACKAGE_NAME}.rb.base \
+ > ${GH_PAGES_PACKAGE_DIR}/${PACKAGE_NAME}.rb \
+ && echo "... ${GH_PAGES_PACKAGE_DIR}/${PACKAGE_NAME}.spec ..." \
+ && cp packaging/${PACKAGE_NAME}.spec \
+ ${GH_PAGES_PACKAGE_DIR}/${PACKAGE_NAME}.spec \
+ && (cd ${GH_PAGES_DIR} \
+ && git add ${GH_PACKAGING_DIR} \
+ && git add ${GH_PACKAGING_DIR}/libxo.rb \
+ ${GH_PACKAGING_DIR}/libxo.spec \
+ && 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/README.md b/README.md
new file mode 100644
index 000000000000..e9b3b4bd093e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+libxo
+=====
+
+libxo - A Library for Generating Text, XML, JSON, and HTML Output
+
+The libxo library allows an application to generate text, XML, JSON,
+and HTML output using a common set of function calls. The application
+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.
+
+```
+ xo_emit(" {:lines/%7ju/%ju} {:words/%7ju/%ju} "
+ "{:characters/%7ju/%ju}{d:filename/%s}\n",
+ linect, wordct, charct, file);
+```
+
+Output can then be generated in various style, using the "--libxo"
+option:
+
+```
+ % wc /etc/motd
+ 25 165 1140 /etc/motd
+ % wc --libxo xml,pretty,warn /etc/motd
+ <wc>
+ <file>
+ <filename>/etc/motd</filename>
+ <lines>25</lines>
+ <words>165</words>
+ <characters>1140</characters>
+ </file>
+ </wc>
+ % wc --libxo json,pretty,warn /etc/motd
+ {
+ "wc": {
+ "file": [
+ {
+ "filename": "/etc/motd",
+ "lines": 25,
+ "words": 165,
+ "characters": 1140
+ }
+ ]
+ }
+ }
+ % 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>
+```
+
+View the beautiful documentation at:
+
+http://juniper.github.io/libxo/libxo-manual.html
+
+[![Analytics](https://ga-beacon.appspot.com/UA-56056421-1/Juniper/libxo/Readme)](https://github.com/Juniper/libxo)
diff --git a/bin/Makefile.am b/bin/Makefile.am
new file mode 100644
index 000000000000..3bda1be4fd29
--- /dev/null
+++ b/bin/Makefile.am
@@ -0,0 +1,29 @@
+#
+# Copyright 2013, 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.
+
+ACLOCAL_AMFLAGS = -I m4
+
+EXTRA_DIST = gt setup.sh
+
+GT_INSTALL_DIR = ${prefix}/bin
+GT_INSTALL_FILES = gt
+
+install-data-hook:
+ @echo "Installing gt ... "
+ @-mkdir -p ${GT_INSTALL_DIR}
+ @for file in ${GT_INSTALL_FILES} ; do \
+ if [ -f $$file ]; then \
+ rfile=$$file ; \
+ else \
+ rfile=${srcdir}/$$file ; \
+ fi ; \
+ mdir=${GT_INSTALL_DIR}/ ; \
+ mkdir -p $$mdir ; \
+ cp $$rfile $$mdir/ ; \
+ done
+ @${CHMOD} a+x ${GT_INSTALL_DIR}/gt
diff --git a/bin/Zaliases b/bin/Zaliases
new file mode 100644
index 000000000000..04cdec7720b3
--- /dev/null
+++ b/bin/Zaliases
@@ -0,0 +1,29 @@
+set top_src=`pwd`
+alias Zautoreconf "(cd $top_src ; autoreconf)"
+
+set opts=' \
+--with-libslax-prefix=/Users/phil/work/root \
+--enable-debug \
+--enable-warnings \
+--enable-printflike \
+--with-gettext=/opt/local \
+--prefix ${HOME}/work/root \
+'
+set opts=`echo $opts`
+
+setenv CONFIGURE_OPTS "$opts"
+setenv ADB_PATH $top_src/build/libxo/.libs
+
+alias Zconfigure "(cd $top_src/build; ../configure $opts)"
+alias Zbuild "(cd $top_src/build; make \!* )"
+alias mi "(cd $top_src/build; make && make install); ."
+
+mkdir -p build
+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/bin/setup.sh b/bin/setup.sh
new file mode 100755
index 000000000000..f49dd48dd860
--- /dev/null
+++ b/bin/setup.sh
@@ -0,0 +1,33 @@
+#
+# Copyright 2013, 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 [ ! -f configure ]; then
+ vers=`autoreconf --version | head -1`
+ echo "Using" $vers
+
+ mkdir -p m4
+
+ autoreconf --install
+
+ if [ ! -f configure ]; then
+ echo "Failed to create configure script"
+ exit 1
+ fi
+fi
+
+echo "Creating build directory ..."
+mkdir build
+
+echo "Setup is complete. To build libslax:"
+
+echo " 1) Type 'cd build ; ../configure' to configure libslax"
+echo " 2) Type 'make' to build libslax"
+echo " 3) Type 'make install' to install libslax"
+
+exit 0
diff --git a/build/.create b/build/.create
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/build/.create
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 000000000000..1783120e4e57
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,452 @@
+#
+# $Id$
+#
+# See ./INSTALL for more info
+#
+
+#
+# Release numbering: even numbered dot releases are official ones, and
+# odd numbers are development ones. The svn version of this file will
+# only (ONLY!) ever (EVER!) contain odd numbers, so I'll always know if
+# a particular user has the dist or svn release.
+#
+
+AC_PREREQ(2.2)
+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.
+# Disable with "configure --disable-silent-rules" or "make V=1"
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+AC_PROG_CC
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_CONFIG_MACRO_DIR([m4])
+AC_PROG_LN_S
+
+# Must be after AC_PROG_AR
+LT_INIT([dlopen shared])
+
+AC_PATH_PROG(BASENAME, basename, /usr/bin/basename)
+AC_PATH_PROG(BISON, bison, /usr/bin/bison)
+AC_PATH_PROG(CAT, cat, /bin/cat)
+AC_PATH_PROG(CHMOD, chmod, /bin/chmod)
+AC_PATH_PROG(CP, cp, /bin/cp)
+AC_PATH_PROG(DIFF, diff, /usr/bin/diff)
+AC_PATH_PROG(MKDIR, mkdir, /bin/mkdir)
+AC_PATH_PROG(MV, mv, /bin/mv)
+AC_PATH_PROG(RM, rm, /bin/rm)
+AC_PATH_PROG(SED, sed, /bin/sed)
+
+AC_STDC_HEADERS
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_INLINE
+AC_TYPE_SIZE_T
+
+# Checks for library functions.
+AC_FUNC_ALLOCA
+AC_FUNC_MALLOC
+AC_FUNC_REALLOC
+AC_CHECK_FUNCS([bzero memmove strchr strcspn strerror strspn])
+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])
+AC_CHECK_FUNCS([dlfunc])
+
+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
+dnl
+AC_DEFUN([VERSION_TO_NUMBER],
+[`$1 | sed -e 's/lib.* //' | awk 'BEGIN { FS = "."; } { printf "%d", ([$]1 * 1000 + [$]2) * 1000 + [$]3;}'`])
+
+LIBSLAX_CONFIG_PREFIX=""
+LIBSLAX_SRC=""
+
+AC_ARG_WITH(libslax-prefix,
+ [ --with-libslax-prefix=[PFX] Specify location of libslax config],
+ LIBSLAX_CONFIG_PREFIX=$withval
+)
+
+AC_MSG_CHECKING(for libslax)
+if test "x$LIBSLAX_CONFIG_PREFIX" != "x"
+then
+ SLAX_CONFIG=${LIBSLAX_CONFIG_PREFIX}/bin/slax-config
+else
+ SLAX_CONFIG=slax-config
+fi
+
+dnl
+dnl make sure slax-config is executable,
+dnl test version and init our variables
+dnl
+
+if ${SLAX_CONFIG} --libs > /dev/null 2>&1
+then
+ LIBSLAX_VERSION=`$SLAX_CONFIG --version`
+ 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)
+
+AC_MSG_CHECKING([whether to build with warnings])
+AC_ARG_ENABLE([warnings],
+ [ --enable-warnings Turn on compiler warnings],
+ [LIBXO_WARNINGS=$enableval],
+ [LIBXO_WARNINGS=no])
+AC_MSG_RESULT([$LIBXO_WARNINGS])
+AM_CONDITIONAL([LIBXO_WARNINGS_HIGH], [test "$LIBXO_WARNINGS" != "no"])
+
+AC_MSG_CHECKING([whether to build with debugging])
+AC_ARG_ENABLE([debug],
+ [ --enable-debug Turn on debugging],
+ [LIBXO_DEBUG=yes; AC_DEFINE([LIBXO_DEBUG], [1], [Enable debugging])],
+ [LIBXO_DEBUG=no])
+AC_MSG_RESULT([$LIBXO_DEBUG])
+AM_CONDITIONAL([LIBXO_DEBUG], [test "$LIBXO_DEBUG" != "no"])
+
+AC_MSG_CHECKING([whether to build with text-only rendering])
+AC_ARG_ENABLE([text-only],
+ [ --enable-text-only Turn on text-only rendering],
+ [LIBXO_TEXT_ONLY=yes; AC_DEFINE([LIBXO_TEXT_ONLY], [1], [Enable text-only rendering])],
+ [LIBXO_TEXT_ONLY=no])
+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"])
+
+AC_MSG_CHECKING([compiler for gcc])
+HAVE_GCC=no
+if test "${CC}" != ""; then
+ HAVE_GCC=`${CC} --version 2>&1 | grep GCC`
+ if test "${HAVE_GCC}" != ""; then
+ HAVE_GCC=yes
+ else
+ HAVE_GCC=no
+ fi
+fi
+AC_MSG_RESULT([$HAVE_GCC])
+AM_CONDITIONAL([HAVE_GCC], [test "$HAVE_GCC" = "yes"])
+
+AC_MSG_CHECKING([whether to build with printflike])
+AC_ARG_ENABLE([printflike],
+ [ --enable-printflike Enable use of GCC __printflike attribute],
+ [HAVE_PRINTFLIKE=yes;
+ AC_DEFINE([HAVE_PRINTFLIKE], [1], [Support printflike])],
+ [HAVE_PRINTFLIKE=no])
+AC_MSG_RESULT([$HAVE_PRINTFLIKE])
+AM_CONDITIONAL([HAVE_PRINTFLIKE], [test "$HAVE_PRINTFLIKE" != ""])
+
+AC_MSG_CHECKING([whether to build with LIBXO_OPTIONS])
+AC_ARG_ENABLE([libxo-options],
+ [ --disable-libxo-options Turn off support for LIBXO_OPTIONS],
+ [LIBXO_OPTS=$enableval],
+ [LIBXO_OPTS=yes])
+AC_MSG_RESULT([$LIBXO_OPTS])
+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
+
+case $prefix in
+ NONE)
+ prefix=/usr/local
+ ;;
+esac
+
+XO_LIBS=-lxo
+XO_SRCDIR=${srcdir}
+XO_LIBDIR=${libdir}
+XO_BINDIR=${bindir}
+XO_INCLUDEDIR=${includedir}
+
+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],
+ [XO_SHAREDIR=$withval],
+ [XO_SHAREDIR=$datarootdir/libxo]
+)
+XO_SHAREDIR=`echo $XO_SHAREDIR | sed "s;\\${prefix};$prefix;"`
+AC_SUBST(XO_SHAREDIR)
+
+dnl for the spec file
+RELDATE=`date +'%Y-%m-%d%n'`
+AC_SUBST(RELDATE)
+
+AC_MSG_RESULT(Using configure dir $ac_abs_confdir)
+
+if test -d $ac_abs_confdir/.git ; then
+ extra=`git branch | awk '/\*/ { print $2 }'`
+ if test "$extra" != "" -a "$extra" != "master"
+ then
+ LIBXO_VERSION_EXTRA="-git-$extra"
+ fi
+fi
+
+LIBXO_VERSION=$PACKAGE_VERSION
+LIBXO_VERSION_NUMBER=VERSION_TO_NUMBER(echo $PACKAGE_VERSION)
+AC_SUBST(LIBXO_VERSION)
+AC_SUBST(LIBXO_VERSION_NUMBER)
+AC_SUBST(LIBXO_VERSION_EXTRA)
+
+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/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
+])
+AC_OUTPUT
+
+AC_MSG_NOTICE([summary of build options:
+
+ libxo version: ${VERSION} ${LIBXO_VERSION_EXTRA}
+ host type: ${host} / ${host_os}
+ install prefix: ${prefix}
+ srcdir: ${XO_SRCDIR}
+ libdir: ${XO_LIBDIR}
+ bindir: ${XO_BINDIR}
+ includedir: ${XO_INCLUDEDIR}
+ share dir: ${XO_SHAREDIR}
+ extensions dir: ${XO_ENCODERDIR}
+ oxtradoc dir: ${SLAX_OXTRADOCDIR}
+
+ compiler: ${CC} (${HAVE_GCC:-no})
+ compiler flags: ${CFLAGS}
+ library types: Shared=${enable_shared}, Static=${enable_static}
+
+ warnings: ${LIBXO_WARNINGS:-no}
+ debug: ${LIBXO_DEBUG:-no}
+ 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/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 000000000000..16d6ba5bffaf
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,70 @@
+#
+# $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.
+
+if HAVE_OXTRADOC
+OXTRADOC_DIR = ${SLAX_OXTRADOCDIR}
+OXTRADOC_PREFIX = ${OXTRADOC_DIR}
+OXTRADOC = ${OXTRADOC_DIR}/oxtradoc
+SLAXPROC_BINDIR = ${SLAX_BINDIR}
+
+XML2RFC = ${OXTRADOC_DIR}/xml2rfc.tcl
+XML2HTMLDIR = ${OXTRADOC_DIR}
+XML2HTMLBIN = ${XML2HTMLDIR}/rfc2629-to-html.slax
+SLAXPROC = ${SLAX_BINDIR}/slaxproc
+
+SLAXPROC_ARGS = \
+ -a oxtradoc-dir ${OXTRADOC_DIR} \
+ -a oxtradoc-install-dir ${OXTRADOC_DIR} \
+ -a anchor-prefix docs
+
+SLAXPROC_ARGS_INLINE = \
+ -a oxtradoc-inline yes
+
+SLAXPROC_ARGS += ${SLAXPROC_ARGS_INLINE}
+
+XML2HTML = \
+ ${SLAXPROC} -g -e -I ${OXTRADOC_DIR} -I . \
+ ${SLAXPROC_ARGS} \
+ ${XML2HTMLBIN}
+
+OX_ARGS = -P ${OXTRADOC_PREFIX} -L ${OXTRADOC_PREFIX}
+OX_ARGS += -S ${SLAXPROC} -p doc
+OX_CMD = ${PERL} ${PERLOPTS} ${OXTRADOC} ${OX_ARGS}
+OXTRADOC_CMD = ${OX_CMD}
+
+OUTPUT = libxo-manual
+INPUT = libxo
+
+EXTRA_DIST = \
+ ${INPUT}.txt \
+ ${OUTPUT}.html \
+ ${OUTPUT}.txt
+
+doc docs: ${OUTPUT}.txt ${OUTPUT}.html
+
+${OUTPUT}.txt: ${INPUT}.txt ${OXTRADOC} xolint.txt
+ ${OXTRADOC_CMD} -m text -o $@ $<
+
+${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 = \
+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/doc/libxo.txt b/doc/libxo.txt
new file mode 100644
index 000000000000..1e7acc7984be
--- /dev/null
+++ b/doc/libxo.txt
@@ -0,0 +1,3757 @@
+#
+# 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
+#
+
+* Overview
+
+libxo - A Library for Generating Text, XML, JSON, and HTML Output
+
+You want to prepare for the future, but you need to live in the
+present. You'd love a flying car, but need to get to work today. You
+want to support features like XML, JSON, and HTML rendering to allow
+integration with NETCONF, REST, and web browsers, but you need to make
+text output for command line users. And you don't want multiple code
+paths that can't help but get out of sync. None of this "if (xml)
+{... } else {...}" logic. And ifdefs are right out. But you'd
+really, really like all the fancy features that modern encoding
+formats can provide. libxo can help.
+
+The libxo library allows an application to generate text, XML, JSON,
+and HTML output using a common set of function calls. The application
+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 (^format-strings^):
+
+ xo_emit(" {:lines/%7ju} {:words/%7ju} "
+ "{:characters/%7ju} {d:filename/%s}\n",
+ linect, wordct, charct, file);
+
+Each field can have a role, with the 'value' role being the default,
+and the role tells libxo how and when to render that field. Output
+can then be generated in various style, using the "--libxo" option:
+
+ % 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>
+
+** Getting libxo
+
+libxo lives on github as:
+
+ https://github.com/Juniper/libxo
+
+The latest release of libxo is available at:
+
+ https://github.com/Juniper/libxo/releases
+
+We are following the branching scheme from
+^http://nvie.com/posts/a-successful-git-branching-model/^ which means
+we will do development under the "develop" branch, and release from
+the "master" branch. To clone a developer tree, run the following
+command:
+
+ git clone https://github.com/Juniper/libxo.git -b develop
+
+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 shipped
+as part of the FreeBSD operating system starting with release 11.0.
+
+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
+extracting details and pattern matching in such output. Often
+programmers need to extract information from this human-oriented
+output. Programmers use tools like grep, awk, and regular expressions
+to ferret out the pieces of information they need. Such solutions are
+fragile and require maintenance when output contents change or evolve,
+along with testing and validation.
+
+Modern tool developers favor encoding schemes like XML and JSON,
+which allow trivial parsing and extraction of data. Such formats are
+simple, well understood, hierarchical, easily parsed, and often
+integrate easier with common tools and environments. Changes to
+content can be done in ways that do not break existing users of the
+data, which can reduce maintenance costs and increase feature velocity.
+
+In addition, modern reality means that more output ends up in web
+browsers than in terminals, making HTML output valuable.
+
+libxo allows a single set of function calls in source code to generate
+traditional text output, as well as XML and JSON formatted data. HTML
+can also be generated; "<div>" elements surround the traditional text
+output, with attributes that detail how to render the data.
+
+A single libxo function call in source code is all that's required:
+
+ xo_emit("Connecting to {:host}.{:domain}...\n", host, domain);
+
+ TEXT:
+ Connecting to my-box.example.com...
+ XML:
+ <host>my-box</host>
+ <domain>example.com</domain>
+ JSON:
+ "host": "my-box",
+ "domain": "example.com"
+ 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 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
+
+Most traditional programs generate text output on standard output,
+with contents like:
+
+ 36 ./src
+ 40 ./bin
+ 90 .
+
+In this example (taken from du source code), the code to generate this
+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
+run time which to generate.
+
+libxo expands on the idea of printf format strings to make a single
+format containing instructions for creating multiple output styles:
+
+ xo_emit("{:blocks/%d}\t{:path/%s}\n", num_blocks, path);
+
+This line will generate the same text output as the earlier printf
+call, but also has enough information to generate XML, JSON, and HTML.
+
+The following sections introduce the other formats.
+
+*** XML Output
+
+XML output consists of a hierarchical set of elements, each encoded
+with a start tag and an end tag. The element should be named for data
+value that it is encoding:
+
+ <item>
+ <blocks>36</blocks>
+ <path>./src</path>
+ </item>
+ <item>
+ <blocks>40</blocks>
+ <path>./bin</path>
+ </item>
+ <item>
+ <blocks>90</blocks>
+ <path>.</path>
+ </item>
+
+XML is a W3C standard for encoding data. See w3c.org/TR/xml for
+additional information.
+
+*** JSON Output
+
+JSON output consists of a hierarchical set of objects and lists, each
+encoded with a quoted name, a colon, and a value. If the value is a
+string, it must be quoted, but numbers are not quoted. Objects are
+encoded using braces; lists are encoded using square brackets.
+Data inside objects and lists is separated using commas:
+
+ items: [
+ { "blocks": 36, "path" : "./src" },
+ { "blocks": 40, "path" : "./bin" },
+ { "blocks": 90, "path" : "./" }
+ ]
+
+*** HTML Output
+
+HTML output is designed to allow the output to be rendered in a web
+browser with minimal effort. Each piece of output data is rendered
+inside a <div> element, with a class name related to the role of the
+data. By using a small set of class attribute values, a CSS
+stylesheet can render the HTML into rich text that mirrors the
+traditional text content.
+
+Additional attributes can be enabled to provide more details about the
+data, including data type, description, and an XPath location.
+
+ <div class="line">
+ <div class="data" data-tag="blocks">36</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="path">./src</div>
+ </div>
+ <div class="line">
+ <div class="data" data-tag="blocks">40</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="path">./bin</div>
+ </div>
+ <div class="line">
+ <div class="data" data-tag="blocks">90</div>
+ <div class="padding"> </div>
+ <div class="data" data-tag="path">./</div>
+ </div>
+
+** Format Strings @format-strings@
+
+libxo uses format strings to control the rendering of data into the
+various output styles. Each format string contains a set of zero or
+more field descriptions, which describe independent data fields. Each
+field description contains a set of modifiers, a content string, and
+zero, one, or two format descriptors. The modifiers tell libxo what
+the field is and how to treat it, while the format descriptors are
+formatting instructions using printf-style format strings, telling
+libxo how to format the field. The field description is placed inside
+a set of braces, with a colon (":") after the modifiers and a slash
+("/") before each format descriptors. Text may be intermixed with
+field descriptions within the format string.
+
+The field description is given as follows:
+
+ '{' [ role | modifier ]* [',' long-names ]* ':' [ content ]
+ [ '/' field-format [ '/' encoding-format ]] '}'
+
+The role describes the function of the field, while the modifiers
+enable optional behaviors. The contents, field-format, and
+encoding-format are used in varying ways, based on the role. These
+are described in the following sections.
+
+In the following example, three field descriptors appear. The first
+is a padding field containing three spaces of padding, the second is a
+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);
+
+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).
+
+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);
+
+*** 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:
+
+|---+--------------+-------------------------------------------------|
+| R | Name | Description |
+|---+--------------+-------------------------------------------------|
+| 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 |
+| T | title | Field is a title value for headings |
+| 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 |
+|---+--------------+-------------------------------------------------|
+
+ 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).
+
+ 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:/fg-%s,bg-%s}{Lwc:Cost}{:cost/%u}{C:reset}\n",
+ fg_color, bg_color, cost);
+
+The following table lists the supported effects:
+
+|---------------+-------------------------------------------------|
+| Name | Description |
+|---------------+-------------------------------------------------|
+| bg-XXXXX | Change background color |
+| bold | Start bold text effect |
+| fg-XXXXX | Change foreground color |
+| inverse | Start inverse (aka reverse) text effect |
+| no-bold | Stop bold text effect |
+| no-inverse | Stop inverse (aka reverse) text effect |
+| no-underline | Stop underline text effect |
+| normal | Reset effects (only) |
+| reset | Reset colors and effects (restore defaults) |
+| underline | Start underline text effect |
+|---------------+-------------------------------------------------|
+
+The following color names are supported:
+
+|---------+--------------------------------------------|
+| Name | Description |
+|---------+--------------------------------------------|
+| black | |
+| blue | |
+| cyan | |
+| default | Default color for foreground or background |
+| green | |
+| magenta | |
+| red | |
+| white | |
+| yellow | |
+|---------+--------------------------------------------|
+
+**** 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. By marking these distinctly, HTML usage scenarios
+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.
+
+ xo_emit("{Lwc:Cost}{:cost/%u}\n", cost);
+
+**** The Note Role ({N:})
+
+Notes are text that appears after a value.
+
+ xo_emit("{:cost/%u} {N:per year}\n", cost);
+
+**** The Padding Role ({P:}) @padding-role@
+
+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,
+if preceded by a slash ("/"):
+
+ xo_emit("{P: }{Lwc:Cost}{:cost/%u}\n", cost);
+ xo_emit("{P:/%30s}{Lwc:Cost}{:cost/%u}\n", "", cost);
+
+**** The Title Role ({T:})
+
+Title are heading or column headers that are meant to be displayed to
+the user. The title 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("{T:Interface Statistics}\n");
+ xo_emit("{T:/%20.20s}{T:/%6.6s}\n", "Item Name", "Cost");
+
+Title fields have an extra convenience feature; if both content and
+format are specified, instead of looking to the argument list for a
+value, the content is used, allowing a mixture of format and content
+within the field descriptor:
+
+ xo_emit("{T:Name/%20s}{T:Count/%6s}\n");
+
+Since the incoming argument is a string, the format must be "%s" or
+something suitable.
+
+**** 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
+for the previous value field.
+
+ xo_emit("{Lwc:Distance}{:distance/%u}{Uw:miles}\n", miles);
+
+Note that the sense of the 'w' modifier is reversed for units;
+a blank is added before the contents, rather than after it.
+
+When the XOF_UNITS flag is set, units are rendered in XML as the
+"units" attribute:
+
+ <distance units="miles">50</distance>
+
+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>
+
+**** 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
+is the default role; if no other role designation is given, the field
+is a value. The field name must appear within the field descriptor,
+followed by one or two format descriptors. The first format
+descriptor is used for display styles (TEXT and HTML), while the
+second one is used for encoding styles (XML and JSON). If no second
+format is given, the encoding format defaults to the first format,
+with any minimum width removed. If no first format is given, both
+format descriptors default to "%s".
+
+ xo_emit("{:length/%02u}x{:width/%02u}x{:height/%02u}\n",
+ length, width, height);
+ xo_emit("{:author} wrote \"{:poem}\" in {:year/%4d}\n,
+ author, poem, year);
+
+**** 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
+or stop anchor can give a field width and it can be either directly in
+the descriptor or passed as an argument. Any fields between the start
+and stop anchor are padded to meet the minimum width given.
+
+To give a width directly, encode it as the content of the anchor tag:
+
+ xo_emit("({[:10}{:min/%d}/{:max/%d}{]:})\n", min, max);
+
+To pass a width as an argument, use "%d" as the format, which must
+appear after the "/". Note that only "%d" is supported for widths.
+Using any other value could ruin your day.
+
+ xo_emit("({[:/%d}{:min/%d}/{:max/%d}{]:})\n", width, min, max);
+
+If the width is negative, padding will be added on the right, suitable
+for left justification. Otherwise the padding will be added to the
+left of the fields between the start and stop anchors, suitable for
+right justification. If the width is zero, nothing happens. If the
+number of columns of output between the start and stop anchors is less
+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.
+
+*** 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:})
+
+The colon modifier appends a single colon to the data value:
+
+ EXAMPLE:
+ xo_emit("{Lc:Name}{:name}\n", "phil");
+ TEXT:
+ Name:phil
+
+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.
+
+**** The Display Modifier ({d:})
+
+The display modifier indicated the field should only be generated for
+the display output styles, TEXT and HTML.
+
+ EXAMPLE:
+ xo_emit("{Lcw:Name}{d:name} {:id/%d}\n", "phil", 1);
+ TEXT:
+ Name: phil 1
+ XML:
+ <id>1</id>
+
+The display modifier is the opposite of the encoding modifier, and
+they are often used to give to distinct views of the underlying data.
+
+**** The Encoding Modifier ({e:}) @e-modifier@
+
+The display modifier indicated the field should only be generated for
+the display output styles, TEXT and HTML.
+
+ EXAMPLE:
+ xo_emit("{Lcw:Name}{:name} {e:id/%d}\n", "phil", 1);
+ TEXT:
+ Name: phil
+ XML:
+ <name>phil</name><id>1</id>
+
+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
+uniquely identify an instance of list data.
+
+ EXAMPLE:
+ xo_open_list("user");
+ for (i = 0; i < num_users; i++) {
+ xo_open_instance("user");
+ xo_emit("User {k:name} has {:count} tickets\n",
+ user[i].u_name, user[i].u_tickets);
+ xo_close_instance("user");
+ }
+ xo_close_list("user");
+
+Currently the key modifier is only used when generating XPath value
+for the HTML output style when XOF_XPATH is set, but other uses are
+likely in the near future.
+
+**** 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.
+
+ EXAMPLE:
+ for (i = 0; i < num_users; i++) {
+ xo_emit("Member {l:user}\n", user[i].u_name);
+ }
+ XML:
+ <user>phil</user>
+ <user>pallavi</user>
+ JSON:
+ "user": [ "phil", "pallavi" ]
+
+The name of the field must match the name of the leaf list.
+
+**** 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
+string value, but no quotes for numeric, boolean, and null data.
+xo_emit applies a simple heuristic to determine whether quotes are
+needed, but often this needs to be controlled by the caller.
+
+ EXAMPLE:
+ const char *bool = is_true ? "true" : "false";
+ xo_emit("{n:fancy/%s}", bool);
+ 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
+the quoting of values in the JSON output style. JSON uses quotes for
+string value, but no quotes for numeric, boolean, and null data.
+xo_emit applies a simple heuristic to determine whether quotes are
+needed, but often this needs to be controlled by the caller.
+
+ EXAMPLE:
+ xo_emit("{q:time/%d}", 2014);
+ JSON:
+ "year": "2014"
+
+**** The White Space Modifier ({w:})
+
+The white space modifier appends a single space to the data value:
+
+ EXAMPLE:
+ xo_emit("{Lw:Name}{:name}\n", "phil");
+ TEXT:
+ Name phil
+
+The white space modifier is only used for the TEXT and HTML output
+styles. It is commonly combined with the colon modifier ('{c:}').
+It is purely a convenience feature.
+
+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.
+
+*** Field Formatting
+
+The field format is similar to the format string for printf(3). Its
+use varies based on the role of the field, but generally is used to
+format the field's contents.
+
+If the format string is not provided for a value field, it defaults to
+"%s".
+
+Note a field definition can contain zero or more printf-style
+'directives', which are sequences that start with a '%' and end with
+one of following characters: "diouxXDOUeEfFgGaAcCsSp". Each directive
+is matched by one of more arguments to the xo_emit function.
+
+The format string has the form:
+
+ '%' format-modifier * format-character
+
+The format- modifier can be:
+- a '#' character, indicating the output value should be prefixed with
+'0x', typically to indicate a base 16 (hex) value.
+- a minus sign ('-'), indicating the output value should be padded on
+the right instead of the left.
+- a leading zero ('0') indicating the output value should be padded on the
+left with zeroes instead of spaces (' ').
+- one or more digits ('0' - '9') indicating the minimum width of the
+argument. If the width in columns of the output value is less that
+the minumum width, the value will be padded to reach the minimum.
+- a period followed by one or more digits indicating the maximum
+number of bytes which will be examined for a string argument, or the maximum
+width for a non-string argument. When handling ASCII strings this
+functions as the field width but for multi-byte characters, a single
+character may be composed of multiple bytes.
+xo_emit will never dereference memory beyond the given number of bytes.
+- 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.
+- one or more 'h' characters, indicating shorter input data.
+- one or more 'l' characters, indicating longer input data.
+- a 'z' character, indicating a 'size_t' argument.
+- a 't' character, indicating a 'ptrdiff_t' argument.
+- a ' ' character, indicating a space should be emitted before
+positive numbers.
+- a '+' character, indicating sign should emitted before any number.
+
+Note that 'q', 'D', 'O', and 'U' are considered deprecated and will be
+removed eventually.
+
+The format character is described in the following table:
+
+|-----+-----------------+----------------------|
+| Ltr | Argument Type | Format |
+|-----+-----------------+----------------------|
+| d | int | base 10 (decimal) |
+| i | int | base 10 (decimal) |
+| o | int | base 8 (octal) |
+| u | unsigned | base 10 (decimal) |
+| x | unsigned | base 16 (hex) |
+| X | unsigned long | base 16 (hex) |
+| D | long | base 10 (decimal) |
+| O | unsigned long | base 8 (octal) |
+| U | unsigned long | base 10 (decimal) |
+| e | double | [-]d.ddde+-dd |
+| E | double | [-]d.dddE+-dd |
+| f | double | [-]ddd.ddd |
+| F | double | [-]ddd.ddd |
+| g | double | as 'e' or 'f' |
+| G | double | as 'E' or 'F' |
+| a | double | [-]0xh.hhhp[+-]d |
+| A | double | [-]0Xh.hhhp[+-]d |
+| c | unsigned char | a character |
+| C | wint_t | a character |
+| s | char * | a UTF-8 string |
+| S | wchar_t * | a unicode/WCS string |
+| p | void * | '%#lx' |
+|-----+-----------------+----------------------|
+
+The 'h' and 'l' modifiers affect the size and treatment of the
+argument:
+
+|-----+-------------+--------------------|
+| Mod | d, i | o, u, x, X |
+|-----+-------------+--------------------|
+| hh | signed char | unsigned char |
+| h | short | unsigned short |
+| l | long | unsigned long |
+| ll | long long | unsigned long long |
+| j | intmax_t | uintmax_t |
+| t | ptrdiff_t | ptrdiff_t |
+| z | size_t | size_t |
+| q | quad_t | u_quad_t |
+|-----+-------------+--------------------|
+
+*** UTF-8 and Locale Strings
+
+For strings, the 'h' and 'l' modifiers affect the interpretation of
+the bytes pointed to argument. The default '%s' string is a 'char *'
+pointer to a string encoded as UTF-8. Since UTF-8 is compatible with
+ASCII data, a normal 7-bit ASCII string can be used. '%ls' expects a
+'wchar_t *' pointer to a wide-character string, encoded as a 32-bit
+Unicode values. '%hs' expects a 'char *' pointer to a multi-byte
+string encoded with the current locale, as given by the LC_CTYPE,
+LANG, or LC_ALL environment varibles. The first of this list of
+variables is used and if none of the variables are set, the locale
+defaults to "UTF-8".
+
+For example, a function is passed a locale-base name, a hat size,
+and a time value. The hat size is formatted in a UTF-8 (ASCII)
+string, and the time value is formatted into a wchar_t string.
+
+ void print_order (const char *name, int size,
+ struct tm *timep) {
+ char buf[32];
+ const char *size_val = "unknown";
+
+ if (size > 0)
+ snprintf(buf, sizeof(buf), "%d", size);
+ size_val = buf;
+ }
+
+ wchar_t when[32];
+ wcsftime(when, sizeof(when), L"%d%b%y", timep);
+
+ xo_emit("The hat for {:name/%hs} is {:size/%s}.\n",
+ name, size_val);
+ xo_emit("It was ordered on {:order-time/%ls}.\n",
+ when);
+ }
+
+It is important to note that xo_emit will perform the conversion
+required to make appropriate output. Text style output uses the
+current locale (as described above), while XML, JSON, and HTML use
+UTF-8.
+
+UTF-8 and locale-encoded strings can use multiple bytes to encode one
+column of data. The traditional "precision'" (aka "max-width") value
+for "%s" printf formatting becomes overloaded since it specifies both
+the number of bytes that can be safely referenced and the maximum
+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 of data at the location given by
+'name' are in used in filling those columns.
+
+ xo_emit("{:name/%3.10.6s}", name);
+
+*** 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. For HTML, these characters are
+placed in a <div> with class "text".
+
+ EXAMPLE:
+ xo_emit("The hat is {:size/%s}.\n", size_val);
+ TEXT:
+ The hat is extra small.
+ XML:
+ <size>extra small</size>
+ JSON:
+ "size": "extra small"
+ HTML:
+ <div class="text">The hat is </div>
+ <div class="data" data-tag="size">extra small</div>
+ <div class="text">.</div>
+
+*** "%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.
+
+*** 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
+with any minimum width removed. If the primary is not given, both
+default to "%s".
+
+*** Content Strings
+
+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:
+
+ xo_emit("{P: }{Lwc:In stock}{:in-stock/%u}\n",
+ instock);
+
+This call will generate the following output:
+
+ TEXT:
+ In stock: 144
+ XML:
+ <in-stock>144</in-stock>
+ JSON:
+ "in-stock": 144,
+ HTML:
+ <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>
+
+Clearly HTML wins the verbosity award, and this output does
+not include XOF_XPATH or XOF_INFO data, which would expand the
+penultimate line to:
+
+ <div class="data" data-tag="in-stock"
+ data-xpath="/top/data/item/in-stock"
+ data-type="number"
+ data-help="Number of items in stock">144</div>
+
+** Command-line Arguments
+
+libxo uses command line options to trigger rendering behavior. The
+following options are recognised:
+
+- --libxo <options>
+- --libxo=<options>
+- --libxo:<brief-options>
+
+Options is a comma-separated list of tokens that correspond to output
+styles, flags, or features:
+
+|-------------+-------------------------------------------------------|
+| 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^.
+
+** Representing Hierarchy
+
+For XML and JSON, individual fields appear inside hierarchies which
+provide context and meaning to the fields. Unfortunately, these
+encoding have a basic disconnect between how lists is similar objects
+are represented.
+
+XML encodes lists as set of sequential elements:
+
+ <user>phil</user>
+ <user>pallavi</user>
+ <user>sjg</user>
+
+JSON encodes lists using a single name and square brackets:
+
+ "user": [ "phil", "pallavi", "sjg" ]
+
+This means libxo needs three distinct indications of hierarchy: one
+for containers of hierarchy appear only once for any specific parent,
+one for lists, and one for each item in a list.
+
+*** Containers
+
+A "container" is an element of a hierarchy that appears only once
+under any specific parent. The container has no value, but serves to
+contain other nodes.
+
+To open a container, call xo_open_container() or
+xo_open_container_h(). The former uses the default handle and
+the latter accepts a specific handle.
+
+ int xo_open_container_h (xo_handle_t *xop, const char *name);
+ int xo_open_container (const char *name);
+
+To close a level, use the xo_close_container() or
+xo_close_container_h() functions:
+
+ int xo_close_container_h (xo_handle_t *xop, const char *name);
+ int xo_close_container (const char *name);
+
+Each open call must have a matching close call. If the XOF_WARN flag
+is set and the name given does not match the name of the currently open
+container, a warning will be generated.
+
+ Example:
+
+ xo_open_container("top");
+ xo_open_container("system");
+ xo_emit("{:host-name/%s%s%s", hostname,
+ domainname ? "." : "", domainname ?: "");
+ xo_close_container("system");
+ xo_close_container("top");
+
+ Sample Output:
+ Text:
+ my-host.example.org
+ XML:
+ <top>
+ <system>
+ <host-name>my-host.example.org</host-name>
+ </system>
+ </top>
+ JSON:
+ "top" : {
+ "system" : {
+ "host-name": "my-host.example.org"
+ }
+ }
+ HTML:
+ <div class="data"
+ data-tag="host-name">my-host.example.org</div>
+
+*** Lists and Instances
+
+A list is set of one or more instances that appear under the same
+parent. The instances contain details about a specific object. One
+can think of instances as objects or records. A call is needed to
+open and close the list, while a distinct call is needed to open and
+close each instance of the list:
+
+ xo_open_list("item");
+
+ for (ip = list; ip->i_title; ip++) {
+ xo_open_instance("item");
+ xo_emit("{L:Item} '{:name/%s}':\n", ip->i_title);
+ xo_close_instance("item");
+ }
+
+ xo_close_list("item");
+
+Getting the list and instance calls correct is critical to the proper
+generation of XML and JSON data.
+
+*** DTRT Mode
+
+Some users may find tracking the names of open containers, lists, and
+instances inconvenient. libxo offers a "Do The Right Thing" mode, where
+libxo will track the names of open containers, lists, and instances so
+the close function can be called without a name. To enable DTRT mode,
+turn on the XOF_DTRT flag prior to making any other libxo output.
+
+ xo_set_flags(NULL, XOF_DTRT);
+
+Each open and close function has a version with the suffix "_d", which
+will close the open container, list, or instance:
+
+ xo_open_container("top");
+ ...
+ xo_close_container_d();
+
+This also works for lists and instances:
+
+ xo_open_list("item");
+ for (...) {
+ xo_open_instance("item");
+ xo_emit(...);
+ xo_close_instance_d();
+ }
+ xo_close_list_d();
+
+Note that the XOF_WARN flag will also cause libxo 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.
+
+*** Markers
+
+Markers are used to protect and restore the state of open constructs.
+While a marker is open, no other open constructs can be closed. When
+a marker is closed, all constructs open since the marker was opened
+will be closed.
+
+Markers use names which are not user-visible, allowing the caller to
+choose appropriate internal names.
+
+In this example, the code whiffles through a list of fish, calling a
+function to emit details about each fish. The marker "fish-guts" is
+used to ensure that any constructs opened by the function are closed
+properly.
+
+ for (i = 0; fish[i]; i++) {
+ xo_open_instance("fish");
+ xo_open_marker("fish-guts");
+ dump_fish_details(i);
+ xo_close_marker("fish-guts");
+ }
+
+** Handles @handles@
+
+libxo uses "handles" to control its rendering functionality. The
+handle contains state and buffered data, as well as callback functions
+to process data.
+
+A default handle is used when a NULL is passed to functions accepting
+a handle. This handle is initialized to write its data to stdout
+using the default style of text (XO_STYLE_TEXT).
+
+For the convenience of callers, the libxo library includes handle-less
+functions that implicitly use the default handle. Any function that
+takes a handle will use the default handle is a value of NULL is
+passed in place of a valid handle.
+
+For example, the following are equivalent:
+
+ xo_emit("test");
+ xo_emit_h(NULL, "test");
+
+Handles are created using xo_create() and destroy using xo_destroy().
+
+** UTF-8
+
+All strings for libxo must be UTF-8. libxo will handle turning them
+into locale-based strings for display to the user.
+
+The only exception is argument formatted using the "%ls" format, which
+require a wide character string (wchar_t *) as input. libxo will
+convert these arguments as needed to either UTF-8 (for XML, JSON, and
+HTML styles) or locale-based strings for display in text style.
+
+ xo_emit("Alll strings are utf-8 content {:tag/%ls}",
+ L"except for wide strings");
+
+"%S" is equivalent to "%ls".
+
+* The libxo API
+
+This section gives details about the functions in libxo, how to call
+them, and the actions they perform.
+
+** Handles
+
+Handles give an abstraction for libxo that encapsulates the state of a
+stream of output. Handles have the data type "xo_handle_t" and are
+opaque to the caller.
+
+The library has a default handle that is automatically initialized.
+By default, this handle will send text style output to standard output.
+The xo_set_style and xo_set_flags functions can be used to change this
+behavior.
+
+Many libxo functions take a handle as their first parameter; most that
+do not use the default handle. Any function taking a handle can
+be passed NULL to access the default handle.
+
+For the typical command that is generating output on standard output,
+there is no need to create an explicit handle, but they are available
+when needed, e.g., for daemons that generate multiple streams of
+output.
+
+*** xo_create
+
+A handle can be allocated using the xo_create() function:
+
+ xo_handle_t *xo_create (unsigned style, unsigned flags);
+
+ Example:
+ xo_handle_t *xop = xo_create(XO_STYLE_JSON, XOF_WARN);
+ ....
+ xo_emit_h(xop, "testing\n");
+
+See also ^styles^ and ^flags^.
+
+*** xo_create_to_file
+
+By default, libxo writes output to standard output. A convenience
+function is provided for situations when output should be written to
+a different file:
+
+ xo_handle_t *xo_create_to_file (FILE *fp, unsigned style,
+ unsigned flags);
+
+Use the XOF_CLOSE_FP flag to trigger a call to fclose() for
+the FILE pointer when the handle is destroyed.
+
+*** xo_set_writer
+
+The xo_set_writer function allows custom 'write' functions
+which can tailor how libxo writes data. An opaque argument is
+recorded and passed back to the write function, allowing the function
+to acquire context information. The 'close' function can
+release this opaque data and any other resources as needed.
+The flush function can flush buffered data associated with the opaque
+object.
+
+ 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);
+
+*** xo_set_style
+
+To set the style, use the xo_set_style() function:
+
+ void xo_set_style(xo_handle_t *xop, unsigned style);
+
+To use the default handle, pass a NULL handle:
+
+ xo_set_style(NULL, XO_STYLE_XML);
+
+**** Output Styles (XO_STYLE_*) @styles@
+
+The libxo functions accept a set of output styles:
+
+|---------------+-------------------------|
+| Flag | Description |
+|---------------+-------------------------|
+| XO_STYLE_TEXT | Traditional text output |
+| XO_STYLE_XML | XML encoded data |
+| XO_STYLE_JSON | JSON encoded data |
+| XO_STYLE_HTML | HTML encoded data |
+|---------------+-------------------------|
+
+**** xo_set_style_name
+
+The xo_set_style_name() can be used to set the style based on a name
+encoded as a string:
+
+ int xo_set_style_name (xo_handle_t *xop, const char *style);
+
+The name can be any of the styles: "text", "xml", "json", or "html".
+
+ EXAMPLE:
+ xo_set_style_name(NULL, "html");
+
+*** xo_set_flags
+
+To set the flags, use the xo_set_flags() function:
+
+ void xo_set_flags(xo_handle_t *xop, unsigned flags);
+
+To use the default handle, pass a NULL handle:
+
+ xo_set_style(NULL, XO_STYLE_XML);
+
+**** Flags (XOF_*) @flags@
+
+The set of valid flags include:
+
+|-------------------+----------------------------------------|
+| 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.
+
+The XOF_WARN flag requests that warnings will trigger diagnostic
+output (on standard error) when the library notices errors during
+operations, or with arguments to functions. Without warnings enabled,
+such conditions are ignored.
+
+Warnings allow developers to debug their interaction with libxo.
+The function "xo_failure" can used as a breakpoint for a debugger,
+regardless of whether warnings are enabled.
+
+If the style is XO_STYLE_HTML, the following additional flags can be
+used:
+
+|---------------+-----------------------------------------|
+| Flag | Description |
+|---------------+-----------------------------------------|
+| XOF_XPATH | Emit "data-xpath" attributes |
+| XOF_INFO | Emit additional info fields |
+|---------------+-----------------------------------------|
+
+The XOF_XPATH flag enables the emission of XPath expressions detailing
+the hierarchy of XML elements used to encode the data field, if the
+XPATH style of output were requested.
+
+The XOF_INFO flag encodes additional informational fields for HTML
+output. See ^info^ for details.
+
+If the style is XO_STYLE_XML, the following additional flags can be
+used:
+
+|---------------+-----------------------------------------|
+| Flag | Description |
+|---------------+-----------------------------------------|
+| XOF_KEYS | Flag 'key' fields for xml |
+|---------------+-----------------------------------------|
+
+The XOF_KEYS flag adds 'key' attribute to the XML encoding for
+field definitions that use the 'k' modifier. The key attribute has
+the value "key":
+
+ xo_emit("{k:name}", item);
+
+ XML:
+ <name key="key">truck</name>
+
+**** xo_clear_flags
+
+The xo_clear_flags() function turns off the given flags in a specific
+handle.
+
+ void xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags);
+
+**** xo_set_options
+
+The xo_set_options() function accepts a comma-separated list of styles
+and flags and enables them for a specific handle.
+
+ int xo_set_options (xo_handle_t *xop, const char *input);
+
+The options are identical to those listed in ^command-line-arguments^.
+
+*** xo_destroy
+
+The xo_destroy function releases a handle and any resources it is
+using. Calling xo_destroy with a NULL handle will release any
+resources associated with the default handle.
+
+ void xo_destroy(xo_handle_t *xop);
+
+** Emitting Content (xo_emit)
+
+The following functions are used to emit output:
+
+ int xo_emit (const char *fmt, ...);
+ int xo_emit_h (xo_handle_t *xop, const char *fmt, ...);
+ int xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap);
+
+The "fmt" argument is a string containing field descriptors as
+specified in ^format-strings^. The use of a handle is optional and
+NULL can be passed to access the internal 'default' handle. See
+^handles^.
+
+The remaining arguments to xo_emit() and xo_emit_h() are a set of
+arguments corresponding to the fields in the format string. Care must
+be taken to ensure the argument types match the fields in the format
+string, since an inappropriate cast can ruin your day. The vap
+argument to xo_emit_hv() points to a variable argument list that can
+be used to retrieve arguments via va_arg().
+
+*** Attributes (xo_attr) @xo_attr@
+
+The xo_attr() function emits attributes for the XML output style.
+
+
+ int xo_attr (const char *name, const char *fmt, ...);
+ int xo_attr_h (xo_handle_t *xop, const char *name,
+ const char *fmt, ...);
+ int xo_attr_hv (xo_handle_t *xop, const char *name,
+ const char *fmt, va_list vap);
+
+The name parameter give the name of the attribute to be encoded. The
+fmt parameter gives a printf-style format string used to format the
+value of the attribute using any remaining arguments, or the vap
+parameter passed to xo_attr_hv().
+
+ EXAMPLE:
+ xo_attr("seconds", "%ld", (unsigned long) login_time);
+ struct tm *tmp = localtime(login_time);
+ strftime(buf, sizeof(buf), "%R", tmp);
+ xo_emit("Logged in at {:login-time}\n", buf);
+ XML:
+ <login-time seconds="1408336270">00:14</login-time>
+
+xo_attr is placed on the next container, instance, leaf, or leaf list
+that is emitted.
+
+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.
+
+*** Flushing Output (xo_flush)
+
+libxo buffers data, both for performance and consistency, but also to
+allow some advanced features to work properly. At various times, the
+caller may wish to flush any data buffered within the library. The
+xo_flush() call is used for this:
+
+ void xo_flush (void);
+ void xo_flush_h (xo_handle_t *xop);
+
+Calling xo_flush also triggers the flush function associated with the
+handle. For the default handle, this is equivalent to
+"fflush(stdio);".
+
+*** Finishing Output (xo_finish)
+
+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.
+
+ 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
+container appears once under a given parent where a list contains
+instances that can appear multiple times. A container is used to hold
+related fields and to give the data organization and scope.
+
+To create a container, use the xo_open_container and
+xo_close_container functions:
+
+ int xo_open_container (const char *name);
+ int xo_open_container_h (xo_handle_t *xop, const char *name);
+ int xo_open_container_hd (xo_handle_t *xop, const char *name);
+ int xo_open_container_d (const char *name);
+
+ int xo_close_container (const char *name);
+ int xo_close_container_h (xo_handle_t *xop, const char *name);
+ int xo_close_container_hd (xo_handle_t *xop);
+ int xo_close_container_d (void);
+
+The name parameter gives the name of the container, encoded in UTF-8.
+Since ASCII is a proper subset of UTF-8, traditional C strings can be
+used directly.
+
+The close functions with the "_d" suffix are used in "Do The Right
+Thing" mode, where the name of the open containers, lists, and
+instances are maintained internally by libxo to allow the caller to
+avoid keeping track of the open container name.
+
+Use the XOF_WARN flag to generate a warning if the name given on the
+close does not match the current open container.
+
+For TEXT and HTML output, containers are not rendered into output
+text, though for HTML they are used when the XOF_XPATH flag is set.
+
+ EXAMPLE:
+ xo_open_container("system");
+ xo_emit("The host name is {:host-name}\n", hn);
+ xo_close_container("system");
+ XML:
+ <system><host-name>foo</host-name></system>
+
+*** Lists and Instances
+
+Lists are sequences of instances of homogeneous data objects. Two
+distinct levels of calls are needed to represent them in our output
+styles. Calls must be made to open and close a list, and for each
+instance of data in that list, calls must be make to open and close
+that instance.
+
+The name given to all calls must be identical, and it is strongly
+suggested that the name be singular, not plural, as a matter of
+style and usage expectations.
+
+ EXAMPLE:
+ xo_open_list("user");
+ for (i = 0; i < num_users; i++) {
+ xo_open_instance("user");
+ xo_emit("{k:name}:{:uid/%u}:{:gid/%u}:{:home}\n",
+ pw[i].pw_name, pw[i].pw_uid,
+ pw[i].pw_gid, pw[i].pw_dir);
+ xo_close_instance("user");
+ }
+ xo_close_list("user");
+ TEXT:
+ phil:1001:1001:/home/phil
+ pallavi:1002:1002:/home/pallavi
+ XML:
+ <user>
+ <name>phil</name>
+ <uid>1001</uid>
+ <gid>1001</gid>
+ <home>/home/phil</home>
+ </user>
+ <user>
+ <name>pallavi</name>
+ <uid>1002</uid>
+ <gid>1002</gid>
+ <home>/home/pallavi</home>
+ </user>
+ JSON:
+ user: [
+ {
+ "name": "phil",
+ "uid": 1001,
+ "gid": 1001,
+ "home": "/home/phil",
+ },
+ {
+ "name": "pallavi",
+ "uid": 1002,
+ "gid": 1002,
+ "home": "/home/pallavi",
+ }
+ ]
+
+** Support Functions
+
+*** 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
+from the argument list so the calling application does not
+need to process them. If successful, a new value for argc
+is returned. On failure, a message it emitted and -1 is returned.
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ 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^
+for a description of valid arguments.
+
+*** xo_set_program
+
+The xo_set_program function sets name of the program as reported by
+functions like xo_failure, xo_warn, xo_err, etc. The program name is
+initialized by xo_parse_args, but subsequent calls to xo_set_program
+can override this value.
+
+ xo_set_program(argv[0]);
+
+Note that the value is not copied, so the memory passed to
+xo_set_program (and xo_parse_args) must be maintained by the caller.
+
+*** xo_set_version
+
+The xo_set_version function records a version number to be emitted as
+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.
+
+ void xo_set_version (const char *version);
+ void xo_set_version_h (xo_handle_t *xop, const char *version);
+
+*** Field Information (xo_info_t) @info@
+
+HTML data can include additional information in attributes that
+begin with "data-". To enable this, three things must occur:
+
+First the application must build an array of xo_info_t structures,
+one per tag. The array must be sorted by name, since libxo uses a
+binary search to find the entry that matches names from format
+instructions.
+
+Second, the application must inform libxo about this information using
+the xo_set_info() call:
+
+ typedef struct xo_info_s {
+ const char *xi_name; /* Name of the element */
+ const char *xi_type; /* Type of field */
+ const char *xi_help; /* Description of field */
+ } xo_info_t;
+
+ void xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count);
+
+Like other libxo calls, passing NULL for the handle tells libxo to use
+the default handle.
+
+If the count is -1, libxo will count the elements of infop, but there
+must be an empty element at the end. More typically, the number is
+known to the application:
+
+ 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" },
+ };
+ int info_count = (sizeof(info) / sizeof(info[0]));
+ ...
+ xo_set_info(NULL, info, info_count);
+
+Third, the emission of info must be triggered with the XOF_INFO flag
+using either the xo_set_flags() function or the "--libxo=info" command
+line argument.
+
+The type and help values, if present, are emitted as the "data-type"
+and "data-help" attributes:
+
+ <div class="data" data-tag="sku" data-type="string"
+ data-help="Stock Keeping Unit">GRO-000-533</div>
+
+*** Memory Allocation
+
+The xo_set_allocator function allows libxo to be used in environments
+where the standard realloc() and free() functions are not available.
+
+ void xo_set_allocator (xo_realloc_func_t realloc_func,
+ xo_free_func_t free_func);
+
+realloc_func should expect the same arguments as realloc(3) and return
+a pointer to memory following the same convention. free_func will
+receive the same argument as free(3) and should release it, as
+appropriate for the environment.
+
+By default, the standard realloc() and free() functions are used.
+
+*** LIBXO_OPTIONS @LIBXO_OPTIONS@
+
+The environment variable "LIBXO_OPTIONS" can be set to a string of
+options:
+
+|--------+---------------------------------------------|
+| 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:
+
+ % env LIBXO_OPTIONS=W my-app
+
+Complete HTML output can be generated with:
+
+ % env LIBXO_OPTIONS=HXI my-app
+
+Since environment variables are inherited, child processes will have
+the same options, which may be undesirable, making the use of the
+"--libxo" option is preferable in most situations.
+
+*** Errors, Warnings, and Messages
+
+Many programs make use of the standard library functions err() and
+warn() to generate errors and warnings for the user. libxo wants to
+pass that information via the current output style, and provides
+compatible functions to allow this:
+
+ void xo_warn (const char *fmt, ...);
+ void xo_warnx (const char *fmt, ...);
+ void xo_warn_c (int code, const char *fmt, ...);
+ void xo_warn_hc (xo_handle_t *xop, int code,
+ const char *fmt, ...);
+ void xo_err (int eval, const char *fmt, ...);
+ void xo_errc (int eval, int code, const char *fmt, ...);
+ void xo_errx (int eval, const char *fmt, ...);
+ void xo_message (const char *fmt, ...);
+ void xo_message_c (int code, const char *fmt, ...);
+ void xo_message_hc (xo_handle_t *xop, int code,
+ const char *fmt, ...);
+ void xo_message_hcv (xo_handle_t *xop, int code,
+ const char *fmt, va_list vap);
+
+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 "errno" or the "code" parameter.
+
+ EXAMPLE:
+ if (open(filename, O_RDONLY) < 0)
+ xo_err(1, "cannot open file '%s'", filename);
+
+*** xo_error
+
+The xo_error function can be used for generic errors that should be
+reported over the handle, rather than to stderr. The xo_error
+function behaves like xo_err for TEXT and HTML output styles, but puts
+the error into XML or JSON elements:
+
+ EXAMPLE::
+ xo_error("Does not %s", "compute");
+ XML::
+ <error><message>Does not compute</message></error>
+ JSON::
+ "error": { "message": "Does not compute" }
+
+*** xo_no_setlocale
+
+libxo automatically initializes the locale based on setting of the
+environment variables LC_CTYPE, LANG, and LC_ALL. The first of this
+list of variables is used and if none of the variables, the locale
+defaults to "UTF-8". The caller may wish to avoid this behavior, and
+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
+the libxo library. Using "xo", shell scripts can emit XML, JSON, and
+HTML using the same commands that emit text output.
+
+The style of output can be selected using a specific option: "-X" for
+XML, "-J" for JSON, "-H" for HTML, or "-T" for TEXT, which is the
+default. The "--style <style>" option can also be used. The
+LIBXO_OPTIONS environment variable can also be used to set the style,
+as well as other flags.
+
+The "xo" utility accepts a format string suitable for xo_emit() and a
+set of zero or more arguments used to supply data for that string.
+
+ xo "The {k:name} weighs {:weight/%d} pounds.\n" fish 6
+
+ TEXT:
+ The fish weighs 6 pounds.
+ XML:
+ <name>fish</name>
+ <weight>6</weight>
+ JSON:
+ "name": "fish",
+ "weight": 6
+ HTML:
+ <div class="line">
+ <div class="text">The </div>
+ <div class="data" data-tag="name">fish</div>
+ <div class="text"> weighs </div>
+ <div class="data" data-tag="weight">6</div>
+ <div class="text"> pounds.</div>
+ </div>
+
+The "--wrap <path>" option can be used to wrap emitted content in a
+specific hierarchy. The path is a set of hierarchical names separated
+by the '/' character.
+
+ xo --wrap top/a/b/c '{:tag}' value
+
+ XML:
+ <top>
+ <a>
+ <b>
+ <c>
+ <tag>value</tag>
+ </c>
+ </b>
+ </a>
+ </top>
+ JSON:
+ "top": {
+ "a": {
+ "b": {
+ "c": {
+ "tag": "value"
+ }
+ }
+ }
+ }
+
+The "--open <path>" and "--close <path>" can be used to emit
+hierarchical information without the matching close and open
+tag. This allows a shell script to emit open tags, data, and
+then close tags. The "--depth" option may be used to set the
+depth for indentation. The "--leading-xpath" may be used to
+prepend data to the XPath values used for HTML output style.
+
+ #!/bin/sh
+ xo --open top/data
+ xo --depth 2 '{tag}' value
+ xo --close top/data
+ XML:
+ <top>
+ <data>
+ <tag>value</tag>
+ </data>
+ </top>
+ JSON:
+ "top": {
+ "data": {
+ "tag": "value"
+ }
+ }
+
+** Command Line Options
+
+Usage: xo [options] format [fields]
+ --close <path> Close tags for the given path
+ --depth <num> Set the depth for pretty printing
+ --help Display this help text
+ --html OR -H Generate HTML output
+ --json OR -J Generate JSON output
+ --leading-xpath <path> Add a prefix to generated XPaths (HTML)
+ --open <path> Open tags for the given path
+ --pretty OR -p Make 'pretty' output (add indent, newlines)
+ --style <style> Generate given style (xml, json, text, html)
+ --text OR -T Generate text output (the default style)
+ --version Display version information
+ --warn OR -W Display warnings in text on stderr
+ --warn-xml Display warnings in xml on stdout
+ --wrap <path> Wrap output in a set of containers
+ --xml OR -X Generate XML output
+ --xpath Add XPath data to HTML output);
+
+** Example
+
+ % xo 'The {:product} is {:status}\n' stereo "in route"
+ The stereo is in route
+ % ./xo/xo -p -X 'The {:product} is {:status}\n' stereo "in route"
+ <product>stereo</product>
+ <status>in route</status>
+
+* xolint
+
+xolint is a tool for reporting common mistakes in format strings
+in source code that invokes xo_emit(). It allows these errors
+to be diagnosed at build time, rather than waiting until runtime.
+
+xolint takes the one or more C files as arguments, and reports
+and errors, warning, or informational messages as needed.
+
+|------------+---------------------------------------------------|
+| Option | Meaning |
+|------------+---------------------------------------------------|
+| -c | Invoke 'cpp' against the input file |
+| -C <flags> | Flags that are passed to 'cpp |
+| -d | Enable debug output |
+| -D | Generate documentation for all xolint messages |
+| -I | Generate info table code |
+| -p | Print the offending lines after the message |
+| -V | Print vocabulary of all field names |
+| -X | Extract samples from xolint, suitable for testing |
+|------------+---------------------------------------------------|
+
+The output message will contain the source filename and line number, the
+class of the message, the message, and, if -p is given, the
+line that contains the error:
+
+ % xolint.pl -t xolint.c
+ xolint.c: 16: error: anchor format should be "%d"
+ 16 xo_emit("{[:/%s}");
+
+The "-I" option will generate a table of xo_info_t structures ,
+
+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 is a tool for turning the output of libxo-enabled commands into
+html files suitable for display in modern HTML web browsers. It can
+be used to test and debug HTML output, as well as to make the user
+ache to escape the world of 70s terminal devices.
+
+xohtml is given a command, either on the command line or via the "-c"
+option. If not command is given, standard input is used. The
+command's output is wrapped in HTML tags, with references to
+supporting CSS and Javascript files, and written to standard output or
+the file given in the "-f" option. The "-b" option can be used to
+provide an alternative base path for the support files.
+
+|--------------+---------------------------------------------------|
+| Option | Meaning |
+|--------------+---------------------------------------------------|
+| -b <base> | Base path for finding css/javascript files |
+| -c <command> | Command to execute |
+| -f <file> | Output file name |
+|--------------+---------------------------------------------------|
+
+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,
+along with answers that might be helpful.
+
+!! list-sections
+
+** General
+
+*** Can you share the history of libxo?
+
+In 2001, we added an XML API to the JUNOS operating system, which is
+built on top of FreeBSD. Eventually this API became standardized as
+the NETCONF API (RFC 6241). As part of this effort, we modified many
+FreeBSD utilities to emit XML, typically via a "-X" switch. The
+results were mixed. The cost of maintaining this code, updating it
+and carrying it were non-trivial, and contributed to our expense (and
+the associated delay) with upgrading the version of FreeBSD on which
+each release of JUNOS is based.
+
+A recent (2014) effort within JUNOS aims at removing our modifications
+to the underlying FreeBSD code as a means of reducing the expense and
+delay. JUNOS is structured to have system components generate XML
+that is rendered by the CLI (think: login shell) into human-readable
+text. This allows the API to use the same plumbing as the CLI, and
+ensures that all components emit XML, and that it is emitted with
+knowledge of the consumer of that XML, yielding an API that have no
+incremental cost or feature delay.
+
+libxo is an effort to mix the best aspects of the JUNOS strategy into
+FreeBSD in a seemless way, allowing commands to make printf-like
+output calls without needing to care how the output is rendered.
+
+*** Did the complex semantics of format strings evolve over time?
+
+The history is both long and short: libxo's functionality is based
+on what JUNOS does in a data modeling language called ODL (output
+definition language). In JUNOS, all subcomponents generate XML,
+which is feed to the CLI, where data from the ODL files tell is
+how to render that XML into text. ODL might had a set of tags
+like:
+
+ tag docsis-state {
+ help "State of the DOCSIS interface";
+ type string;
+ }
+
+ tag docsis-mode {
+ help "DOCSIS mode (2.0/3.0) of the DOCSIS interface";
+ type string;
+ }
+
+ tag docsis-upstream-speed {
+ help "Operational upstream speed of the interface";
+ type string;
+ }
+
+ tag downstream-scanning {
+ help "Result of scanning in downstream direction";
+ type string;
+ }
+
+ tag ranging {
+ help "Result of ranging action";
+ type string;
+ }
+
+ tag signal-to-noise-ratio {
+ help "Signal to noise ratio for all channels";
+ type string;
+ }
+
+ tag power {
+ help "Operational power of the signal on all channels";
+ type string;
+ }
+
+ format docsis-status-format {
+ picture "
+ State : @, Mode: @, Upstream speed: @
+ Downstream scanning: @, Ranging: @
+ Signal to noise ratio: @
+ Power: @
+";
+ line {
+ field docsis-state;
+ field docsis-mode;
+ field docsis-upstream-speed;
+ field downstream-scanning;
+ field ranging;
+ field signal-to-noise-ratio;
+ field power;
+ }
+ }
+
+These tag definitions are compiled into field definitions
+that are triggered when matching XML elements are seen. ODL
+also supports other means of defining output.
+
+The roles and modifiers describe these details.
+
+In moving these ideas to bsd, two things had to happen: the
+formatting had to happen at the source since BSD won't have
+a JUNOS-like CLI to do the rendering, and we can't depend on
+external data models like ODL, which was seen as too hard a
+sell to the BSD community.
+
+The results were that the xo_emit strings are used to encode the
+roles, modifiers, names, and formats. They are dense and a bit
+cryptic, but not so unlike printf format strings that developers will
+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? @good-field-names@
+
+To make useful, consistent field names, follow these guidelines:
+
+= 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.
+= Use hyphens, not underscores
+Use of hyphens is traditional in XML, and the XOF_UNDERSCORES
+flag can be used to generate underscores in JSON, if desired.
+But the raw field name should use hyphens.
+= Use full words
+Don't 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".
+= 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
+"packets-we-have-sent". 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".
+= Reuse existing field names
+Nothing's worse than writing expressions like:
+
+ if ($src1/process[pid == $pid]/name ==
+ $src2/proc-table/proc-list
+ /proc-entry[process-id == $pid]/proc-name) {
+ ...
+ }
+
+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
+content with xo_attr() calls (^xo_attr^) or "{e:}" fields
+(^e-modifier^) to make the data useful.
+= Don't use an arbitrary number postfix
+What does "errors2" mean? No one will know. "errors-after-restart"
+would be a better choice. 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's the
+difference between errors37 and errors63.
+= Be consistent, uniform, unsurprising, and predictable
+Think of your field vocabulary as an API. You want it useful,
+expressive, meaningful, direct, and obvious. You want the client
+application's programmer to move between without the need to
+understand a variety of opinions on how fields are named. They should
+see the system as a single cohesive whole, not a sack of cats.
+
+Field names constitute the means by which client programmers interact
+with our system. By choosing wise names now, you are making their
+lives better.
+
+After using "xolint" to find errors in your field descriptors, use
+"xolint -V" to spell check your field names and to detect different
+names for the same data. "dropped-short" and "dropped-too-short" are
+both reasonable names, but using them both will lead users to ask the
+difference between the two fields. 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.
+
+** What does this message mean?
+
+!!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
+
+Here is the unit test example:
+
+ 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", 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", 17, 14, 2, base_grocery, 2331 },
+ { NULL, 0, 0, 0, NULL, 0 }
+ };
+ struct item list2[] = {
+ { "fish", 1321, 45, 1, base_grocery, 533 },
+ };
+ 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(EXIT_FAILURE);
+
+ xo_set_info(NULL, info, info_count);
+
+ xo_open_container_h(NULL, "top");
+
+ xo_open_container("data");
+ xo_open_list("item");
+
+ for (ip = list; ip->i_title; ip++) {
+ xo_open_instance("item");
+
+ 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: }{Lwc:In stock}{:in-stock/%u}\n",
+ ip->i_instock);
+ xo_emit("{P: }{Lwc: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);
+
+ xo_close_instance("item");
+ }
+
+ xo_close_list("item");
+ xo_close_container("data");
+
+ xo_open_container("data");
+ xo_open_list("item");
+
+ for (ip = list2; ip->i_title; ip++) {
+ xo_open_instance("item");
+
+ xo_emit("{L:Item} '{: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: }{Lwc:In stock}{:in-stock/%u}\n",
+ ip->i_instock);
+ xo_emit("{P: }{Lwc: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);
+
+ xo_close_instance("item");
+ }
+
+ xo_close_list("item");
+ xo_close_container("data");
+
+ xo_close_container_h(NULL, "top");
+
+ return 0;
+ }
+
+Text output:
+
+ % ./testxo --libxo text
+ Item 'gum':
+ Total sold: 1412.0
+ In stock: 54
+ On order: 10
+ SKU: GRO-000-415
+ Item 'rope':
+ Total sold: 85.0
+ In stock: 4
+ On order: 2
+ SKU: HRD-000-212
+ Item 'ladder':
+ Total sold: 0
+ In stock: 2
+ On order: 1
+ SKU: HRD-000-517
+ Item 'bolt':
+ Total sold: 4123.0
+ In stock: 144
+ On order: 42
+ SKU: HRD-000-632
+ Item 'water':
+ Total sold: 17.0
+ In stock: 14
+ On order: 2
+ SKU: GRO-000-2331
+ Item 'fish':
+ Total sold: 1321.0
+ In stock: 45
+ On order: 1
+ SKU: GRO-000-533
+
+JSON output:
+
+ % ./testxo --libxo json,pretty
+ "top": {
+ "data": {
+ "item": [
+ {
+ "name": "gum",
+ "sold": 1412.0,
+ "in-stock": 54,
+ "on-order": 10,
+ "sku": "GRO-000-415"
+ },
+ {
+ "name": "rope",
+ "sold": 85.0,
+ "in-stock": 4,
+ "on-order": 2,
+ "sku": "HRD-000-212"
+ },
+ {
+ "name": "ladder",
+ "sold": 0,
+ "in-stock": 2,
+ "on-order": 1,
+ "sku": "HRD-000-517"
+ },
+ {
+ "name": "bolt",
+ "sold": 4123.0,
+ "in-stock": 144,
+ "on-order": 42,
+ "sku": "HRD-000-632"
+ },
+ {
+ "name": "water",
+ "sold": 17.0,
+ "in-stock": 14,
+ "on-order": 2,
+ "sku": "GRO-000-2331"
+ }
+ ]
+ },
+ "data": {
+ "item": [
+ {
+ "name": "fish",
+ "sold": 1321.0,
+ "in-stock": 45,
+ "on-order": 1,
+ "sku": "GRO-000-533"
+ }
+ ]
+ }
+ }
+
+XML output:
+
+ % ./testxo --libxo pretty,xml
+ <top>
+ <data>
+ <item>
+ <name>gum</name>
+ <sold>1412.0</sold>
+ <in-stock>54</in-stock>
+ <on-order>10</on-order>
+ <sku>GRO-000-415</sku>
+ </item>
+ <item>
+ <name>rope</name>
+ <sold>85.0</sold>
+ <in-stock>4</in-stock>
+ <on-order>2</on-order>
+ <sku>HRD-000-212</sku>
+ </item>
+ <item>
+ <name>ladder</name>
+ <sold>0</sold>
+ <in-stock>2</in-stock>
+ <on-order>1</on-order>
+ <sku>HRD-000-517</sku>
+ </item>
+ <item>
+ <name>bolt</name>
+ <sold>4123.0</sold>
+ <in-stock>144</in-stock>
+ <on-order>42</on-order>
+ <sku>HRD-000-632</sku>
+ </item>
+ <item>
+ <name>water</name>
+ <sold>17.0</sold>
+ <in-stock>14</in-stock>
+ <on-order>2</on-order>
+ <sku>GRO-000-2331</sku>
+ </item>
+ </data>
+ <data>
+ <item>
+ <name>fish</name>
+ <sold>1321.0</sold>
+ <in-stock>45</in-stock>
+ <on-order>1</on-order>
+ <sku>GRO-000-533</sku>
+ </item>
+ </data>
+ </top>
+
+HMTL output:
+
+ % ./testxo --libxo pretty,html
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">GRO-000-415</div>
+ </div>
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">HRD-000-212</div>
+ </div>
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">HRD-000-517</div>
+ </div>
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">HRD-000-632</div>
+ </div>
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">GRO-000-2331</div>
+ </div>
+ <div class="line">
+ <div class="label">Item</div>
+ <div class="text"> '</div>
+ <div class="data" data-tag="name">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">GRO-000-533</div>
+ </div>
+
+HTML output with xpath and info flags:
+
+ % ./testxo --libxo pretty,html,xpath,info
+ <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">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/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/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/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">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">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/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/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/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">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">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/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/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/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">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">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/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/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/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">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">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/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/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/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">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">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/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/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/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">GRO-000-533</div>
+ </div>
+
+{{document:
+ name libxo-manual;
+ private "The libxo Project";
+ ipr none;
+ category exp;
+ abbreviation LIBXO-MANUAL;
+ title "libxo: The Easy Way to Generate text, XML, JSON, and HTML output";
+ contributor "author:Phil Shafer:Juniper Networks:phil@juniper.net";
+}}
diff --git a/encoder/Makefile.am b/encoder/Makefile.am
new file mode 100644
index 000000000000..10d19de73225
--- /dev/null
+++ b/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/encoder/cbor/Makefile.am b/encoder/cbor/Makefile.am
new file mode 100644
index 000000000000..7ce44e09fedd
--- /dev/null
+++ b/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/encoder/cbor/enc_cbor.c b/encoder/cbor/enc_cbor.c
new file mode 100644
index 000000000000..513063fb1508
--- /dev/null
+++ b/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/encoder/test/Makefile.am b/encoder/test/Makefile.am
new file mode 100644
index 000000000000..1d8518e8e7ea
--- /dev/null
+++ b/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/encoder/test/enc_test.c b/encoder/test/enc_test.c
new file mode 100644
index 000000000000..ec49499c00c0
--- /dev/null
+++ b/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/install-sh b/install-sh
new file mode 100755
index 000000000000..377bb8687ffe
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,527 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2011-11-20.07; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" "" $nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit=${DOITPROG-}
+if test -z "$doit"; then
+ doit_exec=exec
+else
+ doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+ test "$posix_glob" != "?" || {
+ if (set -f) 2>/dev/null; then
+ posix_glob=
+ else
+ posix_glob=:
+ fi
+ }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+no_target_directory=
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+ or: $0 [OPTION]... SRCFILES... DIRECTORY
+ or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+ or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+ --help display this help and exit.
+ --version display version info and exit.
+
+ -c (ignored)
+ -C install only if different (preserve the last data modification time)
+ -d create directories instead of installing files.
+ -g GROUP $chgrpprog installed files to GROUP.
+ -m MODE $chmodprog installed files to MODE.
+ -o USER $chownprog installed files to USER.
+ -s $stripprog installed files.
+ -t DIRECTORY install into DIRECTORY.
+ -T report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+ CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+ RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+ case $1 in
+ -c) ;;
+
+ -C) copy_on_change=true;;
+
+ -d) dir_arg=true;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift;;
+
+ --help) echo "$usage"; exit $?;;
+
+ -m) mode=$2
+ case $mode in
+ *' '* | *' '* | *'
+'* | *'*'* | *'?'* | *'['*)
+ echo "$0: invalid mode: $mode" >&2
+ exit 1;;
+ esac
+ shift;;
+
+ -o) chowncmd="$chownprog $2"
+ shift;;
+
+ -s) stripcmd=$stripprog;;
+
+ -t) dst_arg=$2
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ shift;;
+
+ -T) no_target_directory=true;;
+
+ --version) echo "$0 $scriptversion"; exit $?;;
+
+ --) shift
+ break;;
+
+ -*) echo "$0: invalid option: $1" >&2
+ exit 1;;
+
+ *) break;;
+ esac
+ shift
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+ # When -d is used, all remaining arguments are directories to create.
+ # When -t is used, the destination is already specified.
+ # Otherwise, the last argument is the destination. Remove it from $@.
+ for arg
+ do
+ if test -n "$dst_arg"; then
+ # $@ is not empty: it contains at least $arg.
+ set fnord "$@" "$dst_arg"
+ shift # fnord
+ fi
+ shift # arg
+ dst_arg=$arg
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ done
+fi
+
+if test $# -eq 0; then
+ if test -z "$dir_arg"; then
+ echo "$0: no input file specified." >&2
+ exit 1
+ fi
+ # It's OK to call 'install-sh -d' without argument.
+ # This can happen when creating conditional directories.
+ exit 0
+fi
+
+if test -z "$dir_arg"; then
+ do_exit='(exit $ret); exit $ret'
+ trap "ret=129; $do_exit" 1
+ trap "ret=130; $do_exit" 2
+ trap "ret=141; $do_exit" 13
+ trap "ret=143; $do_exit" 15
+
+ # Set umask so as not to create temps with too-generous modes.
+ # However, 'strip' requires both read and write access to temps.
+ case $mode in
+ # Optimize common cases.
+ *644) cp_umask=133;;
+ *755) cp_umask=22;;
+
+ *[0-7])
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw='% 200'
+ fi
+ cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+ *)
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw=,u+rw
+ fi
+ cp_umask=$mode$u_plus_rw;;
+ esac
+fi
+
+for src
+do
+ # Protect names problematic for 'test' and other utilities.
+ case $src in
+ -* | [=\(\)!]) src=./$src;;
+ esac
+
+ if test -n "$dir_arg"; then
+ dst=$src
+ dstdir=$dst
+ test -d "$dstdir"
+ dstdir_status=$?
+ else
+
+ # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+ # might cause directories to be created, which would be especially bad
+ # if $src (and thus $dsttmp) contains '*'.
+ if test ! -f "$src" && test ! -d "$src"; then
+ echo "$0: $src does not exist." >&2
+ exit 1
+ fi
+
+ if test -z "$dst_arg"; then
+ echo "$0: no destination specified." >&2
+ exit 1
+ fi
+ dst=$dst_arg
+
+ # If destination is a directory, append the input filename; won't work
+ # if double slashes aren't ignored.
+ if test -d "$dst"; then
+ if test -n "$no_target_directory"; then
+ echo "$0: $dst_arg: Is a directory" >&2
+ exit 1
+ fi
+ dstdir=$dst
+ dst=$dstdir/`basename "$src"`
+ dstdir_status=0
+ else
+ # Prefer dirname, but fall back on a substitute if dirname fails.
+ dstdir=`
+ (dirname "$dst") 2>/dev/null ||
+ expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$dst" : 'X\(//\)[^/]' \| \
+ X"$dst" : 'X\(//\)$' \| \
+ X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+ echo X"$dst" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'
+ `
+
+ test -d "$dstdir"
+ dstdir_status=$?
+ fi
+ fi
+
+ obsolete_mkdir_used=false
+
+ if test $dstdir_status != 0; then
+ case $posix_mkdir in
+ '')
+ # Create intermediate dirs using mode 755 as modified by the umask.
+ # This is like FreeBSD 'install' as of 1997-10-28.
+ umask=`umask`
+ case $stripcmd.$umask in
+ # Optimize common cases.
+ *[2367][2367]) mkdir_umask=$umask;;
+ .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+ *[0-7])
+ mkdir_umask=`expr $umask + 22 \
+ - $umask % 100 % 40 + $umask % 20 \
+ - $umask % 10 % 4 + $umask % 2
+ `;;
+ *) mkdir_umask=$umask,go-w;;
+ esac
+
+ # With -d, create the new directory with the user-specified mode.
+ # Otherwise, rely on $mkdir_umask.
+ if test -n "$dir_arg"; then
+ mkdir_mode=-m$mode
+ else
+ mkdir_mode=
+ fi
+
+ posix_mkdir=false
+ case $umask in
+ *[123567][0-7][0-7])
+ # POSIX mkdir -p sets u+wx bits regardless of umask, which
+ # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+ ;;
+ *)
+ tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+ if (umask $mkdir_umask &&
+ exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+ then
+ if test -z "$dir_arg" || {
+ # Check for POSIX incompatibilities with -m.
+ # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+ # other-writable bit of parent directory when it shouldn't.
+ # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+ ls_ld_tmpdir=`ls -ld "$tmpdir"`
+ case $ls_ld_tmpdir in
+ d????-?r-*) different_mode=700;;
+ d????-?--*) different_mode=755;;
+ *) false;;
+ esac &&
+ $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+ ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+ test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+ }
+ }
+ then posix_mkdir=:
+ fi
+ rmdir "$tmpdir/d" "$tmpdir"
+ else
+ # Remove any dirs left behind by ancient mkdir implementations.
+ rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+ fi
+ trap '' 0;;
+ esac;;
+ esac
+
+ if
+ $posix_mkdir && (
+ umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+ )
+ then :
+ else
+
+ # The umask is ridiculous, or mkdir does not conform to POSIX,
+ # or it failed possibly due to a race condition. Create the
+ # directory the slow way, step by step, checking for races as we go.
+
+ case $dstdir in
+ /*) prefix='/';;
+ [-=\(\)!]*) prefix='./';;
+ *) prefix='';;
+ esac
+
+ eval "$initialize_posix_glob"
+
+ oIFS=$IFS
+ IFS=/
+ $posix_glob set -f
+ set fnord $dstdir
+ shift
+ $posix_glob set +f
+ IFS=$oIFS
+
+ prefixes=
+
+ for d
+ do
+ test X"$d" = X && continue
+
+ prefix=$prefix$d
+ if test -d "$prefix"; then
+ prefixes=
+ else
+ if $posix_mkdir; then
+ (umask=$mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+ # Don't fail if two instances are running concurrently.
+ test -d "$prefix" || exit 1
+ else
+ case $prefix in
+ *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) qprefix=$prefix;;
+ esac
+ prefixes="$prefixes '$qprefix'"
+ fi
+ fi
+ prefix=$prefix/
+ done
+
+ if test -n "$prefixes"; then
+ # Don't fail if two instances are running concurrently.
+ (umask $mkdir_umask &&
+ eval "\$doit_exec \$mkdirprog $prefixes") ||
+ test -d "$dstdir" || exit 1
+ obsolete_mkdir_used=true
+ fi
+ fi
+ fi
+
+ if test -n "$dir_arg"; then
+ { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+ { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+ else
+
+ # Make a couple of temp file names in the proper directory.
+ dsttmp=$dstdir/_inst.$$_
+ rmtmp=$dstdir/_rm.$$_
+
+ # Trap to clean up those temp files at exit.
+ trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+ # Copy the file name to the temp name.
+ (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+ # and set any options; do chmod last to preserve setuid bits.
+ #
+ # If any of these fail, we abort the whole thing. If we want to
+ # ignore errors from any of these, just make sure not to ignore
+ # errors from the above "$doit $cpprog $src $dsttmp" command.
+ #
+ { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+ { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+ { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+ # If -C, don't bother to copy if it wouldn't change the file.
+ if $copy_on_change &&
+ old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
+ new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
+
+ eval "$initialize_posix_glob" &&
+ $posix_glob set -f &&
+ set X $old && old=:$2:$4:$5:$6 &&
+ set X $new && new=:$2:$4:$5:$6 &&
+ $posix_glob set +f &&
+
+ test "$old" = "$new" &&
+ $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+ then
+ rm -f "$dsttmp"
+ else
+ # Rename the file to the real destination.
+ $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+ # The rename failed, perhaps because mv can't rename something else
+ # to itself, or perhaps because mv is so ancient that it does not
+ # support -f.
+ {
+ # Now remove or move aside any old file at destination location.
+ # We try this two ways since rm can't unlink itself on some
+ # systems and the destination file might be busy for other
+ # reasons. In this case, the final cleanup might fail but the new
+ # file should still install successfully.
+ {
+ test ! -f "$dst" ||
+ $doit $rmcmd -f "$dst" 2>/dev/null ||
+ { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+ { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+ } ||
+ { echo "$0: cannot unlink or rename $dst" >&2
+ (exit 1); exit 1
+ }
+ } &&
+
+ # Now rename the file to the real destination.
+ $doit $mvcmd "$dsttmp" "$dst"
+ }
+ fi || exit 1
+
+ trap '' 0
+ fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/libxo-config.in b/libxo-config.in
new file mode 100644
index 000000000000..3dbb7d41a364
--- /dev/null
+++ b/libxo-config.in
@@ -0,0 +1,119 @@
+#! /bin/sh
+#
+# $Id$
+#
+# Copyright 2011-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.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@
+libdir=@libdir@
+
+usage()
+{
+ cat <<EOF
+Usage: libxo-config [OPTION]
+
+Known values for OPTION are:
+
+ --prefix=DIR change libxo prefix [default $prefix]
+ --exec-prefix=DIR change libxo exec prefix [default $exec_prefix]
+ --libs print library linking information
+ --bindir print the bin directory
+ --cflags print pre-processor and compiler flags
+ --share print share directory
+ --help display this help and exit
+ --version output version information
+EOF
+
+ exit $1
+}
+
+if test $# -eq 0; then
+ usage 1
+fi
+
+cflags=false
+libs=false
+
+while test $# -gt 0; do
+ case "$1" in
+ -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) optarg= ;;
+ esac
+
+ case "$1" in
+ --prefix=*)
+ prefix=$optarg
+ includedir=$prefix/include
+ libdir=$prefix/lib
+ ;;
+
+ --prefix)
+ echo $prefix
+ ;;
+
+ --exec-prefix=*)
+ exec_prefix=$optarg
+ libdir=$exec_prefix/lib
+ ;;
+
+ --exec-prefix)
+ echo $exec_prefix
+ ;;
+
+ --version)
+ echo @VERSION@
+ exit 0
+ ;;
+
+ --help)
+ usage 0
+ ;;
+
+ --cflags)
+ echo -I@LIBXO_INCLUDEDIR@ @LIBXO_CFLAGS@
+ ;;
+
+
+ --share)
+ echo @LIBXO_SHAREDIR@
+ ;;
+
+ --bindir)
+ echo @LIBXO_BINDIR@
+ ;;
+
+ --libdir)
+ echo @LIBXO_LIBDIR@
+ ;;
+
+
+ --libs)
+ if [ "`uname`" = "Linux" ]
+ then
+ if [ "@LIBXO_LIBDIR@" = "-L/usr/lib" -o "@LIBXO_LIBDIR@" = "-L/usr/lib64" ]
+ then
+ echo @LIBXO_LIBS@
+ else
+ echo -L@LIBXO_LIBDIR@ @LIBXO_LIBS@
+ fi
+ else
+ echo -L@LIBXO_LIBDIR@ @LIBXO_LIBS@ @WIN32_EXTRA_LIBADD@
+ fi
+ ;;
+
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+exit 0
diff --git a/libxo/Makefile.am b/libxo/Makefile.am
new file mode 100644
index 000000000000..b11c311e70b2
--- /dev/null
+++ b/libxo/Makefile.am
@@ -0,0 +1,89 @@
+#
+# 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
+if HAVE_GCC
+GCC_WARNINGS = yes
+endif
+include ${top_srcdir}/warnings.mk
+
+libxoincdir = ${includedir}/libxo
+
+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_encoder.h
+
+noinst_HEADERS = \
+ xo_buf.h \
+ xo_humanize.h \
+ xo_wcwidth.h
+
+libxo_la_SOURCES = \
+ libxo.c \
+ xo_encoder.c \
+ xo_syslog.c
+
+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_message.3 \
+ xo_no_setlocale.3 \
+ xo_open_container.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_syslog.3
+
+man5_files = \
+ xo_format.5
+
+man_MANS = ${man3_files} ${man5_files}
+
+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/libxo/add.man b/libxo/add.man
new file mode 100644
index 000000000000..a1e3e11e9984
--- /dev/null
+++ b/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/libxo/add.man.in b/libxo/add.man.in
new file mode 100644
index 000000000000..4eae26566aee
--- /dev/null
+++ b/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/libxo/gen-wide.sh b/libxo/gen-wide.sh
new file mode 100755
index 000000000000..b0342874b179
--- /dev/null
+++ b/libxo/gen-wide.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+FILE=$1
+
+SYMBOLS="
+xo_buffer_s
+xo_buffer_t
+xo_stack_s
+xo_stack_t
+xo_handle_s
+xo_handle_t
+xo_default_handle
+xo_default_inited
+xo_realloc
+xo_free
+xo_write_to_file
+xo_close_file
+xo_buf_init
+xo_init_handle
+xo_default_init
+xo_buf_has_room
+xo_printf
+xo_escape_xml
+xo_escape_json
+xo_buf_append
+xo_buf_escape
+xo_data_append
+xo_data_escape
+xo_default
+xo_indent
+xo_warn
+xo_create
+xo_create_to_file
+xo_destroy
+xo_set_style
+xo_set_flags
+xo_set_info
+xo_set_formatter
+xo_clear_flags
+xo_buf_indent
+xo_line_ensure_open
+xo_line_close
+xo_info_compare
+xo_info_find
+xo_format_data
+xo_buf_append_div
+xo_format_text
+xo_format_label
+xo_format_title
+xo_format_prep
+xo_format_value
+xo_format_decoration
+xo_format_padding
+xo_do_emit
+xo_emit_hv
+xo_emit_h
+xo_emit
+xo_attr_hv
+xo_attr_h
+xo_attr
+xo_depth_change
+xo_open_container_h
+xo_open_container
+xo_close_container_h
+xo_close_container
+xo_open_list_h
+xo_open_list
+xo_close_list_h
+xo_close_list
+xo_open_instance_h
+xo_open_instance
+xo_close_instance_h
+xo_close_instance
+xo_set_writer
+xo_set_allocator
+"
diff --git a/libxo/libxo.3 b/libxo/libxo.3
new file mode 100644
index 000000000000..4e2488cd5f57
--- /dev/null
+++ b/libxo/libxo.3
@@ -0,0 +1,313 @@
+.\" #
+.\" # 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 8, 2014
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm libxo
+.Nd library for emitting text, XML, JSON, or HTML output
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Sh DESCRIPTION
+The functions defined in
+.Nm
+are used to generate a choice of
+.Em TEXT ,
+.Em XML ,
+.Em JSON ,
+or
+.Em HTML
+output.
+A common set of functions are used, with
+command line switches passed to the library to control the details of
+the output.
+.Pp
+Most commands emit text output aimed at humans.
+It is designed
+to be parsed and understood by a user.
+Humans are gifted at extracting
+details and pattern matching.
+Often programmers need to extract
+information from this human-oriented output.
+Programmers use tools
+like
+.Xr grep 1 ,
+.Xr awk 1 ,
+and regular expressions to ferret out the pieces of
+information they need.
+Such solutions are fragile and require
+updates when output contents change or evolve, requiring testing and
+validation.
+.Pp
+Modern tool developers favor encoding schemes like XML and JSON,
+which allow trivial parsing and extraction of data.
+Such formats are
+simple, well understood, hierarchical, easily parsed, and often
+integrate easier with common tools and environments.
+.Pp
+In addition, modern reality means that more output ends up in web
+browsers than in terminals, making HTML output valuable.
+.Pp
+.Nm
+allows a single set of function calls in source code to generate
+traditional text output, as well as XML and JSON formatted data.
+HTML
+can also be generated; "<div>" elements surround the traditional text
+output, with attributes that detail how to render the data.
+.Pp
+There are four encoding styles supported by
+.Nm :
+.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.
+.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
+library allows an application to generate text, XML, JSON,
+and HTML output using a common set of function calls.
+The application
+decides at run time which output style should be produced.
+The
+application calls a function
+.Xr xo_emit 3
+to product output that is
+described in a format string.
+A
+.Dq field descriptor
+tells
+.Nm
+what the field is and what it means.
+Each field descriptor is placed in
+braces with a printf-like format string:
+.Bd -literal -offset indent
+ xo_emit(" {:lines/%7ju} {:words/%7ju} "
+ "{:characters/%7ju}{d:filename/%s}\\n",
+ linect, wordct, charct, file);
+.Ed
+.Pp
+Each field can have a role, with the 'value' role being the default,
+and the role tells
+.Nm
+how and when to render that field, as well as
+a
+.Xr printf 3 Ns -like
+format string.
+.Pp
+Output
+can then be generated in various style, using the "--libxo" option.
+.Sh DEFAULT HANDLE
+Handles give an abstraction for
+.Nm
+that encapsulates the state of a
+stream of output.
+Handles have the data type "xo_handle_t" and are
+opaque to the caller.
+.Pp
+The library has a default handle that is automatically initialized.
+By default, this handle will send text style output to standard output.
+The
+.Xr xo_set_style 3
+and
+.Xr xo_set_flags 3
+functions can be used to change this
+behavior.
+.Pp
+Many
+.Nm
+functions take a handle as their first parameter; most that
+do not use the default handle.
+Any function taking a handle can
+be passed
+.Dv NULL
+to access the default handle.
+.Pp
+For the typical command that is generating output on standard output,
+there is no need to create an explicit handle, but they are available
+when needed, e.g., for daemons that generate multiple streams of
+output.
+.Sh FUNCTION OVERVIEW
+The
+.Nm
+library includes the following functions:
+.Bl -tag -width "xo_close_container_hd"
+.It Sy "Function Description"
+.It Fn xo_attr
+.It Fn xo_attr_h
+.It Fn xo_attr_hv
+Allows the caller to emit XML attributes with the next open element.
+.It Fn xo_create
+.It Fn xo_create_to_file
+Allow the caller to create a new handle.
+Note that
+.Nm
+has a default handle that allows the caller to avoid use of an
+explicitly created handle.
+Only callers writing to files other than
+.Dv stdout
+would need to call
+.Fn xo_create .
+.It Fn xo_destroy
+Frees any resources associated with the handle, including the handle
+itself.
+.It Fn xo_emit
+.It Fn xo_emit_h
+.It Fn xo_emit_hv
+Emit formatted output.
+The
+.Fa fmt
+string controls the conversion of the remaining arguments into
+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
+.It Fn xo_warn_hc
+.It Fn xo_err
+.It Fn xo_errc
+.It Fn xo_errx
+.It Fn xo_message
+.It Fn xo_message_c
+.It Fn xo_message_hc
+.It Fn xo_message_hcv
+These functions are meant to be compatible with their standard libc namesakes.
+.It Fn xo_finish
+.It Fn xo_finish_h
+Flush output, close open construct, and complete any pending
+operations.
+.It Fn xo_flush
+.It Fn xo_flush_h
+Allow the caller to flush any pending output for a handle.
+.It Fn xo_no_setlocale
+Direct
+.Nm
+to avoid initializing the locale.
+This function should be called before any other
+.Nm
+function is called.
+.It Fn xo_open_container
+.It Fn xo_open_container_h
+.It Fn xo_open_container_hd
+.It Fn xo_open_container_d
+.It Fn xo_close_container
+.It Fn xo_close_container_h
+.It Fn xo_close_container_hd
+.It Fn xo_close_container_d
+Containers a singleton levels of hierarchy, typically used to organize
+related content.
+.It Fn xo_open_list_h
+.It Fn xo_open_list
+.It Fn xo_open_list_hd
+.It Fn xo_open_list_d
+.It Fn xo_open_instance_h
+.It Fn xo_open_instance
+.It Fn xo_open_instance_hd
+.It Fn xo_open_instance_d
+.It Fn xo_close_instance_h
+.It Fn xo_close_instance
+.It Fn xo_close_instance_hd
+.It Fn xo_close_instance_d
+.It Fn xo_close_list_h
+.It Fn xo_close_list
+.It Fn xo_close_list_hd
+.It Fn xo_close_list_d
+Lists are levels of hierarchy that can appear multiple times within
+the same parent.
+Two calls are needed to encapsulate them, one for
+the list and one for each instance of that list.
+Typically
+.Fn xo_open_list
+and
+.Fn xo_close_list
+are called outside a
+for-loop, where
+.Fn xo_open_instance
+it called at the top of the loop, and
+.Fn xo_close_instance
+is called at the bottom of the loop.
+.It Fn xo_parse_args
+Inspects command line arguments for directions to
+.Nm .
+This function should be called before
+.Va argv
+is inspected by the application.
+.It Fn xo_set_allocator
+Instructs
+.Nm
+to use an alternative memory allocator and deallocator.
+.It Fn xo_set_flags
+.It Fn xo_clear_flags
+Change the flags set for a handle.
+.It Fn xo_set_info
+Provides additional information about elements for use with HTML
+rendering.
+.It Fn xo_set_options
+Changes formatting options used by handle.
+.It Fn xo_set_style
+.It Fn xo_set_style_name
+Changes the output style used by a handle.
+.It Fn xo_set_writer
+Instructs
+.Nm
+to use an alternative set of low-level output functions.
+.El
+.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 ,
+.Xr xo_no_setlocale 3 ,
+.Xr xo_open_container 3 ,
+.Xr xo_open_list 3 ,
+.Xr xo_parse_args 3 ,
+.Xr xo_set_allocator 3 ,
+.Xr xo_set_flags 3 ,
+.Xr xo_set_info 3 ,
+.Xr xo_set_options 3 ,
+.Xr xo_set_style 3 ,
+.Xr xo_set_writer 3 ,
+.Xr xo_format 5
diff --git a/libxo/libxo.c b/libxo/libxo.c
new file mode 100644
index 000000000000..b531371170d7
--- /dev/null
+++ b/libxo/libxo.c
@@ -0,0 +1,7659 @@
+/*
+ * Copyright (c) 2014-2015, Juniper Networks, Inc.
+ * All rights reserved.
+ * This SOFTWARE is licensed under the LICENSE provided in the
+ * ../Copyright file. By downloading, installing, copying, or otherwise
+ * using the SOFTWARE, you agree to be bound by the terms of that
+ * LICENSE.
+ * Phil Shafer, July 2014
+ *
+ * This is the implementation of libxo, the formatting library that
+ * generates multiple styles of output from a single code path.
+ * Command line utilities can have their normal text output while
+ * automation tools can see XML or JSON output, and web tools can use
+ * HTML output that encodes the text output annotated with additional
+ * information. Specialized encoders can be built that allow custom
+ * encoding including binary ones like CBOR, thrift, protobufs, etc.
+ *
+ * Full documentation is available in ./doc/libxo.txt or online at:
+ * http://juniper.github.io/libxo/libxo-manual.html
+ *
+ * For first time readers, the core bits of code to start looking at are:
+ * - xo_do_emit() -- the central function of the library
+ * - xo_do_format_field() -- handles formatting a single field
+ * - xo_transiton() -- the state machine that keeps things sane
+ * and of course the "xo_handle_t" data structure, which carries all
+ * configuration and state.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <wchar.h>
+#include <locale.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+#include <wctype.h>
+#include <getopt.h>
+
+#include "xo_config.h"
+#include "xo.h"
+#include "xo_encoder.h"
+#include "xo_buf.h"
+
+/*
+ * We ask wcwidth() to do an impossible job, really. It's supposed to
+ * need to tell us the number of columns consumed to display a unicode
+ * character. It returns that number without any sort of context, but
+ * we know they are characters whose glyph differs based on placement
+ * (end of word, middle of word, etc) and many that affect characters
+ * previously emitted. Without content, it can't hope to tell us.
+ * But it's the only standard tool we've got, so we use it. We would
+ * use wcswidth() but it typically just loops thru adding the results
+ * of wcwidth() calls in an entirely unhelpful way.
+ *
+ * Even then, there are many poor implementations (macosx), so we have
+ * to carry our own. We could have configure.ac test this (with
+ * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
+ * to run a binary, which breaks cross-compilation. Hmm... I could
+ * run this test at init time and make a warning for our dear user.
+ *
+ * Anyhow, it remains a best-effort sort of thing. And it's all made
+ * more hopeless because we assume the display code doing the rendering is
+ * playing by the same rules we are. If it display 0x200d as a square
+ * box or a funky question mark, the output will be hosed.
+ */
+#ifdef LIBXO_WCWIDTH
+#include "xo_wcwidth.h"
+#else /* LIBXO_WCWIDTH */
+#define xo_wcwidth(_x) wcwidth(_x)
+#endif /* LIBXO_WCWIDTH */
+
+#ifdef HAVE_STDIO_EXT_H
+#include <stdio_ext.h>
+#endif /* HAVE_STDIO_EXT_H */
+
+/*
+ * humanize_number is a great function, unless you don't have it. So
+ * we carry one in our pocket.
+ */
+#ifdef HAVE_HUMANIZE_NUMBER
+#include <libutil.h>
+#define xo_humanize_number humanize_number
+#else /* HAVE_HUMANIZE_NUMBER */
+#include "xo_humanize.h"
+#endif /* HAVE_HUMANIZE_NUMBER */
+
+#ifdef HAVE_GETTEXT
+#include <libintl.h>
+#endif /* HAVE_GETTEXT */
+
+/*
+ * Three styles of specifying thread-local variables are supported.
+ * configure.ac has the brains to run each possibility thru the
+ * compiler and see what works; we are left to define the THREAD_LOCAL
+ * macro to the right value. Most toolchains (clang, gcc) use
+ * "before", but some (borland) use "after" and I've heard of some
+ * (ms) that use __declspec. Any others out there?
+ */
+#define THREAD_LOCAL_before 1
+#define THREAD_LOCAL_after 2
+#define THREAD_LOCAL_declspec 3
+
+#ifndef HAVE_THREAD_LOCAL
+#define THREAD_LOCAL(_x) _x
+#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
+#define THREAD_LOCAL(_x) __thread _x
+#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
+#define THREAD_LOCAL(_x) _x __thread
+#elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
+#define THREAD_LOCAL(_x) __declspec(_x)
+#else
+#error unknown thread-local setting
+#endif /* HAVE_THREADS_H */
+
+const char xo_version[] = LIBXO_VERSION;
+const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
+
+#ifndef UNUSED
+#define UNUSED __attribute__ ((__unused__))
+#endif /* UNUSED */
+
+#define XO_INDENT_BY 2 /* Amount to indent when pretty printing */
+#define XO_DEPTH 128 /* Default stack depth */
+#define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just sillyb */
+
+#define XO_FAILURE_NAME "failure"
+
+/* Flags for the stack frame */
+typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
+#define XSF_NOT_FIRST (1<<0) /* Not the first element */
+#define XSF_LIST (1<<1) /* Frame is a list */
+#define XSF_INSTANCE (1<<2) /* Frame is an instance */
+#define XSF_DTRT (1<<3) /* Save the name for DTRT mode */
+
+#define XSF_CONTENT (1<<4) /* Some content has been emitted */
+#define XSF_EMIT (1<<5) /* Some field has been emitted */
+#define XSF_EMIT_KEY (1<<6) /* A key has been emitted */
+#define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
+
+/* These are the flags we propagate between markers and their parents */
+#define XSF_MARKER_FLAGS \
+ (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
+
+/*
+ * A word about states: We use a finite state machine (FMS) approach
+ * to help remove fragility from the caller's code. Instead of
+ * requiring a specific order of calls, we'll allow the caller more
+ * flexibility and make the library responsible for recovering from
+ * missed steps. The goal is that the library should not be capable
+ * of emitting invalid xml or json, but the developer shouldn't need
+ * to know or understand all the details about these encodings.
+ *
+ * You can think of states as either states or events, since they
+ * function rather like both. None of the XO_CLOSE_* events will
+ * persist as states, since the matching stack frame will be popped.
+ * Same is true of XSS_EMIT, which is an event that asks us to
+ * prep for emitting output fields.
+ */
+
+/* Stack frame states */
+typedef unsigned xo_state_t;
+#define XSS_INIT 0 /* Initial stack state */
+#define XSS_OPEN_CONTAINER 1
+#define XSS_CLOSE_CONTAINER 2
+#define XSS_OPEN_LIST 3
+#define XSS_CLOSE_LIST 4
+#define XSS_OPEN_INSTANCE 5
+#define XSS_CLOSE_INSTANCE 6
+#define XSS_OPEN_LEAF_LIST 7
+#define XSS_CLOSE_LEAF_LIST 8
+#define XSS_DISCARDING 9 /* Discarding data until recovered */
+#define XSS_MARKER 10 /* xo_open_marker's marker */
+#define XSS_EMIT 11 /* xo_emit has a leaf field */
+#define XSS_EMIT_LEAF_LIST 12 /* xo_emit has a leaf-list ({l:}) */
+#define XSS_FINISH 13 /* xo_finish was called */
+
+#define XSS_MAX 13
+
+#define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
+
+/*
+ * xo_stack_t: As we open and close containers and levels, we
+ * create a stack of frames to track them. This is needed for
+ * XOF_WARN and XOF_XPATH.
+ */
+typedef struct xo_stack_s {
+ xo_xsf_flags_t xs_flags; /* Flags for this frame */
+ xo_state_t xs_state; /* State for this stack frame */
+ char *xs_name; /* Name (for XPath value) */
+ char *xs_keys; /* XPath predicate for any key fields */
+} xo_stack_t;
+
+/*
+ * libxo supports colors and effects, for those who like them.
+ * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
+ * ("effects") are bits since we need to maintain state.
+ */
+#define XO_COL_DEFAULT 0
+#define XO_COL_BLACK 1
+#define XO_COL_RED 2
+#define XO_COL_GREEN 3
+#define XO_COL_YELLOW 4
+#define XO_COL_BLUE 5
+#define XO_COL_MAGENTA 6
+#define XO_COL_CYAN 7
+#define XO_COL_WHITE 8
+
+#define XO_NUM_COLORS 9
+
+/*
+ * Yes, there's no blink. We're civilized. We like users. Blink
+ * isn't something one does to someone you like. Friends don't let
+ * friends use blink. On friends. You know what I mean. Blink is
+ * like, well, it's like bursting into show tunes at a funeral. It's
+ * just not done. Not something anyone wants. And on those rare
+ * instances where it might actually be appropriate, it's still wrong,
+ * since it's likely done by the wrong person for the wrong reason.
+ * Just like blink. And if I implemented blink, I'd be like a funeral
+ * director who adds "Would you like us to burst into show tunes?" on
+ * the list of questions asked while making funeral arrangements.
+ * It's formalizing wrongness in the wrong way. And we're just too
+ * civilized to do that. Hhhmph!
+ */
+#define XO_EFF_RESET (1<<0)
+#define XO_EFF_NORMAL (1<<1)
+#define XO_EFF_BOLD (1<<2)
+#define XO_EFF_UNDERLINE (1<<3)
+#define XO_EFF_INVERSE (1<<4)
+
+#define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
+
+typedef uint8_t xo_effect_t;
+typedef uint8_t xo_color_t;
+typedef struct xo_colors_s {
+ xo_effect_t xoc_effects; /* Current effect set */
+ xo_color_t xoc_col_fg; /* Foreground color */
+ xo_color_t xoc_col_bg; /* Background color */
+} xo_colors_t;
+
+/*
+ * xo_handle_t: this is the principle data structure for libxo.
+ * It's used as a store for state, options, content, and all manor
+ * of other information.
+ */
+struct xo_handle_s {
+ xo_xof_flags_t xo_flags; /* Flags (XOF_*) from the user*/
+ xo_xof_flags_t xo_iflags; /* Internal flags (XOIF_*) */
+ xo_style_t xo_style; /* XO_STYLE_* value */
+ unsigned short xo_indent; /* Indent level (if pretty) */
+ unsigned short xo_indent_by; /* Indent amount (tab stop) */
+ xo_write_func_t xo_write; /* Write callback */
+ xo_close_func_t xo_close; /* Close callback */
+ xo_flush_func_t xo_flush; /* Flush callback */
+ xo_formatter_t xo_formatter; /* Custom formating function */
+ xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
+ void *xo_opaque; /* Opaque data for write function */
+ xo_buffer_t xo_data; /* Output data */
+ xo_buffer_t xo_fmt; /* Work area for building format strings */
+ xo_buffer_t xo_attrs; /* Work area for building XML attributes */
+ xo_buffer_t xo_predicate; /* Work area for building XPath predicates */
+ xo_stack_t *xo_stack; /* Stack pointer */
+ int xo_depth; /* Depth of stack */
+ int xo_stack_size; /* Size of the stack */
+ xo_info_t *xo_info; /* Info fields for all elements */
+ int xo_info_count; /* Number of info entries */
+ va_list xo_vap; /* Variable arguments (stdargs) */
+ char *xo_leading_xpath; /* A leading XPath expression */
+ mbstate_t xo_mbstate; /* Multi-byte character conversion state */
+ unsigned xo_anchor_offset; /* Start of anchored text */
+ unsigned xo_anchor_columns; /* Number of columns since the start anchor */
+ int xo_anchor_min_width; /* Desired width of anchored text */
+ unsigned xo_units_offset; /* Start of units insertion point */
+ unsigned xo_columns; /* Columns emitted during this xo_emit call */
+ uint8_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
+ uint8_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
+ xo_colors_t xo_colors; /* Current color and effect values */
+ xo_buffer_t xo_color_buf; /* HTML: buffer of colors and effects */
+ char *xo_version; /* Version string */
+ int xo_errno; /* Saved errno for "%m" */
+ char *xo_gt_domain; /* Gettext domain, suitable for dgettext(3) */
+ xo_encoder_func_t xo_encoder; /* Encoding function */
+ void *xo_private; /* Private data for external encoders */
+};
+
+/* Flag operations */
+#define XOF_BIT_ISSET(_flag, _bit) (((_flag) & (_bit)) ? 1 : 0)
+#define XOF_BIT_SET(_flag, _bit) do { (_flag) |= (_bit); } while (0)
+#define XOF_BIT_CLEAR(_flag, _bit) do { (_flag) &= ~(_bit); } while (0)
+
+#define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
+#define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
+#define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
+
+#define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
+#define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
+#define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
+
+/* Internal flags */
+#define XOIF_REORDER XOF_BIT(0) /* Reordering fields; record field info */
+#define XOIF_DIV_OPEN XOF_BIT(1) /* A <div> is open */
+#define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
+#define XOIF_ANCHOR XOF_BIT(3) /* An anchor is in place */
+
+#define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
+#define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
+
+/* Flags for formatting functions */
+typedef unsigned long xo_xff_flags_t;
+#define XFF_COLON (1<<0) /* Append a ":" */
+#define XFF_COMMA (1<<1) /* Append a "," iff there's more output */
+#define XFF_WS (1<<2) /* Append a blank */
+#define XFF_ENCODE_ONLY (1<<3) /* Only emit for encoding styles (XML, JSON) */
+
+#define XFF_QUOTE (1<<4) /* Force quotes */
+#define XFF_NOQUOTE (1<<5) /* Force no quotes */
+#define XFF_DISPLAY_ONLY (1<<6) /* Only emit for display styles (text, html) */
+#define XFF_KEY (1<<7) /* Field is a key (for XPath) */
+
+#define XFF_XML (1<<8) /* Force XML encoding style (for XPath) */
+#define XFF_ATTR (1<<9) /* Escape value using attribute rules (XML) */
+#define XFF_BLANK_LINE (1<<10) /* Emit a blank line */
+#define XFF_NO_OUTPUT (1<<11) /* Do not make any output */
+
+#define XFF_TRIM_WS (1<<12) /* Trim whitespace off encoded values */
+#define XFF_LEAF_LIST (1<<13) /* A leaf-list (list of values) */
+#define XFF_UNESCAPE (1<<14) /* Need to printf-style unescape the value */
+#define XFF_HUMANIZE (1<<15) /* Humanize the value (for display styles) */
+
+#define XFF_HN_SPACE (1<<16) /* Humanize: put space before suffix */
+#define XFF_HN_DECIMAL (1<<17) /* Humanize: add one decimal place if <10 */
+#define XFF_HN_1000 (1<<18) /* Humanize: use 1000, not 1024 */
+#define XFF_GT_FIELD (1<<19) /* Call gettext() on a field */
+
+#define XFF_GT_PLURAL (1<<20) /* Call dngettext to find plural form */
+
+/* Flags to turn off when we don't want i18n processing */
+#define XFF_GT_FLAGS (XFF_GT_FIELD | XFF_GT_PLURAL)
+
+/*
+ * Normal printf has width and precision, which for strings operate as
+ * min and max number of columns. But this depends on the idea that
+ * one byte means one column, which UTF-8 and multi-byte characters
+ * pitches on its ear. It may take 40 bytes of data to populate 14
+ * columns, but we can't go off looking at 40 bytes of data without the
+ * caller's permission for fear/knowledge that we'll generate core files.
+ *
+ * So we make three values, distinguishing between "max column" and
+ * "number of bytes that we will inspect inspect safely" We call the
+ * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
+ *
+ * Under the "first do no harm" theory, we default "max" to "size".
+ * This is a reasonable assumption for folks that don't grok the
+ * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
+ * be evil.
+ *
+ * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
+ * columns of output, but will never look at more than 14 bytes of the
+ * input buffer. This is mostly compatible with printf and caller's
+ * expectations.
+ *
+ * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
+ * many bytes (or until a NUL is seen) are needed to fill 14 columns
+ * of output. xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
+ * to xx bytes (or until a NUL is seen) in order to fill 14 columns
+ * of output.
+ *
+ * It's fairly amazing how a good idea (handle all languages of the
+ * world) blows such a big hole in the bottom of the fairly weak boat
+ * that is C string handling. The simplicity and completenesss are
+ * sunk in ways we haven't even begun to understand.
+ */
+#define XF_WIDTH_MIN 0 /* Minimal width */
+#define XF_WIDTH_SIZE 1 /* Maximum number of bytes to examine */
+#define XF_WIDTH_MAX 2 /* Maximum width */
+#define XF_WIDTH_NUM 3 /* Numeric fields in printf (min.size.max) */
+
+/* Input and output string encodings */
+#define XF_ENC_WIDE 1 /* Wide characters (wchar_t) */
+#define XF_ENC_UTF8 2 /* UTF-8 */
+#define XF_ENC_LOCALE 3 /* Current locale */
+
+/*
+ * A place to parse printf-style format flags for each field
+ */
+typedef struct xo_format_s {
+ unsigned char xf_fc; /* Format character */
+ unsigned char xf_enc; /* Encoding of the string (XF_ENC_*) */
+ unsigned char xf_skip; /* Skip this field */
+ unsigned char xf_lflag; /* 'l' (long) */
+ unsigned char xf_hflag;; /* 'h' (half) */
+ unsigned char xf_jflag; /* 'j' (intmax_t) */
+ unsigned char xf_tflag; /* 't' (ptrdiff_t) */
+ unsigned char xf_zflag; /* 'z' (size_t) */
+ unsigned char xf_qflag; /* 'q' (quad_t) */
+ unsigned char xf_seen_minus; /* Seen a minus */
+ int xf_leading_zero; /* Seen a leading zero (zero fill) */
+ unsigned xf_dots; /* Seen one or more '.'s */
+ int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
+ unsigned xf_stars; /* Seen one or more '*'s */
+ unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
+} xo_format_t;
+
+/*
+ * This structure represents the parsed field information, suitable for
+ * processing by xo_do_emit and anything else that needs to parse fields.
+ * Note that all pointers point to the main format string.
+ *
+ * XXX This is a first step toward compilable or cachable format
+ * strings. We can also cache the results of dgettext when no format
+ * is used, assuming the 'p' modifier has _not_ been set.
+ */
+typedef struct xo_field_info_s {
+ xo_xff_flags_t xfi_flags; /* Flags for this field */
+ unsigned xfi_ftype; /* Field type, as character (e.g. 'V') */
+ const char *xfi_start; /* Start of field in the format string */
+ const char *xfi_content; /* Field's content */
+ const char *xfi_format; /* Field's Format */
+ const char *xfi_encoding; /* Field's encoding format */
+ const char *xfi_next; /* Next character in format string */
+ unsigned xfi_len; /* Length of field */
+ unsigned xfi_clen; /* Content length */
+ unsigned xfi_flen; /* Format length */
+ unsigned xfi_elen; /* Encoding length */
+ unsigned xfi_fnum; /* Field number (if used; 0 otherwise) */
+ unsigned xfi_renum; /* Reordered number (0 == no renumbering) */
+} xo_field_info_t;
+
+/*
+ * We keep a 'default' handle to allow callers to avoid having to
+ * allocate one. Passing NULL to any of our functions will use
+ * this default handle. Most functions have a variant that doesn't
+ * require a handle at all, since most output is to stdout, which
+ * the default handle handles handily.
+ */
+static THREAD_LOCAL(xo_handle_t) xo_default_handle;
+static THREAD_LOCAL(int) xo_default_inited;
+static int xo_locale_inited;
+static const char *xo_program;
+
+/*
+ * To allow libxo to be used in diverse environment, we allow the
+ * caller to give callbacks for memory allocation.
+ */
+xo_realloc_func_t xo_realloc = realloc;
+xo_free_func_t xo_free = free;
+
+/* Forward declarations */
+static void
+xo_failure (xo_handle_t *xop, const char *fmt, ...);
+
+static int
+xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
+ xo_state_t new_state);
+
+static void
+xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
+ const char *name, int nlen,
+ const char *value, int vlen,
+ const char *encoding, int elen);
+
+static void
+xo_anchor_clear (xo_handle_t *xop);
+
+/*
+ * xo_style is used to retrieve the current style. When we're built
+ * for "text only" mode, we use this function to drive the removal
+ * of most of the code in libxo. We return a constant and the compiler
+ * happily removes the non-text code that is not longer executed. This
+ * trims our code nicely without needing to trampel perfectly readable
+ * code with ifdefs.
+ */
+static inline xo_style_t
+xo_style (xo_handle_t *xop UNUSED)
+{
+#ifdef LIBXO_TEXT_ONLY
+ return XO_STYLE_TEXT;
+#else /* LIBXO_TEXT_ONLY */
+ return xop->xo_style;
+#endif /* LIBXO_TEXT_ONLY */
+}
+
+/*
+ * Callback to write data to a FILE pointer
+ */
+static int
+xo_write_to_file (void *opaque, const char *data)
+{
+ FILE *fp = (FILE *) opaque;
+
+ return fprintf(fp, "%s", data);
+}
+
+/*
+ * Callback to close a file
+ */
+static void
+xo_close_file (void *opaque)
+{
+ FILE *fp = (FILE *) opaque;
+
+ fclose(fp);
+}
+
+/*
+ * Callback to flush a FILE pointer
+ */
+static int
+xo_flush_file (void *opaque)
+{
+ FILE *fp = (FILE *) opaque;
+
+ return fflush(fp);
+}
+
+/*
+ * Use a rotating stock of buffers to make a printable string
+ */
+#define XO_NUMBUFS 8
+#define XO_SMBUFSZ 128
+
+static const char *
+xo_printable (const char *str)
+{
+ static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
+ static THREAD_LOCAL(int) bufnum = 0;
+
+ if (str == NULL)
+ return "";
+
+ if (++bufnum == XO_NUMBUFS)
+ bufnum = 0;
+
+ char *res = bufset[bufnum], *cp, *ep;
+
+ for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
+ if (*str == '\n') {
+ *cp++ = '\\';
+ *cp = 'n';
+ } else if (*str == '\r') {
+ *cp++ = '\\';
+ *cp = 'r';
+ } else if (*str == '\"') {
+ *cp++ = '\\';
+ *cp = '"';
+ } else
+ *cp = *str;
+ }
+
+ *cp = '\0';
+ return res;
+}
+
+static int
+xo_depth_check (xo_handle_t *xop, int depth)
+{
+ xo_stack_t *xsp;
+
+ if (depth >= xop->xo_stack_size) {
+ depth += XO_DEPTH; /* Extra room */
+
+ xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
+ if (xsp == NULL) {
+ xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
+ return -1;
+ }
+
+ int count = depth - xop->xo_stack_size;
+
+ bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
+ xop->xo_stack_size = depth;
+ xop->xo_stack = xsp;
+ }
+
+ return 0;
+}
+
+void
+xo_no_setlocale (void)
+{
+ xo_locale_inited = 1; /* Skip initialization */
+}
+
+/*
+ * We need to decide if stdout is line buffered (_IOLBF). Lacking a
+ * standard way to decide this (e.g. getlinebuf()), we have configure
+ * look to find __flbf, which glibc supported. If not, we'll rely on
+ * isatty, with the assumption that terminals are the only thing
+ * that's line buffered. We _could_ test for "steam._flags & _IOLBF",
+ * which is all __flbf does, but that's even tackier. Like a
+ * bedazzled Elvis outfit on an ugly lap dog sort of tacky. Not
+ * something we're willing to do.
+ */
+static int
+xo_is_line_buffered (FILE *stream)
+{
+#if HAVE___FLBF
+ if (__flbf(stream))
+ return 1;
+#else /* HAVE___FLBF */
+ if (isatty(fileno(stream)))
+ return 1;
+#endif /* HAVE___FLBF */
+ return 0;
+}
+
+/*
+ * Initialize an xo_handle_t, using both static defaults and
+ * the global settings from the LIBXO_OPTIONS environment
+ * variable.
+ */
+static void
+xo_init_handle (xo_handle_t *xop)
+{
+ xop->xo_opaque = stdout;
+ xop->xo_write = xo_write_to_file;
+ xop->xo_flush = xo_flush_file;
+
+ if (xo_is_line_buffered(stdout))
+ XOF_SET(xop, XOF_FLUSH_LINE);
+
+ /*
+ * We only want to do color output on terminals, but we only want
+ * to do this if the user has asked for color.
+ */
+ if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
+ XOF_SET(xop, XOF_COLOR);
+
+ /*
+ * We need to initialize the locale, which isn't really pretty.
+ * Libraries should depend on their caller to set up the
+ * environment. But we really can't count on the caller to do
+ * this, because well, they won't. Trust me.
+ */
+ if (!xo_locale_inited) {
+ xo_locale_inited = 1; /* Only do this once */
+
+ const char *cp = getenv("LC_CTYPE");
+ if (cp == NULL)
+ cp = getenv("LANG");
+ if (cp == NULL)
+ cp = getenv("LC_ALL");
+ if (cp == NULL)
+ cp = "C"; /* Default for C programs */
+ (void) setlocale(LC_CTYPE, cp);
+ }
+
+ /*
+ * Initialize only the xo_buffers we know we'll need; the others
+ * can be allocated as needed.
+ */
+ xo_buf_init(&xop->xo_data);
+ xo_buf_init(&xop->xo_fmt);
+
+ if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
+ return;
+ XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
+
+ xop->xo_indent_by = XO_INDENT_BY;
+ xo_depth_check(xop, XO_DEPTH);
+
+#if !defined(NO_LIBXO_OPTIONS)
+ if (!XOF_ISSET(xop, XOF_NO_ENV)) {
+ char *env = getenv("LIBXO_OPTIONS");
+ if (env)
+ xo_set_options(xop, env);
+
+ }
+#endif /* NO_GETENV */
+
+ XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
+}
+
+/*
+ * Initialize the default handle.
+ */
+static void
+xo_default_init (void)
+{
+ xo_handle_t *xop = &xo_default_handle;
+
+ xo_init_handle(xop);
+
+ xo_default_inited = 1;
+}
+
+/*
+ * Cheap convenience function to return either the argument, or
+ * the internal handle, after it has been initialized. The usage
+ * is:
+ * xop = xo_default(xop);
+ */
+static xo_handle_t *
+xo_default (xo_handle_t *xop)
+{
+ if (xop == NULL) {
+ if (xo_default_inited == 0)
+ xo_default_init();
+ xop = &xo_default_handle;
+ }
+
+ return xop;
+}
+
+/*
+ * Return the number of spaces we should be indenting. If
+ * we are pretty-printing, this is indent * indent_by.
+ */
+static int
+xo_indent (xo_handle_t *xop)
+{
+ int rc = 0;
+
+ xop = xo_default(xop);
+
+ if (XOF_ISSET(xop, XOF_PRETTY)) {
+ rc = xop->xo_indent * xop->xo_indent_by;
+ if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
+ rc += xop->xo_indent_by;
+ }
+
+ return (rc > 0) ? rc : 0;
+}
+
+static void
+xo_buf_indent (xo_handle_t *xop, int indent)
+{
+ xo_buffer_t *xbp = &xop->xo_data;
+
+ if (indent <= 0)
+ indent = xo_indent(xop);
+
+ if (!xo_buf_has_room(xbp, indent))
+ return;
+
+ memset(xbp->xb_curp, ' ', indent);
+ xbp->xb_curp += indent;
+}
+
+static char xo_xml_amp[] = "&amp;";
+static char xo_xml_lt[] = "&lt;";
+static char xo_xml_gt[] = "&gt;";
+static char xo_xml_quot[] = "&quot;";
+
+static int
+xo_escape_xml (xo_buffer_t *xbp, int len, xo_xff_flags_t flags)
+{
+ int slen;
+ unsigned delta = 0;
+ char *cp, *ep, *ip;
+ const char *sp;
+ int attr = (flags & XFF_ATTR);
+
+ for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
+ /* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
+ if (*cp == '<')
+ delta += sizeof(xo_xml_lt) - 2;
+ else if (*cp == '>')
+ delta += sizeof(xo_xml_gt) - 2;
+ else if (*cp == '&')
+ delta += sizeof(xo_xml_amp) - 2;
+ else if (attr && *cp == '"')
+ delta += sizeof(xo_xml_quot) - 2;
+ }
+
+ if (delta == 0) /* Nothing to escape; bail */
+ return len;
+
+ if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
+ return 0;
+
+ ep = xbp->xb_curp;
+ cp = ep + len;
+ ip = cp + delta;
+ do {
+ cp -= 1;
+ ip -= 1;
+
+ if (*cp == '<')
+ sp = xo_xml_lt;
+ else if (*cp == '>')
+ sp = xo_xml_gt;
+ else if (*cp == '&')
+ sp = xo_xml_amp;
+ else if (attr && *cp == '"')
+ sp = xo_xml_quot;
+ else {
+ *ip = *cp;
+ continue;
+ }
+
+ slen = strlen(sp);
+ ip -= slen - 1;
+ memcpy(ip, sp, slen);
+
+ } while (cp > ep && cp != ip);
+
+ return len + delta;
+}
+
+static int
+xo_escape_json (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
+{
+ unsigned delta = 0;
+ char *cp, *ep, *ip;
+
+ for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
+ if (*cp == '\\' || *cp == '"')
+ delta += 1;
+ else if (*cp == '\n' || *cp == '\r')
+ delta += 1;
+ }
+
+ if (delta == 0) /* Nothing to escape; bail */
+ return len;
+
+ if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
+ return 0;
+
+ ep = xbp->xb_curp;
+ cp = ep + len;
+ ip = cp + delta;
+ do {
+ cp -= 1;
+ ip -= 1;
+
+ if (*cp == '\\' || *cp == '"') {
+ *ip-- = *cp;
+ *ip = '\\';
+ } else if (*cp == '\n') {
+ *ip-- = 'n';
+ *ip = '\\';
+ } else if (*cp == '\r') {
+ *ip-- = 'r';
+ *ip = '\\';
+ } else {
+ *ip = *cp;
+ }
+
+ } while (cp > ep && cp != ip);
+
+ return len + delta;
+}
+
+/*
+ * PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
+ * ; ']' MUST be escaped.
+ */
+static int
+xo_escape_sdparams (xo_buffer_t *xbp, int len, xo_xff_flags_t flags UNUSED)
+{
+ unsigned delta = 0;
+ char *cp, *ep, *ip;
+
+ for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
+ if (*cp == '\\' || *cp == '"' || *cp == ']')
+ delta += 1;
+ }
+
+ if (delta == 0) /* Nothing to escape; bail */
+ return len;
+
+ if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
+ return 0;
+
+ ep = xbp->xb_curp;
+ cp = ep + len;
+ ip = cp + delta;
+ do {
+ cp -= 1;
+ ip -= 1;
+
+ if (*cp == '\\' || *cp == '"' || *cp == ']') {
+ *ip-- = *cp;
+ *ip = '\\';
+ } else {
+ *ip = *cp;
+ }
+
+ } while (cp > ep && cp != ip);
+
+ return len + delta;
+}
+
+static void
+xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
+ const char *str, int len, xo_xff_flags_t flags)
+{
+ if (!xo_buf_has_room(xbp, len))
+ return;
+
+ memcpy(xbp->xb_curp, str, len);
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ case XO_STYLE_HTML:
+ len = xo_escape_xml(xbp, len, flags);
+ break;
+
+ case XO_STYLE_JSON:
+ len = xo_escape_json(xbp, len, flags);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ len = xo_escape_sdparams(xbp, len, flags);
+ break;
+ }
+
+ xbp->xb_curp += len;
+}
+
+/*
+ * Write the current contents of the data buffer using the handle's
+ * xo_write function.
+ */
+static int
+xo_write (xo_handle_t *xop)
+{
+ int rc = 0;
+ xo_buffer_t *xbp = &xop->xo_data;
+
+ if (xbp->xb_curp != xbp->xb_bufp) {
+ xo_buf_append(xbp, "", 1); /* Append ending NUL */
+ xo_anchor_clear(xop);
+ if (xop->xo_write)
+ rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
+ xbp->xb_curp = xbp->xb_bufp;
+ }
+
+ /* Turn off the flags that don't survive across writes */
+ XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
+
+ return rc;
+}
+
+/*
+ * Format arguments into our buffer. If a custom formatter has been set,
+ * we use that to do the work; otherwise we vsnprintf().
+ */
+static int
+xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
+{
+ va_list va_local;
+ int rc;
+ int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+
+ va_copy(va_local, vap);
+
+ if (xop->xo_formatter)
+ rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
+ else
+ rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
+
+ if (rc >= left) {
+ if (!xo_buf_has_room(xbp, rc)) {
+ va_end(va_local);
+ return -1;
+ }
+
+ /*
+ * After we call vsnprintf(), the stage of vap is not defined.
+ * We need to copy it before we pass. Then we have to do our
+ * own logic below to move it along. This is because the
+ * implementation can have va_list be a pointer (bsd) or a
+ * structure (macosx) or anything in between.
+ */
+
+ va_end(va_local); /* Reset vap to the start */
+ va_copy(va_local, vap);
+
+ left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ if (xop->xo_formatter)
+ rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
+ else
+ rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
+ }
+ va_end(va_local);
+
+ return rc;
+}
+
+/*
+ * Print some data thru the handle.
+ */
+static int
+xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
+{
+ xo_buffer_t *xbp = &xop->xo_data;
+ int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ int rc;
+ va_list va_local;
+
+ va_copy(va_local, vap);
+
+ rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
+
+ if (rc >= left) {
+ if (!xo_buf_has_room(xbp, rc)) {
+ va_end(va_local);
+ return -1;
+ }
+
+ va_end(va_local); /* Reset vap to the start */
+ va_copy(va_local, vap);
+
+ left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
+ }
+
+ va_end(va_local);
+
+ if (rc > 0)
+ xbp->xb_curp += rc;
+
+ return rc;
+}
+
+static int
+xo_printf (xo_handle_t *xop, const char *fmt, ...)
+{
+ int rc;
+ va_list vap;
+
+ va_start(vap, fmt);
+
+ rc = xo_printf_v(xop, fmt, vap);
+
+ va_end(vap);
+ return rc;
+}
+
+/*
+ * These next few function are make The Essential UTF-8 Ginsu Knife.
+ * Identify an input and output character, and convert it.
+ */
+static int xo_utf8_bits[7] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
+
+static int
+xo_is_utf8 (char ch)
+{
+ return (ch & 0x80);
+}
+
+static int
+xo_utf8_to_wc_len (const char *buf)
+{
+ unsigned b = (unsigned char) *buf;
+ int len;
+
+ if ((b & 0x80) == 0x0)
+ len = 1;
+ else if ((b & 0xe0) == 0xc0)
+ len = 2;
+ else if ((b & 0xf0) == 0xe0)
+ len = 3;
+ else if ((b & 0xf8) == 0xf0)
+ len = 4;
+ else if ((b & 0xfc) == 0xf8)
+ len = 5;
+ else if ((b & 0xfe) == 0xfc)
+ len = 6;
+ else
+ len = -1;
+
+ return len;
+}
+
+static int
+xo_buf_utf8_len (xo_handle_t *xop, const char *buf, int bufsiz)
+{
+
+ unsigned b = (unsigned char) *buf;
+ int len, i;
+
+ len = xo_utf8_to_wc_len(buf);
+ if (len == -1) {
+ xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
+ return -1;
+ }
+
+ if (len > bufsiz) {
+ xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
+ b, len, bufsiz);
+ return -1;
+ }
+
+ for (i = 2; i < len; i++) {
+ b = (unsigned char ) buf[i];
+ if ((b & 0xc0) != 0x80) {
+ xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
+ return -1;
+ }
+ }
+
+ return len;
+}
+
+/*
+ * Build a wide character from the input buffer; the number of
+ * bits we pull off the first character is dependent on the length,
+ * but we put 6 bits off all other bytes.
+ */
+static wchar_t
+xo_utf8_char (const char *buf, int len)
+{
+ int i;
+ wchar_t wc;
+ const unsigned char *cp = (const unsigned char *) buf;
+
+ wc = *cp & xo_utf8_bits[len];
+ for (i = 1; i < len; i++) {
+ wc <<= 6;
+ wc |= cp[i] & 0x3f;
+ if ((cp[i] & 0xc0) != 0x80)
+ return (wchar_t) -1;
+ }
+
+ return wc;
+}
+
+/*
+ * Determine the number of bytes needed to encode a wide character.
+ */
+static int
+xo_utf8_emit_len (wchar_t wc)
+{
+ int len;
+
+ if ((wc & ((1<<7) - 1)) == wc) /* Simple case */
+ len = 1;
+ else if ((wc & ((1<<11) - 1)) == wc)
+ len = 2;
+ else if ((wc & ((1<<16) - 1)) == wc)
+ len = 3;
+ else if ((wc & ((1<<21) - 1)) == wc)
+ len = 4;
+ else if ((wc & ((1<<26) - 1)) == wc)
+ len = 5;
+ else
+ len = 6;
+
+ return len;
+}
+
+static void
+xo_utf8_emit_char (char *buf, int len, wchar_t wc)
+{
+ int i;
+
+ if (len == 1) { /* Simple case */
+ buf[0] = wc & 0x7f;
+ return;
+ }
+
+ for (i = len - 1; i >= 0; i--) {
+ buf[i] = 0x80 | (wc & 0x3f);
+ wc >>= 6;
+ }
+
+ buf[0] &= xo_utf8_bits[len];
+ buf[0] |= ~xo_utf8_bits[len] << 1;
+}
+
+static int
+xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
+ const char *ibuf, int ilen)
+{
+ wchar_t wc;
+ int len;
+
+ /*
+ * Build our wide character from the input buffer; the number of
+ * bits we pull off the first character is dependent on the length,
+ * but we put 6 bits off all other bytes.
+ */
+ wc = xo_utf8_char(ibuf, ilen);
+ if (wc == (wchar_t) -1) {
+ xo_failure(xop, "invalid utf-8 byte sequence");
+ return 0;
+ }
+
+ if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
+ if (!xo_buf_has_room(xbp, ilen))
+ return 0;
+
+ memcpy(xbp->xb_curp, ibuf, ilen);
+ xbp->xb_curp += ilen;
+
+ } else {
+ if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
+ return 0;
+
+ bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
+ len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
+
+ if (len <= 0) {
+ xo_failure(xop, "could not convert wide char: %lx",
+ (unsigned long) wc);
+ return 0;
+ }
+ xbp->xb_curp += len;
+ }
+
+ return xo_wcwidth(wc);
+}
+
+static void
+xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
+ const char *cp, int len)
+{
+ const char *sp = cp, *ep = cp + len;
+ unsigned save_off = xbp->xb_bufp - xbp->xb_curp;
+ int slen;
+ int cols = 0;
+
+ for ( ; cp < ep; cp++) {
+ if (!xo_is_utf8(*cp)) {
+ cols += 1;
+ continue;
+ }
+
+ /*
+ * We're looking at a non-ascii UTF-8 character.
+ * First we copy the previous data.
+ * Then we need find the length and validate it.
+ * Then we turn it into a wide string.
+ * Then we turn it into a localized string.
+ * Then we repeat. Isn't i18n fun?
+ */
+ if (sp != cp)
+ xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
+
+ slen = xo_buf_utf8_len(xop, cp, ep - cp);
+ if (slen <= 0) {
+ /* Bad data; back it all out */
+ xbp->xb_curp = xbp->xb_bufp + save_off;
+ return;
+ }
+
+ cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
+
+ /* Next time thru, we'll start at the next character */
+ cp += slen - 1;
+ sp = cp + 1;
+ }
+
+ /* Update column values */
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += cols;
+
+ /* Before we fall into the basic logic below, we need reset len */
+ len = ep - sp;
+ if (len != 0) /* Append trailing data */
+ xo_buf_append(xbp, sp, len);
+}
+
+/*
+ * Append the given string to the given buffer, without escaping or
+ * character set conversion. This is the straight copy to the data
+ * buffer with no fanciness.
+ */
+static void
+xo_data_append (xo_handle_t *xop, const char *str, int len)
+{
+ xo_buf_append(&xop->xo_data, str, len);
+}
+
+/*
+ * Append the given string to the given buffer
+ */
+static void
+xo_data_escape (xo_handle_t *xop, const char *str, int len)
+{
+ xo_buf_escape(xop, &xop->xo_data, str, len, 0);
+}
+
+/*
+ * Generate a warning. Normally, this is a text message written to
+ * standard error. If the XOF_WARN_XML flag is set, then we generate
+ * XMLified content on standard output.
+ */
+static void
+xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
+ const char *fmt, va_list vap)
+{
+ xop = xo_default(xop);
+ if (check_warn && !XOF_ISSET(xop, XOF_WARN))
+ return;
+
+ if (fmt == NULL)
+ return;
+
+ int len = strlen(fmt);
+ int plen = xo_program ? strlen(xo_program) : 0;
+ char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
+
+ if (plen) {
+ memcpy(newfmt, xo_program, plen);
+ newfmt[plen++] = ':';
+ newfmt[plen++] = ' ';
+ }
+ memcpy(newfmt + plen, fmt, len);
+ newfmt[len + plen] = '\0';
+
+ if (XOF_ISSET(xop, XOF_WARN_XML)) {
+ static char err_open[] = "<error>";
+ static char err_close[] = "</error>";
+ static char msg_open[] = "<message>";
+ static char msg_close[] = "</message>";
+
+ xo_buffer_t *xbp = &xop->xo_data;
+
+ xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
+ xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
+
+ va_list va_local;
+ va_copy(va_local, vap);
+
+ int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ int rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
+ if (rc >= left) {
+ if (!xo_buf_has_room(xbp, rc)) {
+ va_end(va_local);
+ return;
+ }
+
+ va_end(vap); /* Reset vap to the start */
+ va_copy(vap, va_local);
+
+ left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
+ }
+ va_end(va_local);
+
+ rc = xo_escape_xml(xbp, rc, 1);
+ xbp->xb_curp += rc;
+
+ xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
+ xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
+
+ if (code >= 0) {
+ const char *msg = strerror(code);
+ if (msg) {
+ xo_buf_append(xbp, ": ", 2);
+ xo_buf_append(xbp, msg, strlen(msg));
+ }
+ }
+
+ xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
+ (void) xo_write(xop);
+
+ } else {
+ vfprintf(stderr, newfmt, vap);
+ if (code >= 0) {
+ const char *msg = strerror(code);
+ if (msg)
+ fprintf(stderr, ": %s", msg);
+ }
+ fprintf(stderr, "\n");
+ }
+}
+
+void
+xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(xop, code, 0, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_warn_c (int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, code, 0, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_warn (const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, code, 0, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_warnx (const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, -1, 0, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_err (int eval, const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, code, 0, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
+
+void
+xo_errx (int eval, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, -1, 0, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
+
+void
+xo_errc (int eval, int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(NULL, code, 0, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
+
+/*
+ * Generate a warning. Normally, this is a text message written to
+ * standard error. If the XOF_WARN_XML flag is set, then we generate
+ * XMLified content on standard output.
+ */
+void
+xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
+{
+ static char msg_open[] = "<message>";
+ static char msg_close[] = "</message>";
+ xo_buffer_t *xbp;
+ int rc;
+ va_list va_local;
+
+ xop = xo_default(xop);
+
+ if (fmt == NULL || *fmt == '\0')
+ return;
+
+ int need_nl = (fmt[strlen(fmt) - 1] != '\n');
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ xbp = &xop->xo_data;
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_buf_indent(xop, xop->xo_indent_by);
+ xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
+
+ va_copy(va_local, vap);
+
+ int left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
+ if (rc >= left) {
+ if (!xo_buf_has_room(xbp, rc)) {
+ va_end(va_local);
+ return;
+ }
+
+ va_end(vap); /* Reset vap to the start */
+ va_copy(vap, va_local);
+
+ left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
+ }
+ va_end(va_local);
+
+ rc = xo_escape_xml(xbp, rc, 0);
+ xbp->xb_curp += rc;
+
+ if (need_nl && code > 0) {
+ const char *msg = strerror(code);
+ if (msg) {
+ xo_buf_append(xbp, ": ", 2);
+ xo_buf_append(xbp, msg, strlen(msg));
+ }
+ }
+
+ if (need_nl)
+ xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
+
+ xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
+
+ (void) xo_write(xop);
+ break;
+
+ case XO_STYLE_HTML:
+ {
+ char buf[BUFSIZ], *bp = buf, *cp;
+ int bufsiz = sizeof(buf);
+ int rc2;
+
+ va_copy(va_local, vap);
+
+ rc = vsnprintf(bp, bufsiz, fmt, va_local);
+ if (rc > bufsiz) {
+ bufsiz = rc + BUFSIZ;
+ bp = alloca(bufsiz);
+ va_end(va_local);
+ va_copy(va_local, vap);
+ rc = vsnprintf(bp, bufsiz, fmt, va_local);
+ }
+ va_end(va_local);
+ cp = bp + rc;
+
+ if (need_nl) {
+ rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
+ (code > 0) ? ": " : "",
+ (code > 0) ? strerror(code) : "");
+ if (rc2 > 0)
+ rc += rc2;
+ }
+
+ xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc, NULL, 0);
+ }
+ break;
+
+ case XO_STYLE_JSON:
+ case XO_STYLE_SDPARAMS:
+ case XO_STYLE_ENCODER:
+ /* No means of representing messages */
+ return;
+
+ case XO_STYLE_TEXT:
+ rc = xo_printf_v(xop, fmt, vap);
+ /*
+ * XXX need to handle UTF-8 widths
+ */
+ if (rc > 0) {
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += rc;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += rc;
+ }
+
+ if (need_nl && code > 0) {
+ const char *msg = strerror(code);
+ if (msg) {
+ xo_printf(xop, ": %s", msg);
+ }
+ }
+ if (need_nl)
+ xo_printf(xop, "\n");
+
+ break;
+ }
+
+ (void) xo_flush_h(xop);
+}
+
+void
+xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_message_hcv(xop, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_message_c (int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_message_hcv(NULL, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_message_e (const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_message_hcv(NULL, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_message (const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_message_hcv(NULL, 0, fmt, vap);
+ va_end(vap);
+}
+
+static void
+xo_failure (xo_handle_t *xop, const char *fmt, ...)
+{
+ if (!XOF_ISSET(xop, XOF_WARN))
+ return;
+
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_warn_hcv(xop, -1, 1, fmt, vap);
+ va_end(vap);
+}
+
+/**
+ * Create a handle for use by later libxo functions.
+ *
+ * Note: normal use of libxo does not require a distinct handle, since
+ * the default handle (used when NULL is passed) generates text on stdout.
+ *
+ * @style Style of output desired (XO_STYLE_* value)
+ * @flags Set of XOF_* flags in use with this handle
+ */
+xo_handle_t *
+xo_create (xo_style_t style, xo_xof_flags_t flags)
+{
+ xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
+
+ if (xop) {
+ bzero(xop, sizeof(*xop));
+
+ xop->xo_style = style;
+ XOF_SET(xop, flags);
+ xo_init_handle(xop);
+ xop->xo_style = style; /* Reset style (see LIBXO_OPTIONS) */
+ }
+
+ return xop;
+}
+
+/**
+ * Create a handle that will write to the given file. Use
+ * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
+ * @fp FILE pointer to use
+ * @style Style of output desired (XO_STYLE_* value)
+ * @flags Set of XOF_* flags to use with this handle
+ */
+xo_handle_t *
+xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
+{
+ xo_handle_t *xop = xo_create(style, flags);
+
+ if (xop) {
+ xop->xo_opaque = fp;
+ xop->xo_write = xo_write_to_file;
+ xop->xo_close = xo_close_file;
+ xop->xo_flush = xo_flush_file;
+ }
+
+ return xop;
+}
+
+/**
+ * Release any resources held by the handle.
+ * @xop XO handle to alter (or NULL for default handle)
+ */
+void
+xo_destroy (xo_handle_t *xop_arg)
+{
+ xo_handle_t *xop = xo_default(xop_arg);
+
+ xo_flush_h(xop);
+
+ if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
+ xop->xo_close(xop->xo_opaque);
+
+ xo_free(xop->xo_stack);
+ xo_buf_cleanup(&xop->xo_data);
+ xo_buf_cleanup(&xop->xo_fmt);
+ xo_buf_cleanup(&xop->xo_predicate);
+ xo_buf_cleanup(&xop->xo_attrs);
+ xo_buf_cleanup(&xop->xo_color_buf);
+
+ if (xop->xo_version)
+ xo_free(xop->xo_version);
+
+ if (xop_arg == NULL) {
+ bzero(&xo_default_handle, sizeof(xo_default_handle));
+ xo_default_inited = 0;
+ } else
+ xo_free(xop);
+}
+
+/**
+ * Record a new output style to use for the given handle (or default if
+ * handle is NULL). This output style will be used for any future output.
+ *
+ * @xop XO handle to alter (or NULL for default handle)
+ * @style new output style (XO_STYLE_*)
+ */
+void
+xo_set_style (xo_handle_t *xop, xo_style_t style)
+{
+ xop = xo_default(xop);
+ xop->xo_style = style;
+}
+
+xo_style_t
+xo_get_style (xo_handle_t *xop)
+{
+ xop = xo_default(xop);
+ return xo_style(xop);
+}
+
+static int
+xo_name_to_style (const char *name)
+{
+ if (strcmp(name, "xml") == 0)
+ return XO_STYLE_XML;
+ else if (strcmp(name, "json") == 0)
+ return XO_STYLE_JSON;
+ else if (strcmp(name, "encoder") == 0)
+ return XO_STYLE_ENCODER;
+ else if (strcmp(name, "text") == 0)
+ return XO_STYLE_TEXT;
+ else if (strcmp(name, "html") == 0)
+ return XO_STYLE_HTML;
+ else if (strcmp(name, "sdparams") == 0)
+ return XO_STYLE_SDPARAMS;
+
+ return -1;
+}
+
+/*
+ * Indicate if the style is an "encoding" one as opposed to a "display" one.
+ */
+static int
+xo_style_is_encoding (xo_handle_t *xop)
+{
+ if (xo_style(xop) == XO_STYLE_JSON
+ || xo_style(xop) == XO_STYLE_XML
+ || xo_style(xop) == XO_STYLE_SDPARAMS
+ || xo_style(xop) == XO_STYLE_ENCODER)
+ return 1;
+ return 0;
+}
+
+/* Simple name-value mapping */
+typedef struct xo_mapping_s {
+ xo_xff_flags_t xm_value;
+ const char *xm_name;
+} xo_mapping_t;
+
+static xo_xff_flags_t
+xo_name_lookup (xo_mapping_t *map, const char *value, int len)
+{
+ if (len == 0)
+ return 0;
+
+ if (len < 0)
+ len = strlen(value);
+
+ while (isspace((int) *value)) {
+ value += 1;
+ len -= 1;
+ }
+
+ while (isspace((int) value[len]))
+ len -= 1;
+
+ if (*value == '\0')
+ return 0;
+
+ for ( ; map->xm_name; map++)
+ if (strncmp(map->xm_name, value, len) == 0)
+ return map->xm_value;
+
+ return 0;
+}
+
+#ifdef NOT_NEEDED_YET
+static const char *
+xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
+{
+ if (value == 0)
+ return NULL;
+
+ for ( ; map->xm_name; map++)
+ if (map->xm_value == value)
+ return map->xm_name;
+
+ return NULL;
+}
+#endif /* NOT_NEEDED_YET */
+
+static xo_mapping_t xo_xof_names[] = {
+ { XOF_COLOR_ALLOWED, "color" },
+ { XOF_COLUMNS, "columns" },
+ { XOF_DTRT, "dtrt" },
+ { XOF_FLUSH, "flush" },
+ { XOF_IGNORE_CLOSE, "ignore-close" },
+ { XOF_INFO, "info" },
+ { XOF_KEYS, "keys" },
+ { XOF_LOG_GETTEXT, "log-gettext" },
+ { XOF_LOG_SYSLOG, "log-syslog" },
+ { XOF_NO_HUMANIZE, "no-humanize" },
+ { XOF_NO_LOCALE, "no-locale" },
+ { XOF_NO_TOP, "no-top" },
+ { XOF_NOT_FIRST, "not-first" },
+ { XOF_PRETTY, "pretty" },
+ { XOF_UNDERSCORES, "underscores" },
+ { XOF_UNITS, "units" },
+ { XOF_WARN, "warn" },
+ { XOF_WARN_XML, "warn-xml" },
+ { XOF_XPATH, "xpath" },
+ { 0, NULL }
+};
+
+/*
+ * Convert string name to XOF_* flag value.
+ * Not all are useful. Or safe. Or sane.
+ */
+static unsigned
+xo_name_to_flag (const char *name)
+{
+ return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
+}
+
+int
+xo_set_style_name (xo_handle_t *xop, const char *name)
+{
+ if (name == NULL)
+ return -1;
+
+ int style = xo_name_to_style(name);
+ if (style < 0)
+ return -1;
+
+ xo_set_style(xop, style);
+ return 0;
+}
+
+/*
+ * Set the options for a handle using a string of options
+ * passed in. The input is a comma-separated set of names
+ * and optional values: "xml,pretty,indent=4"
+ */
+int
+xo_set_options (xo_handle_t *xop, const char *input)
+{
+ char *cp, *ep, *vp, *np, *bp;
+ int style = -1, new_style, len, rc = 0;
+ xo_xof_flags_t new_flag;
+
+ if (input == NULL)
+ return 0;
+
+ xop = xo_default(xop);
+
+#ifdef LIBXO_COLOR_ON_BY_DEFAULT
+ /* If the installer used --enable-color-on-by-default, then we allow it */
+ XOF_SET(xop, XOF_COLOR_ALLOWED);
+#endif /* LIBXO_COLOR_ON_BY_DEFAULT */
+
+ /*
+ * We support a simpler, old-school style of giving option
+ * also, using a single character for each option. It's
+ * ideal for lazy people, such as myself.
+ */
+ if (*input == ':') {
+ int sz;
+
+ for (input++ ; *input; input++) {
+ switch (*input) {
+ case 'c':
+ XOF_SET(xop, XOF_COLOR_ALLOWED);
+ break;
+
+ case 'f':
+ XOF_SET(xop, XOF_FLUSH);
+ break;
+
+ case 'F':
+ XOF_SET(xop, XOF_FLUSH_LINE);
+ break;
+
+ case 'g':
+ XOF_SET(xop, XOF_LOG_GETTEXT);
+ break;
+
+ case 'H':
+ xop->xo_style = XO_STYLE_HTML;
+ break;
+
+ case 'I':
+ XOF_SET(xop, XOF_INFO);
+ break;
+
+ case 'i':
+ sz = strspn(input + 1, "0123456789");
+ if (sz > 0) {
+ xop->xo_indent_by = atoi(input + 1);
+ input += sz - 1; /* Skip value */
+ }
+ break;
+
+ case 'J':
+ xop->xo_style = XO_STYLE_JSON;
+ break;
+
+ case 'k':
+ XOF_SET(xop, XOF_KEYS);
+ break;
+
+ case 'n':
+ XOF_SET(xop, XOF_NO_HUMANIZE);
+ break;
+
+ case 'P':
+ XOF_SET(xop, XOF_PRETTY);
+ break;
+
+ case 'T':
+ xop->xo_style = XO_STYLE_TEXT;
+ break;
+
+ case 'U':
+ XOF_SET(xop, XOF_UNITS);
+ break;
+
+ case 'u':
+ XOF_SET(xop, XOF_UNDERSCORES);
+ break;
+
+ case 'W':
+ XOF_SET(xop, XOF_WARN);
+ break;
+
+ case 'X':
+ xop->xo_style = XO_STYLE_XML;
+ break;
+
+ case 'x':
+ XOF_SET(xop, XOF_XPATH);
+ break;
+ }
+ }
+ return 0;
+ }
+
+ len = strlen(input) + 1;
+ bp = alloca(len);
+ memcpy(bp, input, len);
+
+ for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+
+ vp = strchr(cp, '=');
+ if (vp)
+ *vp++ = '\0';
+
+ if (strcmp("colors", cp) == 0) {
+ /* XXX Look for colors=red-blue+green-yellow */
+ continue;
+ }
+
+ /*
+ * For options, we don't allow "encoder" since we want to
+ * handle it explicitly below as "encoder=xxx".
+ */
+ new_style = xo_name_to_style(cp);
+ if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
+ if (style >= 0)
+ xo_warnx("ignoring multiple styles: '%s'", cp);
+ else
+ style = new_style;
+ } else {
+ new_flag = xo_name_to_flag(cp);
+ if (new_flag != 0)
+ XOF_SET(xop, new_flag);
+ else {
+ if (strcmp(cp, "no-color") == 0) {
+ XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
+ } else if (strcmp(cp, "indent") == 0) {
+ if (vp)
+ xop->xo_indent_by = atoi(vp);
+ else
+ xo_failure(xop, "missing value for indent option");
+ } else if (strcmp(cp, "encoder") == 0) {
+ if (vp == NULL)
+ xo_failure(xop, "missing value for encoder option");
+ else {
+ if (xo_encoder_init(xop, vp)) {
+ xo_failure(xop, "encoder not found: %s", vp);
+ rc = -1;
+ }
+ }
+
+ } else {
+ xo_warnx("unknown libxo option value: '%s'", cp);
+ rc = -1;
+ }
+ }
+ }
+ }
+
+ if (style > 0)
+ xop->xo_style= style;
+
+ return rc;
+}
+
+/**
+ * Set one or more flags for a given handle (or default if handle is NULL).
+ * These flags will affect future output.
+ *
+ * @xop XO handle to alter (or NULL for default handle)
+ * @flags Flags to be set (XOF_*)
+ */
+void
+xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
+{
+ xop = xo_default(xop);
+
+ XOF_SET(xop, flags);
+}
+
+xo_xof_flags_t
+xo_get_flags (xo_handle_t *xop)
+{
+ xop = xo_default(xop);
+
+ return xop->xo_flags;
+}
+
+/*
+ * strndup with a twist: len < 0 means strlen
+ */
+static char *
+xo_strndup (const char *str, int len)
+{
+ if (len < 0)
+ len = strlen(str);
+
+ char *cp = xo_realloc(NULL, len + 1);
+ if (cp) {
+ memcpy(cp, str, len);
+ cp[len] = '\0';
+ }
+
+ return cp;
+}
+
+/**
+ * Record a leading prefix for the XPath we generate. This allows the
+ * generated data to be placed within an XML hierarchy but still have
+ * accurate XPath expressions.
+ *
+ * @xop XO handle to alter (or NULL for default handle)
+ * @path The XPath expression
+ */
+void
+xo_set_leading_xpath (xo_handle_t *xop, const char *path)
+{
+ xop = xo_default(xop);
+
+ if (xop->xo_leading_xpath) {
+ xo_free(xop->xo_leading_xpath);
+ xop->xo_leading_xpath = NULL;
+ }
+
+ if (path == NULL)
+ return;
+
+ xop->xo_leading_xpath = xo_strndup(path, -1);
+}
+
+/**
+ * Record the info data for a set of tags
+ *
+ * @xop XO handle to alter (or NULL for default handle)
+ * @info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
+ * @count Number of entries in info (or -1 to count them ourselves)
+ */
+void
+xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
+{
+ xop = xo_default(xop);
+
+ if (count < 0 && infop) {
+ xo_info_t *xip;
+
+ for (xip = infop, count = 0; xip->xi_name; xip++, count++)
+ continue;
+ }
+
+ xop->xo_info = infop;
+ xop->xo_info_count = count;
+}
+
+/**
+ * Set the formatter callback for a handle. The callback should
+ * return a newly formatting contents of a formatting instruction,
+ * meaning the bits inside the braces.
+ */
+void
+xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
+ xo_checkpointer_t cfunc)
+{
+ xop = xo_default(xop);
+
+ xop->xo_formatter = func;
+ xop->xo_checkpointer = cfunc;
+}
+
+/**
+ * Clear one or more flags for a given handle (or default if handle is NULL).
+ * These flags will affect future output.
+ *
+ * @xop XO handle to alter (or NULL for default handle)
+ * @flags Flags to be cleared (XOF_*)
+ */
+void
+xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
+{
+ xop = xo_default(xop);
+
+ XOF_CLEAR(xop, flags);
+}
+
+static const char *
+xo_state_name (xo_state_t state)
+{
+ static const char *names[] = {
+ "init",
+ "open_container",
+ "close_container",
+ "open_list",
+ "close_list",
+ "open_instance",
+ "close_instance",
+ "open_leaf_list",
+ "close_leaf_list",
+ "discarding",
+ "marker",
+ "emit",
+ "emit_leaf_list",
+ "finish",
+ NULL
+ };
+
+ if (state < (sizeof(names) / sizeof(names[0])))
+ return names[state];
+
+ return "unknown";
+}
+
+static void
+xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
+{
+ static char div_open[] = "<div class=\"line\">";
+ static char div_open_blank[] = "<div class=\"blank-line\">";
+
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
+ return;
+
+ if (xo_style(xop) != XO_STYLE_HTML)
+ return;
+
+ XOIF_SET(xop, XOIF_DIV_OPEN);
+ if (flags & XFF_BLANK_LINE)
+ xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
+ else
+ xo_data_append(xop, div_open, sizeof(div_open) - 1);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+}
+
+static void
+xo_line_close (xo_handle_t *xop)
+{
+ static char div_close[] = "</div>";
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_HTML:
+ if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
+ xo_line_ensure_open(xop, 0);
+
+ XOIF_CLEAR(xop, XOIF_DIV_OPEN);
+ xo_data_append(xop, div_close, sizeof(div_close) - 1);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+ break;
+
+ case XO_STYLE_TEXT:
+ xo_data_append(xop, "\n", 1);
+ break;
+ }
+}
+
+static int
+xo_info_compare (const void *key, const void *data)
+{
+ const char *name = key;
+ const xo_info_t *xip = data;
+
+ return strcmp(name, xip->xi_name);
+}
+
+
+static xo_info_t *
+xo_info_find (xo_handle_t *xop, const char *name, int nlen)
+{
+ xo_info_t *xip;
+ char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
+
+ memcpy(cp, name, nlen);
+ cp[nlen] = '\0';
+
+ xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
+ sizeof(xop->xo_info[0]), xo_info_compare);
+ return xip;
+}
+
+#define CONVERT(_have, _need) (((_have) << 8) | (_need))
+
+/*
+ * Check to see that the conversion is safe and sane.
+ */
+static int
+xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
+{
+ switch (CONVERT(have_enc, need_enc)) {
+ case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
+ case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
+ case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
+ case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
+ case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
+ case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
+ return 0;
+
+ default:
+ xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
+ return 1;
+ }
+}
+
+static int
+xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
+ xo_xff_flags_t flags,
+ const wchar_t *wcp, const char *cp, int len, int max,
+ int need_enc, int have_enc)
+{
+ int cols = 0;
+ wchar_t wc = 0;
+ int ilen, olen, width;
+ int attr = (flags & XFF_ATTR);
+ const char *sp;
+
+ if (len > 0 && !xo_buf_has_room(xbp, len))
+ return 0;
+
+ for (;;) {
+ if (len == 0)
+ break;
+
+ if (cp) {
+ if (*cp == '\0')
+ break;
+ if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
+ cp += 1;
+ len -= 1;
+ }
+ }
+
+ if (wcp && *wcp == L'\0')
+ break;
+
+ ilen = 0;
+
+ switch (have_enc) {
+ case XF_ENC_WIDE: /* Wide character */
+ wc = *wcp++;
+ ilen = 1;
+ break;
+
+ case XF_ENC_UTF8: /* UTF-8 */
+ ilen = xo_utf8_to_wc_len(cp);
+ if (ilen < 0) {
+ xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
+ return -1; /* Can't continue; we can't find the end */
+ }
+
+ if (len > 0 && len < ilen) {
+ len = 0; /* Break out of the loop */
+ continue;
+ }
+
+ wc = xo_utf8_char(cp, ilen);
+ if (wc == (wchar_t) -1) {
+ xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
+ *cp, ilen);
+ return -1; /* Can't continue; we can't find the end */
+ }
+ cp += ilen;
+ break;
+
+ case XF_ENC_LOCALE: /* Native locale */
+ ilen = (len > 0) ? len : MB_LEN_MAX;
+ ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
+ if (ilen < 0) { /* Invalid data; skip */
+ xo_failure(xop, "invalid mbs char: %02hhx", *cp);
+ wc = L'?';
+ ilen = 1;
+ }
+
+ if (ilen == 0) { /* Hit a wide NUL character */
+ len = 0;
+ continue;
+ }
+
+ cp += ilen;
+ break;
+ }
+
+ /* Reduce len, but not below zero */
+ if (len > 0) {
+ len -= ilen;
+ if (len < 0)
+ len = 0;
+ }
+
+ /*
+ * Find the width-in-columns of this character, which must be done
+ * in wide characters, since we lack a mbswidth() function. If
+ * it doesn't fit
+ */
+ width = xo_wcwidth(wc);
+ if (width < 0)
+ width = iswcntrl(wc) ? 0 : 1;
+
+ if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
+ if (max > 0 && cols + width > max)
+ break;
+ }
+
+ switch (need_enc) {
+ case XF_ENC_UTF8:
+
+ /* Output in UTF-8 needs to be escaped, based on the style */
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ case XO_STYLE_HTML:
+ if (wc == '<')
+ sp = xo_xml_lt;
+ else if (wc == '>')
+ sp = xo_xml_gt;
+ else if (wc == '&')
+ sp = xo_xml_amp;
+ else if (attr && wc == '"')
+ sp = xo_xml_quot;
+ else
+ break;
+
+ int slen = strlen(sp);
+ if (!xo_buf_has_room(xbp, slen - 1))
+ return -1;
+
+ memcpy(xbp->xb_curp, sp, slen);
+ xbp->xb_curp += slen;
+ goto done_with_encoding; /* Need multi-level 'break' */
+
+ case XO_STYLE_JSON:
+ if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
+ break;
+
+ if (!xo_buf_has_room(xbp, 2))
+ return -1;
+
+ *xbp->xb_curp++ = '\\';
+ if (wc == '\n')
+ wc = 'n';
+ else if (wc == '\r')
+ wc = 'r';
+ else wc = wc & 0x7f;
+
+ *xbp->xb_curp++ = wc;
+ goto done_with_encoding;
+
+ case XO_STYLE_SDPARAMS:
+ if (wc != '\\' && wc != '"' && wc != ']')
+ break;
+
+ if (!xo_buf_has_room(xbp, 2))
+ return -1;
+
+ *xbp->xb_curp++ = '\\';
+ wc = wc & 0x7f;
+ *xbp->xb_curp++ = wc;
+ goto done_with_encoding;
+ }
+
+ olen = xo_utf8_emit_len(wc);
+ if (olen < 0) {
+ xo_failure(xop, "ignoring bad length");
+ continue;
+ }
+
+ if (!xo_buf_has_room(xbp, olen))
+ return -1;
+
+ xo_utf8_emit_char(xbp->xb_curp, olen, wc);
+ xbp->xb_curp += olen;
+ break;
+
+ case XF_ENC_LOCALE:
+ if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
+ return -1;
+
+ olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
+ if (olen <= 0) {
+ xo_failure(xop, "could not convert wide char: %lx",
+ (unsigned long) wc);
+ width = 1;
+ *xbp->xb_curp++ = '?';
+ } else
+ xbp->xb_curp += olen;
+ break;
+ }
+
+ done_with_encoding:
+ cols += width;
+ }
+
+ return cols;
+}
+
+static int
+xo_needed_encoding (xo_handle_t *xop)
+{
+ if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
+ return XF_ENC_UTF8;
+
+ if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
+ return XF_ENC_LOCALE;
+
+ return XF_ENC_UTF8; /* Otherwise, we love UTF-8 */
+}
+
+static int
+xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
+ xo_format_t *xfp)
+{
+ static char null[] = "(null)";
+ static char null_no_quotes[] = "null";
+
+ char *cp = NULL;
+ wchar_t *wcp = NULL;
+ int len, cols = 0, rc = 0;
+ int off = xbp->xb_curp - xbp->xb_bufp, off2;
+ int need_enc = xo_needed_encoding(xop);
+
+ if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
+ return 0;
+
+ len = xfp->xf_width[XF_WIDTH_SIZE];
+
+ if (xfp->xf_fc == 'm') {
+ cp = strerror(xop->xo_errno);
+ if (len < 0)
+ len = cp ? strlen(cp) : 0;
+ goto normal_string;
+
+ } else if (xfp->xf_enc == XF_ENC_WIDE) {
+ wcp = va_arg(xop->xo_vap, wchar_t *);
+ if (xfp->xf_skip)
+ return 0;
+
+ /*
+ * Dont' deref NULL; use the traditional "(null)" instead
+ * of the more accurate "who's been a naughty boy, then?".
+ */
+ if (wcp == NULL) {
+ cp = null;
+ len = sizeof(null) - 1;
+ }
+
+ } else {
+ cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
+
+ normal_string:
+ if (xfp->xf_skip)
+ return 0;
+
+ /* Echo "Dont' deref NULL" logic */
+ if (cp == NULL) {
+ if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
+ cp = null_no_quotes;
+ len = sizeof(null_no_quotes) - 1;
+ } else {
+ cp = null;
+ len = sizeof(null) - 1;
+ }
+ }
+
+ /*
+ * Optimize the most common case, which is "%s". We just
+ * need to copy the complete string to the output buffer.
+ */
+ if (xfp->xf_enc == need_enc
+ && xfp->xf_width[XF_WIDTH_MIN] < 0
+ && xfp->xf_width[XF_WIDTH_SIZE] < 0
+ && xfp->xf_width[XF_WIDTH_MAX] < 0
+ && !(XOIF_ISSET(xop, XOIF_ANCHOR)
+ || XOF_ISSET(xop, XOF_COLUMNS))) {
+ len = strlen(cp);
+ xo_buf_escape(xop, xbp, cp, len, flags);
+
+ /*
+ * Our caller expects xb_curp left untouched, so we have
+ * to reset it and return the number of bytes written to
+ * the buffer.
+ */
+ off2 = xbp->xb_curp - xbp->xb_bufp;
+ rc = off2 - off;
+ xbp->xb_curp = xbp->xb_bufp + off;
+
+ return rc;
+ }
+ }
+
+ cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
+ xfp->xf_width[XF_WIDTH_MAX],
+ need_enc, xfp->xf_enc);
+ if (cols < 0)
+ goto bail;
+
+ /*
+ * xo_buf_append* will move xb_curp, so we save/restore it.
+ */
+ off2 = xbp->xb_curp - xbp->xb_bufp;
+ rc = off2 - off;
+ xbp->xb_curp = xbp->xb_bufp + off;
+
+ if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
+ /*
+ * Find the number of columns needed to display the string.
+ * If we have the original wide string, we just call wcswidth,
+ * but if we did the work ourselves, then we need to do it.
+ */
+ int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
+ if (!xo_buf_has_room(xbp, delta))
+ goto bail;
+
+ /*
+ * If seen_minus, then pad on the right; otherwise move it so
+ * we can pad on the left.
+ */
+ if (xfp->xf_seen_minus) {
+ cp = xbp->xb_curp + rc;
+ } else {
+ cp = xbp->xb_curp;
+ memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
+ }
+
+ /* Set the padding */
+ memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
+ rc += delta;
+ cols += delta;
+ }
+
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += cols;
+
+ return rc;
+
+ bail:
+ xbp->xb_curp = xbp->xb_bufp + off;
+ return 0;
+}
+
+/*
+ * Look backwards in a buffer to find a numeric value
+ */
+static int
+xo_buf_find_last_number (xo_buffer_t *xbp, int start_offset)
+{
+ int rc = 0; /* Fail with zero */
+ int digit = 1;
+ char *sp = xbp->xb_bufp;
+ char *cp = sp + start_offset;
+
+ while (--cp >= sp)
+ if (isdigit((int) *cp))
+ break;
+
+ for ( ; cp >= sp; cp--) {
+ if (!isdigit((int) *cp))
+ break;
+ rc += (*cp - '0') * digit;
+ digit *= 10;
+ }
+
+ return rc;
+}
+
+static int
+xo_count_utf8_cols (const char *str, int len)
+{
+ int tlen;
+ wchar_t wc;
+ int cols = 0;
+ const char *ep = str + len;
+
+ while (str < ep) {
+ tlen = xo_utf8_to_wc_len(str);
+ if (tlen < 0) /* Broken input is very bad */
+ return cols;
+
+ wc = xo_utf8_char(str, tlen);
+ if (wc == (wchar_t) -1)
+ return cols;
+
+ /* We only print printable characters */
+ if (iswprint((wint_t) wc)) {
+ /*
+ * Find the width-in-columns of this character, which must be done
+ * in wide characters, since we lack a mbswidth() function.
+ */
+ int width = xo_wcwidth(wc);
+ if (width < 0)
+ width = iswcntrl(wc) ? 0 : 1;
+
+ cols += width;
+ }
+
+ str += tlen;
+ }
+
+ return cols;
+}
+
+#ifdef HAVE_GETTEXT
+static inline const char *
+xo_dgettext (xo_handle_t *xop, const char *str)
+{
+ const char *domainname = xop->xo_gt_domain;
+ const char *res;
+
+ res = dgettext(domainname, str);
+
+ if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
+ fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
+ domainname ? "domain \"" : "", xo_printable(domainname),
+ domainname ? "\", " : "", xo_printable(str), xo_printable(res));
+
+ return res;
+}
+
+static inline const char *
+xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
+ unsigned long int n)
+{
+ const char *domainname = xop->xo_gt_domain;
+ const char *res;
+
+ res = dngettext(domainname, sing, plural, n);
+ if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
+ fprintf(stderr, "xo: gettext: %s%s%s"
+ "msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
+ domainname ? "domain \"" : "",
+ xo_printable(domainname), domainname ? "\", " : "",
+ xo_printable(sing),
+ xo_printable(plural), n, xo_printable(res));
+
+ return res;
+}
+#else /* HAVE_GETTEXT */
+static inline const char *
+xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
+{
+ return str;
+}
+
+static inline const char *
+xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
+ const char *plural, unsigned long int n)
+{
+ return (n == 1) ? singular : plural;
+}
+#endif /* HAVE_GETTEXT */
+
+/*
+ * This is really _re_formatting, since the normal format code has
+ * generated a beautiful string into xo_data, starting at
+ * start_offset. We need to see if it's plural, which means
+ * comma-separated options, or singular. Then we make the appropriate
+ * call to d[n]gettext() to get the locale-based version. Note that
+ * both input and output of gettext() this should be UTF-8.
+ */
+static int
+xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
+ int start_offset, int cols, int need_enc)
+{
+ xo_buffer_t *xbp = &xop->xo_data;
+
+ if (!xo_buf_has_room(xbp, 1))
+ return cols;
+
+ xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
+
+ char *cp = xbp->xb_bufp + start_offset;
+ int len = xbp->xb_curp - cp;
+ const char *newstr = NULL;
+
+ /*
+ * The plural flag asks us to look backwards at the last numeric
+ * value rendered and disect the string into two pieces.
+ */
+ if (flags & XFF_GT_PLURAL) {
+ int n = xo_buf_find_last_number(xbp, start_offset);
+ char *two = memchr(cp, (int) ',', len);
+ if (two == NULL) {
+ xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
+ return cols;
+ }
+
+ if (two == cp) {
+ xo_failure(xop, "nothing before comma in plural gettext "
+ "field: '%s'", cp);
+ return cols;
+ }
+
+ if (two == xbp->xb_curp) {
+ xo_failure(xop, "nothing after comma in plural gettext "
+ "field: '%s'", cp);
+ return cols;
+ }
+
+ *two++ = '\0';
+ if (flags & XFF_GT_FIELD) {
+ newstr = xo_dngettext(xop, cp, two, n);
+ } else {
+ /* Don't do a gettext() look up, just get the plural form */
+ newstr = (n == 1) ? cp : two;
+ }
+
+ /*
+ * If we returned the first string, optimize a bit by
+ * backing up over comma
+ */
+ if (newstr == cp) {
+ xbp->xb_curp = two - 1; /* One for comma */
+ /*
+ * If the caller wanted UTF8, we're done; nothing changed,
+ * but we need to count the columns used.
+ */
+ if (need_enc == XF_ENC_UTF8)
+ return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
+ }
+
+ } else {
+ /* The simple case (singular) */
+ newstr = xo_dgettext(xop, cp);
+
+ if (newstr == cp) {
+ /* If the caller wanted UTF8, we're done; nothing changed */
+ if (need_enc == XF_ENC_UTF8)
+ return cols;
+ }
+ }
+
+ /*
+ * Since the new string string might be in gettext's buffer or
+ * in the buffer (as the plural form), we make a copy.
+ */
+ int nlen = strlen(newstr);
+ char *newcopy = alloca(nlen + 1);
+ memcpy(newcopy, newstr, nlen + 1);
+
+ xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
+ return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
+ need_enc, XF_ENC_UTF8);
+}
+
+static void
+xo_data_append_content (xo_handle_t *xop, const char *str, int len,
+ xo_xff_flags_t flags)
+{
+ int cols;
+ int need_enc = xo_needed_encoding(xop);
+ int start_offset = xo_buf_offset(&xop->xo_data);
+
+ cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
+ NULL, str, len, -1,
+ need_enc, XF_ENC_UTF8);
+ if (flags & XFF_GT_FLAGS)
+ cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
+
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += cols;
+}
+
+static void
+xo_bump_width (xo_format_t *xfp, int digit)
+{
+ int *ip = &xfp->xf_width[xfp->xf_dots];
+
+ *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
+}
+
+static int
+xo_trim_ws (xo_buffer_t *xbp, int len)
+{
+ char *cp, *sp, *ep;
+ int delta;
+
+ /* First trim leading space */
+ for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
+ if (*cp != ' ')
+ break;
+ }
+
+ delta = cp - sp;
+ if (delta) {
+ len -= delta;
+ memmove(sp, cp, len);
+ }
+
+ /* Then trim off the end */
+ for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
+ if (ep[-1] != ' ')
+ break;
+ }
+
+ delta = sp - ep;
+ if (delta) {
+ len -= delta;
+ cp[len] = '\0';
+ }
+
+ return len;
+}
+
+/*
+ * Interface to format a single field. The arguments are in xo_vap,
+ * and the format is in 'fmt'. If 'xbp' is null, we use xop->xo_data;
+ * this is the most common case.
+ */
+static int
+xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
+ const char *fmt, int flen, xo_xff_flags_t flags)
+{
+ xo_format_t xf;
+ const char *cp, *ep, *sp, *xp = NULL;
+ int rc, cols;
+ int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
+ unsigned make_output = !(flags & XFF_NO_OUTPUT);
+ int need_enc = xo_needed_encoding(xop);
+ int real_need_enc = need_enc;
+ int old_cols = xop->xo_columns;
+
+ /* The gettext interface is UTF-8, so we'll need that for now */
+ if (flags & XFF_GT_FIELD)
+ need_enc = XF_ENC_UTF8;
+
+ if (xbp == NULL)
+ xbp = &xop->xo_data;
+
+ unsigned start_offset = xo_buf_offset(xbp);
+
+ for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
+ /*
+ * Since we're starting a new field, save the starting offset.
+ * We'll need this later for field-related operations.
+ */
+
+ if (*cp != '%') {
+ add_one:
+ if (xp == NULL)
+ xp = cp;
+
+ if (*cp == '\\' && cp[1] != '\0')
+ cp += 1;
+ continue;
+
+ } if (cp + 1 < ep && cp[1] == '%') {
+ cp += 1;
+ goto add_one;
+ }
+
+ if (xp) {
+ if (make_output) {
+ cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
+ NULL, xp, cp - xp, -1,
+ need_enc, XF_ENC_UTF8);
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += cols;
+ }
+
+ xp = NULL;
+ }
+
+ bzero(&xf, sizeof(xf));
+ xf.xf_leading_zero = -1;
+ xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
+
+ /*
+ * "%@" starts an XO-specific set of flags:
+ * @X@ - XML-only field; ignored if style isn't XML
+ */
+ if (cp[1] == '@') {
+ for (cp += 2; cp < ep; cp++) {
+ if (*cp == '@') {
+ break;
+ }
+ if (*cp == '*') {
+ /*
+ * '*' means there's a "%*.*s" value in vap that
+ * we want to ignore
+ */
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
+ va_arg(xop->xo_vap, int);
+ }
+ }
+ }
+
+ /* Hidden fields are only visible to JSON and XML */
+ if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
+ if (style != XO_STYLE_XML
+ && !xo_style_is_encoding(xop))
+ xf.xf_skip = 1;
+ } else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
+ if (style != XO_STYLE_TEXT
+ && xo_style(xop) != XO_STYLE_HTML)
+ xf.xf_skip = 1;
+ }
+
+ if (!make_output)
+ xf.xf_skip = 1;
+
+ /*
+ * Looking at one piece of a format; find the end and
+ * call snprintf. Then advance xo_vap on our own.
+ *
+ * Note that 'n', 'v', and '$' are not supported.
+ */
+ sp = cp; /* Save start pointer */
+ for (cp += 1; cp < ep; cp++) {
+ if (*cp == 'l')
+ xf.xf_lflag += 1;
+ else if (*cp == 'h')
+ xf.xf_hflag += 1;
+ else if (*cp == 'j')
+ xf.xf_jflag += 1;
+ else if (*cp == 't')
+ xf.xf_tflag += 1;
+ else if (*cp == 'z')
+ xf.xf_zflag += 1;
+ else if (*cp == 'q')
+ xf.xf_qflag += 1;
+ else if (*cp == '.') {
+ if (++xf.xf_dots >= XF_WIDTH_NUM) {
+ xo_failure(xop, "Too many dots in format: '%s'", fmt);
+ return -1;
+ }
+ } else if (*cp == '-')
+ xf.xf_seen_minus = 1;
+ else if (isdigit((int) *cp)) {
+ if (xf.xf_leading_zero < 0)
+ xf.xf_leading_zero = (*cp == '0');
+ xo_bump_width(&xf, *cp - '0');
+ } else if (*cp == '*') {
+ xf.xf_stars += 1;
+ xf.xf_star[xf.xf_dots] = 1;
+ } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
+ break;
+ else if (*cp == 'n' || *cp == 'v') {
+ xo_failure(xop, "unsupported format: '%s'", fmt);
+ return -1;
+ }
+ }
+
+ if (cp == ep)
+ xo_failure(xop, "field format missing format character: %s",
+ fmt);
+
+ xf.xf_fc = *cp;
+
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
+ if (*cp == 's' || *cp == 'S') {
+ /* Handle "%*.*.*s" */
+ int s;
+ for (s = 0; s < XF_WIDTH_NUM; s++) {
+ if (xf.xf_star[s]) {
+ xf.xf_width[s] = va_arg(xop->xo_vap, int);
+
+ /* Normalize a negative width value */
+ if (xf.xf_width[s] < 0) {
+ if (s == 0) {
+ xf.xf_width[0] = -xf.xf_width[0];
+ xf.xf_seen_minus = 1;
+ } else
+ xf.xf_width[s] = -1; /* Ignore negative values */
+ }
+ }
+ }
+ }
+ }
+
+ /* If no max is given, it defaults to size */
+ if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
+ xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
+
+ if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
+ xf.xf_lflag = 1;
+
+ if (!xf.xf_skip) {
+ xo_buffer_t *fbp = &xop->xo_fmt;
+ int len = cp - sp + 1;
+ if (!xo_buf_has_room(fbp, len + 1))
+ return -1;
+
+ char *newfmt = fbp->xb_curp;
+ memcpy(newfmt, sp, len);
+ newfmt[0] = '%'; /* If we skipped over a "%@...@s" format */
+ newfmt[len] = '\0';
+
+ /*
+ * Bad news: our strings are UTF-8, but the stock printf
+ * functions won't handle field widths for wide characters
+ * correctly. So we have to handle this ourselves.
+ */
+ if (xop->xo_formatter == NULL
+ && (xf.xf_fc == 's' || xf.xf_fc == 'S'
+ || xf.xf_fc == 'm')) {
+
+ xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
+ : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
+ : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
+
+ rc = xo_format_string(xop, xbp, flags, &xf);
+
+ if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
+ rc = xo_trim_ws(xbp, rc);
+
+ } else {
+ int columns = rc = xo_vsnprintf(xop, xbp, newfmt, xop->xo_vap);
+
+ /*
+ * For XML and HTML, we need "&<>" processing; for JSON,
+ * it's quotes. Text gets nothing.
+ */
+ switch (style) {
+ case XO_STYLE_XML:
+ if (flags & XFF_TRIM_WS)
+ columns = rc = xo_trim_ws(xbp, rc);
+ /* fall thru */
+ case XO_STYLE_HTML:
+ rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
+ break;
+
+ case XO_STYLE_JSON:
+ if (flags & XFF_TRIM_WS)
+ columns = rc = xo_trim_ws(xbp, rc);
+ rc = xo_escape_json(xbp, rc, 0);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ if (flags & XFF_TRIM_WS)
+ columns = rc = xo_trim_ws(xbp, rc);
+ rc = xo_escape_sdparams(xbp, rc, 0);
+ break;
+
+ case XO_STYLE_ENCODER:
+ if (flags & XFF_TRIM_WS)
+ columns = rc = xo_trim_ws(xbp, rc);
+ break;
+ }
+
+ /*
+ * We can assume all the non-%s data we've
+ * added is ASCII, so the columns and bytes are the
+ * same. xo_format_string handles all the fancy
+ * string conversions and updates xo_anchor_columns
+ * accordingly.
+ */
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += columns;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += columns;
+ }
+
+ xbp->xb_curp += rc;
+ }
+
+ /*
+ * Now for the tricky part: we need to move the argument pointer
+ * along by the amount needed.
+ */
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
+
+ if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
+ /*
+ * The 'S' and 's' formats are normally handled in
+ * xo_format_string, but if we skipped it, then we
+ * need to pop it.
+ */
+ if (xf.xf_skip)
+ va_arg(xop->xo_vap, char *);
+
+ } else if (xf.xf_fc == 'm') {
+ /* Nothing on the stack for "%m" */
+
+ } else {
+ int s;
+ for (s = 0; s < XF_WIDTH_NUM; s++) {
+ if (xf.xf_star[s])
+ va_arg(xop->xo_vap, int);
+ }
+
+ if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
+ if (xf.xf_hflag > 1) {
+ va_arg(xop->xo_vap, int);
+
+ } else if (xf.xf_hflag > 0) {
+ va_arg(xop->xo_vap, int);
+
+ } else if (xf.xf_lflag > 1) {
+ va_arg(xop->xo_vap, unsigned long long);
+
+ } else if (xf.xf_lflag > 0) {
+ va_arg(xop->xo_vap, unsigned long);
+
+ } else if (xf.xf_jflag > 0) {
+ va_arg(xop->xo_vap, intmax_t);
+
+ } else if (xf.xf_tflag > 0) {
+ va_arg(xop->xo_vap, ptrdiff_t);
+
+ } else if (xf.xf_zflag > 0) {
+ va_arg(xop->xo_vap, size_t);
+
+ } else if (xf.xf_qflag > 0) {
+ va_arg(xop->xo_vap, quad_t);
+
+ } else {
+ va_arg(xop->xo_vap, int);
+ }
+ } else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
+ if (xf.xf_lflag)
+ va_arg(xop->xo_vap, long double);
+ else
+ va_arg(xop->xo_vap, double);
+
+ else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
+ va_arg(xop->xo_vap, wint_t);
+
+ else if (xf.xf_fc == 'c')
+ va_arg(xop->xo_vap, int);
+
+ else if (xf.xf_fc == 'p')
+ va_arg(xop->xo_vap, void *);
+ }
+ }
+ }
+
+ if (xp) {
+ if (make_output) {
+ cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
+ NULL, xp, cp - xp, -1,
+ need_enc, XF_ENC_UTF8);
+
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += cols;
+ }
+
+ xp = NULL;
+ }
+
+ if (flags & XFF_GT_FLAGS) {
+ /*
+ * Handle gettext()ing the field by looking up the value
+ * and then copying it in, while converting to locale, if
+ * needed.
+ */
+ int new_cols = xo_format_gettext(xop, flags, start_offset,
+ old_cols, real_need_enc);
+
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += new_cols - old_cols;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += new_cols - old_cols;
+ }
+
+ return 0;
+}
+
+static char *
+xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
+{
+ char *cp = encoding;
+
+ if (cp[0] != '%' || !isdigit((int) cp[1]))
+ return encoding;
+
+ for (cp += 2; *cp; cp++) {
+ if (!isdigit((int) *cp))
+ break;
+ }
+
+ cp -= 1;
+ *cp = '%';
+
+ return cp;
+}
+
+static void
+xo_color_append_html (xo_handle_t *xop)
+{
+ /*
+ * If the color buffer has content, we add it now. It's already
+ * prebuilt and ready, since we want to add it to every <div>.
+ */
+ if (!xo_buf_is_empty(&xop->xo_color_buf)) {
+ xo_buffer_t *xbp = &xop->xo_color_buf;
+
+ xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
+ }
+}
+
+/*
+ * A wrapper for humanize_number that autoscales, since the
+ * HN_AUTOSCALE flag scales as needed based on the size of
+ * the output buffer, not the size of the value. I also
+ * wish HN_DECIMAL was more imperative, without the <10
+ * test. But the boat only goes where we want when we hold
+ * the rudder, so xo_humanize fixes part of the problem.
+ */
+static int
+xo_humanize (char *buf, int len, uint64_t value, int flags)
+{
+ int scale = 0;
+
+ if (value) {
+ uint64_t left = value;
+
+ if (flags & HN_DIVISOR_1000) {
+ for ( ; left; scale++)
+ left /= 1000;
+ } else {
+ for ( ; left; scale++)
+ left /= 1024;
+ }
+ scale -= 1;
+ }
+
+ return xo_humanize_number(buf, len, value, "", scale, flags);
+}
+
+/*
+ * This is an area where we can save information from the handle for
+ * later restoration. We need to know what data was rendered to know
+ * what needs cleaned up.
+ */
+typedef struct xo_humanize_save_s {
+ unsigned xhs_offset; /* Saved xo_offset */
+ unsigned xhs_columns; /* Saved xo_columns */
+ unsigned xhs_anchor_columns; /* Saved xo_anchor_columns */
+} xo_humanize_save_t;
+
+/*
+ * Format a "humanized" value for a numeric, meaning something nice
+ * like "44M" instead of "44470272". We autoscale, choosing the
+ * most appropriate value for K/M/G/T/P/E based on the value given.
+ */
+static void
+xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
+ xo_humanize_save_t *savep, xo_xff_flags_t flags)
+{
+ if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
+ return;
+
+ unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
+ if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
+ return;
+
+ /*
+ * We have a string that's allegedly a number. We want to
+ * humanize it, which means turning it back into a number
+ * and calling xo_humanize_number on it.
+ */
+ uint64_t value;
+ char *ep;
+
+ xo_buf_append(xbp, "", 1); /* NUL-terminate it */
+
+ value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
+ if (!(value == ULLONG_MAX && errno == ERANGE)
+ && (ep != xbp->xb_bufp + savep->xhs_offset)) {
+ /*
+ * There are few values where humanize_number needs
+ * more bytes than the original value. I've used
+ * 10 as a rectal number to cover those scenarios.
+ */
+ if (xo_buf_has_room(xbp, 10)) {
+ xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
+
+ int rc;
+ int left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
+ int hn_flags = HN_NOSPACE; /* On by default */
+
+ if (flags & XFF_HN_SPACE)
+ hn_flags &= ~HN_NOSPACE;
+
+ if (flags & XFF_HN_DECIMAL)
+ hn_flags |= HN_DECIMAL;
+
+ if (flags & XFF_HN_1000)
+ hn_flags |= HN_DIVISOR_1000;
+
+ rc = xo_humanize(xbp->xb_curp,
+ left, value, hn_flags);
+ if (rc > 0) {
+ xbp->xb_curp += rc;
+ xop->xo_columns = savep->xhs_columns + rc;
+ xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
+ }
+ }
+ }
+}
+
+static void
+xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
+ const char *name, int nlen,
+ const char *value, int vlen,
+ const char *encoding, int elen)
+{
+ static char div_start[] = "<div class=\"";
+ static char div_tag[] = "\" data-tag=\"";
+ static char div_xpath[] = "\" data-xpath=\"";
+ static char div_key[] = "\" data-key=\"key";
+ static char div_end[] = "\">";
+ static char div_close[] = "</div>";
+
+ /*
+ * To build our XPath predicate, we need to save the va_list before
+ * we format our data, and then restore it before we format the
+ * xpath expression.
+ * Display-only keys implies that we've got an encode-only key
+ * elsewhere, so we don't use them from making predicates.
+ */
+ int need_predidate =
+ (name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
+ && XOF_ISSET(xop, XOF_XPATH));
+
+ if (need_predidate) {
+ va_list va_local;
+
+ va_copy(va_local, xop->xo_vap);
+ if (xop->xo_checkpointer)
+ xop->xo_checkpointer(xop, xop->xo_vap, 0);
+
+ /*
+ * Build an XPath predicate expression to match this key.
+ * We use the format buffer.
+ */
+ xo_buffer_t *pbp = &xop->xo_predicate;
+ pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
+
+ xo_buf_append(pbp, "[", 1);
+ xo_buf_escape(xop, pbp, name, nlen, 0);
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_buf_append(pbp, " = '", 4);
+ else
+ xo_buf_append(pbp, "='", 2);
+
+ /* The encoding format defaults to the normal format */
+ if (encoding == NULL) {
+ char *enc = alloca(vlen + 1);
+ memcpy(enc, value, vlen);
+ enc[vlen] = '\0';
+ encoding = xo_fix_encoding(xop, enc);
+ elen = strlen(encoding);
+ }
+
+ xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
+ pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
+ xo_do_format_field(xop, pbp, encoding, elen, pflags);
+
+ xo_buf_append(pbp, "']", 2);
+
+ /* Now we record this predicate expression in the stack */
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+ int olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
+ int dlen = pbp->xb_curp - pbp->xb_bufp;
+
+ char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
+ if (cp) {
+ memcpy(cp + olen, pbp->xb_bufp, dlen);
+ cp[olen + dlen] = '\0';
+ xsp->xs_keys = cp;
+ }
+
+ /* Now we reset the xo_vap as if we were never here */
+ va_end(xop->xo_vap);
+ va_copy(xop->xo_vap, va_local);
+ va_end(va_local);
+ if (xop->xo_checkpointer)
+ xop->xo_checkpointer(xop, xop->xo_vap, 1);
+ }
+
+ if (flags & XFF_ENCODE_ONLY) {
+ /*
+ * Even if this is encode-only, we need to go thru the
+ * work of formatting it to make sure the args are cleared
+ * from xo_vap.
+ */
+ xo_do_format_field(xop, NULL, encoding, elen,
+ flags | XFF_NO_OUTPUT);
+ return;
+ }
+
+ xo_line_ensure_open(xop, 0);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_buf_indent(xop, xop->xo_indent_by);
+
+ xo_data_append(xop, div_start, sizeof(div_start) - 1);
+ xo_data_append(xop, class, strlen(class));
+
+ /*
+ * If the color buffer has content, we add it now. It's already
+ * prebuilt and ready, since we want to add it to every <div>.
+ */
+ if (!xo_buf_is_empty(&xop->xo_color_buf)) {
+ xo_buffer_t *xbp = &xop->xo_color_buf;
+
+ xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
+ }
+
+ if (name) {
+ xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
+ xo_data_escape(xop, name, nlen);
+
+ /*
+ * Save the offset at which we'd place units. See xo_format_units.
+ */
+ if (XOF_ISSET(xop, XOF_UNITS)) {
+ XOIF_SET(xop, XOIF_UNITS_PENDING);
+ /*
+ * Note: We need the '+1' here because we know we've not
+ * added the closing quote. We add one, knowing the quote
+ * will be added shortly.
+ */
+ xop->xo_units_offset =
+ xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
+ }
+
+ if (XOF_ISSET(xop, XOF_XPATH)) {
+ int i;
+ xo_stack_t *xsp;
+
+ xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
+ if (xop->xo_leading_xpath)
+ xo_data_append(xop, xop->xo_leading_xpath,
+ strlen(xop->xo_leading_xpath));
+
+ for (i = 0; i <= xop->xo_depth; i++) {
+ xsp = &xop->xo_stack[i];
+ if (xsp->xs_name == NULL)
+ continue;
+
+ /*
+ * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
+ * are directly under XSS_OPEN_INSTANCE frames so we
+ * don't need to put these in our XPath expressions.
+ */
+ if (xsp->xs_state == XSS_OPEN_LIST
+ || xsp->xs_state == XSS_OPEN_LEAF_LIST)
+ continue;
+
+ xo_data_append(xop, "/", 1);
+ xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
+ if (xsp->xs_keys) {
+ /* Don't show keys for the key field */
+ if (i != xop->xo_depth || !(flags & XFF_KEY))
+ xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
+ }
+ }
+
+ xo_data_append(xop, "/", 1);
+ xo_data_escape(xop, name, nlen);
+ }
+
+ if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
+ static char in_type[] = "\" data-type=\"";
+ static char in_help[] = "\" data-help=\"";
+
+ xo_info_t *xip = xo_info_find(xop, name, nlen);
+ if (xip) {
+ if (xip->xi_type) {
+ xo_data_append(xop, in_type, sizeof(in_type) - 1);
+ xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
+ }
+ if (xip->xi_help) {
+ xo_data_append(xop, in_help, sizeof(in_help) - 1);
+ xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
+ }
+ }
+ }
+
+ if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
+ xo_data_append(xop, div_key, sizeof(div_key) - 1);
+ }
+
+ xo_buffer_t *xbp = &xop->xo_data;
+ unsigned base_offset = xbp->xb_curp - xbp->xb_bufp;
+
+ xo_data_append(xop, div_end, sizeof(div_end) - 1);
+
+ xo_humanize_save_t save; /* Save values for humanizing logic */
+
+ save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
+ save.xhs_columns = xop->xo_columns;
+ save.xhs_anchor_columns = xop->xo_anchor_columns;
+
+ xo_do_format_field(xop, NULL, value, vlen, flags);
+
+ if (flags & XFF_HUMANIZE) {
+ /*
+ * Unlike text style, we want to retain the original value and
+ * stuff it into the "data-number" attribute.
+ */
+ static const char div_number[] = "\" data-number=\"";
+ int div_len = sizeof(div_number) - 1;
+
+ unsigned end_offset = xbp->xb_curp - xbp->xb_bufp;
+ int olen = end_offset - save.xhs_offset;
+
+ char *cp = alloca(olen + 1);
+ memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
+ cp[olen] = '\0';
+
+ xo_format_humanize(xop, xbp, &save, flags);
+
+ if (xo_buf_has_room(xbp, div_len + olen)) {
+ unsigned new_offset = xbp->xb_curp - xbp->xb_bufp;
+
+
+ /* Move the humanized string off to the left */
+ memmove(xbp->xb_bufp + base_offset + div_len + olen,
+ xbp->xb_bufp + base_offset, new_offset - base_offset);
+
+ /* Copy the data_number attribute name */
+ memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
+
+ /* Copy the original long value */
+ memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
+ xbp->xb_curp += div_len + olen;
+ }
+ }
+
+ xo_data_append(xop, div_close, sizeof(div_close) - 1);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+}
+
+static void
+xo_format_text (xo_handle_t *xop, const char *str, int len)
+{
+ switch (xo_style(xop)) {
+ case XO_STYLE_TEXT:
+ xo_buf_append_locale(xop, &xop->xo_data, str, len);
+ break;
+
+ case XO_STYLE_HTML:
+ xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0);
+ break;
+ }
+}
+
+static void
+xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ const char *str = xfip->xfi_content;
+ unsigned len = xfip->xfi_clen;
+ const char *fmt = xfip->xfi_format;
+ unsigned flen = xfip->xfi_flen;
+ xo_xff_flags_t flags = xfip->xfi_flags;
+
+ static char div_open[] = "<div class=\"title";
+ static char div_middle[] = "\">";
+ static char div_close[] = "</div>";
+
+ if (flen == 0) {
+ fmt = "%s";
+ flen = 2;
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ case XO_STYLE_JSON:
+ case XO_STYLE_SDPARAMS:
+ case XO_STYLE_ENCODER:
+ /*
+ * Even though we don't care about text, we need to do
+ * enough parsing work to skip over the right bits of xo_vap.
+ */
+ if (len == 0)
+ xo_do_format_field(xop, NULL, fmt, flen, flags | XFF_NO_OUTPUT);
+ return;
+ }
+
+ xo_buffer_t *xbp = &xop->xo_data;
+ int start = xbp->xb_curp - xbp->xb_bufp;
+ int left = xbp->xb_size - start;
+ int rc;
+
+ if (xo_style(xop) == XO_STYLE_HTML) {
+ xo_line_ensure_open(xop, 0);
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_buf_indent(xop, xop->xo_indent_by);
+ xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
+ xo_color_append_html(xop);
+ xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
+ }
+
+ start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
+ if (len) {
+ char *newfmt = alloca(flen + 1);
+ memcpy(newfmt, fmt, flen);
+ newfmt[flen] = '\0';
+
+ /* If len is non-zero, the format string apply to the name */
+ char *newstr = alloca(len + 1);
+ memcpy(newstr, str, len);
+ newstr[len] = '\0';
+
+ if (newstr[len - 1] == 's') {
+ char *bp;
+
+ rc = snprintf(NULL, 0, newfmt, newstr);
+ if (rc > 0) {
+ /*
+ * We have to do this the hard way, since we might need
+ * the columns.
+ */
+ bp = alloca(rc + 1);
+ rc = snprintf(bp, rc + 1, newfmt, newstr);
+
+ xo_data_append_content(xop, bp, rc, flags);
+ }
+ goto move_along;
+
+ } else {
+ rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
+ if (rc >= left) {
+ if (!xo_buf_has_room(xbp, rc))
+ return;
+ left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
+ rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
+ }
+
+ if (rc > 0) {
+ if (XOF_ISSET(xop, XOF_COLUMNS))
+ xop->xo_columns += rc;
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xop->xo_anchor_columns += rc;
+ }
+ }
+
+ } else {
+ xo_do_format_field(xop, NULL, fmt, flen, flags);
+
+ /* xo_do_format_field moved curp, so we need to reset it */
+ rc = xbp->xb_curp - (xbp->xb_bufp + start);
+ xbp->xb_curp = xbp->xb_bufp + start;
+ }
+
+ /* If we're styling HTML, then we need to escape it */
+ if (xo_style(xop) == XO_STYLE_HTML) {
+ rc = xo_escape_xml(xbp, rc, 0);
+ }
+
+ if (rc > 0)
+ xbp->xb_curp += rc;
+
+ move_along:
+ if (xo_style(xop) == XO_STYLE_HTML) {
+ xo_data_append(xop, div_close, sizeof(div_close) - 1);
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+ }
+}
+
+static void
+xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
+{
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
+ xo_data_append(xop, ",", 1);
+ if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+ } else
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+}
+
+#if 0
+/* Useful debugging function */
+void
+xo_arg (xo_handle_t *xop);
+void
+xo_arg (xo_handle_t *xop)
+{
+ xop = xo_default(xop);
+ fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
+}
+#endif /* 0 */
+
+static void
+xo_format_value (xo_handle_t *xop, const char *name, int nlen,
+ const char *format, int flen,
+ const char *encoding, int elen, xo_xff_flags_t flags)
+{
+ int pretty = XOF_ISSET(xop, XOF_PRETTY);
+ int quote;
+
+ /*
+ * Before we emit a value, we need to know that the frame is ready.
+ */
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ if (flags & XFF_LEAF_LIST) {
+ /*
+ * Check if we've already started to emit normal leafs
+ * or if we're not in a leaf list.
+ */
+ if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
+ || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
+ char nbuf[nlen + 1];
+ memcpy(nbuf, name, nlen);
+ nbuf[nlen] = '\0';
+
+ int rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
+ if (rc < 0)
+ flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
+ else
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
+ }
+
+ xsp = &xop->xo_stack[xop->xo_depth];
+ if (xsp->xs_name) {
+ name = xsp->xs_name;
+ nlen = strlen(name);
+ }
+
+ } else if (flags & XFF_KEY) {
+ /* Emitting a 'k' (key) field */
+ if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
+ xo_failure(xop, "key field emitted after normal value field: '%.*s'",
+ nlen, name);
+
+ } else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
+ char nbuf[nlen + 1];
+ memcpy(nbuf, name, nlen);
+ nbuf[nlen] = '\0';
+
+ int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
+ if (rc < 0)
+ flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
+ else
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
+
+ xsp = &xop->xo_stack[xop->xo_depth];
+ xsp->xs_flags |= XSF_EMIT_KEY;
+ }
+
+ } else {
+ /* Emitting a normal value field */
+ if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
+ || !(xsp->xs_flags & XSF_EMIT)) {
+ char nbuf[nlen + 1];
+ memcpy(nbuf, name, nlen);
+ nbuf[nlen] = '\0';
+
+ int rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
+ if (rc < 0)
+ flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
+ else
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
+
+ xsp = &xop->xo_stack[xop->xo_depth];
+ xsp->xs_flags |= XSF_EMIT;
+ }
+ }
+
+ xo_buffer_t *xbp = &xop->xo_data;
+ xo_humanize_save_t save; /* Save values for humanizing logic */
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_TEXT:
+ if (flags & XFF_ENCODE_ONLY)
+ flags |= XFF_NO_OUTPUT;
+
+ save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
+ save.xhs_columns = xop->xo_columns;
+ save.xhs_anchor_columns = xop->xo_anchor_columns;
+
+ xo_do_format_field(xop, NULL, format, flen, flags);
+
+ if (flags & XFF_HUMANIZE)
+ xo_format_humanize(xop, xbp, &save, flags);
+ break;
+
+ case XO_STYLE_HTML:
+ if (flags & XFF_ENCODE_ONLY)
+ flags |= XFF_NO_OUTPUT;
+
+ xo_buf_append_div(xop, "data", flags, name, nlen,
+ format, flen, encoding, elen);
+ break;
+
+ case XO_STYLE_XML:
+ /*
+ * Even though we're not making output, we still need to
+ * let the formatting code handle the va_arg popping.
+ */
+ if (flags & XFF_DISPLAY_ONLY) {
+ flags |= XFF_NO_OUTPUT;
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ break;
+ }
+
+ if (encoding) {
+ format = encoding;
+ flen = elen;
+ } else {
+ char *enc = alloca(flen + 1);
+ memcpy(enc, format, flen);
+ enc[flen] = '\0';
+ format = xo_fix_encoding(xop, enc);
+ flen = strlen(format);
+ }
+
+ if (nlen == 0) {
+ static char missing[] = "missing-field-name";
+ xo_failure(xop, "missing field name: %s", format);
+ name = missing;
+ nlen = sizeof(missing) - 1;
+ }
+
+ if (pretty)
+ xo_buf_indent(xop, -1);
+ xo_data_append(xop, "<", 1);
+ xo_data_escape(xop, name, nlen);
+
+ if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
+ xo_data_append(xop, xop->xo_attrs.xb_bufp,
+ xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
+ xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
+ }
+
+ /*
+ * We indicate 'key' fields using the 'key' attribute. While
+ * this is really committing the crime of mixing meta-data with
+ * data, it's often useful. Especially when format meta-data is
+ * difficult to come by.
+ */
+ if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
+ static char attr[] = " key=\"key\"";
+ xo_data_append(xop, attr, sizeof(attr) - 1);
+ }
+
+ /*
+ * Save the offset at which we'd place units. See xo_format_units.
+ */
+ if (XOF_ISSET(xop, XOF_UNITS)) {
+ XOIF_SET(xop, XOIF_UNITS_PENDING);
+ xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
+ }
+
+ xo_data_append(xop, ">", 1);
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ xo_data_append(xop, "</", 2);
+ xo_data_escape(xop, name, nlen);
+ xo_data_append(xop, ">", 1);
+ if (pretty)
+ xo_data_append(xop, "\n", 1);
+ break;
+
+ case XO_STYLE_JSON:
+ if (flags & XFF_DISPLAY_ONLY) {
+ flags |= XFF_NO_OUTPUT;
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ break;
+ }
+
+ if (encoding) {
+ format = encoding;
+ flen = elen;
+ } else {
+ char *enc = alloca(flen + 1);
+ memcpy(enc, format, flen);
+ enc[flen] = '\0';
+ format = xo_fix_encoding(xop, enc);
+ flen = strlen(format);
+ }
+
+ int first = !(xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST);
+
+ xo_format_prep(xop, flags);
+
+ if (flags & XFF_QUOTE)
+ quote = 1;
+ else if (flags & XFF_NOQUOTE)
+ quote = 0;
+ else if (flen == 0) {
+ quote = 0;
+ format = "true"; /* JSON encodes empty tags as a boolean true */
+ flen = 4;
+ } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
+ quote = 1;
+ else
+ quote = 0;
+
+ if (nlen == 0) {
+ static char missing[] = "missing-field-name";
+ xo_failure(xop, "missing field name: %s", format);
+ name = missing;
+ nlen = sizeof(missing) - 1;
+ }
+
+ if (flags & XFF_LEAF_LIST) {
+ if (!first && pretty)
+ xo_data_append(xop, "\n", 1);
+ if (pretty)
+ xo_buf_indent(xop, -1);
+ } else {
+ if (pretty)
+ xo_buf_indent(xop, -1);
+ xo_data_append(xop, "\"", 1);
+
+ xbp = &xop->xo_data;
+ int off = xbp->xb_curp - xbp->xb_bufp;
+
+ xo_data_escape(xop, name, nlen);
+
+ if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
+ int now = xbp->xb_curp - xbp->xb_bufp;
+ for ( ; off < now; off++)
+ if (xbp->xb_bufp[off] == '-')
+ xbp->xb_bufp[off] = '_';
+ }
+ xo_data_append(xop, "\":", 2);
+ if (pretty)
+ xo_data_append(xop, " ", 1);
+ }
+
+ if (quote)
+ xo_data_append(xop, "\"", 1);
+
+ xo_do_format_field(xop, NULL, format, flen, flags);
+
+ if (quote)
+ xo_data_append(xop, "\"", 1);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ if (flags & XFF_DISPLAY_ONLY) {
+ flags |= XFF_NO_OUTPUT;
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ break;
+ }
+
+ if (encoding) {
+ format = encoding;
+ flen = elen;
+ } else {
+ char *enc = alloca(flen + 1);
+ memcpy(enc, format, flen);
+ enc[flen] = '\0';
+ format = xo_fix_encoding(xop, enc);
+ flen = strlen(format);
+ }
+
+ if (nlen == 0) {
+ static char missing[] = "missing-field-name";
+ xo_failure(xop, "missing field name: %s", format);
+ name = missing;
+ nlen = sizeof(missing) - 1;
+ }
+
+ xo_data_escape(xop, name, nlen);
+ xo_data_append(xop, "=\"", 2);
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ xo_data_append(xop, "\" ", 2);
+ break;
+
+ case XO_STYLE_ENCODER:
+ if (flags & XFF_DISPLAY_ONLY) {
+ flags |= XFF_NO_OUTPUT;
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ break;
+ }
+
+ if (flags & XFF_QUOTE)
+ quote = 1;
+ else if (flags & XFF_NOQUOTE)
+ quote = 0;
+ else if (flen == 0) {
+ quote = 0;
+ format = "true"; /* JSON encodes empty tags as a boolean true */
+ flen = 4;
+ } else if (strchr("diouxXDOUeEfFgGaAcCp", format[flen - 1]) == NULL)
+ quote = 1;
+ else
+ quote = 0;
+
+ if (encoding) {
+ format = encoding;
+ flen = elen;
+ } else {
+ char *enc = alloca(flen + 1);
+ memcpy(enc, format, flen);
+ enc[flen] = '\0';
+ format = xo_fix_encoding(xop, enc);
+ flen = strlen(format);
+ }
+
+ if (nlen == 0) {
+ static char missing[] = "missing-field-name";
+ xo_failure(xop, "missing field name: %s", format);
+ name = missing;
+ nlen = sizeof(missing) - 1;
+ }
+
+ unsigned name_offset = xo_buf_offset(&xop->xo_data);
+ xo_data_append(xop, name, nlen);
+ xo_data_append(xop, "", 1);
+
+ unsigned value_offset = xo_buf_offset(&xop->xo_data);
+ xo_do_format_field(xop, NULL, format, flen, flags);
+ xo_data_append(xop, "", 1);
+
+ xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
+ xo_buf_data(&xop->xo_data, name_offset),
+ xo_buf_data(&xop->xo_data, value_offset));
+ xo_buf_reset(&xop->xo_data);
+ break;
+ }
+}
+
+static void
+xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ const char *str = xfip->xfi_content;
+ unsigned len = xfip->xfi_clen;
+ const char *fmt = xfip->xfi_format;
+ unsigned flen = xfip->xfi_flen;
+
+ /* Start by discarding previous domain */
+ if (xop->xo_gt_domain) {
+ xo_free(xop->xo_gt_domain);
+ xop->xo_gt_domain = NULL;
+ }
+
+ /* An empty {G:} means no domainname */
+ if (len == 0 && flen == 0)
+ return;
+
+ int start_offset = -1;
+ if (len == 0 && flen != 0) {
+ /* Need to do format the data to get the domainname from args */
+ start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
+ xo_do_format_field(xop, NULL, fmt, flen, 0);
+
+ int end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
+ len = end_offset - start_offset;
+ str = xop->xo_data.xb_bufp + start_offset;
+ }
+
+ xop->xo_gt_domain = xo_strndup(str, len);
+
+ /* Reset the current buffer point to avoid emitting the name as output */
+ if (start_offset >= 0)
+ xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
+}
+
+static void
+xo_format_content (xo_handle_t *xop, const char *class_name,
+ const char *tag_name,
+ const char *str, int len, const char *fmt, int flen,
+ xo_xff_flags_t flags)
+{
+ switch (xo_style(xop)) {
+ case XO_STYLE_TEXT:
+ if (len)
+ xo_data_append_content(xop, str, len, flags);
+ else
+ xo_do_format_field(xop, NULL, fmt, flen, flags);
+ break;
+
+ case XO_STYLE_HTML:
+ if (len == 0) {
+ str = fmt;
+ len = flen;
+ }
+
+ xo_buf_append_div(xop, class_name, flags, NULL, 0, str, len, NULL, 0);
+ break;
+
+ case XO_STYLE_XML:
+ case XO_STYLE_JSON:
+ case XO_STYLE_SDPARAMS:
+ if (tag_name) {
+ if (len == 0) {
+ str = fmt;
+ len = flen;
+ }
+
+ xo_open_container_h(xop, tag_name);
+ xo_format_value(xop, "message", 7, str, len, NULL, 0, flags);
+ xo_close_container_h(xop, tag_name);
+
+ } else {
+ /*
+ * Even though we don't care about labels, we need to do
+ * enough parsing work to skip over the right bits of xo_vap.
+ */
+ if (len == 0)
+ xo_do_format_field(xop, NULL, fmt, flen,
+ flags | XFF_NO_OUTPUT);
+ }
+ break;
+
+ case XO_STYLE_ENCODER:
+ if (len == 0)
+ xo_do_format_field(xop, NULL, fmt, flen,
+ flags | XFF_NO_OUTPUT);
+ break;
+ }
+}
+
+static const char *xo_color_names[] = {
+ "default", /* XO_COL_DEFAULT */
+ "black", /* XO_COL_BLACK */
+ "red", /* XO_CLOR_RED */
+ "green", /* XO_COL_GREEN */
+ "yellow", /* XO_COL_YELLOW */
+ "blue", /* XO_COL_BLUE */
+ "magenta", /* XO_COL_MAGENTA */
+ "cyan", /* XO_COL_CYAN */
+ "white", /* XO_COL_WHITE */
+ NULL
+};
+
+static int
+xo_color_find (const char *str)
+{
+ int i;
+
+ for (i = 0; xo_color_names[i]; i++) {
+ if (strcmp(xo_color_names[i], str) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static const char *xo_effect_names[] = {
+ "reset", /* XO_EFF_RESET */
+ "normal", /* XO_EFF_NORMAL */
+ "bold", /* XO_EFF_BOLD */
+ "underline", /* XO_EFF_UNDERLINE */
+ "inverse", /* XO_EFF_INVERSE */
+ NULL
+};
+
+static const char *xo_effect_on_codes[] = {
+ "0", /* XO_EFF_RESET */
+ "0", /* XO_EFF_NORMAL */
+ "1", /* XO_EFF_BOLD */
+ "4", /* XO_EFF_UNDERLINE */
+ "7", /* XO_EFF_INVERSE */
+ NULL
+};
+
+#if 0
+/*
+ * See comment below re: joy of terminal standards. These can
+ * be use by just adding:
+ * + if (newp->xoc_effects & bit)
+ * code = xo_effect_on_codes[i];
+ * + else
+ * + code = xo_effect_off_codes[i];
+ * in xo_color_handle_text.
+ */
+static const char *xo_effect_off_codes[] = {
+ "0", /* XO_EFF_RESET */
+ "0", /* XO_EFF_NORMAL */
+ "21", /* XO_EFF_BOLD */
+ "24", /* XO_EFF_UNDERLINE */
+ "27", /* XO_EFF_INVERSE */
+ NULL
+};
+#endif /* 0 */
+
+static int
+xo_effect_find (const char *str)
+{
+ int i;
+
+ for (i = 0; xo_effect_names[i]; i++) {
+ if (strcmp(xo_effect_names[i], str) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static void
+xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
+{
+#ifdef LIBXO_TEXT_ONLY
+ return;
+#endif /* LIBXO_TEXT_ONLY */
+
+ char *cp, *ep, *np, *xp;
+ int len = strlen(str);
+ int rc;
+
+ /*
+ * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
+ */
+ for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
+ /* Trim leading whitespace */
+ while (isspace((int) *cp))
+ cp += 1;
+
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+
+ /* Trim trailing whitespace */
+ xp = cp + strlen(cp) - 1;
+ while (isspace(*xp) && xp > cp)
+ *xp-- = '\0';
+
+ if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
+ rc = xo_color_find(cp + 3);
+ if (rc < 0)
+ goto unknown;
+
+ xocp->xoc_col_fg = rc;
+
+ } else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
+ rc = xo_color_find(cp + 3);
+ if (rc < 0)
+ goto unknown;
+ xocp->xoc_col_bg = rc;
+
+ } else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
+ rc = xo_effect_find(cp + 3);
+ if (rc < 0)
+ goto unknown;
+ xocp->xoc_effects &= ~(1 << rc);
+
+ } else {
+ rc = xo_effect_find(cp);
+ if (rc < 0)
+ goto unknown;
+ xocp->xoc_effects |= 1 << rc;
+
+ switch (1 << rc) {
+ case XO_EFF_RESET:
+ xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
+ /* Note: not "|=" since we want to wipe out the old value */
+ xocp->xoc_effects = XO_EFF_RESET;
+ break;
+
+ case XO_EFF_NORMAL:
+ xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
+ | XO_EFF_INVERSE | XO_EFF_NORMAL);
+ break;
+ }
+ }
+ continue;
+
+ unknown:
+ if (XOF_ISSET(xop, XOF_WARN))
+ xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
+ }
+}
+
+static inline int
+xo_colors_enabled (xo_handle_t *xop UNUSED)
+{
+#ifdef LIBXO_TEXT_ONLY
+ return 0;
+#else /* LIBXO_TEXT_ONLY */
+ return XOF_ISSET(xop, XOF_COLOR);
+#endif /* LIBXO_TEXT_ONLY */
+}
+
+static void
+xo_colors_handle_text (xo_handle_t *xop UNUSED, xo_colors_t *newp)
+{
+ char buf[BUFSIZ];
+ char *cp = buf, *ep = buf + sizeof(buf);
+ unsigned i, bit;
+ xo_colors_t *oldp = &xop->xo_colors;
+ const char *code = NULL;
+
+ /*
+ * Start the buffer with an escape. We don't want to add the '['
+ * now, since we let xo_effect_text_add unconditionally add the ';'.
+ * We'll replace the first ';' with a '[' when we're done.
+ */
+ *cp++ = 0x1b; /* Escape */
+
+ /*
+ * Terminals were designed back in the age before "certainty" was
+ * invented, when standards were more what you'd call "guidelines"
+ * than actual rules. Anyway we can't depend on them to operate
+ * correctly. So when display attributes are changed, we punt,
+ * reseting them all and turning back on the ones we want to keep.
+ * Longer, but should be completely reliable. Savvy?
+ */
+ if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
+ newp->xoc_effects |= XO_EFF_RESET;
+ oldp->xoc_effects = 0;
+ }
+
+ for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
+ if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
+ continue;
+
+ code = xo_effect_on_codes[i];
+
+ cp += snprintf(cp, ep - cp, ";%s", code);
+ if (cp >= ep)
+ return; /* Should not occur */
+
+ if (bit == XO_EFF_RESET) {
+ /* Mark up the old value so we can detect current values as new */
+ oldp->xoc_effects = 0;
+ oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
+ }
+ }
+
+ if (newp->xoc_col_fg != oldp->xoc_col_fg) {
+ cp += snprintf(cp, ep - cp, ";3%u",
+ (newp->xoc_col_fg != XO_COL_DEFAULT)
+ ? newp->xoc_col_fg - 1 : 9);
+ }
+
+ if (newp->xoc_col_bg != oldp->xoc_col_bg) {
+ cp += snprintf(cp, ep - cp, ";4%u",
+ (newp->xoc_col_bg != XO_COL_DEFAULT)
+ ? newp->xoc_col_bg - 1 : 9);
+ }
+
+ if (cp - buf != 1 && cp < ep - 3) {
+ buf[1] = '['; /* Overwrite leading ';' */
+ *cp++ = 'm';
+ *cp = '\0';
+ xo_buf_append(&xop->xo_data, buf, cp - buf);
+ }
+}
+
+static void
+xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
+{
+ xo_colors_t *oldp = &xop->xo_colors;
+
+ /*
+ * HTML colors are mostly trivial: fill in xo_color_buf with
+ * a set of class tags representing the colors and effects.
+ */
+
+ /* If nothing changed, then do nothing */
+ if (oldp->xoc_effects == newp->xoc_effects
+ && oldp->xoc_col_fg == newp->xoc_col_fg
+ && oldp->xoc_col_bg == newp->xoc_col_bg)
+ return;
+
+ unsigned i, bit;
+ xo_buffer_t *xbp = &xop->xo_color_buf;
+
+ xo_buf_reset(xbp); /* We rebuild content after each change */
+
+ for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
+ if (!(newp->xoc_effects & bit))
+ continue;
+
+ xo_buf_append_str(xbp, " effect-");
+ xo_buf_append_str(xbp, xo_effect_names[i]);
+ }
+
+ const char *fg = NULL;
+ const char *bg = NULL;
+
+ if (newp->xoc_col_fg != XO_COL_DEFAULT)
+ fg = xo_color_names[newp->xoc_col_fg];
+ if (newp->xoc_col_bg != XO_COL_DEFAULT)
+ bg = xo_color_names[newp->xoc_col_bg];
+
+ if (newp->xoc_effects & XO_EFF_INVERSE) {
+ const char *tmp = fg;
+ fg = bg;
+ bg = tmp;
+ if (fg == NULL)
+ fg = "inverse";
+ if (bg == NULL)
+ bg = "inverse";
+
+ }
+
+ if (fg) {
+ xo_buf_append_str(xbp, " color-fg-");
+ xo_buf_append_str(xbp, fg);
+ }
+
+ if (bg) {
+ xo_buf_append_str(xbp, " color-bg-");
+ xo_buf_append_str(xbp, bg);
+ }
+}
+
+static void
+xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ const char *str = xfip->xfi_content;
+ unsigned len = xfip->xfi_clen;
+ const char *fmt = xfip->xfi_format;
+ unsigned flen = xfip->xfi_flen;
+
+ xo_buffer_t xb;
+
+ /* If the string is static and we've in an encoding style, bail */
+ if (len != 0 && xo_style_is_encoding(xop))
+ return;
+
+ xo_buf_init(&xb);
+
+ if (len)
+ xo_buf_append(&xb, str, len);
+ else if (flen)
+ xo_do_format_field(xop, &xb, fmt, flen, 0);
+ else
+ xo_buf_append(&xb, "reset", 6); /* Default if empty */
+
+ if (xo_colors_enabled(xop)) {
+ switch (xo_style(xop)) {
+ case XO_STYLE_TEXT:
+ case XO_STYLE_HTML:
+ xo_buf_append(&xb, "", 1);
+
+ xo_colors_t xoc = xop->xo_colors;
+ xo_colors_parse(xop, &xoc, xb.xb_bufp);
+
+ if (xo_style(xop) == XO_STYLE_TEXT) {
+ /*
+ * Text mode means emitting the colors as ANSI character
+ * codes. This will allow people who like colors to have
+ * colors. The issue is, of course conflicting with the
+ * user's perfectly reasonable color scheme. Which leads
+ * to the hell of LSCOLORS, where even app need to have
+ * customization hooks for adjusting colors. Instead we
+ * provide a simpler-but-still-annoying answer where one
+ * can map colors to other colors.
+ */
+ xo_colors_handle_text(xop, &xoc);
+ xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
+
+ } else {
+ /*
+ * HTML output is wrapped in divs, so the color information
+ * must appear in every div until cleared. Most pathetic.
+ * Most unavoidable.
+ */
+ xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
+ xo_colors_handle_html(xop, &xoc);
+ }
+
+ xop->xo_colors = xoc;
+ break;
+
+ case XO_STYLE_XML:
+ case XO_STYLE_JSON:
+ case XO_STYLE_SDPARAMS:
+ case XO_STYLE_ENCODER:
+ /*
+ * Nothing to do; we did all that work just to clear the stack of
+ * formatting arguments.
+ */
+ break;
+ }
+ }
+
+ xo_buf_cleanup(&xb);
+}
+
+static void
+xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ const char *str = xfip->xfi_content;
+ unsigned len = xfip->xfi_clen;
+ const char *fmt = xfip->xfi_format;
+ unsigned flen = xfip->xfi_flen;
+ xo_xff_flags_t flags = xfip->xfi_flags;
+
+ static char units_start_xml[] = " units=\"";
+ static char units_start_html[] = " data-units=\"";
+
+ if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
+ xo_format_content(xop, "units", NULL, str, len, fmt, flen, flags);
+ return;
+ }
+
+ xo_buffer_t *xbp = &xop->xo_data;
+ int start = xop->xo_units_offset;
+ int stop = xbp->xb_curp - xbp->xb_bufp;
+
+ if (xo_style(xop) == XO_STYLE_XML)
+ xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
+ else if (xo_style(xop) == XO_STYLE_HTML)
+ xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
+ else
+ return;
+
+ if (len)
+ xo_data_escape(xop, str, len);
+ else
+ xo_do_format_field(xop, NULL, fmt, flen, flags);
+
+ xo_buf_append(xbp, "\"", 1);
+
+ int now = xbp->xb_curp - xbp->xb_bufp;
+ int delta = now - stop;
+ if (delta <= 0) { /* Strange; no output to move */
+ xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
+ return;
+ }
+
+ /*
+ * Now we're in it alright. We've need to insert the unit value
+ * we just created into the right spot. We make a local copy,
+ * move it and then insert our copy. We know there's room in the
+ * buffer, since we're just moving this around.
+ */
+ char *buf = alloca(delta);
+
+ memcpy(buf, xbp->xb_bufp + stop, delta);
+ memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
+ memmove(xbp->xb_bufp + start, buf, delta);
+}
+
+static int
+xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ const char *str = xfip->xfi_content;
+ unsigned len = xfip->xfi_clen;
+ const char *fmt = xfip->xfi_format;
+ unsigned flen = xfip->xfi_flen;
+
+ long width = 0;
+ char *bp;
+ char *cp;
+
+ if (len) {
+ bp = alloca(len + 1); /* Make local NUL-terminated copy of str */
+ memcpy(bp, str, len);
+ bp[len] = '\0';
+
+ width = strtol(bp, &cp, 0);
+ if (width == LONG_MIN || width == LONG_MAX
+ || bp == cp || *cp != '\0' ) {
+ width = 0;
+ xo_failure(xop, "invalid width for anchor: '%s'", bp);
+ }
+ } else if (flen) {
+ if (flen != 2 || strncmp("%d", fmt, flen) != 0)
+ xo_failure(xop, "invalid width format: '%*.*s'", flen, flen, fmt);
+ if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
+ width = va_arg(xop->xo_vap, int);
+ }
+
+ return width;
+}
+
+static void
+xo_anchor_clear (xo_handle_t *xop)
+{
+ XOIF_CLEAR(xop, XOIF_ANCHOR);
+ xop->xo_anchor_offset = 0;
+ xop->xo_anchor_columns = 0;
+ xop->xo_anchor_min_width = 0;
+}
+
+/*
+ * An anchor is a marker used to delay field width implications.
+ * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
+ * We are looking for output like " 1/4/5"
+ *
+ * To make this work, we record the anchor and then return to
+ * format it when the end anchor tag is seen.
+ */
+static void
+xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
+ return;
+
+ if (XOIF_ISSET(xop, XOIF_ANCHOR))
+ xo_failure(xop, "the anchor already recording is discarded");
+
+ XOIF_SET(xop, XOIF_ANCHOR);
+ xo_buffer_t *xbp = &xop->xo_data;
+ xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
+ xop->xo_anchor_columns = 0;
+
+ /*
+ * Now we find the width, if possible. If it's not there,
+ * we'll get it on the end anchor.
+ */
+ xop->xo_anchor_min_width = xo_find_width(xop, xfip);
+}
+
+static void
+xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip)
+{
+ if (xo_style(xop) != XO_STYLE_TEXT && xo_style(xop) != XO_STYLE_HTML)
+ return;
+
+ if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
+ xo_failure(xop, "no start anchor");
+ return;
+ }
+
+ XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
+
+ int width = xo_find_width(xop, xfip);
+ if (width == 0)
+ width = xop->xo_anchor_min_width;
+
+ if (width == 0) /* No width given; nothing to do */
+ goto done;
+
+ xo_buffer_t *xbp = &xop->xo_data;
+ int start = xop->xo_anchor_offset;
+ int stop = xbp->xb_curp - xbp->xb_bufp;
+ int abswidth = (width > 0) ? width : -width;
+ int blen = abswidth - xop->xo_anchor_columns;
+
+ if (blen <= 0) /* Already over width */
+ goto done;
+
+ if (abswidth > XO_MAX_ANCHOR_WIDTH) {
+ xo_failure(xop, "width over %u are not supported",
+ XO_MAX_ANCHOR_WIDTH);
+ goto done;
+ }
+
+ /* Make a suitable padding field and emit it */
+ char *buf = alloca(blen);
+ memset(buf, ' ', blen);
+ xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
+
+ if (width < 0) /* Already left justified */
+ goto done;
+
+ int now = xbp->xb_curp - xbp->xb_bufp;
+ int delta = now - stop;
+ if (delta <= 0) /* Strange; no output to move */
+ goto done;
+
+ /*
+ * Now we're in it alright. We've need to insert the padding data
+ * we just created (which might be an HTML <div> or text) before
+ * the formatted data. We make a local copy, move it and then
+ * insert our copy. We know there's room in the buffer, since
+ * we're just moving this around.
+ */
+ if (delta > blen)
+ buf = alloca(delta); /* Expand buffer if needed */
+
+ memcpy(buf, xbp->xb_bufp + stop, delta);
+ memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
+ memmove(xbp->xb_bufp + start, buf, delta);
+
+ done:
+ xo_anchor_clear(xop);
+}
+
+static const char *
+xo_class_name (int ftype)
+{
+ switch (ftype) {
+ case 'D': return "decoration";
+ case 'E': return "error";
+ case 'L': return "label";
+ case 'N': return "note";
+ case 'P': return "padding";
+ case 'W': return "warning";
+ }
+
+ return NULL;
+}
+
+static const char *
+xo_tag_name (int ftype)
+{
+ switch (ftype) {
+ case 'E': return "__error";
+ case 'W': return "__warning";
+ }
+
+ return NULL;
+}
+
+static int
+xo_role_wants_default_format (int ftype)
+{
+ switch (ftype) {
+ /* These roles can be completely empty and/or without formatting */
+ case 'C':
+ case 'G':
+ case '[':
+ case ']':
+ return 0;
+ }
+
+ return 1;
+}
+
+static xo_mapping_t xo_role_names[] = {
+ { 'C', "color" },
+ { 'D', "decoration" },
+ { 'E', "error" },
+ { 'L', "label" },
+ { 'N', "note" },
+ { 'P', "padding" },
+ { 'T', "title" },
+ { 'U', "units" },
+ { 'V', "value" },
+ { 'W', "warning" },
+ { '[', "start-anchor" },
+ { ']', "stop-anchor" },
+ { 0, NULL }
+};
+
+#define XO_ROLE_EBRACE '{' /* Escaped braces */
+#define XO_ROLE_TEXT '+'
+#define XO_ROLE_NEWLINE '\n'
+
+static xo_mapping_t xo_modifier_names[] = {
+ { XFF_COLON, "colon" },
+ { XFF_COMMA, "comma" },
+ { XFF_DISPLAY_ONLY, "display" },
+ { XFF_ENCODE_ONLY, "encoding" },
+ { XFF_GT_FIELD, "gettext" },
+ { XFF_HUMANIZE, "humanize" },
+ { XFF_HUMANIZE, "hn" },
+ { XFF_HN_SPACE, "hn-space" },
+ { XFF_HN_DECIMAL, "hn-decimal" },
+ { XFF_HN_1000, "hn-1000" },
+ { XFF_KEY, "key" },
+ { XFF_LEAF_LIST, "leaf-list" },
+ { XFF_LEAF_LIST, "list" },
+ { XFF_NOQUOTE, "no-quotes" },
+ { XFF_NOQUOTE, "no-quote" },
+ { XFF_GT_PLURAL, "plural" },
+ { XFF_QUOTE, "quotes" },
+ { XFF_QUOTE, "quote" },
+ { XFF_TRIM_WS, "trim" },
+ { XFF_WS, "white" },
+ { 0, NULL }
+};
+
+#ifdef NOT_NEEDED_YET
+static xo_mapping_t xo_modifier_short_names[] = {
+ { XFF_COLON, "c" },
+ { XFF_DISPLAY_ONLY, "d" },
+ { XFF_ENCODE_ONLY, "e" },
+ { XFF_GT_FIELD, "g" },
+ { XFF_HUMANIZE, "h" },
+ { XFF_KEY, "k" },
+ { XFF_LEAF_LIST, "l" },
+ { XFF_NOQUOTE, "n" },
+ { XFF_GT_PLURAL, "p" },
+ { XFF_QUOTE, "q" },
+ { XFF_TRIM_WS, "t" },
+ { XFF_WS, "w" },
+ { 0, NULL }
+};
+#endif /* NOT_NEEDED_YET */
+
+static int
+xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
+{
+ int rc = 1;
+ const char *cp;
+
+ for (cp = fmt; *cp; cp++)
+ if (*cp == '{' || *cp == '\n')
+ rc += 1;
+
+ return rc * 2 + 1;
+}
+
+/*
+ * The field format is:
+ * '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
+ * Roles are optional and include the following field types:
+ * 'D': decoration; something non-text and non-data (colons, commmas)
+ * 'E': error message
+ * 'G': gettext() the entire string; optional domainname as content
+ * 'L': label; text preceding data
+ * 'N': note; text following data
+ * 'P': padding; whitespace
+ * 'T': Title, where 'content' is a column title
+ * 'U': Units, where 'content' is the unit label
+ * 'V': value, where 'content' is the name of the field (the default)
+ * 'W': warning message
+ * '[': start a section of anchored text
+ * ']': end a section of anchored text
+ * The following modifiers are also supported:
+ * 'c': flag: emit a colon after the label
+ * 'd': field is only emitted for display styles (text and html)
+ * 'e': field is only emitted for encoding styles (xml and json)
+ * 'g': gettext() the field
+ * 'h': humanize a numeric value (only for display styles)
+ * 'k': this field is a key, suitable for XPath predicates
+ * 'l': a leaf-list, a simple list of values
+ * 'n': no quotes around this field
+ * 'p': the field has plural gettext semantics (ngettext)
+ * 'q': add quotes around this field
+ * 't': trim whitespace around the value
+ * 'w': emit a blank after the label
+ * The print-fmt and encode-fmt strings is the printf-style formating
+ * for this data. JSON and XML will use the encoding-fmt, if present.
+ * If the encode-fmt is not provided, it defaults to the print-fmt.
+ * If the print-fmt is not provided, it defaults to 's'.
+ */
+static const char *
+xo_parse_roles (xo_handle_t *xop, const char *fmt,
+ const char *basep, xo_field_info_t *xfip)
+{
+ const char *sp;
+ unsigned ftype = 0;
+ xo_xff_flags_t flags = 0;
+ uint8_t fnum = 0;
+
+ for (sp = basep; sp; sp++) {
+ if (*sp == ':' || *sp == '/' || *sp == '}')
+ break;
+
+ if (*sp == '\\') {
+ if (sp[1] == '\0') {
+ xo_failure(xop, "backslash at the end of string");
+ return NULL;
+ }
+
+ /* Anything backslashed is ignored */
+ sp += 1;
+ continue;
+ }
+
+ if (*sp == ',') {
+ const char *np;
+ for (np = ++sp; *np; np++)
+ if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
+ break;
+
+ int slen = np - sp;
+ if (slen > 0) {
+ xo_xff_flags_t value;
+
+ value = xo_name_lookup(xo_role_names, sp, slen);
+ if (value)
+ ftype = value;
+ else {
+ value = xo_name_lookup(xo_modifier_names, sp, slen);
+ if (value)
+ flags |= value;
+ else
+ xo_failure(xop, "unknown keyword ignored: '%.*s'",
+ slen, sp);
+ }
+ }
+
+ sp = np - 1;
+ continue;
+ }
+
+ switch (*sp) {
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'G':
+ case 'L':
+ case 'N':
+ case 'P':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case '[':
+ case ']':
+ if (ftype != 0) {
+ xo_failure(xop, "field descriptor uses multiple types: '%s'",
+ xo_printable(fmt));
+ return NULL;
+ }
+ ftype = *sp;
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ fnum = (fnum * 10) + (*sp - '0');
+ break;
+
+ case 'c':
+ flags |= XFF_COLON;
+ break;
+
+ case 'd':
+ flags |= XFF_DISPLAY_ONLY;
+ break;
+
+ case 'e':
+ flags |= XFF_ENCODE_ONLY;
+ break;
+
+ case 'g':
+ flags |= XFF_GT_FIELD;
+ break;
+
+ case 'h':
+ flags |= XFF_HUMANIZE;
+ break;
+
+ case 'k':
+ flags |= XFF_KEY;
+ break;
+
+ case 'l':
+ flags |= XFF_LEAF_LIST;
+ break;
+
+ case 'n':
+ flags |= XFF_NOQUOTE;
+ break;
+
+ case 'p':
+ flags |= XFF_GT_PLURAL;
+ break;
+
+ case 'q':
+ flags |= XFF_QUOTE;
+ break;
+
+ case 't':
+ flags |= XFF_TRIM_WS;
+ break;
+
+ case 'w':
+ flags |= XFF_WS;
+ break;
+
+ default:
+ xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
+ xo_printable(fmt));
+ /*
+ * No good answer here; a bad format will likely
+ * mean a core file. We just return and hope
+ * the caller notices there's no output, and while
+ * that seems, well, bad, there's nothing better.
+ */
+ return NULL;
+ }
+
+ if (ftype == 'N' || ftype == 'U') {
+ if (flags & XFF_COLON) {
+ xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
+ "'%s'", xo_printable(fmt));
+ flags &= ~XFF_COLON;
+ }
+ }
+ }
+
+ xfip->xfi_flags = flags;
+ xfip->xfi_ftype = ftype ?: 'V';
+ xfip->xfi_fnum = fnum;
+
+ return sp;
+}
+
+/*
+ * Number any remaining fields that need numbers. Note that some
+ * field types (text, newline, escaped braces) never get numbers.
+ */
+static void
+xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
+ const char *fmt UNUSED,
+ xo_field_info_t *fields)
+{
+ xo_field_info_t *xfip;
+ unsigned fnum, max_fields;
+ uint64_t bits = 0;
+
+ /* First make a list of add the explicitly used bits */
+ for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
+ switch (xfip->xfi_ftype) {
+ case XO_ROLE_NEWLINE: /* Don't get numbered */
+ case XO_ROLE_TEXT:
+ case XO_ROLE_EBRACE:
+ case 'G':
+ continue;
+ }
+
+ fnum += 1;
+ if (fnum >= 63)
+ break;
+
+ if (xfip->xfi_fnum)
+ bits |= 1 << xfip->xfi_fnum;
+ }
+
+ max_fields = fnum;
+
+ for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
+ switch (xfip->xfi_ftype) {
+ case XO_ROLE_NEWLINE: /* Don't get numbered */
+ case XO_ROLE_TEXT:
+ case XO_ROLE_EBRACE:
+ case 'G':
+ continue;
+ }
+
+ if (xfip->xfi_fnum != 0)
+ continue;
+
+ /* Find the next unassigned field */
+ for (fnum++; bits & (1 << fnum); fnum++)
+ continue;
+
+ if (fnum > max_fields)
+ break;
+
+ xfip->xfi_fnum = fnum; /* Mark the field number */
+ bits |= 1 << fnum; /* Mark it used */
+ }
+}
+
+/*
+ * The format string uses field numbers, so we need to whiffle thru it
+ * and make sure everything's sane and lovely.
+ */
+static int
+xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
+ xo_field_info_t *fields, unsigned num_fields)
+{
+ xo_field_info_t *xfip;
+ unsigned field, fnum;
+ uint64_t bits = 0;
+
+ for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
+ /* Fields default to 1:1 with natural position */
+ if (xfip->xfi_fnum == 0)
+ xfip->xfi_fnum = field + 1;
+ else if (xfip->xfi_fnum > num_fields) {
+ xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
+ return -1;
+ }
+
+ fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
+ if (fnum < 64) { /* Only test what fits */
+ if (bits & (1 << fnum)) {
+ xo_failure(xop, "field number %u reused: '%s'",
+ xfip->xfi_fnum, fmt);
+ return -1;
+ }
+ bits |= 1 << fnum;
+ }
+ }
+
+ return 0;
+}
+
+static int
+xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
+ unsigned num_fields, const char *fmt)
+{
+ static const char default_format[] = "%s";
+ const char *cp, *sp, *ep, *basep;
+ unsigned field = 0;
+ xo_field_info_t *xfip = fields;
+ unsigned seen_fnum = 0;
+
+ for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
+ xfip->xfi_start = cp;
+
+ if (*cp == '\n') {
+ xfip->xfi_ftype = XO_ROLE_NEWLINE;
+ xfip->xfi_len = 1;
+ cp += 1;
+ continue;
+ }
+
+ if (*cp != '{') {
+ /* Normal text */
+ for (sp = cp; *sp; sp++) {
+ if (*sp == '{' || *sp == '\n')
+ break;
+ }
+
+ xfip->xfi_ftype = XO_ROLE_TEXT;
+ xfip->xfi_content = cp;
+ xfip->xfi_clen = sp - cp;
+ xfip->xfi_next = sp;
+
+ cp = sp;
+ continue;
+ }
+
+ if (cp[1] == '{') { /* Start of {{escaped braces}} */
+ xfip->xfi_start = cp + 1; /* Start at second brace */
+ xfip->xfi_ftype = XO_ROLE_EBRACE;
+
+ cp += 2; /* Skip over _both_ characters */
+ for (sp = cp; *sp; sp++) {
+ if (*sp == '}' && sp[1] == '}')
+ break;
+ }
+ if (*sp == '\0') {
+ xo_failure(xop, "missing closing '}}': '%s'",
+ xo_printable(fmt));
+ return -1;
+ }
+
+ xfip->xfi_len = sp - xfip->xfi_start + 1;
+
+ /* Move along the string, but don't run off the end */
+ if (*sp == '}' && sp[1] == '}')
+ sp += 2;
+ cp = *sp ? sp : sp;
+ xfip->xfi_next = cp;
+ continue;
+ }
+
+ /* We are looking at the start of a field definition */
+ xfip->xfi_start = basep = cp + 1;
+
+ const char *format = NULL;
+ int flen = 0;
+
+ /* Looking at roles and modifiers */
+ sp = xo_parse_roles(xop, fmt, basep, xfip);
+ if (sp == NULL) {
+ /* xo_failure has already been called */
+ return -1;
+ }
+
+ if (xfip->xfi_fnum)
+ seen_fnum = 1;
+
+ /* Looking at content */
+ if (*sp == ':') {
+ for (ep = ++sp; *sp; sp++) {
+ if (*sp == '}' || *sp == '/')
+ break;
+ if (*sp == '\\') {
+ if (sp[1] == '\0') {
+ xo_failure(xop, "backslash at the end of string");
+ return -1;
+ }
+ sp += 1;
+ continue;
+ }
+ }
+ if (ep != sp) {
+ xfip->xfi_clen = sp - ep;
+ xfip->xfi_content = ep;
+ }
+ } else {
+ xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
+ return -1;
+ }
+
+ /* Looking at main (display) format */
+ if (*sp == '/') {
+ for (ep = ++sp; *sp; sp++) {
+ if (*sp == '}' || *sp == '/')
+ break;
+ if (*sp == '\\') {
+ if (sp[1] == '\0') {
+ xo_failure(xop, "backslash at the end of string");
+ return -1;
+ }
+ sp += 1;
+ continue;
+ }
+ }
+ flen = sp - ep;
+ format = ep;
+ }
+
+ /* Looking at encoding format */
+ if (*sp == '/') {
+ for (ep = ++sp; *sp; sp++) {
+ if (*sp == '}')
+ break;
+ }
+
+ xfip->xfi_encoding = ep;
+ xfip->xfi_elen = sp - ep;
+ }
+
+ if (*sp != '}') {
+ xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
+ return -1;
+ }
+
+ xfip->xfi_len = sp - xfip->xfi_start;
+ xfip->xfi_next = ++sp;
+
+ /* If we have content, then we have a default format */
+ if (xfip->xfi_clen || format) {
+ if (format) {
+ xfip->xfi_format = format;
+ xfip->xfi_flen = flen;
+ } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
+ xfip->xfi_format = default_format;
+ xfip->xfi_flen = 2;
+ }
+ }
+
+ cp = sp;
+ }
+
+ int rc = 0;
+
+ /*
+ * If we saw a field number on at least one field, then we need
+ * to enforce some rules and/or guidelines.
+ */
+ if (seen_fnum)
+ rc = xo_parse_field_numbers(xop, fmt, fields, field);
+
+ return rc;
+}
+
+/*
+ * We are passed a pointer to a format string just past the "{G:}"
+ * field. We build a simplified version of the format string.
+ */
+static int
+xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
+ xo_buffer_t *xbp,
+ xo_field_info_t *fields,
+ int this_field,
+ const char *fmt UNUSED,
+ xo_simplify_field_func_t field_cb)
+{
+ unsigned ftype;
+ xo_xff_flags_t flags;
+ int field = this_field + 1;
+ xo_field_info_t *xfip;
+ char ch;
+
+ for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
+ ftype = xfip->xfi_ftype;
+ flags = xfip->xfi_flags;
+
+ if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
+ if (field_cb)
+ field_cb(xfip->xfi_content, xfip->xfi_clen,
+ (flags & XFF_GT_PLURAL) ? 1 : 0);
+ }
+
+ switch (ftype) {
+ case 'G':
+ /* Ignore gettext roles */
+ break;
+
+ case XO_ROLE_NEWLINE:
+ xo_buf_append(xbp, "\n", 1);
+ break;
+
+ case XO_ROLE_EBRACE:
+ xo_buf_append(xbp, "{", 1);
+ xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
+ xo_buf_append(xbp, "}", 1);
+ break;
+
+ case XO_ROLE_TEXT:
+ xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
+ break;
+
+ default:
+ xo_buf_append(xbp, "{", 1);
+ if (ftype != 'V') {
+ ch = ftype;
+ xo_buf_append(xbp, &ch, 1);
+ }
+
+ unsigned fnum = xfip->xfi_fnum ?: 0;
+ if (fnum) {
+ char num[12];
+ /* Field numbers are origin 1, not 0, following printf(3) */
+ snprintf(num, sizeof(num), "%u", fnum);
+ xo_buf_append(xbp, num, strlen(num));
+ }
+
+ xo_buf_append(xbp, ":", 1);
+ xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
+ xo_buf_append(xbp, "}", 1);
+ }
+ }
+
+ xo_buf_append(xbp, "", 1);
+ return 0;
+}
+
+void
+xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
+void
+xo_dump_fields (xo_field_info_t *fields)
+{
+ xo_field_info_t *xfip;
+
+ for (xfip = fields; xfip->xfi_ftype; xfip++) {
+ printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
+ (unsigned long) (xfip - fields), xfip->xfi_fnum,
+ (unsigned long) xfip->xfi_flags,
+ isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
+ xfip->xfi_ftype,
+ xfip->xfi_clen, xfip->xfi_content ?: "",
+ xfip->xfi_flen, xfip->xfi_format ?: "",
+ xfip->xfi_elen, xfip->xfi_encoding ?: "");
+ }
+}
+
+#ifdef HAVE_GETTEXT
+/*
+ * Find the field that matches the given field number
+ */
+static xo_field_info_t *
+xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
+{
+ xo_field_info_t *xfip;
+
+ for (xfip = fields; xfip->xfi_ftype; xfip++)
+ if (xfip->xfi_fnum == fnum)
+ return xfip;
+
+ return NULL;
+}
+
+/*
+ * At this point, we need to consider if the fields have been reordered,
+ * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
+ *
+ * We need to rewrite the new_fields using the old fields order,
+ * so that we can render the message using the arguments as they
+ * appear on the stack. It's a lot of work, but we don't really
+ * want to (eventually) fall into the standard printf code which
+ * means using the arguments straight (and in order) from the
+ * varargs we were originally passed.
+ */
+static void
+xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
+ xo_field_info_t *fields, unsigned max_fields)
+{
+ xo_field_info_t tmp[max_fields];
+ bzero(tmp, max_fields * sizeof(tmp[0]));
+
+ unsigned fnum = 0;
+ xo_field_info_t *newp, *outp, *zp;
+ for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
+ switch (newp->xfi_ftype) {
+ case XO_ROLE_NEWLINE: /* Don't get numbered */
+ case XO_ROLE_TEXT:
+ case XO_ROLE_EBRACE:
+ case 'G':
+ *outp = *newp;
+ outp->xfi_renum = 0;
+ continue;
+ }
+
+ zp = xo_gettext_find_field(fields, ++fnum);
+ if (zp == NULL) { /* Should not occur */
+ *outp = *newp;
+ outp->xfi_renum = 0;
+ continue;
+ }
+
+ *outp = *zp;
+ outp->xfi_renum = newp->xfi_fnum;
+ }
+
+ memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
+}
+
+/*
+ * We've got two lists of fields, the old list from the original
+ * format string and the new one from the parsed gettext reply. The
+ * new list has the localized words, where the old list has the
+ * formatting information. We need to combine them into a single list
+ * (the new list).
+ *
+ * If the list needs to be reordered, then we've got more serious work
+ * to do.
+ */
+static int
+xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
+ const char *gtfmt, xo_field_info_t *old_fields,
+ xo_field_info_t *new_fields, unsigned new_max_fields,
+ int *reorderedp)
+{
+ int reordered = 0;
+ xo_field_info_t *newp, *oldp, *startp = old_fields;
+
+ xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
+
+ for (newp = new_fields; newp->xfi_ftype; newp++) {
+ switch (newp->xfi_ftype) {
+ case XO_ROLE_NEWLINE:
+ case XO_ROLE_TEXT:
+ case XO_ROLE_EBRACE:
+ continue;
+
+ case 'V':
+ for (oldp = startp; oldp->xfi_ftype; oldp++) {
+ if (oldp->xfi_ftype != 'V')
+ continue;
+ if (newp->xfi_clen != oldp->xfi_clen
+ || strncmp(newp->xfi_content, oldp->xfi_content,
+ oldp->xfi_clen) != 0) {
+ reordered = 1;
+ continue;
+ }
+ startp = oldp + 1;
+ break;
+ }
+
+ /* Didn't find it on the first pass (starting from start) */
+ if (oldp->xfi_ftype == 0) {
+ for (oldp = old_fields; oldp < startp; oldp++) {
+ if (oldp->xfi_ftype != 'V')
+ continue;
+ if (newp->xfi_clen != oldp->xfi_clen)
+ continue;
+ if (strncmp(newp->xfi_content, oldp->xfi_content,
+ oldp->xfi_clen) != 0)
+ continue;
+ reordered = 1;
+ break;
+ }
+ if (oldp == startp) {
+ /* Field not found */
+ xo_failure(xop, "post-gettext format can't find field "
+ "'%.*s' in format '%s'",
+ newp->xfi_clen, newp->xfi_content,
+ xo_printable(gtfmt));
+ return -1;
+ }
+ }
+ break;
+
+ default:
+ /*
+ * Other fields don't have names for us to use, so if
+ * the types aren't the same, then we'll have to assume
+ * the original field is a match.
+ */
+ for (oldp = startp; oldp->xfi_ftype; oldp++) {
+ if (oldp->xfi_ftype == 'V') /* Can't go past these */
+ break;
+ if (oldp->xfi_ftype == newp->xfi_ftype)
+ goto copy_it; /* Assumably we have a match */
+ }
+ continue;
+ }
+
+ /*
+ * Found a match; copy over appropriate fields
+ */
+ copy_it:
+ newp->xfi_flags = oldp->xfi_flags;
+ newp->xfi_fnum = oldp->xfi_fnum;
+ newp->xfi_format = oldp->xfi_format;
+ newp->xfi_flen = oldp->xfi_flen;
+ newp->xfi_encoding = oldp->xfi_encoding;
+ newp->xfi_elen = oldp->xfi_elen;
+ }
+
+ *reorderedp = reordered;
+ if (reordered) {
+ xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
+ xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
+ }
+
+ return 0;
+}
+
+/*
+ * We don't want to make gettext() calls here with a complete format
+ * string, since that means changing a flag would mean a
+ * labor-intensive re-translation expense. Instead we build a
+ * simplified form with a reduced level of detail, perform a lookup on
+ * that string and then re-insert the formating info.
+ *
+ * So something like:
+ * xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
+ * would have a lookup string of:
+ * "close {:fd} returned {:error} {:test}\n"
+ *
+ * We also need to handling reordering of fields, where the gettext()
+ * reply string uses fields in a different order than the original
+ * format string:
+ * "cluse-a {:fd} retoorned {:test}. Bork {:error} Bork. Bork.\n"
+ * If we have to reorder fields within the message, then things get
+ * complicated. See xo_gettext_rewrite_fields.
+ *
+ * Summary: i18n aighn't cheap.
+ */
+static const char *
+xo_gettext_build_format (xo_handle_t *xop UNUSED,
+ xo_field_info_t *fields UNUSED,
+ int this_field UNUSED,
+ const char *fmt, char **new_fmtp)
+{
+ if (xo_style_is_encoding(xop))
+ goto bail;
+
+ xo_buffer_t xb;
+ xo_buf_init(&xb);
+
+ if (xo_gettext_simplify_format(xop, &xb, fields,
+ this_field, fmt, NULL))
+ goto bail2;
+
+ const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
+ if (gtfmt == NULL || gtfmt == fmt || strcmp(gtfmt, fmt) == 0)
+ goto bail2;
+
+ xo_buf_cleanup(&xb);
+
+ char *new_fmt = xo_strndup(gtfmt, -1);
+ if (new_fmt == NULL)
+ goto bail2;
+
+ *new_fmtp = new_fmt;
+ return new_fmt;
+
+ bail2:
+ xo_buf_cleanup(&xb);
+ bail:
+ *new_fmtp = NULL;
+ return fmt;
+}
+
+static void
+xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
+ unsigned *fstart, unsigned min_fstart,
+ unsigned *fend, unsigned max_fend)
+{
+ xo_field_info_t *xfip;
+ char *buf;
+ unsigned base = fstart[min_fstart];
+ unsigned blen = fend[max_fend] - base;
+ xo_buffer_t *xbp = &xop->xo_data;
+
+ if (blen == 0)
+ return;
+
+ buf = xo_realloc(NULL, blen);
+ if (buf == NULL)
+ return;
+
+ memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
+
+ unsigned field = min_fstart, soff, doff = base, len, fnum;
+ xo_field_info_t *zp;
+
+ /*
+ * Be aware there are two competing views of "field number": we
+ * want the user to thing in terms of "The {1:size}" where {G:},
+ * newlines, escaped braces, and text don't have numbers. But is
+ * also the internal view, where we have an array of
+ * xo_field_info_t and every field have an index. fnum, fstart[]
+ * and fend[] are the latter, but xfi_renum is the former.
+ */
+ for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
+ fnum = field;
+ if (xfip->xfi_renum) {
+ zp = xo_gettext_find_field(fields, xfip->xfi_renum);
+ fnum = zp ? zp - fields : field;
+ }
+
+ soff = fstart[fnum];
+ len = fend[fnum] - soff;
+
+ if (len > 0) {
+ soff -= base;
+ memcpy(xbp->xb_bufp + doff, buf + soff, len);
+ doff += len;
+ }
+ }
+
+ xo_free(buf);
+}
+#else /* HAVE_GETTEXT */
+static const char *
+xo_gettext_build_format (xo_handle_t *xop UNUSED,
+ xo_field_info_t *fields UNUSED,
+ int this_field UNUSED,
+ const char *fmt UNUSED, char **new_fmtp)
+{
+ *new_fmtp = NULL;
+ return fmt;
+}
+
+static int
+xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
+ const char *gtfmt UNUSED,
+ xo_field_info_t *old_fields UNUSED,
+ xo_field_info_t *new_fields UNUSED,
+ unsigned new_max_fields UNUSED,
+ int *reorderedp UNUSED)
+{
+ return -1;
+}
+
+static void
+xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
+ xo_field_info_t *fields UNUSED,
+ unsigned *fstart UNUSED, unsigned min_fstart UNUSED,
+ unsigned *fend UNUSED, unsigned max_fend UNUSED)
+{
+ return;
+}
+#endif /* HAVE_GETTEXT */
+
+/*
+ * The central function for emitting libxo output.
+ */
+static int
+xo_do_emit (xo_handle_t *xop, const char *fmt)
+{
+ int gettext_inuse = 0;
+ int gettext_changed = 0;
+ int gettext_reordered = 0;
+ xo_field_info_t *new_fields = NULL;
+
+ int rc = 0;
+ int flush = XOF_ISSET(xop, XOF_FLUSH);
+ int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
+ char *new_fmt = NULL;
+
+ if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
+ flush_line = 0;
+
+ xop->xo_columns = 0; /* Always reset it */
+ xop->xo_errno = errno; /* Save for "%m" */
+
+ unsigned max_fields = xo_count_fields(xop, fmt), field;
+ xo_field_info_t fields[max_fields], *xfip;
+
+ bzero(fields, max_fields * sizeof(fields[0]));
+
+ if (xo_parse_fields(xop, fields, max_fields, fmt))
+ return -1; /* Warning already displayed */
+
+ unsigned ftype;
+ xo_xff_flags_t flags;
+
+ /*
+ * Some overhead for gettext; if the fields in the msgstr returned
+ * by gettext are reordered, then we need to record start and end
+ * for each field. We'll go ahead and render the fields in the
+ * normal order, but later we can then reconstruct the reordered
+ * fields using these fstart/fend values.
+ */
+ unsigned flimit = max_fields * 2; /* Pessimistic limit */
+ unsigned min_fstart = flimit - 1;
+ unsigned max_fend = 0; /* Highest recorded fend[] entry */
+ unsigned fstart[flimit];
+ bzero(fstart, flimit * sizeof(fstart[0]));
+ unsigned fend[flimit];
+ bzero(fend, flimit * sizeof(fend[0]));
+
+ for (xfip = fields, field = 0; xfip->xfi_ftype && field < max_fields;
+ xfip++, field++) {
+ ftype = xfip->xfi_ftype;
+ flags = xfip->xfi_flags;
+
+ /* Record field start offset */
+ if (gettext_reordered) {
+ fstart[field] = xo_buf_offset(&xop->xo_data);
+ if (min_fstart > field)
+ min_fstart = field;
+ }
+
+ if (ftype == XO_ROLE_NEWLINE) {
+ xo_line_close(xop);
+ if (flush_line && xo_flush_h(xop) < 0)
+ return -1;
+ goto bottom;
+
+ } else if (ftype == XO_ROLE_EBRACE) {
+ xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
+ goto bottom;
+
+ } else if (ftype == XO_ROLE_TEXT) {
+ /* Normal text */
+ xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
+ goto bottom;
+ }
+
+ /*
+ * Notes and units need the 'w' flag handled before the content.
+ */
+ if (ftype == 'N' || ftype == 'U') {
+ if (flags & XFF_WS) {
+ xo_format_content(xop, "padding", NULL, " ", 1,
+ NULL, 0, flags);
+ flags &= ~XFF_WS; /* Block later handling of this */
+ }
+ }
+
+ if (ftype == 'V')
+ xo_format_value(xop, xfip->xfi_content, xfip->xfi_clen,
+ xfip->xfi_format, xfip->xfi_flen,
+ xfip->xfi_encoding, xfip->xfi_elen, flags);
+ else if (ftype == '[')
+ xo_anchor_start(xop, xfip);
+ else if (ftype == ']')
+ xo_anchor_stop(xop, xfip);
+ else if (ftype == 'C')
+ xo_format_colors(xop, xfip);
+
+ else if (ftype == 'G') {
+ /*
+ * A {G:domain} field; disect the domain name and translate
+ * the remaining portion of the input string. If the user
+ * didn't put the {G:} at the start of the format string, then
+ * assumably they just want us to translate the rest of it.
+ * Since gettext returns strings in a static buffer, we make
+ * a copy in new_fmt.
+ */
+ xo_set_gettext_domain(xop, xfip);
+
+ if (!gettext_inuse) { /* Only translate once */
+ gettext_inuse = 1;
+ if (new_fmt) {
+ xo_free(new_fmt);
+ new_fmt = NULL;
+ }
+
+ xo_gettext_build_format(xop, fields, field,
+ xfip->xfi_next, &new_fmt);
+ if (new_fmt) {
+ gettext_changed = 1;
+
+ unsigned new_max_fields = xo_count_fields(xop, new_fmt);
+
+ if (++new_max_fields < max_fields)
+ new_max_fields = max_fields;
+
+ /* Leave a blank slot at the beginning */
+ int sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
+ new_fields = alloca(sz);
+ bzero(new_fields, sz);
+
+ if (!xo_parse_fields(xop, new_fields + 1,
+ new_max_fields, new_fmt)) {
+ gettext_reordered = 0;
+
+ if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
+ fields, new_fields + 1,
+ new_max_fields, &gettext_reordered)) {
+
+ if (gettext_reordered) {
+ if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
+ xo_failure(xop, "gettext finds reordered "
+ "fields in '%s' and '%s'",
+ xo_printable(fmt),
+ xo_printable(new_fmt));
+ flush_line = 0; /* Must keep at content */
+ XOIF_SET(xop, XOIF_REORDER);
+ }
+
+ field = -1; /* Will be incremented at top of loop */
+ xfip = new_fields;
+ max_fields = new_max_fields;
+ }
+ }
+ }
+ }
+ continue;
+
+ } else if (xfip->xfi_clen || xfip->xfi_format) {
+
+ const char *class_name = xo_class_name(ftype);
+ if (class_name)
+ xo_format_content(xop, class_name, xo_tag_name(ftype),
+ xfip->xfi_content, xfip->xfi_clen,
+ xfip->xfi_format, xfip->xfi_flen, flags);
+ else if (ftype == 'T')
+ xo_format_title(xop, xfip);
+ else if (ftype == 'U')
+ xo_format_units(xop, xfip);
+ else
+ xo_failure(xop, "unknown field type: '%c'", ftype);
+ }
+
+ if (flags & XFF_COLON)
+ xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
+
+ if (flags & XFF_WS)
+ xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
+
+ bottom:
+ /* Record the end-of-field offset */
+ if (gettext_reordered) {
+ fend[field] = xo_buf_offset(&xop->xo_data);
+ max_fend = field;
+ }
+ }
+
+ if (gettext_changed && gettext_reordered) {
+ /* Final step: rebuild the content using the rendered fields */
+ xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
+ fend, max_fend);
+ }
+
+ XOIF_CLEAR(xop, XOIF_REORDER);
+
+ /* If we don't have an anchor, write the text out */
+ if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
+ if (xo_write(xop) < 0)
+ rc = -1; /* Report failure */
+ else if (xop->xo_flush && xop->xo_flush(xop->xo_opaque) < 0)
+ rc = -1;
+ }
+
+ if (new_fmt)
+ xo_free(new_fmt);
+
+ /*
+ * We've carried the gettext domainname inside our handle just for
+ * convenience, but we need to ensure it doesn't survive across
+ * xo_emit calls.
+ */
+ if (xop->xo_gt_domain) {
+ xo_free(xop->xo_gt_domain);
+ xop->xo_gt_domain = NULL;
+ }
+
+ return (rc < 0) ? rc : (int) xop->xo_columns;
+}
+
+/*
+ * Rebuild a format string in a gettext-friendly format. This function
+ * is exposed to tools can perform this function. See xo(1).
+ */
+char *
+xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
+ xo_simplify_field_func_t field_cb)
+{
+ xop = xo_default(xop);
+
+ xop->xo_columns = 0; /* Always reset it */
+ xop->xo_errno = errno; /* Save for "%m" */
+
+ unsigned max_fields = xo_count_fields(xop, fmt);
+ xo_field_info_t fields[max_fields];
+
+ bzero(fields, max_fields * sizeof(fields[0]));
+
+ if (xo_parse_fields(xop, fields, max_fields, fmt))
+ return NULL; /* Warning already displayed */
+
+ xo_buffer_t xb;
+ xo_buf_init(&xb);
+
+ if (with_numbers)
+ xo_gettext_finish_numbering_fields(xop, fmt, fields);
+
+ if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
+ return NULL;
+
+ return xb.xb_bufp;
+}
+
+int
+xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
+{
+ int rc;
+
+ xop = xo_default(xop);
+ va_copy(xop->xo_vap, vap);
+ rc = xo_do_emit(xop, fmt);
+ va_end(xop->xo_vap);
+ bzero(&xop->xo_vap, sizeof(xop->xo_vap));
+
+ return rc;
+}
+
+int
+xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
+{
+ int rc;
+
+ xop = xo_default(xop);
+ va_start(xop->xo_vap, fmt);
+ rc = xo_do_emit(xop, fmt);
+ va_end(xop->xo_vap);
+ bzero(&xop->xo_vap, sizeof(xop->xo_vap));
+
+ return rc;
+}
+
+int
+xo_emit (const char *fmt, ...)
+{
+ xo_handle_t *xop = xo_default(NULL);
+ int rc;
+
+ va_start(xop->xo_vap, fmt);
+ rc = xo_do_emit(xop, fmt);
+ va_end(xop->xo_vap);
+ bzero(&xop->xo_vap, sizeof(xop->xo_vap));
+
+ return rc;
+}
+
+int
+xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
+{
+ const int extra = 5; /* space, equals, quote, quote, and nul */
+ xop = xo_default(xop);
+
+ int rc = 0;
+ int nlen = strlen(name);
+ xo_buffer_t *xbp = &xop->xo_attrs;
+ unsigned name_offset, value_offset;
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ if (!xo_buf_has_room(xbp, nlen + extra))
+ return -1;
+
+ *xbp->xb_curp++ = ' ';
+ memcpy(xbp->xb_curp, name, nlen);
+ xbp->xb_curp += nlen;
+ *xbp->xb_curp++ = '=';
+ *xbp->xb_curp++ = '"';
+
+ rc = xo_vsnprintf(xop, xbp, fmt, vap);
+
+ if (rc >= 0) {
+ rc = xo_escape_xml(xbp, rc, 1);
+ xbp->xb_curp += rc;
+ }
+
+ if (!xo_buf_has_room(xbp, 2))
+ return -1;
+
+ *xbp->xb_curp++ = '"';
+ *xbp->xb_curp = '\0';
+
+ rc += nlen + extra;
+ break;
+
+ case XO_STYLE_ENCODER:
+ name_offset = xo_buf_offset(xbp);
+ xo_buf_append(xbp, name, nlen);
+ xo_buf_append(xbp, "", 1);
+
+ value_offset = xo_buf_offset(xbp);
+ rc = xo_vsnprintf(xop, xbp, fmt, vap);
+ if (rc >= 0) {
+ xbp->xb_curp += rc;
+ *xbp->xb_curp = '\0';
+ rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
+ xo_buf_data(xbp, name_offset),
+ xo_buf_data(xbp, value_offset));
+ }
+ }
+
+ return rc;
+}
+
+int
+xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
+{
+ int rc;
+ va_list vap;
+
+ va_start(vap, fmt);
+ rc = xo_attr_hv(xop, name, fmt, vap);
+ va_end(vap);
+
+ return rc;
+}
+
+int
+xo_attr (const char *name, const char *fmt, ...)
+{
+ int rc;
+ va_list vap;
+
+ va_start(vap, fmt);
+ rc = xo_attr_hv(NULL, name, fmt, vap);
+ va_end(vap);
+
+ return rc;
+}
+
+static void
+xo_stack_set_flags (xo_handle_t *xop)
+{
+ if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ xsp->xs_flags |= XSF_NOT_FIRST;
+ XOF_CLEAR(xop, XOF_NOT_FIRST);
+ }
+}
+
+static void
+xo_depth_change (xo_handle_t *xop, const char *name,
+ int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
+{
+ if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
+ indent = 0;
+
+ if (XOF_ISSET(xop, XOF_DTRT))
+ flags |= XSF_DTRT;
+
+ if (delta >= 0) { /* Push operation */
+ if (xo_depth_check(xop, xop->xo_depth + delta))
+ return;
+
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
+ xsp->xs_flags = flags;
+ xsp->xs_state = state;
+ xo_stack_set_flags(xop);
+
+ if (name == NULL)
+ name = XO_FAILURE_NAME;
+
+ xsp->xs_name = xo_strndup(name, -1);
+
+ } else { /* Pop operation */
+ if (xop->xo_depth == 0) {
+ if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
+ xo_failure(xop, "close with empty stack: '%s'", name);
+ return;
+ }
+
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+ if (XOF_ISSET(xop, XOF_WARN)) {
+ const char *top = xsp->xs_name;
+ if (top && strcmp(name, top) != 0) {
+ xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
+ name, top);
+ return;
+ }
+ if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
+ xo_failure(xop, "list close on list confict: '%s'",
+ name);
+ return;
+ }
+ if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
+ xo_failure(xop, "list close on instance confict: '%s'",
+ name);
+ return;
+ }
+ }
+
+ if (xsp->xs_name) {
+ xo_free(xsp->xs_name);
+ xsp->xs_name = NULL;
+ }
+ if (xsp->xs_keys) {
+ xo_free(xsp->xs_keys);
+ xsp->xs_keys = NULL;
+ }
+ }
+
+ xop->xo_depth += delta; /* Record new depth */
+ xop->xo_indent += indent;
+}
+
+void
+xo_set_depth (xo_handle_t *xop, int depth)
+{
+ xop = xo_default(xop);
+
+ if (xo_depth_check(xop, depth))
+ return;
+
+ xop->xo_depth += depth;
+ xop->xo_indent += depth;
+}
+
+static xo_xsf_flags_t
+xo_stack_flags (unsigned xflags)
+{
+ if (xflags & XOF_DTRT)
+ return XSF_DTRT;
+ return 0;
+}
+
+static void
+xo_emit_top (xo_handle_t *xop, const char *ppn)
+{
+ xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
+ XOIF_SET(xop, XOIF_TOP_EMITTED);
+
+ if (xop->xo_version) {
+ xo_printf(xop, "%*s\"__version\": \"%s\", %s",
+ xo_indent(xop), "", xop->xo_version, ppn);
+ xo_free(xop->xo_version);
+ xop->xo_version = NULL;
+ }
+}
+
+static int
+xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
+{
+ int rc = 0;
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ if (name == NULL) {
+ xo_failure(xop, "NULL passed for container name");
+ name = XO_FAILURE_NAME;
+ }
+
+ flags |= xop->xo_flags; /* Pick up handle flags */
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
+
+ if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
+ rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
+ xo_data_append(xop, xop->xo_attrs.xb_bufp,
+ xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
+ xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
+ }
+
+ rc += xo_printf(xop, ">%s", ppn);
+ break;
+
+ case XO_STYLE_JSON:
+ xo_stack_set_flags(xop);
+
+ if (!XOF_ISSET(xop, XOF_NO_TOP)
+ && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
+ xo_emit_top(xop, ppn);
+
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ rc = xo_printf(xop, "%s%*s\"%s\": {%s",
+ pre_nl, xo_indent(xop), "", name, ppn);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ break;
+
+ case XO_STYLE_ENCODER:
+ rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL);
+ break;
+ }
+
+ xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
+ xo_stack_flags(flags));
+
+ return rc;
+}
+
+static int
+xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
+{
+ return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
+}
+
+int
+xo_open_container_h (xo_handle_t *xop, const char *name)
+{
+ return xo_open_container_hf(xop, 0, name);
+}
+
+int
+xo_open_container (const char *name)
+{
+ return xo_open_container_hf(NULL, 0, name);
+}
+
+int
+xo_open_container_hd (xo_handle_t *xop, const char *name)
+{
+ return xo_open_container_hf(xop, XOF_DTRT, name);
+}
+
+int
+xo_open_container_d (const char *name)
+{
+ return xo_open_container_hf(NULL, XOF_DTRT, name);
+}
+
+static int
+xo_do_close_container (xo_handle_t *xop, const char *name)
+{
+ xop = xo_default(xop);
+
+ int rc = 0;
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ if (name == NULL) {
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ name = xsp->xs_name;
+ if (name) {
+ int len = strlen(name) + 1;
+ /* We need to make a local copy; xo_depth_change will free it */
+ char *cp = alloca(len);
+ memcpy(cp, name, len);
+ name = cp;
+ } else if (!(xsp->xs_flags & XSF_DTRT)) {
+ xo_failure(xop, "missing name without 'dtrt' mode");
+ name = XO_FAILURE_NAME;
+ }
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
+ rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
+ break;
+
+ case XO_STYLE_JSON:
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ ppn = (xop->xo_depth <= 1) ? "\n" : "";
+
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
+ rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+
+ case XO_STYLE_HTML:
+ case XO_STYLE_TEXT:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
+ rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL);
+ break;
+ }
+
+ return rc;
+}
+
+int
+xo_close_container_h (xo_handle_t *xop, const char *name)
+{
+ return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
+}
+
+int
+xo_close_container (const char *name)
+{
+ return xo_close_container_h(NULL, name);
+}
+
+int
+xo_close_container_hd (xo_handle_t *xop)
+{
+ return xo_close_container_h(xop, NULL);
+}
+
+int
+xo_close_container_d (void)
+{
+ return xo_close_container_h(NULL, NULL);
+}
+
+static int
+xo_do_open_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
+{
+ int rc = 0;
+ int indent = 0;
+
+ xop = xo_default(xop);
+
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
+
+ indent = 1;
+ if (!XOF_ISSET(xop, XOF_NO_TOP)
+ && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
+ xo_emit_top(xop, ppn);
+
+ if (name == NULL) {
+ xo_failure(xop, "NULL passed for list name");
+ name = XO_FAILURE_NAME;
+ }
+
+ xo_stack_set_flags(xop);
+
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ rc = xo_printf(xop, "%s%*s\"%s\": [%s",
+ pre_nl, xo_indent(xop), "", name, ppn);
+ break;
+
+ case XO_STYLE_ENCODER:
+ rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL);
+ break;
+ }
+
+ xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
+ XSF_LIST | xo_stack_flags(flags));
+
+ return rc;
+}
+
+static int
+xo_open_list_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
+{
+ return xo_transition(xop, flags, name, XSS_OPEN_LIST);
+}
+
+int
+xo_open_list_h (xo_handle_t *xop, const char *name UNUSED)
+{
+ return xo_open_list_hf(xop, 0, name);
+}
+
+int
+xo_open_list (const char *name)
+{
+ return xo_open_list_hf(NULL, 0, name);
+}
+
+int
+xo_open_list_hd (xo_handle_t *xop, const char *name UNUSED)
+{
+ return xo_open_list_hf(xop, XOF_DTRT, name);
+}
+
+int
+xo_open_list_d (const char *name)
+{
+ return xo_open_list_hf(NULL, XOF_DTRT, name);
+}
+
+static int
+xo_do_close_list (xo_handle_t *xop, const char *name)
+{
+ int rc = 0;
+ const char *pre_nl = "";
+
+ if (name == NULL) {
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ name = xsp->xs_name;
+ if (name) {
+ int len = strlen(name) + 1;
+ /* We need to make a local copy; xo_depth_change will free it */
+ char *cp = alloca(len);
+ memcpy(cp, name, len);
+ name = cp;
+ } else if (!(xsp->xs_flags & XSF_DTRT)) {
+ xo_failure(xop, "missing name without 'dtrt' mode");
+ name = XO_FAILURE_NAME;
+ }
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
+ rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
+ rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL);
+ break;
+
+ default:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+ }
+
+ return rc;
+}
+
+int
+xo_close_list_h (xo_handle_t *xop, const char *name)
+{
+ return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
+}
+
+int
+xo_close_list (const char *name)
+{
+ return xo_close_list_h(NULL, name);
+}
+
+int
+xo_close_list_hd (xo_handle_t *xop)
+{
+ return xo_close_list_h(xop, NULL);
+}
+
+int
+xo_close_list_d (void)
+{
+ return xo_close_list_h(NULL, NULL);
+}
+
+static int
+xo_do_open_leaf_list (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
+{
+ int rc = 0;
+ int indent = 0;
+
+ xop = xo_default(xop);
+
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
+ indent = 1;
+
+ if (!XOF_ISSET(xop, XOF_NO_TOP)) {
+ if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
+ xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
+ XOIF_SET(xop, XOIF_TOP_EMITTED);
+ }
+ }
+
+ if (name == NULL) {
+ xo_failure(xop, "NULL passed for list name");
+ name = XO_FAILURE_NAME;
+ }
+
+ xo_stack_set_flags(xop);
+
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ rc = xo_printf(xop, "%s%*s\"%s\": [%s",
+ pre_nl, xo_indent(xop), "", name, ppn);
+ break;
+
+ case XO_STYLE_ENCODER:
+ rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL);
+ break;
+ }
+
+ xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
+ XSF_LIST | xo_stack_flags(flags));
+
+ return rc;
+}
+
+static int
+xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
+{
+ int rc = 0;
+ const char *pre_nl = "";
+
+ if (name == NULL) {
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ name = xsp->xs_name;
+ if (name) {
+ int len = strlen(name) + 1;
+ /* We need to make a local copy; xo_depth_change will free it */
+ char *cp = alloca(len);
+ memcpy(cp, name, len);
+ name = cp;
+ } else if (!(xsp->xs_flags & XSF_DTRT)) {
+ xo_failure(xop, "missing name without 'dtrt' mode");
+ name = XO_FAILURE_NAME;
+ }
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
+ rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+
+ case XO_STYLE_ENCODER:
+ rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL);
+ /*fallthru*/
+
+ default:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+xo_do_open_instance (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
+{
+ xop = xo_default(xop);
+
+ int rc = 0;
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ flags |= xop->xo_flags;
+
+ if (name == NULL) {
+ xo_failure(xop, "NULL passed for instance name");
+ name = XO_FAILURE_NAME;
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ rc = xo_printf(xop, "%*s<%s", xo_indent(xop), "", name);
+
+ if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
+ rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
+ xo_data_append(xop, xop->xo_attrs.xb_bufp,
+ xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
+ xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
+ }
+
+ rc += xo_printf(xop, ">%s", ppn);
+ break;
+
+ case XO_STYLE_JSON:
+ xo_stack_set_flags(xop);
+
+ if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+
+ rc = xo_printf(xop, "%s%*s{%s",
+ pre_nl, xo_indent(xop), "", ppn);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ break;
+
+ case XO_STYLE_ENCODER:
+ rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL);
+ break;
+ }
+
+ xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
+
+ return rc;
+}
+
+static int
+xo_open_instance_hf (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name)
+{
+ return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
+}
+
+int
+xo_open_instance_h (xo_handle_t *xop, const char *name)
+{
+ return xo_open_instance_hf(xop, 0, name);
+}
+
+int
+xo_open_instance (const char *name)
+{
+ return xo_open_instance_hf(NULL, 0, name);
+}
+
+int
+xo_open_instance_hd (xo_handle_t *xop, const char *name)
+{
+ return xo_open_instance_hf(xop, XOF_DTRT, name);
+}
+
+int
+xo_open_instance_d (const char *name)
+{
+ return xo_open_instance_hf(NULL, XOF_DTRT, name);
+}
+
+static int
+xo_do_close_instance (xo_handle_t *xop, const char *name)
+{
+ xop = xo_default(xop);
+
+ int rc = 0;
+ const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+ const char *pre_nl = "";
+
+ if (name == NULL) {
+ xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
+
+ name = xsp->xs_name;
+ if (name) {
+ int len = strlen(name) + 1;
+ /* We need to make a local copy; xo_depth_change will free it */
+ char *cp = alloca(len);
+ memcpy(cp, name, len);
+ name = cp;
+ } else if (!(xsp->xs_flags & XSF_DTRT)) {
+ xo_failure(xop, "missing name without 'dtrt' mode");
+ name = XO_FAILURE_NAME;
+ }
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
+ rc = xo_printf(xop, "%*s</%s>%s", xo_indent(xop), "", name, ppn);
+ break;
+
+ case XO_STYLE_JSON:
+ pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
+
+ xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
+ rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
+ xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
+ break;
+
+ case XO_STYLE_HTML:
+ case XO_STYLE_TEXT:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
+ rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL);
+ break;
+ }
+
+ return rc;
+}
+
+int
+xo_close_instance_h (xo_handle_t *xop, const char *name)
+{
+ return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
+}
+
+int
+xo_close_instance (const char *name)
+{
+ return xo_close_instance_h(NULL, name);
+}
+
+int
+xo_close_instance_hd (xo_handle_t *xop)
+{
+ return xo_close_instance_h(xop, NULL);
+}
+
+int
+xo_close_instance_d (void)
+{
+ return xo_close_instance_h(NULL, NULL);
+}
+
+static int
+xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
+{
+ xo_stack_t *xsp;
+ int rc = 0;
+ xo_xsf_flags_t flags;
+
+ for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
+ switch (xsp->xs_state) {
+ case XSS_INIT:
+ /* Nothing */
+ rc = 0;
+ break;
+
+ case XSS_OPEN_CONTAINER:
+ rc = xo_do_close_container(xop, NULL);
+ break;
+
+ case XSS_OPEN_LIST:
+ rc = xo_do_close_list(xop, NULL);
+ break;
+
+ case XSS_OPEN_INSTANCE:
+ rc = xo_do_close_instance(xop, NULL);
+ break;
+
+ case XSS_OPEN_LEAF_LIST:
+ rc = xo_do_close_leaf_list(xop, NULL);
+ break;
+
+ case XSS_MARKER:
+ flags = xsp->xs_flags & XSF_MARKER_FLAGS;
+ xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
+ xop->xo_stack[xop->xo_depth].xs_flags |= flags;
+ rc = 0;
+ break;
+ }
+
+ if (rc < 0)
+ xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
+ }
+
+ return 0;
+}
+
+/*
+ * This function is responsible for clearing out whatever is needed
+ * to get to the desired state, if possible.
+ */
+static int
+xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
+{
+ xo_stack_t *xsp, *limit = NULL;
+ int rc;
+ xo_state_t need_state = new_state;
+
+ if (new_state == XSS_CLOSE_CONTAINER)
+ need_state = XSS_OPEN_CONTAINER;
+ else if (new_state == XSS_CLOSE_LIST)
+ need_state = XSS_OPEN_LIST;
+ else if (new_state == XSS_CLOSE_INSTANCE)
+ need_state = XSS_OPEN_INSTANCE;
+ else if (new_state == XSS_CLOSE_LEAF_LIST)
+ need_state = XSS_OPEN_LEAF_LIST;
+ else if (new_state == XSS_MARKER)
+ need_state = XSS_MARKER;
+ else
+ return 0; /* Unknown or useless new states are ignored */
+
+ for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
+ /*
+ * Marker's normally stop us from going any further, unless
+ * we are popping a marker (new_state == XSS_MARKER).
+ */
+ if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
+ if (name) {
+ xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
+ "not found '%s'",
+ xo_state_name(new_state),
+ xsp->xs_name, name);
+ return 0;
+
+ } else {
+ limit = xsp;
+ xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
+ }
+ break;
+ }
+
+ if (xsp->xs_state != need_state)
+ continue;
+
+ if (name && xsp->xs_name && strcmp(name, xsp->xs_name) != 0)
+ continue;
+
+ limit = xsp;
+ break;
+ }
+
+ if (limit == NULL) {
+ xo_failure(xop, "xo_%s can't find match for '%s'",
+ xo_state_name(new_state), name);
+ return 0;
+ }
+
+ rc = xo_do_close_all(xop, limit);
+
+ return rc;
+}
+
+/*
+ * We are in a given state and need to transition to the new state.
+ */
+static int
+xo_transition (xo_handle_t *xop, xo_xsf_flags_t flags, const char *name,
+ xo_state_t new_state)
+{
+ xo_stack_t *xsp;
+ int rc;
+ int old_state, on_marker;
+
+ xop = xo_default(xop);
+
+ rc = 0;
+ xsp = &xop->xo_stack[xop->xo_depth];
+ old_state = xsp->xs_state;
+ on_marker = (old_state == XSS_MARKER);
+
+ /* If there's a marker on top of the stack, we need to find a real state */
+ while (old_state == XSS_MARKER) {
+ if (xsp == xop->xo_stack)
+ break;
+ xsp -= 1;
+ old_state = xsp->xs_state;
+ }
+
+ /*
+ * At this point, the list of possible states are:
+ * XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
+ * XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
+ */
+ switch (XSS_TRANSITION(old_state, new_state)) {
+
+ open_container:
+ case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
+ rc = xo_do_open_container(xop, flags, name);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_list(xop, NULL);
+ if (rc >= 0)
+ goto open_container;
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ if (rc >= 0)
+ goto open_container;
+ break;
+
+ /*close_container:*/
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
+ /* This is an exception for "xo --close" */
+ rc = xo_do_close_container(xop, name);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ if (rc >= 0)
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ open_list:
+ case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
+ rc = xo_do_open_list(xop, flags, name);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_list(xop, NULL);
+ if (rc >= 0)
+ goto open_list;
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ if (rc >= 0)
+ goto open_list;
+ break;
+
+ /*close_list:*/
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ open_instance:
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
+ rc = xo_do_open_instance(xop, flags, name);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
+ rc = xo_do_open_list(xop, flags, name);
+ if (rc >= 0)
+ goto open_instance;
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
+ if (on_marker) {
+ rc = xo_do_open_list(xop, flags, name);
+ } else {
+ rc = xo_do_close_instance(xop, NULL);
+ }
+ if (rc >= 0)
+ goto open_instance;
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ if (rc >= 0)
+ goto open_instance;
+ break;
+
+ /*close_instance:*/
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_instance(xop, name);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
+ /* This one makes no sense; ignore it */
+ xo_failure(xop, "xo_close_instance ignored when called from "
+ "initial state ('%s')", name ?: "(unknown)");
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ if (rc >= 0)
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ open_leaf_list:
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
+ case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
+ rc = xo_do_open_leaf_list(xop, flags, name);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_list(xop, NULL);
+ if (rc >= 0)
+ goto open_leaf_list;
+ break;
+
+ /*close_leaf_list:*/
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, name);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
+ /* Makes no sense; ignore */
+ xo_failure(xop, "xo_close_leaf_list ignored when called from "
+ "initial state ('%s')", name ?: "(unknown)");
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, name, new_state);
+ break;
+
+ /*emit:*/
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
+ break;
+
+ case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
+ if (on_marker)
+ goto marker_prevents_close;
+ rc = xo_do_close_leaf_list(xop, NULL);
+ break;
+
+ /*emit_leaf_list:*/
+ case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
+ case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
+ rc = xo_do_open_leaf_list(xop, flags, name);
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
+ break;
+
+ case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
+ /*
+ * We need to be backward compatible with the pre-xo_open_leaf_list
+ * API, where both lists and leaf-lists were opened as lists. So
+ * if we find an open list that hasn't had anything written to it,
+ * we'll accept it.
+ */
+ break;
+
+ default:
+ xo_failure(xop, "unknown transition: (%u -> %u)",
+ xsp->xs_state, new_state);
+ }
+
+ return rc;
+
+ marker_prevents_close:
+ xo_failure(xop, "marker '%s' prevents transition from %s to %s",
+ xop->xo_stack[xop->xo_depth].xs_name,
+ xo_state_name(old_state), xo_state_name(new_state));
+ return -1;
+}
+
+int
+xo_open_marker_h (xo_handle_t *xop, const char *name)
+{
+ xop = xo_default(xop);
+
+ xo_depth_change(xop, name, 1, 0, XSS_MARKER,
+ xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
+
+ return 0;
+}
+
+int
+xo_open_marker (const char *name)
+{
+ return xo_open_marker_h(NULL, name);
+}
+
+int
+xo_close_marker_h (xo_handle_t *xop, const char *name)
+{
+ xop = xo_default(xop);
+
+ return xo_do_close(xop, name, XSS_MARKER);
+}
+
+int
+xo_close_marker (const char *name)
+{
+ return xo_close_marker_h(NULL, name);
+}
+
+/*
+ * Record custom output functions into the xo handle, allowing
+ * integration with a variety of output frameworks.
+ */
+void
+xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
+ xo_close_func_t close_func, xo_flush_func_t flush_func)
+{
+ xop = xo_default(xop);
+
+ xop->xo_opaque = opaque;
+ xop->xo_write = write_func;
+ xop->xo_close = close_func;
+ xop->xo_flush = flush_func;
+}
+
+void
+xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
+{
+ xo_realloc = realloc_func;
+ xo_free = free_func;
+}
+
+int
+xo_flush_h (xo_handle_t *xop)
+{
+ static char div_close[] = "</div>";
+ int rc;
+
+ xop = xo_default(xop);
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_HTML:
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
+ XOIF_CLEAR(xop, XOIF_DIV_OPEN);
+ xo_data_append(xop, div_close, sizeof(div_close) - 1);
+
+ if (XOF_ISSET(xop, XOF_PRETTY))
+ xo_data_append(xop, "\n", 1);
+ }
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL);
+ }
+
+ rc = xo_write(xop);
+ if (rc >= 0 && xop->xo_flush)
+ if (xop->xo_flush(xop->xo_opaque) < 0)
+ return -1;
+
+ return rc;
+}
+
+int
+xo_flush (void)
+{
+ return xo_flush_h(NULL);
+}
+
+int
+xo_finish_h (xo_handle_t *xop)
+{
+ const char *cp = "";
+ xop = xo_default(xop);
+
+ if (!XOF_ISSET(xop, XOF_NO_CLOSE))
+ xo_do_close_all(xop, xop->xo_stack);
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_JSON:
+ if (!XOF_ISSET(xop, XOF_NO_TOP)) {
+ if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
+ XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
+ else
+ cp = "{ ";
+ xo_printf(xop, "%*s%s}\n",xo_indent(xop), "", cp);
+ }
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL);
+ break;
+ }
+
+ return xo_flush_h(xop);
+}
+
+int
+xo_finish (void)
+{
+ return xo_finish_h(NULL);
+}
+
+/*
+ * xo_finish_atexit is suitable for atexit() calls, to force clear up
+ * and finalizing output.
+ */
+void
+xo_finish_atexit (void)
+{
+ (void) xo_finish_h(NULL);
+}
+
+/*
+ * Generate an error message, such as would be displayed on stderr
+ */
+void
+xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap)
+{
+ xop = xo_default(xop);
+
+ /*
+ * If the format string doesn't end with a newline, we pop
+ * one on ourselves.
+ */
+ int len = strlen(fmt);
+ if (len > 0 && fmt[len - 1] != '\n') {
+ char *newfmt = alloca(len + 2);
+ memcpy(newfmt, fmt, len);
+ newfmt[len] = '\n';
+ newfmt[len] = '\0';
+ fmt = newfmt;
+ }
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_TEXT:
+ vfprintf(stderr, fmt, vap);
+ break;
+
+ case XO_STYLE_HTML:
+ va_copy(xop->xo_vap, vap);
+
+ xo_buf_append_div(xop, "error", 0, NULL, 0, fmt, strlen(fmt), NULL, 0);
+
+ if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
+ xo_line_close(xop);
+
+ xo_write(xop);
+
+ va_end(xop->xo_vap);
+ bzero(&xop->xo_vap, sizeof(xop->xo_vap));
+ break;
+
+ case XO_STYLE_XML:
+ case XO_STYLE_JSON:
+ va_copy(xop->xo_vap, vap);
+
+ xo_open_container_h(xop, "error");
+ xo_format_value(xop, "message", 7, fmt, strlen(fmt), NULL, 0, 0);
+ xo_close_container_h(xop, "error");
+
+ va_end(xop->xo_vap);
+ bzero(&xop->xo_vap, sizeof(xop->xo_vap));
+ break;
+
+ case XO_STYLE_SDPARAMS:
+ case XO_STYLE_ENCODER:
+ break;
+ }
+}
+
+void
+xo_error_h (xo_handle_t *xop, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_error_hv(xop, fmt, vap);
+ va_end(vap);
+}
+
+/*
+ * Generate an error message, such as would be displayed on stderr
+ */
+void
+xo_error (const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_error_hv(NULL, fmt, vap);
+ va_end(vap);
+}
+
+/*
+ * Parse any libxo-specific options from the command line, removing them
+ * so the main() argument parsing won't see them. We return the new value
+ * for argc or -1 for error. If an error occurred, the program should
+ * exit. A suitable error message has already been displayed.
+ */
+int
+xo_parse_args (int argc, char **argv)
+{
+ static char libxo_opt[] = "--libxo";
+ char *cp;
+ int i, save;
+
+ /* Save our program name for xo_err and friends */
+ xo_program = argv[0];
+ cp = strrchr(xo_program, '/');
+ if (cp)
+ xo_program = cp + 1;
+
+ for (save = i = 1; i < argc; i++) {
+ if (argv[i] == NULL
+ || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
+ if (save != i)
+ argv[save] = argv[i];
+ save += 1;
+ continue;
+ }
+
+ cp = argv[i] + sizeof(libxo_opt) - 1;
+ if (*cp == 0) {
+ cp = argv[++i];
+ if (cp == 0) {
+ xo_warnx("missing libxo option");
+ return -1;
+ }
+
+ if (xo_set_options(NULL, cp) < 0)
+ return -1;
+ } else if (*cp == ':') {
+ if (xo_set_options(NULL, cp) < 0)
+ return -1;
+
+ } else if (*cp == '=') {
+ if (xo_set_options(NULL, ++cp) < 0)
+ return -1;
+
+ } else if (*cp == '-') {
+ cp += 1;
+ if (strcmp(cp, "check") == 0) {
+ exit(XO_HAS_LIBXO);
+
+ } else {
+ xo_warnx("unknown libxo option: '%s'", argv[i]);
+ return -1;
+ }
+ } else {
+ xo_warnx("unknown libxo option: '%s'", argv[i]);
+ return -1;
+ }
+ }
+
+ argv[save] = NULL;
+ return save;
+}
+
+/*
+ * Debugging function that dumps the current stack of open libxo constructs,
+ * suitable for calling from the debugger.
+ */
+void
+xo_dump_stack (xo_handle_t *xop)
+{
+ int i;
+ xo_stack_t *xsp;
+
+ xop = xo_default(xop);
+
+ fprintf(stderr, "Stack dump:\n");
+
+ xsp = xop->xo_stack;
+ for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
+ fprintf(stderr, " [%d] %s '%s' [%x]\n",
+ i, xo_state_name(xsp->xs_state),
+ xsp->xs_name ?: "--", xsp->xs_flags);
+ }
+}
+
+/*
+ * Record the program name used for error messages
+ */
+void
+xo_set_program (const char *name)
+{
+ xo_program = name;
+}
+
+void
+xo_set_version_h (xo_handle_t *xop, const char *version UNUSED)
+{
+ xop = xo_default(xop);
+
+ if (version == NULL || strchr(version, '"') != NULL)
+ return;
+
+ if (!xo_style_is_encoding(xop))
+ return;
+
+ switch (xo_style(xop)) {
+ case XO_STYLE_XML:
+ /* For XML, we record this as an attribute for the first tag */
+ xo_attr_h(xop, "__version", "%s", version);
+ break;
+
+ case XO_STYLE_JSON:
+ /*
+ * For JSON, we record the version string in our handle, and emit
+ * it in xo_emit_top.
+ */
+ xop->xo_version = xo_strndup(version, -1);
+ break;
+
+ case XO_STYLE_ENCODER:
+ xo_encoder_handle(xop, XO_OP_VERSION, NULL, version);
+ break;
+ }
+}
+
+/*
+ * Set the version number for the API content being carried thru
+ * the xo handle.
+ */
+void
+xo_set_version (const char *version)
+{
+ xo_set_version_h(NULL, version);
+}
+
+/*
+ * Generate a warning. Normally, this is a text message written to
+ * standard error. If the XOF_WARN_XML flag is set, then we generate
+ * XMLified content on standard output.
+ */
+void
+xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
+ const char *fmt, va_list vap)
+{
+ xop = xo_default(xop);
+
+ if (fmt == NULL)
+ return;
+
+ xo_open_marker_h(xop, "xo_emit_warn_hcv");
+ xo_open_container_h(xop, as_warning ? "__warning" : "__error");
+
+ if (xo_program)
+ xo_emit("{wc:program}", xo_program);
+
+ if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
+ va_list ap;
+ xo_handle_t temp;
+
+ bzero(&temp, sizeof(temp));
+ temp.xo_style = XO_STYLE_TEXT;
+ xo_buf_init(&temp.xo_data);
+ xo_depth_check(&temp, XO_DEPTH);
+
+ va_copy(ap, vap);
+ (void) xo_emit_hv(&temp, fmt, ap);
+ va_end(ap);
+
+ xo_buffer_t *src = &temp.xo_data;
+ xo_format_value(xop, "message", 7, src->xb_bufp,
+ src->xb_curp - src->xb_bufp, NULL, 0, 0);
+
+ xo_free(temp.xo_stack);
+ xo_buf_cleanup(src);
+ }
+
+ (void) xo_emit_hv(xop, fmt, vap);
+
+ int len = strlen(fmt);
+ if (len > 0 && fmt[len - 1] != '\n') {
+ if (code > 0) {
+ const char *msg = strerror(code);
+ if (msg)
+ xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
+ }
+ xo_emit("\n");
+ }
+
+ xo_close_marker_h(xop, "xo_emit_warn_hcv");
+ xo_flush_h(xop);
+}
+
+void
+xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(xop, 1, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_emit_warn_c (int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_emit_warn (const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_emit_warnx (const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
+ va_end(vap);
+}
+
+void
+xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
+{
+ xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
+ xo_finish();
+ exit(eval);
+}
+
+void
+xo_emit_err (int eval, const char *fmt, ...)
+{
+ int code = errno;
+ va_list vap;
+ va_start(vap, fmt);
+ xo_emit_err_v(0, code, fmt, vap);
+ va_end(vap);
+ exit(eval);
+}
+
+void
+xo_emit_errx (int eval, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_err_v(0, -1, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
+
+void
+xo_emit_errc (int eval, int code, const char *fmt, ...)
+{
+ va_list vap;
+
+ va_start(vap, fmt);
+ xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
+ va_end(vap);
+ xo_finish();
+ exit(eval);
+}
+
+/*
+ * Get the opaque private pointer for an xo handle
+ */
+void *
+xo_get_private (xo_handle_t *xop)
+{
+ xop = xo_default(xop);
+ return xop->xo_private;
+}
+
+/*
+ * Set the opaque private pointer for an xo handle.
+ */
+void
+xo_set_private (xo_handle_t *xop, void *opaque)
+{
+ xop = xo_default(xop);
+ xop->xo_private = opaque;
+}
+
+/*
+ * Get the encoder function
+ */
+xo_encoder_func_t
+xo_get_encoder (xo_handle_t *xop)
+{
+ xop = xo_default(xop);
+ return xop->xo_encoder;
+}
+
+/*
+ * Record an encoder callback function in an xo handle.
+ */
+void
+xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
+{
+ xop = xo_default(xop);
+
+ xop->xo_style = XO_STYLE_ENCODER;
+ xop->xo_encoder = encoder;
+}
diff --git a/libxo/xo.h b/libxo/xo.h
new file mode 100644
index 000000000000..88bcce2999df
--- /dev/null
+++ b/libxo/xo.h
@@ -0,0 +1,596 @@
+/*
+ * Copyright (c) 2014-2015, Juniper Networks, Inc.
+ * All rights reserved.
+ * This SOFTWARE is licensed under the LICENSE provided in the
+ * ../Copyright file. By downloading, installing, copying, or otherwise
+ * using the SOFTWARE, you agree to be bound by the terms of that
+ * LICENSE.
+ * Phil Shafer, July 2014
+ */
+
+/**
+ * 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
+#else
+#define NORETURN
+#endif /* __dead2 */
+
+/*
+ * Normally we'd use the HAVE_PRINTFLIKE define triggered by the
+ * --enable-printflike option to configure, but we don't install
+ * our internal "xoconfig.h", and I'd rather not. Taking the
+ * coward's path, we'll turn it on inside a #if that allows
+ * others to turn it off where needed. Not ideal, but functional.
+ */
+#if !defined(NO_PRINTFLIKE) && !defined(__linux__)
+#define PRINTFLIKE(_x, _y) __printflike(_x, _y)
+#else
+#define PRINTFLIKE(_x, _y)
+#endif /* NO_PRINTFLIKE */
+
+/** Formatting types */
+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_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 */
+#define XOF_INFO XOF_BIT(6) /** Emit additional info fields (HTML) */
+#define XOF_WARN_XML XOF_BIT(7) /** Emit warnings in XML (on stdout) */
+
+#define XOF_NO_ENV XOF_BIT(8) /** Don't look at LIBXO_OPTIONS env var */
+#define XOF_NO_VA_ARG XOF_BIT(9) /** Don't advance va_list w/ va_arg() */
+#define XOF_DTRT XOF_BIT(10) /** Enable "do the right thing" mode */
+#define XOF_KEYS XOF_BIT(11) /** Flag 'key' fields for xml and json */
+
+#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_RESV15 XOF_BIT(15) /* Unused */
+
+#define XOF_NO_TOP XOF_BIT(16) /** Don't emit the top braces in JSON */
+#define XOF_RESV17 XOF_BIT(17) /* Unused */
+#define XOF_UNITS XOF_BIT(18) /** Encode units in XML */
+#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 */
+#define XOF_FLUSH XOF_BIT(22) /** Flush after each xo_emit call */
+#define XOF_FLUSH_LINE XOF_BIT(23) /** Flush after each newline */
+
+#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
+ * additional data emitted via HTML.
+ */
+typedef struct xo_info_s {
+ const char *xi_name; /* Name of the element */
+ const char *xi_type; /* Type of field */
+ 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 */
+
+typedef int (*xo_write_func_t)(void *, const char *);
+typedef void (*xo_close_func_t)(void *);
+typedef int (*xo_flush_func_t)(void *);
+typedef void *(*xo_realloc_func_t)(void *, size_t);
+typedef void (*xo_free_func_t)(void *);
+
+/*
+ * The formatter function mirrors "vsnprintf", with an additional argument
+ * of the xo handle. The caller should return the number of bytes _needed_
+ * to fit the data, even if this exceeds 'len'.
+ */
+typedef int (*xo_formatter_t)(xo_handle_t *, char *, int,
+ const char *, va_list);
+typedef void (*xo_checkpointer_t)(xo_handle_t *, va_list, int);
+
+xo_handle_t *
+xo_create (xo_style_t style, xo_xof_flags_t flags);
+
+xo_handle_t *
+xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags);
+
+void
+xo_destroy (xo_handle_t *xop);
+
+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);
+
+void
+xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func);
+
+void
+xo_set_style (xo_handle_t *xop, xo_style_t style);
+
+xo_style_t
+xo_get_style (xo_handle_t *xop);
+
+int
+xo_set_style_name (xo_handle_t *xop, const char *style);
+
+int
+xo_set_options (xo_handle_t *xop, const char *input);
+
+xo_xof_flags_t
+xo_get_flags (xo_handle_t *xop);
+
+void
+xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags);
+
+void
+xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags);
+
+void
+xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count);
+
+void
+xo_set_formatter (xo_handle_t *xop, xo_formatter_t func, xo_checkpointer_t);
+
+void
+xo_set_depth (xo_handle_t *xop, int depth);
+
+int
+xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap);
+
+int
+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);
+
+int
+xo_open_container (const char *name);
+
+int
+xo_open_container_hd (xo_handle_t *xop, const char *name);
+
+int
+xo_open_container_d (const char *name);
+
+int
+xo_close_container_h (xo_handle_t *xop, const char *name);
+
+int
+xo_close_container (const char *name);
+
+int
+xo_close_container_hd (xo_handle_t *xop);
+
+int
+xo_close_container_d (void);
+
+int
+xo_open_list_h (xo_handle_t *xop, const char *name);
+
+int
+xo_open_list (const char *name);
+
+int
+xo_open_list_hd (xo_handle_t *xop, const char *name);
+
+int
+xo_open_list_d (const char *name);
+
+int
+xo_close_list_h (xo_handle_t *xop, const char *name);
+
+int
+xo_close_list (const char *name);
+
+int
+xo_close_list_hd (xo_handle_t *xop);
+
+int
+xo_close_list_d (void);
+
+int
+xo_open_instance_h (xo_handle_t *xop, const char *name);
+
+int
+xo_open_instance (const char *name);
+
+int
+xo_open_instance_hd (xo_handle_t *xop, const char *name);
+
+int
+xo_open_instance_d (const char *name);
+
+int
+xo_close_instance_h (xo_handle_t *xop, const char *name);
+
+int
+xo_close_instance (const char *name);
+
+int
+xo_close_instance_hd (xo_handle_t *xop);
+
+int
+xo_close_instance_d (void);
+
+int
+xo_open_marker_h (xo_handle_t *xop, const char *name);
+
+int
+xo_open_marker (const char *name);
+
+int
+xo_close_marker_h (xo_handle_t *xop, const char *name);
+
+int
+xo_close_marker (const char *name);
+
+int
+xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...);
+
+int
+xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap);
+
+int
+xo_attr (const char *name, const char *fmt, ...);
+
+void
+xo_error_hv (xo_handle_t *xop, const char *fmt, va_list vap);
+
+void
+xo_error_h (xo_handle_t *xop, const char *fmt, ...);
+
+void
+xo_error (const char *fmt, ...);
+
+int
+xo_flush_h (xo_handle_t *xop);
+
+int
+xo_flush (void);
+
+int
+xo_finish_h (xo_handle_t *xop);
+
+int
+xo_finish (void);
+
+void
+xo_finish_atexit (void);
+
+void
+xo_set_leading_xpath (xo_handle_t *xop, const char *path);
+
+void
+xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...) PRINTFLIKE(3, 4);
+
+void
+xo_warn_c (int code, const char *fmt, ...) PRINTFLIKE(2, 3);
+
+void
+xo_warn (const char *fmt, ...) PRINTFLIKE(1, 2);
+
+void
+xo_warnx (const char *fmt, ...) PRINTFLIKE(1, 2);
+
+void
+xo_err (int eval, const char *fmt, ...) NORETURN PRINTFLIKE(2, 3);
+
+void
+xo_errx (int eval, const char *fmt, ...) NORETURN PRINTFLIKE(2, 3);
+
+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) PRINTFLIKE(3, 0);
+
+void
+xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...) PRINTFLIKE(3, 4);
+
+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);
+
+/**
+ * @brief Lift libxo-specific arguments from a set of arguments
+ *
+ * libxo-enable programs typically use command line options to enable
+ * all the nifty-cool libxo features. xo_parse_args() makes this simple
+ * by pre-processing the command line arguments given to main(), handling
+ * and removing the libxo-specific ones, meaning anything starting with
+ * "--libxo". A full description of these arguments is in the base
+ * documentation.
+ * @param[in] argc Number of arguments (ala #main())
+ * @param[in] argc Array of argument strings (ala #main())
+ * @return New number of arguments, or -1 for failure.
+ */
+int
+xo_parse_args (int argc, char **argv);
+
+/**
+ * This is the "magic" number returned by libxo-supporting commands
+ * when passed the equally magic "--libxo-check" option. If you
+ * return this, we can (unsafely) assume that since you know the magic
+ * handshake, you'll happily handle future --libxo options and not do
+ * something violent like reboot the box or create another hole in the
+ * ozone layer.
+ */
+#define XO_HAS_LIBXO 121
+
+/**
+ * externs for libxo's version number strings
+ */
+extern const char xo_version[]; /** Base version triple string */
+extern const char xo_version_extra[]; /** Extra version magic content */
+
+/**
+ * @brief Dump the internal stack of a libxo handle.
+ *
+ * This diagnostic function is something I will ask you to call from
+ * your program when you write to tell me libxo has gone bat-stink
+ * crazy and has discarded your list or container or content. Output
+ * content will be what we lovingly call "developer entertainment".
+ * @param[in] xop A valid libxo handle, or NULL for the default handle
+ */
+void
+xo_dump_stack (xo_handle_t *xop);
+
+/**
+ * @brief Recode the name of the program, suitable for error output.
+ *
+ * libxo will record the given name for use while generating error
+ * messages. The contents are not copied, so the value must continue
+ * to point to a valid memory location. This allows the caller to change
+ * the value, but requires the caller to manage the memory. Typically
+ * this is called with argv[0] from main().
+ * @param[in] name The name of the current application program
+ */
+void
+xo_set_program (const char *name);
+
+/**
+ * @brief Add a version string to the output, where possible.
+ *
+ * Adds a version number to the output, suitable for tracking
+ * changes in the content. This is only important for the "encoding"
+ * format styles (XML and JSON) and allows a user of the data to
+ * discern which version of the data model is in use.
+ * @param[in] version The version number, encoded as a string
+ */
+void
+xo_set_version (const char *version);
+
+/**
+ * #xo_set_version with a handle.
+ * @param[in] xop A valid libxo handle, or NULL for the default handle
+ * @param[in] version The version number, encoded as a string
+ */
+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/libxo/xo_attr.3 b/libxo/xo_attr.3
new file mode 100644
index 000000000000..c71377fd17eb
--- /dev/null
+++ b/libxo/xo_attr.3
@@ -0,0 +1,60 @@
+.\" #
+.\" # 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 July, 2014
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_attr , xo_attr_h , xo_attr_hv
+.Nd Add attribute name/value pairs to formatted output
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft int
+.Fn xo_attr "const char *name" "const char *fmt" "..."
+.Ft int
+.Fn xo_attr_h "xo_handle_t *handle" "const char *name, const char *fmt" "..."
+.Ft int
+.Fn xo_attr_hv "xo_handle_t *handle" "const char *name" "const char *fmt" "va_list vap"
+.Sh DESCRIPTION
+The
+.Fn xo_attr
+function emits attributes for the XML output style. The attribute
+value is recorded in the
+.Fa handle
+and is attached to the next field that is emitted via a
+.Xr xo_emit 3
+call.
+.Pp
+The
+.Fa name
+parameter give the name of the attribute to be encoded. The
+.Fa fmt
+parameter gives a printf-style format string used to format the
+value of the attribute using any remaining arguments, or the
+.Fa vap
+parameter as passed to
+.Fn xo_attr_hv .
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_attr("seconds", "%ld", (unsigned long) login_time);
+ struct tm *tmp = localtime(login_time);
+ strftime(buf, sizeof(buf), "%R", tmp);
+ xo_emit("Logged in at {:login-time}\\n", buf);
+ XML:
+ <login-time seconds="1408336270">00:14</login-time>
+.Ed
+.Pp
+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 SEE ALSO
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_buf.h b/libxo/xo_buf.h
new file mode 100644
index 000000000000..349e9adefcc5
--- /dev/null
+++ b/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/libxo/xo_config.h b/libxo/xo_config.h
new file mode 100644
index 000000000000..9177962f08d9
--- /dev/null
+++ b/libxo/xo_config.h
@@ -0,0 +1,247 @@
+/* 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.
+ */
+/* #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. */
+#define HAVE_ALLOCA 1
+
+/* 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. */
+#define HAVE_ASPRINTF 1
+
+/* Define to 1 if you have the `bzero' function. */
+#define HAVE_BZERO 1
+
+/* Define to 1 if you have the `ctime' function. */
+#define HAVE_CTIME 1
+
+/* 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
+
+/* Define to 1 if you have the `dlfunc' function. */
+#define HAVE_DLFUNC 1
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `flock' function. */
+#define HAVE_FLOCK 1
+
+/* 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
+
+/* Define to 1 if you have the `crypto' library (-lcrypto). */
+#define HAVE_LIBCRYPTO 1
+
+/* 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
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Support printflike */
+/* #undef HAVE_PRINTFLIKE */
+
+/* Define to 1 if your system has a GNU libc compatible `realloc' function,
+ and to 0 otherwise. */
+#define HAVE_REALLOC 1
+
+/* Define to 1 if you have the `srand' function. */
+#define HAVE_SRAND 1
+
+/* Define to 1 if you have the `sranddev' function. */
+#define HAVE_SRANDDEV 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* 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. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* 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. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strcspn' function. */
+#define HAVE_STRCSPN 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define HAVE_STRLCPY 1
+
+/* 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
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* 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 */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* 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 */
+
+/* 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/"
+
+/* Name of package */
+#define PACKAGE "libxo"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "phil@juniper.net"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libxo"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libxo 0.4.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libxo"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#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
+ 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. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#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. */
+#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/libxo/xo_create.3 b/libxo/xo_create.3
new file mode 100644
index 000000000000..bfbadc4e6ed6
--- /dev/null
+++ b/libxo/xo_create.3
@@ -0,0 +1,67 @@
+.\" #
+.\" # 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_create , xo_create_to_file , xo_destroy
+.Nd create and destroy libxo output handles
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft xo_handle_t *
+.Fn xo_create "unsigned style" "unsigned flags"
+.Ft xo_handle_t *
+.Fn xo_create_to_file "FILE *fp" "unsigned style" "unsigned flags"
+.Ft void
+.Fn xo_destroy "xo_handle_t *handle"
+.Sh DESCRIPTION
+A
+.Nm libxo
+handle can be allocated using the
+.Fn xo_create
+function.
+.Bd -literal -offset indent
+ Example:
+ xo_handle_t *xop = xo_create(XO_STYLE_JSON, XOF_WARN);
+ ....
+ xo_emit_h(xop, "testing\n");
+.Ed
+.Pp
+By default,
+.Nm libxo
+writes output to standard output.
+A convenience
+function is provided for situations when output should be written to a
+different file.
+.Pp
+Use the
+.Dv XOF_CLOSE_FP
+flag to trigger a call to
+.Xr fclose 3
+for the
+.Dv FILE
+pointer when the handle is destroyed.
+.Pp
+The
+.Fn xo_destroy
+function releases a handle and any resources it is
+using.
+Calling
+.Fn xo_destroy
+with a
+.Dv NULL
+handle will release any
+resources associated with the default handle.
+.Sh SEE ALSO
+.Xr xo_emit 3 ,
+.Xr xo_set_options 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_emit.3 b/libxo/xo_emit.3
new file mode 100644
index 000000000000..155ea75d1fdf
--- /dev/null
+++ b/libxo/xo_emit.3
@@ -0,0 +1,104 @@
+.\" #
+.\" # 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 , xo_emit_h , xo_emit_hv
+.Nd emit formatted output based on format string and arguments
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft int
+.Fn xo_emit "const char *fmt" "..."
+.Ft int
+.Fn xo_emit_h "xo_handle_t *xop" "const char *fmt" "..."
+.Ft int
+.Fn xo_emit_hv "xo_handle_t *xop" "const char *fmt" "va_list vap"
+.Sh DESCRIPTION
+The
+.Fn xo_emit
+function emits formatted output using the description in a format
+string along with a set of zero or more arguments, in a style similar
+to
+.Xr printf 3
+but using a more complex format description string, as described in
+.Xr xo_format 5 .
+.Pp
+.Fn xo_emit
+uses the default output handle, as described in
+.Xr libxo 3 ,
+where
+.Fn xo_emit_h
+uses an explicit handle.
+.Fn xo_emit_hv
+accepts a
+.Fa va_list
+for additional flexibility.
+.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
+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 ,
+.Xr libxo 3
diff --git a/libxo/xo_emit_err.3 b/libxo/xo_emit_err.3
new file mode 100644
index 000000000000..bb1ca640c6e7
--- /dev/null
+++ b/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/libxo/xo_encoder.c b/libxo/xo_encoder.c
new file mode 100644
index 000000000000..70195ecaa32e
--- /dev/null
+++ b/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/libxo/xo_encoder.h b/libxo/xo_encoder.h
new file mode 100644
index 000000000000..f73552b11a66
--- /dev/null
+++ b/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/libxo/xo_err.3 b/libxo/xo_err.3
new file mode 100644
index 000000000000..532899ab85ff
--- /dev/null
+++ b/libxo/xo_err.3
@@ -0,0 +1,74 @@
+.\" #
+.\" # 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_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
+.In libxo/xo.h
+.Ft void
+.Fn xo_warn "const char *fmt" "..."
+.Ft void
+.Fn xo_warnx "const char *fmt" "..."
+.Ft void
+.Fn xo_warn_c "int code" "const char *fmt" "..."
+.Ft void
+.Fn xo_warn_hc "xo_handle_t *xop" "int code, const char *fmt" "..."
+.Ft void
+.Fn xo_err "int eval" "const char *fmt" "..."
+.Ft void
+.Fn xo_errc "int eval" "int code" "const char *fmt" "..."
+.Ft void
+.Fn xo_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 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
+.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_emit_err 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_error.3 b/libxo/xo_error.3
new file mode 100644
index 000000000000..e5c99e9dd923
--- /dev/null
+++ b/libxo/xo_error.3
@@ -0,0 +1,41 @@
+.\" #
+.\" # 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_error
+.Nd generate simple error messages in multiple output styles
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_error "const char *fmt" "..."
+.Sh DESCRIPTION
+Use the
+.Fn xo_error
+function to generate error messages to standard error.
+The
+.Fa fmt
+argument is a string containing printf-style formatting
+instructions that describe the remaining arguments.
+.Pp
+When converting an application to
+.Nm libxo ,
+one can replace
+.Em "fprintf(stderr,...)"
+calls with
+.Fn xo_error
+calls.
+.Sh SEE ALSO
+.Xr printf 3 ,
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_finish.3 b/libxo/xo_finish.3
new file mode 100644
index 000000000000..221b1c1779b4
--- /dev/null
+++ b/libxo/xo_finish.3
@@ -0,0 +1,39 @@
+.\" #
+.\" # 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_finish , xo_finish_h
+.Nd finish formatting output
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_finish "void"
+.Ft void
+.Fn xo_finish_h "xo_handle_t *xop"
+.Sh DESCRIPTION
+When the program is ready to exit or close a handle, a call to
+.Fn xo_finish
+is required.
+This flushes any buffered data, closes
+open
+.Nm libxo
+constructs, and completes any pending operations.
+.Pp
+Calling this function is
+.Em vital
+to the proper operation of libxo,
+especially for the non-TEXT output styles.
+.Sh SEE ALSO
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_flush.3 b/libxo/xo_flush.3
new file mode 100644
index 000000000000..e43bae0057e7
--- /dev/null
+++ b/libxo/xo_flush.3
@@ -0,0 +1,35 @@
+.\" #
+.\" # 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_flush , xo_flush_h
+.Nd flush formatted output from libxo handle
+.Sh LIBRARY
+.Lb libxo
+.Sh SYNOPSIS
+.In libxo/xo.h
+.Ft void
+.Fn xo_flush "void"
+.Ft void
+.Fn xo_flush_h "xo_handle_t *handle"
+.Sh DESCRIPTION
+.Nm libxo
+buffers data, both for performance and consistency, but also to
+allow some advanced features to work properly.
+At various times, the
+caller may wish to flush any data buffered within the library.
+The
+.Fn xo_flush
+function is used for this.
+.Sh SEE ALSO
+.Xr xo_emit 3 ,
+.Xr libxo 3
diff --git a/libxo/xo_format.5 b/libxo/xo_format.5
new file mode 100644
index 000000000000..89c010391d51
--- /dev/null
+++ b/libxo/xo_format.5
@@ -0,0 +1,943 @@
+.\" #
+.\" # 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 November 6, 2015
+.Dt LIBXO 3
+.Os
+.Sh NAME
+.Nm xo_format
+.Nd content of format descriptors for xo_emit
+.Sh DESCRIPTION
+.Pp
+.Nm libxo
+uses format strings to control the rendering of data into
+various output styles, including
+.Em text ,
+.Em XML ,
+.Em JSON ,
+and
+.Em HTML .
+Each format string contains a set of zero or more
+.Dq "field descriptions" ,
+which describe independent data fields.
+Each field description contains a set of
+.Dq modifiers ,
+a
+.Dq "content string" ,
+and zero, one, or two
+.Dq "format descriptors" .
+The modifiers tell
+.Nm libxo
+what the field is and how to treat it, while the format descriptors are
+formatting instructions using
+.Xr printf 3 Ns -style
+format strings, telling
+.Nm libxo
+how to format the field.
+The field description is placed inside
+a set of braces, with a colon
+.Ql ( \&: )
+after the modifiers and a slash
+.Ql ( \&/ )
+before each format descriptors.
+Text may be intermixed with
+field descriptions within the format string.
+.Pp
+The field description is given as follows:
+.Bd -literal -offset indent
+ '{' [ role | modifier ]* [',' long-names ]* ':' [ content ]
+ [ '/' field-format [ '/' encoding-format ]] '}'
+.Ed
+.Pp
+The role describes the function of the field, while the modifiers
+enable optional behaviors.
+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
+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
+.Xr xo_emit 3 ,
+function as an unsigned integer.
+.Bd -literal -offset indent
+ 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).
+.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:
+.Bl -column "M" "Name12341234"
+.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"
+.It N "note " "Field is text that follows a value"
+.It P "padding " "Field is spaces needed for vertical alignment"
+.It T "title " "Field is a title value for headings"
+.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"
+.El
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{L:Free}{D::}{P: }{:free/%u} {U:Blocks}\n",
+ free_blocks);
+.Ed
+.Pp
+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).
+.Bd -literal -offset indent
+ 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
+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
+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 "bold " "Start bold text effect"
+.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 "normal " "Reset effects (only)"
+.It "reset " "Reset colors and effects (restore defaults)"
+.It "underline " "Start underline text effect"
+.El
+.Pp
+The following color names are supported:
+.Bl -column "no-underline"
+.It Sy "Name"
+.It black
+.It blue
+.It cyan
+.It default
+.It green
+.It magenta
+.It red
+.It white
+.It yellow
+.El
+.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.
+By marking these distinctly, HTML usage scenarios
+can use CSS to direct their display parameters.
+.Bd -literal -offset indent
+ xo_emit("{D:((}{:name}{D:))}\\n", name);
+.Ed
+.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:})"
+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:})"
+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,
+if preceded by a slash ("/"):
+.Bd -literal -offset indent
+ 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:})"
+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
+the field descriptor, or a printf-style format descriptor can be used,
+if preceded by a slash ("/"):
+.Bd -literal -offset indent
+ xo_emit("{T:Interface Statistics}\\n");
+ xo_emit("{T:/%20.20s}{T:/%6.6s}\\n", "Item Name", "Cost");
+.Ed
+.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
+for the previous value field.
+.Bd -literal -offset indent
+ xo_emit("{Lwc:Distance}{:distance/%u}{Uw:miles}\\n", miles);
+.Ed
+.Pp
+Note that the sense of the 'w' modifier is reversed for units;
+a blank is added before the contents, rather than after it.
+.Pp
+When the
+.Dv XOF_UNITS
+flag is set, units are rendered in XML as the
+.Dq units
+attribute:
+.Bd -literal -offset indent
+ <distance units="miles">50</distance>
+.Ed
+.Pp
+Units can also be rendered in HTML as the "data-units" attribute:
+.Bd -literal -offset indent
+ <div class="data" data-tag="distance" data-units="miles"
+ data-xpath="/top/data/distance">50</div>
+.Ed
+.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
+is the default role; if no other role designation is given, the field
+is a value.
+The field name must appear within the field descriptor,
+followed by one or two format descriptors.
+The first format
+descriptor is used for display styles (TEXT and HTML), while the
+second one is used for encoding styles (XML and JSON).
+If no second
+format is given, the encoding format defaults to the first format,
+with any minimum width removed.
+If no first format is given, both
+format descriptors default to "%s".
+.Bd -literal -offset indent
+ xo_emit("{:length/%02u}x{:width/%02u}x{:height/%02u}\\n",
+ length, width, height);
+ xo_emit("{:author} wrote \"{:poem}\" in {:year/%4d}\\n,
+ author, poem, year);
+.Ed
+.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
+as distinct fields.
+Either the start
+or stop anchor can give a field width and it can be either directly in
+the descriptor or passed as an argument.
+Any fields between the start
+and stop anchor are padded to meet the minimum width given.
+.Pp
+To give a width directly, encode it as the content of the anchor tag:
+.Bd -literal -offset indent
+ xo_emit("({[:10}{:min/%d}/{:max/%d}{]:})\\n", min, max);
+.Ed
+.Pp
+To pass a width as an argument, use "%d" as the format, which must
+appear after the "/".
+Note that only "%d" is supported for widths.
+Using any other value could ruin your day.
+.Bd -literal -offset indent
+ xo_emit("({[:/%d}{:min/%d}/{:max/%d}{]:})\\n", width, min, max);
+.Ed
+.Pp
+If the width is negative, padding will be added on the right, suitable
+for left justification.
+Otherwise the padding will be added to the
+left of the fields between the start and stop anchors, suitable for
+right justification.
+If the width is zero, nothing happens.
+If the
+number of columns of output between the start and stop anchors is less
+than the absolute value of the given width, nothing happens.
+.Pp
+Widths over 8k are considered probable errors and not supported.
+If
+.Dv XOF_WARN
+is set, a warning will be generated.
+.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 t "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
+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.
+.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:
+ xo_emit("{Lc:Name}{:name}\\n", "phil");
+ TEXT:
+ Name:phil
+.Ed
+.Pp
+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:})"
+The display modifier indicated the field should only be generated for
+the display output styles, TEXT and HTML.
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{Lcw:Name}{d:name} {:id/%d}\\n", "phil", 1);
+ TEXT:
+ Name: phil 1
+ XML:
+ <id>1</id>
+.Ed
+.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:})"
+The encoding modifier indicated the field should only be generated for
+the encoding output styles, such as JSON and XML.
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{Lcw:Name}{:name} {e:id/%d}\\n", "phil", 1);
+ TEXT:
+ Name: phil
+ XML:
+ <name>phil</name><id>1</id>
+.Ed
+.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 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
+ EXAMPLE:
+ xo_open_list("user");
+ for (i = 0; i < num_users; i++) {
+ xo_open_instance("user");
+ xo_emit("User {k:name} has {:count} tickets\\n",
+ user[i].u_name, user[i].u_tickets);
+ xo_close_instance("user");
+ }
+ xo_close_list("user");
+.Ed
+.Pp
+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:})"
+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.
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_open_list("user");
+ for (i = 0; i < num_users; i++) {
+ xo_emit("Member {l:name}\n", user[i].u_name);
+ }
+ xo_close_list("user");
+ XML:
+ <user>phil</user>
+ <user>pallavi</user>
+ JSON:
+ "user": [ "phil", "pallavi" ]
+.Ed
+.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
+string values, but no quotes for numeric, boolean, and null data.
+.Xr xo_emit 3
+applies a simple heuristic to determine whether quotes are
+needed, but often this needs to be controlled by the caller.
+.Bd -literal -offset indent
+ EXAMPLE:
+ const char *bool = is_true ? "true" : "false";
+ xo_emit("{n:fancy/%s}", bool);
+ JSON:
+ "fancy": true
+.Ed
+.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
+string values, but no quotes for numeric, boolean, and null data.
+.Xr xo_emit 3
+applies a simple heuristic to determine whether quotes are
+needed, but often this needs to be controlled by the caller.
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{q:time/%d}", 2014);
+ JSON:
+ "year": "2014"
+.Ed
+.Ss "The White Space Modifier ({w:})"
+The white space modifier appends a single space to the data value:
+.Bd -literal -offset indent
+ EXAMPLE:
+ xo_emit("{Lw:Name}{:name}\\n", "phil");
+ TEXT:
+ Name phil
+.Ed
+.Pp
+The white space modifier is only used for the TEXT and HTML output
+styles.
+It is commonly combined with the colon modifier ('{c:}').
+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"
+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
+format the field's contents.
+.Pp
+If the format string is not provided for a value field, it defaults
+to "%s".
+.Pp
+Note a field definition can contain zero or more printf-style
+.Dq directives ,
+which are sequences that start with a '%' and end with
+one of following characters: "diouxXDOUeEfFgGaAcCsSp".
+Each directive
+is matched by one of more arguments to the
+.Xr xo_emit 3
+function.
+.Pp
+The format string has the form:
+.Bd -literal -offset indent
+ '%' format-modifier * format-character
+.Ed
+.Pp
+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.
+.It
+a minus sign ('-'), indicating the output value should be padded on
+the right instead of the left.
+.It
+a leading zero ('0') indicating the output value should be padded on the
+left with zeroes instead of spaces (' ').
+.It
+one or more digits ('0' - '9') indicating the minimum width of the
+argument.
+If the width in columns of the output value is less than
+the minimum width, the value will be padded to reach the minimum.
+.It
+a period followed by one or more digits indicating the maximum
+number of bytes which will be examined for a string argument, or the maximum
+width for a non-string argument.
+When handling ASCII strings this
+functions as the field width but for multi-byte characters, a single
+character may be composed of multiple bytes.
+.Xr xo_emit 3
+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.
+.It
+one or more 'h' characters, indicating shorter input data.
+.It
+one or more 'l' characters, indicating longer input data.
+.It
+a 'z' character, indicating a 'size_t' argument.
+.It
+a 't' character, indicating a 'ptrdiff_t' argument.
+.It
+a ' ' character, indicating a space should be emitted before
+positive numbers.
+.It
+a '+' character, indicating sign should emitted before any number.
+.El
+.Pp
+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:
+.Bl -column C "Argument Type12"