Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
629f942
rfctr: Blacken code base
scanny Sep 24, 2023
80740f2
build: move docx package under src/ directory
scanny Sep 25, 2023
6ac2c69
build: modernize packaging
scanny Sep 25, 2023
163cfc1
lint: add isort linting in ruff
scanny Sep 25, 2023
d509784
lint: fix C4xx comprehension errors
scanny Sep 26, 2023
878f758
lint: fix flake8-pytest-style diagnostics
scanny Sep 26, 2023
c8aad1c
lint: fix flake8-simplify diagnostics
scanny Sep 26, 2023
5cb952f
rfctr: remove Python 2 compatibility layer
scanny Sep 26, 2023
cbf940c
rfctr: bulk Python 2 formatting updates
scanny Sep 26, 2023
e88e1d5
rfctr: change param ref from asterisk to backticks
scanny Sep 28, 2023
1c5bb28
rfctr: bulk best-efforts PEP 257 docstrings
scanny Sep 28, 2023
5cd150e
rfctr: bulk test-layout updates
scanny Sep 27, 2023
ae6592b
rfctr: bulk remove __slots__
scanny Sep 30, 2023
99e9a0e
rfctr: modernize lazyproperty
scanny Sep 30, 2023
08ab7e6
rfctr: improve typing
scanny Sep 26, 2023
8280771
rfctr: add type-stubs for behave
scanny Sep 30, 2023
160e709
rfctr: rename private classes now used for typing
scanny Sep 30, 2023
5c1b2f0
rfctr: extract oxml.parser
scanny Sep 27, 2023
f07823c
spike: rejigger xmlchemy
scanny Sep 27, 2023
4935c30
docs: document hyperlink analysis
Mar 14, 2016
fd54be1
acpt: add Run inner-content scenarios
scanny Sep 30, 2023
45bf74b
rfctr: add types to Run and its tests
scanny Sep 30, 2023
ceb8cbe
rfctr: add types to Paragraph and its tests
scanny Oct 1, 2023
2e13d5c
run: Run.text includes no-break hyphen, etc.
scanny Sep 30, 2023
2364e90
run: add Run.contains_page_break
scanny Sep 30, 2023
08ee10a
run: add Run.iter_inner_content()
scanny Oct 1, 2023
1afedd0
acpt: add Paragraph inner-content scenarios
scanny Oct 1, 2023
b85b24f
para: add Paragraph.contains_page_break
scanny Oct 1, 2023
e654522
para: add Paragraph.hyperlinks
scanny Oct 1, 2023
57d93e1
para: add Paragraph.iter_inner_content()
scanny Oct 1, 2023
9dd2851
para: add Paragraph.rendered_page_breaks
scanny Oct 1, 2023
7868f3e
acpt: add Hyperlink properties scenarios
scanny Oct 1, 2023
16e3f10
hlink: add Hyperlink.address
scanny Oct 1, 2023
d0499b9
hlink: add Hyperlink.contains_page_break
scanny Oct 1, 2023
01061a8
hlink: add Hyperlink.runs
scanny Oct 1, 2023
a02c220
hlink: add Hyperlink.text
scanny Oct 1, 2023
9abd14a
para: Paragraph.text includes hyperlink text
scanny Oct 1, 2023
557fdee
acpt: add RenderedPageBreak split-para scenarios
scanny Oct 1, 2023
ac6c216
lrpb: add RenderedPageBreak.preceding_pa..fragment
scanny Oct 1, 2023
1e42c55
lrpb: add RenderedPageBreak.following_pa..fragment
scanny Oct 1, 2023
e47dfa2
docs: update docs with recent additions
scanny Oct 1, 2023
5b3ee80
release: prepare v1.0.0rc1 release
scanny Oct 2, 2023
8122909
rfctr: rework enums so they type-check
scanny Oct 7, 2023
58c2453
rfctr: Section type-checks clean
scanny Oct 8, 2023
b61b63b
rfctr: Paragraph type-checks clean
scanny Oct 8, 2023
ac26854
rfctr: Run type-checks clean
scanny Oct 9, 2023
44f9ede
rfctr: Font type-checks clean
scanny Oct 9, 2023
12a6bb8
rfctr: remove obsolete inherit-from-`object`
scanny Oct 9, 2023
9c9106f
section: Section.iter_inner_content()
scanny Oct 10, 2023
7ab9d31
docs: small docs fixes
scanny Oct 10, 2023
6df147e
release: prepare v1.0.0 release
scanny Oct 10, 2023
532ddd5
docs: add .readthedocs.yaml
scanny Oct 11, 2023
129dd83
fix: #1256 republish parse_xml() at docx.oxml
scanny Oct 11, 2023
96f80f7
hlink: add Hyperlink.fragment and .url
scanny Oct 12, 2023
b56f516
rfctr: improve typing
scanny Oct 12, 2023
8e05650
release: prepare v1.0.1 release
scanny Oct 12, 2023
e45fd78
fix: add build-backend in pyproject.toml
musicinmybrain Oct 13, 2023
8995a40
fix: include requirements files in PyPI sdist
musicinmybrain Oct 13, 2023
e441969
dev: set looponfailroots and filterwarnings
scanny Nov 3, 2023
a1c6b4f
xml: BaseOxmlElement subclasses etree.ElementBase
scanny Nov 2, 2023
523328c
rfctr: improve xmlchemy typing
scanny Nov 3, 2023
b7f5903
rfctr: resolve StoryChild conflation
scanny Nov 3, 2023
cf17811
rfctr: docx.shared type-checks strict
scanny Nov 3, 2023
e315139
rfctr: improve typing local to BlockItemContainer
scanny Nov 2, 2023
3c16691
xfail: for BlockItemContainer.iter_inner_content()
scanny Nov 3, 2023
f46751f
blk: add Document.iter_inner_content()
scanny Nov 3, 2023
24e4c1b
oxml: add .inner_content_elements props
scanny Nov 3, 2023
ee130dc
blk: add BlockItemContainer.iter_inner_content()
scanny Nov 3, 2023
16c7f34
docs: update docs for .iter_inner_content()
scanny Nov 3, 2023
57d3b9e
release: prepare v1.1.0 release
scanny Nov 4, 2023
630ecbf
rfctr(lint): tune in ruff settings
scanny Apr 29, 2024
5a22c52
rfctr: improve typing for tables
scanny Nov 6, 2023
cf5286c
rfctr: modernize table tests
scanny Apr 27, 2024
d8a3289
rfctr: improve expression
scanny Apr 27, 2024
6c34f12
rfctr: modernize opc.shared.lazyproperty
scanny Apr 29, 2024
4e5dd91
feat(table): add _Row.grid_cols_before
scanny Apr 27, 2024
1cfcee7
feat(table): add _Row.grid_cols_after
scanny Apr 27, 2024
5a1d614
docs: update Table docs
scanny Apr 28, 2024
6d49a69
rfctr(table): reimplement CT_Tc._tr_above
scanny Apr 29, 2024
382d43e
feat(table): add _Cell.grid_span
scanny Apr 28, 2024
7508051
feat(table): add CT_Tc.grid_offset
scanny Apr 28, 2024
512f269
rfctr(table): reimplement CT_Tc.tc_at_grid_offset
scanny Apr 28, 2024
f4a48b5
fix(table): fix _Row.cells can raise IndexError
scanny Apr 28, 2024
89b399b
feat(typing): add py.typed, improve public types
scanny Apr 29, 2024
94802e4
fix: fix some shortlist issues
scanny Apr 29, 2024
5a80006
fix(packaging): small packaging and doc tweaks
scanny Apr 30, 2024
0a09474
rfctr: resolve some import cycles
scanny Apr 30, 2024
e531576
release: prepare v1.1.1 release
scanny Apr 30, 2024
3f56b7d
rfctr(dev): use more performant `fd` for clean
scanny May 1, 2024
e493474
fix: XmlPart._rel_ref_count
scanny May 1, 2024
f246fde
rfctr: improve typing
scanny Apr 30, 2024
0ec5dcd
fix(pkg): pull lxml pin
scanny Apr 30, 2024
4cbbdab
fix: accommodate docxtpl use of Part._rels
scanny Apr 30, 2024
0a8e9c4
fix: Python 3.12 fixes
scanny May 1, 2024
0cf6d71
release: prepare v1.1.2 release
scanny May 1, 2024
3228bc5
proj: modernize project environment
scanny Jun 6, 2025
4262f4d
modn: improve type annotation
scanny Jun 6, 2025
afa670a
modn: modernize tests
scanny Jun 6, 2025
592fa8f
modn: improve ruff compliance
scanny Jun 6, 2025
d9da49b
fix: remove redundant w:pic "insertion"
scanny Jun 6, 2025
5cb32d7
xfail: acceptance test for Document.comments
scanny Jun 10, 2025
451747a
comments: add Document.comments
scanny Jun 10, 2025
8f184cc
comments: add DocumentPart.comments
scanny Jun 10, 2025
ae0e82d
comments: add DocumentPart._comments_part
scanny Jun 10, 2025
9c8a2e9
comments: add CommentsPart.comments
scanny Jun 10, 2025
595decc
comments: package-loader loads CommentsPart
scanny Jun 10, 2025
6c0024c
comments: add Comments.__len__()
scanny Jun 10, 2025
88ff3ca
comments: add Comments.__iter__()
scanny Jun 10, 2025
e2aec42
comments: add Comments.get()
scanny Jun 10, 2025
0eeaa2f
comments: add Comment.comment_id
scanny Jun 10, 2025
7cf36d6
xfail: acceptance test for Comment properties
scanny Jun 10, 2025
8af46fe
comments: add Comment.author
scanny Jun 10, 2025
cab50c5
comments: add Comment.initials
scanny Jun 10, 2025
cfb87e7
comments: add Comment.timestamp
scanny Jun 10, 2025
19175ad
comments: add Comment.paragraphs
scanny Jun 10, 2025
432dd15
drawing: add image extraction from Drawing
scanny Jun 10, 2025
d360409
xfail: acceptance test for Comment mutations
scanny Jun 10, 2025
8ac9fc4
comments: add Comments.add_comment()
scanny Jun 10, 2025
761f4cc
comments: add Comment.author, .initials setters
scanny Jun 10, 2025
66da522
xfail: acceptance test for Document.add_comment()
scanny Jun 10, 2025
af3b973
comments: add Document.add_comment()
scanny Jun 9, 2025
e3a321d
comments: add Run.mark_comment_range()
scanny Jun 10, 2025
a809d6c
comments: add Comment.text
scanny Jun 10, 2025
4fbe1f6
docs: add Comments docs
scanny Jun 12, 2025
1fe6601
build: small adjustments for tox
scanny Jun 13, 2025
e454546
release: prepare v1.2.0 release
scanny Jun 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
rfctr: improve typing local to BlockItemContainer
  • Loading branch information
scanny committed Nov 3, 2023
commit e31513946633a62e9006c6438c3da85d06ac4568
13 changes: 7 additions & 6 deletions features/steps/block.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Step implementations for block content containers."""

from behave import given, then, when
from behave.runner import Context

from docx import Document
from docx.table import Table
Expand All @@ -11,12 +12,12 @@


@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 paragraph")
def given_a_paragraph(context):
def given_a_paragraph(context: Context):
context.document = Document()
context.paragraph = context.document.add_paragraph()

Expand All @@ -25,13 +26,13 @@ def given_a_paragraph(context):


@when("I add a paragraph")
def when_add_paragraph(context):
def when_add_paragraph(context: Context):
document = context.document
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
context.document.add_table(rows, cols)

Expand All @@ -40,12 +41,12 @@ def when_add_table(context):


@then("I can access the table")
def then_can_access_table(context):
def then_can_access_table(context: Context):
table = context.document.tables[-1]
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)
35 changes: 28 additions & 7 deletions src/docx/blkcntnr.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
# pyright: reportImportCycles=false

"""Block item container, used by body, cell, header, etc.

Block level items are things like paragraph and table, although there are a few other
specialized ones like structured document tags.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from typing_extensions import TypeAlias

from docx.oxml.table import CT_Tbl
from docx.shared import Parented
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.

These containers include _Body, _Cell, header, footer, footnote, endnote, comment,
and text box objects. Provides the shared functionality to add a block item like a
paragraph or table.
"""

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.

The paragraph has `text` in a single run if present, and is given paragraph
Expand All @@ -37,7 +58,7 @@ def add_paragraph(self, text="", style=None):
paragraph.style = style
return paragraph

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.

The table is appended appended at the end of the content in this container.
Expand All @@ -47,7 +68,7 @@ def add_table(self, rows, cols, width):
from docx.table import Table

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)

@property
Expand All @@ -64,7 +85,7 @@ def tables(self):

Read-only.
"""
from .table import Table
from docx.table import Table

return [Table(tbl, self) for tbl in self._element.tbl_lst]

Expand Down
58 changes: 41 additions & 17 deletions src/docx/document.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
# pyright: reportImportCycles=false
# pyright: reportPrivateUsage=false

"""|Document| and closely related objects."""

from __future__ import annotations

from typing import IO, TYPE_CHECKING, List

from docx.blkcntnr import BlockItemContainer
from docx.enum.section import WD_SECTION
from docx.enum.text import WD_BREAK
from docx.section import Section, Sections
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):
Expand All @@ -15,12 +31,13 @@ class Document(ElementProxy):
a 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.

The heading paragraph will contain `text` and have its paragraph style
Expand All @@ -39,7 +56,9 @@ def add_page_break(self):
paragraph.add_run().add_break(WD_BREAK.PAGE)
return paragraph

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.

The paragraph is populated with `text` and having paragraph style `style`.
Expand All @@ -51,7 +70,12 @@ def add_paragraph(self, text: str = "", style=None) -> Paragraph:
"""
return self._body.add_paragraph(text, style)

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.

The picture contains the image at `image_path_or_stream`, scaled based on
Expand All @@ -65,7 +89,7 @@ def add_picture(self, image_path_or_stream, width=None, height=None):
run = self.add_paragraph().add_run()
return run.add_picture(image_path_or_stream, width, height)

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.

The optional `start_type` argument must be a member of the :ref:`WdSectionStart`
Expand All @@ -75,7 +99,7 @@ def add_section(self, start_type=WD_SECTION.NEW_PAGE):
new_sectPr.start_type = start_type
return Section(new_sectPr, self._part)

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.

`style` may be a table style object or a table style name. If `style` is |None|,
Expand All @@ -92,7 +116,7 @@ def core_properties(self):

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

An inline shape is a graphical object, such as a picture, contained in a run of
text and behaving like a character glyph, being flowed like other text in a
Expand All @@ -101,7 +125,7 @@ def inline_shapes(self):
return self._part.inline_shapes

@property
def paragraphs(self):
def paragraphs(self) -> List[Paragraph]:
"""The |Paragraph| instances in the document, in document order.

Note that paragraphs within revision marks such as ``<w:ins>`` or ``<w:del>`` do
Expand All @@ -110,11 +134,11 @@ def paragraphs(self):
return self._body.paragraphs

@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`.

`path_or_stream` can be either a path to a filesystem location (a string) or a
Expand All @@ -123,12 +147,12 @@ def save(self, path_or_stream):
self._part.save(path_or_stream)

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

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

Expand All @@ -138,7 +162,7 @@ def styles(self):
return self._part.styles

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

Note that only tables appearing at the top level of the document appear in this
Expand All @@ -149,13 +173,13 @@ def tables(self):
return self._body.tables

@property
def _block_width(self):
def _block_width(self) -> Length:
"""A |Length| object specifying the space between margins in last section."""
section = self.sections[-1]
return Emu(section.page_width - section.left_margin - section.right_margin)

@property
def _body(self):
def _body(self) -> _Body:
"""The |_Body| instance containing the content for this document."""
if self.__body is None:
self.__body = _Body(self._element.body, self)
Expand All @@ -168,7 +192,7 @@ class _Body(BlockItemContainer):
It's primary role is a container for document content.
"""

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

Expand Down
25 changes: 18 additions & 7 deletions src/docx/oxml/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

from __future__ import annotations

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

from docx.oxml.section import CT_SectPr
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrMore, ZeroOrOne

if TYPE_CHECKING:
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P


class CT_Document(BaseOxmlElement):
"""``<w:document>`` element, the root element of a document.xml file."""
Expand All @@ -29,14 +33,22 @@ def sectPr_lst(self) -> List[CT_SectPr]:


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.

The last `w:sectPr` becomes the second-to-last, with the new `w:sectPr` being an
Expand All @@ -63,6 +75,5 @@ def clear_content(self):

Leave the <w:sectPr> element if it is present.
"""
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)
Loading