import os
from typing import List, Dict, Optional
import pandas as pd
from msprof_analyze.prof_common.path_manager import PathManager
from msprof_analyze.prof_common.logger import get_logger
logger = get_logger()
class ExcelUtils:
DEFAULT_FORMAT = {
'valign': 'vcenter',
'border': 1,
'font_name': 'Times New Roman'
}
DEFAULT_HEADER_FORMAT = {
'valign': 'vcenter',
'bold': True,
'border': 1,
'bg_color': '#AFEEEE',
'font_name': 'Times New Roman'
}
def __init__(self):
self.workbook = None
self.writer = None
self.worksheet = None
self.df = None
self._formats_cache = {}
def clear(self) -> None:
if self.writer:
self.writer.close()
self.workbook = None
self.writer = None
self.worksheet = None
self.df = None
self._formats_cache = {}
def create_excel_writer(self,
output_path: str,
file_name: str,
df: pd.DataFrame,
sheet_name: str = 'Sheet1',
format_config: Optional[Dict[str, Dict]] = None) -> None:
"""
初始化ExcelWriter并写入原始数据
Args:
output_path: 输出目录路径
file_name: 输出文件名
df: 要写入的DataFrame数据
sheet_name: 工作表名称 (可选,默认为'Sheet1')
format_config: 格式化配置字典 (可选)
- header: 标题行格式 (可选)
- column: 数据列格式 (可选)
"""
PathManager.check_path_writeable(output_path)
self.writer = pd.ExcelWriter(os.path.join(output_path, file_name), engine='xlsxwriter')
self.workbook = self.writer.book
self.worksheet = self.workbook.add_worksheet(sheet_name)
self.df = df
format_config = format_config or {}
header_fmt = self._get_format(format_config.get('header', self.DEFAULT_HEADER_FORMAT))
for col_idx, col_name in enumerate(df.columns):
self.worksheet.write(0, col_idx, col_name, header_fmt)
default_fmt = self._get_format(format_config.get('column', self.DEFAULT_FORMAT))
for row_idx, row in df.iterrows():
for col_idx, col_name in enumerate(df.columns):
self.worksheet.write(row_idx + 1, col_idx, row[col_name], default_fmt)
def save_and_close(self):
if self.writer:
self.writer.close()
self.writer = None
self.workbook = None
self.worksheet = None
def set_column_width(self, columns_config: Dict[str, int]):
if not self.worksheet:
raise Exception("Worksheet has not been initialized!")
for col, width in columns_config.items():
col_idx = list(columns_config.keys()).index(col)
self.worksheet.set_column(col_idx, col_idx, width)
def set_row_height(self, row: int, height: int):
if not self.worksheet:
raise Exception("Worksheet not initialized")
self.worksheet.set_row(row, height)
def freeze_panes(self, row: int = 1, col: int = 0):
if not self.worksheet:
raise Exception("Worksheet has not been initialized!")
self.worksheet.freeze_panes(row, col)
def merge_duplicate_cells(
self,
columns_to_merge: List[str],
merge_format: Optional[Dict] = None,
header_format: Optional[Dict] = None
):
"""
合并连续相同值的单元格
参数:
df: 输入的 DataFrame
columns_to_merge: 需要合并的列名列表
merge_format: 合并单元格的格式字典
header_format: 标题行的格式字典
"""
if not self.workbook or not self.worksheet:
raise Exception("Worksheet has not been initialized!")
merge_fmt = self._get_format(merge_format if merge_format else self.DEFAULT_FORMAT)
header_fmt = self._get_format(header_format if header_format else self.DEFAULT_HEADER_FORMAT)
for col_num, value in enumerate(self.df.columns.values):
self.worksheet.write(0, col_num, value, header_fmt)
for col in columns_to_merge:
if col not in self.df.columns:
logger.warning(f"Invalid column: {col}, not in dataframe!")
continue
col_idx = self.df.columns.get_loc(col)
current_value = None
start_row = 1
merge_count = 0
for i in range(len(self.df)):
excel_row = i + 1
if self.df[col].iloc[i] == current_value:
continue
else:
if current_value is not None and (excel_row - 1) > start_row:
self.worksheet.merge_range(
start_row, col_idx, excel_row - 1, col_idx,
current_value,
merge_fmt
)
merge_count += 1
current_value = self.df[col].iloc[i]
start_row = excel_row
if current_value is not None and (len(self.df)) > start_row:
self.worksheet.merge_range(
start_row, col_idx, len(self.df), col_idx,
current_value,
merge_fmt
)
merge_count += 1
def _get_format(self, format_dict: Dict):
format_key = frozenset(format_dict.items())
if format_key not in self._formats_cache:
self._formats_cache[format_key] = self.workbook.add_format(format_dict)
return self._formats_cache[format_key]