python-docx
Advanced tools
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| -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
@@ -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
-0
@@ -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 @@ ++++++++++++++++++ |
+1
-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> |
+12
-0
| [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 @@ |
+40
-7
@@ -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] |
+45
-17
@@ -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") |
+20
-12
@@ -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 |
+106
-82
@@ -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 @@ |
+39
-19
@@ -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 @@ |
+37
-27
@@ -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 |
+20
-3
@@ -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 |
+10
-5
| """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
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
8726560
0.49%496
1.85%28308
0.96%