JSON/XMLを出力する簡単なサンプルでLibXoの使い方を調べてみる

[FreeBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/freebsd) 7日目の記事です。
今日は[2日目の記事](https://qiita.com/furandon_pig/items/28b4d83e24dfd4dda115)で紹介した[LibXo](https://wiki.freebsd.org/LibXo)の使い方を簡単なサンプルで紹介しようと思います。

## LibXoのドキュメント

FreeBSDのベースシステムに組み込まれていることもあり、[libxo(3)](https://www.freebsd.org/cgi/man.cgi?query=libxo&sektion=3&n=1)でマニュアルが参照できたり、[チュートリアルを兼ねたサンプル](http://juniper.github.io/libxo/libxo-manual.html#unit-test)も存在しているのですが、ドキュメントはちょっと情報が古い感じです。
JSON/XMLの生成は `xo_finish()` で終了させるのですが、上記のサンプルには記載されていない(ドキュメントが更新されていない？)ですね…。
そのため、LibXoを使用する小さなサンプルを自分で作成して使い方を調べてみましょう。

## LibXoを用いたサンプル

サンプルとして、LibXoを使用して以下のような小さなJSONデータの生成を考えてみます。

```json
{
  "data": {
    "item": [
      {
        "name": "みかん",
        "price": 120
      },
      {
        "name": "大根",
        "price": 140
      }
    ]
  }
}
```

サンプルコードは以下になります。ごく小さいサンプルなので、LibXoを使用するプログラムのひな型にも応用できそうです。

```c
#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)

具体的には以下のような構造体でデータを持ちます。

```c
    struct item {
        const char *name;
        int price;
        const char *kind;
    } list[] = {
        { "みかん", 120, "果物"   },
        { "大根",   140, "野菜" },
        { NULL, 0 }
    };
```

### データの情報を定義する

次に生成するデータの型を `struct xo_info_s` で定義します。
(ただ、ソースコメントを見るとHTMLとして出力する場合に使われ、JSON/XML出力の場合は不要かもしれません)


```c
#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でどう扱うかは悩ましいところですね…。

```c
    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形式に変換して出力できるようになります。

```sh
$ 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()` のような書式指示子を指定できるようで、その辺りも追って調べてみたいところです。