"""MIL 测试用例 Excel 更新模块 主要功能: - 比较新旧数据差异 - 添加新信号列或删除旧信号列 """ import logging from typing import Any from .base import SignalData from .exceptions import CaseDataError from openpyxl import Workbook from openpyxl.worksheet.worksheet import Worksheet logger = logging.getLogger(__name__) def update_case_excel(filename: str, old_data: dict, new_data: dict) -> None: """将旧 Excel 同步到新 Excel 的信号列集合(增 / 删)。 流程: 1. 拆出 wb / sheet / source_row 等元数据; 2. 算 diff:要新增的信号、要删除的信号; 3. 先删后插(保持列号稳定); 4. 回写文件。 Args: filename: 目标 Excel 文件路径。 old_data: 旧数据字典(含 wb / sheet / source_row 及各信号)。 new_data: 新数据字典(结构同上,用于 diff 比较)。 Raises: CaseDataError: 旧数据字典中 Workbook/Worksheet 类型不匹配。 """ logger.info(f"开始更新 Excel 文件 {filename}") # 注意:pop 会从字典中移除键,调用前请确认数据不再被复用 old_wb = old_data.pop('wb') old_sheet = old_data.pop('sheet') old_source_row = old_data.pop('source_row') new_data.pop('wb') new_data.pop('sheet') new_source_row = new_data.pop('source_row') if not isinstance(old_wb, Workbook) or not isinstance(old_sheet, Worksheet): logger.error("数据类型错误,Workbook 或 Worksheet 类型不匹配") raise CaseDataError("数据异常,Workbook 或 Worksheet 类型错误") # 计算"新增"与"删除"两组信号 add_input = _get_add_input(old_data, new_data) del_input = _get_del_input(old_data, new_data) datalog_len = _get_datalog_len(old_data) # 先删除旧列(逆序删除是为了让被删列的索引不会影响后续操作) for name, info in reversed(del_input.items()): old_sheet.delete_cols(info['column']) logger.info(f"删除第{info['column']}列\t信号名: {name}") # 再插入新增列:先 insert_cols 占位,再填充属性行与数据点 for name, info in add_input.items(): old_sheet.insert_cols(info['column']) # 1) 属性行(header 行以下至 source_row - 1) for index in range(1, old_source_row): if index == old_source_row - 1: # source_header 行:从 attributes 字典里取出 source_header 文本 # 注:attributes 的行号空间与 old_data 保持一致,应使用 old_source_row old_sheet.cell(index, info['column']).value = info['attributes'][old_source_row - 1] old_sheet.cell(index, info['column']).data_type = "str" else: old_sheet.cell(index, info['column']).value = info['attributes'][index] old_sheet.cell(index, info['column']).data_type = "str" # 2) 数据行:根据值类型设置 data_type for index in range(old_source_row + 1, datalog_len + old_source_row + 1): try: value = int(info['datalog'][0].value) old_sheet.cell(index, info['column']).value = value old_sheet.cell(index, info['column']).data_type = "int" except (ValueError, TypeError): value = str(info['datalog'][0].value) old_sheet.cell(index, info['column']).value = value old_sheet.cell(index, info['column']).data_type = "str" logger.info(f"新增第{info['column']}列\t信号名: {name}") old_wb.save(filename) logger.info(f"更新 Excel 文件 {filename} 完成") def _get_add_input(old_data: dict, new_data: dict) -> dict[str, SignalData]: """计算"新增"信号集合:存在于 new_data 但不存在于 old_data 的信号。 Args: old_data: 旧数据字典。 new_data: 新数据字典。 Returns: 新增信号字典。 """ add_input: dict[str, SignalData] = {} for name in new_data.keys(): if name not in old_data.keys(): add_input[name] = new_data[name] return add_input def _get_del_input(old_data: dict, new_data: dict) -> dict[str, SignalData]: """计算"删除"信号集合:存在于 old_data 但不存在于 new_data 的信号。 Args: old_data: 旧数据字典。 new_data: 新数据字典。 Returns: 删除信号字典。 """ del_input: dict[str, SignalData] = {} for name in old_data.keys(): if name not in new_data.keys(): del_input[name] = old_data[name] return del_input def _get_datalog_len(old_data: dict) -> int: """获取旧数据中第一个信号的 datalog 长度,作为新增列填充行数的基准。 假设 old_data 中各信号 datalog 长度一致(来自同一时间轴)。 Args: old_data: 旧数据字典。 Returns: 数据日志长度(行数);空字典返回 0。 """ if not old_data: return 0 # 取首项的 datalog 长度即可,无需遍历整张字典 first_name = next(iter(old_data)) return len(old_data[first_name]['datalog'])