JavaScriptでつくるExcel→JSON変換アプリ

[un-T factory! XA Advent Calendar](https://qiita.com/advent-calendar/2017/unt-xa) 14日目の記事です。

#はじめに
[un-T System!](http://www.un-tsystem.com/) の山中です。フロントエンドエンジニアです。

JavaScriptでMacアプリをつくることができるって知ってましたか？
物は試しと、ExcelをJSONに変換するアプリをつくってみました。
Appleによると、この技術はJXA(JavaScript for Automation)という呼び名らしいです。

#きほん
開発にはMac（OS X Yosemite以降）に標準で入ってる「スクリプトエディタ」を使用します。
![script_editor.png](https://qiita-image-store.s3.amazonaws.com/0/163452/d8d3b965-352f-1ded-49a3-84396b3b56e6.png)
すごくシンプルなエディタです。

1. スクリプトエディタを立ち上げる
2. 左上の言語メニューで 「JavaScript」を選択。（選べるのはAppleScriptかJavaScript）
3. コードを書く
4. ファイル → 書き出す（ファイルフォーマット：アプリケーション）

これだけでアプリをつくることができます。 簡単。



#つくった
制作したアプリのコードはこちらです。
Excelファイルをドラッグ＆ドロップすると、JSONファイルが書き出されます。
（注：実行にはMicrosoft Excelがインストールされている必要があります。）

```javascript:JavaScript(JXA)

function analyzeExcel(path) {
  var app, worksheets;
  app = Application('Microsoft Excel');
  app.includeStandardAdditions = true;
  app.activate();
  app.open(path);
  worksheets = app.worksheets;

  // ワークシートごとにJSONをつくる
  for (var i = 0; i < worksheets.length; i++) {
    makeJSON(worksheets[i]);
  }

  function makeJSON(ws) {
    var data = {};
    var title = [];
    var first_row = ws.rows[0];
    var worksheet_name = ws.name();

    // 1行目を見出しとして取得
    for (var col_i = 0; ; col_i++) {
       var check = first_row.columns[col_i].value();
       if (!check) {
          break;
       }
       title.push(check);
    }

    // 2行目からデータとして取得
    for (var row_i = 1; ; row_i++) {
       var row = ws.rows[row_i];
       var row_data = {};
       // 1列目でデータの有無を判断
       var id = row.columns[0].value();
       if (!id) {
          break;
       } else {
          data[id] = {};
       }
       // 行ごとにデータをまとめる
       // 同じ見出しがあれば、配列にまとめる
       for (var i = 1; i < title.length; i++) {
          if (row_data[title[i]]) {
             // 配列化
             if (!Array.isArray(row_data[title[i]])) {
                row_data[title[i]] = [row_data[title[i]]];
             }
             // 追加
             if (row.columns[i].value()) {
                row_data[title[i]].push(row.columns[i].value());
             }
          } else {
             row_data[title[i]] = row.columns[i].value();
          }
       }
       data[id] = row_data;
    }

    // 書き出し設定
    var filePath = app.chooseFileName({
       defaultName: worksheet_name + '.json',
       defaultLocation: app.pathTo('desktop')
    });

    // JSONデータを書き出す（文字コードをUTF-8に変換）
    ObjC.import('Cocoa');
    var text = JSON.stringify(data, null, '  ');
    string = $.NSString.stringWithString(text);
    string.writeToFileAtomicallyEncodingError(
      filePath.toString(),
      true,
      $.NSUTF8StringEncoding,
      $()
    );
  }
}

// アプリにファイルをドラッグ＆ドロップした時の処理
var SystemEvents = Application("System Events");
var fileTypesToProcess = ["ELSX"];
var extensionsToProcess = ["xlsx"];
var typeIdentifiersToProcess = [];
function openDocuments(droppedItems) {
  for (var item of droppedItems) {
    var alias = SystemEvents.aliases.byName(item.toString());
    var extension = alias.nameExtension();
    var fileType = alias.fileType();
    var typeIdentifier = alias.typeIdentifier();
    if (
       fileTypesToProcess.includes(fileType) 
       || extensionsToProcess.includes(extension)
       || typeIdentifiersToProcess.includes(typeIdentifier)
    ) {
      var path = Path(item.toString().slice(1));
      analyzeExcel(path);
    }
  }
}

// アプリのアイコンをダブルクリックした時に使い方を説明する
function run() {
  var sys = Application("System Events");
  sys.includeStandardAdditions = true;
  sys.displayDialog("Excelファイル（xlsx）をドラッグ&ドロップしてください。JSONファイルに変換します。");
}


```


以下が変換ルールです。

- ExcelのワークシートごとにJSONファイルを作成
- 1行目を見出し、2行目以降を値として扱う
- 1列目の値をキーに使い、行ごとにデータをまとめる
- 見出しが同じ場合、配列にする
- UTF-8に変換する（変換しないと文字化け）

たとえば、このようなExcelファイルをドラッグ＆ドロップすると…

![table.png](https://qiita-image-store.s3.amazonaws.com/0/163452/668598f5-c764-7e9f-527b-924048e56b75.png)


このようなJSONが書き出されます！

```json:JSON
{
  "a": {
    "name": "John",
    "num": [
      11,
      12,
      13
    ]
  },
  "b": {
    "name": "Paul",
    "num": [
      21,
      22
    ]
  },
  "c": {
    "name": "George",
    "num": [
      31
    ]
  },
  "d": {
    "name": "Ringo",
    "num": ""
  }
}
```

#まとめ
求めている情報を探すのに時間がかかりました。（スクリプトエディタのライブラリは読みにくい！）
ただ、JavaScriptを使うことができるという利点は大きいです。
いろいろな事が出来るみたいなので、何か便利なものをつくりながら知見を貯めていこうと思います。