ps-python-docx 1.3.0__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.
- docx/__init__.py +68 -0
- docx/api.py +37 -0
- docx/blkcntnr.py +101 -0
- docx/comments.py +163 -0
- docx/dml/__init__.py +0 -0
- docx/dml/color.py +112 -0
- docx/document.py +275 -0
- docx/drawing/__init__.py +59 -0
- docx/enum/__init__.py +0 -0
- docx/enum/base.py +150 -0
- docx/enum/dml.py +103 -0
- docx/enum/section.py +86 -0
- docx/enum/shape.py +19 -0
- docx/enum/style.py +452 -0
- docx/enum/table.py +136 -0
- docx/enum/text.py +367 -0
- docx/exceptions.py +18 -0
- docx/image/__init__.py +23 -0
- docx/image/bmp.py +43 -0
- docx/image/constants.py +172 -0
- docx/image/exceptions.py +13 -0
- docx/image/gif.py +38 -0
- docx/image/helpers.py +86 -0
- docx/image/image.py +234 -0
- docx/image/jpeg.py +425 -0
- docx/image/png.py +253 -0
- docx/image/tiff.py +289 -0
- docx/opc/__init__.py +0 -0
- docx/opc/constants.py +306 -0
- docx/opc/coreprops.py +142 -0
- docx/opc/exceptions.py +12 -0
- docx/opc/oxml.py +247 -0
- docx/opc/package.py +219 -0
- docx/opc/packuri.py +109 -0
- docx/opc/part.py +247 -0
- docx/opc/parts/__init__.py +0 -0
- docx/opc/parts/coreprops.py +48 -0
- docx/opc/phys_pkg.py +119 -0
- docx/opc/pkgreader.py +254 -0
- docx/opc/pkgwriter.py +115 -0
- docx/opc/rel.py +153 -0
- docx/opc/shared.py +31 -0
- docx/opc/spec.py +24 -0
- docx/oxml/__init__.py +261 -0
- docx/oxml/comments.py +124 -0
- docx/oxml/coreprops.py +298 -0
- docx/oxml/document.py +88 -0
- docx/oxml/drawing.py +11 -0
- docx/oxml/exceptions.py +10 -0
- docx/oxml/ns.py +109 -0
- docx/oxml/numbering.py +109 -0
- docx/oxml/parser.py +62 -0
- docx/oxml/section.py +537 -0
- docx/oxml/settings.py +138 -0
- docx/oxml/shape.py +299 -0
- docx/oxml/shared.py +52 -0
- docx/oxml/simpletypes.py +434 -0
- docx/oxml/styles.py +341 -0
- docx/oxml/table.py +977 -0
- docx/oxml/text/__init__.py +0 -0
- docx/oxml/text/font.py +333 -0
- docx/oxml/text/hyperlink.py +45 -0
- docx/oxml/text/pagebreak.py +278 -0
- docx/oxml/text/paragraph.py +106 -0
- docx/oxml/text/parfmt.py +392 -0
- docx/oxml/text/run.py +307 -0
- docx/oxml/xmlchemy.py +696 -0
- docx/package.py +110 -0
- docx/parts/__init__.py +0 -0
- docx/parts/comments.py +51 -0
- docx/parts/document.py +182 -0
- docx/parts/hdrftr.py +53 -0
- docx/parts/image.py +80 -0
- docx/parts/numbering.py +32 -0
- docx/parts/settings.py +50 -0
- docx/parts/story.py +95 -0
- docx/parts/styles.py +42 -0
- docx/parts/theme.py +53 -0
- docx/py.typed +0 -0
- docx/section.py +479 -0
- docx/settings.py +35 -0
- docx/shape.py +103 -0
- docx/shared.py +382 -0
- docx/styles/__init__.py +40 -0
- docx/styles/latent.py +198 -0
- docx/styles/style.py +264 -0
- docx/styles/styles.py +147 -0
- docx/table.py +537 -0
- docx/templates/default-comments.xml +12 -0
- docx/templates/default-docx-template/[Content_Types].xml +17 -0
- docx/templates/default-docx-template/_rels/.rels +7 -0
- docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
- docx/templates/default-docx-template/customXml/item1.xml +2 -0
- docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
- docx/templates/default-docx-template/docProps/app.xml +36 -0
- docx/templates/default-docx-template/docProps/core.xml +13 -0
- docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
- docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
- docx/templates/default-docx-template/word/document.xml +11 -0
- docx/templates/default-docx-template/word/fontTable.xml +61 -0
- docx/templates/default-docx-template/word/numbering.xml +201 -0
- docx/templates/default-docx-template/word/settings.xml +53 -0
- docx/templates/default-docx-template/word/styles.xml +11844 -0
- docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
- docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
- docx/templates/default-docx-template/word/webSettings.xml +5 -0
- docx/templates/default-footer.xml +27 -0
- docx/templates/default-header.xml +27 -0
- docx/templates/default-settings.xml +26 -0
- docx/templates/default-styles.xml +190 -0
- docx/templates/default.docx +0 -0
- docx/text/__init__.py +0 -0
- docx/text/font.py +472 -0
- docx/text/hyperlink.py +121 -0
- docx/text/pagebreak.py +104 -0
- docx/text/paragraph.py +173 -0
- docx/text/parfmt.py +286 -0
- docx/text/run.py +257 -0
- docx/text/tabstops.py +123 -0
- docx/theme.py +67 -0
- docx/types.py +34 -0
- ps_python_docx-1.3.0.dist-info/METADATA +77 -0
- ps_python_docx-1.3.0.dist-info/RECORD +126 -0
- ps_python_docx-1.3.0.dist-info/WHEEL +5 -0
- ps_python_docx-1.3.0.dist-info/licenses/LICENSE +20 -0
- ps_python_docx-1.3.0.dist-info/top_level.txt +1 -0
|
File without changes
|
docx/oxml/text/font.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# pyright: reportAssignmentType=false
|
|
2
|
+
|
|
3
|
+
"""Custom element classes related to run properties (font)."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Callable
|
|
8
|
+
|
|
9
|
+
from docx.enum.dml import MSO_THEME_COLOR
|
|
10
|
+
from docx.enum.text import WD_COLOR_INDEX, WD_UNDERLINE
|
|
11
|
+
from docx.oxml.ns import nsdecls
|
|
12
|
+
from docx.oxml.parser import parse_xml
|
|
13
|
+
from docx.oxml.simpletypes import (
|
|
14
|
+
ST_HexColor,
|
|
15
|
+
ST_HpsMeasure,
|
|
16
|
+
ST_String,
|
|
17
|
+
ST_VerticalAlignRun,
|
|
18
|
+
)
|
|
19
|
+
from docx.oxml.xmlchemy import (
|
|
20
|
+
BaseOxmlElement,
|
|
21
|
+
OptionalAttribute,
|
|
22
|
+
RequiredAttribute,
|
|
23
|
+
ZeroOrOne,
|
|
24
|
+
)
|
|
25
|
+
from docx.shared import RGBColor
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from docx.oxml.shared import CT_OnOff, CT_String
|
|
29
|
+
from docx.shared import Length
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CT_Color(BaseOxmlElement):
|
|
33
|
+
"""`w:color` element, specifying the color of a font and perhaps other objects."""
|
|
34
|
+
|
|
35
|
+
val: RGBColor | str = RequiredAttribute("w:val", ST_HexColor)
|
|
36
|
+
themeColor: MSO_THEME_COLOR | None = OptionalAttribute("w:themeColor", MSO_THEME_COLOR)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CT_Fonts(BaseOxmlElement):
|
|
40
|
+
"""`<w:rFonts>` element.
|
|
41
|
+
|
|
42
|
+
Specifies typeface name for the various language types.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
ascii: str | None = OptionalAttribute("w:ascii", ST_String)
|
|
46
|
+
hAnsi: str | None = OptionalAttribute("w:hAnsi", ST_String)
|
|
47
|
+
asciiTheme: str | None = OptionalAttribute("w:asciiTheme", ST_String)
|
|
48
|
+
hAnsiTheme: str | None = OptionalAttribute("w:hAnsiTheme", ST_String)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CT_Highlight(BaseOxmlElement):
|
|
52
|
+
"""`w:highlight` element, specifying font highlighting/background color."""
|
|
53
|
+
|
|
54
|
+
val: WD_COLOR_INDEX = RequiredAttribute("w:val", WD_COLOR_INDEX)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CT_HpsMeasure(BaseOxmlElement):
|
|
58
|
+
"""Used for `<w:sz>` element and others, specifying font size in half-points."""
|
|
59
|
+
|
|
60
|
+
val: Length = RequiredAttribute("w:val", ST_HpsMeasure)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CT_RPr(BaseOxmlElement):
|
|
64
|
+
"""`<w:rPr>` element, containing the properties for a run."""
|
|
65
|
+
|
|
66
|
+
get_or_add_color: Callable[[], CT_Color]
|
|
67
|
+
get_or_add_highlight: Callable[[], CT_Highlight]
|
|
68
|
+
get_or_add_rFonts: Callable[[], CT_Fonts]
|
|
69
|
+
get_or_add_sz: Callable[[], CT_HpsMeasure]
|
|
70
|
+
get_or_add_vertAlign: Callable[[], CT_VerticalAlignRun]
|
|
71
|
+
_add_rStyle: Callable[..., CT_String]
|
|
72
|
+
_add_u: Callable[[], CT_Underline]
|
|
73
|
+
_remove_color: Callable[[], None]
|
|
74
|
+
_remove_highlight: Callable[[], None]
|
|
75
|
+
_remove_rFonts: Callable[[], None]
|
|
76
|
+
_remove_rStyle: Callable[[], None]
|
|
77
|
+
_remove_sz: Callable[[], None]
|
|
78
|
+
_remove_u: Callable[[], None]
|
|
79
|
+
_remove_vertAlign: Callable[[], None]
|
|
80
|
+
|
|
81
|
+
_tag_seq = (
|
|
82
|
+
"w:rStyle",
|
|
83
|
+
"w:rFonts",
|
|
84
|
+
"w:b",
|
|
85
|
+
"w:bCs",
|
|
86
|
+
"w:i",
|
|
87
|
+
"w:iCs",
|
|
88
|
+
"w:caps",
|
|
89
|
+
"w:smallCaps",
|
|
90
|
+
"w:strike",
|
|
91
|
+
"w:dstrike",
|
|
92
|
+
"w:outline",
|
|
93
|
+
"w:shadow",
|
|
94
|
+
"w:emboss",
|
|
95
|
+
"w:imprint",
|
|
96
|
+
"w:noProof",
|
|
97
|
+
"w:snapToGrid",
|
|
98
|
+
"w:vanish",
|
|
99
|
+
"w:webHidden",
|
|
100
|
+
"w:color",
|
|
101
|
+
"w:spacing",
|
|
102
|
+
"w:w",
|
|
103
|
+
"w:kern",
|
|
104
|
+
"w:position",
|
|
105
|
+
"w:sz",
|
|
106
|
+
"w:szCs",
|
|
107
|
+
"w:highlight",
|
|
108
|
+
"w:u",
|
|
109
|
+
"w:effect",
|
|
110
|
+
"w:bdr",
|
|
111
|
+
"w:shd",
|
|
112
|
+
"w:fitText",
|
|
113
|
+
"w:vertAlign",
|
|
114
|
+
"w:rtl",
|
|
115
|
+
"w:cs",
|
|
116
|
+
"w:em",
|
|
117
|
+
"w:lang",
|
|
118
|
+
"w:eastAsianLayout",
|
|
119
|
+
"w:specVanish",
|
|
120
|
+
"w:oMath",
|
|
121
|
+
)
|
|
122
|
+
rStyle: CT_String | None = ZeroOrOne("w:rStyle", successors=_tag_seq[1:])
|
|
123
|
+
rFonts: CT_Fonts | None = ZeroOrOne("w:rFonts", successors=_tag_seq[2:])
|
|
124
|
+
b: CT_OnOff | None = ZeroOrOne("w:b", successors=_tag_seq[3:])
|
|
125
|
+
bCs = ZeroOrOne("w:bCs", successors=_tag_seq[4:])
|
|
126
|
+
i = ZeroOrOne("w:i", successors=_tag_seq[5:])
|
|
127
|
+
iCs = ZeroOrOne("w:iCs", successors=_tag_seq[6:])
|
|
128
|
+
caps = ZeroOrOne("w:caps", successors=_tag_seq[7:])
|
|
129
|
+
smallCaps = ZeroOrOne("w:smallCaps", successors=_tag_seq[8:])
|
|
130
|
+
strike = ZeroOrOne("w:strike", successors=_tag_seq[9:])
|
|
131
|
+
dstrike = ZeroOrOne("w:dstrike", successors=_tag_seq[10:])
|
|
132
|
+
outline = ZeroOrOne("w:outline", successors=_tag_seq[11:])
|
|
133
|
+
shadow = ZeroOrOne("w:shadow", successors=_tag_seq[12:])
|
|
134
|
+
emboss = ZeroOrOne("w:emboss", successors=_tag_seq[13:])
|
|
135
|
+
imprint = ZeroOrOne("w:imprint", successors=_tag_seq[14:])
|
|
136
|
+
noProof = ZeroOrOne("w:noProof", successors=_tag_seq[15:])
|
|
137
|
+
snapToGrid = ZeroOrOne("w:snapToGrid", successors=_tag_seq[16:])
|
|
138
|
+
vanish = ZeroOrOne("w:vanish", successors=_tag_seq[17:])
|
|
139
|
+
webHidden = ZeroOrOne("w:webHidden", successors=_tag_seq[18:])
|
|
140
|
+
color: CT_Color | None = ZeroOrOne("w:color", successors=_tag_seq[19:])
|
|
141
|
+
sz: CT_HpsMeasure | None = ZeroOrOne("w:sz", successors=_tag_seq[24:])
|
|
142
|
+
highlight: CT_Highlight | None = ZeroOrOne("w:highlight", successors=_tag_seq[26:])
|
|
143
|
+
u: CT_Underline | None = ZeroOrOne("w:u", successors=_tag_seq[27:])
|
|
144
|
+
vertAlign: CT_VerticalAlignRun | None = ZeroOrOne("w:vertAlign", successors=_tag_seq[32:])
|
|
145
|
+
rtl = ZeroOrOne("w:rtl", successors=_tag_seq[33:])
|
|
146
|
+
cs = ZeroOrOne("w:cs", successors=_tag_seq[34:])
|
|
147
|
+
specVanish = ZeroOrOne("w:specVanish", successors=_tag_seq[38:])
|
|
148
|
+
oMath = ZeroOrOne("w:oMath", successors=_tag_seq[39:])
|
|
149
|
+
del _tag_seq
|
|
150
|
+
|
|
151
|
+
def _new_color(self):
|
|
152
|
+
"""Override metaclass method to set `w:color/@val` to RGB black on create."""
|
|
153
|
+
return parse_xml('<w:color %s w:val="000000"/>' % nsdecls("w"))
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def highlight_val(self) -> WD_COLOR_INDEX | None:
|
|
157
|
+
"""Value of `./w:highlight/@val`.
|
|
158
|
+
|
|
159
|
+
Specifies font's highlight color, or `None` if the text is not highlighted.
|
|
160
|
+
"""
|
|
161
|
+
highlight = self.highlight
|
|
162
|
+
if highlight is None:
|
|
163
|
+
return None
|
|
164
|
+
return highlight.val
|
|
165
|
+
|
|
166
|
+
@highlight_val.setter
|
|
167
|
+
def highlight_val(self, value: WD_COLOR_INDEX | None) -> None:
|
|
168
|
+
if value is None:
|
|
169
|
+
self._remove_highlight()
|
|
170
|
+
return
|
|
171
|
+
highlight = self.get_or_add_highlight()
|
|
172
|
+
highlight.val = value
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def rFonts_ascii(self) -> str | None:
|
|
176
|
+
"""The value of `w:rFonts/@w:ascii` or |None| if not present.
|
|
177
|
+
|
|
178
|
+
Represents the assigned typeface name. The rFonts element also specifies other
|
|
179
|
+
special-case typeface names; this method handles the case where just the common
|
|
180
|
+
name is required.
|
|
181
|
+
"""
|
|
182
|
+
rFonts = self.rFonts
|
|
183
|
+
if rFonts is None:
|
|
184
|
+
return None
|
|
185
|
+
return rFonts.ascii
|
|
186
|
+
|
|
187
|
+
@rFonts_ascii.setter
|
|
188
|
+
def rFonts_ascii(self, value: str | None) -> None:
|
|
189
|
+
if value is None:
|
|
190
|
+
self._remove_rFonts()
|
|
191
|
+
return
|
|
192
|
+
rFonts = self.get_or_add_rFonts()
|
|
193
|
+
rFonts.ascii = value
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def rFonts_hAnsi(self) -> str | None:
|
|
197
|
+
"""The value of `w:rFonts/@w:hAnsi` or |None| if not present."""
|
|
198
|
+
rFonts = self.rFonts
|
|
199
|
+
if rFonts is None:
|
|
200
|
+
return None
|
|
201
|
+
return rFonts.hAnsi
|
|
202
|
+
|
|
203
|
+
@rFonts_hAnsi.setter
|
|
204
|
+
def rFonts_hAnsi(self, value: str | None):
|
|
205
|
+
if value is None and self.rFonts is None:
|
|
206
|
+
return
|
|
207
|
+
rFonts = self.get_or_add_rFonts()
|
|
208
|
+
rFonts.hAnsi = value
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def style(self) -> str | None:
|
|
212
|
+
"""String in `./w:rStyle/@val`, or None if `w:rStyle` is not present."""
|
|
213
|
+
rStyle = self.rStyle
|
|
214
|
+
if rStyle is None:
|
|
215
|
+
return None
|
|
216
|
+
return rStyle.val
|
|
217
|
+
|
|
218
|
+
@style.setter
|
|
219
|
+
def style(self, style: str | None) -> None:
|
|
220
|
+
"""Set `./w:rStyle/@val` to `style`, adding the `w:rStyle` element if necessary.
|
|
221
|
+
|
|
222
|
+
If `style` is |None|, remove `w:rStyle` element if present.
|
|
223
|
+
"""
|
|
224
|
+
if style is None:
|
|
225
|
+
self._remove_rStyle()
|
|
226
|
+
elif self.rStyle is None:
|
|
227
|
+
self._add_rStyle(val=style)
|
|
228
|
+
else:
|
|
229
|
+
self.rStyle.val = style
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def subscript(self) -> bool | None:
|
|
233
|
+
"""|True| if `./w:vertAlign/@w:val` is "subscript".
|
|
234
|
+
|
|
235
|
+
|False| if `w:vertAlign/@w:val` contains any other value. |None| if
|
|
236
|
+
`w:vertAlign` is not present.
|
|
237
|
+
"""
|
|
238
|
+
vertAlign = self.vertAlign
|
|
239
|
+
if vertAlign is None:
|
|
240
|
+
return None
|
|
241
|
+
return vertAlign.val == ST_VerticalAlignRun.SUBSCRIPT
|
|
242
|
+
|
|
243
|
+
@subscript.setter
|
|
244
|
+
def subscript(self, value: bool | None) -> None:
|
|
245
|
+
if value is None:
|
|
246
|
+
self._remove_vertAlign()
|
|
247
|
+
elif bool(value) is True:
|
|
248
|
+
self.get_or_add_vertAlign().val = ST_VerticalAlignRun.SUBSCRIPT
|
|
249
|
+
# -- assert bool(value) is False --
|
|
250
|
+
elif self.vertAlign is not None and self.vertAlign.val == ST_VerticalAlignRun.SUBSCRIPT:
|
|
251
|
+
self._remove_vertAlign()
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def superscript(self) -> bool | None:
|
|
255
|
+
"""|True| if `w:vertAlign/@w:val` is 'superscript'.
|
|
256
|
+
|
|
257
|
+
|False| if `w:vertAlign/@w:val` contains any other value. |None| if
|
|
258
|
+
`w:vertAlign` is not present.
|
|
259
|
+
"""
|
|
260
|
+
vertAlign = self.vertAlign
|
|
261
|
+
if vertAlign is None:
|
|
262
|
+
return None
|
|
263
|
+
return vertAlign.val == ST_VerticalAlignRun.SUPERSCRIPT
|
|
264
|
+
|
|
265
|
+
@superscript.setter
|
|
266
|
+
def superscript(self, value: bool | None):
|
|
267
|
+
if value is None:
|
|
268
|
+
self._remove_vertAlign()
|
|
269
|
+
elif bool(value) is True:
|
|
270
|
+
self.get_or_add_vertAlign().val = ST_VerticalAlignRun.SUPERSCRIPT
|
|
271
|
+
# -- assert bool(value) is False --
|
|
272
|
+
elif self.vertAlign is not None and self.vertAlign.val == ST_VerticalAlignRun.SUPERSCRIPT:
|
|
273
|
+
self._remove_vertAlign()
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def sz_val(self) -> Length | None:
|
|
277
|
+
"""The value of `w:sz/@w:val` or |None| if not present."""
|
|
278
|
+
sz = self.sz
|
|
279
|
+
if sz is None:
|
|
280
|
+
return None
|
|
281
|
+
return sz.val
|
|
282
|
+
|
|
283
|
+
@sz_val.setter
|
|
284
|
+
def sz_val(self, value: Length | None):
|
|
285
|
+
if value is None:
|
|
286
|
+
self._remove_sz()
|
|
287
|
+
return
|
|
288
|
+
sz = self.get_or_add_sz()
|
|
289
|
+
sz.val = value
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def u_val(self) -> WD_UNDERLINE | None:
|
|
293
|
+
"""Value of `w:u/@val`, or None if not present.
|
|
294
|
+
|
|
295
|
+
Values `WD_UNDERLINE.SINGLE` and `WD_UNDERLINE.NONE` are mapped to `True` and
|
|
296
|
+
`False` respectively.
|
|
297
|
+
"""
|
|
298
|
+
u = self.u
|
|
299
|
+
if u is None:
|
|
300
|
+
return None
|
|
301
|
+
return u.val
|
|
302
|
+
|
|
303
|
+
@u_val.setter
|
|
304
|
+
def u_val(self, value: WD_UNDERLINE | None):
|
|
305
|
+
self._remove_u()
|
|
306
|
+
if value is not None:
|
|
307
|
+
self._add_u().val = value
|
|
308
|
+
|
|
309
|
+
def _get_bool_val(self, name: str) -> bool | None:
|
|
310
|
+
"""Value of boolean child with `name`, e.g. "w:b", "w:i", and "w:smallCaps"."""
|
|
311
|
+
element = getattr(self, name)
|
|
312
|
+
if element is None:
|
|
313
|
+
return None
|
|
314
|
+
return element.val
|
|
315
|
+
|
|
316
|
+
def _set_bool_val(self, name: str, value: bool | None):
|
|
317
|
+
if value is None:
|
|
318
|
+
getattr(self, "_remove_%s" % name)()
|
|
319
|
+
return
|
|
320
|
+
element = getattr(self, "get_or_add_%s" % name)()
|
|
321
|
+
element.val = value
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class CT_Underline(BaseOxmlElement):
|
|
325
|
+
"""`<w:u>` element, specifying the underlining style for a run."""
|
|
326
|
+
|
|
327
|
+
val: WD_UNDERLINE | None = OptionalAttribute("w:val", WD_UNDERLINE)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class CT_VerticalAlignRun(BaseOxmlElement):
|
|
331
|
+
"""`<w:vertAlign>` element, specifying subscript or superscript."""
|
|
332
|
+
|
|
333
|
+
val: str = RequiredAttribute("w:val", ST_VerticalAlignRun)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Custom element classes related to hyperlinks (CT_Hyperlink)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, List
|
|
6
|
+
|
|
7
|
+
from docx.oxml.simpletypes import ST_OnOff, ST_String, XsdString
|
|
8
|
+
from docx.oxml.text.run import CT_R
|
|
9
|
+
from docx.oxml.xmlchemy import (
|
|
10
|
+
BaseOxmlElement,
|
|
11
|
+
OptionalAttribute,
|
|
12
|
+
ZeroOrMore,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CT_Hyperlink(BaseOxmlElement):
|
|
20
|
+
"""`<w:hyperlink>` element, containing the text and address for a hyperlink."""
|
|
21
|
+
|
|
22
|
+
r_lst: List[CT_R]
|
|
23
|
+
|
|
24
|
+
rId: str | None = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
|
|
25
|
+
anchor: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
26
|
+
"w:anchor", ST_String
|
|
27
|
+
)
|
|
28
|
+
history: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
29
|
+
"w:history", ST_OnOff, default=True
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
r = ZeroOrMore("w:r")
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]:
|
|
36
|
+
"""All `w:lastRenderedPageBreak` descendants of this hyperlink."""
|
|
37
|
+
return self.xpath("./w:r/w:lastRenderedPageBreak")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
41
|
+
"""The textual content of this hyperlink.
|
|
42
|
+
|
|
43
|
+
`CT_Hyperlink` stores the hyperlink-text as one or more `w:r` children.
|
|
44
|
+
"""
|
|
45
|
+
return "".join(r.text for r in self.xpath("w:r"))
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Custom element class for rendered page-break (CT_LastRenderedPageBreak)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from docx.oxml.xmlchemy import BaseOxmlElement
|
|
9
|
+
from docx.shared import lazyproperty
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from docx.oxml.text.hyperlink import CT_Hyperlink
|
|
13
|
+
from docx.oxml.text.paragraph import CT_P
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CT_LastRenderedPageBreak(BaseOxmlElement):
|
|
17
|
+
"""`<w:lastRenderedPageBreak>` element, indicating page break inserted by renderer.
|
|
18
|
+
|
|
19
|
+
A rendered page-break is one inserted by the renderer when it runs out of room on a
|
|
20
|
+
page. It is an empty element (no attrs or children) and is a child of CT_R, peer to
|
|
21
|
+
CT_Text.
|
|
22
|
+
|
|
23
|
+
NOTE: this complex-type name does not exist in the schema, where
|
|
24
|
+
`w:lastRenderedPageBreak` maps to `CT_Empty`. This name was added to give it
|
|
25
|
+
distinguished behavior. CT_Empty is used for many elements.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def following_fragment_p(self) -> CT_P:
|
|
30
|
+
"""A "loose" `CT_P` containing only the paragraph content before this break.
|
|
31
|
+
|
|
32
|
+
Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered
|
|
33
|
+
page-break in its paragraph.
|
|
34
|
+
|
|
35
|
+
The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this
|
|
36
|
+
page-break with this `w:lastRenderedPageBreak` element and all content preceding
|
|
37
|
+
it removed.
|
|
38
|
+
|
|
39
|
+
NOTE: this `w:p` can itself contain one or more `w:renderedPageBreak` elements
|
|
40
|
+
(when the paragraph contained more than one). While this is rare, the caller
|
|
41
|
+
should treat this paragraph the same as other paragraphs and split it if
|
|
42
|
+
necessary in a folloing step or recursion.
|
|
43
|
+
"""
|
|
44
|
+
if not self == self._first_lrpb_in_p(self._enclosing_p):
|
|
45
|
+
raise ValueError("only defined on first rendered page-break in paragraph")
|
|
46
|
+
|
|
47
|
+
# -- splitting approach is different when break is inside a hyperlink --
|
|
48
|
+
return (
|
|
49
|
+
self._following_frag_in_hlink if self._is_in_hyperlink else self._following_frag_in_run
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def follows_all_content(self) -> bool:
|
|
54
|
+
"""True when this page-break element is the last "content" in the paragraph.
|
|
55
|
+
|
|
56
|
+
This is very uncommon case and may only occur in contrived or cases where the
|
|
57
|
+
XML is edited by hand, but it is not precluded by the spec.
|
|
58
|
+
"""
|
|
59
|
+
# -- a page-break inside a hyperlink never meets these criteria (for our
|
|
60
|
+
# -- purposes at least) because it is considered "atomic" and always associated
|
|
61
|
+
# -- with the page it starts on.
|
|
62
|
+
if self._is_in_hyperlink:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
return bool(
|
|
66
|
+
# -- XPath will match zero-or-one w:lastRenderedPageBreak element --
|
|
67
|
+
self._enclosing_p.xpath(
|
|
68
|
+
# -- in first run of paragraph --
|
|
69
|
+
f"(./w:r)[last()]"
|
|
70
|
+
# -- all page-breaks --
|
|
71
|
+
f"/w:lastRenderedPageBreak"
|
|
72
|
+
# -- that are not preceded by any content-bearing elements --
|
|
73
|
+
f"[not(following-sibling::*[{self._run_inner_content_xpath}])]"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def precedes_all_content(self) -> bool:
|
|
79
|
+
"""True when a `w:lastRenderedPageBreak` precedes all paragraph content.
|
|
80
|
+
|
|
81
|
+
This is a common case; it occurs whenever the page breaks on an even paragraph
|
|
82
|
+
boundary.
|
|
83
|
+
"""
|
|
84
|
+
# -- a page-break inside a hyperlink never meets these criteria because there
|
|
85
|
+
# -- is always part of the hyperlink text before the page-break.
|
|
86
|
+
if self._is_in_hyperlink:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return bool(
|
|
90
|
+
# -- XPath will match zero-or-one w:lastRenderedPageBreak element --
|
|
91
|
+
self._enclosing_p.xpath(
|
|
92
|
+
# -- in first run of paragraph --
|
|
93
|
+
f"./w:r[1]"
|
|
94
|
+
# -- all page-breaks --
|
|
95
|
+
f"/w:lastRenderedPageBreak"
|
|
96
|
+
# -- that are not preceded by any content-bearing elements --
|
|
97
|
+
f"[not(preceding-sibling::*[{self._run_inner_content_xpath}])]"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def preceding_fragment_p(self) -> CT_P:
|
|
103
|
+
"""A "loose" `CT_P` containing only the paragraph content before this break.
|
|
104
|
+
|
|
105
|
+
Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered
|
|
106
|
+
paragraph in its paragraph.
|
|
107
|
+
|
|
108
|
+
The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this
|
|
109
|
+
page-break with this `w:lastRenderedPageBreak` element and all its following
|
|
110
|
+
siblings removed.
|
|
111
|
+
"""
|
|
112
|
+
if not self == self._first_lrpb_in_p(self._enclosing_p):
|
|
113
|
+
raise ValueError("only defined on first rendered page-break in paragraph")
|
|
114
|
+
|
|
115
|
+
# -- splitting approach is different when break is inside a hyperlink --
|
|
116
|
+
return (
|
|
117
|
+
self._preceding_frag_in_hlink if self._is_in_hyperlink else self._preceding_frag_in_run
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _enclosing_hyperlink(self, lrpb: CT_LastRenderedPageBreak) -> CT_Hyperlink:
|
|
121
|
+
"""The `w:hyperlink` grandparent of this `w:lastRenderedPageBreak`.
|
|
122
|
+
|
|
123
|
+
Raises `IndexError` when this page-break has a `w:p` grandparent, so only call
|
|
124
|
+
when `._is_in_hyperlink` is True.
|
|
125
|
+
"""
|
|
126
|
+
return lrpb.xpath("./parent::w:r/parent::w:hyperlink")[0]
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def _enclosing_p(self) -> CT_P:
|
|
130
|
+
"""The `w:p` element parent or grandparent of this `w:lastRenderedPageBreak`."""
|
|
131
|
+
return self.xpath("./ancestor::w:p[1]")[0]
|
|
132
|
+
|
|
133
|
+
def _first_lrpb_in_p(self, p: CT_P) -> CT_LastRenderedPageBreak:
|
|
134
|
+
"""The first `w:lastRenderedPageBreak` element in `p`.
|
|
135
|
+
|
|
136
|
+
Raises `ValueError` if there are no rendered page-breaks in `p`.
|
|
137
|
+
"""
|
|
138
|
+
lrpbs = p.xpath("./w:r/w:lastRenderedPageBreak | ./w:hyperlink/w:r/w:lastRenderedPageBreak")
|
|
139
|
+
if not lrpbs:
|
|
140
|
+
raise ValueError("no rendered page-breaks in paragraph element")
|
|
141
|
+
return lrpbs[0]
|
|
142
|
+
|
|
143
|
+
@lazyproperty
|
|
144
|
+
def _following_frag_in_hlink(self) -> CT_P:
|
|
145
|
+
"""Following CT_P fragment when break occurs within a hyperlink.
|
|
146
|
+
|
|
147
|
+
Note this is a *partial-function* and raises when `lrpb` is not inside a
|
|
148
|
+
hyperlink.
|
|
149
|
+
"""
|
|
150
|
+
if not self._is_in_hyperlink:
|
|
151
|
+
raise ValueError("only defined on a rendered page-break in a hyperlink")
|
|
152
|
+
|
|
153
|
+
# -- work on a clone `w:p` so our mutations don't persist --
|
|
154
|
+
p = copy.deepcopy(self._enclosing_p)
|
|
155
|
+
|
|
156
|
+
# -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) --
|
|
157
|
+
lrpb = self._first_lrpb_in_p(p)
|
|
158
|
+
|
|
159
|
+
# -- locate `w:hyperlink` in which this `w:lastRenderedPageBreak` is found --
|
|
160
|
+
hyperlink = lrpb._enclosing_hyperlink(lrpb)
|
|
161
|
+
|
|
162
|
+
# -- delete all w:p inner-content preceding the hyperlink --
|
|
163
|
+
for e in hyperlink.xpath("./preceding-sibling::*[not(self::w:pPr)]"):
|
|
164
|
+
p.remove(e)
|
|
165
|
+
|
|
166
|
+
# -- remove the whole hyperlink, it belongs to the preceding-fragment-p --
|
|
167
|
+
hyperlink.getparent().remove(hyperlink)
|
|
168
|
+
|
|
169
|
+
# -- that's it, return the remaining fragment of `w:p` clone --
|
|
170
|
+
return p
|
|
171
|
+
|
|
172
|
+
@lazyproperty
|
|
173
|
+
def _following_frag_in_run(self) -> CT_P:
|
|
174
|
+
"""following CT_P fragment when break does not occur in a hyperlink.
|
|
175
|
+
|
|
176
|
+
Note this is a *partial-function* and raises when `lrpb` is inside a hyperlink.
|
|
177
|
+
"""
|
|
178
|
+
if self._is_in_hyperlink:
|
|
179
|
+
raise ValueError("only defined on a rendered page-break not in a hyperlink")
|
|
180
|
+
|
|
181
|
+
# -- work on a clone `w:p` so our mutations don't persist --
|
|
182
|
+
p = copy.deepcopy(self._enclosing_p)
|
|
183
|
+
|
|
184
|
+
# -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) --
|
|
185
|
+
lrpb = self._first_lrpb_in_p(p)
|
|
186
|
+
|
|
187
|
+
# -- locate `w:r` in which this `w:lastRenderedPageBreak` is found --
|
|
188
|
+
enclosing_r = lrpb.xpath("./parent::w:r")[0]
|
|
189
|
+
|
|
190
|
+
# -- delete all w:p inner-content preceding that run (but not w:pPr) --
|
|
191
|
+
for e in enclosing_r.xpath("./preceding-sibling::*[not(self::w:pPr)]"):
|
|
192
|
+
p.remove(e)
|
|
193
|
+
|
|
194
|
+
# -- then remove all run inner-content preceding this lrpb in its run (but not
|
|
195
|
+
# -- the `w:rPr`) and also remove the page-break itself
|
|
196
|
+
for e in lrpb.xpath("./preceding-sibling::*[not(self::w:rPr)]"):
|
|
197
|
+
enclosing_r.remove(e)
|
|
198
|
+
enclosing_r.remove(lrpb)
|
|
199
|
+
|
|
200
|
+
return p
|
|
201
|
+
|
|
202
|
+
@lazyproperty
|
|
203
|
+
def _is_in_hyperlink(self) -> bool:
|
|
204
|
+
"""True when this page-break is embedded in a hyperlink run."""
|
|
205
|
+
return bool(self.xpath("./parent::w:r/parent::w:hyperlink"))
|
|
206
|
+
|
|
207
|
+
@lazyproperty
|
|
208
|
+
def _preceding_frag_in_hlink(self) -> CT_P:
|
|
209
|
+
"""Preceding CT_P fragment when break occurs within a hyperlink.
|
|
210
|
+
|
|
211
|
+
Note this is a *partial-function* and raises when `lrpb` is not inside a
|
|
212
|
+
hyperlink.
|
|
213
|
+
"""
|
|
214
|
+
if not self._is_in_hyperlink:
|
|
215
|
+
raise ValueError("only defined on a rendered page-break in a hyperlink")
|
|
216
|
+
|
|
217
|
+
# -- work on a clone `w:p` so our mutations don't persist --
|
|
218
|
+
p = copy.deepcopy(self._enclosing_p)
|
|
219
|
+
|
|
220
|
+
# -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) --
|
|
221
|
+
lrpb = self._first_lrpb_in_p(p)
|
|
222
|
+
|
|
223
|
+
# -- locate `w:hyperlink` in which this `w:lastRenderedPageBreak` is found --
|
|
224
|
+
hyperlink = lrpb._enclosing_hyperlink(lrpb)
|
|
225
|
+
|
|
226
|
+
# -- delete all w:p inner-content following the hyperlink --
|
|
227
|
+
for e in hyperlink.xpath("./following-sibling::*"):
|
|
228
|
+
p.remove(e)
|
|
229
|
+
|
|
230
|
+
# -- remove this page-break from inside the hyperlink --
|
|
231
|
+
lrpb.getparent().remove(lrpb)
|
|
232
|
+
|
|
233
|
+
# -- that's it, the entire hyperlink goes into the preceding fragment so
|
|
234
|
+
# -- the hyperlink is not "split".
|
|
235
|
+
return p
|
|
236
|
+
|
|
237
|
+
@lazyproperty
|
|
238
|
+
def _preceding_frag_in_run(self) -> CT_P:
|
|
239
|
+
"""Preceding CT_P fragment when break does not occur in a hyperlink.
|
|
240
|
+
|
|
241
|
+
Note this is a *partial-function* and raises when `lrpb` is inside a hyperlink.
|
|
242
|
+
"""
|
|
243
|
+
if self._is_in_hyperlink:
|
|
244
|
+
raise ValueError("only defined on a rendered page-break not in a hyperlink")
|
|
245
|
+
|
|
246
|
+
# -- work on a clone `w:p` so our mutations don't persist --
|
|
247
|
+
p = copy.deepcopy(self._enclosing_p)
|
|
248
|
+
|
|
249
|
+
# -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) --
|
|
250
|
+
lrpb = self._first_lrpb_in_p(p)
|
|
251
|
+
|
|
252
|
+
# -- locate `w:r` in which this `w:lastRenderedPageBreak` is found --
|
|
253
|
+
enclosing_r = lrpb.xpath("./parent::w:r")[0]
|
|
254
|
+
|
|
255
|
+
# -- delete all `w:p` inner-content following that run --
|
|
256
|
+
for e in enclosing_r.xpath("./following-sibling::*"):
|
|
257
|
+
p.remove(e)
|
|
258
|
+
|
|
259
|
+
# -- then delete all `w:r` inner-content following this lrpb in its run and
|
|
260
|
+
# -- also remove the page-break itself
|
|
261
|
+
for e in lrpb.xpath("./following-sibling::*"):
|
|
262
|
+
enclosing_r.remove(e)
|
|
263
|
+
enclosing_r.remove(lrpb)
|
|
264
|
+
|
|
265
|
+
return p
|
|
266
|
+
|
|
267
|
+
@lazyproperty
|
|
268
|
+
def _run_inner_content_xpath(self) -> str:
|
|
269
|
+
"""XPath fragment matching any run inner-content elements."""
|
|
270
|
+
return (
|
|
271
|
+
"self::w:br"
|
|
272
|
+
" | self::w:cr"
|
|
273
|
+
" | self::w:drawing"
|
|
274
|
+
" | self::w:noBreakHyphen"
|
|
275
|
+
" | self::w:ptab"
|
|
276
|
+
" | self::w:t"
|
|
277
|
+
" | self::w:tab"
|
|
278
|
+
)
|