import os from docx import Document from docx.oxml.ns import qn from PIL import Image from io import BytesIO from logger import LOG # 引入日志模块,用于记录调试信息 def is_paragraph_list_item(paragraph): """ 检查段落是否为列表项。 判断依据是段落的样式名称是否包含 'list bullet' 或 'list number', 分别对应项目符号列表和编号列表。 """ style_name = paragraph.style.name.lower() return 'list bullet' in style_name or 'list number' in style_name def get_paragraph_list_level(paragraph): """ 获取段落的列表级别(缩进层级)。 首先尝试通过 XML 结构判断,如果无法获取,则通过样式名称中的数字判断。 """ p = paragraph._p numPr = p.find(qn('w:numPr')) if numPr is not None: ilvl = numPr.find(qn('w:ilvl')) if ilvl is not None: return int(ilvl.get(qn('w:val'))) style_name = paragraph.style.name.lower() if 'list bullet' in style_name or 'list number' in style_name: for word in style_name.split(): if word.isdigit(): return int(word) - 1 return 0 def generate_markdown_from_docx(docx_filename): """ 从指定的 docx 文件生成 Markdown 格式的内容,并将所有图像另存为文件并插入 Markdown 内容中。 支持标题、列表项、图像和普通段落的转换。 """ # 获取 docx 文件的基本名称,用于创建图像文件夹 docx_basename = os.path.splitext(os.path.basename(docx_filename))[0] images_dir = f'images/{docx_basename}/' if not os.path.exists(images_dir): os.makedirs(images_dir) # 如果目录不存在,则创建 document = Document(docx_filename) # 打开 docx 文件 markdown_content = '' image_counter = 1 # 图像编号计数器 for para in document.paragraphs: style = para.style.name # 获取段落样式名称 text = para.text.strip() # 获取段落文本并去除首尾空格 # 如果段落为空且没有任何运行对象,则跳过 if not text and not para.runs: continue # 检查段落类型:标题、列表项、普通段落 is_heading = 'Heading' in style is_title = style == 'Title' is_list = is_paragraph_list_item(para) list_level = get_paragraph_list_level(para) if is_list else 0 # 确定标题级别 if is_title: heading_level = 1 elif is_heading: heading_level = int(style.replace('Heading ', '')) + 1 else: heading_level = None # 检查段落中的每个运行,寻找并保存图像 for run in para.runs: # 查找 w:drawing 标签中的图像 drawings = run.element.findall('.//w:drawing', namespaces={'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}) for drawing in drawings: # 查找图像的关系 ID blips = drawing.findall('.//a:blip', namespaces={'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) for blip in blips: rId = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') image_part = document.part.related_parts[rId] image_bytes = image_part.blob # 获取图像数据 image_filename = f'{image_counter}.png' image_path = os.path.join(images_dir, image_filename) # 使用 PIL 保存图像为 PNG 格式 image = Image.open(BytesIO(image_bytes)) if image.mode in ('RGBA', 'P', 'LA'): image = image.convert('RGB') # 将图像转换为 RGB 模式,以兼容 PNG 格式 image.save(image_path, 'PNG') # 在 Markdown 中添加图像链接 markdown_content += f'![图片{image_counter}]({image_path})\n\n' image_counter += 1 # 根据段落类型格式化文本内容 if heading_level: markdown_content += f'{"#" * heading_level} {text}\n\n' # 使用 Markdown 语法表示标题 elif is_list: markdown_content += f'{" " * list_level}- {text}\n' # 使用缩进和 “-” 表示列表项 elif text: markdown_content += f'{text}\n\n' # 普通段落直接添加文本 # 记录调试信息 LOG.debug(f"从 docx 文件解析的 markdown 内容:\n{markdown_content}") return markdown_content if __name__ == "__main__": # 指定输入的 docx 文件路径 docx_filename = 'inputs/docx/multimodal_llm_overview.docx' docx_basename = os.path.splitext(os.path.basename(docx_filename))[0] # 生成 Markdown 内容 markdown_content = generate_markdown_from_docx(docx_filename) # 保存 Markdown 内容到文件 with open(f'{docx_basename}.md', 'w', encoding='utf-8') as f: f.write(markdown_content)