Go xlsファイルを読み取る

# Go xlsファイルを読み取る

今回は、Goで Excel の xlsファイル(Excel 97 ～ Excel 2003 ブック) を読み取る方法を考えてみます。

xlsxファイルの場合は、 [excelize](https://qiita.com/hiro_nico/items/0f180f2dfc62cf2559c7) がオススメです。

## ライブラリ

簡単に読み取りを行うなら、下記のライブラリを使うとよいです。

GitHub - extrame/xls: Pure Golang xls library
https://github.com/extrame/xls

↓ Example を参考に書いてみるとこんな感じ

```go
package main

import (
	"fmt"
	"log"

	"github.com/extrame/xls"
)

func main() {
	xlFile, err := xls.Open("input.xls", "utf-8")
	if err != nil {
		log.Fatal(err)
	}

	sheet1 := xlFile.GetSheet(0)
	if sheet1 != nil {
		fmt.Printf("Total Lines : %d (%s)\n", sheet1.MaxRow, sheet1.Name)
		for row := 0; row < int(sheet1.MaxRow); row++ {
			// sheet から 行 を取り出す
			r := sheet1.Row(row)
			for col := r.FirstCol(); col < r.LastCol(); col++ {
				// 行(row) と 列(col) を指定すると 値 が返ってくる
				value := sheet1.Row(row).Col(col)
				// 下記でもよい
				// value := r.Col(col)

				fmt.Printf("(%d, %d) = %s\n", row, col, value)
			}
		}
	}
}
```

詳しくは、下記をご覧ください。

xls - GoDoc
https://godoc.org/github.com/extrame/xls

2020/06/01 時点では、うまく読み込めないものがあります。

### うまく読み込めないもの

####  数式の含まれたファイル

例えば 参照セル `=A2` というような数式となっていると、紹介したライブラリでは読めなさそう？
※数式の入ったセルの値が空文字となる

#### パスワードがかかったもの

パスワードかかったファイルの読み込みには対応していない

## なんとかして読み込む

そんなファイルたちをOLEで読み込んでみましょう。

Object Linking and Embedding - Wikipedia
https://ja.wikipedia.org/wiki/Object_Linking_and_Embedding

ただOLEを使う場合、デメリットもあります。

* 動作速度が遅い
    * 命令を送ってアプリケーションを操作してというような処理なのであまり早くはならない
* サーバーサイドで使いにくい
    * Excelを直接動かすため、サーバーサイドでは使いにくいですね
* メモリやパワーがいる
    * 裏でExcelが立ち上がって、なんやかんやするのでそれなりに負荷はあります

## 実装

ただ値を取りたいだけなのですが、かなりめんどくさい処理になってます。
※いい方法があったら教えてください

単にちょっとだけ情報取りたいだけなら
OLEで完結させてもいいかもしれません。
※OLE経由でセル情報を取得することができる

すべてのセルを見たい場合は、
別の扱いやすいファイルに直したほうが早いかなと思ったので
以下の方法を考えてみました。

1. テンポラリファイルパスを作る (ioutil.TempFile)
2. Excelを立ち上げる (OLE)
3. xlsファイルを開く (OLE)
4. csvファイル(テンポラリ)へ保存する (OLE)
5. csvファイル(テンポラリ)を開く (encoding/csv)

別にcsvファイルでなくてもよいのですが、
公式packageにcsv読み込めるものがあるので利用しました。

出来上がったソースが、以下です。
※エラーとかあまり考慮してない

`$ go build & xxx.exe xxx.xls` な感じで読み込みできます。

実行には、Excelが必要です。

```go:ソース
package main

import (
	"encoding/csv"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"github.com/mattn/go-ole"
	"github.com/tanaton/go-ole-msoffice/excel"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
)

func main() {
	args := os.Args

	rows, err := getXlsData(args[1])
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v", rows)
}

func getXlsData(path string) ([][]string, error) {
	var rows [][]string

	// テンポラリファイルパス作成
	dist, err := ioutil.TempFile("", "*.csv")
	if err != nil {
		return rows, err
	}
	dist.Close()
	os.Remove(dist.Name())
	// 処理後削除
	defer os.Remove(dist.Name())

	// OLE
	ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE)
	defer ole.CoUninitialize()

	// Excelを取得する
	e := excel.ThisApplication()
	if e == nil {
		return rows, nil
	}

	// Excel設定: アラートの表示(true: する、false: しない)
	e.SetDisplayAlerts(false)
	// Excel設定: Excel自体の表示(true: する、false: しない)
	e.SetVisible(false)

	workbooks := e.GetWorkbooks()

	// 必ず絶対パスにする
	absPath := path
	if !filepath.IsAbs(absPath) {
		absPath, err = filepath.Abs(path)
		if err != nil {
			return rows, err
		}
	}

	// Excelファイルを開く
	book := workbooks.Open(absPath)

	// csvファイルとして保存する
	book.SaveAs(dist.Name(), excel.XlCSV)

	// ファイルとExcelを閉じる
	book.Close()
	e.Quit()
	e.Release()

	// csvファイルを開いて読み込む
	f, err := os.Open(dist.Name())
	if err != nil {
		return rows, err
	}
	defer f.Close()

	r := csv.NewReader(transform.NewReader(f, japanese.ShiftJIS.NewDecoder()))
	return r.ReadAll()
}
```

### パスワード付きを読み込む場合

下記のようにすれば、パスワードがかかったものも読めます。

```go
// Open(FileName, UpdateLinks, ReadOnly, Format, Password)
book := workbooks.Open(src, 0, true, 5, password)
```
Open メソッド (Excel) | Microsoft Docs
https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbooks.open

## まとめ

OLEを使って xlsファイルの読み込みをやってみました。
OLEを使うことによって、いろんなことができると思うので試してみてくださいね。
xlsファイルだけじゃなくてExcelで扱えるファイルは、OLE経由で読めるはず。

あと いい方法・いいpackageあったら教えてください (o*。_。)o