Files

186 lines
7.0 KiB
Python

from __future__ import annotations
from pathlib import Path
import xlrd
from xlutils.copy import copy as copy_workbook
from .config import AppConfig
from .models import WeldRecord
EXCEL_FIELD_TO_RECORD = {
"管线代号": "line_no",
"焊口代号": "weld_no",
"焊接区域(安装/预制)": "weld_area",
"规格(mm)": "spec",
"壁厚": "wall_thickness",
"单线图号": "pipeline_reference",
"管线等级代号": "piping_spec",
"外径": "outside_diameter",
}
EXCEL_FIELD_TO_ENGLISH = {
"单位代码": "unit_code",
"装置工区编号": "plant_area_code",
"管线代号": "line_no",
"焊口代号": "weld_no",
"材质类型代号": "material_type_code",
"材质1代号": "material_1_code",
"材质2代号": "material_2_code",
"材料1": "material_1",
"材料2": "material_2",
"探伤比例代号": "ndt_ratio_code",
"焊缝类型代号": "weld_type_code",
"焊接区域(安装/预制)": "weld_area",
"焊口属性(固定、活动)": "weld_property",
"达因数": "dia_inch",
"规格(mm)": "spec",
"壁厚": "wall_thickness",
"焊接方法代码": "welding_method_code",
"试验压力": "test_pressure",
"焊条代号": "welding_rod_code",
"焊丝代号": "welding_wire_code",
"介质代号": "medium_code",
"单线图号": "isometric_no",
"设计压力": "design_pressure",
"设计温度": "design_temperature",
"坡口代号": "groove_code",
"管线等级代号": "piping_class_code",
"组件一代号": "component_1_code",
"组件二代号": "component_2_code",
"炉批号一": "heat_no_1",
"炉批号二": "heat_no_2",
"所属管段": "pipe_spool",
"预热温度": "preheat_temperature",
"是否需热处理(是,否)": "need_heat_treatment",
"热处理编号": "heat_treatment_no",
"焊接位置(1G/2G/3G/4G/5G/6G)": "welding_position",
"外径": "outside_diameter",
"硬度检测比例(数值)": "hardness_test_ratio",
"焊接气体保护": "shielding_gas",
"是否非标(是/否)": "is_non_standard",
"壁板号": "plate_no",
"延长米": "extended_meter",
"管道长度": "pipe_length",
}
class XlsTemplateWriter:
def __init__(self, config: AppConfig):
self.config = config
def write(self, records: list[WeldRecord], output_path: Path) -> None:
template_path = self.config.template_path
if not template_path.exists():
raise FileNotFoundError(f"模板不存在:{template_path}")
rb = xlrd.open_workbook(
str(template_path),
formatting_info=True,
on_demand=False,
)
sheet_index = rb.sheet_names().index(self.config.output_sheet)
rs = rb.sheet_by_index(sheet_index)
wb = copy_workbook(rb)
ws = wb.get_sheet(sheet_index)
headers = [str(rs.cell_value(0, c)).strip() for c in range(rs.ncols)]
template_style_by_col = self._template_styles(rb, rs, headers)
# 清空模板中已有示例数据,避免生成记录少于示例时残留旧值。
for r in range(1, rs.nrows):
for c in range(rs.ncols):
ws.write(r, c, "", template_style_by_col[c])
for row_offset, record in enumerate(records, start=1):
row_values = self.row_values(headers, record)
for col_idx, value in enumerate(row_values):
ws.write(row_offset, col_idx, value, template_style_by_col[col_idx])
output_path.parent.mkdir(parents=True, exist_ok=True)
wb.save(str(output_path))
def _template_styles(self, rb: xlrd.book.Book, rs: xlrd.sheet.Sheet, headers: list[str]):
styles = []
for col_idx, _header in enumerate(headers):
source_row = 1 if rs.nrows > 1 else 0
xf_idx = rs.cell_xf_index(source_row, col_idx)
styles.append(rb.xf_list[xf_idx])
# xlutils 复制后需要使用目标工作簿内部样式对象。
# 经验上直接使用 rb.xf_list 会失败,因此回退到 xlwt 默认样式时由调用方兜底。
return [self._xf_to_xlwt_style(rb, xf) for xf in styles]
def _xf_to_xlwt_style(self, rb: xlrd.book.Book, xf: xlrd.formatting.XF):
import xlwt
style = xlwt.XFStyle()
font = rb.font_list[xf.font_index]
style.font.name = font.name
style.font.bold = font.bold
style.font.italic = font.italic
style.font.height = font.height
style.font.colour_index = font.colour_index
fmt = rb.format_map.get(xf.format_key)
if fmt:
style.num_format_str = fmt.format_str
alignment = style.alignment
alignment.horz = xf.alignment.hor_align
alignment.vert = xf.alignment.vert_align
alignment.wrap = xf.alignment.text_wrapped
borders = style.borders
borders.left = xf.border.left_line_style
borders.right = xf.border.right_line_style
borders.top = xf.border.top_line_style
borders.bottom = xf.border.bottom_line_style
borders.left_colour = xf.border.left_colour_index
borders.right_colour = xf.border.right_colour_index
borders.top_colour = xf.border.top_colour_index
borders.bottom_colour = xf.border.bottom_colour_index
pattern = style.pattern
pattern.pattern = xf.background.fill_pattern
pattern.pattern_fore_colour = xf.background.pattern_colour_index
pattern.pattern_back_colour = xf.background.background_colour_index
return style
def headers(self) -> list[str]:
rb = xlrd.open_workbook(str(self.config.template_path), formatting_info=False, on_demand=True)
try:
rs = rb.sheet_by_name(self.config.output_sheet)
return [str(rs.cell_value(0, c)).strip() for c in range(rs.ncols)]
finally:
rb.release_resources()
def build_import_rows(
self, records: list[WeldRecord]
) -> tuple[list[str], list[dict[str, str]], list[dict[str, str]]]:
headers = self.headers()
columns = self.import_columns(headers)
keys = [column["key"] for column in columns]
rows = [dict(zip(keys, self.row_values(headers, record))) for record in records]
return headers, columns, rows
def import_columns(self, headers: list[str]) -> list[dict[str, str]]:
used: set[str] = set()
columns: list[dict[str, str]] = []
for index, header in enumerate(headers, start=1):
key = EXCEL_FIELD_TO_ENGLISH.get(header) or f"column_{index:02d}"
if key in used:
key = f"{key}_{index:02d}"
used.add(key)
columns.append({"key": key, "label": header})
return columns
def row_values(self, headers: list[str], record: WeldRecord) -> list[str]:
values: list[str] = []
for header in headers:
if header in EXCEL_FIELD_TO_RECORD:
values.append(str(getattr(record, EXCEL_FIELD_TO_RECORD[header], "") or ""))
else:
values.append(str(self.config.defaults.get(header, "") or ""))
return values