#!/usr/bin/env python3 """Independently verify generated BIFF8 data and visual template contracts.""" from __future__ import annotations from pathlib import Path import sys try: import xlrd except ImportError: sys.stderr.write("[payment-order-xls][FAIL] verifier dependency unavailable\n") raise SystemExit(1) TEMPLATE_PATH = Path(__file__).resolve().parents[1] / "src" / "assets" / "payment-order" / "ccb-payment-order-template.xls" EXPECTED_HEADERS = [ "*序号", "*付款方客户账号", "*付款方账户名称", "*收款方行别代码\n(01-本行 02-国内他行)", "*收款方客户账号", "*收款方账户名称", "收款方开户行名称", "收款方联行号", "客户方流水号", "*金额", "*用途", "备注", "是否短信通知收款人(0-不通知,1-通知,默认为0-不通知。)", "收款人手机号码", "短信通知附加信息", ] REQUIRED_HEADER_COLUMNS = (0, 1, 2, 3, 4, 5, 9, 10) def font_signature(font: xlrd.formatting.Font) -> tuple: return ( font.name, font.height, font.bold, font.italic, font.struck_out, font.outline, font.shadow, font.colour_index, font.weight, font.escapement, font.underline_type, font.family, font.character_set, ) def rich_text_signature(book: xlrd.book.Book, sheet: xlrd.sheet.Sheet, row: int, column: int) -> list[tuple]: text = sheet.cell_value(row, column) runs = sheet.rich_text_runlist_map.get((row, column), []) if not runs: return [] signature = [] if runs[0][0] > 0: base_font_index = book.xf_list[sheet.cell_xf_index(row, column)].font_index signature.append((0, runs[0][0], text[:runs[0][0]], font_signature(book.font_list[base_font_index]))) for index, (offset, font_index) in enumerate(runs): end = runs[index + 1][0] if index + 1 < len(runs) else len(text) signature.append((offset, end, text[offset:end], font_signature(book.font_list[font_index]))) return signature def visual_signature(book: xlrd.book.Book, sheet: xlrd.sheet.Sheet, row: int, column: int) -> tuple: xf = book.xf_list[sheet.cell_xf_index(row, column)] font = book.font_list[xf.font_index] alignment = xf.alignment background = xf.background border = xf.border return ( font.name, font.height, font.bold, font.italic, font.underline_type, font.colour_index, alignment.hor_align, alignment.vert_align, alignment.text_wrapped, alignment.rotation, alignment.shrink_to_fit, background.fill_pattern, background.pattern_colour_index, background.background_colour_index, border.left_line_style, border.right_line_style, border.top_line_style, border.bottom_line_style, border.left_colour_index, border.right_colour_index, border.top_colour_index, border.bottom_colour_index, ) def main() -> int: stage = "read" try: generated_bytes = sys.stdin.buffer.read(16 * 1024 * 1024 + 1) assert len(generated_bytes) <= 16 * 1024 * 1024 template = xlrd.open_workbook(str(TEMPLATE_PATH), formatting_info=True) generated = xlrd.open_workbook(file_contents=generated_bytes, formatting_info=True) stage = "structure" assert generated.sheet_names() == ["Sheet1", "Sheet2", "Sheet3"] assert [(sheet.nrows, sheet.ncols) for sheet in generated.sheets()] == [(5, 15), (0, 0), (0, 0)] source_sheet = template.sheet_by_index(0) sheet = generated.sheet_by_index(0) stage = "headers" assert sheet.row_values(0) == EXPECTED_HEADERS stage = "header_rich_text" assert set(sheet.rich_text_runlist_map) == set(source_sheet.rich_text_runlist_map) for row_column in source_sheet.rich_text_runlist_map: row, column = row_column assert rich_text_signature(generated, sheet, row, column) == rich_text_signature( template, source_sheet, row, column, ) for column in REQUIRED_HEADER_COLUMNS: parts = rich_text_signature(generated, sheet, 0, column) assert parts[0][2] == "*" and parts[0][3][7] == 10 assert "".join(part[2] for part in parts[1:]) == EXPECTED_HEADERS[column][1:] assert all(part[3][7] == 8 for part in parts[1:]) stage = "column_widths" stage = "explicit_column_widths" assert {column: info.width for column, info in sheet.colinfo_map.items()} == { column: info.width for column, info in source_sheet.colinfo_map.items() } stage = "header_height" assert sheet.rowinfo_map[0].height == source_sheet.rowinfo_map[0].height for row in range(1, 5): stage = "data_row_height" assert sheet.rowinfo_map[row].height == source_sheet.rowinfo_map[1].height for column in range(15): stage = "visual_style" assert visual_signature(generated, sheet, row, column) == visual_signature(template, source_sheet, 1, column) stage = "cell_types" for row in range(1, 5): for column in (0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 12): stage = f"text_cell_{column}" assert sheet.cell_type(row, column) == xlrd.XL_CELL_TEXT for column in (7, 13, 14): stage = f"empty_cell_{column}" assert sheet.cell_type(row, column) in (xlrd.XL_CELL_EMPTY, xlrd.XL_CELL_BLANK) stage = "payer_account_value" assert sheet.cell_value(1, 1) == "6217000000000012345" stage = "bank_type_value" assert sheet.cell_value(1, 3) == "01" stage = "payee_account_value" assert sheet.cell_value(1, 4) == "6222000000000012345" stage = "first_customer_serial_value" assert sheet.cell_value(1, 8) == "PB-20260803-001-R001" stage = "last_customer_serial_value" assert sheet.cell_value(4, 8) == "PB-20260803-001-R004" stage = "payee_name_value" assert sheet.cell_value(1, 5) == "合成员工1" stage = "purpose_value" assert sheet.cell_value(1, 10) == "报销款" stage = "sms_flag_value" assert sheet.cell_value(1, 12) == "0" for row in range(1, 5): assert sheet.cell_type(row, 9) == xlrd.XL_CELL_NUMBER stage = "amount_format" assert generated.format_map[generated.xf_list[sheet.cell_xf_index(row, 9)].format_key].format_str == "0.00" sys.stdout.write("[payment-order-xls][PASS] original BIFF8 template data and visual contracts verified\n") return 0 except Exception: sys.stderr.write(f"[payment-order-xls][FAIL] BIFF8 template contract mismatch: {stage}\n") return 1 if __name__ == "__main__": raise SystemExit(main())