LoginSignup
0
1

More than 3 years have passed since last update.

Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る(EXCELの読み込みオブジェクトの追加)

Posted at

概要

前回投稿した「Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る」にEXCELを読み込む関数を追加する。これを実装することでGo側でJavaScriptのJSON, Arrayの作り方、そしてJavaScript側のCallback関数の呼び出しを実装する

環境

  • 前回投稿のソースに追加するだけなので、全ソースコードは掲載しません
  • EXCELのパッケージ
    • github.com/tealeg/xlsx
  • テストEXCELデータ
    • ブック名 Book1.xlsx
    • シート名 Sheet1
A B C
1 111 aaa
2 112 bbb
3 113 ccc
4 114 ddd

実装

excel(ブック名, シート名)の関数を定義する

initialSetting()
    rt.Set("excel", js.excel)

import "github.com/tealeg/xlsx"

type excelSheet struct {
    js    *jsRuntime
    sheet *xlsx.Sheet
}

func (js *jsRuntime) excel(file, sheet string) *excelSheet {
    rt := js.runtime
    xfile, err := xlsx.OpenFile(file)
    if err != nil {
        rt.Interrupt(rt.NewGoError(err))
        return nil
    }
    xsheet := xfile.Sheet[sheet]
    if xsheet == nil {
        rt.Interrupt("Error: not found excel sheet: " + sheet)
        return nil
    }
    return &excelSheet{js: js, sheet: xsheet}
}

オブジェクトは出来ましたがメソッドがないので、これから次の3メソッドを作っていきます。すべてJavaScriptのCallBack関数を呼び出す仕様です。

  • ForEachSlice
    • Go言語のSliceを返すがJavaScriptのArrayではないのでArrayのメソッドで使えないものがあります。
  • ForEachArray
    • JavaScriptのArrayオブジェクトを返す
  • ForEachJSON
    • JavaScriptのJSONオブジェクトを返す

ForEachSliceの実装

convData関数はEXCELのデータタイプを判断してタイプに合った型に変換します。ただし、今回はxlsx.CellTypeNumericとそれ以外だけにしました。

func (s *excelSheet) convData(cell *xlsx.Cell) goja.Value {
    rt := s.js.runtime
    typ := cell.Type()
    if typ == xlsx.CellTypeNumeric {
        val, err := cell.Int()
        if err == nil {
            return rt.ToValue(val)
        }
        return rt.ToValue(cell.String())
    }
    return rt.ToValue(cell.String())
}

ForEachSliceのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のSlice)です。行番号は1から始めます。

func (s *excelSheet) ForEachSlice(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        cells := make([]interface{}, len(row.Cells))
        for cix, cell := range row.Cells {
            cells[cix] = s.convData(cell)
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), rt.ToValue(cells))
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}

callBackの第一引数をundefinedとしていますが、第一引数はJavaScript側でthisとして使われますので、JavaScript側でthisが必要ならForEachSliceの引数にthisが受け取れるように追加してください。当然呼び出し側もthisにしたいオブジェクトをセットします。

func (s *excelSheet) ForEachSlice(this goja.Value, callBack goja.Callable) {
       callBack(this, rt.ToValue(rix+1), rt.ToValue(cells))
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachSlice(function(ix, row) {
...>    print(ix, row)
...> })
1 [1,111,"aaa"]
2 [2,112,"bbb"]
3 [3,113,"ccc"]
4 [4,114,"ddd"]
undefined
>

JavaScriptのArrayではないのでpushなどは使えません。forEachは使えます。

go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachSlice(function(ix, row) {
...>   row.push(ix)
...>   print(ix,row)
...> })
TypeError: Cannot extend Go slice at push (native) at console:1:43(9)
>

ForEachArrayの実装

newArray関数はArrayオブジェクトの生成と要素を追加するpushメソッドを取得します

func (s *excelSheet) newArray() (goja.Value, goja.Callable) {
    rt := s.js.runtime
    arr, err := rt.RunString("new Array()")
    if err != nil {
        rt.Interrupt(err)
        return nil, nil
    }
    arrObj, ok := arr.(*goja.Object)
    if !ok {
        rt.Interrupt("Array not defined")
        return nil, nil
    }

    push := arrObj.Get("push")
    pushFunc, ok := goja.AssertFunction(push)
    if !ok {
        rt.Interrupt("Array.push not defined")
        return nil, nil
    }
    return arr, pushFunc
}

ForEachArrayのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のArray)です。行番号は1から始めます。

func (s *excelSheet) ForEachArray(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        arr, pushFunc := s.newArray()
        if arr == nil {
            return
        }
        for _, cell := range row.Cells {
            pushFunc(arr, s.convData(cell))
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), arr)
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachArray(function(ix, row) {
...>   print(ix,row)
...> })
1 [1,111,"aaa"]
2 [2,112,"bbb"]
3 [3,113,"ccc"]
4 [4,114,"ddd"]
undefined
>

上記の結果はForEachSliceと変わらなく見えます。今度はpushを使ってみます。

go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachArray(function(ix, row) {
...>   row.push(ix)
...>   print(ix,row)
...> })
1 [1,111,"aaa",1]
2 [2,112,"bbb",2]
3 [3,113,"ccc",3]
4 [4,114,"ddd",4]
undefined
>

ちゃんとpushできています。

ForEachJSONの実装

ForEachJSONのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のJSON)です。行番号は1から始めます。下記のJSONのフィールド名はA1,B1,C1のようにEXCELのセルの形式にしました。ただしA~Zまでです。

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

func (s *excelSheet) ForEachJSON(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        json := rt.NewObject()
        for cix, cell := range row.Cells {
            name := fmt.Sprintf("%s%d", alphabet[cix:cix+1], rix+1)
            json.Set(name, s.convData(cell))
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), json)
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachJSON(function(ix, row) {
...>   print(ix,JSON.stringify(row))
...> })
1 {"A1":1,"B1":111,"C1":"aaa"}
2 {"A2":2,"B2":112,"C2":"bbb"}
3 {"A3":3,"B3":113,"C3":"ccc"}
4 {"A4":4,"B4":114,"C4":"ddd"}
undefined
>

サブドキュメントを作るには

   json := rt.NewObject()
   json.Set("a", rt.ToValue(1))
   subdoc := rt.NewObject()
   subdoc.Set("field1", rt.ToValue("abcde"))
   json.Set("subdoc", subdoc)

とすると下記のようなJSONができます。

{"a":1,"subdoc":{"field1":"abcde"}}

参照

まとめ

3つのメソッドを用意しましたが、一つで良ければexcel(ブック名,シート名,callback関数)だけで良いかもしれません。

これでJSONとcallbackができたのでほとんどの事が出来ます。
データ量が少なければ2次元配列またはJSONの配列で全データを返すもありだと思います。この時はcallback関数は不要でexcel()の戻り値として処理します。

いままでエラーはInterruptを使っていますが、JavaScript実行中に呼び出されたGo言語でpanicを使っても異常終了せずに、JavaScriptでthrowと同様のふるまいをします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1