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
docx/text/font.py ADDED
@@ -0,0 +1,472 @@
1
+ """Font-related proxy objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, Literal
6
+
7
+ from docx.dml.color import ColorFormat
8
+ from docx.enum.text import WD_UNDERLINE
9
+ from docx.shared import ElementProxy, Emu
10
+
11
+ if TYPE_CHECKING:
12
+ from docx.enum.text import WD_COLOR_INDEX
13
+ from docx.oxml.text.run import CT_R
14
+ from docx.shared import Length
15
+
16
+
17
+ class Font(ElementProxy):
18
+ """Proxy object for parent of a `<w:rPr>` element and providing access to
19
+ character properties such as font name, font size, bold, and subscript."""
20
+
21
+ def __init__(self, r: CT_R, parent: Any | None = None):
22
+ super().__init__(r, parent)
23
+ self._element = r
24
+ self._r = r
25
+
26
+ @property
27
+ def all_caps(self) -> bool | None:
28
+ """Read/write.
29
+
30
+ Causes text in this font to appear in capital letters.
31
+ """
32
+ return self._get_bool_prop("caps")
33
+
34
+ @all_caps.setter
35
+ def all_caps(self, value: bool | None) -> None:
36
+ self._set_bool_prop("caps", value)
37
+
38
+ @property
39
+ def bold(self) -> bool | None:
40
+ """Read/write.
41
+
42
+ Causes text in this font to appear in bold.
43
+ """
44
+ return self._get_bool_prop("b")
45
+
46
+ @bold.setter
47
+ def bold(self, value: bool | None) -> None:
48
+ self._set_bool_prop("b", value)
49
+
50
+ @property
51
+ def color(self):
52
+ """A |ColorFormat| object providing a way to get and set the text color for this
53
+ font."""
54
+ return ColorFormat(self._element)
55
+
56
+ @property
57
+ def complex_script(self) -> bool | None:
58
+ """Read/write tri-state value.
59
+
60
+ When |True|, causes the characters in the run to be treated as complex script
61
+ regardless of their Unicode values.
62
+ """
63
+ return self._get_bool_prop("cs")
64
+
65
+ @complex_script.setter
66
+ def complex_script(self, value: bool | None) -> None:
67
+ self._set_bool_prop("cs", value)
68
+
69
+ @property
70
+ def cs_bold(self) -> bool | None:
71
+ """Read/write tri-state value.
72
+
73
+ When |True|, causes the complex script characters in the run to be displayed in
74
+ bold typeface.
75
+ """
76
+ return self._get_bool_prop("bCs")
77
+
78
+ @cs_bold.setter
79
+ def cs_bold(self, value: bool | None) -> None:
80
+ self._set_bool_prop("bCs", value)
81
+
82
+ @property
83
+ def cs_italic(self) -> bool | None:
84
+ """Read/write tri-state value.
85
+
86
+ When |True|, causes the complex script characters in the run to be displayed in
87
+ italic typeface.
88
+ """
89
+ return self._get_bool_prop("iCs")
90
+
91
+ @cs_italic.setter
92
+ def cs_italic(self, value: bool | None) -> None:
93
+ self._set_bool_prop("iCs", value)
94
+
95
+ @property
96
+ def double_strike(self) -> bool | None:
97
+ """Read/write tri-state value.
98
+
99
+ When |True|, causes the text in the run to appear with double strikethrough.
100
+ """
101
+ return self._get_bool_prop("dstrike")
102
+
103
+ @double_strike.setter
104
+ def double_strike(self, value: bool | None) -> None:
105
+ self._set_bool_prop("dstrike", value)
106
+
107
+ @property
108
+ def emboss(self) -> bool | None:
109
+ """Read/write tri-state value.
110
+
111
+ When |True|, causes the text in the run to appear as if raised off the page in
112
+ relief.
113
+ """
114
+ return self._get_bool_prop("emboss")
115
+
116
+ @emboss.setter
117
+ def emboss(self, value: bool | None) -> None:
118
+ self._set_bool_prop("emboss", value)
119
+
120
+ @property
121
+ def hidden(self) -> bool | None:
122
+ """Read/write tri-state value.
123
+
124
+ When |True|, causes the text in the run to be hidden from display, unless
125
+ applications settings force hidden text to be shown.
126
+ """
127
+ return self._get_bool_prop("vanish")
128
+
129
+ @hidden.setter
130
+ def hidden(self, value: bool | None) -> None:
131
+ self._set_bool_prop("vanish", value)
132
+
133
+ @property
134
+ def highlight_color(self) -> WD_COLOR_INDEX | None:
135
+ """Color of highlighing applied or |None| if not highlighted."""
136
+ rPr = self._element.rPr
137
+ if rPr is None:
138
+ return None
139
+ return rPr.highlight_val
140
+
141
+ @highlight_color.setter
142
+ def highlight_color(self, value: WD_COLOR_INDEX | None):
143
+ rPr = self._element.get_or_add_rPr()
144
+ rPr.highlight_val = value
145
+
146
+ @property
147
+ def italic(self) -> bool | None:
148
+ """Read/write tri-state value.
149
+
150
+ When |True|, causes the text of the run to appear in italics. |None| indicates
151
+ the effective value is inherited from the style hierarchy.
152
+ """
153
+ return self._get_bool_prop("i")
154
+
155
+ @italic.setter
156
+ def italic(self, value: bool | None) -> None:
157
+ self._set_bool_prop("i", value)
158
+
159
+ @property
160
+ def imprint(self) -> bool | None:
161
+ """Read/write tri-state value.
162
+
163
+ When |True|, causes the text in the run to appear as if pressed into the page.
164
+ """
165
+ return self._get_bool_prop("imprint")
166
+
167
+ @imprint.setter
168
+ def imprint(self, value: bool | None) -> None:
169
+ self._set_bool_prop("imprint", value)
170
+
171
+ @property
172
+ def math(self) -> bool | None:
173
+ """Read/write tri-state value.
174
+
175
+ When |True|, specifies this run contains WML that should be handled as though it
176
+ was Office Open XML Math.
177
+ """
178
+ return self._get_bool_prop("oMath")
179
+
180
+ @math.setter
181
+ def math(self, value: bool | None) -> None:
182
+ self._set_bool_prop("oMath", value)
183
+
184
+ @property
185
+ def name(self) -> str | None:
186
+ """The typeface name for this |Font|.
187
+
188
+ Causes the text it controls to appear in the named font, if a matching font is
189
+ found. |None| indicates the typeface is inherited from the style hierarchy.
190
+ """
191
+ rPr = self._element.rPr
192
+ if rPr is None:
193
+ return None
194
+ return rPr.rFonts_ascii
195
+
196
+ @name.setter
197
+ def name(self, value: str | None) -> None:
198
+ rPr = self._element.get_or_add_rPr()
199
+ rPr.rFonts_ascii = value
200
+ rPr.rFonts_hAnsi = value
201
+
202
+ @property
203
+ def theme_font(self) -> Literal["major", "minor"] | None:
204
+ """Latin theme family, either ``"major"`` (heading) or ``"minor"`` (body).
205
+
206
+ |None| means no uniform local theme family is set for ASCII and high ANSI.
207
+ This does not resolve inherited or mixed references. Assigning a family
208
+ removes conflicting literal Latin names. Assigning |None| removes only
209
+ those theme references, leaving literal names and other scripts unchanged.
210
+
211
+ To use a literal typeface, clear this property before setting ``name``.
212
+ The existing behavior of ``name`` is unchanged.
213
+ """
214
+ rPr = self._element.rPr
215
+ if rPr is None or rPr.rFonts is None:
216
+ return None
217
+ fonts = rPr.rFonts
218
+ if fonts.asciiTheme in ("majorAscii", "majorHAnsi") and fonts.hAnsiTheme in (
219
+ "majorAscii",
220
+ "majorHAnsi",
221
+ ):
222
+ return "major"
223
+ if fonts.asciiTheme in ("minorAscii", "minorHAnsi") and fonts.hAnsiTheme in (
224
+ "minorAscii",
225
+ "minorHAnsi",
226
+ ):
227
+ return "minor"
228
+ return None
229
+
230
+ @theme_font.setter
231
+ def theme_font(self, value: Literal["major", "minor"] | None) -> None:
232
+ if value not in (None, "major", "minor"):
233
+ raise ValueError("theme_font must be 'major', 'minor', or None")
234
+ if value is None:
235
+ rPr = self._element.rPr
236
+ if rPr is not None and rPr.rFonts is not None:
237
+ rPr.rFonts.asciiTheme = None
238
+ rPr.rFonts.hAnsiTheme = None
239
+ return
240
+ fonts = self._element.get_or_add_rPr().get_or_add_rFonts()
241
+ fonts.ascii = None
242
+ fonts.hAnsi = None
243
+ fonts.asciiTheme = f"{value}HAnsi"
244
+ fonts.hAnsiTheme = f"{value}HAnsi"
245
+
246
+ @property
247
+ def no_proof(self) -> bool | None:
248
+ """Read/write tri-state value.
249
+
250
+ When |True|, specifies that the contents of this run should not report any
251
+ errors when the document is scanned for spelling and grammar.
252
+ """
253
+ return self._get_bool_prop("noProof")
254
+
255
+ @no_proof.setter
256
+ def no_proof(self, value: bool | None) -> None:
257
+ self._set_bool_prop("noProof", value)
258
+
259
+ @property
260
+ def outline(self) -> bool | None:
261
+ """Read/write tri-state value.
262
+
263
+ When |True| causes the characters in the run to appear as if they have an
264
+ outline, by drawing a one pixel wide border around the inside and outside
265
+ borders of each character glyph.
266
+ """
267
+ return self._get_bool_prop("outline")
268
+
269
+ @outline.setter
270
+ def outline(self, value: bool | None) -> None:
271
+ self._set_bool_prop("outline", value)
272
+
273
+ @property
274
+ def rtl(self) -> bool | None:
275
+ """Read/write tri-state value.
276
+
277
+ When |True| causes the text in the run to have right-to-left characteristics.
278
+ """
279
+ return self._get_bool_prop("rtl")
280
+
281
+ @rtl.setter
282
+ def rtl(self, value: bool | None) -> None:
283
+ self._set_bool_prop("rtl", value)
284
+
285
+ @property
286
+ def shadow(self) -> bool | None:
287
+ """Read/write tri-state value.
288
+
289
+ When |True| causes the text in the run to appear as if each character has a
290
+ shadow.
291
+ """
292
+ return self._get_bool_prop("shadow")
293
+
294
+ @shadow.setter
295
+ def shadow(self, value: bool | None) -> None:
296
+ self._set_bool_prop("shadow", value)
297
+
298
+ @property
299
+ def size(self) -> Length | None:
300
+ """Font height in English Metric Units (EMU).
301
+
302
+ |None| indicates the font size should be inherited from the style hierarchy.
303
+ |Length| is a subclass of |int| having properties for convenient conversion into
304
+ points or other length units. The :class:`docx.shared.Pt` class allows
305
+ convenient specification of point values::
306
+
307
+ >>> font.size = Pt(24)
308
+ >>> font.size
309
+ 304800
310
+ >>> font.size.pt
311
+ 24.0
312
+
313
+ """
314
+ rPr = self._element.rPr
315
+ if rPr is None:
316
+ return None
317
+ return rPr.sz_val
318
+
319
+ @size.setter
320
+ def size(self, emu: int | Length | None) -> None:
321
+ rPr = self._element.get_or_add_rPr()
322
+ rPr.sz_val = None if emu is None else Emu(emu)
323
+
324
+ @property
325
+ def small_caps(self) -> bool | None:
326
+ """Read/write tri-state value.
327
+
328
+ When |True| causes the lowercase characters in the run to appear as capital
329
+ letters two points smaller than the font size specified for the run.
330
+ """
331
+ return self._get_bool_prop("smallCaps")
332
+
333
+ @small_caps.setter
334
+ def small_caps(self, value: bool | None) -> None:
335
+ self._set_bool_prop("smallCaps", value)
336
+
337
+ @property
338
+ def snap_to_grid(self) -> bool | None:
339
+ """Read/write tri-state value.
340
+
341
+ When |True| causes the run to use the document grid characters per line settings
342
+ defined in the docGrid element when laying out the characters in this run.
343
+ """
344
+ return self._get_bool_prop("snapToGrid")
345
+
346
+ @snap_to_grid.setter
347
+ def snap_to_grid(self, value: bool | None) -> None:
348
+ self._set_bool_prop("snapToGrid", value)
349
+
350
+ @property
351
+ def spec_vanish(self) -> bool | None:
352
+ """Read/write tri-state value.
353
+
354
+ When |True|, specifies that the given run shall always behave as if it is
355
+ hidden, even when hidden text is being displayed in the current document. The
356
+ property has a very narrow, specialized use related to the table of contents.
357
+ Consult the spec (§17.3.2.36) for more details.
358
+ """
359
+ return self._get_bool_prop("specVanish")
360
+
361
+ @spec_vanish.setter
362
+ def spec_vanish(self, value: bool | None) -> None:
363
+ self._set_bool_prop("specVanish", value)
364
+
365
+ @property
366
+ def strike(self) -> bool | None:
367
+ """Read/write tri-state value.
368
+
369
+ When |True| causes the text in the run to appear with a single horizontal line
370
+ through the center of the line.
371
+ """
372
+ return self._get_bool_prop("strike")
373
+
374
+ @strike.setter
375
+ def strike(self, value: bool | None) -> None:
376
+ self._set_bool_prop("strike", value)
377
+
378
+ @property
379
+ def subscript(self) -> bool | None:
380
+ """Boolean indicating whether the characters in this |Font| appear as subscript.
381
+
382
+ |None| indicates the subscript/subscript value is inherited from the style
383
+ hierarchy.
384
+ """
385
+ rPr = self._element.rPr
386
+ if rPr is None:
387
+ return None
388
+ return rPr.subscript
389
+
390
+ @subscript.setter
391
+ def subscript(self, value: bool | None) -> None:
392
+ rPr = self._element.get_or_add_rPr()
393
+ rPr.subscript = value
394
+
395
+ @property
396
+ def superscript(self) -> bool | None:
397
+ """Boolean indicating whether the characters in this |Font| appear as
398
+ superscript.
399
+
400
+ |None| indicates the subscript/superscript value is inherited from the style
401
+ hierarchy.
402
+ """
403
+ rPr = self._element.rPr
404
+ if rPr is None:
405
+ return None
406
+ return rPr.superscript
407
+
408
+ @superscript.setter
409
+ def superscript(self, value: bool | None) -> None:
410
+ rPr = self._element.get_or_add_rPr()
411
+ rPr.superscript = value
412
+
413
+ @property
414
+ def underline(self) -> bool | WD_UNDERLINE | None:
415
+ """The underline style for this |Font|.
416
+
417
+ The value is one of |None|, |True|, |False|, or a member of :ref:`WdUnderline`.
418
+
419
+ |None| indicates the font inherits its underline value from the style hierarchy.
420
+ |False| indicates no underline. |True| indicates single underline. The values
421
+ from :ref:`WdUnderline` are used to specify other outline styles such as double,
422
+ wavy, and dotted.
423
+ """
424
+ rPr = self._element.rPr
425
+ if rPr is None:
426
+ return None
427
+ val = rPr.u_val
428
+ return (
429
+ None
430
+ if val == WD_UNDERLINE.INHERITED
431
+ else True
432
+ if val == WD_UNDERLINE.SINGLE
433
+ else False
434
+ if val == WD_UNDERLINE.NONE
435
+ else val
436
+ )
437
+
438
+ @underline.setter
439
+ def underline(self, value: bool | WD_UNDERLINE | None) -> None:
440
+ rPr = self._element.get_or_add_rPr()
441
+ # -- works fine without these two mappings, but only because True == 1 and
442
+ # -- False == 0, which happen to match the mapping for WD_UNDERLINE.SINGLE
443
+ # -- and .NONE respectively.
444
+ val = (
445
+ WD_UNDERLINE.SINGLE if value is True else WD_UNDERLINE.NONE if value is False else value
446
+ )
447
+ rPr.u_val = val
448
+
449
+ @property
450
+ def web_hidden(self) -> bool | None:
451
+ """Read/write tri-state value.
452
+
453
+ When |True|, specifies that the contents of this run shall be hidden when the
454
+ document is displayed in web page view.
455
+ """
456
+ return self._get_bool_prop("webHidden")
457
+
458
+ @web_hidden.setter
459
+ def web_hidden(self, value: bool | None) -> None:
460
+ self._set_bool_prop("webHidden", value)
461
+
462
+ def _get_bool_prop(self, name: str) -> bool | None:
463
+ """Return the value of boolean child of `w:rPr` having `name`."""
464
+ rPr = self._element.rPr
465
+ if rPr is None:
466
+ return None
467
+ return rPr._get_bool_val(name) # pyright: ignore[reportPrivateUsage]
468
+
469
+ def _set_bool_prop(self, name: str, value: bool | None):
470
+ """Assign `value` to the boolean child `name` of `w:rPr`."""
471
+ rPr = self._element.get_or_add_rPr()
472
+ rPr._set_bool_val(name, value) # pyright: ignore[reportPrivateUsage]
docx/text/hyperlink.py ADDED
@@ -0,0 +1,121 @@
1
+ """Hyperlink-related proxy objects for python-docx, Hyperlink in particular.
2
+
3
+ A hyperlink occurs in a paragraph, at the same level as a Run, and a hyperlink itself
4
+ contains runs, which is where the visible text of the hyperlink is stored. So it's kind
5
+ of in-between, less than a paragraph and more than a run. So it gets its own module.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from docx.shared import Parented
13
+ from docx.text.run import Run
14
+
15
+ if TYPE_CHECKING:
16
+ import docx.types as t
17
+ from docx.oxml.text.hyperlink import CT_Hyperlink
18
+
19
+
20
+ class Hyperlink(Parented):
21
+ """Proxy object wrapping a `<w:hyperlink>` element.
22
+
23
+ A hyperlink occurs as a child of a paragraph, at the same level as a Run. A
24
+ hyperlink itself contains runs, which is where the visible text of the hyperlink is
25
+ stored.
26
+ """
27
+
28
+ def __init__(self, hyperlink: CT_Hyperlink, parent: t.ProvidesStoryPart):
29
+ super().__init__(parent)
30
+ self._parent = parent
31
+ self._hyperlink = self._element = hyperlink
32
+
33
+ @property
34
+ def address(self) -> str:
35
+ """The "URL" of the hyperlink (but not necessarily a web link).
36
+
37
+ While commonly a web link like "https://google.com" the hyperlink address can
38
+ take a variety of forms including "internal links" to bookmarked locations
39
+ within the document. When this hyperlink is an internal "jump" to for example a
40
+ heading from the table-of-contents (TOC), the address is blank. The bookmark
41
+ reference (like "_Toc147925734") is stored in the `.fragment` property.
42
+ """
43
+ rId = self._hyperlink.rId
44
+ return self._parent.part.rels[rId].target_ref if rId else ""
45
+
46
+ @property
47
+ def contains_page_break(self) -> bool:
48
+ """True when the text of this hyperlink is broken across page boundaries.
49
+
50
+ This is not uncommon and can happen for example when the hyperlink text is
51
+ multiple words and occurs in the last line of a page. Theoretically, a hyperlink
52
+ can contain more than one page break but that would be extremely uncommon in
53
+ practice. Still, this value should be understood to mean that "one-or-more"
54
+ rendered page breaks are present.
55
+ """
56
+ return bool(self._hyperlink.lastRenderedPageBreaks)
57
+
58
+ @property
59
+ def fragment(self) -> str:
60
+ """Reference like `#glossary` at end of URL that refers to a sub-resource.
61
+
62
+ Note that this value does not include the fragment-separator character ("#").
63
+
64
+ This value is known as a "named anchor" in an HTML context and "anchor" in the
65
+ MS API, but an "anchor" element (`<a>`) represents a full hyperlink in HTML so
66
+ we avoid confusion by using the more precise RFC 3986 naming "URI fragment".
67
+
68
+ These are also used to refer to bookmarks within the same document, in which
69
+ case the `.address` value with be blank ("") and this property will hold a
70
+ value like "_Toc147925734".
71
+
72
+ To reliably get an entire web URL you will need to concatenate this with the
73
+ `.address` value, separated by "#" when both are present. Consider using the
74
+ `.url` property for that purpose.
75
+
76
+ Word sometimes stores a fragment in this property (an XML attribute) and
77
+ sometimes with the address, depending on how the URL is inserted, so don't
78
+ depend on this field being empty to indicate no fragment is present.
79
+ """
80
+ return self._hyperlink.anchor or ""
81
+
82
+ @property
83
+ def runs(self) -> list[Run]:
84
+ """List of |Run| instances in this hyperlink.
85
+
86
+ Together these define the visible text of the hyperlink. The text of a hyperlink
87
+ is typically contained in a single run will be broken into multiple runs if for
88
+ example part of the hyperlink is bold or the text was changed after the document
89
+ was saved.
90
+ """
91
+ return [Run(r, self._parent) for r in self._hyperlink.r_lst]
92
+
93
+ @property
94
+ def text(self) -> str:
95
+ """String formed by concatenating the text of each run in the hyperlink.
96
+
97
+ Tabs and line breaks in the XML are mapped to ``\\t`` and ``\\n`` characters
98
+ respectively. Note that rendered page-breaks can occur within a hyperlink but
99
+ they are not reflected in this text.
100
+ """
101
+ return self._hyperlink.text
102
+
103
+ @property
104
+ def url(self) -> str:
105
+ """Convenience property to get web URLs from hyperlinks that contain them.
106
+
107
+ This value is the empty string ("") when there is no address portion, so its
108
+ boolean value can also be used to distinguish external URIs from internal "jump"
109
+ hyperlinks like those found in a table-of-contents.
110
+
111
+ Note that this value may also be a link to a file, so if you only want web-urls
112
+ you'll need to check for a protocol prefix like `https://`.
113
+
114
+ When both an address and fragment are present, the return value joins the two
115
+ separated by the fragment-separator hash ("#"). Otherwise this value is the same
116
+ as that of the `.address` property.
117
+ """
118
+ address, fragment = self.address, self.fragment
119
+ if not address:
120
+ return ""
121
+ return f"{address}#{fragment}" if fragment else address
docx/text/pagebreak.py ADDED
@@ -0,0 +1,104 @@
1
+ """Proxy objects related to rendered page-breaks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak
8
+ from docx.shared import Parented
9
+
10
+ if TYPE_CHECKING:
11
+ import docx.types as t
12
+ from docx.text.paragraph import Paragraph
13
+
14
+
15
+ class RenderedPageBreak(Parented):
16
+ """A page-break inserted by Word during page-layout for print or display purposes.
17
+
18
+ This usually does not correspond to a "hard" page-break inserted by the document
19
+ author, rather just that Word ran out of room on one page and needed to start
20
+ another. The position of these can change depending on the printer and page-size, as
21
+ well as margins, etc. They also will change in response to edits, but not until Word
22
+ loads and saves the document.
23
+
24
+ Note these are never inserted by `python-docx` because it has no rendering function.
25
+ These are generally only useful for text-extraction of existing documents when
26
+ `python-docx` is being used solely as a document "reader".
27
+
28
+ NOTE: a rendered page-break can occur within a hyperlink; consider a multi-word
29
+ hyperlink like "excellent Wikipedia article on LLMs" that happens to fall close to
30
+ the end of the last line on a page such that the page breaks between "Wikipedia" and
31
+ "article". In such a "page-breaks-in-hyperlink" case, THESE METHODS WILL "MOVE" THE
32
+ PAGE-BREAK to occur after the hyperlink, such that the entire hyperlink appears in
33
+ the paragraph returned by `.preceding_paragraph_fragment`. While this places the
34
+ "tail" text of the hyperlink on the "wrong" page, it avoids having two hyperlinks
35
+ each with a fragment of the actual text and pointing to the same address.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ lastRenderedPageBreak: CT_LastRenderedPageBreak,
41
+ parent: t.ProvidesStoryPart,
42
+ ):
43
+ super().__init__(parent)
44
+ self._element = lastRenderedPageBreak
45
+ self._lastRenderedPageBreak = lastRenderedPageBreak
46
+
47
+ @property
48
+ def preceding_paragraph_fragment(self) -> Paragraph | None:
49
+ """A "loose" paragraph containing the content preceding this page-break.
50
+
51
+ Compare `.following_paragraph_fragment` as these two are intended to be used
52
+ together.
53
+
54
+ This value is `None` when no content precedes this page-break. This case is
55
+ common and occurs whenever a page breaks on an even paragraph boundary.
56
+ Returning `None` for this case avoids "inserting" a non-existent paragraph into
57
+ the content stream. Note that content can include DrawingML items like images or
58
+ charts.
59
+
60
+ Note the returned paragraph *is divorced from the document body*. Any changes
61
+ made to it will not be reflected in the document. It is intended to provide a
62
+ familiar container (`Paragraph`) to interrogate for the content preceding this
63
+ page-break in the paragraph in which it occured.
64
+
65
+ Contains the entire hyperlink when this break occurs within a hyperlink.
66
+ """
67
+ if self._lastRenderedPageBreak.precedes_all_content:
68
+ return None
69
+
70
+ from docx.text.paragraph import Paragraph
71
+
72
+ return Paragraph(self._lastRenderedPageBreak.preceding_fragment_p, self._parent)
73
+
74
+ @property
75
+ def following_paragraph_fragment(self) -> Paragraph | None:
76
+ """A "loose" paragraph containing the content following this page-break.
77
+
78
+ HAS POTENTIALLY SURPRISING BEHAVIORS so read carefully to be sure this is what
79
+ you want. This is primarily targeted toward text-extraction use-cases for which
80
+ precisely associating text with the page it occurs on is important.
81
+
82
+ Compare `.preceding_paragraph_fragment` as these two are intended to be used
83
+ together.
84
+
85
+ This value is `None` when no content follows this page-break. This case is
86
+ unlikely to occur in practice because Word places even-paragraph-boundary
87
+ page-breaks on the paragraph *following* the page-break. Still, it is possible
88
+ and must be checked for. Returning `None` for this case avoids "inserting" an
89
+ extra, non-existent paragraph into the content stream. Note that content can
90
+ include DrawingML items like images or charts, not just text.
91
+
92
+ The returned paragraph *is divorced from the document body*. Any changes made to
93
+ it will not be reflected in the document. It is intended to provide a container
94
+ (`Paragraph`) with familiar properties and methods that can be used to
95
+ characterize the paragraph content following a mid-paragraph page-break.
96
+
97
+ Contains no portion of the hyperlink when this break occurs within a hyperlink.
98
+ """
99
+ if self._lastRenderedPageBreak.follows_all_content:
100
+ return None
101
+
102
+ from docx.text.paragraph import Paragraph
103
+
104
+ return Paragraph(self._lastRenderedPageBreak.following_fragment_p, self._parent)