概要
前回投稿した「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(ブック名, シート名)
の関数を定義する
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
のメソッドで使えないものがあります。
- Go言語の
- ForEachArray
- JavaScriptの
Array
オブジェクトを返す
- JavaScriptの
- ForEachJSON
- JavaScriptの
JSON
オブジェクトを返す
- JavaScriptの
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"}}
参照
- EXCELパッケージ https://github.com/tealeg/xlsx
まとめ
3つのメソッドを用意しましたが、一つで良ければexcel(ブック名,シート名,callback関数)
だけで良いかもしれません。
これでJSONとcallbackができたのでほとんどの事が出来ます。
データ量が少なければ2次元配列またはJSONの配列で全データを返すもありだと思います。この時はcallback関数は不要でexcel()
の戻り値として処理します。
いままでエラーはInterrupt
を使っていますが、JavaScript実行中に呼び出されたGo言語でpanic
を使っても異常終了せずに、JavaScriptでthrow
と同様のふるまいをします。