pydocmaker 2.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydocmaker/__init__.py +192 -0
- pydocmaker/backend/__init__.py +0 -0
- pydocmaker/backend/baseformatter.py +173 -0
- pydocmaker/backend/ex_docx.py +174 -0
- pydocmaker/backend/ex_html.py +276 -0
- pydocmaker/backend/ex_ipynb.py +244 -0
- pydocmaker/backend/ex_markdown.py +100 -0
- pydocmaker/backend/ex_redmine.py +157 -0
- pydocmaker/backend/ex_tex.py +486 -0
- pydocmaker/backend/mdx_latex.py +675 -0
- pydocmaker/backend/pandoc_api.py +315 -0
- pydocmaker/backend/pdf_maker.py +256 -0
- pydocmaker/core.py +1686 -0
- pydocmaker/templating.py +377 -0
- pydocmaker/util.py +216 -0
- pydocmaker-2.2.9.dist-info/METADATA +413 -0
- pydocmaker-2.2.9.dist-info/RECORD +19 -0
- pydocmaker-2.2.9.dist-info/WHEEL +5 -0
- pydocmaker-2.2.9.dist-info/top_level.txt +1 -0
pydocmaker/__init__.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
__version__ = '2.2.9'
|
|
2
|
+
|
|
3
|
+
from pydocmaker.core import DocBuilder, construct, constr, buildingblocks, print_to_pdf, get_latex_compiler, set_latex_compiler, make_pdf_from_tex, show_pdf
|
|
4
|
+
from pydocmaker.util import upload_report_to_redmine, bcolors, txtcolor, colors_dc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from pydocmaker.backend.ex_tex import can_run_pandoc
|
|
8
|
+
from pydocmaker.backend.pdf_maker import get_all_installed_latex_compilers, get_latex_compiler
|
|
9
|
+
from pydocmaker.backend.pandoc_api import pandoc_convert_file, pandoc_set_allowed
|
|
10
|
+
|
|
11
|
+
from pydocmaker.core import DocBuilder as Doc
|
|
12
|
+
from pydocmaker.templating import DocTemplate, TemplateDirSource, register_new_template_dir, get_registered_template_dirs, get_available_template_ids, test_template_exists, remove_from_template_dir
|
|
13
|
+
|
|
14
|
+
from latex import escape as tex_escape
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
# tests and caches already if pandoc is installed when import is used, so its faster later when we want to use it (or not)
|
|
18
|
+
can_run_pandoc()
|
|
19
|
+
except Exception as err:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def pandoc_set_enabled():
|
|
23
|
+
"""short for pandoc_set_allowed(True), which will allow pandoc to be used as a valid conversion option"""
|
|
24
|
+
return pandoc_set_allowed(True)
|
|
25
|
+
|
|
26
|
+
def pandoc_set_disabled():
|
|
27
|
+
"""short for pandoc_set_allowed(False), which will disallow pandoc to be used as a valid conversion option"""
|
|
28
|
+
return pandoc_set_allowed(False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_schema():
|
|
32
|
+
return {k: getattr(constr, k)() for k in buildingblocks}
|
|
33
|
+
|
|
34
|
+
def get_example():
|
|
35
|
+
return Doc.get_example()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load(path):
|
|
39
|
+
"""Load a JSON file and return a DocBuilder object.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
path (str or file-like object): The path to the JSON file or a file-like object.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
DocBuilder: A DocBuilder object initialized with the loaded JSON data.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
json.JSONDecodeError: If the JSON file is not valid.
|
|
49
|
+
TypeError: If the loaded JSON object is not of type list.
|
|
50
|
+
"""
|
|
51
|
+
return DocBuilder.load_json(path)
|
|
52
|
+
|
|
53
|
+
def md2tex(children='', **kwargs):
|
|
54
|
+
"""convenience function to quickly convert markdown to tex
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
children (str, optional): the markdown string to convert. Defaults to ''.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
str: the corresponding tex string
|
|
61
|
+
"""
|
|
62
|
+
return Doc().add_md(children=children, **kwargs).to_tex(text_only=True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def mk_chapter(title, description, parent=None, order=None):
|
|
66
|
+
"""Creates a new chapter.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
title (str): The title of the chapter.
|
|
70
|
+
description (str): A brief description of the chapter.
|
|
71
|
+
parent (Chapter, optional): The parent chapter. Defaults to None.
|
|
72
|
+
order (int, optional): The order of the chapter. Defaults to None.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Chapter: The newly created chapter.
|
|
76
|
+
"""
|
|
77
|
+
return DocBuilder.add_chapter(title, description, parent, order)[0]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def mk_meta(project_name, version, description, author, author_email, url, license):
|
|
81
|
+
"""
|
|
82
|
+
Generate metadata for the documentation.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
project_name (str): The name of the project.
|
|
86
|
+
version (str): The version of the project.
|
|
87
|
+
description (str): A brief description of the project.
|
|
88
|
+
author (str): The author's name.
|
|
89
|
+
author_email (str): The author's email address.
|
|
90
|
+
url (str): The URL of the project.
|
|
91
|
+
license (str): The license of the project.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict: A dictionary containing the metadata.
|
|
95
|
+
"""
|
|
96
|
+
return DocBuilder.add_meta(project_name, version, description, author, author_email, url, license)[0]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def mk_tex(children=None, index=None, chapter=None, color='', end=None, **kwargs):
|
|
100
|
+
"""
|
|
101
|
+
Creates a new LaTeX document part.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
children (str or list, optional): The "children" for this element. Either text directly (as string) or a list of other parts.
|
|
105
|
+
index (int, optional): The index where to insert the part. If None, appends to the end.
|
|
106
|
+
chapter (str | int, optional): The chapter name or index where to insert the part. If None, appends to the end.
|
|
107
|
+
color (str, optional): Any color which can be rendered by HTML or LaTeX. Empty string for default.
|
|
108
|
+
end (str, optional): If you want to insert a different line ending (than the default) for this element set this argument to any string. None for default.
|
|
109
|
+
**kwargs: Additional keyword arguments for the document part.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict: The newly created LaTeX document part.
|
|
113
|
+
"""
|
|
114
|
+
return DocBuilder().add_tex(children=children, index=index, chapter=chapter, color=color, end=end, **kwargs)[0]
|
|
115
|
+
|
|
116
|
+
def mk_md(children=None, index=None, chapter=None, color='', end=None, **kwargs):
|
|
117
|
+
"""
|
|
118
|
+
Creates a new markdown document part.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
children (str or list, optional): The "children" for this element. Either text directly (as string) or a list of other parts.
|
|
122
|
+
index (int, optional): The index where to insert the part. If None, appends to the end.
|
|
123
|
+
chapter (str | int, optional): The chapter name or index where to insert the part. If None, appends to the end.
|
|
124
|
+
color (str, optional): Any color which can be rendered by html or latex. Empty string for default.
|
|
125
|
+
end (str, optional): If you want to insert a different line ending (than the default) for this element set this argument to any string. None for default.
|
|
126
|
+
**kwargs: Additional keyword arguments for the document part.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
DocBuilder: A new DocBuilder object with the added markdown document part.
|
|
130
|
+
"""
|
|
131
|
+
return DocBuilder().add_md(children=children, index=index, chapter=chapter, color=color, end=end, **kwargs)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def mk_pre(children=None, index=None, chapter=None, color='', end=None, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Creates a preformatted document part.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
children (str or list, optional): The "children" for this element. Either text directly (as string) or a list of other parts.
|
|
140
|
+
index (int, optional): The index where to insert the part. If None, appends to the end.
|
|
141
|
+
chapter (str | int, optional): The chapter name or index where to insert the part. If None, appends to the end.
|
|
142
|
+
color (str, optional): Any color which can be rendered by HTML or LaTeX. Empty string for default.
|
|
143
|
+
end (str, optional): If you want to insert a different line ending (than the default) for this element set this argument to any string. None for default.
|
|
144
|
+
**kwargs: Additional keyword arguments for the document part.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
dict: The created document part.
|
|
148
|
+
"""
|
|
149
|
+
return DocBuilder().add_pre(children=children, index=index, chapter=chapter, color=color, end=end, **kwargs)[0]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def mk_fig(fig=None, caption='', width=0.8, children=None, color='', end=None, **kwargs):
|
|
153
|
+
"""make an image document part from a pyplot figure type dict from given image input.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
fig (matplotlib figure, optional): the figure which to upload (or the current figure if None). Defaults to None.
|
|
157
|
+
caption (str, optional): the caption to give to the image. Defaults to ''.
|
|
158
|
+
width (float, optional): The width for the image to have in the document. Defaults to 0.8.
|
|
159
|
+
children (str, optional): A specific name/id to give to the image (will be auto generated if None). Defaults to None.
|
|
160
|
+
index (int, optional): The index where to insert the part. If None, appends to the end.
|
|
161
|
+
chapter (str | int, optional): The chapter name or index where to insert the part. If None, appends to the end.
|
|
162
|
+
color (str, optional): any color which can be rendered by html or latex. Empty string for default.
|
|
163
|
+
end (str, optional): If you want to insert a different line ending (than the default) for this element set this argument to any string. None for default.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
dict: The created document part.
|
|
167
|
+
"""
|
|
168
|
+
return DocBuilder().add_fig(fig=fig, caption=caption, width=width, children=children, color=color, end=end, **kwargs)[0]
|
|
169
|
+
|
|
170
|
+
def mk_image(image, caption='', width=0.8, children=None, color='', end=None, **kwargs):
|
|
171
|
+
"""make an image type dict from given image input.
|
|
172
|
+
image can be of type:
|
|
173
|
+
- pyplot figure
|
|
174
|
+
- link to download an image from
|
|
175
|
+
- filelike
|
|
176
|
+
- numpy NxMx1 or NxMx3 matrix
|
|
177
|
+
- PIL image
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
im (np.array): the image as NxMx
|
|
181
|
+
caption (str, optional): the caption to give to the image. Defaults to ''.
|
|
182
|
+
width (float, optional): The width for the image to have in the document. Defaults to 0.8.
|
|
183
|
+
children (str, optional): A specific name/id to give to the image (will be auto generated if None). Defaults to None.
|
|
184
|
+
index (int, optional): The index where to insert the part. If None, appends to the end.
|
|
185
|
+
chapter (str | int, optional): The chapter name or index where to insert the part. If None, appends to the end.
|
|
186
|
+
color (str, optional): any color which can be rendered by html or latex. Empty string for default.
|
|
187
|
+
end (str, optional): If you want to insert a different line ending (than the default) for this element set this argument to any string. None for default.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
return DocBuilder().add_image(image, caption, width, children, color, end, **kwargs)[0]
|
|
191
|
+
|
|
192
|
+
|
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import traceback
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from jinja2 import Template
|
|
8
|
+
|
|
9
|
+
def _handle_template(template, default_template):
|
|
10
|
+
if template is None:
|
|
11
|
+
template = default_template
|
|
12
|
+
|
|
13
|
+
attachments = {}
|
|
14
|
+
if hasattr(template, 'render'):
|
|
15
|
+
template_obj = template
|
|
16
|
+
elif isinstance(template, str) and os.path.exists(template):
|
|
17
|
+
with open(template, 'r') as fp:
|
|
18
|
+
template_obj = Template(fp.read())
|
|
19
|
+
elif isinstance(template, str) and not template:
|
|
20
|
+
template_obj = Template('{{ body }}')
|
|
21
|
+
elif isinstance(template, str):
|
|
22
|
+
template_obj = Template(template)
|
|
23
|
+
else:
|
|
24
|
+
raise KeyError(f'Unknown template type! {type(template)=}')
|
|
25
|
+
return template_obj, attachments
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseFormatter(abc.ABC):
|
|
30
|
+
|
|
31
|
+
default_linebreak = '\n\n'
|
|
32
|
+
|
|
33
|
+
def digest_iterator(self, **kwargs):
|
|
34
|
+
content = kwargs.get('children', kwargs.get('content'))
|
|
35
|
+
return f''.join([self.digest(c) for c in content])
|
|
36
|
+
|
|
37
|
+
def digest_str(self, el):
|
|
38
|
+
return str(el)
|
|
39
|
+
|
|
40
|
+
def digest_text(self, children='', **kwargs) -> list:
|
|
41
|
+
return self.digest(children) # will result in digest_str being called
|
|
42
|
+
|
|
43
|
+
def digest_line(self, **kwargs):
|
|
44
|
+
return self.digest_text(**kwargs)
|
|
45
|
+
|
|
46
|
+
def digest_meta(self, **kwargs):
|
|
47
|
+
return '' # meta element will not influence the rendering and is just ignored
|
|
48
|
+
|
|
49
|
+
@abc.abstractmethod
|
|
50
|
+
def digest_table(self, children=None, **kwargs) -> str:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@abc.abstractmethod
|
|
54
|
+
def digest_markdown(self, children='', **kwargs) -> str:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abc.abstractmethod
|
|
58
|
+
def digest_image(self, children='', width=0.8, caption='', imageblob='', **kwargs) -> str:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abc.abstractmethod
|
|
62
|
+
def digest_verbatim(self, children='', **kwargs) -> str:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abc.abstractmethod
|
|
66
|
+
def digest_latex(self, children:str, **kwargs):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def digest(self, children, **kwargs) -> str:
|
|
70
|
+
try:
|
|
71
|
+
|
|
72
|
+
if not children:
|
|
73
|
+
ret = ''
|
|
74
|
+
elif isinstance(children, str):
|
|
75
|
+
ret = self.digest_str(children)
|
|
76
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'meta':
|
|
77
|
+
ret = self.digest_meta(children=children, **kwargs)
|
|
78
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'table':
|
|
79
|
+
ret = self.digest_table(**children, **kwargs)
|
|
80
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'iter':
|
|
81
|
+
ret = self.digest_iterator(children=children, **kwargs)
|
|
82
|
+
elif isinstance(children, list) and children:
|
|
83
|
+
ret = self.digest_iterator(children=children, **kwargs)
|
|
84
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'image':
|
|
85
|
+
ret = self.digest_image(**children)
|
|
86
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'text':
|
|
87
|
+
ret = self.digest_text(**children)
|
|
88
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'latex':
|
|
89
|
+
ret = self.digest_latex(**children)
|
|
90
|
+
elif isinstance(children, dict) and children.get('typ', None) == 'line':
|
|
91
|
+
ret = self.digest_line(**children)
|
|
92
|
+
elif isinstance(children, dict) and 'typ' in children and children['typ'] == 'verbatim':
|
|
93
|
+
ret = self.digest_verbatim(**children)
|
|
94
|
+
elif isinstance(children, dict) and 'typ' in children and children['typ'] == 'markdown':
|
|
95
|
+
ret = self.digest_markdown(**children)
|
|
96
|
+
else:
|
|
97
|
+
ret = self.handle_error(f'the element of type {type(children)} {children=}, could not be parsed.', children)
|
|
98
|
+
|
|
99
|
+
if isinstance(ret, str):
|
|
100
|
+
linebreak = self.default_linebreak
|
|
101
|
+
if isinstance(children, dict):
|
|
102
|
+
tmp = children.get('end', None)
|
|
103
|
+
if not tmp is None:
|
|
104
|
+
linebreak = tmp
|
|
105
|
+
|
|
106
|
+
tmp = kwargs.get('end', None)
|
|
107
|
+
if not tmp is None:
|
|
108
|
+
linebreak = tmp
|
|
109
|
+
|
|
110
|
+
ret += linebreak
|
|
111
|
+
|
|
112
|
+
return ret
|
|
113
|
+
|
|
114
|
+
except Exception as err:
|
|
115
|
+
return self.handle_error(err, children)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def handle_error(self, err, el=None) -> list:
|
|
119
|
+
e = str(el)
|
|
120
|
+
if len(e) > 300:
|
|
121
|
+
e = e[:300] + f'... (n={len(e)-300} more chars hidden)'
|
|
122
|
+
|
|
123
|
+
txt = 'ERROR WHILE HANDLING ELEMENT:\n{}\n\n'.format(e)
|
|
124
|
+
if not isinstance(err, str):
|
|
125
|
+
txt += '\n'.join(traceback.format_exception(type(err), value=err, tb=err.__traceback__, limit=5))
|
|
126
|
+
else:
|
|
127
|
+
txt += err
|
|
128
|
+
return self.digest_verbatim(children=(txt + '\n'), color='red')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def format(self, doc:list) -> str:
|
|
132
|
+
if hasattr(doc, 'dump'):
|
|
133
|
+
doc = doc.dump()
|
|
134
|
+
if not isinstance(doc, list):
|
|
135
|
+
doc = [doc]
|
|
136
|
+
return ''.join([self.digest(p) for p in doc])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _map_table2mat(self, children=None, **kwargs) -> str:
|
|
141
|
+
if children is None:
|
|
142
|
+
children = [[]]
|
|
143
|
+
|
|
144
|
+
assert isinstance(children, (list, tuple)), f'children must be of type list! but was {type(children)=} {children=}'
|
|
145
|
+
header = kwargs.get('header', None)
|
|
146
|
+
header = list(header) if header else []
|
|
147
|
+
|
|
148
|
+
assert isinstance(header, (list, tuple)), f'header must be of type list! but was {type(header)=} {header=}'
|
|
149
|
+
data = list(children)
|
|
150
|
+
wrong_rows = [row for row in children if not isinstance(row, (list, tuple))]
|
|
151
|
+
assert not wrong_rows, f'all rows must be of type list! but found {wrong_rows=}'
|
|
152
|
+
|
|
153
|
+
n_rows = kwargs.get('n_rows', None)
|
|
154
|
+
if n_rows is None:
|
|
155
|
+
n_rows = len(data)
|
|
156
|
+
n_cols = kwargs.get('n_cols', None)
|
|
157
|
+
if n_cols is None:
|
|
158
|
+
n_cols = max(len(header), max([len(row) for row in data]))
|
|
159
|
+
|
|
160
|
+
head = [self.digest(el) for el in header]
|
|
161
|
+
if len(head) < n_cols:
|
|
162
|
+
head += ['']*(n_cols-len(head))
|
|
163
|
+
|
|
164
|
+
mat = []
|
|
165
|
+
for i in range(n_rows):
|
|
166
|
+
mat.append(['']*n_cols)
|
|
167
|
+
|
|
168
|
+
for irow, row in enumerate(data):
|
|
169
|
+
for icol, el in enumerate(row):
|
|
170
|
+
mat[irow][icol] = self.digest(el)
|
|
171
|
+
|
|
172
|
+
return head, mat
|
|
173
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
import io
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import docx
|
|
8
|
+
from docx.shared import Inches, Pt
|
|
9
|
+
|
|
10
|
+
import tempfile
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
import markdown
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from pydocmaker.backend.baseformatter import BaseFormatter
|
|
17
|
+
except Exception as err:
|
|
18
|
+
from .baseformatter import BaseFormatter
|
|
19
|
+
|
|
20
|
+
can_run_pandoc = lambda : False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from pydocmaker.backend.pandoc_api import can_run_pandoc, pandoc_convert, pandoc_convert_file
|
|
25
|
+
except Exception as err:
|
|
26
|
+
from .pandoc_api import can_run_pandoc, pandoc_convert, pandoc_convert_file
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from pydocmaker.backend.ex_html import convert as convert_html
|
|
30
|
+
except Exception as err:
|
|
31
|
+
from .ex_html import convert as convert_html
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def blue(run):
|
|
36
|
+
run.font.color.rgb = docx.shared.RGBColor(0, 0, 255)
|
|
37
|
+
|
|
38
|
+
def red(run):
|
|
39
|
+
run.font.color.rgb = docx.shared.RGBColor(255, 0, 0)
|
|
40
|
+
|
|
41
|
+
def convert_pandoc(doc:List[dict]) -> bytes:
|
|
42
|
+
|
|
43
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
44
|
+
html_file_path = os.path.join(temp_dir, 'temp.html')
|
|
45
|
+
docx_file_path = os.path.join(temp_dir, 'temp.docx')
|
|
46
|
+
|
|
47
|
+
with open(html_file_path, 'w', encoding='utf-8') as fp:
|
|
48
|
+
fp.write(convert_html(doc))
|
|
49
|
+
|
|
50
|
+
pandoc_convert_file(html_file_path, docx_file_path)
|
|
51
|
+
with open(docx_file_path, 'rb') as fp:
|
|
52
|
+
return fp.read()
|
|
53
|
+
|
|
54
|
+
def convert(doc:List[dict]) -> bytes:
|
|
55
|
+
|
|
56
|
+
if can_run_pandoc():
|
|
57
|
+
return convert_pandoc(doc)
|
|
58
|
+
else:
|
|
59
|
+
renderer = docx_renderer()
|
|
60
|
+
renderer.digest(doc)
|
|
61
|
+
return renderer.doc_to_bytes()
|
|
62
|
+
|
|
63
|
+
class docx_renderer(BaseFormatter):
|
|
64
|
+
def __init__(self, template_path:str=None, make_blue=False) -> None:
|
|
65
|
+
self.d = docx.Document(template_path)
|
|
66
|
+
self.make_blue = make_blue
|
|
67
|
+
|
|
68
|
+
def add_paragraph(self, newtext, *args, **kwargs):
|
|
69
|
+
new_paragraph = self.d.add_paragraph(newtext, *args, **kwargs)
|
|
70
|
+
if self.make_blue:
|
|
71
|
+
for r in new_paragraph.runs:
|
|
72
|
+
blue(r)
|
|
73
|
+
return new_paragraph
|
|
74
|
+
|
|
75
|
+
def add_run(self, text, *args, **kwargs):
|
|
76
|
+
if not self.d.paragraphs:
|
|
77
|
+
self.add_paragraph('')
|
|
78
|
+
|
|
79
|
+
last_paragraph = self.d.paragraphs[-1]
|
|
80
|
+
|
|
81
|
+
if not last_paragraph.runs:
|
|
82
|
+
last_run = last_paragraph.add_run(text)
|
|
83
|
+
else:
|
|
84
|
+
last_run = last_paragraph.runs[-1]
|
|
85
|
+
last_run.add_text(text)
|
|
86
|
+
|
|
87
|
+
if self.make_blue:
|
|
88
|
+
blue(last_run)
|
|
89
|
+
return last_run
|
|
90
|
+
|
|
91
|
+
def digest_text(self, children, *args, **kwargs):
|
|
92
|
+
return self.add_paragraph(children)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def digest_str(self, children, *args, **kwargs):
|
|
96
|
+
return self.add_run(children)
|
|
97
|
+
|
|
98
|
+
def digest_line(self, children, *args, **kwargs):
|
|
99
|
+
return self.add_run(children + '\n')
|
|
100
|
+
|
|
101
|
+
def digest_markdown(self, children, *args, **kwargs):
|
|
102
|
+
return self.add_paragraph(children, style='Normal')
|
|
103
|
+
|
|
104
|
+
def digest_verbatim(self, children, *args, **kwargs):
|
|
105
|
+
new_run = self.add_run(children)
|
|
106
|
+
new_run.font.name = 'Courier New' # Or any other monospace font
|
|
107
|
+
new_run.font.size = docx.shared.Pt(8) # Adjust font size as needed
|
|
108
|
+
return new_run
|
|
109
|
+
|
|
110
|
+
def digest_latex(self, children, *args, **kwargs):
|
|
111
|
+
new_run = self.add_run(children)
|
|
112
|
+
new_run.font.name = 'Courier New' # Or any other monospace font
|
|
113
|
+
new_run.font.size = docx.shared.Pt(8) # Adjust font size as needed
|
|
114
|
+
return new_run
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def handle_error(self, err, el=None) -> list:
|
|
118
|
+
if isinstance(err, BaseException):
|
|
119
|
+
traceback.print_exc(limit=5)
|
|
120
|
+
err = '\n'.join(traceback.format_exception(type(err), value=err, tb=err.__traceback__, limit=5))
|
|
121
|
+
|
|
122
|
+
new_run = self.add_run(err)
|
|
123
|
+
new_run.font.name = 'Courier New' # Or any other monospace font
|
|
124
|
+
new_run.font.size = docx.shared.Pt(8) # Adjust font size as needed
|
|
125
|
+
red(new_run)
|
|
126
|
+
return new_run
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def digest_iterator(self, children, *args, **kwargs):
|
|
130
|
+
if children:
|
|
131
|
+
return [self.digest(val, *args, **kwargs) for val in children]
|
|
132
|
+
return []
|
|
133
|
+
|
|
134
|
+
def digest_table(self, children=None, **kwargs) -> str:
|
|
135
|
+
self.handle_error(NotImplementedError(f'exporter of type {type(self)} can not handle tables'))
|
|
136
|
+
|
|
137
|
+
def digest_image(self, children, *args, **kwargs):
|
|
138
|
+
|
|
139
|
+
image_width = Inches(max(1, kwargs.get('width', 0.8)*5))
|
|
140
|
+
image_caption = kwargs.get('caption', '')
|
|
141
|
+
image_blob = kwargs.get('imageblob', '')
|
|
142
|
+
|
|
143
|
+
assert image_blob, 'no image data given!'
|
|
144
|
+
|
|
145
|
+
btsb64 = image_blob.split(',')[-1]
|
|
146
|
+
|
|
147
|
+
# Decode the base64 image
|
|
148
|
+
img_bytes = base64.b64decode(btsb64)
|
|
149
|
+
|
|
150
|
+
# Create an image stream from the bytes
|
|
151
|
+
image_stream = io.BytesIO(img_bytes)
|
|
152
|
+
|
|
153
|
+
picture = self.d.add_picture(image_stream, width=image_width)
|
|
154
|
+
# picture.width = image_width # Ensure fixed width
|
|
155
|
+
# picture.height = None # Adjust height automatically
|
|
156
|
+
picture.alignment = 1
|
|
157
|
+
|
|
158
|
+
run = self.add_paragraph(image_caption)
|
|
159
|
+
# run.style = 'Caption' # Apply the 'Caption' style for formatting
|
|
160
|
+
|
|
161
|
+
return run
|
|
162
|
+
|
|
163
|
+
def format(self, *args, **kwargs):
|
|
164
|
+
raise NotImplementedError('Can not format a docx document directly')
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def doc_to_bytes(self):
|
|
168
|
+
with io.BytesIO() as fp:
|
|
169
|
+
self.d.save(fp)
|
|
170
|
+
fp.seek(0)
|
|
171
|
+
return fp.read()
|
|
172
|
+
|
|
173
|
+
def save(self, filepath):
|
|
174
|
+
self.d.save(filepath)
|