FileMaker + kuromoji.js + Gird.js で形態素解析した後の json データを簡単にリスト表示化

# はじめに
## 前書き
* 本記事は [FileMaker Advent Calendar 2021](https://qiita.com/advent-calendar/2021/filemaker) 21 日目の記事です。

## 対象読者

* Claris FileMaker Pro 19 ユーザ
* FileMaker の WEB ビューアを用いて形態素解析をやってみたいという（奇特な）方
* FileMaker の WEB ビューアで手軽にリスト表示／リスト内ソート／リスト内検索などを実装したい方

## 前提
* 前編記事 [FileMaker + kuromoji.js で形態素解析の序章](https://qiita.com/Hi_Noguchi/items/557df6d3c0a76ae187c3) を読まれていること

## 検証環境
* Windows 10 Pro
* FileMaker Pro 19.4.1
* kuromoji.js 0.1.2
* Grid.js 5.0.2
* インターネット接続環境

## Grid.js とは
* https://gridjs.io/
* オープンソースの JavaScript によるテーブル表現用ライブラリ

# 準備
## Grid.js
* CDN で CSS と JS ファイルを読み込みます
* 前回書いた `g_for_webview` フィールドに、新たに `Grid.js` を読むための記述を加えます。以下の通り

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/gridjs/dist/theme/mermaid.min.css" rel="stylesheet">
</head>
<body>
<div id="result"></div>

<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
<script src="[[[kuromoji_js_path]]]"></script>
<script>
kuromoji.builder({ dicPath: "[[[kuromoji_dict_path]]]" }).build(function (err, tokenizer) {
    var result = tokenizer.tokenize("[[[input]]]")
    var result = JSON.stringify(result, null, 2)
})
</script>
</body>
</html>
```

* 以下の二行が追記されています。

```html
<link href="https://cdn.jsdelivr.net/npm/gridjs/dist/theme/mermaid.min.css" rel="stylesheet">
```
```html
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
```

* 逆に、以下の行を削除しています。

```javascript
document.getElementById('result').innerHTML = result
```

## FileMaker 側
### フィールド追加
* 利便性を兼ねて、形態素解析された JSON データをフィールドに待避させておきたいので、 `json` という安直なフィールドを作成しましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/b60877c8-6bd9-6a86-a91c-632a0cb619f9.png)

### レイアウトの整理
* 前回のままだと雑然としてしまうので、少し整理します。以下のような感じに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/a503b8cb-ac09-e633-9c22-2698dd1f139c.png)


# 作成
## スクリプト作成
* まずは `json` フィールドに kuromoji.js で生成されたデータを入力するためのスクリプトを作成します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/c02a5335-bc2c-401b-cf05-52ddec7115ae.png)

* 計算式の中身は以下のような感じ

```sh
Let (
  [
    json = $json;
    json = Substitute ( json ; "[" ; "" );
    json = Substitute ( json ; "]" ; "" )
  ];

  json
)
```

* `FileMaker.PerformScriptWithOption` から実行されるものになりますので、引数を受け取って、余計な `[]` を除去した上で、フィールドに値を格納するというだけの、単純なものです
  * ここで `[]` を除去してあげないと Grid.js が正しくデータを食べてくれません😢

## FileMaker.PerformScriptWithOption の実行
* `kuromoji.builder` の中で、実行させてあげましょう

```html
<script>
kuromoji.builder({ dicPath: "[[[kuromoji_dict_path]]]" }).build(function (err, tokenizer) {
    var result = tokenizer.tokenize("[[[input]]]")
    var result = JSON.stringify(result, null, 2)
    FileMaker.PerformScriptWithOption ('input_json_field', result, 0)
})
</script>
```

* 以下の一行を追加しています。`input_json_field` というのが、先に作成していたスクリプト名ですね。`result` は直前に定義されている変数です

```javascript
FileMaker.PerformScriptWithOption ('input_json_field', result, 0)
```

* WEB ビューアのオプションで `JavaScript による FileMaker スクリプトの実行を許可` にチェックが入っていれば、これで問題なく動くはずです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/4ff09e0e-dc85-1f92-b85b-1eef75e4d2d4.png)

* 動かしてみると `json` フィールドの中に、以下のように値が格納されることが確認できるはずです

```json
  {
    "word_id": 415760,
    "word_type": "KNOWN",
    "word_position": 1,
    "surface_form": "すもも",
    "pos": "名詞",
    "pos_detail_1": "一般",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "すもも",
    "reading": "スモモ",
    "pronunciation": "スモモ"
  },
  {
    "word_id": 93220,
    "word_type": "KNOWN",
    "word_position": 4,
    "surface_form": "も",
    "pos": "助詞",
    "pos_detail_1": "係助詞",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "も",
    "reading": "モ",
    "pronunciation": "モ"
  },
  {
    "word_id": 1614710,
    "word_type": "KNOWN",
    "word_position": 5,
    "surface_form": "もも",
    "pos": "名詞",
    "pos_detail_1": "一般",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "もも",
    "reading": "モモ",
    "pronunciation": "モモ"
  },
  {
    "word_id": 93220,
    "word_type": "KNOWN",
    "word_position": 7,
    "surface_form": "も",
    "pos": "助詞",
    "pos_detail_1": "係助詞",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "も",
    "reading": "モ",
    "pronunciation": "モ"
  },
  {
    "word_id": 1614710,
    "word_type": "KNOWN",
    "word_position": 8,
    "surface_form": "もも",
    "pos": "名詞",
    "pos_detail_1": "一般",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "もも",
    "reading": "モモ",
    "pronunciation": "モモ"
  },
  {
    "word_id": 93100,
    "word_type": "KNOWN",
    "word_position": 10,
    "surface_form": "の",
    "pos": "助詞",
    "pos_detail_1": "連体化",
    "pos_detail_2": "*",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "の",
    "reading": "ノ",
    "pronunciation": "ノ"
  },
  {
    "word_id": 62510,
    "word_type": "KNOWN",
    "word_position": 11,
    "surface_form": "うち",
    "pos": "名詞",
    "pos_detail_1": "非自立",
    "pos_detail_2": "副詞可能",
    "pos_detail_3": "*",
    "conjugated_type": "*",
    "conjugated_form": "*",
    "basic_form": "うち",
    "reading": "ウチ",
    "pronunciation": "ウチ"
  }
```

## WEB ビューア
* WEB ビューアの指定は以下のように

```sh
Let (
  [
    kuromoji_js_path = $$kuromoji_js_path;
    kuromoji_dict_path = $$kuromoji_dict_path;
    input = texts::input;
    json = texts::json;

    result = texts::g_for_webview;
    result = Substitute ( result ; "[[[kuromoji_js_path]]]" ; kuromoji_js_path );
    result = Substitute ( result ; "[[[kuromoji_dict_path]]]" ; kuromoji_dict_path );
    result = Substitute ( result ; "[[[input]]]" ; input );
    result = Substitute ( result ; "[[[json]]]" ; json )
  ];

  result
)
```

* 以下の二行が追記されています

```sh
json = texts::json;
```

```sh
result = Substitute ( result ; "[[[json]]]" ; json )
```

## g_for_webview
* 最後の仕上げとして Grid.js のレンダリングをしてあげます

```javascript
new gridjs.Grid({
  search: true,
  data: [
    [[[json]]]
  ],
  sort: true,
  fixedHeader: true,
  pagination: {
    enabled: true,
    limit: 100,
    summary: false
  }
}).render(document.getElementById('result'))
```

* 上記を含んだ `g_for_webview` の完成形は以下の通りです（よくわからなければ、もう全コピペしてしまってください） 

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/gridjs/dist/theme/mermaid.min.css" rel="stylesheet">
</head>
<body>
<div id="result"></div>

<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
<script src="[[[kuromoji_js_path]]]"></script>
<script>
kuromoji.builder({ dicPath: "[[[kuromoji_dict_path]]]" }).build(function (err, tokenizer) {
    var result = tokenizer.tokenize("[[[input]]]")
    var result = JSON.stringify(result, null, 2)
    FileMaker.PerformScriptWithOption ('input_json_field', result, 0)
})

new gridjs.Grid({
  search: true,
  data: [
    [[[json]]]
  ],
  sort: true,
  fixedHeader: true,
  pagination: {
    enabled: true,
    limit: 100,
    summary: false
  }
}).render(document.getElementById('result'))
</script>
</body>
</html>
```

# 結果
## とりあえず見た目
* 以下のような感じで出力されるようになります！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/504cde7b-c418-be8b-ed21-a4ab01ff1ffb.png)

## フィルタ
* 以下のように、左上に表示されているフィルタ欄に値を入力するとリアルタイムにフィルタリングされてくれます。数万行程度なら、かなり高速にフィルタリングされてくれます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/2b443f23-899c-baa0-529e-94df551f0faa.png)

* 部分一致で表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/13d250b5-b634-5494-7d4f-fbc27ffcbe06.png)

* 以下のように true / false でフィルタ欄の有無を制御できます。まあ、あって損はないでしょう

```javascript
  search: true,
```

* 対象列は全ての列になってますので、色々と弄りたい方は公式ドキュメントを参照
  * https://gridjs.io/docs/examples/search


## ページネーション
* 件数が多い場合のページネーションも可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/369fa417-b00f-6982-2849-bfcb7b6fda35.png)

* これについては Grid.js の以下の箇所を弄ることで色々と設定可能です

```javascript
  pagination: {
    enabled: true,
    limit: 100,
    summary: false
  }
```

## ソート
* ヘッダ行のところにソート機能をつけるかどうか
* Grid.js の以下の箇所で true / false 設定できます

```javascript
  sort: true,
```

## その他
* ドキュメントが決してわかりよいとは言えないような気がしますけれど、色々困ったら見てみると助かることになるかもしれません
  * https://gridjs.io/docs/examples/hello-world

# おわりに
## 感想
* ね、簡単でしょう？
* 今回は、主には Grid.js が便利だよーという内容の記事になったかと思いますが、形態素解析もこうしてリスト表示化してあげると、何か便利かもしれません？
* ちょっと動的にソートやフィルタなどを兼ね備えたリスト表示をおこなおうとすると、色々と手間がかかってしまうところ、Grid.js を使うと簡単に表現することが可能ですので、今回のような形態素解析に限らずに、json データ + Grid.js は使ってみてください。軽量ですしメンテもされているので、オススメです！
* 形態素解析の次は構文解析になるわけですが、過去に kuromoji.js で構文解析をされている lacolaco さんの記事があるようでしたので、そちらが参考になると思われます（FileMaker でこれ以上は踏み込まずにおきます）
  * [Qiita - kuromoji.js使って構文解析した](https://qiita.com/lacolaco/items/b75dc8689cf4f08b21f6)

## 余談
* ちなみに WEB ビューアにレンダリングするための HTML をわざわざグローバルフィールド使って書いているのは、分かる人には分かると思いますが、エスケープ処理が面倒だし気持ち悪いからです

## オマケ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/60b19ecc-0afd-8d6e-d2f4-cf5e50ed6dbd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/90fbc6ab-1f2c-339a-f445-46e384c76d01.png)