991 lines
44 KiB
Python
991 lines
44 KiB
Python
"""
|
||
Document creation and manipulation tools for Word Document Server.
|
||
"""
|
||
import os
|
||
import json, re
|
||
from typing import Dict, List, Optional, Any
|
||
from docx import Document
|
||
|
||
from core.tables import copy_table
|
||
from utils.file_utils import check_file_writeable, ensure_docx_extension, create_document_copy
|
||
from utils.document_utils import get_document_properties, extract_document_text, get_document_structure, clear_header
|
||
from core.styles import ensure_heading_style, ensure_table_style
|
||
from docx.oxml.shared import qn
|
||
from docx.oxml import OxmlElement, parse_xml
|
||
from tools.content_tools import search_and_replace,add_picture_to_table,add_picture
|
||
from tools.Get_Json import get_full_picture_url
|
||
from tools.get_pictures import resize_and_reduce_quality, get_template_pic
|
||
from tools.defines import *
|
||
from docx.enum.section import WD_SECTION
|
||
from tools.json_to_docx import list_to_json_with_merges, json_to_docx
|
||
|
||
|
||
async def create_document(filename: str, title: Optional[str] = None, author: Optional[str] = None, section_args: Optional[Dict[str, Any]] = None) -> str:
|
||
"""创建一个包含可选元数据的新Word文档。
|
||
|
||
参数:
|
||
filename: 要创建的文档名称(带或不带.docx扩展名)
|
||
title: 可选标题
|
||
author: 可选作者
|
||
"""
|
||
filename = ensure_docx_extension(filename)
|
||
|
||
# Check if file is writeable
|
||
is_writeable, error_message = check_file_writeable(filename)
|
||
if not is_writeable:
|
||
return f"Cannot create document: {error_message}"
|
||
|
||
try:
|
||
doc = Document()
|
||
|
||
# Set properties if provided
|
||
if title:
|
||
doc.core_properties.title = title
|
||
if author:
|
||
doc.core_properties.author = author
|
||
|
||
# Ensure necessary styles exist
|
||
ensure_heading_style(doc)
|
||
ensure_table_style(doc)
|
||
# 更改纸张大小为A4
|
||
from docx.shared import Mm, Inches
|
||
sections = doc.sections
|
||
if section_args:
|
||
for section in sections:
|
||
section.page_height = Mm(section_args.get('page_height', 297))
|
||
section.page_width = Mm(section_args.get('page_width', 210))
|
||
section.left_margin = Mm(section_args.get('left_margin', 20))
|
||
section.right_margin = Mm(section_args.get('right_margin', 20))
|
||
section.top_margin = Mm(section_args.get('top_margin', 10))
|
||
section.bottom_margin = Mm(section_args.get('bottom_margin', 10))
|
||
else:
|
||
for section in sections:
|
||
section.page_height = Mm(297)
|
||
section.page_width = Mm(210)
|
||
section.left_margin = Inches(0.94)
|
||
section.right_margin = Inches(0.94)
|
||
# Save the document
|
||
doc.save(filename)
|
||
|
||
return f"Document {filename} created successfully"
|
||
except Exception as e:
|
||
return f"Failed to create document: {str(e)}"
|
||
|
||
|
||
async def get_document_info(filename: str) -> str:
|
||
"""获得文档信息
|
||
|
||
Args:
|
||
filename: 目标文档
|
||
"""
|
||
filename = ensure_docx_extension(filename)
|
||
|
||
if not os.path.exists(filename):
|
||
return f"Document {filename} does not exist"
|
||
|
||
try:
|
||
properties = get_document_properties(filename)
|
||
return json.dumps(properties, indent=2)
|
||
except Exception as e:
|
||
return f"Failed to get document info: {str(e)}"
|
||
|
||
|
||
async def get_document_text(filename: str) -> str:
|
||
"""获得文档的所有文本
|
||
|
||
Args:
|
||
filename: 目标文档
|
||
"""
|
||
filename = ensure_docx_extension(filename)
|
||
|
||
return extract_document_text(filename)
|
||
|
||
|
||
async def get_document_outline(filename: str) -> str:
|
||
"""获得文档的所有结构信息
|
||
|
||
Args:
|
||
filename: 目标文档
|
||
"""
|
||
filename = ensure_docx_extension(filename)
|
||
|
||
structure = get_document_structure(filename)
|
||
return json.dumps(structure, indent=2)
|
||
|
||
|
||
async def list_available_documents(directory: str = ".") -> str:
|
||
"""列出目录下所有Word文档
|
||
|
||
Args:
|
||
directory: 目录
|
||
"""
|
||
try:
|
||
if not os.path.exists(directory):
|
||
return f"Directory {directory} does not exist"
|
||
|
||
docx_files = [f for f in os.listdir(directory) if f.endswith('.docx')]
|
||
|
||
if not docx_files:
|
||
return f"No Word documents found in {directory}"
|
||
|
||
result = f"Found {len(docx_files)} Word documents in {directory}:\n"
|
||
for file in docx_files:
|
||
file_path = os.path.join(directory, file)
|
||
size = os.path.getsize(file_path) / 1024 # KB
|
||
result += f"- {file} ({size:.2f} KB)\n"
|
||
|
||
return result
|
||
except Exception as e:
|
||
return f"Failed to list documents: {str(e)}"
|
||
|
||
|
||
async def copy_document(source_filename: str, destination_filename: Optional[str] = None) -> str:
|
||
"""创建文档的副本
|
||
|
||
Args:
|
||
source_filename: 源文档路径
|
||
destination_filename: 目标文档路径,为空则为当前目录
|
||
"""
|
||
source_filename = ensure_docx_extension(source_filename)
|
||
|
||
if destination_filename:
|
||
destination_filename = ensure_docx_extension(destination_filename)
|
||
|
||
success, message, new_path = create_document_copy(source_filename, destination_filename)
|
||
if success:
|
||
return message
|
||
else:
|
||
return f"Failed to copy document: {message}"
|
||
|
||
def add_documents(target_filename: str, source_filename: str) -> str:
|
||
"""将源文档(文本)添加到目标文档尾部
|
||
Args:
|
||
target_doc: 目标文档
|
||
source_filename: 源文档路径
|
||
"""
|
||
target_doc = Document(target_filename)
|
||
source_filename = ensure_docx_extension(source_filename)
|
||
source_doc = Document(source_filename)
|
||
|
||
for source_paragraph in source_doc.paragraphs:
|
||
new_paragraph = target_doc.add_paragraph(source_paragraph.text)
|
||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||
|
||
#获取合并等样式2025427
|
||
new_paragraph.alignment = source_paragraph.alignment
|
||
|
||
# Try to match the style if possible
|
||
try:
|
||
if source_paragraph.style and source_paragraph.style.name in target_doc.styles:
|
||
new_paragraph.style = target_doc.styles[source_paragraph.style.name]
|
||
except Exception as e:
|
||
print(f"Failed to apply style: {e}")
|
||
|
||
# Copy run formatting
|
||
for i, run in enumerate(source_paragraph.runs):
|
||
if i < len(new_paragraph.runs):
|
||
new_run = new_paragraph.runs[i]
|
||
# Copy basic formatting
|
||
new_run.bold = run.bold
|
||
new_run.italic = run.italic
|
||
new_run.underline = run.underline
|
||
#添加同时合并字体2025427
|
||
new_run.font.name = run.font.name
|
||
rPr = new_run.element.get_or_add_rPr()
|
||
rFonts = rPr.get_or_add_rFonts()
|
||
# 检查 run.font.name 是否为 None
|
||
if run.font.name is None:
|
||
# 设置默认的中文字体名称
|
||
run.font.name = '宋体 (中文正文)' # 或者使用其他你喜欢的中文字体
|
||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||
new_run.font.color.rgb = run.font.color.rgb
|
||
|
||
# Font size if specified
|
||
if run.font.size:
|
||
new_run.font.size = run.font.size
|
||
target_doc.save(target_filename)
|
||
return f"{target_filename}添加{source_filename}成功"
|
||
|
||
|
||
|
||
def write_table(target_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER') -> Document:
|
||
"""填写word文档里的表格,返回填写后的文档
|
||
|
||
Args:
|
||
target_filename: 目标文档路径
|
||
rows: 表格行数
|
||
cols: 表格列数
|
||
table_num: 表格序号
|
||
data: 表格数据,二维列表,每个单元格为字符串
|
||
ifadjustheight: bool,为真则表格行高自动调整
|
||
"""
|
||
table_num = -1
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
# Check if target file is writeable
|
||
is_writeable, error_message = check_file_writeable(target_filename)
|
||
if not is_writeable:
|
||
return f"Cannot create target document: {error_message}"
|
||
|
||
try:
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
target_doc = Document(target_filename)
|
||
except Exception as e:
|
||
print(f"获取{target_filename}失败:{str(e)}")
|
||
|
||
# Try to set the table style
|
||
try:
|
||
target_doc.tables[table_num].style = 'Table Grid'
|
||
except KeyError as k:
|
||
pass
|
||
except Exception as e:
|
||
print(f"{target_doc}最后一个表格更改样式失败: {str(e)}")
|
||
|
||
print("开始写入表格")
|
||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||
from docx.shared import Pt, Inches, Cm, RGBColor
|
||
try:
|
||
if data:
|
||
for i, row_data in enumerate(data):
|
||
if i >= rows + 1:
|
||
break
|
||
for j, cell_text in enumerate(row_data):
|
||
if j >= cols + 1:
|
||
break
|
||
if str(cell_text) == "": continue
|
||
print(f"在[{i},{j}]处写入{str(cell_text)}")
|
||
target_doc.tables[table_num].cell(i,j).text = str(cell_text)
|
||
print(key_words, cell_text)
|
||
if key_words and key_words.search(str(cell_text)):
|
||
print(f'{cell_text}包含关键之,已置红')
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.size = Pt(10.5) # 字体大小
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
|
||
if ALIGMENT == 'CENTER':
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||
elif ALIGMENT == 'LEFT':
|
||
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.LEFT
|
||
target_doc.tables[table_num].cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||
if ifadjustheight:
|
||
target_doc.tables[table_num].rows[i].height = Cm(height)
|
||
except Exception as e:
|
||
print(f"写入{target_filename}tables.cell({i},{j})失败:{str(e)}")
|
||
print("表格写入完成")
|
||
return target_doc
|
||
|
||
def set_document_para(target_doc: Document) -> Document:
|
||
"""设置文档的段落格式
|
||
"""
|
||
paragraphs_to_remove = []
|
||
for i, paragraph in enumerate(target_doc.paragraphs):
|
||
if i <= 11:
|
||
continue
|
||
if not paragraph.text.strip():
|
||
paragraphs_to_remove.append(paragraph)
|
||
|
||
for paragraph in paragraphs_to_remove:
|
||
p = paragraph._element
|
||
p.getparent().remove(p)
|
||
|
||
return target_doc
|
||
|
||
def add_table_to_document(target_filename: str, source_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER', if_merge : Optional[bool] = True, Report_Enum = 'DT', if_para : Optional[bool] = True) -> str:
|
||
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
|
||
Args:
|
||
target_filename: 目标文档路径
|
||
source_doc: 源文档路径
|
||
rows: 表格行数
|
||
cols: 表格列数
|
||
table_num: 表格序号
|
||
data: 表格数据,二维列表,每个单元格为字符串
|
||
ifadjustheight: bool,为真则表格行高自动调整
|
||
key_words: list, 关键字
|
||
"""
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
source_filename = ensure_docx_extension(source_filename)
|
||
source_doc = Document(source_filename)
|
||
target_doc = Document(target_filename)
|
||
if if_para:
|
||
try:
|
||
# Copy all paragraphs
|
||
for paragraph in source_doc.paragraphs:
|
||
# Create a new paragraph with the same text and style
|
||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||
|
||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||
#获取合并等样式2025427
|
||
new_paragraph.alignment = paragraph.alignment
|
||
|
||
# 复制段落分页属性
|
||
new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before
|
||
# Try to match the style if possible
|
||
try:
|
||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||
except:
|
||
pass
|
||
|
||
|
||
# Copy run formatting
|
||
for i, run in enumerate(paragraph.runs):
|
||
if i < len(new_paragraph.runs):
|
||
new_run = new_paragraph.runs[i]
|
||
# Copy basic formatting
|
||
new_run.bold = run.bold
|
||
new_run.italic = run.italic
|
||
new_run.underline = run.underline
|
||
#添加同时合并字体2025427
|
||
new_run.font.name = run.font.name
|
||
rPr = new_run.element.get_or_add_rPr()
|
||
rFonts = rPr.get_or_add_rFonts()
|
||
# 检查 run.font.name 是否为 None
|
||
if run.font.name is None:
|
||
# 设置默认的中文字体名称
|
||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||
new_run.font.color.rgb = run.font.color.rgb
|
||
|
||
|
||
# Font size if specified
|
||
if run.font.size:
|
||
new_run.font.size = run.font.size
|
||
|
||
except Exception as e:
|
||
print(f"添加表格前文章失败:{str(e)}")
|
||
|
||
try:# Copy all tables
|
||
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, if_merge, REPORT_ENUM=Report_Enum)
|
||
except Exception as e:
|
||
print(f"添加表格失败:{str(e)}")
|
||
print(f"{target_doc}写入表格{source_doc.tables[0]}成功")
|
||
if data:
|
||
try:
|
||
target_doc = write_table(target_filename, rows, cols, table_num, data, ifadjustheight, height, key_words, ALIGMENT)
|
||
except Exception as e:
|
||
print(f"{target_filename}写入{data}失败:{str(e)}")
|
||
target_doc.save(target_filename)
|
||
return target_doc,f"{target_filename}添加表格{source_doc}成功"
|
||
from docx.document import Document as Document_
|
||
from docx.table import Table
|
||
def add_table(target_filename: str, source_filename : str | Document_, if_merge = True, REPORT_ENUM = 'DT'):
|
||
if isinstance(source_filename, str):
|
||
output_doc = copy_table(Document(source_filename).tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM)
|
||
output_doc.save(target_filename)
|
||
else:
|
||
try:
|
||
output_doc = copy_table(source_filename.tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM)
|
||
output_doc.save(target_filename)
|
||
except Exception as e:
|
||
print(f"添加表格失败:{str(e)}")
|
||
return f"{target_filename}添加表格{source_filename}成功"
|
||
|
||
def add_table_and_replace(
|
||
target_filename: str,
|
||
source_filename: str,
|
||
ifadjustheight: Optional[bool] = True,
|
||
list_to_replace: dict = {},
|
||
height: Optional[float] = 1,
|
||
no_para : Optional[bool] = False,
|
||
REPORT_ENUM = 'DT'):
|
||
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
|
||
Args:
|
||
target_filename: 目标文档路径
|
||
source_doc: 源文档路径
|
||
ifadjustheight: bool,为真则表格行高自动调整
|
||
list_to_replace: dict, 待替换内容和替换内容
|
||
height: float, 表格行高
|
||
no_para: bool, 无段落
|
||
"""
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
source_filename = ensure_docx_extension(source_filename)
|
||
source_doc = Document(source_filename)
|
||
target_doc = Document(target_filename)
|
||
if not no_para:
|
||
try:
|
||
# Copy all paragraphs
|
||
for paragraph in source_doc.paragraphs:
|
||
# Create a new paragraph with the same text and style
|
||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||
#获取合并等样式2025427
|
||
new_paragraph.alignment = paragraph.alignment
|
||
|
||
# 复制段落分页属性
|
||
new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before
|
||
# Try to match the style if possible
|
||
try:
|
||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||
except:
|
||
pass
|
||
# Copy run formatting
|
||
for i, run in enumerate(paragraph.runs):
|
||
if i < len(new_paragraph.runs):
|
||
new_run = new_paragraph.runs[i]
|
||
# Copy basic formatting
|
||
new_run.bold = run.bold
|
||
new_run.italic = run.italic
|
||
new_run.underline = run.underline
|
||
#添加同时合并字体2025427
|
||
new_run.font.name = run.font.name
|
||
rPr = new_run.element.get_or_add_rPr()
|
||
rFonts = rPr.get_or_add_rFonts()
|
||
# 检查 run.font.name 是否为 None
|
||
if run.font.name is None:
|
||
# 设置默认的中文字体名称
|
||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||
new_run.font.color.rgb = run.font.color.rgb
|
||
|
||
|
||
# Font size if specified
|
||
if run.font.size:
|
||
new_run.font.size = run.font.size
|
||
|
||
# 复制分页符(处理w:br标签)
|
||
for element in run._element:
|
||
if element.tag.endswith('br'):
|
||
br_type = element.get(qn('type'), '')
|
||
if br_type == 'page':
|
||
new_br = OxmlElement('w:br')
|
||
new_br.set(qn('type'), 'page')
|
||
new_run._element.append(new_br)
|
||
except Exception as e:
|
||
print(f"添加表格前文章失败:{str(e)}")
|
||
try:# Copy all tables
|
||
from core.tables import copy_table
|
||
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, REPORT_ENUM=REPORT_ENUM)
|
||
target_doc.save(target_filename)
|
||
except Exception as e:
|
||
print(f"添加表格失败:{str(e)}")
|
||
for find_text, replace_text in list_to_replace.items():
|
||
print(search_and_replace(target_filename, find_text, replace_text))
|
||
|
||
async def merge_documents(target_filename: str, source_filenames: List[str], add_page_breaks: bool = True) -> str:
|
||
"""合并文档(文本) 表格会添加到最后
|
||
|
||
Args:
|
||
target_filename: 合并后文档路径
|
||
source_filenames: 源文档路径(列表)
|
||
add_page_breaks: bool,为真则每个源文档中间加入分页符
|
||
"""
|
||
from core.tables import copy_table
|
||
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
|
||
# Check if target file is writeable
|
||
is_writeable, error_message = check_file_writeable(target_filename)
|
||
if not is_writeable:
|
||
return f"Cannot create target document: {error_message}"
|
||
|
||
# Validate all source documents exist
|
||
missing_files = []
|
||
for filename in source_filenames:
|
||
doc_filename = ensure_docx_extension(filename)
|
||
if not os.path.exists(doc_filename):
|
||
missing_files.append(doc_filename)
|
||
|
||
if missing_files:
|
||
return f"Cannot merge documents. The following source files do not exist: {', '.join(missing_files)}"
|
||
|
||
try:
|
||
# Create a new document for the merged result
|
||
target_doc = Document()
|
||
|
||
# Process each source document
|
||
for i, filename in enumerate(source_filenames):
|
||
doc_filename = ensure_docx_extension(filename)
|
||
source_doc = Document(doc_filename)
|
||
|
||
# Add page break between documents (except before the first one)
|
||
if add_page_breaks and i > 0:
|
||
target_doc.add_page_break()
|
||
|
||
# Copy all paragraphs
|
||
for paragraph in source_doc.paragraphs:
|
||
# Create a new paragraph with the same text and style
|
||
new_paragraph = target_doc.add_paragraph(paragraph.text)
|
||
new_paragraph.style = target_doc.styles['Normal'] # Default style
|
||
#获取合并等样式2025427
|
||
new_paragraph.alignment = paragraph.alignment
|
||
|
||
# Try to match the style if possible
|
||
try:
|
||
if paragraph.style and paragraph.style.name in target_doc.styles:
|
||
new_paragraph.style = target_doc.styles[paragraph.style.name]
|
||
except:
|
||
pass
|
||
|
||
|
||
# Copy run formatting
|
||
for i, run in enumerate(paragraph.runs):
|
||
if i < len(new_paragraph.runs):
|
||
new_run = new_paragraph.runs[i]
|
||
# Copy basic formatting
|
||
new_run.bold = run.bold
|
||
new_run.italic = run.italic
|
||
new_run.underline = run.underline
|
||
#添加同时合并字体2025427
|
||
new_run.font.name = run.font.name
|
||
rPr = new_run.element.get_or_add_rPr()
|
||
rFonts = rPr.get_or_add_rFonts()
|
||
# 检查 run.font.name 是否为 None
|
||
if run.font.name is None:
|
||
# 设置默认的中文字体名称
|
||
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
|
||
rFonts.set(qn('w:eastAsia'), run.font.name)
|
||
new_run.font.color.rgb = run.font.color.rgb
|
||
|
||
|
||
# Font size if specified
|
||
if run.font.size:
|
||
new_run.font.size = run.font.size
|
||
|
||
# Copy all tables
|
||
for table in source_doc.tables:
|
||
copy_table(table, target_doc)
|
||
|
||
# Save the merged document
|
||
target_doc.save(target_filename)
|
||
return f"Successfully merged {len(source_filenames)} documents into {target_filename}"
|
||
except Exception as e:
|
||
return f"Failed to merge documents: {str(e)}"
|
||
|
||
|
||
async def right_align_last_three_para(target_filename: str) -> str:
|
||
"""右对齐最后三个段落
|
||
|
||
Args:
|
||
target_filename: 目标文档路径
|
||
"""
|
||
target_filename = ensure_docx_extension(target_filename)
|
||
|
||
# Check if target file is writeable
|
||
is_writeable, error_message = check_file_writeable(target_filename)
|
||
if not is_writeable:
|
||
return f"Cannot right align paragraphs: {error_message}"
|
||
|
||
try:
|
||
# Open the target document
|
||
target_doc = Document(target_filename)
|
||
|
||
# Get the last three paragraphs
|
||
paragraphs = target_doc.paragraphs[-3:]
|
||
|
||
# Set the alignment of each paragraph to right
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
for paragraph in paragraphs:
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||
|
||
# Save the modified document
|
||
target_doc.save(target_filename)
|
||
return f"Successfully right aligned the last three paragraphs in {target_filename}"
|
||
except Exception as e:
|
||
return f"Failed to right align paragraphs: {str(e)}"
|
||
|
||
async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao):
|
||
"""创建动态表
|
||
|
||
Args:
|
||
output_doc (Document): 文档对象
|
||
output_dir (str): 输出目录
|
||
table_num (int): 表格序号
|
||
TABLES (list): 表格数据
|
||
JIANCHA_XIANGQING_DIR (str): 检查详情表目录
|
||
PICTURES (dict): 图片数据字典,键为表索引,值为图片路径列表
|
||
row (int): 行数
|
||
col (int): 列数
|
||
i (int): 表格序号
|
||
FLAG: 其他标志
|
||
|
||
Returns:
|
||
tuple: (i, table_num) 更新后的表格序号和表格数量
|
||
"""
|
||
for table_idx, Table in enumerate(TABLES):
|
||
print(Table)
|
||
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG)
|
||
print(message)
|
||
# 获取当前表格对应的图片
|
||
current_table_pictures = PICTURES.get(table_idx, [])
|
||
print(f"开始处理图片列表: {current_table_pictures}")
|
||
|
||
for picturedir in current_table_pictures:
|
||
if picturedir is None:
|
||
print(f"图片路径为空,跳过")
|
||
continue
|
||
try:
|
||
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
|
||
resize_and_reduce_quality(picturedir, picturedir)
|
||
await add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
|
||
except Exception as e:
|
||
print(f"添加图片失败:{e}")
|
||
|
||
print(await search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}'))
|
||
table_num += 1
|
||
i += 1
|
||
xuhao += 1
|
||
return i, table_num, xuhao
|
||
|
||
async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words = None):
|
||
"""添加对应表格且填写图片名与插入图片
|
||
|
||
Args:
|
||
data_dict (dict): dict内容,图片:图片路径
|
||
output_dir (str): 输出路径
|
||
start_i (int): 总表格数量
|
||
JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径
|
||
|
||
Returns:
|
||
int: 最后使用的表格序号
|
||
"""
|
||
items = list(data_dict.items())
|
||
picture_num = len(items)
|
||
line_index = 0
|
||
picture_index = 0
|
||
i = start_i
|
||
|
||
for content_row in range(((picture_num + 2) // 3) * 2):
|
||
if content_row % 2 == 1:
|
||
# 文字行(从 items 取图片名)
|
||
JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列
|
||
for k in range(1): # 只有1行
|
||
for l in range(3):
|
||
if line_index >= picture_num:
|
||
break
|
||
JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名
|
||
print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}')
|
||
line_index += 1
|
||
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
|
||
print(f"当前表格序号为 {i}")
|
||
output_doc, message = await add_table_to_document(
|
||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
|
||
)
|
||
i += 1
|
||
else:
|
||
# 图片行(从 items 取图片路径)
|
||
print(f"当前表格序号为 {i}")
|
||
output_doc, message = await add_table_to_document(
|
||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
|
||
)
|
||
for k in range(3):
|
||
if picture_index < picture_num:
|
||
pic_path = items[picture_index][1] # 图片路径
|
||
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
|
||
print(await add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
|
||
picture_index += 1
|
||
i += 1
|
||
print(message)
|
||
return i # 返回最后使用的表格序号
|
||
|
||
async def process_server_images_table(data_list, image_source_list, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words=None):
|
||
"""从服务器获取的图片数据添加对应表格且填写图片名与插入图片
|
||
|
||
逻辑:按来源排序,添加
|
||
Args:
|
||
data_list (list): 图片数据列表,每个元素为图片路径
|
||
[
|
||
{'imageId': '41543f531be24522b7bec741b9a483a2', 'imageName': 'F02_机组10060_叶片,叶片PS面距叶根15.5米处轴向裂纹,轴向:260mm,弦向:20mm。DJI_20250526090227_0052_Z.png', 'imagePath': '/static/image/temp/out-work/12bc30fb209f3af3bf530541c5b062bd/F02_机组10060_叶片,叶片PS面距叶根15.5米处轴向裂纹,轴向:260mm,弦向:20mm。DJI_20250526090227_0052_Z.png', 'partId': '12bc30fb209f3af3bf530541c5b062bd', 'partName': '叶片1', 'imageResolution': '8000x6000', 'focalDistance': None, 'shootingTime': None, 'cameraManufacturer': None, 'cameraModel': None, 'weather': None, 'weatherLabel': None, 'humidness': None, 'temperature': None, 'windLevel': None, 'shootingMethod': 'UAV', 'shootingMethodLabel': '无人机航拍', 'shootingDistance': None, 'collectorName': '程启谦', 'imageSource': 'out-work', 'imageSourceLabel': '外部工作', 'imageType': 'DEFECT', 'imageTypeLabel': '缺陷影像', 'audioList': None, 'preImagePath': None, 'preTreatment': False, 'projectId': None, 'gps': None},
|
||
...
|
||
]
|
||
image_source_list (list): 来源列表,每个元素为来源名称
|
||
['out-work', 'in-work', 'in-field'] (来源可能会少于3个,pop出没有给出对应来源的图片)
|
||
output_dir (str): 输出路径
|
||
start_i (int): 总表格数量
|
||
JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径
|
||
key_words (re)
|
||
|
||
Returns:
|
||
int: 最后使用的表格序号
|
||
"""
|
||
# 按来源对图片数据进行分组和排序
|
||
sorted_data = {source: [] for source in image_source_list}
|
||
|
||
# 将图片数据按来源分组
|
||
for item in data_list:
|
||
source = item['imageSource']
|
||
if source in sorted_data:
|
||
# 存储图片名和图片路径的元组,与非server版本保持一致
|
||
sorted_data[source].append((item['imageTypeLabel'], item['imagePath']))
|
||
|
||
i = start_i
|
||
# 按照image_source_list的顺序处理每个来源的图片
|
||
for source in image_source_list:
|
||
items = sorted_data[source]
|
||
if not items:
|
||
continue # 如果该来源没有图片则跳过
|
||
|
||
print(f"处理来源: {source}")
|
||
picture_num = len(items)
|
||
line_index = 0
|
||
picture_index = 0
|
||
|
||
for content_row in range(((picture_num + 2) // 3) * 2):
|
||
if content_row % 2 == 1:
|
||
# 文字行(从 items 取图片名)
|
||
JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列
|
||
for k in range(1): # 只有1行
|
||
for l in range(3):
|
||
if line_index >= picture_num:
|
||
break
|
||
JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名
|
||
print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}')
|
||
line_index += 1
|
||
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
|
||
print(f"当前表格序号为 {i}")
|
||
output_doc, message = await add_table_to_document(
|
||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
|
||
)
|
||
i += 1
|
||
else:
|
||
# 图片行(从 items 取图片路径)
|
||
print(f"当前表格序号为 {i}")
|
||
output_doc, message = await add_table_to_document(
|
||
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
|
||
)
|
||
for k in range(3):
|
||
if picture_index < picture_num:
|
||
pic_path = items[picture_index][1] # 图片路径
|
||
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
|
||
print(await add_picture_to_table(output_doc, output_dir, 0, k, get_full_picture_url(pic_path), i, 1.8898))
|
||
picture_index += 1
|
||
i += 1
|
||
print(message)
|
||
return i # 返回最后使用的表格序号
|
||
|
||
def add_header(target_dir : str, report_enum : str, if_clear_header : Optional[bool] = True, if_section : Optional[bool] = True, text : str = None):
|
||
"""添加页眉,添加封面后调用此函数,会分离页面和后续页面的节。
|
||
|
||
Args:
|
||
target_dir (str): 目标目录
|
||
start_section (int): 开始节
|
||
end_section (int): 结束节
|
||
report_enum (str): 报告类型(DT或JF)
|
||
"""
|
||
document = Document(target_dir) # 打开文档
|
||
|
||
if if_clear_header:
|
||
for section in document.sections: # 遍历所有节的页眉
|
||
clear_header(section) # 清除页眉的段落
|
||
|
||
if if_section:
|
||
print(f"文档节数:{len(document.sections)},开始往当前节后添加页眉")
|
||
document.sections[0].header.is_linked_to_previous = False # 取消页眉与上一页关联
|
||
|
||
document.add_section(WD_SECTION.NEW_PAGE)
|
||
header = document.sections[1].header
|
||
|
||
header.is_linked_to_previous = False # 取消页眉与上一页关联
|
||
else:
|
||
header = document.sections[-1].header
|
||
header.is_linked_to_previous = False # 取消页眉与上一页关联
|
||
paragraph = header.paragraphs[0] # 获取页眉的第一个段落
|
||
run = paragraph.add_run()
|
||
|
||
if report_enum == 'JF':
|
||
print("添加金风模板的页眉")
|
||
pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.JINFENG_HEADER.PIC_DIR))
|
||
if text:
|
||
run = paragraph.add_run(text)
|
||
else:
|
||
run = paragraph.add_run(TEMPLATE_HEADER.JINFENG_HEADER.PARA)
|
||
run.font.name = TEMPLATE_HEADER.JINFENG_HEADER.FONT
|
||
run.font.size = TEMPLATE_HEADER.JINFENG_HEADER.PT
|
||
|
||
elif report_enum == 'DT':
|
||
print("添加迪特模板的页眉")
|
||
pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.DT_HEADER.PIC_DIR))
|
||
if text:
|
||
run = paragraph.add_run(text)
|
||
else:
|
||
run = paragraph.add_run(TEMPLATE_HEADER.DT_HEADER.PARA)
|
||
run.font.name = TEMPLATE_HEADER.DT_HEADER.FONT
|
||
run.font.size = TEMPLATE_HEADER.DT_HEADER.PT
|
||
|
||
else:
|
||
print("未知模板,不添加页眉")
|
||
# 定义边框的 XML 字符串
|
||
border_xml = """
|
||
<w:pBdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||
<w:bottom w:val="single" w:sz="8" w:space="1" w:color="000000"/>
|
||
</w:pBdr>
|
||
"""
|
||
|
||
# 解析 XML 并添加到段落属性
|
||
pPr = paragraph._element.get_or_add_pPr()
|
||
pBdr = parse_xml(border_xml)
|
||
pPr.append(pBdr)
|
||
|
||
print(f"文档节数:{len(document.sections)}")
|
||
document.save(target_dir) # 保存文档
|
||
|
||
from docx.enum.section import WD_ORIENT
|
||
from docx.enum.text import WD_BREAK
|
||
from docx.document import Document as Document_
|
||
def add_landscape_section(target_dir : str):
|
||
# 添加横向节
|
||
doc = Document(target_dir)
|
||
section = doc.add_section()
|
||
section.orientation = WD_ORIENT.LANDSCAPE
|
||
section.page_width, section.page_height = section.page_height, section.page_width
|
||
print(f"文档:{target_dir},添加了新横向节,页宽:{section.page_width}, 页高:{section.page_height}")
|
||
return doc
|
||
|
||
def add_section(target_dir : str) -> Document_:
|
||
doc = Document(target_dir)
|
||
section = doc.add_section()
|
||
return doc
|
||
|
||
def add_title(target_dir : str, title : str, style_config : dict = {}):
|
||
doc = Document(target_dir)
|
||
para = doc.add_paragraph()
|
||
run = para.add_run(title)
|
||
font_config = style_config.get('font', {})
|
||
run.font.name = font_config.get('name', "宋体")
|
||
run.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体"))
|
||
run.font.size = font_config.get('size', Pt(14))
|
||
run.bold = font_config.get('bold', False)
|
||
para.alignment = style_config.get('alignment', WD_ALIGN_PARAGRAPH.CENTER)
|
||
print(f"添加新标题:{title},字体:{run.font.name},大小:{run.font.size},是否加粗:{run.bold}")
|
||
return doc
|
||
|
||
def merge_documents(target_dir : str, source_dirs : List[str]):
|
||
"""合并多个文档、图片
|
||
|
||
Args:
|
||
target_dir (str): 目标目录
|
||
source_dirs (List[str]): 源目录列表
|
||
"""
|
||
# 打开目标文档
|
||
document = Document(target_dir)
|
||
|
||
for dir in source_dirs:
|
||
if dir.endswith('.docx'):
|
||
print(add_documents(target_dir, dir))
|
||
if dir.endswith('.jpg') or dir.endswith('.png'):
|
||
print(add_picture(target_dir, dir, is_center=True))
|
||
|
||
def add_defect_info_table(output_dir, defect_info, MUBAN_DIR, total_table_num, REPORT_ENUM):
|
||
"""将defectinfo写入word文档
|
||
|
||
Args:
|
||
output_dir: 输出目录
|
||
defect_info: 缺陷信息
|
||
{
|
||
'叶片1': { #按叶片分记录
|
||
'表面裂纹': { 'SLIGHT': #按类型分等级,等级从'defectLevel'获取
|
||
[ #按等级分记录
|
||
{
|
||
record:{...}
|
||
}, #一张图片一个记录
|
||
...
|
||
]
|
||
...
|
||
}
|
||
}
|
||
total_table_num: 总表格数量
|
||
Returns:
|
||
table_num: 表格数量
|
||
功能:表头分别为:序号,缺陷类型,等级,损伤数量,处理建议,备注(是否完成处理) (默认否)。
|
||
总体就按等级分类,但损伤数量需要说明为:n支m处。n为有这个缺陷和等级的叶片数,m为有这个缺陷和等级的所有叶片加起来的缺陷数。
|
||
处理建议从record[0]的值中获取
|
||
"""
|
||
table_data = [] # 存储所有表格数据的列表,每个元素是一个字典 # 添加新记录(使用字典格式)
|
||
table_data.append({
|
||
"xuhao" : "序号", # 序号
|
||
"defecttype" : "损伤名称", # 缺陷类型
|
||
"level" : "损伤等级", # 等级
|
||
"defectnuminfo" : "损伤数量", # 损伤数量
|
||
"suggestion" : "处理建议", # 处理建议
|
||
"remark" : "备注(是否处理完成)" # 备注(是否完成处理)
|
||
})
|
||
# 遍历缺陷信息,整理数据
|
||
for blade_name, defects in defect_info.items():
|
||
for defect_type, levels in defects.items():
|
||
for level, records in levels.items():
|
||
if records: # 如果有记录
|
||
# 计算损伤数量:n支m处
|
||
blade_count = 1 # 当前叶片就是1支
|
||
defect_count = len(records) # 当前叶片的缺陷数量
|
||
|
||
# 查找是否已有同类型同等级的数据
|
||
found = False
|
||
for item in table_data:
|
||
if item["defecttype"] == defect_type and item["level"] == level:
|
||
# 更新已有记录
|
||
current_n = item["defectnuminfo"]
|
||
parts = current_n.split("支")
|
||
existing_blades = int(parts[0])
|
||
existing_defects = int(parts[1].split("处")[0])
|
||
item["defectnuminfo"] = f"{existing_blades + blade_count}支{existing_defects + defect_count}处"
|
||
found = True
|
||
break
|
||
|
||
if not found:
|
||
# 获取处理建议(假设第一条记录中有处理建议)
|
||
suggestion = records[0].get('record', {}).get('处理建议', '')
|
||
|
||
# 添加新记录(使用字典格式)
|
||
table_data.append({
|
||
"xuhao" : str(len(table_data)), # 序号
|
||
"defecttype" : defect_type, # 缺陷类型
|
||
"level" : level, # 等级
|
||
"defectnuminfo" : f"{blade_count}支{defect_count}处", # 损伤数量
|
||
"suggestion" : suggestion, # 处理建议
|
||
"remark" : "否" # 备注(是否完成处理)
|
||
})
|
||
# 添加空行
|
||
for _ in range(2):
|
||
table_data.append({
|
||
"xuhao" : "", # 序号
|
||
"defecttype" : "", # 缺陷类型
|
||
"level" : "", # 等级
|
||
"defectnuminfo" : "", # 损伤数量
|
||
"suggestion" : "", # 处理建议
|
||
"remark" : "" # 备注(是否完成处理)
|
||
})
|
||
for row in table_data:
|
||
add_table_and_replace(output_dir, MUBAN_DIR, list_to_replace=row, no_para = True, ifadjustheight=True, REPORT_ENUM=REPORT_ENUM)
|
||
total_table_num += 1
|
||
return total_table_num
|
||
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
from docx.shared import RGBColor
|
||
def add_table_title(output_dir, TITLE):
|
||
doc = Document(output_dir)
|
||
table = doc.add_table(rows=1, cols=6, style='Table Grid')
|
||
table.cell(0,0).merge(table.cell(0,5))
|
||
para = table.cell(0,0).paragraphs[0]
|
||
run = para.add_run(TITLE)
|
||
run.font.name = "宋体"
|
||
run.font.size = Pt(9)
|
||
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
print(f"添加了标题表格:{TITLE}")
|
||
doc.save(output_dir)
|
||
|
||
def change_heading(output_dir, style_name, style_config = {}):
|
||
doc = Document(output_dir)
|
||
heading_style = doc.styles[style_name]
|
||
font_config = style_config.get('font', {})
|
||
heading_style.font.name = font_config.get('name', "宋体")
|
||
heading_style.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体"))
|
||
heading_style.font.size = font_config.get('size', Pt(11))
|
||
heading_style.bold = font_config.get('bold', True)
|
||
heading_style.font.color.rgb = font_config.get('color', DocxColors.BLACK.rgb)
|
||
print(f"修改了标题样式:{style_name},字体:{heading_style.font.name},大小:{heading_style.font.size},是否加粗:{heading_style.bold}")
|
||
doc.save(output_dir)
|
||
|
||
from docxtpl import DocxTemplate
|
||
|
||
def add_auto_toc_at_end(doc_path):
|
||
"""使用 python-docx-template 在末尾自动生成目录"""
|
||
doc = DocxTemplate(doc_path)
|
||
|
||
# 渲染模板(r.toc=True 会触发目录生成)
|
||
context = {
|
||
'r': {
|
||
'toc': True # 自动生成目录
|
||
}
|
||
}
|
||
doc.render(context)
|
||
doc.save(doc_path) |