import ast
import copy
import logging
import os
import shutil
from abc import ABCMeta
from docxtpl import DocxTemplate, R
from jinja2 import Environment, meta, Template
from wordmarker.contexts import YamlContext, SystemContext
from wordmarker.creatives import FactoryBean, AbstractBuilder
from wordmarker.loaders import DefaultResourceLoader
from wordmarker.utils import PathUtils, log, YamlUtils
[文档]class DocxHelper(DefaultResourceLoader):
"""
::
通过读取配置文件,获取docx文件模板的相关信息
"""
__docx_helper = None
def __init__(self):
super().__init__()
self.__yaml_context: YamlContext = FactoryBean().get_bean("yaml_context")
self._docx_in_path = self._get_docx_in_path()
self._docx_out_path = self._get_docx_out_path()
self._docx = self._get_docx()
def _get_docx_in_path(self):
"""
.. note::
通过读取yaml文件的 ``data.docx.input.path`` 属性,获取输入的docx文件或目录的绝对路径
:return: - docx文件或目录的绝对路径
"""
prop = "data.docx.input.path"
value = self.__yaml_context.get_value(prop)
docx_in_path = PathUtils(self.__yaml_context.path, value).get_relative_path()
self._docx_in_path = docx_in_path
return docx_in_path
def _get_docx_out_path(self):
"""
.. note::
通过读取yaml文件的 ``data.docx.output.dir`` 属性,获取输出的docx目录的绝对路径
:return: - docx目录的绝对路径
"""
prop = "data.docx.output.dir"
value = self.__yaml_context.get_value(prop)
return PathUtils(self.__yaml_context.path, value).get_relative_path()
def _get_docx(self):
"""
.. note::
获取docx文件的绝对路径
:return: - yaml文件中 ``data.docx.input.path`` 是目录,返回当前目录下docx文件的绝对路径
- yaml文件中 ``data.docx.input.path`` 是文件,返回docx文件的绝对路径
"""
docx = self.get_resource(self._docx_in_path).get_file()
if not self.get_resource(self._docx_in_path).is_file():
# 是目录
docx = PathUtils.filter_file(docx, ['.docx'])
else:
# 是文件
docx = PathUtils.filter_file(docx, ['.docx'])[0]
return docx
[文档] def get_docx_file_name(self):
"""
.. note::
获取docx文件的文件名
:return: - 是docx文件,返回文件的名字
- 是目录,返回当前目录下的所有docx文件的文件名
"""
docx = self._docx
if type(docx) is list:
temp_list = []
for item in docx:
temp_list.append(os.path.split(item)[1])
return temp_list
else:
return os.path.split(docx)[1]
@property
def docx_in_path(self):
"""
.. note::
通过读取yaml文件的 ``data.docx.input.path`` 属性,获取输入的docx文件或目录的绝对路径
:return: - docx文件或目录的绝对路径
"""
return self._docx_in_path
@property
def docx_out_path(self):
"""
.. note::
通过读取yaml文件的 ``data.docx.output.dir`` 属性,获取输出的docx目录的绝对路径
:return: - docx目录的绝对路径
"""
return self._docx_out_path
@property
def docx(self):
"""
.. note::
获取docx文件的绝对路径
:return: - yaml文件中 ``data.docx.input.path`` 是目录,返回当前目录下docx文件的绝对路径
- yaml文件中 ``data.docx.input.path`` 是文件,返回docx文件的绝对路径
"""
return self._docx
def __new__(cls, *args, **kwargs):
if cls.__docx_helper is None:
cls.__docx_helper = object.__new__(cls)
return cls.__docx_helper
[文档]class ImgHelper(SystemContext):
"""
::
通过读取配置文件,获取img文件的相关信息
"""
__img_helper = None
def __init__(self):
super().__init__()
self.__yaml_context: YamlContext = FactoryBean().get_bean("yaml_context")
self.__img_out_path = self._get_img_out_path()
def _get_img_out_path(self):
prop = "data.img.output.dir"
value = self.__yaml_context.get_value(prop)
return PathUtils(self.__yaml_context.path, value).get_relative_path()
[文档] def get_img_file(self, img_name):
"""
.. note::
根据图片的名字,获取图片的绝对路径
.. warning::
必须先将图片输出到输出目录下,才能获取到
:param img_name: 图片的名字
:return: - 图片的绝对路径
"""
final_path = self.__img_out_path + self.path_separator + img_name
return final_path
@property
def img_out_path(self):
"""
.. note::
通过读取yaml文件的 ``data.img.output.dir`` 属性,获取输出的img目录的绝对路径
:return: - img目录的绝对路径
"""
return self.__img_out_path
[文档] def clear_img(self):
"""
.. note::
清除 ``data.img.output.dir`` 属性对应的img的输出目录下的所有文件和目录
"""
if os.path.exists(self.__img_out_path):
shutil.rmtree(self.__img_out_path)
os.mkdir(self.__img_out_path)
def __new__(cls, *args, **kwargs):
if cls.__img_helper is None:
cls.__img_helper = object.__new__(cls)
return cls.__img_helper
[文档]class TextHelper:
"""
::
通过读取配置文件,获取文本yaml文件的相关信息
"""
__text_helper = None
def __init__(self):
super().__init__()
self.__yaml_context: YamlContext = FactoryBean().get_bean("yaml_context")
self.__text_in_path = self._get_text_in_path()
self.__text_context = YamlContext(self.text_in_path)
self.__yaml_singleton = {}
def _get_text_in_path(self):
prop = "data.text.input.path"
value = self.__yaml_context.get_value(prop)
return PathUtils(self.__yaml_context.path, value).get_relative_path()
@property
def text_in_path(self):
"""
.. note::
通过读取yaml文件的 ``data.text.input.path`` 属性,获取输入的文本yaml文件的绝对路径
:return: - 文本yaml文件的绝对路径
"""
return self.__text_in_path
[文档] def get_value(self, prop):
"""
.. note::
从yaml字典中,根据属性获取对应的值
加载多个yaml文件,排在后面的文件里的值,会覆盖前面的文件里的值
:param prop: 属性,用 ``.`` 分隔,例如,``pdbc.engine.url``
:return: - yaml字典中对应的值
"""
return self.__text_context.get_value(prop)
[文档] def get_yaml(self) -> dict:
"""
.. note::
获取从yaml文件中读取的数据,类型为dict
:return: - path为文件,返回一个字典,内容为yaml文件的内容
- path为目录,返回一个嵌套的字典
- key为yaml文件的绝对路径
- value为yaml文件的内容,是一个字典
"""
return self.__text_context.get_yaml()
[文档] def get_yaml_singleton(self):
"""
.. note::
获取从 ``data.text.input.path`` 属性中对应的路径下所有yaml文件中的数据,类型为dict
加载多个yaml文件,排在后面的文件里的值,会覆盖前面的文件里的值
:return: - 返回一个字典,内容为所有yaml文件的内容
"""
for i in self.get_yaml().values():
self.__yaml_singleton.update(i)
return self.__yaml_singleton
[文档] def get_yaml_singleton_str(self):
"""
.. note::
获取从 ``data.text.input.path`` 属性中对应的路径下所有yaml文件中的数据,类型为str,内容为一个字典
加载多个yaml文件,排在后面的文件里的值,会覆盖前面的文件里的值
:return: - 返回一个字符串,内容为一个字典,内容为所有yaml文件的内容
"""
return str(self.get_yaml_singleton())
def __new__(cls, *args, **kwargs):
if cls.__text_helper is None:
cls.__text_helper = object.__new__(cls)
return cls.__text_helper
[文档]class WordTemplate(AbstractBuilder, TextHelper, ImgHelper, DocxHelper):
"""
::
操作docx文件的模板
"""
@log
def __init__(self, tpl_name=None):
super().__init__()
self.content = {'page_break': R('\f')}
self.__tpl_name = tpl_name
self.__tpl = DocxTemplate(self.__get_tpl_name_abs())
def __get_tpl_name_abs(self):
if type(self._get_docx()) is list:
if self.__tpl_name is None:
self._logger: logging.Logger
self._logger.error("请输入模板的文件名")
else:
for i in self.get_docx_file_name():
if self.__tpl_name == i:
for j in self._get_docx():
if self.__tpl_name == os.path.split(j)[1]:
return j
else:
self._logger.error("模板的文件名不正确")
break
else:
return self._docx
[文档] def append(self, content):
"""
.. note::
添加其他的content到全局的content中
:param content: 其他的content,类型是 ``dict``
:return: - self
"""
self.content.update(content)
return self
[文档] def build(self, file_name=None):
"""
.. note::
创建docx文件
:param file_name: docx文件的文件名
"""
if file_name:
self.__tpl.render(self.content)
path = self.docx_out_path + self.path_separator + os.path.splitext(file_name)[0]
if os.path.exists(path):
shutil.rmtree(path)
os.mkdir(path)
img_path = path + self.path_separator + 'img'
if os.path.exists(img_path):
shutil.rmtree(img_path)
shutil.copytree(self.img_out_path, img_path)
self.__tpl.save(path + self.path_separator + file_name)
else:
self._logger: logging.Logger
self._logger.error('请输入输出的docx文件的文件名')
@property
def tpl(self):
"""
.. note::
获取 ``DocxTemplate`` 对象,
.. tip::
``DocxTemplate`` 对象的详细信息,请访问 `python-docx-template的文档 <https://docxtpl.readthedocs.io/>`_
:return: - ``DocxTemplate`` 对象
"""
return self.__tpl
[文档]class AbstractConverter(metaclass=ABCMeta):
"""
::
此类是用来实现的,可以将yaml模板中的插值进行转换
定义的方法可以为@staticmethod修饰的方法,不能有任何参数,也可以为由self一个参数构成的方法
"""
__yaml_dict: dict = {}
def __init__(self, word_tpl: WordTemplate):
self.word_tpl = word_tpl
[文档] def convert_to_str(self) -> str:
"""
.. note::
将读取的yaml模板中的内容转换为字符串
:return: - yaml模板中的内容,类型为字符串
"""
content = {}
data = self.word_tpl.get_yaml_singleton_str()
env = Environment()
parse_rs = env.parse(data)
for tag in meta.find_undeclared_variables(parse_rs):
content[tag] = getattr(self, tag)()
return Template(data).render(content)
[文档] def convert_to_dict(self) -> dict:
"""
.. note::
将读取的yaml模板中的内容转换为字典
:return: - yaml模板中的内容,类型为字典
"""
return ast.literal_eval(self.convert_to_str())
[文档] def get_value(self, prop):
"""
.. note::
从转换后的yaml字典中,根据属性获取对应的值
加载多个yaml文件,排在后面的文件里的值,会覆盖前面的文件里的值
:param prop: 属性,用 ``.`` 分隔,例如,``pdbc.engine.url``
:return: - 转换后的yaml字典中对应的值
"""
prop_list = prop.split('.')
if len(self.__yaml_dict) == 0:
yaml_dict = self.convert_to_dict()
self.__yaml_dict.update(yaml_dict)
value = None
temp_list = copy.deepcopy(prop_list)
v = YamlUtils().get_value(self.__yaml_dict, temp_list, prop, '')
if v is not None:
value = v
return value