@@ -0,0 +1,109 @@
#!/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 ( ) )