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
docx/oxml/__init__.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# ruff: noqa: E402, I001
|
|
2
|
+
|
|
3
|
+
"""Initializes oxml sub-package.
|
|
4
|
+
|
|
5
|
+
This including registering custom element classes corresponding to Open XML elements.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from docx.oxml.drawing import CT_Drawing
|
|
11
|
+
from docx.oxml.parser import OxmlElement, parse_xml, register_element_cls
|
|
12
|
+
from docx.oxml.shape import (
|
|
13
|
+
CT_Anchor,
|
|
14
|
+
CT_Blip,
|
|
15
|
+
CT_BlipFillProperties,
|
|
16
|
+
CT_GraphicalObject,
|
|
17
|
+
CT_GraphicalObjectData,
|
|
18
|
+
CT_Inline,
|
|
19
|
+
CT_NonVisualDrawingProps,
|
|
20
|
+
CT_Picture,
|
|
21
|
+
CT_PictureNonVisual,
|
|
22
|
+
CT_Point2D,
|
|
23
|
+
CT_PositiveSize2D,
|
|
24
|
+
CT_ShapeProperties,
|
|
25
|
+
CT_Transform2D,
|
|
26
|
+
)
|
|
27
|
+
from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String
|
|
28
|
+
from docx.oxml.text.hyperlink import CT_Hyperlink
|
|
29
|
+
from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak
|
|
30
|
+
from docx.oxml.text.run import (
|
|
31
|
+
CT_R,
|
|
32
|
+
CT_Br,
|
|
33
|
+
CT_Cr,
|
|
34
|
+
CT_NoBreakHyphen,
|
|
35
|
+
CT_PTab,
|
|
36
|
+
CT_Text,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# -- `OxmlElement` and `parse_xml()` are not used in this module but several downstream
|
|
40
|
+
# -- "extension" packages expect to find them here and there's no compelling reason
|
|
41
|
+
# -- not to republish them here so those keep working.
|
|
42
|
+
__all__ = ["OxmlElement", "parse_xml"]
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# DrawingML-related elements
|
|
46
|
+
|
|
47
|
+
register_element_cls("a:blip", CT_Blip)
|
|
48
|
+
register_element_cls("a:ext", CT_PositiveSize2D)
|
|
49
|
+
register_element_cls("a:graphic", CT_GraphicalObject)
|
|
50
|
+
register_element_cls("a:graphicData", CT_GraphicalObjectData)
|
|
51
|
+
register_element_cls("a:off", CT_Point2D)
|
|
52
|
+
register_element_cls("a:xfrm", CT_Transform2D)
|
|
53
|
+
register_element_cls("pic:blipFill", CT_BlipFillProperties)
|
|
54
|
+
register_element_cls("pic:cNvPr", CT_NonVisualDrawingProps)
|
|
55
|
+
register_element_cls("pic:nvPicPr", CT_PictureNonVisual)
|
|
56
|
+
register_element_cls("pic:pic", CT_Picture)
|
|
57
|
+
register_element_cls("pic:spPr", CT_ShapeProperties)
|
|
58
|
+
register_element_cls("w:drawing", CT_Drawing)
|
|
59
|
+
register_element_cls("wp:anchor", CT_Anchor)
|
|
60
|
+
register_element_cls("wp:docPr", CT_NonVisualDrawingProps)
|
|
61
|
+
register_element_cls("wp:extent", CT_PositiveSize2D)
|
|
62
|
+
register_element_cls("wp:inline", CT_Inline)
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# hyperlink-related elements
|
|
66
|
+
|
|
67
|
+
register_element_cls("w:hyperlink", CT_Hyperlink)
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# text-related elements
|
|
71
|
+
|
|
72
|
+
register_element_cls("w:br", CT_Br)
|
|
73
|
+
register_element_cls("w:cr", CT_Cr)
|
|
74
|
+
register_element_cls("w:lastRenderedPageBreak", CT_LastRenderedPageBreak)
|
|
75
|
+
register_element_cls("w:noBreakHyphen", CT_NoBreakHyphen)
|
|
76
|
+
register_element_cls("w:ptab", CT_PTab)
|
|
77
|
+
register_element_cls("w:r", CT_R)
|
|
78
|
+
register_element_cls("w:t", CT_Text)
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# header/footer-related mappings
|
|
82
|
+
|
|
83
|
+
register_element_cls("w:evenAndOddHeaders", CT_OnOff)
|
|
84
|
+
register_element_cls("w:titlePg", CT_OnOff)
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# other custom element class mappings
|
|
88
|
+
|
|
89
|
+
from .comments import CT_Comments, CT_Comment
|
|
90
|
+
|
|
91
|
+
register_element_cls("w:comments", CT_Comments)
|
|
92
|
+
register_element_cls("w:comment", CT_Comment)
|
|
93
|
+
|
|
94
|
+
from .coreprops import CT_CoreProperties
|
|
95
|
+
|
|
96
|
+
register_element_cls("cp:coreProperties", CT_CoreProperties)
|
|
97
|
+
|
|
98
|
+
from .document import CT_Body, CT_Document
|
|
99
|
+
|
|
100
|
+
register_element_cls("w:body", CT_Body)
|
|
101
|
+
register_element_cls("w:document", CT_Document)
|
|
102
|
+
|
|
103
|
+
from .numbering import CT_Num, CT_Numbering, CT_NumLvl, CT_NumPr
|
|
104
|
+
|
|
105
|
+
register_element_cls("w:abstractNumId", CT_DecimalNumber)
|
|
106
|
+
register_element_cls("w:ilvl", CT_DecimalNumber)
|
|
107
|
+
register_element_cls("w:lvlOverride", CT_NumLvl)
|
|
108
|
+
register_element_cls("w:num", CT_Num)
|
|
109
|
+
register_element_cls("w:numId", CT_DecimalNumber)
|
|
110
|
+
register_element_cls("w:numPr", CT_NumPr)
|
|
111
|
+
register_element_cls("w:numbering", CT_Numbering)
|
|
112
|
+
register_element_cls("w:startOverride", CT_DecimalNumber)
|
|
113
|
+
|
|
114
|
+
from .section import (
|
|
115
|
+
CT_HdrFtr,
|
|
116
|
+
CT_HdrFtrRef,
|
|
117
|
+
CT_PageMar,
|
|
118
|
+
CT_PageSz,
|
|
119
|
+
CT_SectPr,
|
|
120
|
+
CT_SectType,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
register_element_cls("w:footerReference", CT_HdrFtrRef)
|
|
124
|
+
register_element_cls("w:ftr", CT_HdrFtr)
|
|
125
|
+
register_element_cls("w:hdr", CT_HdrFtr)
|
|
126
|
+
register_element_cls("w:headerReference", CT_HdrFtrRef)
|
|
127
|
+
register_element_cls("w:pgMar", CT_PageMar)
|
|
128
|
+
register_element_cls("w:pgSz", CT_PageSz)
|
|
129
|
+
register_element_cls("w:sectPr", CT_SectPr)
|
|
130
|
+
register_element_cls("w:type", CT_SectType)
|
|
131
|
+
|
|
132
|
+
from .settings import CT_Settings
|
|
133
|
+
|
|
134
|
+
register_element_cls("w:settings", CT_Settings)
|
|
135
|
+
|
|
136
|
+
from .styles import (
|
|
137
|
+
CT_DocDefaults,
|
|
138
|
+
CT_LatentStyles,
|
|
139
|
+
CT_LsdException,
|
|
140
|
+
CT_RPrDefault,
|
|
141
|
+
CT_Style,
|
|
142
|
+
CT_Styles,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
register_element_cls("w:docDefaults", CT_DocDefaults)
|
|
146
|
+
register_element_cls("w:rPrDefault", CT_RPrDefault)
|
|
147
|
+
register_element_cls("w:link", CT_String)
|
|
148
|
+
register_element_cls("w:basedOn", CT_String)
|
|
149
|
+
register_element_cls("w:latentStyles", CT_LatentStyles)
|
|
150
|
+
register_element_cls("w:locked", CT_OnOff)
|
|
151
|
+
register_element_cls("w:lsdException", CT_LsdException)
|
|
152
|
+
register_element_cls("w:name", CT_String)
|
|
153
|
+
register_element_cls("w:next", CT_String)
|
|
154
|
+
register_element_cls("w:qFormat", CT_OnOff)
|
|
155
|
+
register_element_cls("w:semiHidden", CT_OnOff)
|
|
156
|
+
register_element_cls("w:style", CT_Style)
|
|
157
|
+
register_element_cls("w:styles", CT_Styles)
|
|
158
|
+
register_element_cls("w:uiPriority", CT_DecimalNumber)
|
|
159
|
+
register_element_cls("w:unhideWhenUsed", CT_OnOff)
|
|
160
|
+
|
|
161
|
+
from .table import (
|
|
162
|
+
CT_Height,
|
|
163
|
+
CT_Row,
|
|
164
|
+
CT_Tbl,
|
|
165
|
+
CT_TblGrid,
|
|
166
|
+
CT_TblGridCol,
|
|
167
|
+
CT_TblLayoutType,
|
|
168
|
+
CT_TblPr,
|
|
169
|
+
CT_TblPrEx,
|
|
170
|
+
CT_TblWidth,
|
|
171
|
+
CT_Tc,
|
|
172
|
+
CT_TcPr,
|
|
173
|
+
CT_TrPr,
|
|
174
|
+
CT_VMerge,
|
|
175
|
+
CT_VerticalJc,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
register_element_cls("w:bidiVisual", CT_OnOff)
|
|
179
|
+
register_element_cls("w:gridAfter", CT_DecimalNumber)
|
|
180
|
+
register_element_cls("w:gridBefore", CT_DecimalNumber)
|
|
181
|
+
register_element_cls("w:gridCol", CT_TblGridCol)
|
|
182
|
+
register_element_cls("w:gridSpan", CT_DecimalNumber)
|
|
183
|
+
register_element_cls("w:tbl", CT_Tbl)
|
|
184
|
+
register_element_cls("w:tblGrid", CT_TblGrid)
|
|
185
|
+
register_element_cls("w:tblLayout", CT_TblLayoutType)
|
|
186
|
+
register_element_cls("w:tblPr", CT_TblPr)
|
|
187
|
+
register_element_cls("w:tblPrEx", CT_TblPrEx)
|
|
188
|
+
register_element_cls("w:tblStyle", CT_String)
|
|
189
|
+
register_element_cls("w:tc", CT_Tc)
|
|
190
|
+
register_element_cls("w:tcPr", CT_TcPr)
|
|
191
|
+
register_element_cls("w:tcW", CT_TblWidth)
|
|
192
|
+
register_element_cls("w:tr", CT_Row)
|
|
193
|
+
register_element_cls("w:trHeight", CT_Height)
|
|
194
|
+
register_element_cls("w:trPr", CT_TrPr)
|
|
195
|
+
register_element_cls("w:vAlign", CT_VerticalJc)
|
|
196
|
+
register_element_cls("w:vMerge", CT_VMerge)
|
|
197
|
+
|
|
198
|
+
from .text.font import (
|
|
199
|
+
CT_Color,
|
|
200
|
+
CT_Fonts,
|
|
201
|
+
CT_Highlight,
|
|
202
|
+
CT_HpsMeasure,
|
|
203
|
+
CT_RPr,
|
|
204
|
+
CT_Underline,
|
|
205
|
+
CT_VerticalAlignRun,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
register_element_cls("w:b", CT_OnOff)
|
|
209
|
+
register_element_cls("w:bCs", CT_OnOff)
|
|
210
|
+
register_element_cls("w:caps", CT_OnOff)
|
|
211
|
+
register_element_cls("w:color", CT_Color)
|
|
212
|
+
register_element_cls("w:cs", CT_OnOff)
|
|
213
|
+
register_element_cls("w:dstrike", CT_OnOff)
|
|
214
|
+
register_element_cls("w:emboss", CT_OnOff)
|
|
215
|
+
register_element_cls("w:highlight", CT_Highlight)
|
|
216
|
+
register_element_cls("w:i", CT_OnOff)
|
|
217
|
+
register_element_cls("w:iCs", CT_OnOff)
|
|
218
|
+
register_element_cls("w:imprint", CT_OnOff)
|
|
219
|
+
register_element_cls("w:noProof", CT_OnOff)
|
|
220
|
+
register_element_cls("w:oMath", CT_OnOff)
|
|
221
|
+
register_element_cls("w:outline", CT_OnOff)
|
|
222
|
+
register_element_cls("w:rFonts", CT_Fonts)
|
|
223
|
+
register_element_cls("w:rPr", CT_RPr)
|
|
224
|
+
register_element_cls("w:rStyle", CT_String)
|
|
225
|
+
register_element_cls("w:rtl", CT_OnOff)
|
|
226
|
+
register_element_cls("w:shadow", CT_OnOff)
|
|
227
|
+
register_element_cls("w:smallCaps", CT_OnOff)
|
|
228
|
+
register_element_cls("w:snapToGrid", CT_OnOff)
|
|
229
|
+
register_element_cls("w:specVanish", CT_OnOff)
|
|
230
|
+
register_element_cls("w:strike", CT_OnOff)
|
|
231
|
+
register_element_cls("w:sz", CT_HpsMeasure)
|
|
232
|
+
register_element_cls("w:u", CT_Underline)
|
|
233
|
+
register_element_cls("w:vanish", CT_OnOff)
|
|
234
|
+
register_element_cls("w:vertAlign", CT_VerticalAlignRun)
|
|
235
|
+
register_element_cls("w:webHidden", CT_OnOff)
|
|
236
|
+
|
|
237
|
+
from .text.paragraph import CT_P
|
|
238
|
+
|
|
239
|
+
register_element_cls("w:p", CT_P)
|
|
240
|
+
|
|
241
|
+
from .text.parfmt import (
|
|
242
|
+
CT_Ind,
|
|
243
|
+
CT_Jc,
|
|
244
|
+
CT_PPr,
|
|
245
|
+
CT_Spacing,
|
|
246
|
+
CT_TabStop,
|
|
247
|
+
CT_TabStops,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
register_element_cls("w:ind", CT_Ind)
|
|
251
|
+
register_element_cls("w:jc", CT_Jc)
|
|
252
|
+
register_element_cls("w:keepLines", CT_OnOff)
|
|
253
|
+
register_element_cls("w:keepNext", CT_OnOff)
|
|
254
|
+
register_element_cls("w:outlineLvl", CT_DecimalNumber)
|
|
255
|
+
register_element_cls("w:pageBreakBefore", CT_OnOff)
|
|
256
|
+
register_element_cls("w:pPr", CT_PPr)
|
|
257
|
+
register_element_cls("w:pStyle", CT_String)
|
|
258
|
+
register_element_cls("w:spacing", CT_Spacing)
|
|
259
|
+
register_element_cls("w:tab", CT_TabStop)
|
|
260
|
+
register_element_cls("w:tabs", CT_TabStops)
|
|
261
|
+
register_element_cls("w:widowControl", CT_OnOff)
|
docx/oxml/comments.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Custom element classes related to document comments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
from typing import TYPE_CHECKING, Callable, cast
|
|
7
|
+
|
|
8
|
+
from docx.oxml.ns import nsdecls
|
|
9
|
+
from docx.oxml.parser import parse_xml
|
|
10
|
+
from docx.oxml.simpletypes import ST_DateTime, ST_DecimalNumber, ST_String
|
|
11
|
+
from docx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, RequiredAttribute, ZeroOrMore
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from docx.oxml.table import CT_Tbl
|
|
15
|
+
from docx.oxml.text.paragraph import CT_P
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CT_Comments(BaseOxmlElement):
|
|
19
|
+
"""`w:comments` element, the root element for the comments part.
|
|
20
|
+
|
|
21
|
+
Simply contains a collection of `w:comment` elements, each representing a single comment. Each
|
|
22
|
+
contained comment is identified by a unique `w:id` attribute, used to reference the comment
|
|
23
|
+
from the document text. The offset of the comment in this collection is arbitrary; it is
|
|
24
|
+
essentially a _set_ implemented as a list.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# -- type-declarations to fill in the gaps for metaclass-added methods --
|
|
28
|
+
comment_lst: list[CT_Comment]
|
|
29
|
+
|
|
30
|
+
comment = ZeroOrMore("w:comment")
|
|
31
|
+
|
|
32
|
+
def add_comment(self) -> CT_Comment:
|
|
33
|
+
"""Return newly added `w:comment` child of this `w:comments`.
|
|
34
|
+
|
|
35
|
+
The returned `w:comment` element is the minimum valid value, having a `w:id` value unique
|
|
36
|
+
within the existing comments and the required `w:author` attribute present but set to the
|
|
37
|
+
empty string. It's content is limited to a single run containing the necessary annotation
|
|
38
|
+
reference but no text. Content is added by adding runs to this first paragraph and by
|
|
39
|
+
adding additional paragraphs as needed.
|
|
40
|
+
"""
|
|
41
|
+
next_id = self._next_available_comment_id()
|
|
42
|
+
comment = cast(
|
|
43
|
+
CT_Comment,
|
|
44
|
+
parse_xml(
|
|
45
|
+
f'<w:comment {nsdecls("w")} w:id="{next_id}" w:author="">'
|
|
46
|
+
f" <w:p>"
|
|
47
|
+
f" <w:pPr>"
|
|
48
|
+
f' <w:pStyle w:val="CommentText"/>'
|
|
49
|
+
f" </w:pPr>"
|
|
50
|
+
f" <w:r>"
|
|
51
|
+
f" <w:rPr>"
|
|
52
|
+
f' <w:rStyle w:val="CommentReference"/>'
|
|
53
|
+
f" </w:rPr>"
|
|
54
|
+
f" <w:annotationRef/>"
|
|
55
|
+
f" </w:r>"
|
|
56
|
+
f" </w:p>"
|
|
57
|
+
f"</w:comment>"
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
self.append(comment)
|
|
61
|
+
return comment
|
|
62
|
+
|
|
63
|
+
def get_comment_by_id(self, comment_id: int) -> CT_Comment | None:
|
|
64
|
+
"""Return the `w:comment` element identified by `comment_id`, or |None| if not found."""
|
|
65
|
+
comment_elms = self.xpath(f"(./w:comment[@w:id='{comment_id}'])[1]")
|
|
66
|
+
return comment_elms[0] if comment_elms else None
|
|
67
|
+
|
|
68
|
+
def _next_available_comment_id(self) -> int:
|
|
69
|
+
"""The next available comment id.
|
|
70
|
+
|
|
71
|
+
According to the schema, this can be any positive integer, as big as you like, and the
|
|
72
|
+
default mechanism is to use `max() + 1`. However, if that yields a value larger than will
|
|
73
|
+
fit in a 32-bit signed integer, we take a more deliberate approach to use the first
|
|
74
|
+
ununsed integer starting from 0.
|
|
75
|
+
"""
|
|
76
|
+
used_ids = [int(x) for x in self.xpath("./w:comment/@w:id")]
|
|
77
|
+
|
|
78
|
+
next_id = max(used_ids, default=-1) + 1
|
|
79
|
+
|
|
80
|
+
if next_id <= 2**31 - 1:
|
|
81
|
+
return next_id
|
|
82
|
+
|
|
83
|
+
# -- fall-back to enumerating all used ids to find the first unused one --
|
|
84
|
+
for expected, actual in enumerate(sorted(used_ids)):
|
|
85
|
+
if expected != actual:
|
|
86
|
+
return expected
|
|
87
|
+
|
|
88
|
+
return len(used_ids)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CT_Comment(BaseOxmlElement):
|
|
92
|
+
"""`w:comment` element, representing a single comment.
|
|
93
|
+
|
|
94
|
+
A comment is a so-called "story" and can contain paragraphs and tables much like a table-cell.
|
|
95
|
+
While probably most often used for a single sentence or phrase, a comment can contain rich
|
|
96
|
+
content, including multiple rich-text paragraphs, hyperlinks, images, and tables.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# -- attributes on `w:comment` --
|
|
100
|
+
id: int = RequiredAttribute("w:id", ST_DecimalNumber) # pyright: ignore[reportAssignmentType]
|
|
101
|
+
author: str = RequiredAttribute("w:author", ST_String) # pyright: ignore[reportAssignmentType]
|
|
102
|
+
initials: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
103
|
+
"w:initials", ST_String
|
|
104
|
+
)
|
|
105
|
+
date: dt.datetime | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
106
|
+
"w:date", ST_DateTime
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# -- children --
|
|
110
|
+
|
|
111
|
+
p = ZeroOrMore("w:p", successors=())
|
|
112
|
+
tbl = ZeroOrMore("w:tbl", successors=())
|
|
113
|
+
|
|
114
|
+
# -- type-declarations for methods added by metaclass --
|
|
115
|
+
|
|
116
|
+
add_p: Callable[[], CT_P]
|
|
117
|
+
p_lst: list[CT_P]
|
|
118
|
+
tbl_lst: list[CT_Tbl]
|
|
119
|
+
_insert_tbl: Callable[[CT_Tbl], CT_Tbl]
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def inner_content_elements(self) -> list[CT_P | CT_Tbl]:
|
|
123
|
+
"""Generate all `w:p` and `w:tbl` elements in this comment."""
|
|
124
|
+
return self.xpath("./w:p | ./w:tbl")
|
docx/oxml/coreprops.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Custom element classes for core properties-related XML elements."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, cast
|
|
8
|
+
|
|
9
|
+
from docx.oxml.ns import nsdecls, qn
|
|
10
|
+
from docx.oxml.parser import parse_xml
|
|
11
|
+
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from lxml.etree import _Element as etree_Element # pyright: ignore[reportPrivateUsage]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CT_CoreProperties(BaseOxmlElement):
|
|
18
|
+
"""`<cp:coreProperties>` element, the root element of the Core Properties part.
|
|
19
|
+
|
|
20
|
+
Stored as `/docProps/core.xml`. Implements many of the Dublin Core document metadata
|
|
21
|
+
elements. String elements resolve to an empty string ("") if the element is not
|
|
22
|
+
present in the XML. String elements are limited in length to 255 unicode characters.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
get_or_add_revision: Callable[[], etree_Element]
|
|
26
|
+
|
|
27
|
+
category = ZeroOrOne("cp:category", successors=())
|
|
28
|
+
contentStatus = ZeroOrOne("cp:contentStatus", successors=())
|
|
29
|
+
created = ZeroOrOne("dcterms:created", successors=())
|
|
30
|
+
creator = ZeroOrOne("dc:creator", successors=())
|
|
31
|
+
description = ZeroOrOne("dc:description", successors=())
|
|
32
|
+
identifier = ZeroOrOne("dc:identifier", successors=())
|
|
33
|
+
keywords = ZeroOrOne("cp:keywords", successors=())
|
|
34
|
+
language = ZeroOrOne("dc:language", successors=())
|
|
35
|
+
lastModifiedBy = ZeroOrOne("cp:lastModifiedBy", successors=())
|
|
36
|
+
lastPrinted = ZeroOrOne("cp:lastPrinted", successors=())
|
|
37
|
+
modified = ZeroOrOne("dcterms:modified", successors=())
|
|
38
|
+
revision: etree_Element | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
39
|
+
"cp:revision", successors=()
|
|
40
|
+
)
|
|
41
|
+
subject = ZeroOrOne("dc:subject", successors=())
|
|
42
|
+
title = ZeroOrOne("dc:title", successors=())
|
|
43
|
+
version = ZeroOrOne("cp:version", successors=())
|
|
44
|
+
|
|
45
|
+
_coreProperties_tmpl = "<cp:coreProperties %s/>\n" % nsdecls("cp", "dc", "dcterms")
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def new(cls) -> CT_CoreProperties:
|
|
49
|
+
"""Return a new `<cp:coreProperties>` element."""
|
|
50
|
+
xml = cls._coreProperties_tmpl
|
|
51
|
+
coreProperties = cast(CT_CoreProperties, parse_xml(xml))
|
|
52
|
+
return coreProperties
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def author_text(self) -> str:
|
|
56
|
+
"""The text in the `dc:creator` child element."""
|
|
57
|
+
return self._text_of_element("creator")
|
|
58
|
+
|
|
59
|
+
@author_text.setter
|
|
60
|
+
def author_text(self, value: str):
|
|
61
|
+
self._set_element_text("creator", value)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def category_text(self) -> str:
|
|
65
|
+
return self._text_of_element("category")
|
|
66
|
+
|
|
67
|
+
@category_text.setter
|
|
68
|
+
def category_text(self, value: str):
|
|
69
|
+
self._set_element_text("category", value)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def comments_text(self) -> str:
|
|
73
|
+
return self._text_of_element("description")
|
|
74
|
+
|
|
75
|
+
@comments_text.setter
|
|
76
|
+
def comments_text(self, value: str):
|
|
77
|
+
self._set_element_text("description", value)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def contentStatus_text(self) -> str:
|
|
81
|
+
return self._text_of_element("contentStatus")
|
|
82
|
+
|
|
83
|
+
@contentStatus_text.setter
|
|
84
|
+
def contentStatus_text(self, value: str):
|
|
85
|
+
self._set_element_text("contentStatus", value)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def created_datetime(self) -> dt.datetime | None:
|
|
89
|
+
return self._datetime_of_element("created")
|
|
90
|
+
|
|
91
|
+
@created_datetime.setter
|
|
92
|
+
def created_datetime(self, value: dt.datetime):
|
|
93
|
+
self._set_element_datetime("created", value)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def identifier_text(self) -> str:
|
|
97
|
+
return self._text_of_element("identifier")
|
|
98
|
+
|
|
99
|
+
@identifier_text.setter
|
|
100
|
+
def identifier_text(self, value: str):
|
|
101
|
+
self._set_element_text("identifier", value)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def keywords_text(self) -> str:
|
|
105
|
+
return self._text_of_element("keywords")
|
|
106
|
+
|
|
107
|
+
@keywords_text.setter
|
|
108
|
+
def keywords_text(self, value: str):
|
|
109
|
+
self._set_element_text("keywords", value)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def language_text(self) -> str:
|
|
113
|
+
return self._text_of_element("language")
|
|
114
|
+
|
|
115
|
+
@language_text.setter
|
|
116
|
+
def language_text(self, value: str):
|
|
117
|
+
self._set_element_text("language", value)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def lastModifiedBy_text(self) -> str:
|
|
121
|
+
return self._text_of_element("lastModifiedBy")
|
|
122
|
+
|
|
123
|
+
@lastModifiedBy_text.setter
|
|
124
|
+
def lastModifiedBy_text(self, value: str):
|
|
125
|
+
self._set_element_text("lastModifiedBy", value)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def lastPrinted_datetime(self) -> dt.datetime | None:
|
|
129
|
+
return self._datetime_of_element("lastPrinted")
|
|
130
|
+
|
|
131
|
+
@lastPrinted_datetime.setter
|
|
132
|
+
def lastPrinted_datetime(self, value: dt.datetime):
|
|
133
|
+
self._set_element_datetime("lastPrinted", value)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def modified_datetime(self) -> dt.datetime | None:
|
|
137
|
+
return self._datetime_of_element("modified")
|
|
138
|
+
|
|
139
|
+
@modified_datetime.setter
|
|
140
|
+
def modified_datetime(self, value: dt.datetime):
|
|
141
|
+
self._set_element_datetime("modified", value)
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def revision_number(self) -> int:
|
|
145
|
+
"""Integer value of revision property."""
|
|
146
|
+
revision = self.revision
|
|
147
|
+
if revision is None:
|
|
148
|
+
return 0
|
|
149
|
+
revision_str = str(revision.text)
|
|
150
|
+
try:
|
|
151
|
+
revision = int(revision_str)
|
|
152
|
+
except ValueError:
|
|
153
|
+
# non-integer revision strings also resolve to 0
|
|
154
|
+
revision = 0
|
|
155
|
+
# as do negative integers
|
|
156
|
+
if revision < 0:
|
|
157
|
+
revision = 0
|
|
158
|
+
return revision
|
|
159
|
+
|
|
160
|
+
@revision_number.setter
|
|
161
|
+
def revision_number(self, value: int):
|
|
162
|
+
"""Set revision property to string value of integer `value`."""
|
|
163
|
+
if not isinstance(value, int) or value < 1: # pyright: ignore[reportUnnecessaryIsInstance]
|
|
164
|
+
tmpl = "revision property requires positive int, got '%s'"
|
|
165
|
+
raise ValueError(tmpl % value)
|
|
166
|
+
revision = self.get_or_add_revision()
|
|
167
|
+
revision.text = str(value)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def subject_text(self) -> str:
|
|
171
|
+
return self._text_of_element("subject")
|
|
172
|
+
|
|
173
|
+
@subject_text.setter
|
|
174
|
+
def subject_text(self, value: str):
|
|
175
|
+
self._set_element_text("subject", value)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def title_text(self) -> str:
|
|
179
|
+
return self._text_of_element("title")
|
|
180
|
+
|
|
181
|
+
@title_text.setter
|
|
182
|
+
def title_text(self, value: str):
|
|
183
|
+
self._set_element_text("title", value)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def version_text(self) -> str:
|
|
187
|
+
return self._text_of_element("version")
|
|
188
|
+
|
|
189
|
+
@version_text.setter
|
|
190
|
+
def version_text(self, value: str):
|
|
191
|
+
self._set_element_text("version", value)
|
|
192
|
+
|
|
193
|
+
def _datetime_of_element(self, property_name: str) -> dt.datetime | None:
|
|
194
|
+
element = getattr(self, property_name)
|
|
195
|
+
if element is None:
|
|
196
|
+
return None
|
|
197
|
+
datetime_str = element.text
|
|
198
|
+
try:
|
|
199
|
+
return self._parse_W3CDTF_to_datetime(datetime_str)
|
|
200
|
+
except ValueError:
|
|
201
|
+
# invalid datetime strings are ignored
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
def _get_or_add(self, prop_name: str) -> BaseOxmlElement:
|
|
205
|
+
"""Return element returned by "get_or_add_" method for `prop_name`."""
|
|
206
|
+
get_or_add_method_name = "get_or_add_%s" % prop_name
|
|
207
|
+
get_or_add_method = getattr(self, get_or_add_method_name)
|
|
208
|
+
element = get_or_add_method()
|
|
209
|
+
return element
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def _offset_dt(cls, dt_: dt.datetime, offset_str: str) -> dt.datetime:
|
|
213
|
+
"""A |datetime| instance offset from `dt_` by timezone offset in `offset_str`.
|
|
214
|
+
|
|
215
|
+
`offset_str` is like `"-07:00"`.
|
|
216
|
+
"""
|
|
217
|
+
match = cls._offset_pattern.match(offset_str)
|
|
218
|
+
if match is None:
|
|
219
|
+
raise ValueError("'%s' is not a valid offset string" % offset_str)
|
|
220
|
+
sign, hours_str, minutes_str = match.groups()
|
|
221
|
+
sign_factor = -1 if sign == "+" else 1
|
|
222
|
+
hours = int(hours_str) * sign_factor
|
|
223
|
+
minutes = int(minutes_str) * sign_factor
|
|
224
|
+
td = dt.timedelta(hours=hours, minutes=minutes)
|
|
225
|
+
return dt_ + td
|
|
226
|
+
|
|
227
|
+
_offset_pattern = re.compile(r"([+-])(\d\d):(\d\d)")
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def _parse_W3CDTF_to_datetime(cls, w3cdtf_str: str) -> dt.datetime:
|
|
231
|
+
# valid W3CDTF date cases:
|
|
232
|
+
# yyyy e.g. "2003"
|
|
233
|
+
# yyyy-mm e.g. "2003-12"
|
|
234
|
+
# yyyy-mm-dd e.g. "2003-12-31"
|
|
235
|
+
# UTC timezone e.g. "2003-12-31T10:14:55Z"
|
|
236
|
+
# numeric timezone e.g. "2003-12-31T10:14:55-08:00"
|
|
237
|
+
templates = (
|
|
238
|
+
"%Y-%m-%dT%H:%M:%S",
|
|
239
|
+
"%Y-%m-%d",
|
|
240
|
+
"%Y-%m",
|
|
241
|
+
"%Y",
|
|
242
|
+
)
|
|
243
|
+
# strptime isn't smart enough to parse literal timezone offsets like
|
|
244
|
+
# "-07:30", so we have to do it ourselves
|
|
245
|
+
parseable_part = w3cdtf_str[:19]
|
|
246
|
+
offset_str = w3cdtf_str[19:]
|
|
247
|
+
dt_ = None
|
|
248
|
+
for tmpl in templates:
|
|
249
|
+
try:
|
|
250
|
+
dt_ = dt.datetime.strptime(parseable_part, tmpl)
|
|
251
|
+
except ValueError:
|
|
252
|
+
continue
|
|
253
|
+
if dt_ is None:
|
|
254
|
+
tmpl = "could not parse W3CDTF datetime string '%s'"
|
|
255
|
+
raise ValueError(tmpl % w3cdtf_str)
|
|
256
|
+
if len(offset_str) == 6:
|
|
257
|
+
dt_ = cls._offset_dt(dt_, offset_str)
|
|
258
|
+
return dt_.replace(tzinfo=dt.timezone.utc)
|
|
259
|
+
|
|
260
|
+
def _set_element_datetime(self, prop_name: str, value: dt.datetime) -> None:
|
|
261
|
+
"""Set date/time value of child element having `prop_name` to `value`."""
|
|
262
|
+
if not isinstance(value, dt.datetime): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
263
|
+
tmpl = "property requires <type 'datetime.datetime'> object, got %s"
|
|
264
|
+
raise ValueError(tmpl % type(value))
|
|
265
|
+
element = self._get_or_add(prop_name)
|
|
266
|
+
dt_str = value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
267
|
+
element.text = dt_str
|
|
268
|
+
if prop_name in ("created", "modified"):
|
|
269
|
+
# These two require an explicit "xsi:type="dcterms:W3CDTF""
|
|
270
|
+
# attribute. The first and last line are a hack required to add
|
|
271
|
+
# the xsi namespace to the root element rather than each child
|
|
272
|
+
# element in which it is referenced
|
|
273
|
+
self.set(qn("xsi:foo"), "bar")
|
|
274
|
+
element.set(qn("xsi:type"), "dcterms:W3CDTF")
|
|
275
|
+
del self.attrib[qn("xsi:foo")]
|
|
276
|
+
|
|
277
|
+
def _set_element_text(self, prop_name: str, value: Any) -> None:
|
|
278
|
+
"""Set string value of `name` property to `value`."""
|
|
279
|
+
if not isinstance(value, str):
|
|
280
|
+
value = str(value)
|
|
281
|
+
|
|
282
|
+
if len(value) > 255:
|
|
283
|
+
tmpl = "exceeded 255 char limit for property, got:\n\n'%s'"
|
|
284
|
+
raise ValueError(tmpl % value)
|
|
285
|
+
element = self._get_or_add(prop_name)
|
|
286
|
+
element.text = value
|
|
287
|
+
|
|
288
|
+
def _text_of_element(self, property_name: str) -> str:
|
|
289
|
+
"""The text in the element matching `property_name`.
|
|
290
|
+
|
|
291
|
+
The empty string if the element is not present or contains no text.
|
|
292
|
+
"""
|
|
293
|
+
element = getattr(self, property_name)
|
|
294
|
+
if element is None:
|
|
295
|
+
return ""
|
|
296
|
+
if element.text is None:
|
|
297
|
+
return ""
|
|
298
|
+
return element.text
|