【Python】Sideオブジェクト、Borderオブジェクトを使って罫線をひく。

pythonを使用してExcelファイルの操作を勉強しています。
本日の気づき(復習)は、罫線に関してです。
pythonでExcelを操作するため、openpyxlというパッケージを使用しています。

今回は前回の復習(日報作成)から約一か月ということで(適当)
いままでの勉強内容を含め再度の復習もかねて、表に罫線を引きたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927441/e85180af-2ad6-1e4f-c2a9-7a3e2f9f3e99.png)

上記のようなブック「商品リストを」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927441/4feb7dfa-2d45-7b36-6cba-9906c49b90f9.png)

こんな感じにしたいです。

今回の目標は

- 表の見出しやデータ量の増減に対応する。
- 表の位置がどこであっても対応する。

の二つです。

# マージセルの操作関数
当初はWorksheet.iter_rowメソッドを使用してと考えていたのですが
公式に何かないかなとずるを考えだしたら・・・、ありました。
https://openpyxl.readthedocs.io/en/stable/styles.html#cell-styles-and-named-styles

```python
from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
from openpyxl import Workbook

def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment=None):
    top = Border(top=border.top)
    left = Border(left=border.left)
    right = Border(right=border.right)
    bottom = Border(bottom=border.bottom)
    first_cell = ws[cell_range.split(":")[0]]
    if alignment:
        ws.merge_cells(cell_range)
        first_cell.alignment = alignment
    rows = ws[cell_range]
    if font:
        first_cell.font = font
    for cell in rows[0]:
        cell.border = cell.border + top
    for cell in rows[-1]:
        cell.border = cell.border + bottom
    for row in rows:
        l = row[0]
        r = row[-1]
        l.border = l.border + left
        r.border = r.border + right
        if fill:
            for c in row:
                c.fill = fill
```

フォントなどは今回使わないのですが、そのまま持ってきちゃいました。
目的の罫線を引くため、外枠の罫線設定を使用します。
対象範囲の縦線と横線を別に設定して引いていくという感じでしょうか。

# Sideオブジェクト

```python
Side(color=RGB形式の色指定, border_style=線のスタイル)
```

color引数は文字の時と同じですね。
border_style引数に関しては

- hair:極細
- thin:通常の太さ
- medium:通常と太線の中間
- thick:太線
- double:二重線

この辺はＥｘｃｅｌと同じですね。

# Borderオブジェクト

```python
cell.border = Border(left=Sideオブジェクト,
                     right=Sideオブジェクト,
                     top=Sideオブジェクト,
                     bottom=Sideオブジェクト)
```

生成したSideオブジェクトを使用して、Borderオブジェクトを生成します。
そちらを、セルのborder属性に設定することで罫線を引く事が出来ます。

こちらを踏まえて

# 最終的なコード

```python
from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
from openpyxl import load_workbook

# 公式にあるマージセルの操作関数


def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment=None):
    top = Border(top=border.top)
    left = Border(left=border.left)
    right = Border(right=border.right)
    bottom = Border(bottom=border.bottom)
    first_cell = ws[cell_range.split(":")[0]]
    if alignment:
        ws.merge_cells(cell_range)
        first_cell.alignment = alignment
    rows = ws[cell_range]
    if font:
        first_cell.font = font
    for cell in rows[0]:
        cell.border = cell.border + top
    for cell in rows[-1]:
        cell.border = cell.border + bottom
    for row in rows:
        l = row[0]
        r = row[-1]
        l.border = l.border + left
        r.border = r.border + right
        if fill:
            for c in row:
                c.fill = fill


wb = load_workbook('商品リスト.xlsx')
ws = wb.active

# 各線のスタイルを設定
hair = Side(color='000000', border_style='hair')
thin = Side(color='000000', border_style='thin')
double = Side(color='000000', border_style='double')
thick = Side(color='000000', border_style='thick')

# 縦線の種類と位置を設定
border_ver_thin = Border(left=thin)
border_ver_start = Border(left=thick)
border_ver_end = Border(left=thin, right=thick)

# 横線の種類と位置を設定
border_hor_hair = Border(top=hair)
border_hor_thin = Border(top=thin)
border_hor_double = Border(top=double)
border_hor_thick = Border(top=thick)

# 縦線を引く
for col in range(ws.min_column, ws.max_column + 1):
    Alpha = ws.cell(row=ws.min_row, column=col).column_letter
    ver_range = Alpha + f'{ws.min_row}' + ':' + Alpha + f'{ws.max_row}'
    if col == ws.min_column:
        style_range(ws, ver_range, border=border_ver_start)
    elif col == ws.max_column:
        style_range(ws, ver_range, border=border_ver_end)
    style_range(ws, ver_range, border=border_ver_thin)

# 横線を引く
hor_start = ws.cell(row=ws.min_row, column=ws.min_column).column_letter
hor_end = ws.cell(row=ws.min_row, column=ws.max_column).column_letter
for row in range(ws.min_row, ws.max_row + 2):
    hor_range = hor_start + str(row) + ':' + hor_end + str(row)
    if row == ws.min_row or row == ws.max_row + 1:
        style_range(ws, hor_range, border=border_hor_thick)
    elif row == ws.min_row + 1:
        style_range(ws, hor_range, border=border_hor_double)
    elif (row - ws.min_row - 1) % 5 == 0:
        style_range(ws, hor_range, border=border_hor_thin)
    style_range(ws, hor_range, border=border_hor_hair)

wb.save('商品リスト_傍線.xlsx')
```

こんな感じになりました。
これから汎用性を高めたコードに変えていきたいのですが
今日は力尽きました・・・。

ちなみに、数値などの入力をしていなくてもセルの幅を変えてしまうと
そのセルもデータが入っていると認識されるので注意が必要です。