Skip to content

Commit

Permalink
tbl: scanny#86 add/remove row and column support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ignisor committed Jun 27, 2018
1 parent adf5fbd commit f7eeda0
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -10,3 +10,4 @@ _scratch/
/spec/gen_spec/spec*.db
tags
/tests/debug.py
.idea
17 changes: 17 additions & 0 deletions docs/api/table.rst
Expand Up @@ -21,6 +21,14 @@ A |Table| object is added to a slide using the
:undoc-members:


|_ColumnCollection| objects
---------------------------
.. autoclass:: _ColumnCollection()
:members:
:member-order: bysource
:undoc-members:


|_Column| objects
-----------------

Expand All @@ -30,6 +38,15 @@ A |Table| object is added to a slide using the
:undoc-members:


|_RowCollection| objects
---------------------------
.. autoclass:: _RowCollection()
:members:
:member-order: bysource
:undoc-members:



|_Row| objects
--------------

Expand Down
47 changes: 47 additions & 0 deletions features/steps/table.py
Expand Up @@ -108,6 +108,25 @@ def when_set_table_column_widths(context):
context.table_.columns[1].width = Inches(3.00)


@when("I add a row to a table")
def when_add_row_to_table(context):
context.table_.rows.add_row()


@when("I remove a row from a table")
def when_add_row_to_table(context):
context.table_.rows.remove(context.table_.rows[1])


@when("I add a column to a table")
def when_add_row_to_table(context):
context.table_.columns.add_column()


@when("I remove a column from a table")
def when_add_row_to_table(context):
context.table_.columns.remove(context.table_.columns[1])

# then ====================================================


Expand Down Expand Up @@ -192,3 +211,31 @@ def then_text_appears_in_first_cell_of_table(context):
table = prs.slides[0].shapes[3].table
text = table.cell(0, 0).text_frame.paragraphs[0].runs[0].text
assert text == 'test text'


@then('the table now has 3 rows')
def table_has_three_rows(context):
prs = Presentation(saved_pptx_path)
table = prs.slides[0].shapes[3].table
assert len(table.rows) == 3


@then('the table now has 1 row')
def table_has_three_rows(context):
prs = Presentation(saved_pptx_path)
table = prs.slides[0].shapes[3].table
assert len(table.rows) == 1


@then('the table now has 3 columns')
def table_has_three_rows(context):
prs = Presentation(saved_pptx_path)
table = prs.slides[0].shapes[3].table
assert len(table.columns) == 3


@then('the table now has 1 column')
def table_has_three_rows(context):
prs = Presentation(saved_pptx_path)
table = prs.slides[0].shapes[3].table
assert len(table.columns) == 1
28 changes: 28 additions & 0 deletions features/tbl-table-props.feature
Expand Up @@ -51,3 +51,31 @@ Feature: Change properties of table
When I set the vert_banding property to True
And I save the presentation
Then the columns of the table have alternating shading


Scenario: Append table row
Given a 2x2 table
When I add a row to a table
And I save the presentation
Then the table now has 3 rows


Scenario: Remove table row
Given a 2x2 table
When I remove a row from a table
And I save the presentation
Then the table now has 1 row


Scenario: Append table column
Given a 2x2 table
When I add a column to a table
And I save the presentation
Then the table now has 3 columns


Scenario: Append table column
Given a 2x2 table
When I remove a column from a table
And I save the presentation
Then the table now has 1 column
54 changes: 54 additions & 0 deletions pptx/shapes/table.py
Expand Up @@ -6,6 +6,8 @@

from __future__ import absolute_import, print_function

import copy

from . import Subshape
from ..compat import is_integer, to_unicode
from ..dml.fill import FillFormat
Expand Down Expand Up @@ -370,6 +372,36 @@ def notify_width_changed(self):
"""
self._parent.notify_width_changed()

def add_column(self):
"""
Duplicates last column to keep formatting and resets it's cells text_frames
(e.g. ``column = table.columns.add_column()``).
Returns new |_Column| instance.
"""
new_col = copy.deepcopy(self._tbl.tblGrid.gridCol_lst[-1])
self._tbl.tblGrid.append(new_col) # copies last grid element

for tr in self._tbl.tr_lst:
# duplicate last cell of each row
new_tc = copy.deepcopy(tr.tc_lst[-1])
tr.append(new_tc)

cell = _Cell(new_tc, tr.tc_lst)
cell.text = ''

return _Column(new_col, self)

def remove(self, column):
"""
Removes specified *column* (e.g. ``table.columns.remove(table.columns[0])``).
"""
col_idx = self._tbl.tblGrid.index(column._gridCol)

for tr in self._tbl.tr_lst:
tr.remove(tr.tc_lst[col_idx])

self._tbl.tblGrid.remove(column._gridCol)


class _RowCollection(Subshape):
"""
Expand Down Expand Up @@ -399,3 +431,25 @@ def notify_height_changed(self):
Called by a row when its height changes. Pass along to parent.
"""
self._parent.notify_height_changed()

def add_row(self):
"""
Duplicates last row to keep formatting and resets it's cells text_frames
(e.g. ``row = table.rows.add_row()``).
Returns new |_Row| instance.
"""
new_row = copy.deepcopy(self._tbl.tr_lst[-1]) # copies last row element

for tc in new_row.tc_lst:
cell = _Cell(tc, new_row.tc_lst)
cell.text = ''

self._tbl.append(new_row)

return _Row(new_row, self)

def remove(self, row):
"""
Removes specified *row* (e.g. ``table.rows.remove(table.rows[0])``).
"""
self._tbl.remove(row._tr)
58 changes: 58 additions & 0 deletions tests/shapes/test_table.py
Expand Up @@ -425,6 +425,14 @@ def it_raises_on_indexed_access_out_of_range(self):
with pytest.raises(IndexError):
columns[9]

def it_supports_appending_column(self, append_fixture):
columns, expected_count = append_fixture
assert len(columns) == expected_count

def it_supports_removing_column(self, remove_fixture):
columns, expected_count = remove_fixture
assert len(columns) == expected_count

# fixtures -------------------------------------------------------

@pytest.fixture(params=[
Expand Down Expand Up @@ -462,6 +470,28 @@ def len_fixture(self, request):
columns = _ColumnCollection(element(tbl_cxml), None)
return columns, expected_len

@pytest.fixture(params=[
('a:tbl/a:tblGrid/a:gridCol', 2),
('a:tbl/a:tblGrid/(a:gridCol, a:gridCol)', 3),
])
def append_fixture(self, request):
tbl_cxml, expected_len = request.param
columns = _ColumnCollection(element(tbl_cxml), None)
columns.add_column()

return columns, expected_len

@pytest.fixture(params=[
('a:tbl/a:tblGrid/a:gridCol', 0),
('a:tbl/a:tblGrid/(a:gridCol, a:gridCol)', 1),
])
def remove_fixture(self, request):
tbl_cxml, expected_len = request.param
columns = _ColumnCollection(element(tbl_cxml), None)
columns.remove(columns[0])

return columns, expected_len


class Describe_Row(object):

Expand Down Expand Up @@ -555,6 +585,14 @@ def it_raises_on_indexed_access_out_of_range(self):
with pytest.raises(IndexError):
rows[9]

def it_supports_appending_row(self, append_fixture):
rows, expected_count = append_fixture
assert len(rows) == expected_count

def it_supports_removing_row(self, remove_fixture):
rows, expected_count = remove_fixture
assert len(rows) == expected_count

# fixtures -------------------------------------------------------

@pytest.fixture(params=[
Expand Down Expand Up @@ -584,3 +622,23 @@ def len_fixture(self, request):
tbl_cxml, expected_len = request.param
rows = _RowCollection(element(tbl_cxml), None)
return rows, expected_len

@pytest.fixture(params=[
('a:tbl/a:tr', 2), ('a:tbl/(a:tr, a:tr)', 3),
])
def append_fixture(self, request):
tbl_cxml, expected_len = request.param
rows = _RowCollection(element(tbl_cxml), None)
rows.add_row()

return rows, expected_len

@pytest.fixture(params=[
('a:tbl/a:tr', 0), ('a:tbl/(a:tr, a:tr)', 1),
])
def remove_fixture(self, request):
tbl_cxml, expected_len = request.param
rows = _RowCollection(element(tbl_cxml), None)
rows.remove(rows[0])

return rows, expected_len

0 comments on commit f7eeda0

Please sign in to comment.