From 5bd5b8d5e5aebed7ba7cfa27b0ff3b9f844bb5e1 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 5 May 2016 18:23:37 -0700 Subject: [PATCH 01/14] acpt: add scenarios for TabStop property getters --- docs/api/enum/WdTabAlignment.rst | 38 ++++++++++++ docs/api/enum/WdTabLeader.rst | 26 ++++++++ docs/api/enum/index.rst | 4 +- docx/enum/text.py | 74 +++++++++++++++++++++++ features/steps/tabstops.py | 45 ++++++++++++++ features/steps/test_files/tab-stops.docx | Bin 12828 -> 13170 bytes features/tab-tabstop-props.feature | 37 ++++++++++++ 7 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 docs/api/enum/WdTabAlignment.rst create mode 100644 docs/api/enum/WdTabLeader.rst create mode 100644 features/tab-tabstop-props.feature diff --git a/docs/api/enum/WdTabAlignment.rst b/docs/api/enum/WdTabAlignment.rst new file mode 100644 index 000000000..a4adb0fc9 --- /dev/null +++ b/docs/api/enum/WdTabAlignment.rst @@ -0,0 +1,38 @@ +.. _WdTabAlignment: + +``WD_TAB_ALIGNMENT`` +==================== + +Specifies the tab stop alignment to apply. + +---- + +LEFT + Left-aligned. + +CENTER + Center-aligned. + +RIGHT + Right-aligned. + +DECIMAL + Decimal-aligned. + +BAR + Bar-aligned. + +LIST + List-aligned. (deprecated) + +CLEAR + Clear an inherited tab stop. + +END + Right-aligned. (deprecated) + +NUM + Left-aligned. (deprecated) + +START + Left-aligned. (deprecated) diff --git a/docs/api/enum/WdTabLeader.rst b/docs/api/enum/WdTabLeader.rst new file mode 100644 index 000000000..73990eeef --- /dev/null +++ b/docs/api/enum/WdTabLeader.rst @@ -0,0 +1,26 @@ +.. _WdTabLeader: + +``WD_TAB_LEADER`` +================= + +Specifies the character to use as the leader with formatted tabs. + +---- + +SPACES + Spaces. Default. + +DOTS + Dots. + +DASHES + Dashes. + +LINES + Double lines. + +HEAVY + A heavy line. + +MIDDLE_DOT + A vertically-centered dot. diff --git a/docs/api/enum/index.rst b/docs/api/enum/index.rst index 9c7371dcf..f6ba1e261 100644 --- a/docs/api/enum/index.rst +++ b/docs/api/enum/index.rst @@ -15,8 +15,10 @@ can be found here: WdColorIndex WdLineSpacing WdOrientation + WdRowAlignment WdSectionStart WdStyleType - WdRowAlignment + WdTabAlignment + WdTabLeader WdTableDirection WdUnderline diff --git a/docx/enum/text.py b/docx/enum/text.py index 97597eed1..f4111eb92 100644 --- a/docx/enum/text.py +++ b/docx/enum/text.py @@ -195,6 +195,80 @@ class WD_LINE_SPACING(XmlEnumeration): ) +class WD_TAB_ALIGNMENT(XmlEnumeration): + """ + Specifies the tab stop alignment to apply. + """ + + __ms_name__ = 'WdTabAlignment' + + __url__ = 'https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx' + + __members__ = ( + XmlMappedEnumMember( + 'LEFT', 0, 'left', 'Left-aligned.' + ), + XmlMappedEnumMember( + 'CENTER', 1, 'center', 'Center-aligned.' + ), + XmlMappedEnumMember( + 'RIGHT', 2, 'right', 'Right-aligned.' + ), + XmlMappedEnumMember( + 'DECIMAL', 3, 'decimal', 'Decimal-aligned.' + ), + XmlMappedEnumMember( + 'BAR', 4, 'bar', 'Bar-aligned.' + ), + XmlMappedEnumMember( + 'LIST', 6, 'list', 'List-aligned. (deprecated)' + ), + XmlMappedEnumMember( + 'CLEAR', 101, 'clear', 'Clear an inherited tab stop.' + ), + XmlMappedEnumMember( + 'END', 102, 'end', 'Right-aligned. (deprecated)' + ), + XmlMappedEnumMember( + 'NUM', 103, 'num', 'Left-aligned. (deprecated)' + ), + XmlMappedEnumMember( + 'START', 104, 'start', 'Left-aligned. (deprecated)' + ), + ) + + +class WD_TAB_LEADER(XmlEnumeration): + """ + Specifies the character to use as the leader with formatted tabs. + """ + + __ms_name__ = 'WdTabLeader' + + __url__ = 'https://msdn.microsoft.com/en-us/library/office/ff845050.aspx' + + __members__ = ( + XmlMappedEnumMember( + 'SPACES', 0, 'none', 'Spaces. Default.' + ), + XmlMappedEnumMember( + 'DOTS', 1, 'dot', 'Dots.' + ), + XmlMappedEnumMember( + 'DASHES', 2, 'hyphen', 'Dashes.' + ), + XmlMappedEnumMember( + 'LINES', 3, 'underscore', 'Double lines.' + ), + XmlMappedEnumMember( + 'HEAVY', 4, 'heavy', 'A heavy line.' + ), + XmlMappedEnumMember( + 'MIDDLE_DOT', 5, 'middleDot', 'A vertically-centered dot.' + ), + ) + + class WD_UNDERLINE(XmlEnumeration): """ Specifies the style of underline applied to a run of characters. diff --git a/features/steps/tabstops.py b/features/steps/tabstops.py index b6396f8bb..8f8250e59 100644 --- a/features/steps/tabstops.py +++ b/features/steps/tabstops.py @@ -7,6 +7,7 @@ from behave import given, then from docx import Document +from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER from docx.text.tabstops import TabStop from helpers import test_docx @@ -22,6 +23,30 @@ def given_a_tab_stops_having_count_tab_stops(context, count): context.tab_stops = paragraph_format.tab_stops +@given('a tab stop 0.5 inches {in_or_out} from the paragraph left edge') +def given_a_tab_stop_inches_from_paragraph_left_edge(context, in_or_out): + tab_idx = {'out': 0, 'in': 1}[in_or_out] + document = Document(test_docx('tab-stops')) + paragraph_format = document.paragraphs[2].paragraph_format + context.tab_stop = paragraph_format.tab_stops[tab_idx] + + +@given('a tab stop having {alignment} alignment') +def given_a_tab_stop_having_alignment_alignment(context, alignment): + tab_idx = {'LEFT': 0, 'CENTER': 1, 'RIGHT': 2}[alignment] + document = Document(test_docx('tab-stops')) + paragraph_format = document.paragraphs[1].paragraph_format + context.tab_stop = paragraph_format.tab_stops[tab_idx] + + +@given('a tab stop having {leader} leader') +def given_a_tab_stop_having_leader_leader(context, leader): + tab_idx = {'no specified': 0, 'a dotted': 2}[leader] + document = Document(test_docx('tab-stops')) + paragraph_format = document.paragraphs[1].paragraph_format + context.tab_stop = paragraph_format.tab_stops[tab_idx] + + # then ===================================================== @then('I can access a tab stop by index') @@ -43,3 +68,23 @@ def then_I_can_iterate_the_TabStops_object(context): def then_len_tab_stops_is_count(context, count): tab_stops = context.tab_stops assert len(tab_stops) == int(count) + + +@then('tab_stop.alignment is {alignment}') +def then_tab_stop_alignment_is_alignment(context, alignment): + expected_value = getattr(WD_TAB_ALIGNMENT, alignment) + tab_stop = context.tab_stop + assert tab_stop.alignment == expected_value + + +@then('tab_stop.leader is {leader}') +def then_tab_stop_leader_is_leader(context, leader): + expected_value = getattr(WD_TAB_LEADER, leader) + tab_stop = context.tab_stop + assert tab_stop.leader == expected_value + + +@then('tab_stop.position is {position}') +def then_tab_stop_position_is_position(context, position): + tab_stop = context.tab_stop + assert tab_stop.position == int(position) diff --git a/features/steps/test_files/tab-stops.docx b/features/steps/test_files/tab-stops.docx index a33532779dc9647b56e4e1a0f94d864c4041de6e..fa51252ed4102b62265cbab8a535e69a191f5233 100644 GIT binary patch delta 8788 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 diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature new file mode 100644 index 000000000..fe8632ed2 --- /dev/null +++ b/features/tab-tabstop-props.feature @@ -0,0 +1,37 @@ +Feature: Tab stop properties + To change the properties of an individual tab stop + As a developer using python-docx + I need a set of read/write properties on TabStop + + + @wip + Scenario Outline: Get tab stop position + Given a tab stop 0.5 inches from the paragraph left edge + Then tab_stop.position is + + Examples: tab stop positions + | in-or-out | position | + | in | 457200 | + | out | -457200 | + + + @wip + Scenario Outline: Get tab stop alignment + Given a tab stop having alignment + Then tab_stop.alignment is + + Examples: tab stop alignments + | alignment | + | LEFT | + | RIGHT | + + + @wip + Scenario Outline: Get tab stop leader + Given a tab stop having leader + Then tab_stop.leader is + + Examples: tab stop leaders + | leader | value | + | no specified | SPACES | + | a dotted | DOTS | From 062afba4ba67a1fdcdb9d7fa3198b371cbc9369b Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 5 May 2016 19:20:32 -0700 Subject: [PATCH 02/14] tabs: add TabStop.position --- docx/oxml/__init__.py | 5 ++++- docx/oxml/text/parfmt.py | 7 +++++++ docx/text/tabstops.py | 8 ++++++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 8c5b2a6ed..528b1eac7 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -181,7 +181,9 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None): from .text.paragraph import CT_P register_element_cls('w:p', CT_P) -from .text.parfmt import CT_Ind, CT_Jc, CT_PPr, CT_Spacing, CT_TabStops +from .text.parfmt import ( + CT_Ind, CT_Jc, CT_PPr, CT_Spacing, CT_TabStop, CT_TabStops +) register_element_cls('w:ind', CT_Ind) register_element_cls('w:jc', CT_Jc) register_element_cls('w:keepLines', CT_OnOff) @@ -190,6 +192,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None): register_element_cls('w:pPr', CT_PPr) register_element_cls('w:pStyle', CT_String) register_element_cls('w:spacing', CT_Spacing) +register_element_cls('w:tab', CT_TabStop) register_element_cls('w:tabs', CT_TabStops) register_element_cls('w:widowControl', CT_OnOff) diff --git a/docx/oxml/text/parfmt.py b/docx/oxml/text/parfmt.py index 78922e231..42fc11807 100644 --- a/docx/oxml/text/parfmt.py +++ b/docx/oxml/text/parfmt.py @@ -315,6 +315,13 @@ class CT_Spacing(BaseOxmlElement): lineRule = OptionalAttribute('w:lineRule', WD_LINE_SPACING) +class CT_TabStop(BaseOxmlElement): + """ + ```` element, representing an individual tab stop. + """ + pos = RequiredAttribute('w:pos', ST_SignedTwipsMeasure) + + class CT_TabStops(BaseOxmlElement): """ ```` element, container for a sorted sequence of tab stops. diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index a857299c0..771503ee3 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -63,3 +63,11 @@ class TabStop(ElementProxy): def __init__(self, element): super(TabStop, self).__init__(element, None) self._tab = element + + @property + def position(self): + """ + The distance (in EMU) of this tab stop from the inside edge of the + paragraph. May be positive or negative. + """ + return self._tab.pos diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index fe8632ed2..ef1880214 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -4,7 +4,6 @@ Feature: Tab stop properties I need a set of read/write properties on TabStop - @wip Scenario Outline: Get tab stop position Given a tab stop 0.5 inches from the paragraph left edge Then tab_stop.position is diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 05652cdd1..ae05f81aa 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -9,6 +9,7 @@ absolute_import, division, print_function, unicode_literals ) +from docx.shared import Twips from docx.text.tabstops import TabStop, TabStops import pytest @@ -17,6 +18,20 @@ from ..unitutil.mock import call, class_mock, instance_mock +class DescribeTabStop(object): + + def it_knows_its_position(self, position_get_fixture): + tab_stop, expected_value = position_get_fixture + assert tab_stop.position == expected_value + + # fixture -------------------------------------------------------- + + @pytest.fixture + def position_get_fixture(self, request): + tab_stop = TabStop(element('w:tab{w:pos=720}')) + return tab_stop, Twips(720) + + class DescribeTabStops(object): def it_knows_its_length(self, len_fixture): From 565c20eb766d186b1029ff679d410374e2ed4d8f Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 5 May 2016 19:27:50 -0700 Subject: [PATCH 03/14] tabs: add TabStop.alignment --- docx/oxml/text/parfmt.py | 5 ++++- docx/text/tabstops.py | 8 ++++++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docx/oxml/text/parfmt.py b/docx/oxml/text/parfmt.py index 42fc11807..4835ec55f 100644 --- a/docx/oxml/text/parfmt.py +++ b/docx/oxml/text/parfmt.py @@ -4,7 +4,9 @@ Custom element classes related to paragraph properties (CT_PPr). """ -from ...enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING +from ...enum.text import ( + WD_ALIGN_PARAGRAPH, WD_LINE_SPACING, WD_TAB_ALIGNMENT +) from ...shared import Length from ..simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure from ..xmlchemy import ( @@ -319,6 +321,7 @@ class CT_TabStop(BaseOxmlElement): """ ```` element, representing an individual tab stop. """ + val = RequiredAttribute('w:val', WD_TAB_ALIGNMENT) pos = RequiredAttribute('w:pos', ST_SignedTwipsMeasure) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 771503ee3..6d6677d85 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -64,6 +64,14 @@ def __init__(self, element): super(TabStop, self).__init__(element, None) self._tab = element + @property + def alignment(self): + """ + A member of :ref:`WdTabAlignment` specifying the alignment setting + for this tab stop. + """ + return self._tab.val + @property def position(self): """ diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index ef1880214..122ca1898 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -14,7 +14,6 @@ Feature: Tab stop properties | out | -457200 | - @wip Scenario Outline: Get tab stop alignment Given a tab stop having alignment Then tab_stop.alignment is diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index ae05f81aa..b6dffd7df 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -9,6 +9,7 @@ absolute_import, division, print_function, unicode_literals ) +from docx.enum.text import WD_TAB_ALIGNMENT from docx.shared import Twips from docx.text.tabstops import TabStop, TabStops @@ -24,8 +25,22 @@ def it_knows_its_position(self, position_get_fixture): tab_stop, expected_value = position_get_fixture assert tab_stop.position == expected_value + def it_knows_its_alignment(self, alignment_get_fixture): + tab_stop, expected_value = alignment_get_fixture + assert tab_stop.alignment == expected_value + # fixture -------------------------------------------------------- + @pytest.fixture(params=[ + ('w:tab{w:val=left}', 'LEFT'), + ('w:tab{w:val=right}', 'RIGHT'), + ]) + def alignment_get_fixture(self, request): + tab_stop_cxml, member = request.param + tab_stop = TabStop(element(tab_stop_cxml)) + expected_value = getattr(WD_TAB_ALIGNMENT, member) + return tab_stop, expected_value + @pytest.fixture def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) From 8d61431fbc65547898864cb2957b9d8277b2283d Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 5 May 2016 19:39:00 -0700 Subject: [PATCH 04/14] tabs: add TabStop.leader --- docx/oxml/text/parfmt.py | 5 ++++- docx/text/tabstops.py | 9 +++++++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docx/oxml/text/parfmt.py b/docx/oxml/text/parfmt.py index 4835ec55f..10529be7e 100644 --- a/docx/oxml/text/parfmt.py +++ b/docx/oxml/text/parfmt.py @@ -5,7 +5,7 @@ """ from ...enum.text import ( - WD_ALIGN_PARAGRAPH, WD_LINE_SPACING, WD_TAB_ALIGNMENT + WD_ALIGN_PARAGRAPH, WD_LINE_SPACING, WD_TAB_ALIGNMENT, WD_TAB_LEADER ) from ...shared import Length from ..simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure @@ -322,6 +322,9 @@ class CT_TabStop(BaseOxmlElement): ```` element, representing an individual tab stop. """ val = RequiredAttribute('w:val', WD_TAB_ALIGNMENT) + leader = OptionalAttribute( + 'w:leader', WD_TAB_LEADER, default=WD_TAB_LEADER.SPACES + ) pos = RequiredAttribute('w:pos', ST_SignedTwipsMeasure) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 6d6677d85..c344c907b 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -72,6 +72,15 @@ def alignment(self): """ return self._tab.val + @property + def leader(self): + """ + A member of :ref:`WdTabLeader` specifying a repeating character used + as a "leader", filling in the space spanned by this tab. Assigning + |None| produces the same result as assigning `WD_TAB_LEADER.SPACES`. + """ + return self._tab.leader + @property def position(self): """ diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index 122ca1898..ae411a924 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -24,7 +24,6 @@ Feature: Tab stop properties | RIGHT | - @wip Scenario Outline: Get tab stop leader Given a tab stop having leader Then tab_stop.leader is diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index b6dffd7df..b4062b92e 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -9,7 +9,7 @@ absolute_import, division, print_function, unicode_literals ) -from docx.enum.text import WD_TAB_ALIGNMENT +from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER from docx.shared import Twips from docx.text.tabstops import TabStop, TabStops @@ -29,6 +29,10 @@ def it_knows_its_alignment(self, alignment_get_fixture): tab_stop, expected_value = alignment_get_fixture assert tab_stop.alignment == expected_value + def it_knows_its_leader(self, leader_get_fixture): + tab_stop, expected_value = leader_get_fixture + assert tab_stop.leader == expected_value + # fixture -------------------------------------------------------- @pytest.fixture(params=[ @@ -41,6 +45,17 @@ def alignment_get_fixture(self, request): expected_value = getattr(WD_TAB_ALIGNMENT, member) return tab_stop, expected_value + @pytest.fixture(params=[ + ('w:tab', 'SPACES'), + ('w:tab{w:leader=none}', 'SPACES'), + ('w:tab{w:leader=dot}', 'DOTS'), + ]) + def leader_get_fixture(self, request): + tab_stop_cxml, member = request.param + tab_stop = TabStop(element(tab_stop_cxml)) + expected_value = getattr(WD_TAB_LEADER, member) + return tab_stop, expected_value + @pytest.fixture def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) From 934624b16a8b72957549dade08545c9ea246a84b Mon Sep 17 00:00:00 2001 From: DKWoods Date: Fri, 6 May 2016 17:29:59 -0700 Subject: [PATCH 05/14] acpt: add scenarios for TabStop property setters --- features/steps/tabstops.py | 21 ++++++++++++++++- features/tab-tabstop-props.feature | 36 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/features/steps/tabstops.py b/features/steps/tabstops.py index 8f8250e59..e69cc598d 100644 --- a/features/steps/tabstops.py +++ b/features/steps/tabstops.py @@ -4,7 +4,7 @@ Step implementations for paragraph-related features """ -from behave import given, then +from behave import given, then, when from docx import Document from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER @@ -47,6 +47,25 @@ def given_a_tab_stop_having_leader_leader(context, leader): context.tab_stop = paragraph_format.tab_stops[tab_idx] +# when ==================================================== + +@when('I assign {member} to tab_stop.alignment') +def when_I_assign_member_to_tab_stop_alignment(context, member): + value = getattr(WD_TAB_ALIGNMENT, member) + context.tab_stop.alignment = value + + +@when('I assign {member} to tab_stop.leader') +def when_I_assign_member_to_tab_stop_leader(context, member): + value = getattr(WD_TAB_LEADER, member) + context.tab_stop.leader = value + + +@when('I assign {value} to tab_stop.position') +def when_I_assign_value_to_tab_stop_value(context, value): + context.tab_stop.position = int(value) + + # then ===================================================== @then('I can access a tab stop by index') diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index ae411a924..66593fc4e 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -14,6 +14,18 @@ Feature: Tab stop properties | out | -457200 | + @wip + Scenario Outline: Set tab stop position + Given a tab stop 0.5 inches in from the paragraph left edge + When I assign to tab_stop.position + Then tab_stop.position is + + Examples: tab stop positions + | value | + | 228600 | + | -228600 | + + Scenario Outline: Get tab stop alignment Given a tab stop having alignment Then tab_stop.alignment is @@ -24,6 +36,18 @@ Feature: Tab stop properties | RIGHT | + @wip + Scenario Outline: Set tab stop alignment + Given a tab stop having alignment + When I assign to tab_stop.alignment + Then tab_stop.alignment is + + Examples: tab stop alignments + | alignment | member | + | LEFT | CENTER | + | RIGHT | LEFT | + + Scenario Outline: Get tab stop leader Given a tab stop having leader Then tab_stop.leader is @@ -32,3 +56,15 @@ Feature: Tab stop properties | leader | value | | no specified | SPACES | | a dotted | DOTS | + + + @wip + Scenario Outline: Set tab stop leader + Given a tab stop having leader + When I assign to tab_stop.leader + Then tab_stop.leader is + + Examples: tab stop leaders + | leader | member | + | no specified | DOTS | + | a dotted | SPACES | From 189075f2d1dda4923a3637f256090d538b4c21c6 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Fri, 6 May 2016 17:35:53 -0700 Subject: [PATCH 06/14] tabs: add TabStop.position setter --- docx/text/tabstops.py | 4 ++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index c344c907b..7cafa4aac 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -88,3 +88,7 @@ def position(self): paragraph. May be positive or negative. """ return self._tab.pos + + @position.setter + def position(self, value): + self._tab.pos = value diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index 66593fc4e..8401261bd 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -14,7 +14,6 @@ Feature: Tab stop properties | out | -457200 | - @wip Scenario Outline: Set tab stop position Given a tab stop 0.5 inches in from the paragraph left edge When I assign to tab_stop.position diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index b4062b92e..ef38e34e4 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -15,7 +15,7 @@ import pytest -from ..unitutil.cxml import element +from ..unitutil.cxml import element, xml from ..unitutil.mock import call, class_mock, instance_mock @@ -25,6 +25,11 @@ def it_knows_its_position(self, position_get_fixture): tab_stop, expected_value = position_get_fixture assert tab_stop.position == expected_value + def it_can_change_its_position(self, position_set_fixture): + tab_stop, value, expected_xml = position_set_fixture + tab_stop.position = value + assert tab_stop._element.xml == expected_xml + def it_knows_its_alignment(self, alignment_get_fixture): tab_stop, expected_value = alignment_get_fixture assert tab_stop.alignment == expected_value @@ -61,6 +66,16 @@ def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) return tab_stop, Twips(720) + @pytest.fixture(params=[ + ('w:tab{w:pos=360}', Twips(720), 'w:tab{w:pos=720}'), + ('w:tab{w:pos=360}', Twips(-720), 'w:tab{w:pos=-720}') + ]) + def position_set_fixture(self, request): + tab_stop_cxml, value, expected_cxml = request.param + tab_stop = TabStop(element(tab_stop_cxml)) + expected_xml = xml(expected_cxml) + return tab_stop, value, expected_xml + class DescribeTabStops(object): From a32122c873ab464d5e786b757ac0a0a6cbf5bb4f Mon Sep 17 00:00:00 2001 From: DKWoods Date: Fri, 6 May 2016 17:43:33 -0700 Subject: [PATCH 07/14] tabs: add TabStop.alignment setter --- docx/text/tabstops.py | 4 ++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 7cafa4aac..6cbf1120d 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -72,6 +72,10 @@ def alignment(self): """ return self._tab.val + @alignment.setter + def alignment(self, value): + self._tab.val = value + @property def leader(self): """ diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index 8401261bd..fe9de1d9b 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -35,7 +35,6 @@ Feature: Tab stop properties | RIGHT | - @wip Scenario Outline: Set tab stop alignment Given a tab stop having alignment When I assign to tab_stop.alignment diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index ef38e34e4..4fe4cd361 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -34,6 +34,11 @@ def it_knows_its_alignment(self, alignment_get_fixture): tab_stop, expected_value = alignment_get_fixture assert tab_stop.alignment == expected_value + def it_can_change_its_alignment(self, alignment_set_fixture): + tab_stop, value, expected_xml = alignment_set_fixture + tab_stop.alignment = value + assert tab_stop._element.xml == expected_xml + def it_knows_its_leader(self, leader_get_fixture): tab_stop, expected_value = leader_get_fixture assert tab_stop.leader == expected_value @@ -50,6 +55,17 @@ def alignment_get_fixture(self, request): expected_value = getattr(WD_TAB_ALIGNMENT, member) return tab_stop, expected_value + @pytest.fixture(params=[ + ('w:tab{w:val=left}', 'RIGHT', 'w:tab{w:val=right}'), + ('w:tab{w:val=right}', 'LEFT', 'w:tab{w:val=left}'), + ]) + def alignment_set_fixture(self, request): + tab_stop_cxml, member, expected_cxml = request.param + tab_stop = TabStop(element(tab_stop_cxml)) + expected_xml = xml(expected_cxml) + value = getattr(WD_TAB_ALIGNMENT, member) + return tab_stop, value, expected_xml + @pytest.fixture(params=[ ('w:tab', 'SPACES'), ('w:tab{w:leader=none}', 'SPACES'), From 276ebe9c36249600609e5f85ab292375b9cddaa1 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Fri, 6 May 2016 17:56:15 -0700 Subject: [PATCH 08/14] tabs: add TabStop.leader setter --- docx/text/tabstops.py | 4 ++++ features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 6cbf1120d..79d3c2eb7 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -85,6 +85,10 @@ def leader(self): """ return self._tab.leader + @leader.setter + def leader(self, value): + self._tab.leader = value + @property def position(self): """ diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index fe9de1d9b..1aa1dc45f 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -56,7 +56,6 @@ Feature: Tab stop properties | a dotted | DOTS | - @wip Scenario Outline: Set tab stop leader Given a tab stop having leader When I assign to tab_stop.leader diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 4fe4cd361..3a03d6d8e 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -43,6 +43,11 @@ def it_knows_its_leader(self, leader_get_fixture): tab_stop, expected_value = leader_get_fixture assert tab_stop.leader == expected_value + def it_can_change_its_leader(self, leader_set_fixture): + tab_stop, value, expected_xml = leader_set_fixture + tab_stop.leader = value + assert tab_stop._element.xml == expected_xml + # fixture -------------------------------------------------------- @pytest.fixture(params=[ @@ -77,6 +82,23 @@ def leader_get_fixture(self, request): expected_value = getattr(WD_TAB_LEADER, member) return tab_stop, expected_value + @pytest.fixture(params=[ + ('w:tab', 'DOTS', 'w:tab{w:leader=dot}'), + ('w:tab{w:leader=dot}', 'DASHES', 'w:tab{w:leader=hyphen}'), + ('w:tab{w:leader=hyphen}', 'SPACES', 'w:tab'), + ('w:tab{w:leader=hyphen}', None, 'w:tab'), + ('w:tab', 'SPACES', 'w:tab'), + ('w:tab', None, 'w:tab'), + ]) + def leader_set_fixture(self, request): + tab_stop_cxml, new_value, expected_cxml = request.param + tab_stop = TabStop(element(tab_stop_cxml)) + value = ( + None if new_value is None else getattr(WD_TAB_LEADER, new_value) + ) + expected_xml = xml(expected_cxml) + return tab_stop, value, expected_xml + @pytest.fixture def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) From 76eab78ee45b99ff6a4657a2f41407bf7ad0391f Mon Sep 17 00:00:00 2001 From: DKWoods Date: Wed, 18 May 2016 21:00:21 -0700 Subject: [PATCH 09/14] acpt: add scenarios for add/remove tab stop --- features/steps/tabstops.py | 33 ++++++++++++++++++++++++++++++++ features/tab-access-tabs.feature | 28 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/features/steps/tabstops.py b/features/steps/tabstops.py index e69cc598d..65e38bab7 100644 --- a/features/steps/tabstops.py +++ b/features/steps/tabstops.py @@ -8,6 +8,7 @@ from docx import Document from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER +from docx.shared import Inches from docx.text.tabstops import TabStop from helpers import test_docx @@ -49,6 +50,12 @@ def given_a_tab_stop_having_leader_leader(context, leader): # when ==================================================== +@when('I add a tab stop') +def when_I_add_a_tab_stop(context): + tab_stops = context.tab_stops + tab_stops.add_tab(Inches(1.75)) + + @when('I assign {member} to tab_stop.alignment') def when_I_assign_member_to_tab_stop_alignment(context, member): value = getattr(WD_TAB_ALIGNMENT, member) @@ -66,6 +73,18 @@ def when_I_assign_value_to_tab_stop_value(context, value): context.tab_stop.position = int(value) +@when('I call tab_stops.clear_all()') +def when_I_call_tab_stops_clear_all(context): + tab_stops = context.tab_stops + tab_stops.clear_all() + + +@when('I remove a tab stop') +def when_I_remove_a_tab_stop(context): + tab_stops = context.tab_stops + del tab_stops[1] + + # then ===================================================== @then('I can access a tab stop by index') @@ -107,3 +126,17 @@ def then_tab_stop_leader_is_leader(context, leader): def then_tab_stop_position_is_position(context, position): tab_stop = context.tab_stop assert tab_stop.position == int(position) + + +@then('the removed tab stop is no longer present in tab_stops') +def then_the_removed_tab_stop_is_no_longer_present_in_tab_stops(context): + tab_stops = context.tab_stops + assert tab_stops[0].position == Inches(1) + assert tab_stops[1].position == Inches(3) + + +@then('the tab stops are sequenced in position order') +def then_the_tab_stops_are_sequenced_in_position_order(context): + tab_stops = context.tab_stops + for idx in range(len(tab_stops) - 1): + assert tab_stops[idx].position < tab_stops[idx+1].position diff --git a/features/tab-access-tabs.feature b/features/tab-access-tabs.feature index c517ed012..390d22dbc 100644 --- a/features/tab-access-tabs.feature +++ b/features/tab-access-tabs.feature @@ -18,3 +18,31 @@ Feature: Access TabStop objects Given a tab_stops having 3 tab stops Then I can iterate the TabStops object And I can access a tab stop by index + + + @wip + Scenario Outline: TabStops.add_tab_stop() + Given a tab_stops having tab stops + When I add a tab stop + Then len(tab_stops) is + And the tab stops are sequenced in position order + + Examples: tab stop object counts + | count | new-count | + | 0 | 1 | + | 3 | 4 | + + + @wip + Scenario: TabStops.__delitem__() + Given a tab_stops having 3 tab stops + When I remove a tab stop + Then len(tab_stops) is 2 + And the removed tab stop is no longer present in tab_stops + + + @wip + Scenario: TabStops.clear_all() + Given a tab_stops having 3 tab stops + When I call tab_stops.clear_all() + Then len(tab_stops) is 0 From fe8b5d75907a1bdc4ec061809f23ba8634aff80f Mon Sep 17 00:00:00 2001 From: DKWoods Date: Tue, 24 May 2016 19:12:21 -0700 Subject: [PATCH 10/14] tab: add TabStops.add_tab_stop() --- docx/oxml/text/parfmt.py | 13 +++++++++++++ docx/text/tabstops.py | 14 ++++++++++++++ features/steps/tabstops.py | 2 +- features/tab-access-tabs.feature | 1 - tests/text/test_tabstops.py | 27 +++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docx/oxml/text/parfmt.py b/docx/oxml/text/parfmt.py index 10529be7e..466b11b1b 100644 --- a/docx/oxml/text/parfmt.py +++ b/docx/oxml/text/parfmt.py @@ -333,3 +333,16 @@ class CT_TabStops(BaseOxmlElement): ```` element, container for a sorted sequence of tab stops. """ tab = OneOrMore('w:tab', successors=()) + + def insert_tab_in_order(self, pos, align, leader): + """ + Insert a newly created `w:tab` child element in *pos* order. + """ + new_tab = self._new_tab() + new_tab.pos, new_tab.val, new_tab.leader = pos, align, leader + for tab in self.tab_lst: + if new_tab.pos < tab.pos: + tab.addprevious(new_tab) + return new_tab + self.append(new_tab) + return new_tab diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 79d3c2eb7..fc238a1ed 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -9,6 +9,7 @@ ) from ..shared import ElementProxy +from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER class TabStops(ElementProxy): @@ -51,6 +52,19 @@ def __len__(self): return 0 return len(tabs.tab_lst) + def add_tab_stop(self, position, alignment=WD_TAB_ALIGNMENT.LEFT, + leader=WD_TAB_LEADER.SPACES): + """ + Add a new tab stop at *position*. Tab alignment defaults to left, but + may be specified by passing a member of the :ref:`WdTabAlignment` + enumeration as *alignment*. An optional leader character can be + specified by passing a member of the :ref:`WdTabLeader` enumeration + as *leader*. + """ + tabs = self._pPr.get_or_add_tabs() + tab = tabs.insert_tab_in_order(position, alignment, leader) + return TabStop(tab) + class TabStop(ElementProxy): """ diff --git a/features/steps/tabstops.py b/features/steps/tabstops.py index 65e38bab7..40eba1a16 100644 --- a/features/steps/tabstops.py +++ b/features/steps/tabstops.py @@ -53,7 +53,7 @@ def given_a_tab_stop_having_leader_leader(context, leader): @when('I add a tab stop') def when_I_add_a_tab_stop(context): tab_stops = context.tab_stops - tab_stops.add_tab(Inches(1.75)) + tab_stops.add_tab_stop(Inches(1.75)) @when('I assign {member} to tab_stop.alignment') diff --git a/features/tab-access-tabs.feature b/features/tab-access-tabs.feature index 390d22dbc..5d81ed145 100644 --- a/features/tab-access-tabs.feature +++ b/features/tab-access-tabs.feature @@ -20,7 +20,6 @@ Feature: Access TabStop objects And I can access a tab stop by index - @wip Scenario Outline: TabStops.add_tab_stop() Given a tab_stops having tab stops When I add a tab stop diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 3a03d6d8e..7e5cb7f13 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -143,8 +143,35 @@ def it_raises_on_indexed_access_when_empty(self): with pytest.raises(IndexError): tab_stops[0] + def it_can_add_a_tab_stop(self, add_tab_fixture): + tab_stops, position, kwargs, expected_xml = add_tab_fixture + tab_stops.add_tab_stop(position, **kwargs) + assert tab_stops._element.xml == expected_xml + # fixture -------------------------------------------------------- + @pytest.fixture(params=[ + ('w:pPr', Twips(42), {}, + 'w:pPr/w:tabs/w:tab{w:pos=42,w:val=left}'), + ('w:pPr', Twips(72), {'alignment': WD_TAB_ALIGNMENT.RIGHT}, + 'w:pPr/w:tabs/w:tab{w:pos=72,w:val=right}'), + ('w:pPr', Twips(24), + {'alignment': WD_TAB_ALIGNMENT.CENTER, + 'leader': WD_TAB_LEADER.DOTS}, + 'w:pPr/w:tabs/w:tab{w:pos=24,w:val=center,w:leader=dot}'), + ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(72), {}, + 'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=72,w:val=left})'), + ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(24), {}, + 'w:pPr/w:tabs/(w:tab{w:pos=24,w:val=left},w:tab{w:pos=42})'), + ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(42), {}, + 'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=42,w:val=left})'), + ]) + def add_tab_fixture(self, request): + pPr_cxml, position, kwargs, expected_cxml = request.param + tab_stops = TabStops(element(pPr_cxml)) + expected_xml = xml(expected_cxml) + return tab_stops, position, kwargs, expected_xml + @pytest.fixture(params=[ ('w:pPr/w:tabs/w:tab{w:pos=0}', 0), ('w:pPr/w:tabs/(w:tab{w:pos=1},w:tab{w:pos=2},w:tab{w:pos=3})', 1), From 435f6d05feeed70a8953f7c1a4af175484f41184 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Wed, 25 May 2016 15:08:52 -0700 Subject: [PATCH 11/14] tab: add TabStops.__delitem__() --- docx/text/tabstops.py | 13 ++++++++++++ features/tab-access-tabs.feature | 1 - tests/text/test_tabstops.py | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index fc238a1ed..ae05d8227 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -26,6 +26,19 @@ def __init__(self, element): super(TabStops, self).__init__(element, None) self._pPr = element + def __delitem__(self, idx): + """ + Remove the tab at offset *idx* in this sequence. + """ + tabs = self._pPr.tabs + try: + tabs.remove(tabs[idx]) + except (AttributeError, IndexError): + raise IndexError('tab index out of range') + + if len(tabs) == 0: + self._pPr.remove(tabs) + def __getitem__(self, idx): """ Enables list-style access by index. diff --git a/features/tab-access-tabs.feature b/features/tab-access-tabs.feature index 5d81ed145..8a8c60dcf 100644 --- a/features/tab-access-tabs.feature +++ b/features/tab-access-tabs.feature @@ -32,7 +32,6 @@ Feature: Access TabStop objects | 3 | 4 | - @wip Scenario: TabStops.__delitem__() Given a tab_stops having 3 tab stops When I remove a tab stop diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 7e5cb7f13..26b1fab5b 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -148,8 +148,42 @@ def it_can_add_a_tab_stop(self, add_tab_fixture): tab_stops.add_tab_stop(position, **kwargs) assert tab_stops._element.xml == expected_xml + def it_can_delete_a_tab_stop(self, del_fixture): + tab_stops, idx, expected_xml = del_fixture + del tab_stops[idx] + assert tab_stops._element.xml == expected_xml + + def it_raises_on_del_idx_invalid(self, del_raises_fixture): + tab_stops, idx = del_raises_fixture + with pytest.raises(IndexError) as exc: + del tab_stops[idx] + assert exc.value.args[0] == 'tab index out of range' + # fixture -------------------------------------------------------- + @pytest.fixture(params=[ + ('w:pPr/w:tabs/w:tab{w:pos=42}', 0, + 'w:pPr'), + ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 0, + 'w:pPr/w:tabs/w:tab{w:pos=42}'), + ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 1, + 'w:pPr/w:tabs/w:tab{w:pos=24}'), + ]) + def del_fixture(self, request): + pPr_cxml, idx, expected_cxml = request.param + tab_stops = TabStops(element(pPr_cxml)) + expected_xml = xml(expected_cxml) + return tab_stops, idx, expected_xml + + @pytest.fixture(params=[ + ('w:pPr', 0), + ('w:pPr/w:tabs/w:tab{w:pos=42}', 1), + ]) + def del_raises_fixture(self, request): + tab_stops_cxml, idx = request.param + tab_stops = TabStops(element(tab_stops_cxml)) + return tab_stops, idx + @pytest.fixture(params=[ ('w:pPr', Twips(42), {}, 'w:pPr/w:tabs/w:tab{w:pos=42,w:val=left}'), From d2a72cd849107142ae8e0175ce6e0c639aa4ad1c Mon Sep 17 00:00:00 2001 From: DKWoods Date: Wed, 25 May 2016 17:46:43 -0700 Subject: [PATCH 12/14] tab: add TabStops.clear_all() --- docx/text/tabstops.py | 6 ++++++ features/tab-access-tabs.feature | 1 - tests/text/test_tabstops.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index ae05d8227..4816baf94 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -78,6 +78,12 @@ def add_tab_stop(self, position, alignment=WD_TAB_ALIGNMENT.LEFT, tab = tabs.insert_tab_in_order(position, alignment, leader) return TabStop(tab) + def clear_all(self): + """ + Remove all custom tab stops. + """ + self._pPr._remove_tabs() + class TabStop(ElementProxy): """ diff --git a/features/tab-access-tabs.feature b/features/tab-access-tabs.feature index 8a8c60dcf..92adff4a2 100644 --- a/features/tab-access-tabs.feature +++ b/features/tab-access-tabs.feature @@ -39,7 +39,6 @@ Feature: Access TabStop objects And the removed tab stop is no longer present in tab_stops - @wip Scenario: TabStops.clear_all() Given a tab_stops having 3 tab stops When I call tab_stops.clear_all() diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 26b1fab5b..78382762b 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -159,8 +159,24 @@ def it_raises_on_del_idx_invalid(self, del_raises_fixture): del tab_stops[idx] assert exc.value.args[0] == 'tab index out of range' + def it_can_clear_all_its_tab_stops(self, clear_all_fixture): + tab_stops, expected_xml = clear_all_fixture + tab_stops.clear_all() + assert tab_stops._element.xml == expected_xml + # fixture -------------------------------------------------------- + @pytest.fixture(params=[ + 'w:pPr', + 'w:pPr/w:tabs/w:tab{w:pos=42}', + 'w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', + ]) + def clear_all_fixture(self, request): + pPr_cxml = request.param + tab_stops = TabStops(element(pPr_cxml)) + expected_xml = xml('w:pPr') + return tab_stops, expected_xml + @pytest.fixture(params=[ ('w:pPr/w:tabs/w:tab{w:pos=42}', 0, 'w:pPr'), From 358233da880137284c14517b41ee2dd715e24008 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 26 May 2016 14:01:55 -0500 Subject: [PATCH 13/14] acpt: Adjust tests for keeping tabs in position order when changing position values --- features/steps/tabstops.py | 16 +++++++++++++++- features/tab-tabstop-props.feature | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/features/steps/tabstops.py b/features/steps/tabstops.py index 40eba1a16..637a765ef 100644 --- a/features/steps/tabstops.py +++ b/features/steps/tabstops.py @@ -29,6 +29,7 @@ def given_a_tab_stop_inches_from_paragraph_left_edge(context, in_or_out): tab_idx = {'out': 0, 'in': 1}[in_or_out] document = Document(test_docx('tab-stops')) paragraph_format = document.paragraphs[2].paragraph_format + context.tab_stops = paragraph_format.tab_stops context.tab_stop = paragraph_format.tab_stops[tab_idx] @@ -69,10 +70,15 @@ def when_I_assign_member_to_tab_stop_leader(context, member): @when('I assign {value} to tab_stop.position') -def when_I_assign_value_to_tab_stop_value(context, value): +def when_I_assign_value_to_tab_stop_position(context, value): context.tab_stop.position = int(value) +@when('I change the tab at {index} position to {value}') +def when_I_change_the_tab_at_index_position_to_value(context, index, value): + context.tab_stops[int(index)].position = int(value) + + @when('I call tab_stops.clear_all()') def when_I_call_tab_stops_clear_all(context): tab_stops = context.tab_stops @@ -128,6 +134,14 @@ def then_tab_stop_position_is_position(context, position): assert tab_stop.position == int(position) +# Use this rather than the above method when you change the position, +# as setting position invalidates context.tab_stop +@then('tab_stops at position {index} is {value}') +def then_tab_stops_at_position_index_is_value(context, index, value): + tab_stops = context.tab_stops + assert tab_stops[int(index)].position == int(value) + + @then('the removed tab stop is no longer present in tab_stops') def then_the_removed_tab_stop_is_no_longer_present_in_tab_stops(context): tab_stops = context.tab_stops diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index 1aa1dc45f..724437b35 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -17,13 +17,24 @@ Feature: Tab stop properties Scenario Outline: Set tab stop position Given a tab stop 0.5 inches in from the paragraph left edge When I assign to tab_stop.position - Then tab_stop.position is + Then tab_stops at position 1 is Examples: tab stop positions | value | | 228600 | | -228600 | - + + + @wip + Scenario Outline: Maintain tab stop position order when changing position + Given a tab_stops having 3 tab stops + When I change the tab at position to + Then the tab stops are sequenced in position order + + Examples: tab stop positions + | index | value | + | 0 | 2285000 | + | 2 | 1371600 | Scenario Outline: Get tab stop alignment Given a tab stop having alignment From 409b2c18d376064c089a8bad6772db2bc2437a17 Mon Sep 17 00:00:00 2001 From: DKWoods Date: Thu, 26 May 2016 14:06:55 -0500 Subject: [PATCH 14/14] tabs: when changing TabStop.position, keep TabStops in position order --- docx/text/tabstops.py | 5 +++- features/tab-tabstop-props.feature | 1 - tests/text/test_tabstops.py | 43 ++++++++++++++++++++---------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/docx/text/tabstops.py b/docx/text/tabstops.py index 4816baf94..29d8aeb66 100644 --- a/docx/text/tabstops.py +++ b/docx/text/tabstops.py @@ -132,4 +132,7 @@ def position(self): @position.setter def position(self, value): - self._tab.pos = value + if self._tab.pos != value: + self._tab.getparent().insert_tab_in_order(value, self._tab.val, + self._tab.leader) + self._tab.getparent().remove(self._element) diff --git a/features/tab-tabstop-props.feature b/features/tab-tabstop-props.feature index 724437b35..66aa89931 100644 --- a/features/tab-tabstop-props.feature +++ b/features/tab-tabstop-props.feature @@ -25,7 +25,6 @@ Feature: Tab stop properties | -228600 | - @wip Scenario Outline: Maintain tab stop position order when changing position Given a tab_stops having 3 tab stops When I change the tab at position to diff --git a/tests/text/test_tabstops.py b/tests/text/test_tabstops.py index 78382762b..326269d41 100644 --- a/tests/text/test_tabstops.py +++ b/tests/text/test_tabstops.py @@ -25,10 +25,8 @@ def it_knows_its_position(self, position_get_fixture): tab_stop, expected_value = position_get_fixture assert tab_stop.position == expected_value - def it_can_change_its_position(self, position_set_fixture): - tab_stop, value, expected_xml = position_set_fixture - tab_stop.position = value - assert tab_stop._element.xml == expected_xml + # NOTE: Changing a tab's position invalidates an individual tab, + # so is tested at the TabStops level rather than here. def it_knows_its_alignment(self, alignment_get_fixture): tab_stop, expected_value = alignment_get_fixture @@ -104,16 +102,6 @@ def position_get_fixture(self, request): tab_stop = TabStop(element('w:tab{w:pos=720}')) return tab_stop, Twips(720) - @pytest.fixture(params=[ - ('w:tab{w:pos=360}', Twips(720), 'w:tab{w:pos=720}'), - ('w:tab{w:pos=360}', Twips(-720), 'w:tab{w:pos=-720}') - ]) - def position_set_fixture(self, request): - tab_stop_cxml, value, expected_cxml = request.param - tab_stop = TabStop(element(tab_stop_cxml)) - expected_xml = xml(expected_cxml) - return tab_stop, value, expected_xml - class DescribeTabStops(object): @@ -164,8 +152,35 @@ def it_can_clear_all_its_tab_stops(self, clear_all_fixture): tab_stops.clear_all() assert tab_stops._element.xml == expected_xml + def it_can_change_tab_positions(self, position_set_fixture): + tab_stops, index, value, expected_xml = position_set_fixture + tab_stops[index].position = value + assert tab_stops._element.xml == expected_xml + # fixture -------------------------------------------------------- + @pytest.fixture(params=[ + ('w:pPr/w:tabs/w:tab{w:pos=360,w:val=left}', 0, Twips(720), + 'w:pPr/w:tabs/w:tab{w:pos=720,w:val=left}'), + ('w:pPr/w:tabs/w:tab{w:pos=360,w:val=left}', 0, Twips(-720), + 'w:pPr/w:tabs/w:tab{w:pos=-720,w:val=left}'), + ('w:pPr/w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,' + 'w:val=center})', 0, Twips(900), 'w:pPr/w:tabs/(w:tab{w:pos=720,' + 'w:val=center},w:tab{w:pos=900,w:val=left})'), + ('w:pPr/w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,' + 'w:val=center})', 1, Twips(180), 'w:pPr/w:tabs/(w:tab{w:pos=180,' + 'w:val=center},w:tab{w:pos=360,w:val=left})'), + ('w:pPr/w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,' + 'w:val=center},w:tab{w:pos=900,w:val=left})', 0, Twips(800), + 'w:pPr/w:tabs/(w:tab{w:pos=720,w:val=center},w:tab{w:pos=800,' + 'w:val=left},w:tab{w:pos=900,w:val=left})'), + ]) + def position_set_fixture(self, request): + pPr_cxml, index, value, expected_cxml = request.param + tab_stops = TabStops(element(pPr_cxml)) + expected_xml = xml(expected_cxml) + return tab_stops, index, value, expected_xml + @pytest.fixture(params=[ 'w:pPr', 'w:pPr/w:tabs/w:tab{w:pos=42}',