Linux上使用python處理docx轉pdf教程

信2005-2刘海涛發表於2024-04-02

  今天在使用flask將生成好的docx文件轉化為pdf的過程中,遇到了一些問題,本來在windows上轉化的好好的,但是到了Linux上卻是直接報錯
顯示ModuleNotFoundError: No module named 'win32com'

  很明顯他說的是在Linux系統下並沒有win32com這個模組,所以透過百度發現python使用pdf2docx這個包將docx轉化為pdf的使用環境必須為windows,
那麼在Linux上我們應該使用什麼來進行文件的轉化呢?百度後發現了一個解決方法:傳送門

  但是上述程式碼的時間應該是有段時間了,pywpsrpc的程式碼已經更新了,目前的最新使用程式碼可以去github上訪問官方文件
需要注意的是需要安裝好qt5-default, 我使用的Linux版本是Ubuntu23.04,在安裝的時候會報錯,所以百度後提供了別人的解決方案粘在下面。

  qt5-default安裝失敗解決方案

  以下是程式碼解決方案,程式碼來自於官方,我僅僅呼叫了其中的函式:


#!/usr/bin/python3

#**
# * Copyright (c) 2020 Weitian Leung
# *
# * This file is part of pywpsrpc.
# *
# * This file is distributed under the MIT License.
# * See the LICENSE file for details.
# *
#*

import os
import sys
import argparse

from pywpsrpc.rpcwpsapi import (createWpsRpcInstance, wpsapi)
from pywpsrpc.common import (S_OK, QtApp)


formats = {
    "doc": wpsapi.wdFormatDocument,
    "docx": wpsapi.wdFormatXMLDocument,
    "rtf": wpsapi.wdFormatRTF,
    "html": wpsapi.wdFormatHTML,
    "pdf": wpsapi.wdFormatPDF,
    "xml": wpsapi.wdFormatXML,
}


class ConvertException(Exception):

    def __init__(self, text, hr):
        self.text = text
        self.hr = hr

    def __str__(self):
        return """Convert failed:
Details: {}
ErrCode: {}
""".format(self.text, hex(self.hr & 0xFFFFFFFF))


def convert_to(paths, format, abort_on_fails=False):
    hr, rpc = createWpsRpcInstance()
    if hr != S_OK:
        raise ConvertException("Can't create the rpc instance", hr)

    hr, app = rpc.getWpsApplication()
    if hr != S_OK:
        raise ConvertException("Can't get the application", hr)

    # we don't need the gui
    app.Visible = False

    docs = app.Documents

    def _handle_result(hr):
        if abort_on_fails and hr != S_OK:
            raise ConvertException("convert_file failed", hr)

    for path in paths:
        abs_path = os.path.realpath(path)
        if os.path.isdir(abs_path):
            files = [(os.path.join(abs_path, f)) for f in os.listdir(abs_path)]
            for file in files:
                hr = convert_file(file, docs, format)
                _handle_result(hr)
        else:
            hr = convert_file(abs_path, docs, format)
            _handle_result(hr)

    app.Quit()


def convert_file(file, docs, format):
    hr, doc = docs.Open(file, ReadOnly=True)
    if hr != S_OK:
        return hr

    out_dir = os.path.dirname(os.path.realpath(file)) + "/out"
    os.makedirs(out_dir, exist_ok=True)

    # you have to handle if the new_file already exists
    new_file = out_dir + "/" + os.path.splitext(os.path.basename(file))[0] + "." + format
    ret = doc.SaveAs2(new_file, FileFormat=formats[format])

    # always close the doc
    doc.Close(wpsapi.wdDoNotSaveChanges)

    return ret


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--format", "-f",
                        required=True,
                        metavar="<DOC_TYPE>",
                        choices=["doc", "docx", "rtf", "html", "pdf", "xml"],
                        help="convert to <DOC_TYPE>,")

    parser.add_argument("--abort", "-a",
                        action="store_true",
                        help="abort if one convert fails")

    parser.add_argument("path",
                        metavar="<path>",
                        nargs='+',
                        help="the <path> can be one or more file or folder")

    args = parser.parse_args()

    qApp = QtApp(sys.argv)

    try:
        convert_to(args.path, args.format, args.abort)
    except ConvertException as e:
        print(e)


if __name__ == "__main__":
    main()

  上面是官方程式碼,下面是我的flask呼叫函式:

from .convertto import convert_to

@medical.route('/insertMoadlDocx', methods=['POST', 'GET'])
@login_required
def insertModalDocx():
    try:
        image_id = request.form.get('image_id')
        # 查詢相關資訊
        medical_picture_info = MedicalPicture.query.filter_by(id=image_id).first()
        user_info = User.query.filter_by(id=medical_picture_info.user_id).first()
        user_message_info = UserMessage.query.filter_by(user_id=user_info.id).first()
        modal_list_info = ModalList.query.filter_by(image_id=image_id).all()

        # 讀取docx模板
        template_path = os.path.join(current_app.root_path, 'static', 'word', 'template.docx')
        doc = Document(template_path)

        # 替換表格佔位符
        placeholders = {
            '{{username}}': user_info.username,
            '{{name}}': user_message_info.name,
            '{{sex}}': '男' if user_message_info.sex == 1 else '女',
            '{{age}}': str(user_message_info.age),
            '{{imageType}}': medical_picture_info.imageType,
            '{{uploadTime}}': str(medical_picture_info.uploadTime),
            '{{phone}}': user_message_info.phone,
            '{{idCard}}': str(user_message_info.idCard),
            '{{asset}}': user_message_info.asset
        }

        for table in doc.tables:
            for row in table.rows:
                for cell in row.cells:
                    for key, value in placeholders.items():
                        if key in cell.text:
                            # 保留原始字型格式
                            for paragraph in cell.paragraphs:
                                for run in paragraph.runs:
                                    if key in run.text:
                                        run.text = run.text.replace(key, value)

        # 迴圈插入ModalList資訊
        for index, item in enumerate(modal_list_info):
            if index == 0:
                # 如果是第一條記錄,直接替換原有的佔位符
                for paragraph in doc.paragraphs:
                    if '{{description}}' in paragraph.text:
                        paragraph.text = paragraph.text.replace('{{description}}','\t' + item.description)
                    if '{{image}}' in paragraph.text:
                        # 刪除原有的佔位符
                        paragraph.text = paragraph.text.replace('{{image}}', '')
                        # 新增圖片
                        run = paragraph.add_run()
                        image_path = os.path.join(current_app.root_path, item.image.lstrip('/'))
                        run.add_picture(image_path, width=docx.shared.Cm(14.5), height=docx.shared.Cm(5.2))
            else:
                # 如果不是第一條記錄,在報告醫師資訊的上一行插入新段落並插入資料
                paragraphs_copy, paragraphs_iter = tee(doc.paragraphs)
                for i, paragraph in enumerate(paragraphs_iter):
                    if '報告醫師:' in paragraph.text:
                        # 在報告醫師資訊的上一行插入空白行
                        doc.paragraphs[i - 1].insert_paragraph_before()

                        # 在空白行之後插入新段落
                        new_paragraph = doc.paragraphs[i].insert_paragraph_before()

                        # 插入診斷描述和圖片資訊
                        new_run1 = new_paragraph.add_run(f"診斷描述:\n")
                        new_run1.font.name = '宋體'  # 設定字型為宋體
                        new_run1.font.size = Pt(12)  # 設定字號為12磅
                        new_paragraph.add_run('\t')  # 新增製表符實現縮排
                        new_paragraph.add_run(item.description)
                        new_run2 =  new_paragraph.add_run(f"\n診斷圖片:\n")  # 設定字型為宋體
                        new_run2.font.name = '宋體'  # 設定字型為宋體
                        new_run2.font.size = Pt(12)  # 設定字號為12磅
                        image_path = os.path.join(current_app.root_path, item.image.lstrip('/'))
                        new_paragraph.add_run().add_picture(image_path, width=docx.shared.Cm(14.5),
                                                            height=docx.shared.Cm(5.2))
                        break

                # 新增一個空行,用於分隔不同的記錄
                doc.add_paragraph()

        docx_filename = f"{image_id}_{user_message_info.name}_{medical_picture_info.imageType}.docx"
        folder_name = os.path.splitext(docx_filename)[0]  # 去掉檔案尾綴
        docx_folder = os.path.join(current_app.root_path, 'static', 'word', folder_name)  # 使用去掉尾綴後的檔名作為資料夾名

        # 確保資料夾存在,如果不存在則建立
        if not os.path.exists(docx_folder):
            os.makedirs(docx_folder)

        # 儲存 DOCX 檔案
        docx_path = os.path.join(docx_folder, docx_filename)
        doc.save(docx_path)

        # from win32com.client import pythoncom  # 匯入 pythoncom
        # pythoncom.CoInitialize()  # 初始化 COM 執行緒
        # # 構建 PDF 檔案路徑
        # pdf_filename = docx_filename.replace('.docx', '.pdf')
        # pdf_folder = docx_folder  # 與 DOCX 檔案相同的目錄
        # pdf_path = os.path.join(pdf_folder, pdf_filename)
        #
        # # 將 DOCX 檔案轉換為 PDF
        # convert(docx_path, pdf_path)

        # 建立 PDF 檔案
        pdf_filename = docx_filename.replace('.docx', '.pdf')
        convert_to([docx_path], "pdf")

        # 構建目標檔案的路徑
        docx_save_path = os.path.join('/static', 'word', folder_name, docx_filename)
        pdf_save_path = os.path.join('/static', 'word', folder_name, 'out', pdf_filename)

        # 替換所有路徑中的反斜槓為正斜槓
        docx_save_path = docx_save_path.replace('\\', '/')
        pdf_save_path = pdf_save_path.replace('\\', '/')

        # 將路徑儲存到資料庫中
        medical_picture_info.pdf_path = pdf_save_path
        medical_picture_info.docx_path = docx_save_path

        db.session.commit()

        # 返回 JSON 響應
        return jsonify({'message': '報告生成成功!'}), 200
    except Exception as e:
        # 返回 JSON 響應,表示修改失敗
        return jsonify({'error': str(e)}), 500

相關文章