Excel VBAをJavaScriptに翻訳 その６

# はじめに
[**前回**](https://qiita.com/pooshikin/items/89610f11f3fab3baf591)は、VBAの構文解析する前提としてVBAの予約語の定義とあわせて翻訳のサンプルVBAを提示しました.
今回は、VBA翻訳結果のJavaScriptをコーディングします。
このJavaScriptへの翻訳が後続の道しるべとしたいと考えています。
# 前提
* OS : Windows7以上
* PoweShellのターミナルで実行
* VSCodeでコード編集
* node.js環境構築済み

# ExcelJSのインストール
Excelのブックを読み書きするために、npmから**ExcelJS**をローカルにインストールします。

```shell
npm install --save exceljs
```

# JavaScriptへの翻訳
**ExcelJS**の特徴として読込が非同期のため考慮が必要です。

```JavaScript
// read from a file
var workbook = new Excel.Workbook();
workbook.xlsx.readFile(filename)
    .then(function() {
        // use workbook
    });
```

またシートのセルはリッチテキストとして取得可能で、内容はJSON形式で値を得ることができます。
前回準備したExcelのブックのセル(A1)の取得結果が以下になります。

```shell

[ { text: '徳川 家康（とくがわ いえやす、旧字体: 德川家康）または' },
  { font:
     { size: 11,
       color: [Object],
       name: 'ＭＳ Ｐゴシック',
       family: 3,
       charset: 128,
       scheme: 'minor' },
    text: '松平 元康' },
  { font:
     { size: 11,
       name: 'ＭＳ Ｐゴシック',
       family: 3,
       charset: 128,
       scheme: 'minor' },
    text: '（' },
  { font:
     { size: 11,
       color: [Object],
       name: 'ＭＳ Ｐゴシック',
       family: 3,
       charset: 128,
       scheme: 'minor' },
    text: 'まつだいら もとやす' },
  { font:
     { size: 11,
       name: 'ＭＳ Ｐゴシック',
       family: 3,
       charset: 128,
       scheme: 'minor' },
    text: '）' },
  { font:
     { size: 11,
       color: [Object],
       name: 'ＭＳ Ｐゴシック',
       family: 2,
       scheme: 'minor' },
    text: 'は、戦国時代から安土桃山時代にかけての武将・戦国大名[1]。江戸幕府の初代征夷大将軍[1]。三英傑の一人。「海道一の' },
  { font:
     { size: 11,
       color: [Object],
       name: 'ＭＳ Ｐゴシック',
       family: 3,
       charset: 128,
       scheme: 'minor' },
    text: '弓取り' },
  { font:
     { size: 11,
       color: [Object],
       name: 'ＭＳ Ｐゴシック',
       family: 2,
       scheme: 'minor' },
    text: '」の異名を持つ。' } ]
```
colorはobjectになっています。
objectは { key : key, val: val } の 構造になっています。

```Shell
color : [Object]
```
この文字色を判断する関数を以下のように定義します。

```JavaScript
/*
 *  Color フォント属性の設定色を返却
 *  @PARAM {any}  : obj - font object
 *  @Return {any} : color - color
 */
function Color(obj) {
    if (typeof obj == 'object') {
        var color = '';
        for (key in obj) {
            if (key == 'color') {
                for (val in obj[key]) {
                    if (val == 'argb') {
                        color = obj[key][val];
                    }
                }
            }
        }
        return color;
    } else {
        return obj;
    }
}
```
読み取りが非同期のため **Promise** を使って制御します。
関数の制御は以下の構造になります。

```JavaScript
function 関数名(引数1, 引数2) {
    return new Promise(function(resolve) {
        // asynchronous : 非同期処理
        wb.xlsx.readFile(filePath).then(function() {
            let sh = wb.getWorksheet("Sheet1");
            処理....
            ret = 処理結果;
            resolve(ret);
        });
    });
}

function ラッパー関数(引数1, 引数2) {
    return 関数名(引数1, 引数2).then(function(val) {
      return val;
    });
}

```
**Promise** の関数を呼び出すラッパー関数に引数を与え、処理結果を取得します。

## AnsColor : 問題取得関数

文字色が赤の文字を'□'に変換する関数を作成します。
AnsColorがラッパー関数で、GetProblemが**Promise**関数になります。

```JavaScript
/*
 *  GetAns 問題取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : resolve - Problem
 */
function GetProblem(adrs, clr) {
    return new Promise(function(resolve) {
        // asynchronous : 非同期
        wb.xlsx.readFile(filePath).then(function() {
            let sh = wb.getWorksheet("Sheet1");
            let cell = sh.getCell(adrs).value;
            let buf = '';
            for (i = 0 ; i < cell.richText.length ; i++) {
                if (Color(cell.richText[i].font) == clr) {
                    for ( j = 0 ; j < cell.richText[i].text.length ; j++) {
                        buf = buf +  '□';
                    }
                } else {
                    buf = buf +  cell.richText[i].text;
                }
            }
            resolve(buf);
        });
    });
}

function AnsColor(adrs, clr) {
    return GetProblem(adrs, clr).then(function(val) {
      return val;
    });
}

```

## AnzColor : 解答取得

文字色が赤の文字列を取り出す関数を作成します。
AnzColorがラッパー関数で、GetAnsが**Promise**関数になります。

```JavaScript
/*
 *  GetAns 解答取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : resolve - Answer 
 */
function GetAns(adrs, clr) {
    return new Promise(function(resolve) {
        // asynchronous : 非同期
        wb.xlsx.readFile(filePath).then(function() {
            let sh = wb.getWorksheet("Sheet1");
            let cell = sh.getCell(adrs).value;
            let buf = '';
            for (i = 0 ; i < cell.richText.length ; i++) {
                if (Color(cell.richText[i].font) == clr) {
                    buf = buf + "," + cell.richText[i].text;
                }
            }
            resolve(Mid(buf,2));
        });
    });
}

/*
 *  AnzColor 文字列より指定色の文字列を取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : val - Answer 
 */
function AnzColor(adrs, clr) {
    return GetAns(adrs, clr).then(function(val) {
      return val;
    });
}
```

## ファイルヘッダーの宣言と他の関数

関数で共通で使用する変数を宣言します。
Assert/AssertN/Left/Right/Mid関数も定義します。

```JavaScript

const Excel = require('exceljs');
let wb = new Excel.Workbook();
const path = require('path');
let filePath = path.resolve(__dirname,'temp.xlsx');
```
# tomexcel.js

<details><summary>上記の全ソースです。</summary><div>

```JavaScript

const Excel = require('exceljs');
let wb = new Excel.Workbook();
const path = require('path');
let filePath = path.resolve(__dirname,'temp.xlsx');

/*
 * Assert 引数有無判定
 * @PARAM {any} : a - Message
 * @PARAM {any} : b - Character String
 */
function Assert(a, b) {
    if (!b) { console.log(a + " 引数がありません"); return true; } else { return false; }   
}

/*
 * AssertN 引数数値判定
 * @PARAM {any} : a - Message
 * @PARAM {any} : b - Numeric Value
 */
function AssertN(a, b) { 
    if (Assert(a, b)) {return true;} if (isNaN(b)) { console.log(a + " 数値ではありません"); return true; 
    } else { return false; }
}

/*
 * Left
 * @PARAM {any} : str - Character String
 * @PARAM {any} : size - Character Length
 */
function Left(str, size) {
    if (Assert("Left " + "str", str)) {return Err;}
    if (AssertN("Left " + "size", size)) {return Err;}
    let len = (str.length < size) ? str.length : size;
    return str.substring(0, len);
}

/* Right
 * @PARAM {any} : str - Character String
 * @PARAM {any} : size - Character Length
 */
function Right(str, size) {
    if (Assert("Right " + "str", str)) {return Err;}
    if (AssertN("Right " + "size", size)) {return Err;}
    let len = (str.length < size) ? size : str.length;
    return str.substr(len - size, size);
}

/* Mid
 * @PARAM {any} : str - Character String
 * @PARAM {any} : pos - Start Position
 * @PARAM {any} : size - Character Length
 */
function Mid(str, pos, size) {
    if (Assert("Mid " + "str", str)) {return Err;}
    if (AssertN("Mid " + "pos", pos)) {return Err;}
    // size指定あり
    if(size) {if (AssertN("Mid " + "size", size)) {return Err;}}        
    let len = size + 1 || str.length - pos + 1;
    return str.substring(pos - 1, len + 1);
}

/*
 *  Color フォント属性の設定色を返却
 *  @PARAM {any}  : obj - font object
 *  @Return {any} : color - color
 */
function Color(obj) {
    if (typeof obj == 'object') {
        let color = '';
        for (key in obj) {
            if (key == 'color') {
                for (val in obj[key]) {
                    if (val == 'argb') {
                        color = obj[key][val];
                    }
                }
            }
        }
        return color;
    } else {
        return obj;
    }
}

/*
 *  GetAns 問題取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : resolve - Problem
 */
function GetProblem(adrs, clr) {
    return new Promise(function(resolve) {
        // asynchronous : 非同期
        wb.xlsx.readFile(filePath).then(function() {
            let sh = wb.getWorksheet("Sheet1");
            let cell = sh.getCell(adrs).value;
            let buf = '';
            for (i = 0 ; i < cell.richText.length ; i++) {
                if (Color(cell.richText[i].font) == clr) {
                    for ( j = 0 ; j < cell.richText[i].text.length ; j++) {
                        buf = buf +  '□';
                    }
                } else {
                    buf = buf +  cell.richText[i].text;
                }
            }
            resolve(buf);
        });
    });
}


function AnsColor(adrs, clr) {
    return GetProblem(adrs, clr).then(function(val) {
      return val;
    });
}

/*
 *  GetAns 解答取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : resolve - Answer 
 */
function GetAns(adrs, clr) {
    return new Promise(function(resolve) {
        // asynchronous : 非同期
        wb.xlsx.readFile(filePath).then(function() {
            let sh = wb.getWorksheet("Sheet1");
            let cell = sh.getCell(adrs).value;
            let buf = '';
            for (i = 0 ; i < cell.richText.length ; i++) {
                if (Color(cell.richText[i].font) == clr) {
                    buf = buf + "," + cell.richText[i].text;
                }
            }
            resolve(Mid(buf,2));
        });
    });
}

/*
 *  AnzColor 文字列より指定色の文字列を取得
 *  @PARAM {any}  : adrs - Cell Address
 *  @PARAM {any}  : cls  - font Color
 *  @Return {any} : val - Answer 
 */
function AnzColor(adrs, clr) {
    return GetAns(adrs, clr).then(function(val) {
      return val;
    });
}

module.exports = { 
    AnsColor: AnsColor,
    AnzColor: AnzColor
}; 

```
</div></details>

# runexcel.js

作成した関数を実行する処理を作成します。

```JavaScript

if( process.argv[2] == undefined || process.argv[3] == undefined ) {
    console.log( '引数を指定してください！' );
    return;
};

let f = require('./tomexcel.js');
f.AnsColor(process.argv[2], process.argv[3]).then(function(val) {
    console.log(val);
});

f.AnzColor(process.argv[2], process.argv[3]).then(function(val) {
    console.log(val);
});
```

# 実行結果

runexcel.jsの引数にセル(**A1**)と赤(**FFFF0000**)を指定します。

```Shell
PS C:\~\tom> node runexcel.js 'A1' 'FFFF0000'
徳川 家康（とくがわ いえやす、旧字体: 德川家康）または□□□□□（□□□□□□□□□□）は、戦国時代から安土桃山時代にかけての武将・戦国大名[1]。江戸幕府の初代征夷
大将軍[1]。三英傑の一人。「海道一の□□□」の異名を持つ。
松平 元康,まつだいら もとやす,弓取り
PS C:\~\tom>
```

# まとめ
ExcelJSを使用してExcelのブックを読込んで結果を取得するJavaScriptをコーディングしました。
（試験が不十分のためバグがありあそうです）
なんとなく翻訳結果を作成してみました。これに至るようにVBAからの翻訳を設計してみます。
次回はブックを更新するコーディングをやります。