今天在使用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