🚨 Latest Research:Tanstack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack.Learn More
Socket
Book a DemoSign in
Socket

python-docx

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-docx - pypi Package Compare versions

Comparing version
1.0.1
to
1.1.0
features/blk-iter-inner-content.feature

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

+5
-r requirements-test.txt
build
setuptools>=61.0.0
tox
twine
Sphinx==1.8.6
Jinja2==2.11.3
MarkupSafe==0.23
-e .
-r requirements.txt
behave>=1.2.3
pyparsing>=2.0.1
pytest>=2.5
ruff
lxml>=3.1.0
typing-extensions
"""Unit-test suite for `docx.oxml.document` module."""
from __future__ import annotations
from typing import cast
from docx.oxml.document import CT_Body
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from ..unitutil.cxml import element
class DescribeCT_Body:
"""Unit-test suite for selected units of `docx.oxml.document.CT_Body`."""
def it_knows_its_inner_content_block_item_elements(self):
body = cast(CT_Body, element("w:body/(w:tbl, w:p,w:p)"))
assert [type(e) for e in body.inner_content_elements] == [CT_Tbl, CT_P, CT_P]
"""Unit-test suite for `docx.oxml.section` module."""
from __future__ import annotations
from typing import cast
from docx.oxml.section import CT_HdrFtr
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from ..unitutil.cxml import element
class DescribeCT_HdrFtr:
"""Unit-test suite for selected units of `docx.oxml.section.CT_HdrFtr`."""
def it_knows_its_inner_content_block_item_elements(self):
hdr = cast(CT_HdrFtr, element("w:hdr/(w:tbl,w:tbl,w:p)"))
assert [type(e) for e in hdr.inner_content_elements] == [CT_Tbl, CT_Tbl, CT_P]

Sorry, the diff of this file is not supported yet

+2
-0

@@ -25,3 +25,5 @@

.. autoclass:: _Cell
:inherited-members:
:members:
:exclude-members: part

@@ -28,0 +30,0 @@

"""Step implementations for block content containers."""
from behave import given, then, when
from behave.runner import Context

@@ -13,9 +14,31 @@ from docx import Document

@given("a _Cell object with paragraphs and tables")
def given_a_cell_with_paragraphs_and_tables(context: Context):
context.cell = (
Document(test_docx("blk-paras-and-tables")).tables[1].rows[0].cells[0]
)
@given("a Document object with paragraphs and tables")
def given_a_document_with_paragraphs_and_tables(context: Context):
context.document = Document(test_docx("blk-paras-and-tables"))
@given("a document containing a table")
def given_a_document_containing_a_table(context):
def given_a_document_containing_a_table(context: Context):
context.document = Document(test_docx("blk-containing-table"))
@given("a Footer object with paragraphs and tables")
def given_a_footer_with_paragraphs_and_tables(context: Context):
context.footer = Document(test_docx("blk-paras-and-tables")).sections[0].footer
@given("a Header object with paragraphs and tables")
def given_a_header_with_paragraphs_and_tables(context: Context):
context.header = Document(test_docx("blk-paras-and-tables")).sections[0].header
@given("a paragraph")
def given_a_paragraph(context):
def given_a_paragraph(context: Context):
context.document = Document()

@@ -29,3 +52,3 @@ context.paragraph = context.document.add_paragraph()

@when("I add a paragraph")
def when_add_paragraph(context):
def when_add_paragraph(context: Context):
document = context.document

@@ -36,3 +59,3 @@ context.p = document.add_paragraph()

@when("I add a table")
def when_add_table(context):
def when_add_table(context: Context):
rows, cols = 2, 2

@@ -45,4 +68,32 @@ context.document.add_table(rows, cols)

@then("cell.iter_inner_content() produces the block-items in document order")
def then_cell_iter_inner_content_produces_the_block_items(context: Context):
actual = [type(item).__name__ for item in context.cell.iter_inner_content()]
expected = ["Paragraph", "Table", "Paragraph"]
assert actual == expected, f"expected: {expected}, got: {actual}"
@then("document.iter_inner_content() produces the block-items in document order")
def then_document_iter_inner_content_produces_the_block_items(context: Context):
actual = [type(item).__name__ for item in context.document.iter_inner_content()]
expected = ["Table", "Paragraph", "Table", "Paragraph", "Table", "Paragraph"]
assert actual == expected, f"expected: {expected}, got: {actual}"
@then("footer.iter_inner_content() produces the block-items in document order")
def then_footer_iter_inner_content_produces_the_block_items(context: Context):
actual = [type(item).__name__ for item in context.footer.iter_inner_content()]
expected = ["Paragraph", "Table", "Paragraph"]
assert actual == expected, f"expected: {expected}, got: {actual}"
@then("header.iter_inner_content() produces the block-items in document order")
def then_header_iter_inner_content_produces_the_block_items(context: Context):
actual = [type(item).__name__ for item in context.header.iter_inner_content()]
expected = ["Table", "Paragraph"]
assert actual == expected, f"expected: {expected}, got: {actual}"
@then("I can access the table")
def then_can_access_table(context):
def then_can_access_table(context: Context):
table = context.document.tables[-1]

@@ -53,4 +104,4 @@ assert isinstance(table, Table)

@then("the new table appears in the document")
def then_new_table_appears_in_document(context):
def then_new_table_appears_in_document(context: Context):
table = context.document.tables[-1]
assert isinstance(table, Table)

@@ -6,2 +6,8 @@ .. :changelog:

1.1.0 (2023-11-03)
++++++++++++++++++
- Add BlockItemContainer.iter_inner_content()
1.0.1 (2023-10-12)

@@ -8,0 +14,0 @@ ++++++++++++++++++

include HISTORY.rst LICENSE README.rst tox.ini
include requirements*.txt
graft src/docx/templates

@@ -3,0 +4,0 @@ graft features

+1
-1
Metadata-Version: 2.1
Name: python-docx
Version: 1.0.1
Version: 1.1.0
Summary: Create, read, and update Microsoft Word .docx files.

@@ -5,0 +5,0 @@ Author-email: Steve Canny <stcanny@gmail.com>

[build-system]
requires = ["setuptools>=61.0.0"]
build-backend = "setuptools.build_meta"

@@ -44,2 +45,13 @@ [project]

[tool.pytest.ini_options]
filterwarnings = [
# -- exit on any warning not explicitly ignored here --
"error",
# -- pytest-xdist plugin may warn about `looponfailroots` deprecation --
"ignore::DeprecationWarning:xdist",
# -- pytest complains when pytest-xdist is not installed --
"ignore:Unknown config option. looponfailroots:pytest.PytestConfigWarning",
]
looponfailroots = ["src", "tests"]
norecursedirs = [

@@ -46,0 +58,0 @@ "doc",

@@ -16,3 +16,3 @@ """Initialize `docx` package.

__version__ = "1.0.1"
__version__ = "1.1.0"

@@ -19,0 +19,0 @@

@@ -0,1 +1,3 @@

# pyright: reportImportCycles=false
"""Block item container, used by body, cell, header, etc.

@@ -7,8 +9,26 @@

from __future__ import annotations
from typing import TYPE_CHECKING, Iterator
from typing_extensions import TypeAlias
from docx.oxml.table import CT_Tbl
from docx.shared import Parented
from docx.oxml.text.paragraph import CT_P
from docx.shared import StoryChild
from docx.text.paragraph import Paragraph
if TYPE_CHECKING:
from docx import types as t
from docx.oxml.document import CT_Body
from docx.oxml.section import CT_HdrFtr
from docx.oxml.table import CT_Tc
from docx.shared import Length
from docx.styles.style import ParagraphStyle
from docx.table import Table
class BlockItemContainer(Parented):
BlockItemElement: TypeAlias = "CT_Body | CT_HdrFtr | CT_Tc"
class BlockItemContainer(StoryChild):
"""Base class for proxy objects that can contain block items.

@@ -21,7 +41,9 @@

def __init__(self, element, parent):
def __init__(self, element: BlockItemElement, parent: t.ProvidesStoryPart):
super(BlockItemContainer, self).__init__(parent)
self._element = element
def add_paragraph(self, text="", style=None):
def add_paragraph(
self, text: str = "", style: str | ParagraphStyle | None = None
) -> Paragraph:
"""Return paragraph newly added to the end of the content in this container.

@@ -42,3 +64,3 @@

def add_table(self, rows, cols, width):
def add_table(self, rows: int, cols: int, width: Length) -> Table:
"""Return table of `width` having `rows` rows and `cols` columns.

@@ -53,5 +75,16 @@

tbl = CT_Tbl.new_tbl(rows, cols, width)
self._element._insert_tbl(tbl)
self._element._insert_tbl(tbl) # # pyright: ignore[reportPrivateUsage]
return Table(tbl, self)
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each `Paragraph` or `Table` in this container in document order."""
from docx.table import Table
for element in self._element.inner_content_elements:
yield (
Paragraph(element, self)
if isinstance(element, CT_P)
else Table(element, self)
)
@property

@@ -71,3 +104,3 @@ def paragraphs(self):

"""
from .table import Table
from docx.table import Table

@@ -74,0 +107,0 @@ return [Table(tbl, self) for tbl in self._element.tbl_lst]

@@ -0,3 +1,10 @@

# pyright: reportImportCycles=false
# pyright: reportPrivateUsage=false
"""|Document| and closely related objects."""
from __future__ import annotations
from typing import IO, TYPE_CHECKING, Iterator, List
from docx.blkcntnr import BlockItemContainer

@@ -8,5 +15,14 @@ from docx.enum.section import WD_SECTION

from docx.shared import ElementProxy, Emu
from docx.text.paragraph import Paragraph
if TYPE_CHECKING:
from docx import types as t
from docx.oxml.document import CT_Body, CT_Document
from docx.parts.document import DocumentPart
from docx.settings import Settings
from docx.shared import Length
from docx.styles.style import ParagraphStyle, _TableStyle
from docx.table import Table
from docx.text.paragraph import Paragraph
class Document(ElementProxy):

@@ -19,8 +35,9 @@ """WordprocessingML (WML) document.

def __init__(self, element, part):
def __init__(self, element: CT_Document, part: DocumentPart):
super(Document, self).__init__(element)
self._element = element
self._part = part
self.__body = None
def add_heading(self, text="", level=1):
def add_heading(self, text: str = "", level: int = 1):
"""Return a heading paragraph newly added to the end of the document.

@@ -44,3 +61,5 @@

def add_paragraph(self, text: str = "", style=None) -> Paragraph:
def add_paragraph(
self, text: str = "", style: str | ParagraphStyle | None = None
) -> Paragraph:
"""Return paragraph newly added to the end of the document.

@@ -57,3 +76,8 @@

def add_picture(self, image_path_or_stream, width=None, height=None):
def add_picture(
self,
image_path_or_stream: str | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
):
"""Return new picture shape added in its own paragraph at end of the document.

@@ -72,3 +96,3 @@

def add_section(self, start_type=WD_SECTION.NEW_PAGE):
def add_section(self, start_type: WD_SECTION = WD_SECTION.NEW_PAGE):
"""Return a |Section| object newly added at the end of the document.

@@ -83,3 +107,3 @@

def add_table(self, rows, cols, style=None):
def add_table(self, rows: int, cols: int, style: str | _TableStyle | None = None):
"""Add a table having row and column counts of `rows` and `cols` respectively.

@@ -101,3 +125,3 @@

def inline_shapes(self):
"""The |InlineShapes| collectoin for this document.
"""The |InlineShapes| collection for this document.

@@ -110,4 +134,8 @@ An inline shape is a graphical object, such as a picture, contained in a run of

def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each `Paragraph` or `Table` in this document in document order."""
return self._body.iter_inner_content()
@property
def paragraphs(self):
def paragraphs(self) -> List[Paragraph]:
"""The |Paragraph| instances in the document, in document order.

@@ -121,7 +149,7 @@

@property
def part(self):
def part(self) -> DocumentPart:
"""The |DocumentPart| object of this document."""
return self._part
def save(self, path_or_stream):
def save(self, path_or_stream: str | IO[bytes]):
"""Save this document to `path_or_stream`.

@@ -135,3 +163,3 @@

@property
def sections(self):
def sections(self) -> Sections:
"""|Sections| object providing access to each section in this document."""

@@ -141,3 +169,3 @@ return Sections(self._element, self._part)

@property
def settings(self):
def settings(self) -> Settings:
"""A |Settings| object providing access to the document-level settings."""

@@ -152,3 +180,3 @@ return self._part.settings

@property
def tables(self):
def tables(self) -> List[Table]:
"""All |Table| instances in the document, in document order.

@@ -164,3 +192,3 @@

@property
def _block_width(self):
def _block_width(self) -> Length:
"""A |Length| object specifying the space between margins in last section."""

@@ -171,3 +199,3 @@ section = self.sections[-1]

@property
def _body(self):
def _body(self) -> _Body:
"""The |_Body| instance containing the content for this document."""

@@ -185,3 +213,3 @@ if self.__body is None:

def __init__(self, body_elm, parent):
def __init__(self, body_elm: CT_Body, parent: t.ProvidesStoryPart):
super(_Body, self).__init__(body_elm, parent)

@@ -188,0 +216,0 @@ self._body = body_elm

@@ -13,5 +13,5 @@ """DrawingML-related objects are in this subpackage."""

def __init__(self, drawing: CT_Drawing, parent: t.StoryChild):
def __init__(self, drawing: CT_Drawing, parent: t.ProvidesStoryPart):
super().__init__(parent)
self._parent = parent
self._drawing = self._element = drawing

@@ -70,3 +70,5 @@ """Base classes and other objects used by enumerations."""

"""XML value of this enum member, generally an XML attribute value."""
return cls(value).xml_value
# -- presence of multi-arg `__new__()` method fools type-checker, but getting a
# -- member by its value using EnumCls(val) works as usual.
return cls(value).xml_value # pyright: ignore[reportGeneralTypeIssues]

@@ -73,0 +75,0 @@

@@ -5,3 +5,3 @@ """Custom element classes that correspond to the document part, e.g. <w:document>."""

from typing import List
from typing import TYPE_CHECKING, Callable, List

@@ -11,3 +11,7 @@ from docx.oxml.section import CT_SectPr

if TYPE_CHECKING:
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
class CT_Document(BaseOxmlElement):

@@ -34,10 +38,18 @@ """``<w:document>`` element, the root element of a document.xml file."""

class CT_Body(BaseOxmlElement):
"""``<w:body>``, the container element for the main document story in
``document.xml``."""
"""`w:body`, the container element for the main document story in `document.xml`."""
add_p: Callable[[], CT_P]
get_or_add_sectPr: Callable[[], CT_SectPr]
p_lst: List[CT_P]
tbl_lst: List[CT_Tbl]
_insert_tbl: Callable[[CT_Tbl], CT_Tbl]
p = ZeroOrMore("w:p", successors=("w:sectPr",))
tbl = ZeroOrMore("w:tbl", successors=("w:sectPr",))
sectPr = ZeroOrOne("w:sectPr", successors=())
sectPr: CT_SectPr | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
"w:sectPr", successors=()
)
def add_section_break(self):
def add_section_break(self) -> CT_SectPr:
"""Return `w:sectPr` element for new section added at end of document.

@@ -69,4 +81,12 @@

"""
content_elms = self[:-1] if self.sectPr is not None else self[:]
for content_elm in content_elms:
for content_elm in self.xpath("./*[not(self::w:sectPr)]"):
self.remove(content_elm)
@property
def inner_content_elements(self) -> List[CT_P | CT_Tbl]:
"""Generate all `w:p` and `w:tbl` elements in this document-body.
Elements appear in document order. Elements shaded by nesting in a `w:ins` or
other "wrapper" element will not be included.
"""
return self.xpath("./w:p | ./w:tbl")

@@ -6,3 +6,3 @@ """Section-related custom element classes."""

from copy import deepcopy
from typing import Callable, Iterator, Sequence, Union, cast
from typing import Callable, Iterator, List, Sequence, cast

@@ -27,3 +27,3 @@ from lxml import etree

BlockElement: TypeAlias = Union[CT_P, CT_Tbl]
BlockElement: TypeAlias = "CT_P | CT_Tbl"

@@ -34,6 +34,21 @@

add_p: Callable[[], CT_P]
p_lst: List[CT_P]
tbl_lst: List[CT_Tbl]
_insert_tbl: Callable[[CT_Tbl], CT_Tbl]
p = ZeroOrMore("w:p", successors=())
tbl = ZeroOrMore("w:tbl", successors=())
@property
def inner_content_elements(self) -> List[CT_P | CT_Tbl]:
"""Generate all `w:p` and `w:tbl` elements in this header or footer.
Elements appear in document order. Elements shaded by nesting in a `w:ins` or
other "wrapper" element will not be included.
"""
return self.xpath("./w:p | ./w:tbl")
class CT_HdrFtrRef(BaseOxmlElement):

@@ -493,9 +508,4 @@ """`w:headerReference` and `w:footerReference` elements."""

xpath = self._compiled_blocks_xpath
# -- XPath callable results are Any (basically), so need a cast. Also the
# -- callable wants an etree._Element, which CT_SectPr is, but we haven't
# -- figured out the typing through the metaclass yet.
return cast(
Sequence[BlockElement],
xpath(sectPr), # pyright: ignore[reportGeneralTypeIssues]
)
# -- XPath callable results are Any (basically), so need a cast. --
return cast(Sequence[BlockElement], xpath(sectPr))

@@ -540,5 +550,3 @@ @lazyproperty

# -- numeric XPath results are always float, so need an int() conversion --
return int(
cast(float, xpath(sectPr)) # pyright: ignore[reportGeneralTypeIssues]
)
return int(cast(float, xpath(sectPr)))

@@ -545,0 +553,0 @@ @lazyproperty

@@ -0,1 +1,3 @@

# pyright: reportImportCycles=false
"""Simple-type classes, corresponding to ST_* schema items.

@@ -10,3 +12,3 @@

from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Tuple

@@ -17,3 +19,2 @@ from docx.exceptions import InvalidXmlError

if TYPE_CHECKING:
from docx import types as t
from docx.shared import Length

@@ -26,7 +27,7 @@

@classmethod
def from_xml(cls, xml_value: str):
def from_xml(cls, xml_value: str) -> Any:
return cls.convert_from_xml(xml_value)
@classmethod
def to_xml(cls, value):
def to_xml(cls, value: Any) -> str:
cls.validate(value)

@@ -37,7 +38,15 @@ str_value = cls.convert_to_xml(value)

@classmethod
def convert_from_xml(cls, str_value: str) -> t.AbstractSimpleTypeMember:
def convert_from_xml(cls, str_value: str) -> Any:
return int(str_value)
@classmethod
def validate_int(cls, value):
def convert_to_xml(cls, value: Any) -> str:
...
@classmethod
def validate(cls, value: Any) -> None:
...
@classmethod
def validate_int(cls, value: object):
if not isinstance(value, int):

@@ -47,3 +56,5 @@ raise TypeError("value must be <type 'int'>, got %s" % type(value))

@classmethod
def validate_int_in_range(cls, value, min_inclusive, max_inclusive):
def validate_int_in_range(
cls, value: int, min_inclusive: int, max_inclusive: int
) -> None:
cls.validate_int(value)

@@ -65,11 +76,11 @@ if value < min_inclusive or value > max_inclusive:

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> int:
return int(str_value)
@classmethod
def convert_to_xml(cls, value):
def convert_to_xml(cls, value: int) -> str:
return str(value)
@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int(value)

@@ -93,4 +104,6 @@

class BaseStringEnumerationType(BaseStringType):
_members: Tuple[str, ...]
@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_string(value)

@@ -102,10 +115,12 @@ if value not in cls._members:

class XsdAnyUri(BaseStringType):
"""There's a regular expression this is supposed to meet but so far thinking
spending cycles on validating wouldn't be worth it for the number of programming
errors it would catch."""
"""There's a regex in the spec this is supposed to meet...
but current assessment is that spending cycles on validating wouldn't be worth it
for the number of programming errors it would catch.
"""
class XsdBoolean(BaseSimpleType):
@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> bool:
if str_value not in ("1", "0", "true", "false"):

@@ -118,7 +133,7 @@ raise InvalidXmlError(

@classmethod
def convert_to_xml(cls, value):
def convert_to_xml(cls, value: bool) -> str:
return {True: "1", False: "0"}[value]
@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
if value not in (True, False):

@@ -142,3 +157,3 @@ raise TypeError(

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, -2147483648, 2147483647)

@@ -149,3 +164,3 @@

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, -9223372036854775808, 9223372036854775807)

@@ -171,3 +186,3 @@

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, 0, 4294967295)

@@ -178,3 +193,3 @@

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, 0, 18446744073709551615)

@@ -194,3 +209,3 @@

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_string(value)

@@ -204,3 +219,3 @@ valid_values = ("page", "column", "textWrapping")

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> Length:
if "i" in str_value or "m" in str_value or "p" in str_value:

@@ -211,3 +226,3 @@ return ST_UniversalMeasure.convert_from_xml(str_value)

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
ST_CoordinateUnqualified.validate(value)

@@ -218,3 +233,3 @@

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, -27273042329600, 27273042316900)

@@ -233,3 +248,5 @@

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml( # pyright: ignore[reportIncompatibleMethodOverride]
cls, str_value: str
) -> RGBColor | str:
if str_value == "auto":

@@ -240,3 +257,5 @@ return ST_HexColorAuto.AUTO

@classmethod
def convert_to_xml(cls, value):
def convert_to_xml( # pyright: ignore[reportIncompatibleMethodOverride]
cls, value: RGBColor
) -> str:
"""Keep alpha hex numerals all uppercase just for consistency."""

@@ -247,3 +266,3 @@ # expecting 3-tuple of ints in range 0-255

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
# must be an RGBColor object ---

@@ -292,3 +311,3 @@ if not isinstance(value, RGBColor):

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> bool:
if str_value not in ("1", "0", "true", "false", "on", "off"):

@@ -304,7 +323,7 @@ raise InvalidXmlError(

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> Length:
return Emu(int(str_value))
@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_int_in_range(value, 0, 27273042316900)

@@ -319,3 +338,3 @@

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> Length:
if "i" in str_value or "m" in str_value or "p" in str_value:

@@ -326,3 +345,3 @@ return ST_UniversalMeasure.convert_from_xml(str_value)

@classmethod
def convert_to_xml(cls, value):
def convert_to_xml(cls, value: int | Length) -> str:
emu = Emu(value)

@@ -339,3 +358,3 @@ twips = emu.twips

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_string(value)

@@ -349,3 +368,3 @@ valid_values = ("fixed", "autofit")

@classmethod
def validate(cls, value):
def validate(cls, value: Any) -> None:
cls.validate_string(value)

@@ -359,3 +378,3 @@ valid_values = ("auto", "dxa", "nil", "pct")

@classmethod
def convert_from_xml(cls, str_value):
def convert_from_xml(cls, str_value: str) -> Length:
if "i" in str_value or "m" in str_value or "p" in str_value:

@@ -366,3 +385,3 @@ return ST_UniversalMeasure.convert_from_xml(str_value)

@classmethod
def convert_to_xml(cls, value):
def convert_to_xml(cls, value: int | Length) -> str:
emu = Emu(value)

@@ -369,0 +388,0 @@ twips = emu.twips

"""Custom element classes for tables."""
from __future__ import annotations
from typing import TYPE_CHECKING, Callable, List
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE

@@ -25,3 +29,7 @@ from docx.exceptions import InvalidSpanError

if TYPE_CHECKING:
from docx.oxml.text.paragraph import CT_P
from docx.shared import Length
class CT_Height(BaseOxmlElement):

@@ -144,5 +152,7 @@ """Used for ``<w:trHeight>`` to specify a row height and row height rule."""

@classmethod
def new_tbl(cls, rows, cols, width):
"""Return a new `w:tbl` element having `rows` rows and `cols` columns with
`width` distributed evenly between the columns."""
def new_tbl(cls, rows: int, cols: int, width: Length) -> CT_Tbl:
"""Return a new `w:tbl` element having `rows` rows and `cols` columns.
`width` is distributed evenly between the columns.
"""
return parse_xml(cls._tbl_xml(rows, cols, width))

@@ -172,3 +182,3 @@

@classmethod
def _tbl_xml(cls, rows, cols, width):
def _tbl_xml(cls, rows: int, cols: int, width: Length) -> str:
col_width = Emu(width / cols) if cols > 0 else Emu(0)

@@ -301,3 +311,3 @@ return (

@property
def autofit(self):
def autofit(self) -> bool:
"""|False| when there is a `w:tblLayout` child with `@w:type="fixed"`.

@@ -311,3 +321,3 @@

@autofit.setter
def autofit(self, value):
def autofit(self, value: bool):
tblLayout = self.get_or_add_tblLayout()

@@ -360,2 +370,8 @@ tblLayout.type = "autofit" if value else "fixed"

add_p: Callable[[], CT_P]
p_lst: List[CT_P]
tbl_lst: List[CT_Tbl]
_insert_tbl: Callable[[CT_Tbl], CT_Tbl]
tcPr = ZeroOrOne("w:tcPr") # bunches of successors, overriding insert

@@ -408,2 +424,11 @@ p = OneOrMore("w:p")

@property
def inner_content_elements(self) -> List[CT_P | CT_Tbl]:
"""Generate all `w:p` and `w:tbl` elements in this document-body.
Elements appear in document order. Elements shaded by nesting in a `w:ins` or
other "wrapper" element will not be included.
"""
return self.xpath("./w:p | ./w:tbl")
def iter_block_items(self):

@@ -410,0 +435,0 @@ """Generate a reference to each of the block-level content elements in this

@@ -0,1 +1,3 @@

# pyright: reportImportCycles=false
"""Enabling declarative definition of lxml custom element classes."""

@@ -6,6 +8,16 @@

import re
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Tuple, Type, TypeVar
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Sequence,
Tuple,
Type,
TypeVar,
)
from lxml import etree
from lxml.etree import ElementBase
from lxml.etree import ElementBase, _Element # pyright: ignore[reportPrivateUsage]

@@ -17,3 +29,2 @@ from docx.oxml.exceptions import InvalidXmlError

if TYPE_CHECKING:
from docx import types as t
from docx.enum.base import BaseXmlEnum

@@ -23,3 +34,3 @@ from docx.oxml.simpletypes import BaseSimpleType

def serialize_for_reading(element):
def serialize_for_reading(element: ElementBase):
"""Serialize `element` to human-readable XML suitable for tests.

@@ -45,3 +56,5 @@

def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, str):
return False
lines = self.splitlines()

@@ -56,6 +69,6 @@ lines_other = other.splitlines()

def __ne__(self, other):
def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
def _attr_seq(self, attrs):
def _attr_seq(self, attrs: str) -> List[str]:
"""Return a sequence of attribute strings parsed from `attrs`.

@@ -69,3 +82,3 @@

def _eq_elm_strs(self, line, line_2):
def _eq_elm_strs(self, line: str, line_2: str):
"""Return True if the element in `line_2` is XML equivalent to the element in

@@ -86,6 +99,7 @@ `line`."""

@classmethod
def _parse_line(cls, line):
"""Return front, attrs, close, text 4-tuple result of parsing XML element string
`line`."""
def _parse_line(cls, line: str) -> Tuple[str, str, str, str]:
"""(front, attrs, close, text) 4-tuple result of parsing XML element `line`."""
match = cls._xml_elm_line_patt.match(line)
if match is None:
return "", "", "", ""
front, attrs, close, text = [match.group(n) for n in range(1, 5)]

@@ -101,8 +115,2 @@ return front, attrs, close, text

def __new__(
cls: Type[_T], clsname: str, bases: Tuple[type, ...], namespace: Dict[str, Any]
) -> _T:
bases = (*bases, etree.ElementBase)
return super().__new__(cls, clsname, bases, namespace)
def __init__(cls, clsname: str, bases: Tuple[type, ...], namespace: Dict[str, Any]):

@@ -137,3 +145,3 @@ dispatchable = (

def populate_class_members(
self, element_cls: Type[BaseOxmlElement], prop_name: str
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:

@@ -147,7 +155,9 @@ """Add the appropriate methods to `element_cls`."""

def _add_attr_property(self):
"""Add a read/write ``{prop_name}`` property to the element class that returns
the interpreted value of this attribute on access and changes the attribute
value to its ST_* counterpart on assignment."""
"""Add a read/write `.{prop_name}` property to the element class.
The property returns the interpreted value of this attribute on access and
changes the attribute value to its ST_* counterpart on assignment.
"""
property_ = property(self._getter, self._setter, None)
# assign unconditionally to overwrite element name definition
# -- assign unconditionally to overwrite element name definition --
setattr(self._element_cls, self._prop_name, property_)

@@ -162,3 +172,3 @@

@property
def _getter(self) -> Callable[[BaseOxmlElement], t.AbstractSimpleTypeMember]:
def _getter(self) -> Callable[[BaseOxmlElement], Any | None]:
...

@@ -169,3 +179,3 @@

self,
) -> Callable[[BaseOxmlElement, t.AbstractSimpleTypeMember | None], None]:
) -> Callable[[BaseOxmlElement, Any | None], None]:
...

@@ -204,3 +214,3 @@

self,
) -> Callable[[BaseOxmlElement], str | bool | t.AbstractSimpleTypeMember]:
) -> Callable[[BaseOxmlElement], Any | None]:
"""Function suitable for `__get__()` method on attribute property descriptor."""

@@ -210,3 +220,3 @@

obj: BaseOxmlElement,
) -> str | bool | t.AbstractSimpleTypeMember:
) -> Any | None:
attr_str_value = obj.get(self._clark_name)

@@ -221,8 +231,6 @@ if attr_str_value is None:

@property
def _setter(self) -> Callable[[BaseOxmlElement, t.AbstractSimpleTypeMember], None]:
def _setter(self) -> Callable[[BaseOxmlElement, Any], None]:
"""Function suitable for `__set__()` method on attribute property descriptor."""
def set_attr_value(
obj: BaseOxmlElement, value: t.AbstractSimpleTypeMember | None
):
def set_attr_value(obj: BaseOxmlElement, value: Any | None):
if value is None or value == self._default:

@@ -233,2 +241,6 @@ if self._clark_name in obj.attrib:

str_value = self._simple_type.to_xml(value)
if str_value is None:
if self._clark_name in obj.attrib:
del obj.attrib[self._clark_name]
return
obj.set(self._clark_name, str_value)

@@ -259,7 +271,6 @@

@property
def _getter(self):
"""Return a function object suitable for the "get" side of the attribute
property descriptor."""
def _getter(self) -> Callable[[BaseOxmlElement], Any]:
"""function object suitable for "get" side of attr property descriptor."""
def get_attr_value(obj):
def get_attr_value(obj: BaseOxmlElement) -> Any | None:
attr_str_value = obj.get(self._clark_name)

@@ -277,8 +288,9 @@ if attr_str_value is None:

@property
def _setter(self):
"""Return a function object suitable for the "set" side of the attribute
property descriptor."""
def _setter(self) -> Callable[[BaseOxmlElement, Any], None]:
"""function object suitable for "set" side of attribute property descriptor."""
def set_attr_value(obj, value):
def set_attr_value(obj: BaseOxmlElement, value: Any):
str_value = self._simple_type.to_xml(value)
if str_value is None:
raise ValueError(f"cannot assign {value} to this required attribute")
obj.set(self._clark_name, str_value)

@@ -290,6 +302,9 @@

class _BaseChildElement:
"""Base class for the child element classes corresponding to varying cardinalities,
such as ZeroOrOne and ZeroOrMore."""
"""Base class for the child-element classes.
def __init__(self, nsptagname, successors=()):
The child-element sub-classes correspond to varying cardinalities, such as ZeroOrOne
and ZeroOrMore.
"""
def __init__(self, nsptagname: str, successors: Tuple[str, ...] = ()):
super(_BaseChildElement, self).__init__()

@@ -299,3 +314,5 @@ self._nsptagname = nsptagname

def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Baseline behavior for adding the appropriate methods to `element_cls`."""

@@ -308,3 +325,3 @@ self._element_cls = element_cls

def _add_child(obj, **attrs):
def _add_child(obj: BaseOxmlElement, **attrs: Any):
new_method = getattr(obj, self._new_method_name)

@@ -338,3 +355,3 @@ child = new_method()

property_ = property(self._getter, None, None)
# assign unconditionally to overwrite element name definition
# -- assign unconditionally to overwrite element name definition --
setattr(self._element_cls, self._prop_name, property_)

@@ -345,3 +362,3 @@

def _insert_child(obj, child):
def _insert_child(obj: BaseOxmlElement, child: BaseOxmlElement):
obj.insert_element_before(child, *self._successors)

@@ -370,3 +387,3 @@ return child

def add_child(obj):
def add_child(obj: BaseOxmlElement):
private_add_method = getattr(obj, self._add_method_name)

@@ -382,3 +399,3 @@ child = private_add_method()

def _add_to_class(self, name, method):
def _add_to_class(self, name: str, method: Callable[..., Any]):
"""Add `method` to the target class as `name`, unless `name` is already defined

@@ -391,7 +408,7 @@ on the class."""

@property
def _creator(self):
def _creator(self) -> Callable[[BaseOxmlElement], BaseOxmlElement]:
"""Callable that creates an empty element of the right type, with no attrs."""
from docx.oxml.parser import OxmlElement
def new_child_element(obj):
def new_child_element(obj: BaseOxmlElement):
return OxmlElement(self._nsptagname)

@@ -410,3 +427,3 @@

def get_child_element(obj):
def get_child_element(obj: BaseOxmlElement):
return obj.find(qn(self._nsptagname))

@@ -428,3 +445,3 @@

def get_child_element_list(obj):
def get_child_element_list(obj: BaseOxmlElement):
return obj.findall(qn(self._nsptagname))

@@ -465,3 +482,8 @@

def populate_class_members(self, element_cls, group_prop_name, successors):
def populate_class_members( # pyright: ignore[reportIncompatibleMethodOverride]
self,
element_cls: MetaOxmlElement,
group_prop_name: str,
successors: Tuple[str, ...],
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -482,3 +504,3 @@ self._element_cls = element_cls

def get_or_change_to_child(obj):
def get_or_change_to_child(obj: BaseOxmlElement):
child = getattr(obj, self._prop_name)

@@ -516,6 +538,8 @@ if child is not None:

def __init__(self, nsptagname):
super(OneAndOnlyOne, self).__init__(nsptagname, None)
def __init__(self, nsptagname: str):
super(OneAndOnlyOne, self).__init__(nsptagname, ())
def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -530,3 +554,3 @@ super(OneAndOnlyOne, self).populate_class_members(element_cls, prop_name)

def get_child_element(obj):
def get_child_element(obj: BaseOxmlElement):
child = obj.find(qn(self._nsptagname))

@@ -549,3 +573,5 @@ if child is None:

def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -564,3 +590,5 @@ super(OneOrMore, self).populate_class_members(element_cls, prop_name)

def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -579,3 +607,5 @@ super(ZeroOrMore, self).populate_class_members(element_cls, prop_name)

def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -594,3 +624,3 @@ super(ZeroOrOne, self).populate_class_members(element_cls, prop_name)

def get_or_add_child(obj):
def get_or_add_child(obj: BaseOxmlElement):
child = getattr(obj, self._prop_name)

@@ -610,3 +640,3 @@ if child is None:

def _remove_child(obj):
def _remove_child(obj: BaseOxmlElement):
obj.remove_all(self._nsptagname)

@@ -628,7 +658,9 @@

def __init__(self, choices, successors=()):
def __init__(self, choices: Sequence[Choice], successors: Tuple[str, ...] = ()):
self._choices = choices
self._successors = successors
def populate_class_members(self, element_cls, prop_name):
def populate_class_members(
self, element_cls: MetaOxmlElement, prop_name: str
) -> None:
"""Add the appropriate methods to `element_cls`."""

@@ -654,3 +686,3 @@ super(ZeroOrOneChoice, self).populate_class_members(element_cls, prop_name)

def _remove_choice_group(obj):
def _remove_choice_group(obj: BaseOxmlElement):
for tagname in self._member_nsptagnames:

@@ -669,3 +701,3 @@ obj.remove_all(tagname)

def get_group_member_element(obj):
def get_group_member_element(obj: BaseOxmlElement):
return obj.first_child_found_in(*self._member_nsptagnames)

@@ -690,3 +722,6 @@

class BaseOxmlElement(metaclass=MetaOxmlElement):
# -- lxml typing isn't quite right here, just ignore this error on _Element --
class BaseOxmlElement( # pyright: ignore[reportGeneralTypeIssues]
etree.ElementBase, metaclass=MetaOxmlElement
):
"""Effective base class for all custom element classes.

@@ -697,15 +732,2 @@

addprevious: Callable[[BaseOxmlElement], None]
attrib: Dict[str, str]
append: Callable[[BaseOxmlElement], None]
find: Callable[[str], ElementBase | None]
findall: Callable[[str], List[ElementBase]]
get: Callable[[str], str | None]
getparent: Callable[[], BaseOxmlElement]
insert: Callable[[int, BaseOxmlElement], None]
remove: Callable[[BaseOxmlElement], None]
set: Callable[[str, str], None]
tag: str
text: str | None
def __repr__(self):

@@ -718,3 +740,3 @@ return "<%s '<%s>' at 0x%0x>" % (

def first_child_found_in(self, *tagnames: str) -> ElementBase | None:
def first_child_found_in(self, *tagnames: str) -> _Element | None:
"""First child with tag in `tagnames`, or None if not found."""

@@ -750,3 +772,5 @@ for tagname in tagnames:

def xpath(self, xpath_str: str) -> Any:
def xpath( # pyright: ignore[reportIncompatibleMethodOverride]
self, xpath_str: str
) -> Any:
"""Override of `lxml` _Element.xpath() method.

@@ -753,0 +777,0 @@

@@ -6,5 +6,17 @@ """Objects shared by docx modules."""

import functools
from typing import TYPE_CHECKING, Any, Callable, Generic, Iterator, List, TypeVar, cast
from typing import (
TYPE_CHECKING,
Any,
Callable,
Generic,
Iterator,
List,
Tuple,
TypeVar,
cast,
)
if TYPE_CHECKING:
from docx import types as t
from docx.opc.part import XmlPart
from docx.oxml.xmlchemy import BaseOxmlElement

@@ -28,3 +40,3 @@ from docx.parts.story import StoryPart

def __new__(cls, emu):
def __new__(cls, emu: int):
return int.__new__(cls, emu)

@@ -66,3 +78,3 @@

def __new__(cls, inches):
def __new__(cls, inches: float):
emu = int(inches * Length._EMUS_PER_INCH)

@@ -75,3 +87,3 @@ return Length.__new__(cls, emu)

def __new__(cls, cm):
def __new__(cls, cm: float):
emu = int(cm * Length._EMUS_PER_CM)

@@ -85,3 +97,3 @@ return Length.__new__(cls, emu)

def __new__(cls, emu):
def __new__(cls, emu: int):
return Length.__new__(cls, int(emu))

@@ -93,3 +105,3 @@

def __new__(cls, mm):
def __new__(cls, mm: float):
emu = int(mm * Length._EMUS_PER_MM)

@@ -102,3 +114,3 @@ return Length.__new__(cls, emu)

def __new__(cls, points):
def __new__(cls, points: float):
emu = int(points * Length._EMUS_PER_PT)

@@ -114,3 +126,3 @@ return Length.__new__(cls, emu)

def __new__(cls, twips):
def __new__(cls, twips: float):
emu = int(twips * Length._EMUS_PER_TWIP)

@@ -120,9 +132,13 @@ return Length.__new__(cls, emu)

class RGBColor(tuple):
class RGBColor(Tuple[int, int, int]):
"""Immutable value object defining a particular RGB color."""
def __new__(cls, r, g, b):
def __new__(cls, r: int, g: int, b: int):
msg = "RGBColor() takes three integer values 0-255"
for val in (r, g, b):
if not isinstance(val, int) or val < 0 or val > 255:
if (
not isinstance(val, int) # pyright: ignore[reportUnnecessaryIsInstance]
or val < 0
or val > 255
):
raise ValueError(msg)

@@ -139,3 +155,3 @@ return super(RGBColor, cls).__new__(cls, (r, g, b))

@classmethod
def from_string(cls, rgb_hex_str):
def from_string(cls, rgb_hex_str: str) -> RGBColor:
"""Return a new instance from an RGB color hex string like ``'3C2F80'``."""

@@ -262,3 +278,3 @@ r = int(rgb_hex_str[:2], 16)

def write_only_property(f):
def write_only_property(f: Callable[[Any, Any], None]):
"""@write_only_property decorator.

@@ -282,7 +298,9 @@

def __init__(self, element: BaseOxmlElement, parent: Any | None = None):
def __init__(
self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None
):
self._element = element
self._parent = parent
def __eq__(self, other):
def __eq__(self, other: object):
"""Return |True| if this proxy object refers to the same oxml element as does

@@ -299,3 +317,3 @@ `other`.

def __ne__(self, other):
def __ne__(self, other: object):
if not isinstance(other, ElementProxy):

@@ -311,4 +329,6 @@ return True

@property
def part(self):
def part(self) -> XmlPart:
"""The package part containing this object."""
if self._parent is None:
raise ValueError("part is not accessible from this element")
return self._parent.part

@@ -325,3 +345,3 @@

def __init__(self, parent):
def __init__(self, parent: t.ProvidesXmlPart):
self._parent = parent

@@ -346,3 +366,3 @@

def __init__(self, parent: StoryChild):
def __init__(self, parent: t.ProvidesStoryPart):
self._parent = parent

@@ -349,0 +369,0 @@

@@ -5,3 +5,3 @@ """The |Table| object and related proxy classes."""

from typing import List, Tuple, overload
from typing import TYPE_CHECKING, List, Tuple, overload

@@ -13,11 +13,18 @@ from docx.blkcntnr import BlockItemContainer

if TYPE_CHECKING:
from docx import types as t
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_TABLE_DIRECTION
from docx.oxml.table import CT_Tbl, CT_TblPr
from docx.shared import Length
from docx.styles.style import _TableStyle # pyright: ignore[reportPrivateUsage]
class Table(Parented):
"""Proxy class for a WordprocessingML ``<w:tbl>`` element."""
def __init__(self, tbl, parent):
def __init__(self, tbl: CT_Tbl, parent: t.StoryChild):
super(Table, self).__init__(parent)
self._element = self._tbl = tbl
def add_column(self, width):
def add_column(self, width: Length):
"""Return a |_Column| object of `width`, newly added rightmost to the table."""

@@ -42,3 +49,3 @@ tblGrid = self._tbl.tblGrid

@property
def alignment(self):
def alignment(self) -> WD_TABLE_ALIGNMENT | None:
"""Read/write.

@@ -53,7 +60,7 @@

@alignment.setter
def alignment(self, value):
def alignment(self, value: WD_TABLE_ALIGNMENT | None):
self._tblPr.alignment = value
@property
def autofit(self):
def autofit(self) -> bool:
"""|True| if column widths can be automatically adjusted to improve the fit of

@@ -68,12 +75,14 @@ cell contents.

@autofit.setter
def autofit(self, value):
def autofit(self, value: bool):
self._tblPr.autofit = value
def cell(self, row_idx, col_idx):
"""Return |_Cell| instance correponding to table cell at `row_idx`, `col_idx`
intersection, where (0, 0) is the top, left-most cell."""
def cell(self, row_idx: int, col_idx: int) -> _Cell:
"""|_Cell| at `row_idx`, `col_idx` intersection.
(0, 0) is the top, left-most cell.
"""
cell_idx = col_idx + (row_idx * self._column_count)
return self._cells[cell_idx]
def column_cells(self, column_idx):
def column_cells(self, column_idx: int) -> List[_Cell]:
"""Sequence of cells in the column at `column_idx` in this table."""

@@ -89,3 +98,3 @@ cells = self._cells

def row_cells(self, row_idx):
def row_cells(self, row_idx: int) -> List[_Cell]:
"""Sequence of cells in the row at `row_idx` in this table."""

@@ -103,12 +112,13 @@ column_count = self._column_count

@property
def style(self):
"""Read/write. A |_TableStyle| object representing the style applied to this
table. The default table style for the document (often `Normal Table`) is
def style(self) -> _TableStyle | None:
"""|_TableStyle| object representing the style applied to this table.
Read/write. The default table style for the document (often `Normal Table`) is
returned if the table has no directly-applied style. Assigning |None| to this
property removes any directly-applied table style causing it to inherit the
default table style of the document. Note that the style name of a table style
differs slightly from that.
default table style of the document.
displayed in the user interface; a hyphen, if it appears, must be removed. For
example, `Light Shading - Accent 1` becomes `Light Shading Accent 1`.
Note that the style name of a table style differs slightly from that displayed
in the user interface; a hyphen, if it appears, must be removed. For example,
`Light Shading - Accent 1` becomes `Light Shading Accent 1`.
"""

@@ -119,3 +129,3 @@ style_id = self._tbl.tblStyle_val

@style.setter
def style(self, style_or_name):
def style(self, style_or_name: _TableStyle | None):
style_id = self.part.get_style_id(style_or_name, WD_STYLE_TYPE.TABLE)

@@ -135,7 +145,7 @@ self._tbl.tblStyle_val = style_id

@property
def table_direction(self):
"""A member of :ref:`WdTableDirection` indicating the direction in which the
table cells are ordered, e.g. `WD_TABLE_DIRECTION.LTR`.
def table_direction(self) -> WD_TABLE_DIRECTION | None:
"""Member of :ref:`WdTableDirection` indicating cell-ordering direction.
|None| indicates the value is inherited from the style hierarchy.
For example: `WD_TABLE_DIRECTION.LTR`. |None| indicates the value is inherited
from the style hierarchy.
"""

@@ -145,7 +155,7 @@ return self._element.bidiVisual_val

@table_direction.setter
def table_direction(self, value):
def table_direction(self, value: WD_TABLE_DIRECTION | None):
self._element.bidiVisual_val = value
@property
def _cells(self):
def _cells(self) -> List[_Cell]:
"""A sequence of |_Cell| objects, one for each cell of the layout grid.

@@ -174,3 +184,3 @@

@property
def _tblPr(self):
def _tblPr(self) -> CT_TblPr:
return self._tbl.tblPr

@@ -177,0 +187,0 @@

@@ -26,3 +26,3 @@ """Hyperlink-related proxy objects for python-docx, Hyperlink in particular.

def __init__(self, hyperlink: CT_Hyperlink, parent: t.StoryChild):
def __init__(self, hyperlink: CT_Hyperlink, parent: t.ProvidesStoryPart):
super().__init__(parent)

@@ -29,0 +29,0 @@ self._parent = parent

@@ -39,3 +39,5 @@ """Proxy objects related to rendered page-breaks."""

def __init__(
self, lastRenderedPageBreak: CT_LastRenderedPageBreak, parent: t.StoryChild
self,
lastRenderedPageBreak: CT_LastRenderedPageBreak,
parent: t.ProvidesStoryPart,
):

@@ -42,0 +44,0 @@ super().__init__(parent)

@@ -5,12 +5,11 @@ """Paragraph-related proxy types."""

from typing import Iterator, List, cast
from typing import TYPE_CHECKING, Iterator, List, cast
from typing_extensions import Self
from docx import types as t
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.oxml.text.paragraph import CT_P
from docx.oxml.text.run import CT_R
from docx.shared import StoryChild
from docx.styles.style import CharacterStyle, ParagraphStyle
from docx.styles.style import ParagraphStyle
from docx.text.hyperlink import Hyperlink

@@ -21,7 +20,12 @@ from docx.text.pagebreak import RenderedPageBreak

if TYPE_CHECKING:
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.oxml.text.paragraph import CT_P
from docx.styles.style import CharacterStyle
class Paragraph(StoryChild):
"""Proxy object wrapping a `<w:p>` element."""
def __init__(self, p: CT_P, parent: StoryChild):
def __init__(self, p: CT_P, parent: t.ProvidesStoryPart):
super(Paragraph, self).__init__(parent)

@@ -28,0 +32,0 @@ self._p = self._element = p

@@ -7,2 +7,3 @@ """Run-related proxy objects for python-docx, Run in particular."""

from docx import types as t
from docx.drawing import Drawing

@@ -34,3 +35,3 @@ from docx.enum.style import WD_STYLE_TYPE

def __init__(self, r: CT_R, parent: StoryChild):
def __init__(self, r: CT_R, parent: t.ProvidesStoryPart):
super().__init__(parent)

@@ -37,0 +38,0 @@ self._r = self._element = self.element = r

@@ -5,9 +5,13 @@ """Abstract types used by `python-docx`."""

from typing import TYPE_CHECKING
from typing_extensions import Protocol
from docx.parts.story import StoryPart
if TYPE_CHECKING:
from docx.opc.part import XmlPart
from docx.parts.story import StoryPart
class StoryChild(Protocol):
"""An object that can fulfill the `parent` role in a `Parented` class.
class ProvidesStoryPart(Protocol):
"""An object that provides access to the StoryPart.

@@ -21,1 +25,14 @@ This type is for objects that have a story part like document or header as their

...
class ProvidesXmlPart(Protocol):
"""An object that provides access to its XmlPart.
This type is for objects that need access to their part but it either isn't a
StoryPart or they don't care, possibly because they just need access to the package
or related parts.
"""
@property
def part(self) -> XmlPart:
...
Metadata-Version: 2.1
Name: python-docx
Version: 1.0.1
Version: 1.1.0
Summary: Create, read, and update Microsoft Word .docx files.

@@ -5,0 +5,0 @@ Author-email: Steve Canny <stcanny@gmail.com>

@@ -6,2 +6,6 @@ HISTORY.rst

pyproject.toml
requirements-dev.txt
requirements-docs.txt
requirements-test.txt
requirements.txt
tox.ini

@@ -96,2 +100,3 @@ docs/Makefile

features/blk-add-table.feature
features/blk-iter-inner-content.feature
features/cel-add-table.feature

@@ -198,2 +203,3 @@ features/cel-text.feature

features/steps/test_files/blk-containing-table.docx
features/steps/test_files/blk-paras-and-tables.docx
features/steps/test_files/court-exif.jpg

@@ -407,3 +413,5 @@ features/steps/test_files/doc-access-sections.docx

tests/oxml/test__init__.py
tests/oxml/test_document.py
tests/oxml/test_ns.py
tests/oxml/test_section.py
tests/oxml/test_styles.py

@@ -445,2 +453,3 @@ tests/oxml/test_table.py

tests/test_files/CVS_LOGO.WMF
tests/test_files/blk-inner-content.docx
tests/test_files/exif-420-dpi.jpg

@@ -447,0 +456,0 @@ tests/test_files/having-images.docx

"""pytest fixtures that are shared across test modules."""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from docx import types as t
from docx.parts.story import StoryPart
if TYPE_CHECKING:
from docx import types as t
from docx.parts.story import StoryPart
@pytest.fixture
def fake_parent() -> t.StoryChild:
class StoryChild:
def fake_parent() -> t.ProvidesStoryPart:
class ProvidesStoryPart:
@property

@@ -16,2 +21,2 @@ def part(self) -> StoryPart:

return StoryChild()
return ProvidesStoryPart()
"""Test suite for the docx.oxml.text module."""
from __future__ import annotations
from typing import cast
import pytest

@@ -7,3 +11,4 @@

from docx.oxml.parser import parse_xml
from docx.oxml.table import CT_Row, CT_Tc
from docx.oxml.table import CT_Row, CT_Tbl, CT_Tc
from docx.oxml.text.paragraph import CT_P

@@ -104,2 +109,6 @@ from ..unitutil.cxml import element, xml

def it_knows_its_inner_content_block_item_elements(self):
tc = cast(CT_Tc, element("w:tc/(w:p,w:tbl,w:p)"))
assert [type(e) for e in tc.inner_content_elements] == [CT_P, CT_Tbl, CT_P]
def it_can_swallow_the_next_tc_help_merge(self, swallow_fixture):

@@ -106,0 +115,0 @@ tc, grid_width, top_tc, tr, expected_xml = swallow_fixture

@@ -5,2 +5,3 @@ """Test suite for the docx.blkcntnr (block item container) module."""

from docx import Document
from docx.blkcntnr import BlockItemContainer

@@ -12,3 +13,3 @@ from docx.shared import Inches

from .unitutil.cxml import element, xml
from .unitutil.file import snippet_seq
from .unitutil.file import snippet_seq, test_file
from .unitutil.mock import call, instance_mock, method_mock

@@ -18,2 +19,4 @@

class DescribeBlockItemContainer:
"""Unit-test suite for `docx.blkcntnr.BlockItemContainer`."""
def it_can_add_a_paragraph(self, add_paragraph_fixture, _add_paragraph_):

@@ -38,2 +41,22 @@ text, style, paragraph_, add_run_calls = add_paragraph_fixture

def it_can_iterate_its_inner_content(self):
document = Document(test_file("blk-inner-content.docx"))
inner_content = document.iter_inner_content()
para = next(inner_content)
assert isinstance(para, Paragraph)
assert para.text == "P1"
# --
t = next(inner_content)
assert isinstance(t, Table)
assert t.rows[0].cells[0].text == "T2"
# --
para = next(inner_content)
assert isinstance(para, Paragraph)
assert para.text == "P3"
# --
with pytest.raises(StopIteration):
next(inner_content)
def it_provides_access_to_the_paragraphs_it_contains(self, paragraphs_fixture):

@@ -40,0 +63,0 @@ # test len(), iterable, and indexed access

@@ -0,3 +1,10 @@

# pyright: reportPrivateUsage=false
# pyright: reportUnknownMemberType=false
"""Unit test suite for the docx.document module."""
from __future__ import annotations
from typing import cast
import pytest

@@ -9,2 +16,3 @@

from docx.opc.coreprops import CoreProperties
from docx.oxml.document import CT_Document
from docx.parts.document import DocumentPart

@@ -21,6 +29,8 @@ from docx.section import Section, Sections

from .unitutil.cxml import element, xml
from .unitutil.mock import class_mock, instance_mock, method_mock, property_mock
from .unitutil.mock import Mock, class_mock, instance_mock, method_mock, property_mock
class DescribeDocument:
"""Unit-test suite for `docx.Document`."""
def it_can_add_a_heading(self, add_heading_fixture, add_paragraph_, paragraph_):

@@ -102,2 +112,12 @@ level, style = add_heading_fixture

def it_can_iterate_the_inner_content_of_the_document(
self, body_prop_: Mock, body_: Mock, document_part_: Mock
):
document_elm = cast(CT_Document, element("w:document"))
body_prop_.return_value = body_
body_.iter_inner_content.return_value = iter((1, 2, 3))
document = Document(document_elm, document_part_)
assert list(document.iter_inner_content()) == [1, 2, 3]
def it_provides_access_to_its_paragraphs(self, paragraphs_fixture):

@@ -104,0 +124,0 @@ document, paragraphs_ = paragraphs_fixture

@@ -29,3 +29,3 @@ """Test suite for the docx.text.hyperlink module."""

def it_knows_the_hyperlink_address(
self, hlink_cxml: str, expected_value: str, fake_parent: t.StoryChild
self, hlink_cxml: str, expected_value: str, fake_parent: t.ProvidesStoryPart
):

@@ -48,3 +48,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

def it_knows_whether_it_contains_a_page_break(
self, hlink_cxml: str, expected_value: bool, fake_parent: t.StoryChild
self, hlink_cxml: str, expected_value: bool, fake_parent: t.ProvidesStoryPart
):

@@ -64,3 +64,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

def it_knows_the_link_fragment_when_there_is_one(
self, hlink_cxml: str, expected_value: str, fake_parent: t.StoryChild
self, hlink_cxml: str, expected_value: str, fake_parent: t.ProvidesStoryPart
):

@@ -84,3 +84,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

def it_provides_access_to_the_runs_it_contains(
self, hlink_cxml: str, count: int, fake_parent: t.StoryChild
self, hlink_cxml: str, count: int, fake_parent: t.ProvidesStoryPart
):

@@ -107,3 +107,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

def it_knows_the_visible_text_of_the_link(
self, hlink_cxml: str, expected_text: str, fake_parent: t.StoryChild
self, hlink_cxml: str, expected_text: str, fake_parent: t.ProvidesStoryPart
):

@@ -130,3 +130,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

def it_knows_the_full_url_for_web_addresses(
self, hlink_cxml: str, expected_value: str, fake_parent: t.StoryChild
self, hlink_cxml: str, expected_value: str, fake_parent: t.ProvidesStoryPart
):

@@ -141,3 +141,3 @@ hlink = cast(CT_Hyperlink, element(hlink_cxml))

@pytest.fixture
def fake_parent(self, story_part: Mock, rel: Mock) -> t.StoryChild:
def fake_parent(self, story_part: Mock, rel: Mock) -> t.ProvidesStoryPart:
class StoryChild:

@@ -144,0 +144,0 @@ @property

@@ -20,3 +20,3 @@ # pyright: reportPrivateUsage=false

def it_raises_on_preceding_fragment_when_page_break_is_not_first_in_paragrah(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -32,3 +32,3 @@ p_cxml = 'w:p/(w:r/(w:t"abc",w:lastRenderedPageBreak,w:lastRenderedPageBreak))'

def it_produces_None_for_preceding_fragment_when_page_break_is_leading(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -46,3 +46,3 @@ """A page-break with no preceding content is "leading"."""

def it_can_split_off_the_preceding_paragraph_content_when_in_a_run(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -67,3 +67,3 @@ p_cxml = (

def and_it_can_split_off_the_preceding_paragraph_content_when_in_a_hyperlink(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -88,3 +88,3 @@ p_cxml = (

def it_raises_on_following_fragment_when_page_break_is_not_first_in_paragrah(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -100,3 +100,3 @@ p_cxml = 'w:p/(w:r/(w:lastRenderedPageBreak,w:lastRenderedPageBreak,w:t"abc"))'

def it_produces_None_for_following_fragment_when_page_break_is_trailing(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -114,3 +114,3 @@ """A page-break with no following content is "trailing"."""

def it_can_split_off_the_following_paragraph_content_when_in_a_run(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -135,3 +135,3 @@ p_cxml = (

def and_it_can_split_off_the_following_paragraph_content_when_in_a_hyperlink(
self, fake_parent: t.StoryChild
self, fake_parent: t.ProvidesStoryPart
):

@@ -138,0 +138,0 @@ p_cxml = (

@@ -34,3 +34,3 @@ """Unit test suite for the docx.text.paragraph module."""

def it_knows_whether_it_contains_a_page_break(
self, p_cxml: str, expected_value: bool, fake_parent: t.StoryChild
self, p_cxml: str, expected_value: bool, fake_parent: t.ProvidesStoryPart
):

@@ -54,3 +54,3 @@ p = cast(CT_P, element(p_cxml))

def it_provides_access_to_the_hyperlinks_it_contains(
self, p_cxml: str, count: int, fake_parent: t.StoryChild
self, p_cxml: str, count: int, fake_parent: t.ProvidesStoryPart
):

@@ -77,3 +77,3 @@ p = cast(CT_P, element(p_cxml))

def it_can_iterate_its_inner_content_items(
self, p_cxml: str, expected: List[str], fake_parent: t.StoryChild
self, p_cxml: str, expected: List[str], fake_parent: t.ProvidesStoryPart
):

@@ -126,3 +126,3 @@ p = cast(CT_P, element(p_cxml))

def it_provides_access_to_the_rendered_page_breaks_it_contains(
self, p_cxml: str, count: int, fake_parent: t.StoryChild
self, p_cxml: str, count: int, fake_parent: t.ProvidesStoryPart
):

@@ -129,0 +129,0 @@ p = cast(CT_P, element(p_cxml))

@@ -73,3 +73,3 @@ # pyright: reportPrivateUsage=false

def it_can_iterate_its_inner_content_items(
self, r_cxml: str, expected: List[str], fake_parent: t.StoryChild
self, r_cxml: str, expected: List[str], fake_parent: t.ProvidesStoryPart
):

@@ -76,0 +76,0 @@ r = cast(CT_R, element(r_cxml))

Sorry, the diff of this file is not supported yet