python-docx-oss 0.1.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 (132) hide show
  1. docx/__init__.py +71 -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 +285 -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 +32 -0
  19. docx/image/bmp.py +43 -0
  20. docx/image/constants.py +172 -0
  21. docx/image/emf.py +46 -0
  22. docx/image/exceptions.py +13 -0
  23. docx/image/gif.py +38 -0
  24. docx/image/helpers.py +86 -0
  25. docx/image/image.py +237 -0
  26. docx/image/jpeg.py +469 -0
  27. docx/image/png.py +253 -0
  28. docx/image/svg.py +118 -0
  29. docx/image/tiff.py +289 -0
  30. docx/opc/__init__.py +0 -0
  31. docx/opc/constants.py +307 -0
  32. docx/opc/coreprops.py +142 -0
  33. docx/opc/customprops.py +59 -0
  34. docx/opc/exceptions.py +12 -0
  35. docx/opc/oxml.py +247 -0
  36. docx/opc/package.py +254 -0
  37. docx/opc/packuri.py +109 -0
  38. docx/opc/part.py +267 -0
  39. docx/opc/parts/__init__.py +0 -0
  40. docx/opc/parts/coreprops.py +48 -0
  41. docx/opc/parts/customprops.py +61 -0
  42. docx/opc/phys_pkg.py +119 -0
  43. docx/opc/pkgreader.py +254 -0
  44. docx/opc/pkgwriter.py +115 -0
  45. docx/opc/rel.py +169 -0
  46. docx/opc/shared.py +31 -0
  47. docx/opc/spec.py +24 -0
  48. docx/oxml/__init__.py +252 -0
  49. docx/oxml/comments.py +124 -0
  50. docx/oxml/coreprops.py +298 -0
  51. docx/oxml/customprops.py +141 -0
  52. docx/oxml/dml/CT_ExtensionList.py +48 -0
  53. docx/oxml/document.py +88 -0
  54. docx/oxml/drawing.py +11 -0
  55. docx/oxml/exceptions.py +10 -0
  56. docx/oxml/ns.py +111 -0
  57. docx/oxml/numbering.py +109 -0
  58. docx/oxml/parser.py +62 -0
  59. docx/oxml/section.py +537 -0
  60. docx/oxml/settings.py +138 -0
  61. docx/oxml/shape.py +426 -0
  62. docx/oxml/shared.py +52 -0
  63. docx/oxml/simpletypes.py +434 -0
  64. docx/oxml/styles.py +320 -0
  65. docx/oxml/table.py +977 -0
  66. docx/oxml/text/__init__.py +0 -0
  67. docx/oxml/text/font.py +358 -0
  68. docx/oxml/text/hyperlink.py +45 -0
  69. docx/oxml/text/pagebreak.py +278 -0
  70. docx/oxml/text/paragraph.py +106 -0
  71. docx/oxml/text/parfmt.py +392 -0
  72. docx/oxml/text/run.py +316 -0
  73. docx/oxml/xmlchemy.py +711 -0
  74. docx/package.py +119 -0
  75. docx/parts/__init__.py +0 -0
  76. docx/parts/comments.py +51 -0
  77. docx/parts/customxml.py +67 -0
  78. docx/parts/document.py +193 -0
  79. docx/parts/hdrftr.py +53 -0
  80. docx/parts/image.py +80 -0
  81. docx/parts/numbering.py +51 -0
  82. docx/parts/settings.py +50 -0
  83. docx/parts/story.py +113 -0
  84. docx/parts/styles.py +42 -0
  85. docx/py.typed +0 -0
  86. docx/section.py +479 -0
  87. docx/settings.py +35 -0
  88. docx/shape.py +103 -0
  89. docx/shared.py +382 -0
  90. docx/styles/__init__.py +40 -0
  91. docx/styles/latent.py +198 -0
  92. docx/styles/style.py +254 -0
  93. docx/styles/styles.py +136 -0
  94. docx/table.py +537 -0
  95. docx/templates/default-comments.xml +12 -0
  96. docx/templates/default-docx-template/[Content_Types].xml +17 -0
  97. docx/templates/default-docx-template/_rels/.rels +7 -0
  98. docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
  99. docx/templates/default-docx-template/customXml/item1.xml +2 -0
  100. docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
  101. docx/templates/default-docx-template/docProps/app.xml +36 -0
  102. docx/templates/default-docx-template/docProps/core.xml +13 -0
  103. docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
  104. docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
  105. docx/templates/default-docx-template/word/document.xml +11 -0
  106. docx/templates/default-docx-template/word/fontTable.xml +61 -0
  107. docx/templates/default-docx-template/word/numbering.xml +201 -0
  108. docx/templates/default-docx-template/word/settings.xml +53 -0
  109. docx/templates/default-docx-template/word/styles.xml +11844 -0
  110. docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
  111. docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
  112. docx/templates/default-docx-template/word/webSettings.xml +5 -0
  113. docx/templates/default-footer.xml +27 -0
  114. docx/templates/default-header.xml +27 -0
  115. docx/templates/default-numbering.xml +20 -0
  116. docx/templates/default-settings.xml +26 -0
  117. docx/templates/default-styles.xml +190 -0
  118. docx/templates/default.docx +0 -0
  119. docx/text/__init__.py +0 -0
  120. docx/text/font.py +466 -0
  121. docx/text/hyperlink.py +121 -0
  122. docx/text/pagebreak.py +104 -0
  123. docx/text/paragraph.py +173 -0
  124. docx/text/parfmt.py +286 -0
  125. docx/text/run.py +272 -0
  126. docx/text/tabstops.py +123 -0
  127. docx/types.py +34 -0
  128. python_docx_oss-0.1.0.dist-info/METADATA +57 -0
  129. python_docx_oss-0.1.0.dist-info/RECORD +132 -0
  130. python_docx_oss-0.1.0.dist-info/WHEEL +5 -0
  131. python_docx_oss-0.1.0.dist-info/licenses/LICENSE +21 -0
  132. python_docx_oss-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ """
2
+ lxml custom element classes for core properties-related XML elements.
3
+ """
4
+ import re
5
+ from datetime import datetime, timedelta
6
+
7
+ from . import parse_xml
8
+ from .ns import nsdecls, qn
9
+ from .xmlchemy import BaseOxmlElement
10
+
11
+
12
+ class CT_CustomProperties(BaseOxmlElement):
13
+ """
14
+ ``<Properties>`` element, the root element of the Custom Properties
15
+ part stored as ``/docProps/custom.xml``. String elements are
16
+ limited in length to 255 unicode characters.
17
+ """
18
+
19
+ _customProperties_tmpl = (
20
+ '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" %s/>\n'
21
+ % nsdecls("vt")
22
+ )
23
+
24
+ @classmethod
25
+ def new(cls):
26
+ """
27
+ Return a new ``<property>`` element
28
+ """
29
+ xml = cls._customProperties_tmpl
30
+ return parse_xml(xml)
31
+
32
+ def _datetime_of_element(self, property_name):
33
+ element = getattr(self, property_name)
34
+ if element is None:
35
+ return None
36
+ datetime_str = element.text
37
+ try:
38
+ return self._parse_W3CDTF_to_datetime(datetime_str)
39
+ except ValueError:
40
+ # invalid datetime strings are ignored
41
+ return None
42
+
43
+ def _get_or_add(self, prop_name):
44
+ """
45
+ Return element returned by 'get_or_add_' method for *prop_name*.
46
+ """
47
+ get_or_add_method_name = f"get_or_add_{prop_name}"
48
+ get_or_add_method = getattr(self, get_or_add_method_name)
49
+ return get_or_add_method()
50
+
51
+ @classmethod
52
+ def _offset_dt(cls, dt, offset_str):
53
+ """
54
+ Return a |datetime| instance that is offset from datetime *dt* by
55
+ the timezone offset specified in *offset_str*, a string like
56
+ ``'-07:00'``.
57
+ """
58
+ match = cls._offset_pattern.match(offset_str)
59
+ if match is None:
60
+ raise ValueError("'%s' is not a valid offset string" % offset_str)
61
+ sign, hours_str, minutes_str = match.groups()
62
+ sign_factor = -1 if sign == "+" else 1
63
+ hours = int(hours_str) * sign_factor
64
+ minutes = int(minutes_str) * sign_factor
65
+ td = timedelta(hours=hours, minutes=minutes)
66
+ return dt + td
67
+
68
+ _offset_pattern = re.compile(r"([+-])(\d\d):(\d\d)")
69
+
70
+ @classmethod
71
+ def _parse_W3CDTF_to_datetime(cls, w3cdtf_str):
72
+ # valid W3CDTF date cases:
73
+ # yyyy e.g. '2003'
74
+ # yyyy-mm e.g. '2003-12'
75
+ # yyyy-mm-dd e.g. '2003-12-31'
76
+ # UTC timezone e.g. '2003-12-31T10:14:55Z'
77
+ # numeric timezone e.g. '2003-12-31T10:14:55-08:00'
78
+ templates = (
79
+ "%Y-%m-%dT%H:%M:%S",
80
+ "%Y-%m-%d",
81
+ "%Y-%m",
82
+ "%Y",
83
+ )
84
+ # strptime isn't smart enough to parse literal timezone offsets like
85
+ # '-07:30', so we have to do it ourselves
86
+ parseable_part = w3cdtf_str[:19]
87
+ offset_str = w3cdtf_str[19:]
88
+ dt = None
89
+ for tmpl in templates:
90
+ try:
91
+ dt = datetime.strptime(parseable_part, tmpl)
92
+ except ValueError:
93
+ continue
94
+ if dt is None:
95
+ tmpl = "could not parse W3CDTF datetime string '%s'"
96
+ raise ValueError(tmpl % w3cdtf_str)
97
+ if len(offset_str) == 6:
98
+ return cls._offset_dt(dt, offset_str)
99
+ return dt
100
+
101
+ def _set_element_datetime(self, prop_name, value):
102
+ """
103
+ Set date/time value of child element having *prop_name* to *value*.
104
+ """
105
+ if not isinstance(value, datetime):
106
+ tmpl = "property requires <type 'datetime.datetime'> object, got %s"
107
+ raise ValueError(tmpl % type(value))
108
+ element = self._get_or_add(prop_name)
109
+ dt_str = value.strftime("%Y-%m-%dT%H:%M:%SZ")
110
+ element.text = dt_str
111
+ if prop_name in ("created", "modified"):
112
+ # These two require an explicit 'xsi:type="dcterms:W3CDTF"'
113
+ # attribute. The first and last line are a hack required to add
114
+ # the xsi namespace to the root element rather than each child
115
+ # element in which it is referenced
116
+ self.set(qn("xsi:foo"), "bar")
117
+ element.set(qn("xsi:type"), "dcterms:W3CDTF")
118
+ del self.attrib[qn("xsi:foo")]
119
+
120
+ def _set_element_text(self, prop_name, value):
121
+ """
122
+ Set string value of *name* property to *value*.
123
+ """
124
+ value = str(value)
125
+ if len(value) > 255:
126
+ tmpl = "exceeded 255 char limit for property, got:\n\n'%s'"
127
+ raise ValueError(tmpl % value)
128
+ element = self._get_or_add(prop_name)
129
+ element.text = value
130
+
131
+ def _text_of_element(self, property_name):
132
+ """
133
+ Return the text in the element matching *property_name*, or an empty
134
+ string if the element is not present or contains no text.
135
+ """
136
+ element = getattr(self, property_name)
137
+ if element is None:
138
+ return ""
139
+ if element.text is None:
140
+ return ""
141
+ return element.text
@@ -0,0 +1,48 @@
1
+ # pyright: reportPrivateUsage=false
2
+
3
+ """This module is for elements which has extLst child."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Any, 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 docx.oxml.shape import CT_Extension
15
+
16
+
17
+ class CT_ExtensionList(BaseOxmlElement):
18
+ """Base class for `extLst` element in DrawingML."""
19
+
20
+ ext: CT_Extension = ZeroOrOne("a:ext")
21
+ _add_ext: Any
22
+
23
+ @classmethod
24
+ def new(cls):
25
+ """Return a new `extLst` element."""
26
+ extLst = cast(
27
+ CT_ExtensionList,
28
+ parse_xml(f"<a:extLst {nsdecls('a')}/>"),
29
+ )
30
+ extLst._add_ext()
31
+ return extLst
32
+
33
+
34
+ class CT_Extension(BaseOxmlElement):
35
+ """
36
+ `<a:ext>` element in `extLst`, which can hold a `svgBlip` element.
37
+ """
38
+
39
+ @property
40
+ def svgBlip(self) -> "CT_Transform2D":
41
+ from docx.oxml.shape import CT_Transform2D
42
+
43
+ return self.xpath("./asvg:svgBlip")[0]
44
+
45
+ @classmethod
46
+ def new(cls):
47
+ """Return a new `ext` element."""
48
+ return cast(CT_Extension, parse_xml(f"<a:ext {nsdecls('a')}/>"))
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."""
@@ -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,111 @@
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
+ "vt": "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
20
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
21
+ "w14": "http://schemas.microsoft.com/office/word/2010/wordml",
22
+ "wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
23
+ "xml": "http://www.w3.org/XML/1998/namespace",
24
+ "xsi": "http://www.w3.org/2001/XMLSchema-instance",
25
+ "asvg": "http://schemas.microsoft.com/office/drawing/2016/SVG/main",
26
+ }
27
+
28
+ pfxmap = {value: key for key, value in nsmap.items()}
29
+
30
+
31
+ class NamespacePrefixedTag(str):
32
+ """Value object that knows the semantics of an XML tag having a namespace prefix."""
33
+
34
+ def __new__(cls, nstag: str):
35
+ return super(NamespacePrefixedTag, cls).__new__(cls, nstag)
36
+
37
+ def __init__(self, nstag: str):
38
+ self._pfx, self._local_part = nstag.split(":")
39
+ self._ns_uri = nsmap[self._pfx]
40
+
41
+ @property
42
+ def clark_name(self) -> str:
43
+ return "{%s}%s" % (self._ns_uri, self._local_part)
44
+
45
+ @classmethod
46
+ def from_clark_name(cls, clark_name: str) -> NamespacePrefixedTag:
47
+ nsuri, local_name = clark_name[1:].split("}")
48
+ nstag = "%s:%s" % (pfxmap[nsuri], local_name)
49
+ return cls(nstag)
50
+
51
+ @property
52
+ def local_part(self) -> str:
53
+ """The local part of this tag.
54
+
55
+ E.g. "foobar" is returned for tag "f:foobar".
56
+ """
57
+ return self._local_part
58
+
59
+ @property
60
+ def nsmap(self) -> Dict[str, str]:
61
+ """Single-member dict mapping prefix of this tag to it's namespace name.
62
+
63
+ Example: `{"f": "http://foo/bar"}`. This is handy for passing to xpath calls
64
+ and other uses.
65
+ """
66
+ return {self._pfx: self._ns_uri}
67
+
68
+ @property
69
+ def nspfx(self) -> str:
70
+ """The namespace-prefix for this tag.
71
+
72
+ For example, "f" is returned for tag "f:foobar".
73
+ """
74
+ return self._pfx
75
+
76
+ @property
77
+ def nsuri(self) -> str:
78
+ """The namespace URI for this tag.
79
+
80
+ For example, "http://foo/bar" would be returned for tag "f:foobar" if the "f"
81
+ prefix maps to "http://foo/bar" in nsmap.
82
+ """
83
+ return self._ns_uri
84
+
85
+
86
+ def nsdecls(*prefixes: str) -> str:
87
+ """Namespace declaration including each namespace-prefix in `prefixes`.
88
+
89
+ Handy for adding required namespace declarations to a tree root element.
90
+ """
91
+ return " ".join(['xmlns:%s="%s"' % (pfx, nsmap[pfx]) for pfx in prefixes])
92
+
93
+
94
+ def nspfxmap(*nspfxs: str) -> Dict[str, str]:
95
+ """Subset namespace-prefix mappings specified by *nspfxs*.
96
+
97
+ Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p").
98
+ """
99
+ return {pfx: nsmap[pfx] for pfx in nspfxs}
100
+
101
+
102
+ def qn(tag: str) -> str:
103
+ """Stands for "qualified name".
104
+
105
+ This utility function converts a familiar namespace-prefixed tag name like "w:p"
106
+ into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns
107
+ "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p".
108
+ """
109
+ prefix, tagroot = tag.split(":")
110
+ uri = nsmap[prefix]
111
+ 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)