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/document.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Custom element classes that correspond to the document part, e.g. <w:document>."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Callable, List
|
|
6
|
+
|
|
7
|
+
from docx.oxml.section import CT_SectPr
|
|
8
|
+
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrMore, ZeroOrOne
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from docx.oxml.table import CT_Tbl
|
|
12
|
+
from docx.oxml.text.paragraph import CT_P
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CT_Document(BaseOxmlElement):
|
|
16
|
+
"""``<w:document>`` element, the root element of a document.xml file."""
|
|
17
|
+
|
|
18
|
+
body: CT_Body = ZeroOrOne("w:body") # pyright: ignore[reportAssignmentType]
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def sectPr_lst(self) -> List[CT_SectPr]:
|
|
22
|
+
"""All `w:sectPr` elements directly accessible from document element.
|
|
23
|
+
|
|
24
|
+
Note this does not include a `sectPr` child in a paragraphs wrapped in
|
|
25
|
+
revision marks or other intervening layer, perhaps `w:sdt` or customXml
|
|
26
|
+
elements.
|
|
27
|
+
|
|
28
|
+
`w:sectPr` elements appear in document order. The last one is always
|
|
29
|
+
`w:body/w:sectPr`, all preceding are `w:p/w:pPr/w:sectPr`.
|
|
30
|
+
"""
|
|
31
|
+
xpath = "./w:body/w:p/w:pPr/w:sectPr | ./w:body/w:sectPr"
|
|
32
|
+
return self.xpath(xpath)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CT_Body(BaseOxmlElement):
|
|
36
|
+
"""`w:body`, the container element for the main document story in `document.xml`."""
|
|
37
|
+
|
|
38
|
+
add_p: Callable[[], CT_P]
|
|
39
|
+
get_or_add_sectPr: Callable[[], CT_SectPr]
|
|
40
|
+
p_lst: List[CT_P]
|
|
41
|
+
tbl_lst: List[CT_Tbl]
|
|
42
|
+
|
|
43
|
+
_insert_tbl: Callable[[CT_Tbl], CT_Tbl]
|
|
44
|
+
|
|
45
|
+
p = ZeroOrMore("w:p", successors=("w:sectPr",))
|
|
46
|
+
tbl = ZeroOrMore("w:tbl", successors=("w:sectPr",))
|
|
47
|
+
sectPr: CT_SectPr | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
48
|
+
"w:sectPr", successors=()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def add_section_break(self) -> CT_SectPr:
|
|
52
|
+
"""Return `w:sectPr` element for new section added at end of document.
|
|
53
|
+
|
|
54
|
+
The last `w:sectPr` becomes the second-to-last, with the new `w:sectPr` being an
|
|
55
|
+
exact clone of the previous one, except that all header and footer references
|
|
56
|
+
are removed (and are therefore now "inherited" from the prior section).
|
|
57
|
+
|
|
58
|
+
A copy of the previously-last `w:sectPr` will now appear in a new `w:p` at the
|
|
59
|
+
end of the document. The returned `w:sectPr` is the sentinel `w:sectPr` for the
|
|
60
|
+
document (and as implemented, `is` the prior sentinel `w:sectPr` with headers
|
|
61
|
+
and footers removed).
|
|
62
|
+
"""
|
|
63
|
+
# ---get the sectPr at file-end, which controls last section (sections[-1])---
|
|
64
|
+
sentinel_sectPr = self.get_or_add_sectPr()
|
|
65
|
+
# ---add exact copy to new `w:p` element; that is now second-to last section---
|
|
66
|
+
self.add_p().set_sectPr(sentinel_sectPr.clone())
|
|
67
|
+
# ---remove any header or footer references from "new" last section---
|
|
68
|
+
for hdrftr_ref in sentinel_sectPr.xpath("w:headerReference|w:footerReference"):
|
|
69
|
+
sentinel_sectPr.remove(hdrftr_ref)
|
|
70
|
+
# ---the sentinel `w:sectPr` now controls the new last section---
|
|
71
|
+
return sentinel_sectPr
|
|
72
|
+
|
|
73
|
+
def clear_content(self):
|
|
74
|
+
"""Remove all content child elements from this <w:body> element.
|
|
75
|
+
|
|
76
|
+
Leave the <w:sectPr> element if it is present.
|
|
77
|
+
"""
|
|
78
|
+
for content_elm in self.xpath("./*[not(self::w:sectPr)]"):
|
|
79
|
+
self.remove(content_elm)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def inner_content_elements(self) -> List[CT_P | CT_Tbl]:
|
|
83
|
+
"""Generate all `w:p` and `w:tbl` elements in this document-body.
|
|
84
|
+
|
|
85
|
+
Elements appear in document order. Elements shaded by nesting in a `w:ins` or
|
|
86
|
+
other "wrapper" element will not be included.
|
|
87
|
+
"""
|
|
88
|
+
return self.xpath("./w:p | ./w:tbl")
|
docx/oxml/drawing.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Custom element-classes for DrawingML-related elements like `<w:drawing>`.
|
|
2
|
+
|
|
3
|
+
For legacy reasons, many DrawingML-related elements are in `docx.oxml.shape`. Expect
|
|
4
|
+
those to move over here as we have reason to touch them.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from docx.oxml.xmlchemy import BaseOxmlElement
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CT_Drawing(BaseOxmlElement):
|
|
11
|
+
"""`<w:drawing>` element, containing a DrawingML object like a picture or chart."""
|
docx/oxml/exceptions.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Exceptions for oxml sub-package."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class XmlchemyError(Exception):
|
|
5
|
+
"""Generic error class."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidXmlError(XmlchemyError):
|
|
9
|
+
"""Raised when invalid XML is encountered, such as on attempt to access a missing
|
|
10
|
+
required child element."""
|
docx/oxml/ns.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Namespace-related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
nsmap = {
|
|
8
|
+
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
9
|
+
"c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
|
|
10
|
+
"cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
|
|
11
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
|
12
|
+
"dcmitype": "http://purl.org/dc/dcmitype/",
|
|
13
|
+
"dcterms": "http://purl.org/dc/terms/",
|
|
14
|
+
"dgm": "http://schemas.openxmlformats.org/drawingml/2006/diagram",
|
|
15
|
+
"m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
|
|
16
|
+
"pic": "http://schemas.openxmlformats.org/drawingml/2006/picture",
|
|
17
|
+
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
18
|
+
"sl": "http://schemas.openxmlformats.org/schemaLibrary/2006/main",
|
|
19
|
+
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
20
|
+
"w14": "http://schemas.microsoft.com/office/word/2010/wordml",
|
|
21
|
+
"wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
|
22
|
+
"xml": "http://www.w3.org/XML/1998/namespace",
|
|
23
|
+
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pfxmap = {value: key for key, value in nsmap.items()}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NamespacePrefixedTag(str):
|
|
30
|
+
"""Value object that knows the semantics of an XML tag having a namespace prefix."""
|
|
31
|
+
|
|
32
|
+
def __new__(cls, nstag: str):
|
|
33
|
+
return super(NamespacePrefixedTag, cls).__new__(cls, nstag)
|
|
34
|
+
|
|
35
|
+
def __init__(self, nstag: str):
|
|
36
|
+
self._pfx, self._local_part = nstag.split(":")
|
|
37
|
+
self._ns_uri = nsmap[self._pfx]
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def clark_name(self) -> str:
|
|
41
|
+
return "{%s}%s" % (self._ns_uri, self._local_part)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_clark_name(cls, clark_name: str) -> NamespacePrefixedTag:
|
|
45
|
+
nsuri, local_name = clark_name[1:].split("}")
|
|
46
|
+
nstag = "%s:%s" % (pfxmap[nsuri], local_name)
|
|
47
|
+
return cls(nstag)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def local_part(self) -> str:
|
|
51
|
+
"""The local part of this tag.
|
|
52
|
+
|
|
53
|
+
E.g. "foobar" is returned for tag "f:foobar".
|
|
54
|
+
"""
|
|
55
|
+
return self._local_part
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def nsmap(self) -> Dict[str, str]:
|
|
59
|
+
"""Single-member dict mapping prefix of this tag to it's namespace name.
|
|
60
|
+
|
|
61
|
+
Example: `{"f": "http://foo/bar"}`. This is handy for passing to xpath calls
|
|
62
|
+
and other uses.
|
|
63
|
+
"""
|
|
64
|
+
return {self._pfx: self._ns_uri}
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def nspfx(self) -> str:
|
|
68
|
+
"""The namespace-prefix for this tag.
|
|
69
|
+
|
|
70
|
+
For example, "f" is returned for tag "f:foobar".
|
|
71
|
+
"""
|
|
72
|
+
return self._pfx
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def nsuri(self) -> str:
|
|
76
|
+
"""The namespace URI for this tag.
|
|
77
|
+
|
|
78
|
+
For example, "http://foo/bar" would be returned for tag "f:foobar" if the "f"
|
|
79
|
+
prefix maps to "http://foo/bar" in nsmap.
|
|
80
|
+
"""
|
|
81
|
+
return self._ns_uri
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def nsdecls(*prefixes: str) -> str:
|
|
85
|
+
"""Namespace declaration including each namespace-prefix in `prefixes`.
|
|
86
|
+
|
|
87
|
+
Handy for adding required namespace declarations to a tree root element.
|
|
88
|
+
"""
|
|
89
|
+
return " ".join(['xmlns:%s="%s"' % (pfx, nsmap[pfx]) for pfx in prefixes])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def nspfxmap(*nspfxs: str) -> Dict[str, str]:
|
|
93
|
+
"""Subset namespace-prefix mappings specified by *nspfxs*.
|
|
94
|
+
|
|
95
|
+
Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p").
|
|
96
|
+
"""
|
|
97
|
+
return {pfx: nsmap[pfx] for pfx in nspfxs}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def qn(tag: str) -> str:
|
|
101
|
+
"""Stands for "qualified name".
|
|
102
|
+
|
|
103
|
+
This utility function converts a familiar namespace-prefixed tag name like "w:p"
|
|
104
|
+
into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns
|
|
105
|
+
"{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p".
|
|
106
|
+
"""
|
|
107
|
+
prefix, tagroot = tag.split(":")
|
|
108
|
+
uri = nsmap[prefix]
|
|
109
|
+
return "{%s}%s" % (uri, tagroot)
|
docx/oxml/numbering.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Custom element classes related to the numbering part."""
|
|
2
|
+
|
|
3
|
+
from docx.oxml.parser import OxmlElement
|
|
4
|
+
from docx.oxml.shared import CT_DecimalNumber
|
|
5
|
+
from docx.oxml.simpletypes import ST_DecimalNumber
|
|
6
|
+
from docx.oxml.xmlchemy import (
|
|
7
|
+
BaseOxmlElement,
|
|
8
|
+
OneAndOnlyOne,
|
|
9
|
+
RequiredAttribute,
|
|
10
|
+
ZeroOrMore,
|
|
11
|
+
ZeroOrOne,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CT_Num(BaseOxmlElement):
|
|
16
|
+
"""``<w:num>`` element, which represents a concrete list definition instance, having
|
|
17
|
+
a required child <w:abstractNumId> that references an abstract numbering definition
|
|
18
|
+
that defines most of the formatting details."""
|
|
19
|
+
|
|
20
|
+
abstractNumId = OneAndOnlyOne("w:abstractNumId")
|
|
21
|
+
lvlOverride = ZeroOrMore("w:lvlOverride")
|
|
22
|
+
numId = RequiredAttribute("w:numId", ST_DecimalNumber)
|
|
23
|
+
|
|
24
|
+
def add_lvlOverride(self, ilvl):
|
|
25
|
+
"""Return a newly added CT_NumLvl (<w:lvlOverride>) element having its ``ilvl``
|
|
26
|
+
attribute set to `ilvl`."""
|
|
27
|
+
return self._add_lvlOverride(ilvl=ilvl)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def new(cls, num_id, abstractNum_id):
|
|
31
|
+
"""Return a new ``<w:num>`` element having numId of `num_id` and having a
|
|
32
|
+
``<w:abstractNumId>`` child with val attribute set to `abstractNum_id`."""
|
|
33
|
+
num = OxmlElement("w:num")
|
|
34
|
+
num.numId = num_id
|
|
35
|
+
abstractNumId = CT_DecimalNumber.new("w:abstractNumId", abstractNum_id)
|
|
36
|
+
num.append(abstractNumId)
|
|
37
|
+
return num
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CT_NumLvl(BaseOxmlElement):
|
|
41
|
+
"""``<w:lvlOverride>`` element, which identifies a level in a list definition to
|
|
42
|
+
override with settings it contains."""
|
|
43
|
+
|
|
44
|
+
startOverride = ZeroOrOne("w:startOverride", successors=("w:lvl",))
|
|
45
|
+
ilvl = RequiredAttribute("w:ilvl", ST_DecimalNumber)
|
|
46
|
+
|
|
47
|
+
def add_startOverride(self, val):
|
|
48
|
+
"""Return a newly added CT_DecimalNumber element having tagname
|
|
49
|
+
``w:startOverride`` and ``val`` attribute set to `val`."""
|
|
50
|
+
return self._add_startOverride(val=val)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CT_NumPr(BaseOxmlElement):
|
|
54
|
+
"""A ``<w:numPr>`` element, a container for numbering properties applied to a
|
|
55
|
+
paragraph."""
|
|
56
|
+
|
|
57
|
+
ilvl = ZeroOrOne("w:ilvl", successors=("w:numId", "w:numberingChange", "w:ins"))
|
|
58
|
+
numId = ZeroOrOne("w:numId", successors=("w:numberingChange", "w:ins"))
|
|
59
|
+
|
|
60
|
+
# @ilvl.setter
|
|
61
|
+
# def _set_ilvl(self, val):
|
|
62
|
+
# """
|
|
63
|
+
# Get or add a <w:ilvl> child and set its ``w:val`` attribute to `val`.
|
|
64
|
+
# """
|
|
65
|
+
# ilvl = self.get_or_add_ilvl()
|
|
66
|
+
# ilvl.val = val
|
|
67
|
+
|
|
68
|
+
# @numId.setter
|
|
69
|
+
# def numId(self, val):
|
|
70
|
+
# """
|
|
71
|
+
# Get or add a <w:numId> child and set its ``w:val`` attribute to
|
|
72
|
+
# `val`.
|
|
73
|
+
# """
|
|
74
|
+
# numId = self.get_or_add_numId()
|
|
75
|
+
# numId.val = val
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CT_Numbering(BaseOxmlElement):
|
|
79
|
+
"""``<w:numbering>`` element, the root element of a numbering part, i.e.
|
|
80
|
+
numbering.xml."""
|
|
81
|
+
|
|
82
|
+
num = ZeroOrMore("w:num", successors=("w:numIdMacAtCleanup",))
|
|
83
|
+
|
|
84
|
+
def add_num(self, abstractNum_id):
|
|
85
|
+
"""Return a newly added CT_Num (<w:num>) element referencing the abstract
|
|
86
|
+
numbering definition identified by `abstractNum_id`."""
|
|
87
|
+
next_num_id = self._next_numId
|
|
88
|
+
num = CT_Num.new(next_num_id, abstractNum_id)
|
|
89
|
+
return self._insert_num(num)
|
|
90
|
+
|
|
91
|
+
def num_having_numId(self, numId):
|
|
92
|
+
"""Return the ``<w:num>`` child element having ``numId`` attribute matching
|
|
93
|
+
`numId`."""
|
|
94
|
+
xpath = './w:num[@w:numId="%d"]' % numId
|
|
95
|
+
try:
|
|
96
|
+
return self.xpath(xpath)[0]
|
|
97
|
+
except IndexError:
|
|
98
|
+
raise KeyError("no <w:num> element with numId %d" % numId)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def _next_numId(self):
|
|
102
|
+
"""The first ``numId`` unused by a ``<w:num>`` element, starting at 1 and
|
|
103
|
+
filling any gaps in numbering between existing ``<w:num>`` elements."""
|
|
104
|
+
numId_strs = self.xpath("./w:num/@w:numId")
|
|
105
|
+
num_ids = [int(numId_str) for numId_str in numId_strs]
|
|
106
|
+
for num in range(1, len(num_ids) + 2):
|
|
107
|
+
if num not in num_ids:
|
|
108
|
+
break
|
|
109
|
+
return num
|
docx/oxml/parser.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# pyright: reportImportCycles=false
|
|
2
|
+
|
|
3
|
+
"""XML parser for python-docx."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, Type, cast
|
|
8
|
+
|
|
9
|
+
from lxml import etree
|
|
10
|
+
|
|
11
|
+
from docx.oxml.ns import NamespacePrefixedTag, nsmap
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from docx.oxml.xmlchemy import BaseOxmlElement
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# -- configure XML parser --
|
|
18
|
+
element_class_lookup = etree.ElementNamespaceClassLookup()
|
|
19
|
+
oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False)
|
|
20
|
+
oxml_parser.set_element_class_lookup(element_class_lookup)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_xml(xml: str | bytes) -> "BaseOxmlElement":
|
|
24
|
+
"""Root lxml element obtained by parsing XML character string `xml`.
|
|
25
|
+
|
|
26
|
+
The custom parser is used, so custom element classes are produced for elements in
|
|
27
|
+
`xml` that have them.
|
|
28
|
+
"""
|
|
29
|
+
return cast("BaseOxmlElement", etree.fromstring(xml, oxml_parser))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register_element_cls(tag: str, cls: Type["BaseOxmlElement"]):
|
|
33
|
+
"""Register an lxml custom element-class to use for `tag`.
|
|
34
|
+
|
|
35
|
+
A instance of `cls` to be constructed when the oxml parser encounters an element
|
|
36
|
+
with matching `tag`. `tag` is a string of the form `nspfx:tagroot`, e.g.
|
|
37
|
+
`'w:document'`.
|
|
38
|
+
"""
|
|
39
|
+
nspfx, tagroot = tag.split(":")
|
|
40
|
+
namespace = element_class_lookup.get_namespace(nsmap[nspfx])
|
|
41
|
+
namespace[tagroot] = cls
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def OxmlElement(
|
|
45
|
+
nsptag_str: str,
|
|
46
|
+
attrs: Dict[str, str] | None = None,
|
|
47
|
+
nsdecls: Dict[str, str] | None = None,
|
|
48
|
+
) -> BaseOxmlElement | etree._Element: # pyright: ignore[reportPrivateUsage]
|
|
49
|
+
"""Return a 'loose' lxml element having the tag specified by `nsptag_str`.
|
|
50
|
+
|
|
51
|
+
The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`.
|
|
52
|
+
The resulting element is an instance of the custom element class for this tag name
|
|
53
|
+
if one is defined. A dictionary of attribute values may be provided as `attrs`; they
|
|
54
|
+
are set if present. All namespaces defined in the dict `nsdecls` are declared in the
|
|
55
|
+
element using the key as the prefix and the value as the namespace name. If
|
|
56
|
+
`nsdecls` is not provided, a single namespace declaration is added based on the
|
|
57
|
+
prefix on `nsptag_str`.
|
|
58
|
+
"""
|
|
59
|
+
nsptag = NamespacePrefixedTag(nsptag_str)
|
|
60
|
+
if nsdecls is None:
|
|
61
|
+
nsdecls = nsptag.nsmap
|
|
62
|
+
return oxml_parser.makeelement(nsptag.clark_name, attrib=attrs, nsmap=nsdecls)
|