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/__init__.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Initialize `docx` package.
|
|
2
|
+
|
|
3
|
+
Export the `Document` constructor function and establish the mapping of part-type to
|
|
4
|
+
the part-classe that implements that type.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Type
|
|
10
|
+
|
|
11
|
+
from docx.api import Document
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from docx.opc.part import Part
|
|
15
|
+
|
|
16
|
+
__version__ = "1.3.0"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["Document"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# -- register custom Part classes with opc package reader --
|
|
23
|
+
|
|
24
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
25
|
+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
26
|
+
from docx.opc.part import PartFactory
|
|
27
|
+
from docx.opc.parts.coreprops import CorePropertiesPart
|
|
28
|
+
from docx.parts.comments import CommentsPart
|
|
29
|
+
from docx.parts.document import DocumentPart
|
|
30
|
+
from docx.parts.hdrftr import FooterPart, HeaderPart
|
|
31
|
+
from docx.parts.image import ImagePart
|
|
32
|
+
from docx.parts.numbering import NumberingPart
|
|
33
|
+
from docx.parts.settings import SettingsPart
|
|
34
|
+
from docx.parts.styles import StylesPart
|
|
35
|
+
from docx.parts.theme import ThemePart
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def part_class_selector(content_type: str, reltype: str) -> Type[Part] | None:
|
|
39
|
+
if reltype == RT.IMAGE:
|
|
40
|
+
return ImagePart
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
PartFactory.part_class_selector = part_class_selector
|
|
45
|
+
PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart
|
|
46
|
+
PartFactory.part_type_for[CT.WML_COMMENTS] = CommentsPart
|
|
47
|
+
PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart
|
|
48
|
+
PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart
|
|
49
|
+
PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart
|
|
50
|
+
PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart
|
|
51
|
+
PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart
|
|
52
|
+
PartFactory.part_type_for[CT.WML_STYLES] = StylesPart
|
|
53
|
+
PartFactory.part_type_for[CT.OFC_THEME] = ThemePart
|
|
54
|
+
|
|
55
|
+
del (
|
|
56
|
+
CT,
|
|
57
|
+
CorePropertiesPart,
|
|
58
|
+
CommentsPart,
|
|
59
|
+
DocumentPart,
|
|
60
|
+
FooterPart,
|
|
61
|
+
HeaderPart,
|
|
62
|
+
NumberingPart,
|
|
63
|
+
PartFactory,
|
|
64
|
+
SettingsPart,
|
|
65
|
+
StylesPart,
|
|
66
|
+
ThemePart,
|
|
67
|
+
part_class_selector,
|
|
68
|
+
)
|
docx/api.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Directly exposed API functions and classes, :func:`Document` for now.
|
|
2
|
+
|
|
3
|
+
Provides a syntactically more convenient API for interacting with the OpcPackage graph.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import IO, TYPE_CHECKING, cast
|
|
10
|
+
|
|
11
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
12
|
+
from docx.package import Package
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from docx.document import Document as DocumentObject
|
|
16
|
+
from docx.parts.document import DocumentPart
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def Document(docx: str | IO[bytes] | None = None) -> DocumentObject:
|
|
20
|
+
"""Return a |Document| object loaded from `docx`, where `docx` can be either a path
|
|
21
|
+
to a ``.docx`` file (a string) or a file-like object.
|
|
22
|
+
|
|
23
|
+
If `docx` is missing or ``None``, the built-in default document "template" is
|
|
24
|
+
loaded.
|
|
25
|
+
"""
|
|
26
|
+
docx = _default_docx_path() if docx is None else docx
|
|
27
|
+
document_part = cast("DocumentPart", Package.open(docx).main_document_part)
|
|
28
|
+
if document_part.content_type != CT.WML_DOCUMENT_MAIN:
|
|
29
|
+
tmpl = "file '%s' is not a Word file, content type is '%s'"
|
|
30
|
+
raise ValueError(tmpl % (docx, document_part.content_type))
|
|
31
|
+
return document_part.document
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _default_docx_path():
|
|
35
|
+
"""Return the path to the built-in default .docx package."""
|
|
36
|
+
_thisdir = os.path.split(__file__)[0]
|
|
37
|
+
return os.path.join(_thisdir, "templates", "default.docx")
|
docx/blkcntnr.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# pyright: reportImportCycles=false
|
|
2
|
+
|
|
3
|
+
"""Block item container, used by body, cell, header, etc.
|
|
4
|
+
|
|
5
|
+
Block level items are things like paragraph and table, although there are a few other
|
|
6
|
+
specialized ones like structured document tags.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Iterator
|
|
12
|
+
|
|
13
|
+
from typing_extensions import TypeAlias
|
|
14
|
+
|
|
15
|
+
from docx.oxml.table import CT_Tbl
|
|
16
|
+
from docx.oxml.text.paragraph import CT_P
|
|
17
|
+
from docx.shared import StoryChild
|
|
18
|
+
from docx.text.paragraph import Paragraph
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import docx.types as t
|
|
22
|
+
from docx.oxml.comments import CT_Comment
|
|
23
|
+
from docx.oxml.document import CT_Body
|
|
24
|
+
from docx.oxml.section import CT_HdrFtr
|
|
25
|
+
from docx.oxml.table import CT_Tc
|
|
26
|
+
from docx.shared import Length
|
|
27
|
+
from docx.styles.style import ParagraphStyle
|
|
28
|
+
from docx.table import Table
|
|
29
|
+
|
|
30
|
+
BlockItemElement: TypeAlias = "CT_Body | CT_Comment | CT_HdrFtr | CT_Tc"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BlockItemContainer(StoryChild):
|
|
34
|
+
"""Base class for proxy objects that can contain block items.
|
|
35
|
+
|
|
36
|
+
These containers include _Body, _Cell, header, footer, footnote, endnote, comment,
|
|
37
|
+
and text box objects. Provides the shared functionality to add a block item like a
|
|
38
|
+
paragraph or table.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, element: BlockItemElement, parent: t.ProvidesStoryPart):
|
|
42
|
+
super(BlockItemContainer, self).__init__(parent)
|
|
43
|
+
self._element = element
|
|
44
|
+
|
|
45
|
+
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
|
|
46
|
+
"""Return paragraph newly added to the end of the content in this container.
|
|
47
|
+
|
|
48
|
+
The paragraph has `text` in a single run if present, and is given paragraph
|
|
49
|
+
style `style`.
|
|
50
|
+
|
|
51
|
+
If `style` is |None|, no paragraph style is applied, which has the same effect
|
|
52
|
+
as applying the 'Normal' style.
|
|
53
|
+
"""
|
|
54
|
+
paragraph = self._add_paragraph()
|
|
55
|
+
if text:
|
|
56
|
+
paragraph.add_run(text)
|
|
57
|
+
if style is not None:
|
|
58
|
+
paragraph.style = style
|
|
59
|
+
return paragraph
|
|
60
|
+
|
|
61
|
+
def add_table(self, rows: int, cols: int, width: Length) -> Table:
|
|
62
|
+
"""Return table of `width` having `rows` rows and `cols` columns.
|
|
63
|
+
|
|
64
|
+
The table is appended appended at the end of the content in this container.
|
|
65
|
+
|
|
66
|
+
`width` is evenly distributed between the table columns.
|
|
67
|
+
"""
|
|
68
|
+
from docx.table import Table
|
|
69
|
+
|
|
70
|
+
tbl = CT_Tbl.new_tbl(rows, cols, width)
|
|
71
|
+
self._element._insert_tbl(tbl) # pyright: ignore[reportPrivateUsage]
|
|
72
|
+
return Table(tbl, self)
|
|
73
|
+
|
|
74
|
+
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
|
|
75
|
+
"""Generate each `Paragraph` or `Table` in this container in document order."""
|
|
76
|
+
from docx.table import Table
|
|
77
|
+
|
|
78
|
+
for element in self._element.inner_content_elements:
|
|
79
|
+
yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def paragraphs(self):
|
|
83
|
+
"""A list containing the paragraphs in this container, in document order.
|
|
84
|
+
|
|
85
|
+
Read-only.
|
|
86
|
+
"""
|
|
87
|
+
return [Paragraph(p, self) for p in self._element.p_lst]
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def tables(self):
|
|
91
|
+
"""A list containing the tables in this container, in document order.
|
|
92
|
+
|
|
93
|
+
Read-only.
|
|
94
|
+
"""
|
|
95
|
+
from docx.table import Table
|
|
96
|
+
|
|
97
|
+
return [Table(tbl, self) for tbl in self._element.tbl_lst]
|
|
98
|
+
|
|
99
|
+
def _add_paragraph(self):
|
|
100
|
+
"""Return paragraph newly added to the end of the content in this container."""
|
|
101
|
+
return Paragraph(self._element.add_p(), self)
|
docx/comments.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Collection providing access to comments added to this document."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
from typing import TYPE_CHECKING, Iterator
|
|
7
|
+
|
|
8
|
+
from docx.blkcntnr import BlockItemContainer
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from docx.oxml.comments import CT_Comment, CT_Comments
|
|
12
|
+
from docx.parts.comments import CommentsPart
|
|
13
|
+
from docx.styles.style import ParagraphStyle
|
|
14
|
+
from docx.text.paragraph import Paragraph
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Comments:
|
|
18
|
+
"""Collection containing the comments added to this document."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, comments_elm: CT_Comments, comments_part: CommentsPart):
|
|
21
|
+
self._comments_elm = comments_elm
|
|
22
|
+
self._comments_part = comments_part
|
|
23
|
+
|
|
24
|
+
def __iter__(self) -> Iterator[Comment]:
|
|
25
|
+
"""Iterator over the comments in this collection."""
|
|
26
|
+
return (
|
|
27
|
+
Comment(comment_elm, self._comments_part)
|
|
28
|
+
for comment_elm in self._comments_elm.comment_lst
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def __len__(self) -> int:
|
|
32
|
+
"""The number of comments in this collection."""
|
|
33
|
+
return len(self._comments_elm.comment_lst)
|
|
34
|
+
|
|
35
|
+
def add_comment(self, text: str = "", author: str = "", initials: str | None = "") -> Comment:
|
|
36
|
+
"""Add a new comment to the document and return it.
|
|
37
|
+
|
|
38
|
+
The comment is added to the end of the comments collection and is assigned a unique
|
|
39
|
+
comment-id.
|
|
40
|
+
|
|
41
|
+
If `text` is provided, it is added to the comment. This option provides for the common
|
|
42
|
+
case where a comment contains a modest passage of plain text. Multiple paragraphs can be
|
|
43
|
+
added using the `text` argument by separating their text with newlines (`"\\\\n"`).
|
|
44
|
+
Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`.
|
|
45
|
+
|
|
46
|
+
The default is to place a single empty paragraph in the comment, which is the same
|
|
47
|
+
behavior as the Word UI when you add a comment. New runs can be added to the first
|
|
48
|
+
paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more
|
|
49
|
+
complex text with emphasis or images. Additional paragraphs can be added using
|
|
50
|
+
`.add_paragraph()`.
|
|
51
|
+
|
|
52
|
+
`author` is a required attribute, set to the empty string by default.
|
|
53
|
+
|
|
54
|
+
`initials` is an optional attribute, set to the empty string by default. Passing |None|
|
|
55
|
+
for the `initials` parameter causes that attribute to be omitted from the XML.
|
|
56
|
+
"""
|
|
57
|
+
comment_elm = self._comments_elm.add_comment()
|
|
58
|
+
comment_elm.author = author
|
|
59
|
+
comment_elm.initials = initials
|
|
60
|
+
comment_elm.date = dt.datetime.now(dt.timezone.utc)
|
|
61
|
+
comment = Comment(comment_elm, self._comments_part)
|
|
62
|
+
|
|
63
|
+
if text == "":
|
|
64
|
+
return comment
|
|
65
|
+
|
|
66
|
+
para_text_iter = iter(text.split("\n"))
|
|
67
|
+
|
|
68
|
+
first_para_text = next(para_text_iter)
|
|
69
|
+
first_para = comment.paragraphs[0]
|
|
70
|
+
first_para.add_run(first_para_text)
|
|
71
|
+
|
|
72
|
+
for s in para_text_iter:
|
|
73
|
+
comment.add_paragraph(text=s)
|
|
74
|
+
|
|
75
|
+
return comment
|
|
76
|
+
|
|
77
|
+
def get(self, comment_id: int) -> Comment | None:
|
|
78
|
+
"""Return the comment identified by `comment_id`, or |None| if not found."""
|
|
79
|
+
comment_elm = self._comments_elm.get_comment_by_id(comment_id)
|
|
80
|
+
return Comment(comment_elm, self._comments_part) if comment_elm is not None else None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Comment(BlockItemContainer):
|
|
84
|
+
"""Proxy for a single comment in the document.
|
|
85
|
+
|
|
86
|
+
Provides methods to access comment metadata such as author, initials, and date.
|
|
87
|
+
|
|
88
|
+
A comment is also a block-item container, similar to a table cell, so it can contain both
|
|
89
|
+
paragraphs and tables and its paragraphs can contain rich text, hyperlinks and images,
|
|
90
|
+
although the common case is that a comment contains a single paragraph of plain text like a
|
|
91
|
+
sentence or phrase.
|
|
92
|
+
|
|
93
|
+
Note that certain content like tables may not be displayed in the Word comment sidebar due to
|
|
94
|
+
space limitations. Such "over-sized" content can still be viewed in the review pane.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, comment_elm: CT_Comment, comments_part: CommentsPart):
|
|
98
|
+
super().__init__(comment_elm, comments_part)
|
|
99
|
+
self._comment_elm = comment_elm
|
|
100
|
+
|
|
101
|
+
def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph:
|
|
102
|
+
"""Return paragraph newly added to the end of the content in this container.
|
|
103
|
+
|
|
104
|
+
The paragraph has `text` in a single run if present, and is given paragraph style `style`.
|
|
105
|
+
When `style` is |None| or ommitted, the "CommentText" paragraph style is applied, which is
|
|
106
|
+
the default style for comments.
|
|
107
|
+
"""
|
|
108
|
+
paragraph = super().add_paragraph(text, style)
|
|
109
|
+
|
|
110
|
+
# -- have to assign style directly to element because `paragraph.style` raises when
|
|
111
|
+
# -- a style is not present in the styles part
|
|
112
|
+
if style is None:
|
|
113
|
+
paragraph._p.style = "CommentText" # pyright: ignore[reportPrivateUsage]
|
|
114
|
+
|
|
115
|
+
return paragraph
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def author(self) -> str:
|
|
119
|
+
"""Read/write. The recorded author of this comment.
|
|
120
|
+
|
|
121
|
+
This field is required but can be set to the empty string.
|
|
122
|
+
"""
|
|
123
|
+
return self._comment_elm.author
|
|
124
|
+
|
|
125
|
+
@author.setter
|
|
126
|
+
def author(self, value: str):
|
|
127
|
+
self._comment_elm.author = value
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def comment_id(self) -> int:
|
|
131
|
+
"""The unique identifier of this comment."""
|
|
132
|
+
return self._comment_elm.id
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def initials(self) -> str | None:
|
|
136
|
+
"""Read/write. The recorded initials of the comment author.
|
|
137
|
+
|
|
138
|
+
This attribute is optional in the XML, returns |None| if not set. Assigning |None| removes
|
|
139
|
+
any existing initials from the XML.
|
|
140
|
+
"""
|
|
141
|
+
return self._comment_elm.initials
|
|
142
|
+
|
|
143
|
+
@initials.setter
|
|
144
|
+
def initials(self, value: str | None):
|
|
145
|
+
self._comment_elm.initials = value
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def text(self) -> str:
|
|
149
|
+
"""The text content of this comment as a string.
|
|
150
|
+
|
|
151
|
+
Only content in paragraphs is included and of course all emphasis and styling is stripped.
|
|
152
|
+
|
|
153
|
+
Paragraph boundaries are indicated with a newline (`"\\\\n"`)
|
|
154
|
+
"""
|
|
155
|
+
return "\n".join(p.text for p in self.paragraphs)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def timestamp(self) -> dt.datetime | None:
|
|
159
|
+
"""The date and time this comment was authored.
|
|
160
|
+
|
|
161
|
+
This attribute is optional in the XML, returns |None| if not set.
|
|
162
|
+
"""
|
|
163
|
+
return self._comment_elm.date
|
docx/dml/__init__.py
ADDED
|
File without changes
|
docx/dml/color.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""DrawingML objects related to color, ColorFormat being the most prominent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, cast
|
|
6
|
+
|
|
7
|
+
from typing_extensions import TypeAlias
|
|
8
|
+
|
|
9
|
+
from docx.enum.dml import MSO_COLOR_TYPE
|
|
10
|
+
from docx.oxml.simpletypes import ST_HexColorAuto
|
|
11
|
+
from docx.shared import ElementProxy, RGBColor
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from docx.enum.dml import MSO_THEME_COLOR
|
|
15
|
+
from docx.oxml.text.font import CT_Color
|
|
16
|
+
from docx.oxml.text.run import CT_R
|
|
17
|
+
|
|
18
|
+
# -- other element types can be a parent of an `w:rPr` element, but for now only `w:r` is --
|
|
19
|
+
RPrParent: TypeAlias = "CT_R"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ColorFormat(ElementProxy):
|
|
23
|
+
"""Provides access to color settings like RGB color, theme color, and luminance adjustments."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, rPr_parent: RPrParent):
|
|
26
|
+
super(ColorFormat, self).__init__(rPr_parent)
|
|
27
|
+
self._element = rPr_parent
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def rgb(self) -> RGBColor | None:
|
|
31
|
+
"""An |RGBColor| value or |None| if no RGB color is specified.
|
|
32
|
+
|
|
33
|
+
When :attr:`type` is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an
|
|
34
|
+
|RGBColor| value. It may also be an |RGBColor| value if :attr:`type` is
|
|
35
|
+
`MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is
|
|
36
|
+
assigned. In that case, the RGB value should be interpreted as no more than a good guess
|
|
37
|
+
however, as the theme color takes precedence at rendering time. Its value is |None|
|
|
38
|
+
whenever :attr:`type` is either |None| or `MSO_COLOR_TYPE.AUTO`.
|
|
39
|
+
|
|
40
|
+
Assigning an |RGBColor| value causes :attr:`type` to become `MSO_COLOR_TYPE.RGB` and any
|
|
41
|
+
theme color is removed. Assigning |None| causes any color to be removed such that the
|
|
42
|
+
effective color is inherited from the style hierarchy.
|
|
43
|
+
"""
|
|
44
|
+
color = self._color
|
|
45
|
+
if color is None:
|
|
46
|
+
return None
|
|
47
|
+
if color.val == ST_HexColorAuto.AUTO:
|
|
48
|
+
return None
|
|
49
|
+
return cast(RGBColor, color.val)
|
|
50
|
+
|
|
51
|
+
@rgb.setter
|
|
52
|
+
def rgb(self, value: RGBColor | None):
|
|
53
|
+
if value is None and self._color is None:
|
|
54
|
+
return
|
|
55
|
+
rPr = self._element.get_or_add_rPr()
|
|
56
|
+
rPr._remove_color() # pyright: ignore[reportPrivateUsage]
|
|
57
|
+
if value is not None:
|
|
58
|
+
rPr.get_or_add_color().val = value
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def theme_color(self) -> MSO_THEME_COLOR | None:
|
|
62
|
+
"""Member of :ref:`MsoThemeColorIndex` or |None| if no theme color is specified.
|
|
63
|
+
|
|
64
|
+
When :attr:`type` is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a
|
|
65
|
+
member of :ref:`MsoThemeColorIndex`. When :attr:`type` has any other value, the value of
|
|
66
|
+
this property is |None|.
|
|
67
|
+
|
|
68
|
+
Assigning a member of :ref:`MsoThemeColorIndex` causes :attr:`type` to become
|
|
69
|
+
`MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning
|
|
70
|
+
|None| causes any color specification to be removed such that the effective color is
|
|
71
|
+
inherited from the style hierarchy.
|
|
72
|
+
"""
|
|
73
|
+
color = self._color
|
|
74
|
+
if color is None:
|
|
75
|
+
return None
|
|
76
|
+
return color.themeColor
|
|
77
|
+
|
|
78
|
+
@theme_color.setter
|
|
79
|
+
def theme_color(self, value: MSO_THEME_COLOR | None):
|
|
80
|
+
if value is None:
|
|
81
|
+
if self._color is not None and self._element.rPr is not None:
|
|
82
|
+
self._element.rPr._remove_color() # pyright: ignore[reportPrivateUsage]
|
|
83
|
+
return
|
|
84
|
+
self._element.get_or_add_rPr().get_or_add_color().themeColor = value
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def type(self) -> MSO_COLOR_TYPE | None:
|
|
88
|
+
"""Read-only.
|
|
89
|
+
|
|
90
|
+
A member of :ref:`MsoColorType`, one of RGB, THEME, or AUTO, corresponding to the way this
|
|
91
|
+
color is defined. Its value is |None| if no color is applied at this level, which causes
|
|
92
|
+
the effective color to be inherited from the style hierarchy.
|
|
93
|
+
"""
|
|
94
|
+
color = self._color
|
|
95
|
+
if color is None:
|
|
96
|
+
return None
|
|
97
|
+
if color.themeColor is not None:
|
|
98
|
+
return MSO_COLOR_TYPE.THEME
|
|
99
|
+
if color.val == ST_HexColorAuto.AUTO:
|
|
100
|
+
return MSO_COLOR_TYPE.AUTO
|
|
101
|
+
return MSO_COLOR_TYPE.RGB
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def _color(self) -> CT_Color | None:
|
|
105
|
+
"""Return `w:rPr/w:color` or |None| if not present.
|
|
106
|
+
|
|
107
|
+
Helper to factor out repetitive element access.
|
|
108
|
+
"""
|
|
109
|
+
rPr = self._element.rPr
|
|
110
|
+
if rPr is None:
|
|
111
|
+
return None
|
|
112
|
+
return rPr.color
|