0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

openpyxlでVBAっぽくセルを扱う

Posted at

はじめに

openpyxlでブックをReadOnlyで開くとセルをインデックスアクセスしたときに異常に遅くなることから、iter_rowsを使えてなおかつVBAのCellsっぽく使えるクラス作りたくなったので作った。

from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.utils import column_index_from_string
from openpyxl.utils import get_column_letter
from openpyxl.cell.cell import Cell
import re
from dataclasses import dataclass

MAX_ROW = 1048576  # Excelの最大行数


@dataclass
class CellRange:
    """セルの上端、下端、左端、右端の行列番号を管理するデータクラス"""
    sheet: Worksheet
    top: int
    bottom: int
    left: int
    right: int

    @classmethod
    def from_string(cls, sheet: Worksheet, range_str: str) -> "CellRange":
        """
        Excelのセル範囲文字列(例: 'B2:P8')を CellRange に変換する。

        :param sheet: ワークシートオブジェクト
        :param range_str: セル範囲文字列
        :return: `CellRange` のインスタンス
        """
        match = re.fullmatch(r"([A-Z]{1,3})(\d+):([A-Z]{1,3})(\d+)", range_str, re.I)
        if not match:
            raise ValueError(f"無効な範囲指定: {range_str}")

        col1, row1, col2, row2 = match.groups()

        try:
            top = int(row1)
            bottom = int(row2)

            if not (1 <= top <= MAX_ROW) or not (1 <= bottom <= MAX_ROW):
                raise ValueError(f"行番号が範囲外: {top} または {bottom} (許可範囲: 1~{MAX_ROW})")

            left = column_index_from_string(col1.upper())
            right = column_index_from_string(col2.upper())
        except ValueError:
            raise ValueError(f"無効な列名または行番号を検出: {col1}{row1} または {col2}{row2}")

        return cls(sheet=sheet, top=top, bottom=bottom, left=left, right=right)

    def get_cells(self) -> list[tuple[Cell | None, ...]]:
        """
        このセル範囲のセルデータを `list[tuple[Cell | None]]` で返す。

        :return: セル範囲のリスト(各行が `tuple` になっている)
        """
        return [
            tuple(cell if cell.value is not None else None for cell in row)
            for row in self.sheet.iter_rows(
                min_row=self.top, max_row=self.bottom,
                min_col=self.left, max_col=self.right
            )
        ]

    def cells(self, row: int, col: int) -> Cell | None:
        """
        Excelの絶対行列番号でセルを取得する(シート全体の座標基準)。
        VBAの `Cells(row, col)` に相当。

        :param row: Excelの行番号(1始まり)
        :param col: Excelの列番号(1始まり)
        :return: `Cell` オブジェクトまたは `None`
        """
        if not (self.top <= row <= self.bottom and self.left <= col <= self.right):
            return None  # 範囲外なら None を返す

        return self.sheet.cell(row=row, column=col)

    def rows_count(self) -> int:
        """
        行数を返す
        VBAの `Rows.Count` に相当。
        :return: 行数
        """
        return self.bottom - self.top + 1

    def columns_count(self) -> int:
        """
        列数を返す
        VBAの `Columns.Count` に相当。
        :return: 列数
        """
        return self.right - self.left + 1

    def address(self) -> str:
        """
        セル範囲のExcelアドレス表記を返す(例: 'A123:AB345')

        :return: セル範囲の文字列
        """
        left_col = get_column_letter(self.left)
        right_col = get_column_letter(self.right)
        return f"{left_col}{self.top}:{right_col}{self.bottom}"

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?