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.
Files changed (126) hide show
  1. docx/__init__.py +68 -0
  2. docx/api.py +37 -0
  3. docx/blkcntnr.py +101 -0
  4. docx/comments.py +163 -0
  5. docx/dml/__init__.py +0 -0
  6. docx/dml/color.py +112 -0
  7. docx/document.py +275 -0
  8. docx/drawing/__init__.py +59 -0
  9. docx/enum/__init__.py +0 -0
  10. docx/enum/base.py +150 -0
  11. docx/enum/dml.py +103 -0
  12. docx/enum/section.py +86 -0
  13. docx/enum/shape.py +19 -0
  14. docx/enum/style.py +452 -0
  15. docx/enum/table.py +136 -0
  16. docx/enum/text.py +367 -0
  17. docx/exceptions.py +18 -0
  18. docx/image/__init__.py +23 -0
  19. docx/image/bmp.py +43 -0
  20. docx/image/constants.py +172 -0
  21. docx/image/exceptions.py +13 -0
  22. docx/image/gif.py +38 -0
  23. docx/image/helpers.py +86 -0
  24. docx/image/image.py +234 -0
  25. docx/image/jpeg.py +425 -0
  26. docx/image/png.py +253 -0
  27. docx/image/tiff.py +289 -0
  28. docx/opc/__init__.py +0 -0
  29. docx/opc/constants.py +306 -0
  30. docx/opc/coreprops.py +142 -0
  31. docx/opc/exceptions.py +12 -0
  32. docx/opc/oxml.py +247 -0
  33. docx/opc/package.py +219 -0
  34. docx/opc/packuri.py +109 -0
  35. docx/opc/part.py +247 -0
  36. docx/opc/parts/__init__.py +0 -0
  37. docx/opc/parts/coreprops.py +48 -0
  38. docx/opc/phys_pkg.py +119 -0
  39. docx/opc/pkgreader.py +254 -0
  40. docx/opc/pkgwriter.py +115 -0
  41. docx/opc/rel.py +153 -0
  42. docx/opc/shared.py +31 -0
  43. docx/opc/spec.py +24 -0
  44. docx/oxml/__init__.py +261 -0
  45. docx/oxml/comments.py +124 -0
  46. docx/oxml/coreprops.py +298 -0
  47. docx/oxml/document.py +88 -0
  48. docx/oxml/drawing.py +11 -0
  49. docx/oxml/exceptions.py +10 -0
  50. docx/oxml/ns.py +109 -0
  51. docx/oxml/numbering.py +109 -0
  52. docx/oxml/parser.py +62 -0
  53. docx/oxml/section.py +537 -0
  54. docx/oxml/settings.py +138 -0
  55. docx/oxml/shape.py +299 -0
  56. docx/oxml/shared.py +52 -0
  57. docx/oxml/simpletypes.py +434 -0
  58. docx/oxml/styles.py +341 -0
  59. docx/oxml/table.py +977 -0
  60. docx/oxml/text/__init__.py +0 -0
  61. docx/oxml/text/font.py +333 -0
  62. docx/oxml/text/hyperlink.py +45 -0
  63. docx/oxml/text/pagebreak.py +278 -0
  64. docx/oxml/text/paragraph.py +106 -0
  65. docx/oxml/text/parfmt.py +392 -0
  66. docx/oxml/text/run.py +307 -0
  67. docx/oxml/xmlchemy.py +696 -0
  68. docx/package.py +110 -0
  69. docx/parts/__init__.py +0 -0
  70. docx/parts/comments.py +51 -0
  71. docx/parts/document.py +182 -0
  72. docx/parts/hdrftr.py +53 -0
  73. docx/parts/image.py +80 -0
  74. docx/parts/numbering.py +32 -0
  75. docx/parts/settings.py +50 -0
  76. docx/parts/story.py +95 -0
  77. docx/parts/styles.py +42 -0
  78. docx/parts/theme.py +53 -0
  79. docx/py.typed +0 -0
  80. docx/section.py +479 -0
  81. docx/settings.py +35 -0
  82. docx/shape.py +103 -0
  83. docx/shared.py +382 -0
  84. docx/styles/__init__.py +40 -0
  85. docx/styles/latent.py +198 -0
  86. docx/styles/style.py +264 -0
  87. docx/styles/styles.py +147 -0
  88. docx/table.py +537 -0
  89. docx/templates/default-comments.xml +12 -0
  90. docx/templates/default-docx-template/[Content_Types].xml +17 -0
  91. docx/templates/default-docx-template/_rels/.rels +7 -0
  92. docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
  93. docx/templates/default-docx-template/customXml/item1.xml +2 -0
  94. docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
  95. docx/templates/default-docx-template/docProps/app.xml +36 -0
  96. docx/templates/default-docx-template/docProps/core.xml +13 -0
  97. docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
  98. docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
  99. docx/templates/default-docx-template/word/document.xml +11 -0
  100. docx/templates/default-docx-template/word/fontTable.xml +61 -0
  101. docx/templates/default-docx-template/word/numbering.xml +201 -0
  102. docx/templates/default-docx-template/word/settings.xml +53 -0
  103. docx/templates/default-docx-template/word/styles.xml +11844 -0
  104. docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
  105. docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
  106. docx/templates/default-docx-template/word/webSettings.xml +5 -0
  107. docx/templates/default-footer.xml +27 -0
  108. docx/templates/default-header.xml +27 -0
  109. docx/templates/default-settings.xml +26 -0
  110. docx/templates/default-styles.xml +190 -0
  111. docx/templates/default.docx +0 -0
  112. docx/text/__init__.py +0 -0
  113. docx/text/font.py +472 -0
  114. docx/text/hyperlink.py +121 -0
  115. docx/text/pagebreak.py +104 -0
  116. docx/text/paragraph.py +173 -0
  117. docx/text/parfmt.py +286 -0
  118. docx/text/run.py +257 -0
  119. docx/text/tabstops.py +123 -0
  120. docx/theme.py +67 -0
  121. docx/types.py +34 -0
  122. ps_python_docx-1.3.0.dist-info/METADATA +77 -0
  123. ps_python_docx-1.3.0.dist-info/RECORD +126 -0
  124. ps_python_docx-1.3.0.dist-info/WHEEL +5 -0
  125. ps_python_docx-1.3.0.dist-info/licenses/LICENSE +20 -0
  126. 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
+ )