python完全拷贝源EXCEL的内容和格式到目标EXCEL的尾部

查看 90|回复 9
作者:dreamrise   
完全拷贝源EXCEL的内容和格式到目标EXCEL的尾部
import openpyxl as op
import copy
import re
from collections import OrderedDict
from openpyxl.utils import get_column_letter, column_index_from_string
from openpyxl.utils.cell import coordinate_from_string
def fullcopy_sheet_s2t(s_wb, t_wb, s_ws=None, t_ws=None):
    """完全拷贝源EXCEL的内容和格式到目标EXCEL的尾部
    Args:
        s_wb (_type_): 源工作簿
        t_wb (_type_): 目标工作簿
        s_ws (_type_, optional): 源工作表. Defaults to None.
        t_ws (_type_, optional): 目标工作表. Defaults to None.
    """
    def get_effective_data_range(wb, sheet_name=None):
        """获取工作簿(第一个或指定)工作表的数据范围
        Args:
            wb (_type_): 工作表
            sheet_name (_type_, optional): 工作表名. Defaults to None.
        Returns:
            _type_: 开始CELL坐标,结束CELL坐标,有效数据行数
        """
        # 获取指定工作表
        if sheet_name is None:
            ws = wb[wb.sheetnames[0]]
        else:
            ws = wb[sheet_name]
        cells = [cell for row in ws.rows for cell in row]
        # print(" ~ file: copyxlsx240517.py:199 ~ _list:", cells)
        if cells:
            start = cells[0]
            # print(" ~ file: copyxlsx240517.py:201 ~ cells[0]:", cells[0])
            end = cells[-1]
            # print(" ~ file: copyxlsx240517.py:203 ~ cells[-1]:", cells[-1])
            start_coordinate = start.coordinate
            end_coordinate = end.coordinate
            # 计算有效数据行数(不包括空行)
            data_row_count = end.row - start.row + 1
        else:
            # 空表格
            start_coordinate = None
            end_coordinate = None
            data_row_count = 0
        # 返回结果
        return (start_coordinate, end_coordinate, data_row_count)
    def get_newarea(start_coordinate, end_coordinate, current_row, newrows):
        """根据原始坐标, 开始行, 新增行数,生成新增数据的目标区域
        Args:
            start_coordinate (_type_): 开始坐标
            end_coordinate (_type_): 结束坐标
            current_row (_type_): 开始行
            newrows (_type_): 新增行数
        Returns:
            _type_: _description_
        """
        start_col, start_row = coordinate_from_string(start_coordinate)
        end_col, end_row = coordinate_from_string(end_coordinate)
        new_start_row = start_row + current_row
        new_end_row = end_row + current_row
        new_start_col = start_col
        new_end_col = end_col
        # print(new_start_col + str(new_start_row), new_end_col + str(new_end_row))
        return new_start_col + str(new_start_row), new_end_col + str(new_end_row)
    if s_ws is None:
        s_ws = s_wb.sheetnames[0]
    if t_ws is None:
        t_ws = s_ws
    ws_from = s_wb[s_ws]
    if t_ws not in t_wb.sheetnames:
        ws_to = t_wb.create_sheet(t_ws)
    else:
        ws_to = t_wb[t_ws]
    # 删除默认创建的空sheet
    if s_ws != "Sheet" and "Sheet" in t_wb.sheetnames:
        del t_wb["Sheet"]
    _, _, lastrow = get_effective_data_range(t_wb)
    start_coordinate, end_coordinate, data_row_count = get_effective_data_range(s_wb)
    s_range = f"{start_coordinate}:{end_coordinate}"
    targe_start_coordinate, targe_end_coordinate = get_newarea(
        start_coordinate, end_coordinate, lastrow, data_row_count
    )
    t_range = f"{targe_start_coordinate}:{targe_end_coordinate}"
    ws_to.sheet_properties.tabColor = ws_from.sheet_properties.tabColor
    if s_range is not None:
        source_area = ws_from[s_range]
    else:
        source_area = ws_from
    merge_cell_dict = OrderedDict()
    merged_ranges = ws_from.merged_cells.ranges
    for source_row in source_area:
        for source_cell in source_row:
            sc_str = str(source_cell)
            point_time = sc_str.count(".")
            sc_str = sc_str.replace(".", "", point_time - 1)
            start = sc_str.find(".")
            sc_str = sc_str[start + 1 : -1]
            for merged_range in merged_ranges:
                if source_cell.coordinate in merged_range:
                    _cell_value = ws_from.cell(
                        row=merged_range.min_row, column=merged_range.min_col
                    )
                    merge_cell_dict[sc_str] = (
                        merged_range.min_row,
                        merged_range.min_col,
                        _cell_value,
                    )
                    continue
    range_li = []
    for val in set(merge_cell_dict.values()):
        tmp = []
        for x, y in merge_cell_dict.items():
            if y == val:
                tmp.append(x)
        if len(tmp):
            range_li.append(min(tmp) + ":" + max(tmp))
    for i in range_li:
        # print(i)
        if s_range is not None:
            base_point_letter = s_range.split(":")[0]
            base_point = ws_from[base_point_letter]
            base_row = base_point.row
            base_col = base_point.column
        else:
            base_point_letter = i.split(":")[0]
            base_point = ws_from[base_point_letter]
            base_row = base_point.row
            base_col = base_point.column
        s = i.split(":")[0]
        e = i.split(":")[1]
        # 模板区间第一个点相对顶点距离
        base_delta_row = ws_from[s].row - base_row
        base_delta_col = ws_from[s].column - base_col
        # 模板区间两个端点距离
        delta_row = ws_from[e].row - ws_from[s].row
        delta_col = ws_from[e].column - ws_from[s].column
        # print(base_delta_row, base_delta_col, delta_row, delta_col)
        if t_range is not None:
            tar_s = t_range.split(":")[0]
            tar_s_letter = re.findall(r"([A-Za-z]+)", tar_s)[0]
            tar_base_col_idx = column_index_from_string(tar_s_letter)
            tar_base_row_idx = int(re.findall(r"(\d+)", tar_s)[0])
        else:
            tar_s = s
            tar_s_letter = re.findall(r"([A-Za-z]+)", tar_s)[0]
            tar_base_col_idx = column_index_from_string(tar_s_letter)
            tar_base_row_idx = int(re.findall(r"(\d+)", tar_s)[0])
        # print(tar_base_row_idx, tar_base_col_idx)
        tar_range_s_col = get_column_letter(tar_base_col_idx + base_delta_col)
        tar_range_s_idx = tar_base_row_idx + base_delta_row
        tar_range_e_col = get_column_letter(
            tar_base_col_idx + base_delta_col + delta_col
        )
        tar_range_e_idx = tar_base_row_idx + base_delta_row + delta_row
        tar_merge = (
            tar_range_s_col
            + str(tar_range_s_idx)
            + ":"
            + tar_range_e_col
            + str(tar_range_e_idx)
        )
        # print('tar merge:', tar_merge)
        ws_to.merge_cells(tar_merge)
    if s_range is not None and t_range is not None:
        source_point_letter = s_range.split(":")[0]
        source_point = ws_from[source_point_letter]
        source_row = source_point.row
        source_col = source_point.column
        tar_point_letter = t_range.split(":")[0]
        tar_point = ws_from[tar_point_letter]
        tar_row = tar_point.row
        tar_col = tar_point.column
        delta_row = tar_row - source_row
        delta_col = tar_col - source_col
        print("ROW:", tar_row, source_row)
        print("COL:", tar_col, source_col)
    else:
        delta_row = 0
        delta_col = 0
    # print("DELTA ROW COL:", delta_row, delta_col)
    for source_row in source_area:
        update_row_h = False
        for source_cell in source_row:
            source_x = source_cell.row
            new_x = source_x + delta_row
            source_y = source_cell.column
            new_y = source_y + delta_col
            if not update_row_h:
                ws_to.row_dimensions[new_x].height = ws_from.row_dimensions[
                    source_x
                ].height
                update_row_h = True
            ws_to.column_dimensions[
                get_column_letter(new_y)
            ].width = ws_from.column_dimensions[get_column_letter(source_y)].width
            ws_to.cell(row=new_x, column=new_y, value=source_cell.value)
            # 设置单元格格式
            target_cell = ws_to.cell(new_x, new_y)
            target_cell.fill = copy.copy(source_cell.fill)
            if source_cell.has_style:
                target_cell._style = copy.copy(source_cell._style)
                target_cell.font = copy.copy(source_cell.font)
                target_cell.border = copy.copy(source_cell.border)
                target_cell.fill = copy.copy(source_cell.fill)
                target_cell.number_format = copy.copy(source_cell.number_format)
                target_cell.protection = copy.copy(source_cell.protection)
                target_cell.alignment = copy.copy(source_cell.alignment)
    s_wb.close()
    t_wb.close()
targe_wb = op.Workbook()  # 新建一个EXCEL
source_wb1 = op.load_workbook("head.xlsx")
source_wb2 = op.load_workbook("data.xlsx")
source_wb3 = op.load_workbook("tail.xlsx")
fullcopy_sheet_s2t(source_wb1, targe_wb)
fullcopy_sheet_s2t(source_wb2, targe_wb)
fullcopy_sheet_s2t(source_wb3, targe_wb)
targe_wb.save(filename="all_V2.xlsx")

目标, 工作

dreamrise
OP
  

XLWINGS 版本,缺点是需要安装OFFICE,不能用于LINUX服务器。优点是代码简单,内部有函数,还支持自动列宽。
[Python] 纯文本查看 复制代码
def merge_excel_parts(
    head_file_path: str, data_file_path: str, tail_file_path: str, output_file_path: str, sheetname: str = "Sheet1"
) -> str:
    """
    将三个Excel文件(表头、数据、表尾)合并成一个新文件,并返回合并后文件的路径。
    参数:
        head_file_path (str): 表头部分的Excel文件路径。
        data_file_path (str): 数据部分的Excel文件路径。
        tail_file_path (str): 表尾部分的Excel文件路径。
        output_file_path (str): 合并后Excel文件的输出路径。
    返回:
        str: 合并后Excel文件的路径。
    """
    import xlwings as xw
    try:
        xwapp = xw.App(visible=False)
        # 创建一个新的Excel工作簿用于保存合并结果
        wb_output = xw.Book()
        # 不支持中文
        ws_output = wb_output.sheets[0]
        # 头部和数据
        for part_path in [head_file_path, data_file_path]:
            try:
                check_file_exists(part_path)
            except FileNotFoundError:
                print(f"文件{part_path}不存在,退出处理")
                exit()
            # 打开源Excel文件
            wb_source = xw.Book(part_path)
            ws_source = wb_source.sheets[0]
            # 复制源工作簿的格式和数据
            # 使用 range.copy() 方法以保留原始格式
            ws_source.used_range.copy(ws_output.cells(1 + ws_output.used_range.last_cell.row, 1))
            # 关闭源Excel文件
            wb_source.close()
        # 尾部:如果没有尾部文件可以忽略
        try:
            check_file_exists(tail_file_path)
            wb_source = xw.Book(tail_file_path)
            ws_source = wb_source.sheets[0]
            ws_source.used_range.copy(ws_output.cells(1 + ws_output.used_range.last_cell.row, 1))
            wb_source.close()
        except FileNotFoundError:
            """如果没有尾部文件可以忽略"""
        # 设置所有列的宽度为自动列宽
        for col in ws_output.used_range.columns:
            col.autofit()
        wb_output.save(output_file_path)
        wb_output.close()
        xwapp.quit()
        result = {"status": "success", "data": output_file_path}
    except Exception as e:
        result = {"status": "fail", "data": e}
    return result
q546624527   

我记得xlwings这个库比较好用,还能嵌入excel内部
Moinul   

大神,能支持识别每一列的title然后sheet2拷贝到sheet1对应的那一行吗?
dreamrise
OP
  


q546624527 发表于 2024-5-20 09:12
我记得xlwings这个库比较好用,还能嵌入excel内部

XLWINGS需要安装OFFICE,而且只支持WINDOWS、MAC OS。
因为服务器LINUX部署,而且不装OFFICE,所以用openpyxl写了一遍。
shiltonlace   

代码中的小火箭是啥意思?
dreamrise
OP
  

[Python] 纯文本查看 复制代码
def merge_excel_parts(
    head_file_path: str, data_file_path: str, tail_file_path: str, output_file_path: str
) -> str:
    """
    将三个Excel文件(表头、数据、表尾)合并成一个新文件,并返回合并后文件的路径。
    参数:
        head_file_path (str): 表头部分的Excel文件路径。
        data_file_path (str): 数据部分的Excel文件路径。
        tail_file_path (str): 表尾部分的Excel文件路径。
        output_file_path (str): 合并后Excel文件的输出路径。
    返回:
        DICT
    """
    targe_wb = op.Workbook()  # 新建一个EXCEL
    source_wb1 = op.load_workbook(head_file_path)
    source_wb2 = op.load_workbook(data_file_path)
    source_wb3 = op.load_workbook(tail_file_path)
    fullcopy_sheet_s2t(source_wb1, targe_wb)
    fullcopy_sheet_s2t(source_wb2, targe_wb)
    fullcopy_sheet_s2t(source_wb3, targe_wb)
    targe_wb.save(filename=output_file_path)
    result = {"status": "success", "data": output_file_path}
    return result
# 只传文件路径的方法
merge_excel_parts(
    "head.xlsx",
    "data.xlsx",
    "tail.xlsx",
    "all_v3.xlsx",
)
w1066602520   

这个真的是有用 马上一试
wangyp0506c   

先收藏下 后边再试试
space218   

没看懂是干啥用的!
您需要登录后才可以回帖 登录 | 立即注册

返回顶部