FreeBSD Advent Calendar 2021 7日目の記事です。
今日は2日目の記事で紹介したLibXoの使い方を簡単なサンプルで紹介しようと思います。
LibXoのドキュメント
FreeBSDのベースシステムに組み込まれていることもあり、libxo(3)でマニュアルが参照できたり、チュートリアルを兼ねたサンプルも存在しているのですが、ドキュメントはちょっと情報が古い感じです。
JSON/XMLの生成は xo_finish()
で終了させるのですが、上記のサンプルには記載されていない(ドキュメントが更新されていない?)ですね…。
そのため、LibXoを使用する小さなサンプルを自分で作成して使い方を調べてみましょう。
LibXoを用いたサンプル
サンプルとして、LibXoを使用して以下のような小さなJSONデータの生成を考えてみます。
{
"data": {
"item": [
{
"name": "みかん",
"price": 120
},
{
"name": "大根",
"price": 140
}
]
}
}
サンプルコードは以下になります。ごく小さいサンプルなので、LibXoを使用するプログラムのひな型にも応用できそうです。
#include <libxo/xo.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
struct item {
const char *name;
int price;
const char *kind;
} list[] = {
{ "みかん", 120, "果物" },
{ "大根", 140, "野菜" },
{ NULL, 0 }
};
#if 0
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;
#endif
xo_info_t info[] = {
{ "name", "string", NULL },
{ "price", "number", NULL },
{ "kind", "string", NULL },
{ 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, "data");
xo_open_list("item");
for(struct item *ip = list; ip->name; ip++) {
xo_open_instance("item");
xo_attr("kind", ip->kind);
xo_emit("{L:Item} '{:name/%s}':\n", ip->name);
xo_emit("{L:Price} '{:price/%d}':\n", ip->price);
xo_close_instance("item");
}
xo_close_list("item");
xo_close_container_h(NULL, "data");
xo_finish();
return 0;
}
LibXoを用いたデータ生成手順
データの定義
今回のサンプルでは、以下の3つの要素をデータに含めています。
- 商品名(name)
- 価格(price)
- 商品種別(kind)
具体的には以下のような構造体でデータを持ちます。
struct item {
const char *name;
int price;
const char *kind;
} list[] = {
{ "みかん", 120, "果物" },
{ "大根", 140, "野菜" },
{ NULL, 0 }
};
データの情報を定義する
次に生成するデータの型を struct xo_info_s
で定義します。
(ただ、ソースコメントを見るとHTMLとして出力する場合に使われ、JSON/XML出力の場合は不要かもしれません)
#if 0
/*
* 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;
#endif
xo_info_t info[] = {
{ "name", "string", NULL },
{ "price", "number", NULL },
{ "kind", "string", NULL },
{ NULL, NULL, NULL }
};
出力するデータを記述する
LibXoでデータを出力する際は、 xo_
というプレフィックスがついた関数を使用します。
出力処理の大まかな流れをJSON形式で出力する場合を例に解説します。
まずは xo_parse_args()
でプログラムに渡される --libxo json,pretty
等のLibXo向けの引数を処理します。
xo_open_container_h()
でトップレベルのオブジェクトキーを宣言し、 xo_open_list()
で配列形式の始まりを宣言します。配列の各要素ごとに xo_open_instance()
~ xo_close_instance()
を使用し、データは xo_emit()
で出力します。
また、XMLで <ITEM attribute="...">
のような属性を持つデータは xo_attr()
で属性値を生成することができます。
ただ、 xo_attr()
で宣言したデータはJSON形式では出力されないようです。たしかにJSON/XMLを相互変換する場合はXMLの属性値をJSONでどう扱うかは悩ましいところですね…。
argc = xo_parse_args(argc, argv);
...
xo_set_info(NULL, info, info_count);
xo_open_container_h(NULL, "data");
xo_open_list("item");
...
xo_open_instance("item");
xo_attr("kind", ip->kind);
xo_emit("{L:Item} '{:name/%s}':\n", ip->name);
xo_close_instance("item");
...
xo_close_list("item");
xo_close_container_h(NULL, "data");
xo_finish();
サンプルを動かしてみる
作成したサンプルを動かしてみます。LibXoを用いることで、テキスト形式だけでなく、JSON/XML、HTML形式に変換して出力できるようになります。
$ clang -Wall -Werror -g -o libxo_sample libxo_sample.c -lxo
$
$ # テキストで出力する。
$ ./libxo_sample
Item 'みかん':
Price '120':
Item '大根':
Price '140':
$
$ # XMLで出力する。
$ ./libxo_sample --libxo xml,pretty
<data>
<item>
<name kind="果物">みかん</name>
<price>120</price>
</item>
<item>
<name kind="野菜">大根</name>
<price>140</price>
</item>
</data>
$
$ # JSONで出力する。
$ ./libxo_sample --libxo json,pretty
{
"data": {
"item": [
{
"name": "みかん",
"price": 120
},
{
"name": "大根",
"price": 140
}
]
}
}
$
$ # HTMLで出力する。
$ ./libxo_sample --libxo html,pretty
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
<div class="data" data-tag="name">みかん</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="label">Price</div>
<div class="text"> '</div>
<div class="data" data-tag="price">120</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="label">Item</div>
<div class="text"> '</div>
<div class="data" data-tag="name">大根</div>
<div class="text">':</div>
</div>
<div class="line">
<div class="label">Price</div>
<div class="text"> '</div>
<div class="data" data-tag="price">140</div>
<div class="text">':</div>
</div>
まとめ
簡単なサンプルでLibXoライブラリの使い方を紹介しました。
今回は小さなサンプルでしたが、 xo_emit()
は printf()
のような書式指示子を指定できるようで、その辺りも追って調べてみたいところです。