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 authored and mokemokechicken committed May 24, 2020
1 parent b31cf20 commit f3dec27
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
58 changes: 57 additions & 1 deletion features/steps/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pptx.enum.text import MSO_ANCHOR # noqa
from pptx.util import Inches

from helpers import test_pptx
from helpers import test_pptx, saved_pptx_path


# given ===================================================
Expand Down Expand Up @@ -158,6 +158,25 @@ def when_I_call_origin_cell_merge_other_cell(context):
context.origin_cell.merge(context.other_cell)


@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 @@ -309,3 +328,40 @@ def then_table_vert_banding_is_value(context, bool_lit):
actual = context.table_.vert_banding
expected = eval(bool_lit)
assert actual is expected, "table.vert_banding is %s" % actual


@then('the text appears in the first cell of the table')
def then_text_appears_in_first_cell_of_table(context):
prs = Presentation(saved_pptx_path)
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

81 changes: 81 additions & 0 deletions features/tbl-table-props.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
Feature: Change properties of table
In order to change the formatting of a table to meet my needs
As a developer using python-pptx
I need to set the properties of a table


Scenario: Set column widths
Given a 2x2 table
When I set the width of the table's columns
And I save the presentation
Then the table appears with the new column widths


Scenario: Set table first_row property
Given a 2x2 table
When I set the first_row property to True
And I save the presentation
Then the first row of the table has special formatting


Scenario: Set table first_col property
Given a 2x2 table
When I set the first_col property to True
And I save the presentation
Then the first column of the table has special formatting


Scenario: Set table last_row property
Given a 2x2 table
When I set the last_row property to True
And I save the presentation
Then the last row of the table has special formatting


Scenario: Set table last_col property
Given a 2x2 table
When I set the last_col property to True
And I save the presentation
Then the last column of the table has special formatting


Scenario: Set table horz_banding property
Given a 2x2 table
When I set the horz_banding property to True
And I save the presentation
Then the rows of the table have alternating shading


Scenario: Set table vert_banding property
Given a 2x2 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/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import copy

from pptx.compat import is_integer
from pptx.dml.fill import FillFormat
from pptx.oxml.table import TcRange
Expand Down Expand Up @@ -493,6 +495,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):
"""Sequence of table rows"""
Expand Down Expand Up @@ -521,3 +553,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)
65 changes: 63 additions & 2 deletions tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

from pptx.dml.fill import FillFormat
from pptx.enum.text import MSO_ANCHOR
from pptx.enum.text import MSO_VERTICAL_ANCHOR as MSO_ANCHOR
from pptx.oxml.ns import qn
from pptx.oxml.table import CT_Table, CT_TableCell, TcRange
from pptx.shapes.graphfrm import GraphicFrame
Expand Down Expand Up @@ -660,6 +660,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(
Expand Down Expand Up @@ -703,6 +711,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):
def it_knows_its_height(self, height_get_fixture):
Expand Down Expand Up @@ -730,7 +760,10 @@ def cells_fixture(self, _CellCollection_, cells_):
row = _Row(element("a:tr"), None)
return row, _CellCollection_, cells_

@pytest.fixture(params=[("a:tr{h=914400}", Inches(1)), ("a:tr{h=10pt}", Pt(10))])
@pytest.fixture(params=[
('a:tr{h=914400}', Inches(1)),
('a:tr{h=10pt}', Pt(10)),
])
def height_get_fixture(self, request):
tr_cxml, expected_value = request.param
row = _Row(element(tr_cxml), None)
Expand Down Expand Up @@ -791,6 +824,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=["a:tbl", "a:tbl/a:tr", "a:tbl/(a:tr, a:tr, a:tr)"])
Expand All @@ -814,3 +855,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 f3dec27

Please sign in to comment.