简体   繁体   English

是否可以将 StyleFrame 附加到现有的 excel 工作表?

[英]Is it possible to append a StyleFrame to an existing excel worksheet?

I was wondering if it was possible to write a StyleFrame to an arbitrary position in an existing excel worksheet while maintaining the original formatting and styling of pre-existing cells?我想知道是否可以将 StyleFrame 写入现有 Excel 工作表中的任意位置,同时保持预先存在单元格的原始格式和样式?

Eg In the example below, I'd like to set the output of the Styleframe to start from cell 'A9' while maintaining the formatting and coloring of the other cells (Cells 'A1','A2','A3' etc):例如,在下面的示例中,我想将 Styleframe 的输出设置为从单元格“A9”开始,同时保持其他单元格(单元格“A1”、“A2”、“A3”等)的格式和颜色:

在此处输入图片说明

So user @MaxU has a helpful answer over here outlining a function that appends regular dataframes to arbitrary positions in an existing excel worksheet.所以用户@MaxU 在这里有一个有用的答案,概述了一个函数,该函数将常规数据帧附加到现有 excel 工作表中的任意位置。 With some minor changes I've been able to modify this function to work on styleframes.通过一些小的改动,我已经能够修改这个函数来处理样式框架。 Note that while the modified function successfully writes most cell properties into existing worksheets for some reason it does not copy cell alignments over.请注意,虽然修改后的函数由于某种原因成功地将大多数单元格属性写入现有工作表,但它不会复制单元格对齐方式。 So I have hard-coded the Alignments of appended cells to be top,left and wrapped.因此,我将附加单元格的对齐方式硬编码为顶部、左侧和包装。 Please see the modified code below:请看下面修改后的代码:

from pathlib import Path
from copy import copy
from typing import Union, Optional
import numpy as np
import pandas as pd
import openpyxl
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from styleframe import StyleFrame
from openpyxl.styles.alignment import Alignment


def copy_excel_cell_range(
        src_ws: openpyxl.worksheet.worksheet.Worksheet,
        min_row: int = None,
        max_row: int = None,
        min_col: int = None,
        max_col: int = None,
        tgt_ws: openpyxl.worksheet.worksheet.Worksheet = None,
        tgt_min_row: int = 1,
        tgt_min_col: int = 1,
        with_style: bool = True
) -> openpyxl.worksheet.worksheet.Worksheet:
    if tgt_ws is None:
        tgt_ws = src_ws

    # https://stackoverflow.com/a/34838233/5741205
    for row in src_ws.iter_rows(min_row=min_row, max_row=max_row,
                                min_col=min_col, max_col=max_col):
        for cell in row:
            tgt_cell = tgt_ws.cell(
                row=cell.row + tgt_min_row - 1,
                column=cell.col_idx + tgt_min_col - 1,
                value=cell.value
            )
            if with_style and cell.has_style:
                # tgt_cell._style = copy(cell._style)
                tgt_cell.font = copy(cell.font)
                tgt_cell.border = copy(cell.border)
                tgt_cell.fill = copy(cell.fill)
                tgt_cell.number_format = copy(cell.number_format)
                tgt_cell.protection = copy(cell.protection)
                tgt_cell.alignment = Alignment(horizontal='left', vertical='top',wrapText=True)
    return tgt_ws

def append_sf_to_excel(
        filename: Union[str, Path],
        sf: StyleFrame,
        sheet_name: str = 'Sheet1',
        startrow: Optional[int] = None,
        max_col_width: int = 30,
        autofilter: bool = False,
        fmt_int: str = "#,##0",
        fmt_float: str = "#,##0.00",
        fmt_date: str = "yyyy-mm-dd",
        fmt_datetime: str = "yyyy-mm-dd hh:mm",
        truncate_sheet: bool = False,
        storage_options: Optional[dict] = None,
        **to_excel_kwargs
) -> None:

    def set_column_format(ws, column_letter, fmt):
        for cell in ws[column_letter]:
            cell.number_format = fmt
    filename = Path(filename)
    file_exists = filename.is_file()
    # process parameters
    # calculate first column number
    # if the sf will be written using `index=True`, then `first_col = 2`, else `first_col = 1`
    first_col = int(to_excel_kwargs.get("index", True)) + 1
    # ignore [engine] parameter if it was passed
    if 'engine' in to_excel_kwargs:
        to_excel_kwargs.pop('engine')
    # save content of existing sheets
    if file_exists:
        wb = load_workbook(filename)
        sheet_names = wb.sheetnames
        sheet_exists = sheet_name in sheet_names
        sheets = {ws.title: ws for ws in wb.worksheets}

    with StyleFrame.ExcelWriter(
        filename.with_suffix(".xlsx"),
        mode="a" if file_exists else "w",
        if_sheet_exists="new" if file_exists else None,
        date_format=fmt_date,
        datetime_format=fmt_datetime,
        storage_options=storage_options
    ) as writer:
        if file_exists:
            # try to open an existing workbook
            writer.book = wb
            # get the last row in the existing Excel sheet
            # if it was not specified explicitly
            # for row in wb['Sheet1'].iter_rows():
            #     for cell in row:
            #         print(f'{cell.alignment}\n\n')
            if startrow is None and sheet_name in writer.book.sheetnames:
                startrow = writer.book[sheet_name].max_row
            # truncate sheet
            if truncate_sheet and sheet_name in writer.book.sheetnames:
                # index of [sheet_name] sheet
                idx = writer.book.sheetnames.index(sheet_name)
                # remove [sheet_name]
                writer.book.remove(writer.book.worksheets[idx])
                # create an empty sheet [sheet_name] using old index
                writer.book.create_sheet(sheet_name, idx)
            # copy existing sheets
            writer.sheets = sheets
        else:
            # file doesn't exist, we are creating a new one
            startrow = 0

        # write out the DataFrame to an ExcelWriter
        sf.to_excel(writer, sheet_name=sheet_name)
        worksheet = writer.sheets[sheet_name]

        if autofilter:
            worksheet.auto_filter.ref = worksheet.dimensions

        for xl_col_no, dtyp in enumerate(sf.data_df.dtypes, first_col):
            col_no = xl_col_no - first_col
            width = max(sf.iloc[:, col_no].astype(str).str.len().max(),
                        len(sf.columns[col_no]) + 6)
            width = min(max_col_width, width)
            column_letter = get_column_letter(xl_col_no)
            worksheet.column_dimensions[column_letter].width = width
            if np.issubdtype(dtyp, np.integer):
                set_column_format(worksheet, column_letter, fmt_int)
            if np.issubdtype(dtyp, np.floating):
                set_column_format(worksheet, column_letter, fmt_float)

    if file_exists and sheet_exists:
        # move (append) rows from new worksheet to the `sheet_name` worksheet
        wb = load_workbook(filename)
        # retrieve generated worksheet name
        new_sheet_name = set(wb.sheetnames) - set(sheet_names)
        if new_sheet_name:
            new_sheet_name = list(new_sheet_name)[0]
        # copy rows written by `sf.to_excel(...)` to

        copy_excel_cell_range(
            src_ws=wb[new_sheet_name],
            tgt_ws=wb[sheet_name],
            tgt_min_row=startrow + 1,
            with_style=True
        )
        # remove new (generated by Pandas) worksheet
        del wb[new_sheet_name]
        wb.save(filename)
        wb.close()

Credit to Maxu for writing this function, and thanks to Deepspace for making me aware of this solution.感谢 Maxu 编写了这个函数,感谢 Deepspace 让我知道这个解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM