openpyxlを使ってみた

Excelをテンプレートとして読み込んで、編集した後に出力したかったのでOpenPyXLを使ってみたのでメモ書き程度に使ってみた結果を書きとどめてみた。
基本的な使い方は下記を参照。
https://openpyxl.readthedocs.io/en/stable/

## 結合されたセルの罫線が消える
load → saveで結合されたセルの罫線の一部が消える。

```
from openpyxl import load_workbook
wb = load_workbook('./test.xlsx')
wb.save('./test2.xlsx')
```

before:
![image](https://qiita-image-store.s3.amazonaws.com/0/74829/67474e7e-2113-fdca-10d7-20ad49353c08.png)

after:
![image](https://qiita-image-store.s3.amazonaws.com/0/74829/99acd3c6-13a2-29e5-c7ab-2bbd9ab1496e.png)

ドキュメント読むとそういうものなので諦めるしか無いみたい。
http://openpyxl.readthedocs.io/en/default/styles.html#styling-merged-cells

なのでloadするexcelでは結合せずに罫線を引いておいて、load後に`merge_cells()`で結合するしかない。。めんどくさい。

## スタイルのコピー
`copy`を使ってできるらしい。
http://openpyxl.readthedocs.io/en/default/styles.html#copying-styles

けど全てのスタイルをコピーしたい場合にどうしたらいいのかよくわからなかったので調べるとborderとかfontとか指定してコピーしたらって提案がされている。
http://stackoverflow.com/questions/23332259/copy-cell-style-openpyxl#answer-34838233

```
for row in default_sheet.rows:
    for cell in row:
        new_cell = new_sheet.cell(
                   row=cell.row_idx, col=cell.col_idx, value= cell.value)
        if cell.has_style:
            new_cell.font = cell.font
            new_cell.border = cell.border
            new_cell.fill = cell.fill
            new_cell.number_format = cell.number_format
            new_cell.protection = cell.protection
            new_cell.alignment = cell.alignment
```

正直めんどいし、これがスタイルの全てかわからんなぁ、って思ってopenpyxlのソースコード見てたら`openpyxl.worksheet.copier._copy_cells`で`target_cell._style = copy(source_cell._style)`ってしているのを見つけた。
https://bitbucket.org/openpyxl/openpyxl/src/644ea21bb4056f93184d3e743f4abf05c51f84af/openpyxl/worksheet/copier.py?at=default&fileviewer=file-view-default#copier.py-54

ということでスタイルを全コピーする場合は下記で対応する。

```
to_cell._style = copy(cell._style)
```

というかセルをコピーする場合は`openpyxl.worksheet.copier._copy_cells`を参考にするといいと思う。
https://bitbucket.org/openpyxl/openpyxl/src/644ea21bb4056f93184d3e743f4abf05c51f84af/openpyxl/worksheet/copier.py?at=default&fileviewer=file-view-default#copier.py-46:60


## row_dimensionsは1番目から設定されている
テンプレートから行単位でコピーする場合に行の高さを取得してコピー先の行に設定したくなったりするのだけれども、そんなときにworksheetのrow_dimensionsにアクセスする必要がある。
ただ、1行目のrow_dimensionsはrow_dimensions[0]ではなくrow_dimensions[1]に入っているので間違えないようにする。

## 範囲指定してコピーする
とあるデータの件数分テンプレートのシートから行をコピーして出力したかったので範囲指定してコピーする関数を実装してみた。

```
def copy_cell_range(from_sheet, from_range_string, to_sheet, to_cell_string='A1'):
    to_cell = to_sheet[to_cell_string]
    to_row = to_cell.row
    to_col = to_cell.col_idx

    for row_num, row in enumerate(from_sheet[from_range_string]):
        if not row:
            continue

        # 行の高さをコピー
        to_row_dimension = to_sheet.row_dimensions[to_row + row_num]
        from_row_dimension = from_sheet.row_dimensions[row[0].row]
        to_row_dimension.height = from_row_dimension.height

        for col_num, cell in enumerate(row):
            to_cell = to_sheet.cell(row=to_row + row_num, column=to_col + col_num)

            # cellの値をコピー
            to_cell.value = cell.value

            # cellのスタイルをコピー
            if cell.has_style:
                to_cell._style = copy(cell._style)
```

row_dimensionもcopyできるんじゃないかと思うけど未確認です。
cellのスタイル以外も必要ならコピーすると良いと思う。