Python-openpyxl-MjExcel

本文介绍了MjExcel工具类,用于Python中高效地处理Excel文件,包括创建、复制工作表,操作单元格(合并、解除合并),以及读写数据,支持工作簿管理和基本的索引转换功能。
摘要由CSDN通过智能技术生成

Python操作Excel文件工具类:

  • 获取工作表
  • 创建工作表、复制工作表、删除工作表
  • 获取单元格
  • 合并单元格、解除合并单元格
  • 向工作表里写入数据,占位符${name}
  • 读取工作表里的数据
  • 字母索引和数字索引的相互转换
"""
@Title: MjExcel工具类
@Time: 2024/1/30 9:45
@Author: Michael
"""

import os

from openpyxl import Workbook, load_workbook
from openpyxl.cell import Cell
from openpyxl.worksheet.worksheet import Worksheet

from office.MjUtil import MjUtil


class MjExcel:
    # 字母表
    letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
               "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
               "U", "V", "W", "X", "Y", "Z"]

    # 构造函数
    def __init__(self,
                 path: str = None):
        """
        :param path: 文件路径,若为空则创建新文件,默认创建一个名为"Sheet"的工作表
        """
        self.path = path
        if MjUtil.is_none(path):
            # 新建文件
            self.xlsx = Workbook()
        else:
            if not os.path.isfile(path):
                raise Exception("非文件路径!")
            post = path.lower().split(".")[-1]
            if post != "xlsx":
                raise Exception("非xlsx文件!")
            self.xlsx = load_workbook(path)
        self.sheets = self.get_sheets()  # 所有的工作表
        self.cur_sheet = None  # 当前的工作表
        self.set_cur_sheet()

    # 设置当前的工作表
    def set_cur_sheet(self,
                      inx: int | str = None):
        """
        :param inx: 工作表索引/名称,若为空则返回第一个
        :return:
        """
        self.cur_sheet = self.get_sheet(0) if inx is None else self.get_sheet(inx)

    # 获取所有的工作表
    def get_sheets(self,
                   empty: bool = False) -> list[Worksheet]:
        """
        :param empty: 是否剔除空白工作表,默认不剔除
        :return: list[Worksheet]
        """
        self.sheets = self.xlsx.worksheets
        if not empty:
            return self.sheets
        # 根据工作表大小判断是否为空
        sheets = []
        for worksheet in self.sheets:
            cell = self.get_cell("A1", worksheet)
            if self.get_sheet_shape(worksheet) == (1, 1) and MjUtil.is_none(cell.value):
                continue
            sheets.append(worksheet)
        return sheets

    # 获取所有的工作表名称
    def get_sheets_name(self) -> list[str]:
        return self.xlsx.sheetnames

    # 获取特定的工作表
    def get_sheet(self,
                  inx: int | str) -> Worksheet:
        """
        :param inx: 工作表索引/名称
        :return: Worksheet
        """
        sheets = self.get_sheets()
        return sheets[inx] if type(inx).__name__ == "int" else self.xlsx[inx]

    # 获取工作表的工作区域大小,空工作表大小为(1, 1)
    def get_sheet_shape(self,
                        sheet: Worksheet | int | str = None) -> tuple:
        """
        :param sheet: 工作表/名称/索引,若为空则返回当前工作表大小
        :return: tuple
        """
        sheet = self.__get_sheet_by_type(sheet)
        return sheet.max_row, sheet.max_column

    # 创建工作表
    def create_sheet(self,
                     name: str,
                     inx: int = None) -> Worksheet:
        """
        :param name: 工作表名称
        :param inx: 新工作表位置
        :return: Worksheet
        """
        if self.__judge_sheet_is_exist(name):
            raise Exception("名称为" + name + "的工作表已存在!")
        new_sheet = self.xlsx.create_sheet(name, inx)
        # 更新
        self.sheets = self.get_sheets()
        return new_sheet

    # 复制工作表
    def copy_sheet(self,
                   inx: int | str) -> Worksheet:
        """
        :param inx: 工作表索引/名称
        :return: Worksheet
        """
        sheet = self.get_sheet(inx)
        sheet_copy = self.xlsx.copy_worksheet(sheet)
        # 更新
        self.sheets = self.get_sheets()
        return sheet_copy

    # 删除工作表
    def remove_sheet(self,
                     inx: int | str):
        """
        :param inx: 工作表索引/名称
        :return:
        """
        sheet = self.get_sheet(inx)
        self.xlsx.remove(sheet)
        # 更新
        self.sheets = self.get_sheets()

    # 获取特定的单元格,(1, 1)/“A1”
    def get_cell(self,
                 inx: tuple | str,
                 sheet: Worksheet | int | str = None) -> Cell:
        """
        :param inx: 单元格索引
        :param sheet: 工作表/索引/名称,若为空则返回当前工作表
        :return: Cell
        """
        sheet = self.__get_sheet_by_type(sheet)
        # 判断索引类型
        if type(inx).__name__ == "tuple":
            # 索引从1开始
            cell = sheet.cell(int(inx[0]) + 1, int(inx[1]) + 1)
        else:
            cell = sheet[inx.upper()]
        return cell

    # 获取切片单元格,(0, 0, 1, 3)/("A1", "D2")
    def get_cells(self,
                  inx: tuple = None,
                  sheet: Worksheet | int | str = None,
                  tf: bool = True) -> list[Cell] | tuple:
        """
        :param inx: 单元格索引,若为空则返回所有单元格
        :param sheet: 工作表/索引/名称,若为空则返回当前工作表
        :param tf: 是否将元组转换成列表
        :return: list[Cell] | tuple
        """
        if inx is None:
            shape = self.get_sheet_shape()
            inx = (0, 0, shape[0], shape[1])
        sheet = self.__get_sheet_by_type(sheet)
        # 获取的单元格以行列元组返回
        cell_tuple = None
        if len(inx) == 4:
            start_cell = self.get_cell((inx[0], inx[1]), sheet)
            end_cell = self.get_cell((inx[2], inx[3]), sheet)
            cell_tuple = sheet[start_cell.coordinate:end_cell.coordinate]
        elif len(inx) == 2:
            cell_tuple = sheet[inx[0].upper():inx[1].upper()]
        # 将((row_cells1), (row_cell2))转换成列表
        if tf:
            cell_list = []
            for row_cells in cell_tuple:
                for row_cell in row_cells:
                    cell_list.append(row_cell)
            return cell_list
        else:
            return cell_tuple

    # 合并单元格,(0, 0, 1, 3)/("A1", "D2")
    def merge_cells(self,
                    inx: tuple,
                    sheet: Worksheet | int | str = None):
        """
        :param inx: 单元格索引
        :param sheet: 工作表/索引/名称,若为空则返回当前工作表
        :return:
        """
        sheet = self.__get_sheet_by_type(sheet)
        if len(inx) == 4:
            sheet.merge_cells(start_row=inx[0], start_column=inx[1], end_row=inx[2], end_column=inx[3])
        elif len(inx) == 2:
            sheet.merge_cells(str(inx[0]).upper() + ":" + str(inx[1]).upper())

    # 解除合并单元格,(0, 0, 1, 3)/("A1", "D2")
    def unmerge_cells(self,
                      inx: tuple,
                      sheet: Worksheet | int | str = None):
        """
        :param inx: 单元格索引
        :param sheet: 工作表/索引/名称,若为空则返回当前工作表
        :return:
        """
        sheet = self.__get_sheet_by_type(sheet)
        if len(inx) == 4:
            sheet.unmerge_cells(start_row=inx[0], start_column=inx[1], end_row=inx[2], end_column=inx[3])
        elif len(inx) == 2:
            sheet.unmerge_cells(str(inx[0]).upper() + ":" + str(inx[1]).upper())

    # 向块区域写入数据,(0, 0, 1, 3)/("A1", "D2")
    def write_data_to_block(self,
                            data: dict,
                            inx: tuple = None,
                            sheet: Worksheet | str | int = None):
        """
        :param data: 数据
        :param inx: 单元格索引
        :param sheet: 工作表/名称/索引,若为空则返回当前工作表
        :return:
        """
        sheet = self.__get_sheet_by_type(sheet)
        block_cell = self.get_cells(inx, sheet)
        for cell in block_cell:
            text = cell.value
            cell.value = MjUtil.write_data(text, data)

    # 向块区域写入多头数据,(0, 0, 1, 3)/("A1", "D2")
    def write_data_list_to_block(self,
                                 data_list: list,
                                 sheet: Worksheet | str | int = None,
                                 head_inx: int = 0):
        """
        :param data_list: 数据
        :param sheet: 工作表/名称/索引,若为空则返回当前工作表
        :param head_inx: 占位符行索引
        :return:
        """
        sheet = self.__get_sheet_by_type(sheet)
        # 获取占位符
        temp_list = []
        head_cells = sheet[head_inx + 1]
        for head_cell in head_cells:
            temp_list.append(head_cell.value)
        # 写入数据
        for i in range(len(data_list)):
            cells = sheet[head_inx + i + 1]
            for j in range(len(cells)):
                if not MjUtil.is_none(temp_list[j]):
                    cells[j].value = MjUtil.write_data(temp_list[j], data_list[i])

    # 读取工作表中的数据
    def read_sheet(self,
                   temp: Worksheet,
                   text: Worksheet | str | int = None,
                   inx: tuple = None,
                   data: dict = None) -> dict:
        """
        :param temp: 模板工作表
        :param text: 数据工作表/名称/索引,若为空则返回当前工作表
        :param inx: 单元格索引
        :param data: 数据
        :return:
        """
        text = self.__get_sheet_by_type(text)
        # 获取工作区域
        temp_block_cell = self.get_cells(inx, temp)
        text_block_cell = self.get_cells(inx, text)
        for i, temp_cell in enumerate(temp_block_cell):
            MjUtil.read_data(temp_cell.value, text_block_cell[i].value, data)
        return data

    # 读取工作表中的数据
    def read_sheet_list(self,
                        temp: Worksheet,
                        text: Worksheet | str | int = None,
                        head_inx: int = 0) -> list:
        """
        :param temp: 模板工作表
        :param text: 数据工作表/名称/索引,若为空则返回当前工作表
        :param head_inx: 占位符行索引
        :return: list
        """
        text = self.__get_sheet_by_type(text)
        # 获取占位符
        temp_list = []
        head_cells = temp[head_inx + 1]
        for head_cell in head_cells:
            temp_list.append(head_cell.value)
        # 判断写入区域是否有限制
        data_list = []
        text_rows = text.rows
        for i in range(head_inx, len(text_rows)):
            # 一行对应一条数据
            data = {}
            text_cells = text_rows[i].cells
            for j, text_cell in enumerate(text_cells):
                MjUtil.read_data(temp_list[j], text_cell.text, data)
            data_list.append(data)
        return data_list

    # 保存
    def save(self,
             path: str = None):
        """
        :param path: 保存路径
        :return:
        """
        if path is None or path == "":
            self.xlsx.save(self.path)
        else:
            # 另存为
            self.xlsx.save(path)

    # 转换列索引,"A" -> 0
    @staticmethod
    def tf_letter_to_figure(inx: str) -> int:
        """
        :param inx: 字母索引,"A"
        :return: int
        """
        inx = inx.upper()  # 转换大写
        figure = 0
        # 从后向前遍历索引字符串,676 * (x + 1) + 26 * (y + 1) + z
        for index, every_char in enumerate(reversed(inx)):
            num = MjExcel.letters.index(every_char)
            if index == 0:
                figure += num
            else:
                figure += (26 ** index) * (num + 1)
        if figure > 16383:
            raise Exception("索引超出!")

        return figure

    # 转换列索引,0 -> "A"
    @staticmethod
    def tf_figure_to_letter(inx: int) -> str:
        """
        :param inx: 数字索引,26
        :return: str
        """
        letter = ""
        if inx > 16383 or inx < 0:
            raise Exception("索引超出!")
        # 计算末尾字母
        letter += MjExcel.letters[inx % 26]
        inx -= MjExcel.letters.index(letter)
        if inx == 0:
            return letter
        # 从后向前循环计算
        power = 1
        while True:
            if inx // (26 ** power) > 26:
                temp = (inx % (26 ** (power + 1))) // 26 ** power
                # 前一位对应的数值刚好是后一位的最大公倍数
                if temp == 0:
                    temp = 26
                letter += MjExcel.letters[temp - 1]
                inx -= 26 ** power * temp
                power += 1
            else:
                letter += MjExcel.letters[inx // 26 ** power - 1]
                break

        return letter[::-1]

    # 根据名称判断工作表是否存在
    def __judge_sheet_is_exist(self,
                               name: str) -> bool:
        """
        :param name: 工作表名称
        :return: bool
        """
        sheets_name = self.get_sheets_name()
        return name in sheets_name

    # 根据参数类型获取特定的工作表
    def __get_sheet_by_type(self,
                            inx: Worksheet | int | str = None) -> Worksheet:
        """
        :param inx: 工作表/索引/名称,若为空则返回当前工作表
        :return: Worksheet
        """
        if inx is None:
            sheet = self.cur_sheet
        elif type(inx).__name__ == "int" or type(inx).__name__ == "str":
            sheet = self.get_sheet(inx)
        else:
            sheet = inx

        return sheet

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值