Help us understand the problem. What is going on with this article?

Excel VBAをJavaScriptに翻訳 その6

More than 1 year has passed since last update.

はじめに

前回は、VBAの構文解析する前提としてVBAの予約語の定義とあわせて翻訳のサンプルVBAを提示しました.
今回は、VBA翻訳結果のJavaScriptをコーディングします。
このJavaScriptへの翻訳が後続の道しるべとしたいと考えています。

前提

  • OS : Windows7以上
  • PoweShellのターミナルで実行
  • VSCodeでコード編集
  • node.js環境構築済み

ExcelJSのインストール

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

npm install --save exceljs

JavaScriptへの翻訳

ExcelJSの特徴として読込が非同期のため考慮が必要です。

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

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

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

colorはobjectになっています。
objectは { key : key, val: val } の 構造になっています。

color : [Object]

この文字色を判断する関数を以下のように定義します。

/*
 *  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 を使って制御します。
関数の制御は以下の構造になります。

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関数になります。

/*
 *  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関数になります。

/*
 *  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関数も定義します。

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

tomexcel.js

上記の全ソースです。
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
}; 

runexcel.js

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

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)を指定します。

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

まとめ

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away