Add --sheet parameter to specify worksheets by name or index (1-based). Change default behavior to use first worksheet instead of active sheet, with proper error messages for invalid worksheet references. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
246 lines
8.8 KiB
Python
246 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Excel to Markdown Converter
|
||
将Excel文档转换为Markdown表格格式
|
||
"""
|
||
|
||
import openpyxl
|
||
from openpyxl.utils import get_column_letter
|
||
from typing import Optional, Tuple
|
||
import argparse
|
||
|
||
|
||
class ExcelToMarkdown:
|
||
"""Excel转Markdown转换器"""
|
||
|
||
def __init__(self, excel_file: str, sheet_name: Optional[str] = None):
|
||
"""
|
||
初始化转换器
|
||
|
||
Args:
|
||
excel_file: Excel文件路径
|
||
sheet_name: 工作表名称或索引(None表示使用第一个工作表)
|
||
"""
|
||
self.workbook = openpyxl.load_workbook(excel_file, data_only=True)
|
||
|
||
# 获取指定的工作表
|
||
if sheet_name is None:
|
||
# 使用第一个工作表
|
||
self.sheet = self.workbook.worksheets[0]
|
||
elif isinstance(sheet_name, int) or sheet_name.isdigit():
|
||
# 按索引获取(从1开始)
|
||
sheet_index = int(sheet_name) - 1
|
||
if 0 <= sheet_index < len(self.workbook.worksheets):
|
||
self.sheet = self.workbook.worksheets[sheet_index]
|
||
else:
|
||
raise ValueError(f"工作表索引 {sheet_name} 超出范围(1-{len(self.workbook.worksheets)})")
|
||
else:
|
||
# 按名称获取
|
||
if sheet_name in self.workbook.sheetnames:
|
||
self.sheet = self.workbook[sheet_name]
|
||
else:
|
||
available_sheets = ", ".join(self.workbook.sheetnames)
|
||
raise ValueError(f"找不到工作表 '{sheet_name}',可用的工作表: {available_sheets}")
|
||
|
||
def detect_data_range(self, max_scan_rows: int = 100, max_scan_cols: int = 100) -> Tuple[int, int]:
|
||
"""
|
||
自动检测数据范围
|
||
|
||
Args:
|
||
max_scan_rows: 最大扫描行数
|
||
max_scan_cols: 最大扫描列数
|
||
|
||
Returns:
|
||
(最大行号, 最大列号)
|
||
"""
|
||
max_row = 0
|
||
max_col = 0
|
||
|
||
# 扫描前100行和前100列
|
||
scan_rows = min(max_scan_rows, self.sheet.max_row)
|
||
scan_cols = min(max_scan_cols, self.sheet.max_column)
|
||
|
||
for row in range(1, scan_rows + 1):
|
||
for col in range(1, scan_cols + 1):
|
||
cell_value = self.sheet.cell(row=row, column=col).value
|
||
if cell_value is not None and str(cell_value).strip() != "":
|
||
max_row = max(max_row, row)
|
||
max_col = max(max_col, col)
|
||
|
||
return max_row, max_col
|
||
|
||
def convert(self,
|
||
max_rows: Optional[int] = None,
|
||
max_cols: Optional[int] = None,
|
||
show_row_numbers: bool = False,
|
||
show_col_numbers: bool = False,
|
||
start_row: int = 1,
|
||
start_col: int = 1) -> str:
|
||
"""
|
||
转换Excel为Markdown格式
|
||
|
||
Args:
|
||
max_rows: 最大行数(None表示自动检测)
|
||
max_cols: 最大列数(None表示自动检测)
|
||
show_row_numbers: 是否显示行号
|
||
show_col_numbers: 是否显示列号(字母形式)
|
||
start_row: 起始行号(默认1)
|
||
start_col: 起始列号(默认1)
|
||
|
||
Returns:
|
||
Markdown格式的表格字符串
|
||
"""
|
||
# 自动检测数据范围
|
||
if max_rows is None or max_cols is None:
|
||
detected_rows, detected_cols = self.detect_data_range()
|
||
if max_rows is None:
|
||
max_rows = detected_rows
|
||
if max_cols is None:
|
||
max_cols = detected_cols
|
||
|
||
# 确保至少有1行1列
|
||
if max_rows < 1 or max_cols < 1:
|
||
return "| 无数据 |\n|--------|\n"
|
||
|
||
# 计算实际的结束位置
|
||
end_row = min(start_row + max_rows - 1, self.sheet.max_row)
|
||
end_col = min(start_col + max_cols - 1, self.sheet.max_column)
|
||
|
||
markdown_lines = []
|
||
|
||
# 构建列号行(如果需要)
|
||
if show_col_numbers:
|
||
header_parts = []
|
||
if show_row_numbers:
|
||
header_parts.append("") # 行号列的占位
|
||
|
||
for col in range(start_col, end_col + 1):
|
||
col_letter = get_column_letter(col)
|
||
header_parts.append(col_letter)
|
||
|
||
markdown_lines.append("| " + " | ".join(header_parts) + " |")
|
||
|
||
# 分隔线
|
||
separator_parts = ["---"] * len(header_parts)
|
||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||
|
||
# 构建数据行
|
||
for row in range(start_row, end_row + 1):
|
||
row_parts = []
|
||
|
||
# 添加行号(如果需要)
|
||
if show_row_numbers:
|
||
row_parts.append(str(row))
|
||
|
||
# 添加单元格数据
|
||
for col in range(start_col, end_col + 1):
|
||
cell_value = self.sheet.cell(row=row, column=col).value
|
||
|
||
# 处理空值
|
||
if cell_value is None:
|
||
cell_str = ""
|
||
else:
|
||
cell_str = str(cell_value).strip()
|
||
# 转义Markdown特殊字符
|
||
cell_str = cell_str.replace("|", "\\|")
|
||
cell_str = cell_str.replace("\n", "<br>")
|
||
|
||
row_parts.append(cell_str)
|
||
|
||
markdown_lines.append("| " + " | ".join(row_parts) + " |")
|
||
|
||
# 如果是第一行且没有显示列号,添加分隔线
|
||
if row == start_row and not show_col_numbers:
|
||
separator_parts = ["---"] * len(row_parts)
|
||
markdown_lines.append("| " + " | ".join(separator_parts) + " |")
|
||
|
||
return "\n".join(markdown_lines)
|
||
|
||
def save_to_file(self, markdown_text: str, output_file: str):
|
||
"""
|
||
保存Markdown到文件
|
||
|
||
Args:
|
||
markdown_text: Markdown文本
|
||
output_file: 输出文件路径
|
||
"""
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write(markdown_text)
|
||
|
||
def close(self):
|
||
"""关闭工作簿"""
|
||
self.workbook.close()
|
||
|
||
|
||
def main():
|
||
"""命令行入口"""
|
||
parser = argparse.ArgumentParser(
|
||
description='将Excel文档转换为Markdown表格格式',
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
示例:
|
||
# 自动检测范围并转换
|
||
python excel_to_markdown.py input.xlsx -o output.md
|
||
|
||
# 指定转换前10行、前5列
|
||
python excel_to_markdown.py input.xlsx -o output.md -r 10 -c 5
|
||
|
||
# 显示行号和列号
|
||
python excel_to_markdown.py input.xlsx -o output.md --show-rows --show-cols
|
||
|
||
# 从第2行第2列开始,转换5行3列
|
||
python excel_to_markdown.py input.xlsx -o output.md -r 5 -c 3 --start-row 2 --start-col 2
|
||
|
||
# 指定工作表(按名称)
|
||
python excel_to_markdown.py input.xlsx -o output.md --sheet "Sheet2"
|
||
|
||
# 指定工作表(按索引)
|
||
python excel_to_markdown.py input.xlsx -o output.md --sheet 2
|
||
"""
|
||
)
|
||
|
||
parser.add_argument('input_file', help='输入的Excel文件路径')
|
||
parser.add_argument('-o', '--output', help='输出的Markdown文件路径(不指定则输出到控制台)')
|
||
parser.add_argument('-r', '--max-rows', type=int, help='最大转换行数(不指定则自动检测)')
|
||
parser.add_argument('-c', '--max-cols', type=int, help='最大转换列数(不指定则自动检测)')
|
||
parser.add_argument('--show-rows', action='store_true', help='显示行号')
|
||
parser.add_argument('--show-cols', action='store_true', help='显示列号(字母形式)')
|
||
parser.add_argument('--start-row', type=int, default=1, help='起始行号(默认1)')
|
||
parser.add_argument('--start-col', type=int, default=1, help='起始列号(默认1)')
|
||
parser.add_argument('--sheet', help='工作表名称或索引(不指定则使用第一个工作表)')
|
||
|
||
args = parser.parse_args()
|
||
|
||
try:
|
||
# 创建转换器
|
||
converter = ExcelToMarkdown(args.input_file, args.sheet)
|
||
|
||
# 执行转换
|
||
markdown_text = converter.convert(
|
||
max_rows=args.max_rows,
|
||
max_cols=args.max_cols,
|
||
show_row_numbers=args.show_rows,
|
||
show_col_numbers=args.show_cols,
|
||
start_row=args.start_row,
|
||
start_col=args.start_col
|
||
)
|
||
|
||
# 输出结果
|
||
if args.output:
|
||
converter.save_to_file(markdown_text, args.output)
|
||
print(f"✓ 转换完成!已保存到: {args.output}")
|
||
else:
|
||
print(markdown_text)
|
||
|
||
# 关闭工作簿
|
||
converter.close()
|
||
|
||
except FileNotFoundError:
|
||
print(f"✗ 错误: 找不到文件 '{args.input_file}'")
|
||
except Exception as e:
|
||
print(f"✗ 错误: {str(e)}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |