Skill: runmanfm-bit/markdown-to-word-skill (version 1.0.0)

# Markdown转Word技能 ## 描述 使用Python的`python-docx`、`markdown`、`beautifulsoup4`库将Markdown文件转换为格式良好的Word文档。支持标题、段落、列表、表格、代码块、图片等Markdown元素的转换。 ## 激活条件 当用户需要将Markdown文件转换为Word文档时激活此技能。 ## 技能目录结构 ``` markdown-to-word-skill/ ├── SKILL.md # 技能定义文件 ├── README.md # 详细文档 ├── requirements.txt # Python依赖 ├── install.sh # 安装脚本 ├── quick_start.sh # 快速开始脚本 ├── scripts/ │ ├── md2docx.py # 主转换脚本 │ ├── md2docx_batch.py # 批量转换脚本 │ ├── md2docx_with_images.py # 带图片转换脚本 │ └── template_processor.py # 模板处理器 ├── templates/ │ ├── academic.docx # 学术论文模板 │ ├── business.docx # 商业报告模板 │ └── technical.docx # 技术文档模板 ├── examples/ │ ├── sample.md # 示例Markdown文件 │ ├── sample_output.docx # 示例输出 │ └── test_images/ # 测试图片目录 └── config/ └── styles.json # 样式配置 ``` ## 核心功能 ### 1. 基础转换 - 将Markdown文件转换为Word文档 - 支持所有基本Markdown语法 - 保持文档结构和格式 ### 2. 高级功能 - **图片处理**：自动插入图片并调整大小 - **表格转换**：将Markdown表格转换为Word表格 - **代码块**：保留代码格式和语法高亮 - **数学公式**：支持LaTeX数学公式 - **自定义样式**：应用预定义或自定义样式 ### 3. 批量处理 - 批量转换多个Markdown文件 - 支持文件夹递归处理 - 生成进度报告 ### 4. 模板系统 - 使用预定义模板（学术、商业、技术） - 自定义模板支持 - 自动应用样式 ## 依赖安装 ### Python依赖 ```bash pip install python-docx markdown beautifulsoup4 pillow ``` ### 系统依赖（可选） ```bash # Ubuntu/Debian sudo apt-get install python3-pip python3-venv # macOS brew install python3 ``` ## 使用方法 ### 1. 基本转换 ```bash python scripts/md2docx.py --input sample.md --output sample.docx ``` ### 2. 使用模板 ```bash python scripts/md2docx.py --input sample.md --output sample.docx --template templates/academic.docx ``` ### 3. 批量转换 ```bash python scripts/md2docx_batch.py --input-dir ./markdown_files --output-dir ./word_docs ``` ### 4. 带图片转换 ```bash python scripts/md2docx_with_images.py --input sample.md --output sample.docx --image-dir ./images ``` ## 脚本说明 ### md2docx.py - 主转换脚本 ```python """ Markdown转Word主转换脚本 支持： - 标题（H1-H6） - 段落和文本格式（粗体、斜体、删除线） - 列表（有序、无序） - 表格 - 代码块 - 引用块 - 水平线 - 图片 - 链接 """ # 基本用法 python md2docx.py --input input.md --output output.docx # 高级选项 python md2docx.py --input input.md --output output.docx \ --template academic.docx \ --style-title "Title" \ --style-heading1 "Heading 1" \ --style-heading2 "Heading 2" \ --style-paragraph "Normal" \ --style-code "Code" \ --style-quote "Quote" ``` ### md2docx_batch.py - 批量转换脚本 ```python """ 批量转换脚本 支持： - 批量处理文件夹中的所有Markdown文件 - 递归处理子文件夹 - 保持目录结构 - 生成转换报告 """ # 基本用法 python md2docx_batch.py --input-dir ./docs --output-dir ./word_docs # 高级选项 python md2docx_batch.py --input-dir ./docs --output-dir ./word_docs \ --recursive \ --template technical.docx \ --report report.json \ --skip-existing ``` ### md2docx_with_images.py - 带图片转换脚本 ```python """ 带图片处理的转换脚本 支持： - 自动查找并插入图片 - 图片大小调整 - 图片居中/对齐 - 图片标题 - 相对路径支持 """ # 基本用法 python md2docx_with_images.py --input article.md --output article.docx --image-dir ./images # 高级选项 python md2docx_with_images.py --input article.md --output article.docx \ --image-dir ./images \ --image-width 500 \ --image-height 300 \ --image-quality 85 \ --add-captions ``` ## 支持的Markdown元素 ### 1. 标题 ```markdown # 一级标题 ## 二级标题 ### 三级标题 ``` ### 2. 文本格式 ```markdown **粗体文本** *斜体文本* ~~删除线文本~~ ``` ### 3. 列表 ```markdown - 无序列表项1 - 无序列表项2 1. 有序列表项1 2. 有序列表项2 ``` ### 4. 表格 ```markdown | 列1 | 列2 | 列3 | |-----|-----|-----| | 数据1 | 数据2 | 数据3 | | 数据4 | 数据5 | 数据6 | ``` ### 5. 代码块 ````markdown ```python def hello_world(): print("Hello, World!") ``` ```` ### 6. 图片 ```markdown ![图片描述](image.jpg) ``` ### 7. 链接 ```markdown [链接文本](https://example.com) ``` ### 8. 引用 ```markdown > 这是一段引用文字 > 可以有多行 ``` ### 9. 水平线 ```markdown --- ``` ## 样式配置 ### 默认样式映射 ```json { "h1": "Heading 1", "h2": "Heading 2", "h3": "Heading 3", "h4": "Heading 4", "h5": "Heading 5", "h6": "Heading 6", "p": "Normal", "code": "Code", "quote": "Quote", "table": "Table Grid", "image": "Image Caption" } ``` ### 自定义样式 创建 `config/styles.json`： ```json { "styles": { "title": "自定义标题", "heading1": "自定义标题1", "heading2": "自定义标题2", "paragraph": "自定义段落", "code": "代码样式", "quote": "引用样式" }, "font": { "title": {"name": "微软雅黑", "size": 24, "bold": true}, "heading1": {"name": "微软雅黑", "size": 18, "bold": true}, "paragraph": {"name": "宋体", "size": 12} }, "colors": { "title": "2E74B5", "heading1": "2E74B5", "code_background": "F2F2F2" } } ``` ## 模板系统 ### 预定义模板 1. **academic.docx** - 学术论文模板 - 符合学术论文格式 - 包含摘要、关键词、参考文献 - 支持章节编号 2. **business.docx** - 商业报告模板 - 专业商业风格 - 包含公司Logo、页眉页脚 - 图表和表格样式 3. **technical.docx** - 技术文档模板 - 代码友好格式 - 技术图表支持 - API文档样式 ### 创建自定义模板 1. 在Word中创建模板文档 2. 定义样式（标题、段落、代码等） 3. 保存为 `.docx` 文件到 `templates/` 目录 4. 使用 `--template` 参数指定 ## 安装脚本 ### install.sh ```bash #!/bin/bash # Markdown转Word技能安装脚本 echo "安装Markdown转Word技能..." # 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装Python依赖 pip install python-docx markdown beautifulsoup4 pillow # 创建必要目录 mkdir -p templates examples test_images config echo "✅ 安装完成！" echo "激活虚拟环境：source venv/bin/activate" echo "运行测试：python scripts/md2docx.py --input examples/sample.md --output examples/sample_output.docx" ``` ### quick_start.sh ```bash #!/bin/bash # 快速开始脚本 echo "Markdown转Word技能快速开始..." # 检查虚拟环境 if [ ! -d "venv" ]; then echo "创建虚拟环境..." python3 -m venv venv fi # 激活虚拟环境 source venv/bin/activate # 安装依赖 pip install -q python-docx markdown beautifulsoup4 pillow # 运行示例 echo "运行示例转换..." python scripts/md2docx.py --input examples/sample.md --output examples/sample_output.docx if [ $? -eq 0 ]; then echo "✅ 示例转换成功！" echo "输出文件：examples/sample_output.docx" else echo "❌ 转换失败，请检查错误信息" fi ``` ## 示例文件 ### sample.md ```markdown # 示例文档 这是一个Markdown转Word的示例文档。 ## 章节1：介绍 这是第一章节的内容。**粗体文本**和*斜体文本*都可以正常转换。 ### 子章节1.1 这是一个子章节。 ## 章节2：列表示例 ### 无序列表 - 项目1 - 项目2 - 子项目2.1 - 子项目2.2 - 项目3 ### 有序列表 1. 第一步 2. 第二步 3. 第三步 ## 章节3：表格示例 | 姓名 | 年龄 | 职业 | |------|------|------| | 张三 | 25 | 工程师 | | 李四 | 30 | 设计师 | | 王五 | 28 | 产品经理 | ## 章节4：代码示例 ```python def calculate_sum(a, b): """计算两个数的和""" return a + b result = calculate_sum(10, 20) print(f"结果: {result}") ``` ## 章节5：引用示例 > 这是引用内容 > 可以有多行 > > — 引用来源 ## 章节6：图片示例 ![示例图片](test_images/sample.jpg) --- 文档结束。 ``` ## 故障排除 ### 常见问题 #### 1. 缺少依赖 ``` ModuleNotFoundError: No module named 'docx' ``` **解决方案**： ```bash pip install python-docx ``` #### 2. 图片路径错误 ``` FileNotFoundError: [Errno 2] No such file or directory: 'image.jpg' ``` **解决方案**： - 使用绝对路径 - 使用 `--image-dir` 参数指定图片目录 - 确保图片文件存在 #### 3. 样式不存在 ``` KeyError: 'Heading 1' ``` **解决方案**： - 检查模板文件中的样式名称 - 使用 `--list-styles` 查看可用样式 - 创建自定义样式配置 #### 4. 编码问题 ``` UnicodeDecodeError: 'utf-8' codec can't decode byte... ``` **解决方案**： ```bash python md2docx.py --input input.md --output output.docx --encoding utf-8-sig ``` ### 调试模式 ```bash python md2docx.py --input input.md --output output.docx --debug ``` ## 在OpenClaw中的使用 ### 作为独立技能 ```bash # 转换单个文件 exec("cd /path/to/markdown-to-word-skill && python scripts/md2docx.py --input document.md --output document.docx") # 批量转换 exec("cd /path/to/markdown-to-word-skill && python scripts/md2docx_batch.py --input-dir ./docs --output-dir ./word_docs") ``` ### 在技能中集成 ```python def convert_markdown_to_word(md_file, docx_file, template=None): """转换Markdown到Word""" import subprocess cmd = ["python", "scripts/md2docx.py", "--input", md_file, "--output", docx_file] if template: cmd.extend(["--template", template]) result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: return {"success": True, "output": docx_file} else: return {"success": False, "error": result.stderr} ``` ## 性能优化 ### 1. 批量处理优化 - 使用多进程处理大文件 - 内存优化 - 缓存已解析的模板 ### 2. 图片处理优化 - 图片压缩 - 缩略图生成 - 并行处理 ### 3. 样式缓存 - 缓存样式定义 - 减少重复操作 - 提高转换速度 ## 扩展功能 ### 1. 自定义转换器 ```python from md2docx import MarkdownToDocx class CustomConverter(MarkdownToDocx): def convert_custom_element(self, element): """处理自定义Markdown元素""" pass ``` ### 2. 插件系统 ```python # 创建插件 class ImagePlugin: def process(self, converter, element): """处理图片元素""" pass # 注册插件 converter.register_plugin(ImagePlugin()) ``` ### 3. Web API ```python from flask import Flask, request, send_file app = Flask(__name__) @app.route('/convert', methods=['POST']) def convert(): md_content = request.form['markdown'] # 转换逻辑 return send_file('output.docx') ``` ## 更新日志 ### v1.0.0 (2026-03-02) - 初始版本发布 - 支持基本Markdown元素转换 - 添加图片处理功能 - 实现批量转换 - 添加模板系统 ## 贡献指南 1. Fork项目 2. 创建功能分支 3. 提交更改 4. 推送到分支 5. 创建Pull Request ## 许可证 MIT License ## 联系方式 - 问题反馈：创建GitHub Issue - 功能建议：提交Pull Request - 文档改进：编辑README.md --- **技能创建时间**：2026-03-02 10:12 GMT+8 **创建者**：OpenClaw AI Assistant **版本**：1.0.0

path: install.sh
content: # 解码base64图片
echo "🖼️  解码测试图片..."
base64 -d test_images/sample.jpg.base64 > test_images/sample.jpg
rm test_images/sample.jpg.base64

# 创建配置文件
echo "⚙️  创建配置文件..."
cat > config/styles.json << 'EOF'
{
  "styles": {
    "title": "标题",
    "heading1": "标题 1",
    "heading2": "标题 2",
    "heading3": "标题 3",
    "paragraph": "正文",
    "code": "代码",
    "quote": "引用",
    "table": "表格",
    "image": "图片标题"
  },
  "font": {
    "title": {
      "name": "微软雅黑",
      "size": 24,
      "bold": true,
      "color": "2E74B5"
    },
    "heading1": {
      "name": "微软雅黑",
      "size": 18,
      "bold": true,
      "color": "2E74B5"
    },
    "heading2": {
      "name": "微软雅黑",
      "size": 16,
      "bold": true,
      "color": "4F81BD"
    },
    "paragraph": {
      "name": "宋体",
      "size": 12
    },
    "code": {
      "name": "Consolas",
      "size": 11,
      "color": "000000"
    }
  },
  "colors": {
    "title": "2E74B5",
    "heading1": "2E74B5",
    "heading2": "4F81BD",
    "code_background": "F2F2F2",
    "quote_border": "CCCCCC"
  }
}
EOF

# 创建模板文件
echo "📄 创建模板文件..."
cat > templates/academic.docx.info << 'EOF'
学术论文模板
包含：标题页、摘要、关键词、章节编号、参考文献
EOF

cat > templates/business.docx.info << 'EOF'
商业报告模板
包含：公司Logo、页眉页脚、商业图表样式、专业配色
EOF

cat > templates/technical.docx.info << 'EOF'
技术文档模板
包含：代码样式、技术图表、API文档格式、版本历史
EOF

# 设置脚本权限
echo "🔧 设置脚本权限..."
chmod +x scripts/*.py
chmod +x install.sh

# 创建快速开始脚本
cat > quick_start.sh << 'EOF'
#!/bin/bash
# 快速开始脚本

echo "========================================="
echo "  Markdown转Word技能快速开始"
echo "========================================="

# 检查虚拟环境
if [ ! -d "venv" ]; then
    echo "❌ 虚拟环境不存在，请先运行 ./install.sh"
    exit 1
fi

# 激活虚拟环境
source venv/bin/activate

# 运行示例转换
echo "🚀 运行示例转换..."
python scripts/md2docx.py \
  --input examples/sample.md \
  --output examples/sample_output.docx \
  --debug

if [ $? -eq 0 ]; then
    echo ""
    echo "✅ 示例转换成功！"
    echo ""
    echo "📁 输出文件: examples/sample_output.docx"
    echo ""
    echo "🎯 使用示例:"
    echo "  1. 转换单个文件:"
    echo "     python scripts/md2docx.py --input your_file.md --output your_file.docx"
    echo ""
    echo "  2. 批量转换:"
    echo "     python scripts/md2docx_batch.py --input-dir ./docs --output-dir ./word_docs"
    echo ""
    echo "  3. 带图片转换:"
    echo "     python scripts/md2docx_with_images.py --input article.md --output article.docx --image-dir ./images"
    echo ""
    echo "📚 更多信息请查看 README.md"
else
    echo "❌ 示例转换失败"
    exit 1
fi
EOF

chmod +x quick_start.sh

echo ""
echo "========================================="
echo "✅ 安装完成！"
echo "========================================="
echo ""
echo "🎯 下一步:"
echo "  1. 激活虚拟环境: source venv/bin/activate"
echo "  2. 运行快速开始: ./quick_start.sh"
echo "  3. 查看文档: cat README.md"
echo ""
echo "📚 技能目录结构:"
echo "  📁 markdown-to-word-skill/"
echo "  ├── 📄 SKILL.md          # 技能定义"
echo "  ├── 📄 README.md         # 详细文档"
echo "  ├── 📁 scripts/          # Python脚本"
echo "  │   ├── md2docx.py       # 主转换脚本"
echo "  │   ├── md2docx_batch.py # 批量转换"
echo "  │   └── md2docx_with_images.py # 带图片转换"
echo "  ├── 📁 templates/        # Word模板"
echo "  ├── 📁 examples/         # 示例文件"
echo "  ├── 📁 test_images/      # 测试图片"
echo "  └── 📁 config/           # 配置文件"
echo ""
echo "🚀 开始使用: ./quick_start.sh"

path: scripts/md2docx.py
content:         if not os.path.exists(input_file):
            print(f"❌ 输入文件不存在: {input_file}")
            return False
        
        # 读取Markdown文件
        with open(input_file, 'r', encoding='utf-8') as f:
            markdown_text = f.read()
        
        if debug:
            print(f"📄 读取Markdown文件: {input_file} ({len(markdown_text)} 字符)")
        
        # 创建转换器
        converter = MarkdownToDocxConverter(template, debug)
        
        # 转换
        converter.convert(markdown_text, image_dir)
        
        # 保存
        converter.save(output_file)
        
        print(f"✅ 转换成功: {input_file} → {output_file}")
        return True
        
    except Exception as e:
        print(f"❌ 转换失败: {e}")
        if debug:
            import traceback
            traceback.print_exc()
        return False


def list_available_styles(template_path: Optional[str] = None):
    """列出可用样式"""
    try:
        if template_path and os.path.exists(template_path):
            doc = Document(template_path)
            print(f"📋 模板中的样式: {template_path}")
        else:
            doc = Document()
            print("📋 默认样式:")
        
        for style in doc.styles:
            print(f"  - {style.name} ({style.type})")
    
    except Exception as e:
        print(f"❌ 列出样式失败: {e}")


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='Markdown转Word文档转换器')
    parser.add_argument('--input', '-i', required=True, help='输入Markdown文件路径')
    parser.add_argument('--output', '-o', required=True, help='输出Word文件路径')
    parser.add_argument('--template', '-t', help='Word模板文件路径')
    parser.add_argument('--image-dir', '-d', help='图片目录路径')
    parser.add_argument('--list-styles', action='store_true', help='列出可用样式')
    parser.add_argument('--debug', action='store_true', help='启用调试模式')
    parser.add_argument('--encoding', default='utf-8', help='文件编码（默认: utf-8）')
    
    args = parser.parse_args()
    
    if args.list_styles:
        list_available_styles(args.template)
        return
    
    # 确保输出目录存在
    output_dir = os.path.dirname(args.output)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir, exist_ok=True)
    
    # 执行转换
    success = convert_markdown_to_docx(
        input_file=args.input,
        output_file=args.output,
        template=args.template,
        image_dir=args.image_dir,
        debug=args.debug
    )
    
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()

path: scripts/md2docx_batch.py
content: #!/usr/bin/env python3
"""
批量Markdown转Word转换器
"""

import argparse
import os
import sys
import json
import time
from pathlib import Path
from typing import List, Dict, Any
from datetime import datetime

# 导入主转换器
from md2docx import convert_markdown_to_docx


class BatchConverter:
    """批量转换器"""
    
    def __init__(self, debug: bool = False):
        self.debug = debug
        self.report = {
            'start_time': None,
            'end_time': None,
            'total_files': 0,
            'successful': 0,
            'failed': 0,
            'files': []
        }
    
    def convert_directory(self, input_dir: str, output_dir: str, 
                         template: Optional[str] = None,
                         image_dir: Optional[str] = None,
                         recursive: bool = False,
                         skip_existing: bool = False) -> Dict[str, Any]:
        """
        转换目录中的所有Markdown文件
        
        Args:
            input_dir: 输入目录
            output_dir: 输出目录
            template: 模板文件路径
            image_dir: 图片目录路径
            recursive: 是否递归处理子目录
            skip_existing: 是否跳过已存在的输出文件
            
        Returns:
            Dict: 转换报告
        """
        self.report['start_time'] = datetime.now().isoformat()
        
        # 确保输出目录存在
        os.makedirs(output_dir, exist_ok=True)
        
        # 查找Markdown文件
        markdown_files = self._find_markdown_files(input_dir, recursive)
        self.report['total_files'] = len(markdown_files)
        
        print(f"📁 找到 {len(markdown_files)} 个Markdown文件")
        
        # 转换每个文件
        for i, input_file in enumerate(markdown_files, 1):
            print(f"\n[{i}/{len(markdown_files)}] 处理: {input_file}")
            
            # 确定输出文件路径
            output_file = self._get_output_path(input_file, input_dir, output_dir)
            
            # 检查是否跳过已存在的文件
            if skip_existing and os.path.exists(output_file):
                print(f"  ⏭️  跳过已存在的文件: {output_file}")
                self.report['files'].append({
                    'input': input_file,
                    'output': output_file,
                    'status': 'skipped',
                    'reason': '文件已存在'
                })
                self.report['successful'] += 1
                continue
            
            # 执行转换
            start_time = time.time()
            success = convert_markdown_to_docx(
                input_file=input_file,
                output_file=output_file,
                template=template,
                image_dir=image_dir,
                debug=self.debug
            )
            elapsed_time = time.time() - start_time
            
            # 记录结果
            file_result = {
                'input': input_file,
                'output': output_file,
                'status': 'success' if success else 'failed',
                'elapsed_time': round(elapsed_time, 2)
            }
            
            if success:
                self.report['successful'] += 1
                print(f"  ✅ 转换成功 ({elapsed_time:.2f}秒)")
            else:
                self.report['failed'] += 1
                file_result['reason'] = '转换失败'
                print(f"  ❌ 转换失败")
            
            self.report['files'].append(file_result)
        
        self.report['end_time'] = datetime.now().isoformat()
        
        return self.report
    
    def _find_markdown_files(self, directory: str, recursive: bool = False) -> List[str]:
        """查找Markdown文件"""
        markdown_extensions = ['.md', '.markdown', '.mdown', '.mkd', '.mkdn']
        files = []
        
        if recursive:
            # 递归查找
            for root, _, filenames in os.walk(directory):
                for filename in filenames:
                    if any(filename.lower().endswith(ext) for ext in markdown_extensions):
                        files.append(os.path.join(root, filename))
        else:
            # 仅当前目录
            for filename in os.listdir(directory):
                filepath = os.path.join(directory, filename)
                if os.path.isfile(filepath):
                    if any(filename.lower().endswith(ext) for ext in markdown_extensions):
                        files.append(filepath)
        
        # 按文件名排序
        files.sort()
        
        return files
    
    def _get_output_path(self, input_file: str, input_dir: str, output_dir: str) -> str:
        """获取输出文件路径"""
        # 获取相对路径
        if input_dir:
            rel_path = os.path.relpath(input_file, input_dir)
        else:
            rel_path = os.path.basename(input_file)
        
        # 更改扩展名为 .docx
        base_name = os.path.splitext(rel_path)[0]
        output_file = os.path.join(output_dir, f"{base_name}.docx")
        
        # 确保目录存在
        os.makedirs(os.path.dirname(output_file), exist_ok=True)
        
        return output_file
    
    def save_report(self, report_path: str):
        """保存转换报告"""
        try:
            with open(report_path, 'w', encoding='utf-8') as f:
                json.dump(self.report, f, ensure_ascii=False, indent=2)
            print(f"📊 报告已保存: {report_path}")
        except Exception as e:
            print(f"❌ 保存报告失败: {e}")
    
    def print_summary(self):
        """打印转换摘要"""
        print("\n" + "="*60)
        print("📈 转换摘要")
        print("="*60)
        
        total = self.report['total_files']
        successful = self.report['successful']
        failed = self.report['failed']
        
        print(f"总文件数: {total}")
        print(f"成功: {successful} ({successful/total*100:.1f}%)")
        print(f"失败: {failed} ({failed/total*100:.1f}%)")
        
        if 'start_time' in self.report and 'end_time' in self.report:
            start = datetime.fromisoformat(self.report['start_time'])
            end = datetime.fromisoformat(self.report['end_time'])
            duration = (end - start).total_seconds()
            print(f"总耗时: {duration:.2f}秒")
        
        # 显示失败的文件
        if failed > 0:
            print("\n❌ 失败的文件:")
            for file_info in self.report['files']:
                if file_info['status'] == 'failed':
                    print(f"  - {file_info['input']}")


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='批量Markdown转Word转换器')
    parser.add_argument('--input-dir', '-i', required=True, help='输入目录路径')
    parser.add_argument('--output-dir', '-o', required=True, help='输出目录路径')
    parser.add_argument('--template', '-t', help='Word模板文件路径')
    parser.add_argument('--image-dir', '-d', help='图片目录路径')
    parser.add_argument('--recursive', '-r', action='store_true', help='递归处理子目录')
    parser.add_argument('--skip-existing', '-s', action='store_true', help='跳过已存在的输出文件')
    parser.add_argument('--report', help='保存转换报告到JSON文件')
    parser.add_argument('--debug', action='store_true', help='启用调试模式')
    
    args = parser.parse_args()
    
    # 检查输入目录
    if not os.path.exists(args.input_dir):
        print(f"❌ 输入目录不存在: {args.input_dir}")
        sys.exit(1)
    
    # 创建批量转换器
    converter = BatchConverter(debug=args.debug)
    
    # 执行批量转换
    report = converter.convert_directory(
        input_dir=args.input_dir,
        output_dir=args.output_dir,
        template=args.template,
        image_dir=args.image_dir,
        recursive=args.recursive,
        skip_existing=args.skip_existing
    )
    
    # 打印摘要
    converter.print_summary()
    
    # 保存报告
    if args.report:
        converter.save_report(args.report)
    
    # 根据结果退出
    if report['failed'] > 0:
        print(f"\n⚠️  有 {report['failed']} 个文件转换失败")
        sys.exit(1)
    else:
        print(f"\n✅ 所有文件转换成功！")
        sys.exit(0)


if __name__ == '__main__':
    main()

path: scripts/md2docx_with_images.py
content: #!/usr/bin/env python3
"""
带图片处理的Markdown转Word转换器
"""

import argparse
import os
import sys
import re
from pathlib import Path
from typing import Optional, List, Tuple
from PIL import Image

# 导入主转换器
from md2docx import convert_markdown_to_docx, MarkdownToDocxConverter


class ImageProcessor:
    """图片处理器"""
    
    def __init__(self, image_dir: str, debug: bool = False):
        self.image_dir = image_dir
        self.debug = debug
        self.image_cache = {}
        
        # 支持的图片格式
        self.supported_formats = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff']
    
    def find_image(self, image_src: str) -> Optional[str]:
        """
        查找图片文件
        
        Args:
            image_src: 图片路径（相对或绝对）
            
        Returns:
            Optional[str]: 找到的图片路径，或None
        """
        # 如果是绝对路径，直接检查
        if os.path.isabs(image_src):
            if os.path.exists(image_src):
                return image_src
            return None
        
        # 检查缓存
        if image_src in self.image_cache:
            return self.image_cache[image_src]
        
        # 可能的查找位置
        search_paths = []
        
        # 1. 相对于图片目录
        if self.image_dir:
            search_paths.append(os.path.join(self.image_dir, image_src))
        
        # 2. 相对于当前目录
        search_paths.append(image_src)
        
        # 3. 在图片目录中搜索文件名
        if self.image_dir:
            filename = os.path.basename(image_src)
            for root, _, files in os.walk(self.image_dir):
                if filename in files:
                    search_paths.append(os.path.join(root, filename))
        
        # 尝试每个路径
        for path in search_paths:
            if os.path.exists(path):
                self.image_cache[image_src] = path
                if self.debug:
                    print(f"🔍 找到图片: {image_src} → {path}")
                return path
        
        if self.debug:
            print(f"⚠️  未找到图片: {image_src}")
        return None
    
    def process_image(self, image_path: str, max_width: Optional[int] = None, 
                     max_height: Optional[int] = None, quality: int = 85) -> Tuple[str, dict]:
        """
        处理图片（调整大小、压缩等）
        
        Args:
            image_path: 图片路径
            max_width: 最大宽度（像素）
            max_height: 最大高度（像素）
            quality: JPEG质量（1-100）
            
        Returns:
            Tuple[str, dict]: (处理后的图片路径, 图片信息)
        """
        if not os.path.exists(image_path):
            return image_path, {'error': '文件不存在'}
        
        try:
            # 打开图片
            with Image.open(image_path) as img:
                original_size = img.size
                format_name = img.format
                mode = img.mode
                
                info = {
                    'path': image_path,
                    'original_size': original_size,
                    'format': format_name,
                    'mode': mode,
                    'file_size': os.path.getsize(image_path)
                }
                
                # 检查是否需要调整大小
                needs_resize = False
                new_size = original_size
                
                if max_width and original_size[0] > max_width:
                    ratio = max_width / original_size[0]
                    new_width = max_width
                    new_height = int(original_size[1] * ratio)
                    new_size = (new_width, new_height)
                    needs_resize = True
                
                if max_height and new_size[1] > max_height:
                    ratio = max_height / new_size[1]
                    new_width = int(new_size[0] * ratio)
                    new_height = max_height
                    new_size = (new_width, new_height)
                    needs_resize = True
                
                if needs_resize:
                    # 调整大小
                    img_resized = img.resize(new_size, Image.Resampling.LANCZOS)
                    
                    # 保存调整后的图片
                    output_path = self._get_temp_image_path(image_path)
                    img_resized.save(output_path, quality=quality)
                    
                    info['resized_size'] = new_size
                    info['resized_path'] = output_path
                    info['resized_file_size'] = os.path.getsize(output_path)
                    
                    if self.debug:
                        print(f"🖼️  图片调整大小: {original_size} → {new_size}")
                        print(f"    文件大小: {info['file_size']:,} → {info['resized_file_size']:,} 字节")
                    
                    return output_path, info
                else:
                    # 不需要调整大小
                    if self.debug:
                        print(f"🖼️  图片保持原大小: {original_size}")
                    
                    return image_path, info
        
        except Exception as e:
            if self.debug:
                print(f"❌ 图片处理失败: {e}")
            return image_path, {'error': str(e)}
    
    def _get_temp_image_path(self, original_path: str) -> str:
        """获取临时图片路径"""
        import tempfile
        import hashlib
        
        # 生成唯一文件名
        file_hash = hashlib.md5(original_path.encode()).hexdigest()[:8]
        ext = os.path.splitext(original_path)[1]
        
        temp_dir = tempfile.gettempdir()
        temp_filename = f"md2docx_{file_hash}{ext}"
        
        return os.path.join(temp_dir, temp_filename)
    
    def cleanup_temp_files(self):
        """清理临时文件"""
        import tempfile
        import glob
        
        temp_dir = tempfile.gettempdir()
        temp_pattern = os.path.join(temp_dir, "md2docx_*")
        
        temp_files = glob.glob(temp_pattern)
        for temp_file in temp_files:
            try:
                os.remove(temp_file)
                if self.debug:
                    print(f"🧹 清理临时文件: {temp_file}")
            except:
                pass


class EnhancedMarkdownToDocxConverter(MarkdownToDocxConverter):
    """增强的Markdown转Word转换器（带图片处理）"""
    
    def __init__(self, template_path: Optional[str] = None, 
                 image_processor: Optional[ImageProcessor] = None,
                 debug: bool = False):
        super().__init__(template_path, debug)
        self.image_processor = image_processor
        self.image_info = []
    
    def _add_image(self, element, image_dir: Optional[str] = None):
        """添加图片（增强版）"""
        src = element.get('src', '')
        alt = element.get('alt', '图片')
        
        if not src:
            return
        
        # 查找图片
        image_path = None
        if self.image_processor:
            image_path = self.image_processor.find_image(src)
        
        # 如果没找到，尝试其他方法
        if not image_path and image_dir:
            # 相对于图片目录
            potential_path = os.path.join(image_dir, src)
            if os.path.exists(potential_path):
                image_path = potential_path
        
        if not image_path:
            # 直接使用路径
            image_path = src
        
        # 检查图片文件是否存在
        if not os.path.exists(image_path):
            if self.debug:
                print(f"⚠️  图片文件不存在: {image_path}")
            # 添加替代文本
            p = self.doc.add_paragraph(f"[图片: {alt}]")
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER
            return
        
        try:
            # 处理图片（调整大小等）
            processed_path = image_path
            image_data = {'original_path': image_path}
            
            if self.image_processor:
                processed_path, image_data = self.image_processor.process_image(
                    image_path, 
                    max_width=1200,  # 最大宽度1200像素
                    max_height=800,  # 最大高度800像素
                    quality=85
                )
                self.image_info.append(image_data)
            
            # 添加图片到文档
            from docx.shared import Inches
            self.doc.add_picture(processed_path, width=Inches(5.0))
            
            # 添加图片标题
            if alt:
                p = self.doc.add_paragraph(f"图: {alt}", style=self.style_map['img'])
                p.alignment = WD_ALIGN_PARAGRAPH.CENTER
            
            if self.debug:
                print(f"🖼️  添加图片: {alt}")
                if 'resized_size' in image_data:
                    print(f"    大小: {image_data['original_size']} → {image_data['resized_size']}")
        
        except Exception as e:
            if self.debug:
                print(f"❌ 添加图片失败: {e}")
            # 添加替代文本
            p = self.doc.add_paragraph(f"[图片加载失败: {alt}]")
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER


def convert_with_images(input_file: str, output_file: str, 
                       template: Optional[str] = None,
                       image_dir: Optional[str] = None,
                       max_width: Optional[int] = None,
                       max_height: Optional[int] = None,
                       image_quality: int = 85,
                       add_captions: bool = True,
                       debug: bool = False) -> bool:
    """
    带图片处理的Markdown转Word转换
    
    Args:
        input_file: 输入Markdown文件路径
        output_file: 输出Word文件路径
        template: 模板文件路径
        image_dir: 图片目录路径
        max_width: 图片最大宽度
        max_height: 图片最大高度
        image_quality: 图片质量（1-100）
        add_captions: 是否添加图片标题
        debug: 是否启用调试模式
        
    Returns:
        bool: 转换是否成功
    """
    try:
        # 检查输入文件
        if not os.path.exists(input_file):
            print(f"❌ 输入文件不存在: {input_file}")
            return False
        
        # 读取Markdown文件
        with open(input_file, 'r', encoding='utf-8') as f:
            markdown_text = f.read()
        
        if debug:
            print(f"📄 读取Markdown文件: {input_file} ({len(markdown_text)} 字符)")
        
        # 创建图片处理器
        image_processor = None
        if image_dir:
            image_processor = ImageProcessor(image_dir, debug)
            if debug:
                print(f"🖼️  使用图片目录: {image_dir}")
        
        # 创建增强转换器
        converter = EnhancedMarkdownToDocxConverter(template, image_processor, debug)
        
        # 转换
        converter.convert(markdown_text, image_dir)
        
        # 保存
        converter.save(output_file)
        
        # 清理临时文件
        if image_processor:
            image_processor.cleanup_temp_files()
        
        # 打印图片处理摘要
        if converter.image_info:
            print(f"\n📊 图片处理摘要:")
            total_original = sum(info.get('file_size', 0) for info in converter.image_info)
            total_resized = sum(info.get('resized_file_size', 0) for info in converter.image_info 
                              if 'resized_file_size' in info)
            
            if total_resized > 0:
                reduction = (total_original - total_resized) / total_original * 100
                print(f"  处理图片: {len(converter.image_info)} 张")
                print(f"  原始大小: {total_original:,} 字节")
                print(f"  处理后大小: {total_resized:,} 字节")
                print(f"  节省空间: {reduction:.1f}%")
        
        print(f"✅ 转换成功: {input_file} → {output_file}")
        return True
        
    except Exception as e:
        print(f"❌ 转换失败: {e}")
        if debug:
            import traceback
            traceback.print_exc()
        return False


def extract_image_references(markdown_text: str) -> List[str]:
    """提取Markdown中的图片引用"""
    # 匹配Markdown图片语法 ![alt](src)
    pattern = r'!\[.*?\]\((.*?)\)'
    matches = re.findall(pattern, markdown_text)
    
    # 匹配HTML img标签
    html_pattern = r'<img.*?src=["\'](.*?)["\'].*?>'
    html_matches = re.findall(html_pattern, markdown_text, re.IGNORECASE)
    
    return list(set(matches + html_matches))


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='带图片处理的Markdown转Word转换器')
    parser.add_argument('--input', '-i', required=True, help='输入Markdown文件路径')
    parser.add_argument('--output', '-o', required=True, help='输出Word文件路径')
    parser.add_argument('--template', '-t', help='Word模板文件路径')
    parser.add_argument('--image-dir', '-d', help='图片目录路径')
    parser.add_argument('--image-width', type=int, help='图片最大宽度（像素）')
    parser.add_argument('--image-height', type=int, help='图片最大高度（像素）')
    parser.add_argument('--image-quality', type=int, default=85, help='图片质量（1-100，默认85）')
    parser.add_argument('--add-captions', action='store_true', help='添加图片标题')
    parser.add_argument('--list-images', action='store_true', help='列出图片引用')
    parser.add_argument('--debug', action='store_true', help='启用调试模式')
    
    args = parser.parse_args()
    
    if args.list_images:
        # 列出图片引用
        try:
            with open(args.input, 'r', encoding='utf-8') as f:
                markdown_text = f.read()
            
            images = extract_image_references(markdown_text)
            print(f"📷 找到 {len(images)} 个图片引用:")
            for i, img in enumerate(images, 1):
                print(f"  {i}. {img}")
            
            # 检查图片是否存在
            if args.image_dir:
                print(f"\n🔍 在目录 {args.image_dir} 中查找图片:")
                for i, img in enumerate(images, 1):
                    potential_path = os.path.join(args.image_dir, img)
                    if os.path.exists(potential_path):
                        print(f"  {i}. ✅ {img} → {potential_path}")
                    else:
                        print(f"  {i}. ❌ {img} (未找到)")
            
            return
        except Exception as e:
            print(f"❌ 列出图片失败: {e}")
            sys.exit(1)
    
    # 确保输出目录存在
    output_dir = os.path.dirname(args.output)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir, exist_ok=True)
    
    # 执行转换
    success = convert_with_images(
        input_file=args.input,
        output_file=args.output,
        template=args.template,
        image_dir=args.image_dir,
        max_width=args.image_width,
        max_height=args.image_height,
        image_quality=args.image_quality,
        add_captions=args.add_captions,
        debug=args.debug
    )
    
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()

path: scripts/md2docx_with_mermaid.py
content:     parser = argparse.ArgumentParser(description='支持Mermaid的Markdown转Word转换器')
    parser.add_argument('--input', '-i', required=True, help='输入Markdown文件路径')
    parser.add_argument('--output', '-o', required=True, help='输出Word文件路径')
    parser.add_argument('--template', '-t', help='Word模板文件路径')
    parser.add_argument('--image-dir', '-d', help='图片目录路径')
    parser.add_argument('--debug', action='store_true', help='启用调试模式')
    
    args = parser.parse_args()
    
    # 确保输出目录存在
    output_dir = os.path.dirname(args.output)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir, exist_ok=True)
    
    # 执行转换
    success = convert_markdown_with_mermaid(
        input_file=args.input,
        output_file=args.output,
        template=args.template,
        image_dir=args.image_dir,
        debug=args.debug
    )
    
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)