Files
CraftKit/plugins/doc/skills/xlsx-to-md/scripts/convert.py
T

110 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""将 XLSX 工作簿的二维数据转换为 Markdown。"""
from __future__ import annotations
import argparse
import datetime as dt
import sys
from pathlib import Path
from openpyxl import load_workbook
def display(value: object) -> str:
"""将单元格值转换为稳定、可读且适合表格的文本。"""
if value is None:
return ""
if isinstance(value, dt.datetime):
value = value.isoformat(sep=" ", timespec="seconds")
elif isinstance(value, (dt.date, dt.time)):
value = value.isoformat()
elif isinstance(value, bool):
value = "TRUE" if value else "FALSE"
text = str(value).replace("\\", "\\\\").replace("|", "\\|")
return text.replace("\r\n", "<br>").replace("\n", "<br>").replace("\r", "<br>")
def matrix_for(sheet, merged_mode: str) -> tuple[list[list[str]], int]:
"""读取有效区域,并按选择的策略表达合并单元格。"""
values = [[cell.value for cell in row] for row in sheet.iter_rows()]
merged_count = len(sheet.merged_cells.ranges)
if merged_mode == "fill":
for merged in sheet.merged_cells.ranges:
anchor = values[merged.min_row - 1][merged.min_col - 1]
for row in range(merged.min_row - 1, merged.max_row):
for column in range(merged.min_col - 1, merged.max_col):
values[row][column] = anchor
while values and all(value is None for value in values[-1]):
values.pop()
width = max((max((i + 1 for i, value in enumerate(row) if value is not None), default=0) for row in values), default=0)
return [[display(value) for value in row[:width]] for row in values], merged_count
def render_sheet(title: str, rows: list[list[str]]) -> str:
"""将单个工作表渲染为 Markdown 章节。"""
lines = [f"## {title}", ""]
if not rows or not rows[0]:
return "\n".join(lines + ["_空工作表_"])
width = max(map(len, rows))
normalized = [row + [""] * (width - len(row)) for row in rows]
lines.extend((f"| {' | '.join(normalized[0])} |", f"| {' | '.join(['---'] * width)} |"))
lines.extend(f"| {' | '.join(row)} |" for row in normalized[1:])
return "\n".join(lines)
def convert(source: Path, output: Path, values_only: bool, merged_mode: str) -> tuple[int, int, int, list[str]]:
"""打开工作簿、转换全部工作表并写入 UTF-8 Markdown。"""
workbook = load_workbook(source, read_only=False, data_only=values_only)
sections = [f"# {source.stem}"]
row_count = merged_count = 0
warnings: list[str] = []
for sheet in workbook.worksheets:
rows, merged = matrix_for(sheet, merged_mode)
sections.append(render_sheet(sheet.title, rows))
row_count += len(rows)
merged_count += merged
if not rows or not rows[0]:
warnings.append(f"空工作表:{sheet.title}")
sheet_count = len(workbook.worksheets)
workbook.close()
output.write_text("\n\n".join(sections).rstrip() + "\n", encoding="utf-8")
return sheet_count, row_count, merged_count, warnings
def main(argv: list[str] | None = None) -> int:
"""校验文件类型、覆盖授权和输出路径后执行转换。"""
parser = argparse.ArgumentParser(description="将 XLSX 工作簿转换为 Markdown")
parser.add_argument("input", type=Path)
parser.add_argument("--output", type=Path)
parser.add_argument("--values", action="store_true", help="读取缓存值而不是公式文本")
parser.add_argument("--merged", choices=("fill", "anchor"), default="fill")
parser.add_argument("--force", action="store_true")
args = parser.parse_args(argv)
source = args.input.resolve()
output = (args.output or source.with_suffix(".md")).resolve()
if not source.is_file() or source.suffix.lower() != ".xlsx":
print("错误:只支持存在的 .xlsx 文件;旧版 .xls 不受支持", file=sys.stderr); return 2
if output.suffix.lower() not in {".md", ".markdown"}:
print("错误:输出文件必须是 Markdown", file=sys.stderr); return 2
if output.exists() and not args.force:
print(f"错误:输出文件已存在:{output}", file=sys.stderr); return 1
output.parent.mkdir(parents=True, exist_ok=True)
try:
sheets, rows, merged, warnings = convert(source, output, args.values, args.merged)
except (OSError, ValueError) as error:
print(f"错误:{error}", file=sys.stderr); return 1
print(f"Markdown:{output}")
print(f"工作表:{sheets},数据行:{rows},合并区域:{merged}")
for warning in warnings: print(f"警告:{warning}")
return 0
if __name__ == "__main__":
raise SystemExit(main())