1-python 自定义模板导出文档-基础实现

发布于:2025-08-29 ⋅ 阅读:(19) ⋅ 点赞:(0)

使用 Python 根据自定义的 Word 模板和传入的 JSON 数据生成 Word 报告,是自动化文档生成的常见需求。最常用的方法是使用 python-docxdocxtpl 库。其中,docxtpl 是基于 python-docx 的模板引擎,支持 Jinja2 模板语法,非常适合根据模板和数据生成报告。

1. 新建项目、引入依赖

pip install docxtpl

2. 创建word模板

在这里插入图片描述

3. 程序导出word文档

from datetime import datetime

from docxtpl import DocxTemplate
import json


def generate_report(template_path, json_data, output_path):
    # 加载模板
    doc = DocxTemplate(template_path)

    # 加载 JSON 数据
    context = json.loads(json_data) if isinstance(json_data, str) else json_data

    # 渲染模板
    doc.render(context)

    # 保存为新文件
    doc.save(output_path)
    print(f"报告已生成:{output_path}")


# 使用示例
if __name__ == "__main__":
    template_file = "./template/test-tempalte01.docx"
    output_file = "./output/test-output01.docx"

    # 模拟 JSON 数据
    data = {
        "report_title": "测试报告",
        "report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "server_infos": [
            {"server_name": "server01", "ip": "192.168.10.5", "port": 80},
            {"server_name": "server02", "ip": "192.168.10.6", "port": 80},
            {"server_name": "server03", "ip": "192.168.10.7", "port": 80}
        ]
    }

    generate_report(template_file, data, output_file)

在这里插入图片描述

4. 程序导出PDF

  1. 安装依赖

    pip install docx2pdf
    
  2. 修改程序

    from datetime import datetime
    
    from docxtpl import DocxTemplate
    from docx2pdf import convert
    import json
    
    
    def generate_report(template_path, json_data, output_path, export_pdf=False):
        # 加载模板
        doc = DocxTemplate(template_path)
    
        # 加载 JSON 数据
        context = json.loads(json_data) if isinstance(json_data, str) else json_data
    
        # 渲染模板
        doc.render(context)
    
        # 保存为Word文件
        doc.save(output_path)
        print(f"Word报告已生成:{output_path}")
    
        # 如果需要导出PDF
        if export_pdf:
            pdf_path = output_path.replace('.docx', '.pdf')
            convert(output_path, pdf_path)
            print(f"PDF报告已生成:{pdf_path}")
            return pdf_path
    
        return output_path
    
    
    # 使用示例
    if __name__ == "__main__":
        template_file = "./template/test-tempalte01.docx"
        output_file = "./output/test-output01.docx"
    
        # 模拟 JSON 数据
        data = {
            "report_title": "测试报告",
            "report_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "server_infos": [
                {"server_name": "server01", "ip": "192.168.10.5", "port": 80},
                {"server_name": "server02", "ip": "192.168.10.6", "port": 80},
                {"server_name": "server03", "ip": "192.168.10.7", "port": 80}
            ]
        }
    
        generate_report(template_file, data, output_file, export_pdf=True)
    
    

    此时会同时导出word和pdf文件

    注意:使用docx2pdf会依赖Microsoft Office,所以在Windows环境下可以转换成pdf文件,但是在Linux环境会有问题。需要修改为LibreOffice方案。详见下面步骤。

5. 将程序导出为工具使用(windows)

  1. 安装LibreOffice

    下载地址:https://www.libreoffice.org/download/download-libreoffice/

    安装完成后,将安装目录的program目录添加到Path环境变量。

    # 验证
    soffice --version
    
  2. 修改程序

    from datetime import datetime
    from docxtpl import DocxTemplate
    import json
    import argparse
    import sys
    import os
    import subprocess
    
    def generate_report(template_path, json_data, output_path, export_pdf=False):
        # 转换路径为绝对路径并规范化
        template_path = os.path.abspath(template_path)
        output_path = os.path.abspath(output_path)
    
        print(f"执行中, 正在检查模板文件路径: {template_path}")
    
        if not os.path.exists(template_path):
            raise FileNotFoundError(f"执行异常, 模板文件不存在!请检查路径是否正确。\n路径: {template_path}")
    
        if not os.path.isfile(template_path):
            raise FileNotFoundError(f"执行异常, 路径存在,但不是一个文件(可能是目录)。\n路径: {template_path}")
    
        file_size = os.path.getsize(template_path)
        if file_size == 0:
            raise ValueError(f"执行异常, 模板文件大小为 0,可能是空文件。\n路径: {template_path}")
    
        print(f"执行中, 模板文件存在,大小: {file_size} 字节")
    
        # === 确保输出目录存在 ===
        output_dir = os.path.dirname(output_path)  # 根据输出文件的路径获取父目录(导出文件的目录)
        if output_dir and not os.path.exists(output_dir):
            print(f"执行中, 输出目录不存在,正在创建: {output_dir}")
            os.makedirs(output_dir, exist_ok=True)  # 递归创建目录
        # =============================
    
        # 加载模板
        try:
            doc = DocxTemplate(template_path)
        except Exception as e:
            raise RuntimeError(f"加载模板失败: {e}")
    
        # 解析 JSON 数据
        try:
            context = json.loads(json_data) if isinstance(json_data, str) else json_data
            # 添加当前时间(如果模板中使用了 report_time 但未提供)
            if 'report_time' not in context:
                context['report_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        except json.JSONDecodeError as e:
            raise ValueError(f"执行异常, JSON 数据解析失败: {e}")
    
        # 渲染模板
        try:
            doc.render(context)
        except Exception as e:
            raise RuntimeError(f"执行异常, 模板渲染失败,请检查上下文变量: {e}")
    
        # 保存 Word 文件
        doc.save(output_path)
        print(f"执行中, Word报告已生成:{output_path}")
    
        # 导出 PDF
        if export_pdf:
            try:
                pdf_path = output_path.replace('.docx', '.pdf')
                # convert(output_path, pdf_path)
                convert_to_pdf_with_libreoffice(output_path, pdf_path)
                print(f"执行中, PDF报告已生成:{pdf_path}")
                return pdf_path
            except Exception as e:
                raise RuntimeError(f"执行异常, 转换PDF失败: {e}")
    
        return output_path
    
    
    def convert_to_pdf_with_libreoffice(input_docx, output_pdf):
        """使用 LibreOffice 将 .docx 转为 .pdf"""
        # 确保目录存在
        pdf_dir = os.path.dirname(output_pdf)
        if pdf_dir and not os.path.exists(pdf_dir):
            os.makedirs(pdf_dir, exist_ok=True)
    
        # 调用 LibreOffice 命令
        try:
            subprocess.run([
                'soffice',
                '--headless',
                '--convert-to', 'pdf',
                '--outdir', pdf_dir,
                input_docx
            ], check=True, capture_output=True)
    
            # LibreOffice 会生成在指定目录,文件名相同,扩展名为 .pdf
            generated_pdf = os.path.join(pdf_dir, os.path.basename(input_docx).rsplit('.', 1)[0] + '.pdf')
    
            # 移动或重命名为目标路径(避免同名冲突)
            if generated_pdf != output_pdf:
                os.replace(generated_pdf, output_pdf)
    
            print(f"执行中, PDF转换成功: {output_pdf}")
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"执行异常, LibreOffice 转换失败: {e.stderr.decode()}")
        except Exception as e:
            raise RuntimeError(f"执行异常, 调用 LibreOffice 时出错: {e}")
    
    
    def main():
        parser = argparse.ArgumentParser(description="从模板和JSON数据生成Word/PDF报告")
    
        parser.add_argument(
            '--template', '-t',
            required=True,
            help='Word模板文件路径 (.docx)'
        )
        parser.add_argument(
            '--output', '-o',
            required=True,
            help='输出文件路径,例如: ./output/report.docx'
        )
        parser.add_argument(
            '--data', '-d',
            required=True,
            help='JSON格式的数据字符串,或以@开头的文件路径,如: @data.json'
        )
        parser.add_argument(
            '--pdf',
            action='store_true',
            help='是否同时导出PDF文件'
        )
    
        args = parser.parse_args()
    
        # 支持从文件读取 JSON 数据(以 @ 开头)
        if args.data.startswith('@'):
            data_file = args.data[1:]
            try:
                with open(data_file, 'r', encoding='utf-8') as f:
                    json_data = f.read()
            except FileNotFoundError:
                print(f"执行异常, 数据文件未找到: {data_file}", file=sys.stderr)
                sys.exit(1)
            except Exception as e:
                print(f"执行异常, 读取数据文件失败: {e}", file=sys.stderr)
                sys.exit(1)
        else:
            json_data = args.data
    
        # 调用生成函数
        try:
            result_path = generate_report(
                template_path=args.template,
                json_data=json_data,
                output_path=args.output,
                export_pdf=args.pdf
            )
            print(f"执行完成: {result_path}")
        except Exception as e:
            print(f"执行异常, 生成报告失败: {e}", file=sys.stderr)
            sys.exit(1)
    
    
    if __name__ == "__main__":
        main()
    
  3. 导出为可执行程序

    # 安装依赖
    python -m pip install pyinstaller
    
    # 导出,此时会生成一个dist目录,可执行文件在此目录中
    pyinstaller main.py --onefile --console --clean
    
    # 重命名(可选)
    ren main.exe xxx.exe
    
  4. 组装数据(data.json)

    {
    	"report_title": "测试报告",
    	"server_infos": [
    		{"server_name": "server01", "ip": "192.168.10.5", "port": 80},
    		{"server_name": "server02", "ip": "192.168.10.6", "port": 80},
    		{"server_name": "server03", "ip": "192.168.10.7", "port": 80}
    	]
    }
    
  5. 运行exe文件

    # windows 数据使用文件的方式
    main.exe --template "D:/universal_export/universal_export/template/test-template01.docx" --output "D:/universal_export/universal_export/output/test-template01.docx" --data @D:/universal_export/universal_export/data.json --pdf
    
    # windows 数据使用字符串的方式
    main.exe --template "D:/universal_export/universal_export/template/test-template01.docx" --output "D:/universal_export/universal_export/output/test-template01.docx" --data "{\"report_title\":\"测试报告\",\"server_infos\":[{\"server_name\":\"server01\",\"ip\":\"192.168.10.5\",\"port\":80},{\"server_name\":\"server02\",\"ip\":\"192.168.10.6\",\"port\":80},{\"server_name\":\"server03\",\"ip\":\"192.168.10.7\",\"port\":80}]}" --pdf
    

6. 将程序导出为工具使用(Centos)

  1. Centos安装python环境

    # 安装依赖
    yum -y groupinstall "Development tools"
    yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel
    yum install -y libffi-devel zlib1g-dev
    yum install zlib* -y
    
    # 下载源码包
    cd /opt
    sudo wget https://www.python.org/ftp/python/3.12.6/Python-3.12.6.tgz
    sudo tar -xzf Python-3.12.6.tgz
    
    # 创建编译安装目录
    mkdir /usr/local/python3 
    
    # 检查openssl版本
    openssl version
    # 对于python 3.12,openssl版本不能低于1.1.1
    # 添加 EPEL 仓库
    yum install -y epel-release
    # 安装较新的 OpenSSL
    yum install -y openssl11 openssl11-devel
    
    # 安装
    export CPPFLAGS="-I/usr/include/openssl11"
    export LDFLAGS="-L/usr/lib64/openssl11"
    cd Python-3.12.6
    ./configure --prefix=/usr/local/python3 --with-openssl=/usr --with-openssl-include=/usr/include/openssl11 --with-openssl-lib=/usr/lib64/openssl11 --enable-shared
    make -j$(nproc)
    sudo make install
    
    # 配置库路径
    echo '/usr/local/python3/lib' | sudo tee /etc/ld.so.conf.d/python3.conf
    sudo ldconfig
    
    # 创建软链接
    ln -s /usr/local/python3/bin/python3 /usr/local/bin/python3
    ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3
    
    # 验证
    python3 -V
    pip3 -V
    python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
    
    # 修改pip源
    cd ~
    mkdir .pip
    cd .pip
    vim pip.conf
     
    #进入后添加以下内容,保存退出.
    [global]
    index-url = https://pypi.tuna.tsinghua.edu.cn/simple
    
  2. Centos安装libreoffice

    # 在线安装
    yum install epel-release -y
    yum install libreoffice-headless -y
    
    # 验证
    soffice --version
    
  3. 将程序上传至Linux服务器后编译

    # 安装 PyInstaller
    pip3 install pyinstaller
    
    # 安装程序所需要的依赖
    pip3 install docxtpl
    
    # 打包,命名为universal_export
    python3 -m PyInstaller --onefile --clean --name=universal_export main.py
    
  4. 执行导出命令

    ./universal_export -t template/test-template01.docx -o out/test.docx -d @data.json --pdf
    

网站公告

今日签到

点亮在社区的每一天
去签到