Skip to content

Commit

Permalink
feat(table): add _Row.grid_cols_after
Browse files Browse the repository at this point in the history
  • Loading branch information
scanny committed Apr 29, 2024
1 parent 4e5dd91 commit 1cfcee7
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 0 deletions.
14 changes: 14 additions & 0 deletions features/steps/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ def given_a_table_having_two_rows(context: Context):
context.table_ = document.tables[0]


@given("a table row ending with {count} empty grid columns")
def given_a_table_row_ending_with_count_empty_grid_columns(context: Context, count: str):
document = Document(test_docx("tbl-props"))
table = document.tables[8]
context.row = table.rows[int(count)]


@given("a table row having height of {state}")
def given_a_table_row_having_height_of_state(context: Context, state: str):
table_idx = {"no explicit setting": 0, "2 inches": 2, "3 inches": 3}[state]
Expand Down Expand Up @@ -354,6 +361,13 @@ def then_can_iterate_over_row_collection(context: Context):
assert actual_count == 2


@then("row.grid_cols_after is {value}")
def then_row_grid_cols_after_is_value(context: Context, value: str):
expected = int(value)
actual = context.row.grid_cols_after
assert actual == expected, "expected %s, got %s" % (expected, actual)


@then("row.grid_cols_before is {value}")
def then_row_grid_cols_before_is_value(context: Context, value: str):
expected = int(value)
Expand Down
Binary file modified features/steps/test_files/tbl-props.docx
Binary file not shown.
11 changes: 11 additions & 0 deletions features/tbl-row-props.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Feature: Get and set table row properties
I need a way to get and set the properties of a table row


Scenario Outline: Get Row.grid_cols_after
Given a table row ending with <count> empty grid columns
Then row.grid_cols_after is <count>

Examples: Row.grid_cols_after value cases
| count |
| 0 |
| 1 |
| 2 |


Scenario Outline: Get Row.grid_cols_before
Given a table row starting with <count> empty grid columns
Then row.grid_cols_before is <count>
Expand Down
1 change: 1 addition & 0 deletions src/docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
)

register_element_cls("w:bidiVisual", CT_OnOff)
register_element_cls("w:gridAfter", CT_DecimalNumber)
register_element_cls("w:gridBefore", CT_DecimalNumber)
register_element_cls("w:gridCol", CT_TblGridCol)
register_element_cls("w:gridSpan", CT_DecimalNumber)
Expand Down
17 changes: 17 additions & 0 deletions src/docx/oxml/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ class CT_Row(BaseOxmlElement):
trPr: CT_TrPr | None = ZeroOrOne("w:trPr") # pyright: ignore[reportAssignmentType]
tc = ZeroOrMore("w:tc")

@property
def grid_after(self) -> int:
"""The number of unpopulated layout-grid cells at the end of this row."""
trPr = self.trPr
if trPr is None:
return 0
return trPr.grid_after

@property
def grid_before(self) -> int:
"""The number of unpopulated layout-grid cells at the start of this row."""
Expand Down Expand Up @@ -893,6 +901,9 @@ class CT_TrPr(BaseOxmlElement):
"w:del",
"w:trPrChange",
)
gridAfter: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
"w:gridAfter", successors=_tag_seq[4:]
)
gridBefore: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
"w:gridBefore", successors=_tag_seq[3:]
)
Expand All @@ -901,6 +912,12 @@ class CT_TrPr(BaseOxmlElement):
)
del _tag_seq

@property
def grid_after(self) -> int:
"""The number of unpopulated layout-grid cells at the end of this row."""
gridAfter = self.gridAfter
return 0 if gridAfter is None else gridAfter.val

@property
def grid_before(self) -> int:
"""The number of unpopulated layout-grid cells at the start of this row."""
Expand Down
17 changes: 17 additions & 0 deletions src/docx/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,23 @@ def cells(self) -> tuple[_Cell, ...]:
"""Sequence of |_Cell| instances corresponding to cells in this row."""
return tuple(self.table.row_cells(self._index))

@property
def grid_cols_after(self) -> int:
"""Count of unpopulated grid-columns after the last cell in this row.
Word allows a row to "end early", meaning that one or more cells are not present at the
end of that row.
Note these are not simply "empty" cells. The renderer reads this value and "skips" this
many columns after drawing the last cell.
Note this also implies that not all rows are guaranteed to have the same number of cells,
e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same
table. Visually this appears as a column (at the beginning or end, not in the middle) with
one or more cells missing.
"""
return self._tr.grid_after

@property
def grid_cols_before(self) -> int:
"""Count of unpopulated grid-columns before the first cell in this row.
Expand Down
13 changes: 13 additions & 0 deletions tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,19 @@ def table_(self, request: FixtureRequest):
class Describe_Row:
"""Unit-test suite for `docx.table._Row` objects."""

@pytest.mark.parametrize(
("tr_cxml", "expected_value"),
[
("w:tr", 0),
("w:tr/w:trPr", 0),
("w:tr/w:trPr/w:gridAfter{w:val=0}", 0),
("w:tr/w:trPr/w:gridAfter{w:val=4}", 4),
],
)
def it_knows_its_grid_cols_after(self, tr_cxml: str, expected_value: int | None, parent_: Mock):
row = _Row(cast(CT_Row, element(tr_cxml)), parent_)
assert row.grid_cols_after == expected_value

@pytest.mark.parametrize(
("tr_cxml", "expected_value"),
[
Expand Down

0 comments on commit 1cfcee7

Please sign in to comment.