From 5be5651dd5a9167ad6340e6a86960ddbc860de08 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 24 Mar 2014 16:51:20 -0400 Subject: [PATCH 01/24] Added basic underline added basic underline feature, hopefully figure out how to expand it to allow for all underline options. i.e. 'double', 'dash', etc. --- docx/oxml/text.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index fbcf6974f..54ce791ff 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -342,6 +342,16 @@ def add_i(self): i = OxmlElement('w:i') self.insert(0, i) return i + + def add_u(self, type = 'dashed'): + """ + Return a newly added child element. + type can be: single, double, thick, + """ + u = OxmlElement('w:u') + u.set(qn('w:val'), type) + self.insert(0, u) + return u def add_iCs(self): """ @@ -572,6 +582,11 @@ def remove_i(self): i_lst = self.findall(qn('w:i')) for i in i_lst: self.remove(i) + + def remove_u(self): + u_lst = self.findall(qn('w:u')) + for u in u_lst: + self.remove(u) def remove_iCs(self): iCs_lst = self.findall(qn('w:iCs')) From 1424053d268552593d93ddf45790397c50d7b43e Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 24 Mar 2014 16:53:43 -0400 Subject: [PATCH 02/24] Added basic underline added basic underline support. i.e. `.underline=True` Hopefully add more support in the future for 'single', 'double', 'dash', etc. --- docx/text.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docx/text.py b/docx/text.py index 3d9c648ad..5c9e36dad 100644 --- a/docx/text.py +++ b/docx/text.py @@ -211,6 +211,14 @@ def italic(self): to appear in italics. """ return 'i' + + @boolproperty + def underline(self): + """ + Read/write tri-state value. When |True|, causes the text of the run + to appear underlined. + """ + return 'u' @boolproperty def imprint(self): From 3cf96b08e0958637c8e0181579b9242d8b040cde Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 24 Mar 2014 17:02:15 -0400 Subject: [PATCH 03/24] Added basic underline support --- docx/text.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docx/text.py b/docx/text.py index 5c9e36dad..ea3c11640 100644 --- a/docx/text.py +++ b/docx/text.py @@ -217,6 +217,10 @@ def underline(self): """ Read/write tri-state value. When |True|, causes the text of the run to appear underlined. + + TODO: Need to support: single, words, double, thick, dotted, dottedHeavy, dash, + dashedHeavy, dashLong, dashLongHeavy, dotDash, dashDotHeavy, dotDotDash, dashDotDotHeavy, + wave, wavyHeavy, wavyDouble, none """ return 'u' From 292f74db7d96a7a4b526a21353d6abc1de83e019 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 25 Mar 2014 10:19:52 -0400 Subject: [PATCH 04/24] added underline support for runs Added an attribute that can hold a underline style string. --- docx/text.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/docx/text.py b/docx/text.py index ea3c11640..36b9d0b7b 100644 --- a/docx/text.py +++ b/docx/text.py @@ -212,17 +212,41 @@ def italic(self): """ return 'i' - @boolproperty + @property def underline(self): """ - Read/write tri-state value. When |True|, causes the text of the run - to appear underlined. + String name of underline style to be applied. If "none" or False, then no underline + is applied. If True, single underline is applied. - TODO: Need to support: single, words, double, thick, dotted, dottedHeavy, dash, - dashedHeavy, dashLong, dashLongHeavy, dotDash, dashDotHeavy, dotDotDash, dashDotDotHeavy, - wave, wavyHeavy, wavyDouble, none + Valid Values: True, False, 'single', 'words', 'double', 'thick', 'dotted', 'dottedHeavy', + 'dash', 'dashedHeavy', 'dashLong', 'dashLongHeavy', 'dotDash', 'dashDotHeavy', + 'dotDotDash', 'dashDotDotHeavy', 'wave', 'wavyHeavy', 'wavyDouble', 'none' """ - return 'u' + u = self._r.get_or_add_rPr().underline + if u is None: + return None + return u.val + + @underline.setter + def underline(self, style): + # interperate style + if style is None: + style = 'none' + if type(style)==bool: + style = 'single' if style else 'none' + + validStyles = ['single', 'words', 'double', 'thick', 'dotted', 'dottedHeavy', 'dash', + 'dashedHeavy', 'dashLong', 'dashLongHeavy', 'dotDash', 'dashDotHeavy', 'dotDotDash', 'dashDotDotHeavy', + 'wave', 'wavyHeavy', 'wavyDouble', 'none'] + + if style not in validStyles: + raise ValueError('"'+style+'" is not valid. Needs to be: '+', '.join(validStyles)) + + u = self._r.get_or_add_rPr().underline + if u is None: + self._r.get_or_add_rPr().add_underline(style) + else: + u.val = style @boolproperty def imprint(self): From c8aeeb120f628271f0e7841c223140af44517353 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 25 Mar 2014 10:33:12 -0400 Subject: [PATCH 05/24] added underline support for runs It can handle all the underline types. --- docx/oxml/text.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 54ce791ff..c701c175e 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -287,6 +287,23 @@ class CT_RPr(OxmlBaseElement): """ ```` element, containing the properties for a run. """ + + @property + def underline(self): + """ + First ```` child element or None if none are present. + """ + return self.find(qn('w:u')) + + def add_underline(self, style): + """ + Return a newly added child element. + """ + u = OxmlElement('w:u') + u.set(qn('w:val'), style) + self.insert(0, u) + return u + def add_b(self): """ Return a newly added child element. @@ -342,16 +359,6 @@ def add_i(self): i = OxmlElement('w:i') self.insert(0, i) return i - - def add_u(self, type = 'dashed'): - """ - Return a newly added child element. - type can be: single, double, thick, - """ - u = OxmlElement('w:u') - u.set(qn('w:val'), type) - self.insert(0, u) - return u def add_iCs(self): """ @@ -582,11 +589,6 @@ def remove_i(self): i_lst = self.findall(qn('w:i')) for i in i_lst: self.remove(i) - - def remove_u(self): - u_lst = self.findall(qn('w:u')) - for u in u_lst: - self.remove(u) def remove_iCs(self): iCs_lst = self.findall(qn('w:iCs')) From cd3c86e95f4fd2262ff0d4c69c894fc3e1ea210e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 25 Mar 2014 14:11:49 -0400 Subject: [PATCH 06/24] cleaned Using already built functions instead of re-writing code... DRY! --- docx/oxml/text.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index c701c175e..f30de114c 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -299,8 +299,7 @@ def add_underline(self, style): """ Return a newly added child element. """ - u = OxmlElement('w:u') - u.set(qn('w:val'), style) + u = CT_String.new('w:u', style) self.insert(0, u) return u From 84bdbbefd29404a24713c05ea4c0dd9744ab2773 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 25 Mar 2014 21:03:22 -0400 Subject: [PATCH 07/24] Cleaned up underline, started adding table cell properties underline module almost done, need to work on tests. created class for `` to handle cell properties like `gridSpan` --- docx/enum/text.py | 47 +++++++++++++++++++++++++++++++++++++++++ docx/oxml/__init__.py | 3 ++- docx/oxml/table.py | 49 +++++++++++++++++++++++++++++++++++++++++++ docx/oxml/text.py | 2 +- docx/table.py | 19 ++++++++++++++++- docx/text.py | 27 +++++++++++------------- 6 files changed, 129 insertions(+), 18 deletions(-) diff --git a/docx/enum/text.py b/docx/enum/text.py index 9260e6fa0..668d76f04 100644 --- a/docx/enum/text.py +++ b/docx/enum/text.py @@ -25,3 +25,50 @@ class WD_BREAK_TYPE(object): TEXT_WRAPPING = 11 WD_BREAK = WD_BREAK_TYPE + +class WD_UNDERLINE_TYPE(object): + """ + Underline types, corresponding to WdUnderline enumeration + http://msdn.microsoft.com/en-us/library/office/ff822388(v=office.15).aspx + """ + DASH = 7 # Dashes. + DASH_HEAVY = 23 # Heavy dashes. + DASH_LONG = 39 # Long dashes. + LONG_HEAVY = 55 # Long heavy dashes. + DOT_DASH = 9 # Alternating dots and dashes. + DOT_DASH_HEAVY = 25 # Alternating heavy dots and heavy dashes. + DOT_DOT_DASH = 10 # An alternating dot-dot-dash pattern. + DOT_DOT_DASH_HEAVY = 26 # An alternating heavy dot-dot-dash pattern. + DOTTED = 4 # Dots. + DOTTED_HEAVY = 20 # Heavy dots. + DOUBLE = 3 # A double line. + NONE = 0 # No underline. + SINGLE = 1 # A single line. default. + THICK = 6 # A single thick line. + WAVY = 11 #A single wavy line. + WAVY_DOUBLE = 43 # A double wavy line. + WAVY_HEAVY = 27 # A heavy wavy line. + WORDS = 2 # Underline individual words only. + + stringDict={ + DASH:'dash', + DASH_HEAVY:'dashHeavy', + DASH_LONG:'dashLong', + LONG_HEAVY:'longHeavy', + DOT_DASH:'dotDash', + DOT_DASH_HEAVY:'dotDashHeavy', + DOT_DOT_DASH:'dotDotDash', + DOT_DOT_DASH_HEAVY:'dotDotDashHeavy', + DOTTED:'dotted', + DOTTED_HEAVY:'dottedHeavy', + DOUBLE:'double', + NONE:'none', + SINGLE:'single', + THICK:'thick', + WAVY:'wavy', + WAVY_DOUBLE:'wavyDouble', + WAVY_HEAVY:'wavyHeavy', + WORDS:'words', + } + +WD_UNDERLINE = WD_UNDERLINE_TYPE \ No newline at end of file diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 7f90cb134..d988daf91 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -45,13 +45,14 @@ register_custom_element_class('w:style', CT_Style) register_custom_element_class('w:styles', CT_Styles) -from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc +from docx.oxml.table import CT_Row, CT_Tbl, CT_TblGrid, CT_TblPr, CT_Tc, CT_TcPr register_custom_element_class('w:tbl', CT_Tbl) register_custom_element_class('w:tblGrid', CT_TblGrid) register_custom_element_class('w:tblPr', CT_TblPr) register_custom_element_class('w:tblStyle', CT_String) register_custom_element_class('w:tc', CT_Tc) register_custom_element_class('w:tr', CT_Row) +register_custom_element_class('w:tcPr', CT_TcPr) from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text register_custom_element_class('w:b', CT_OnOff) diff --git a/docx/oxml/table.py b/docx/oxml/table.py index 866f354e2..de5c42848 100644 --- a/docx/oxml/table.py +++ b/docx/oxml/table.py @@ -251,3 +251,52 @@ def tcPr(self): child element or |None| if not present. """ return self.find(qn('w:tcPr')) + + def get_or_add_tcPr(self): + """ + Return the tcPr child element, newly added if not present. + """ + tcPr = self.tcPr + if tcPr is None: + tcPr = self._add_tcPr() + return tcPr + + def _add_tcPr(self): + """ + Return a newly added tcPr child element. Assumes one is not present. + """ + tcPr = CT_TcPr.new() + self.append(tcPr) + return tcPr + +class CT_TcPr(OxmlBaseElement): + """ + ```` element, child of ````, holds child elements that + define cell properties such as gridSpan. + """ + @classmethod + def new(cls): + """ + Return a new ```` element. + """ + return OxmlElement('w:tcPr') + + def add_gridSpan(self, span): + """ + Return a new element newly inserted in sequence among + the existing child elements.gridSpan + """ + gridSpan = CT_String.new('w:gridSpan', str(span)) + self.append(gridSpan) # append or insert? + return gridSpan + + @property + def gridSpan(self): + """ + Optional child element, or |None| if not present. + """ + return self.find(qn('w:gridSpan')) + + + + \ No newline at end of file diff --git a/docx/oxml/text.py b/docx/oxml/text.py index f30de114c..d1fa968a9 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -299,7 +299,7 @@ def add_underline(self, style): """ Return a newly added child element. """ - u = CT_String.new('w:u', style) + u = CT_String.new('w:u', str(style)) self.insert(0, u) return u diff --git a/docx/table.py b/docx/table.py index 65cf1250a..1ba678b26 100644 --- a/docx/table.py +++ b/docx/table.py @@ -113,7 +113,24 @@ def text(self, text): p = tc.add_p() r = p.add_r() r.add_t(text) - + + @property + def colSpan(self): + """ + Set the column span + """ + span = self._tc.get_or_add_tcPr().gridSpan + if span is None: + return None + return span.val + + @colSpan.setter + def colSpan(self, val): + span = self._tc.get_or_add_tcPr().gridSpan + if span is None: + self._tc.get_or_add_tcPr().add_gridSpan(val) + else: + span.val = val class _Column(object): """ diff --git a/docx/text.py b/docx/text.py index 36b9d0b7b..3747479bb 100644 --- a/docx/text.py +++ b/docx/text.py @@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals -from docx.enum.text import WD_BREAK +from docx.enum.text import WD_BREAK, WD_UNDERLINE def boolproperty(f): @@ -215,12 +215,14 @@ def italic(self): @property def underline(self): """ - String name of underline style to be applied. If "none" or False, then no underline - is applied. If True, single underline is applied. + Underline text with style of *underline_type*. *underline_type* can + take the values `WD_UNDERLINE.SINGLE`, `WD_UNDERLINE.DOUBLE`, etc. + where `WD_UNDERLINE` is imported from `docx.enum.text`. - Valid Values: True, False, 'single', 'words', 'double', 'thick', 'dotted', 'dottedHeavy', - 'dash', 'dashedHeavy', 'dashLong', 'dashLongHeavy', 'dotDash', 'dashDotHeavy', - 'dotDotDash', 'dashDotDotHeavy', 'wave', 'wavyHeavy', 'wavyDouble', 'none' + Shorthand: + setting to True results in `WD_UNDERLINE.SINGLE`. + setting to False results in `WD_UNDERLINE.NONE`. + setting to None results in `WD_UNDERLINE.NONE`. """ u = self._r.get_or_add_rPr().underline if u is None: @@ -231,16 +233,11 @@ def underline(self): def underline(self, style): # interperate style if style is None: - style = 'none' + style = WD_UNDERLINE.NONE if type(style)==bool: - style = 'single' if style else 'none' - - validStyles = ['single', 'words', 'double', 'thick', 'dotted', 'dottedHeavy', 'dash', - 'dashedHeavy', 'dashLong', 'dashLongHeavy', 'dotDash', 'dashDotHeavy', 'dotDotDash', 'dashDotDotHeavy', - 'wave', 'wavyHeavy', 'wavyDouble', 'none'] - - if style not in validStyles: - raise ValueError('"'+style+'" is not valid. Needs to be: '+', '.join(validStyles)) + style = WD_UNDERLINE.SINGLE if style else WD_UNDERLINE.NONE + + style = WD_UNDERLINE.stringDict[style] u = self._r.get_or_add_rPr().underline if u is None: From 1f808b374c2a428663d57e9d6b1d99308b569072 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 08:19:12 -0400 Subject: [PATCH 08/24] test_text: added bool prop test for underline Added bool prop tests for underline. Moved from `WD_UNDERLINE` conversion to string into `docx\oxml\text.py` --- docx/oxml/text.py | 4 ++-- docx/text.py | 2 -- tests/oxml/unitdata/text.py | 4 +++- tests/test_text.py | 6 +++++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index d1fa968a9..4de6e777c 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -9,7 +9,7 @@ from docx.oxml.shared import ( CT_String, nsdecls, OxmlBaseElement, OxmlElement, oxml_fromstring, qn ) - +from docx.enum.text import WD_UNDERLINE class CT_Br(OxmlBaseElement): """ @@ -299,7 +299,7 @@ def add_underline(self, style): """ Return a newly added child element. """ - u = CT_String.new('w:u', str(style)) + u = CT_String.new('w:u', WD_UNDERLINE.stringDict[style])) self.insert(0, u) return u diff --git a/docx/text.py b/docx/text.py index 3747479bb..f832ac503 100644 --- a/docx/text.py +++ b/docx/text.py @@ -236,8 +236,6 @@ def underline(self, style): style = WD_UNDERLINE.NONE if type(style)==bool: style = WD_UNDERLINE.SINGLE if style else WD_UNDERLINE.NONE - - style = WD_UNDERLINE.stringDict[style] u = self._r.get_or_add_rPr().underline if u is None: diff --git a/tests/oxml/unitdata/text.py b/tests/oxml/unitdata/text.py index dd4b9243f..5915d2d16 100644 --- a/tests/oxml/unitdata/text.py +++ b/tests/oxml/unitdata/text.py @@ -136,7 +136,9 @@ def an_emboss(): def an_i(): return CT_OnOffBuilder('w:i') - + +def an_underline(): + return CT_OnOffBuilder('w:u') def an_iCs(): return CT_OnOffBuilder('w:iCs') diff --git a/tests/test_text.py b/tests/test_text.py index bb9f0ccc3..09d468201 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -20,7 +20,7 @@ a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, - an_r, an_rPr, an_rtl + an_r, an_rPr, an_rtl, an_underline ) from .unitutil import class_mock, instance_mock @@ -186,6 +186,7 @@ def add_text_fixture(self, request, run, Text_): ('all_caps', True), ('all_caps', False), ('all_caps', None), ('bold', True), ('bold', False), ('bold', None), ('italic', True), ('italic', False), ('italic', None), + ('underline', True), ('underline', False), ('underline', None), ('complex_script', True), ('complex_script', False), ('complex_script', None), ('cs_bold', True), ('cs_bold', False), ('cs_bold', None), @@ -231,6 +232,7 @@ def bool_prop_get_fixture(self, request): 'spec_vanish': a_specVanish, 'strike': a_strike, 'web_hidden': a_webHidden, + 'underline': an_underline, }[bool_prop_name] r_bldr = an_r().with_nsdecls() if expected_state is not None: @@ -247,6 +249,7 @@ def bool_prop_get_fixture(self, request): ('all_caps', True), ('all_caps', False), ('all_caps', None), ('bold', True), ('bold', False), ('bold', None), ('italic', True), ('italic', False), ('italic', None), + ('underline', True), ('underline', False), ('underline', None), ('complex_script', True), ('complex_script', False), ('complex_script', None), ('cs_bold', True), ('cs_bold', False), ('cs_bold', None), @@ -292,6 +295,7 @@ def bool_prop_set_fixture(self, request): 'spec_vanish': a_specVanish, 'strike': a_strike, 'web_hidden': a_webHidden, + 'underline': an_underline, }[bool_prop_name] # run -------------------------- r = an_r().with_nsdecls().element From f414e48397c30b66e91dc538d5db6bd5a515cc3d Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 08:26:23 -0400 Subject: [PATCH 09/24] hot fix parentheses error --- docx/oxml/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 4de6e777c..8802d4db6 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -299,7 +299,7 @@ def add_underline(self, style): """ Return a newly added child element. """ - u = CT_String.new('w:u', WD_UNDERLINE.stringDict[style])) + u = CT_String.new('w:u', WD_UNDERLINE.stringDict[style]) self.insert(0, u) return u From c4296730a1a12462751511f7f3b46e16fcc6519e Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 08:51:16 -0400 Subject: [PATCH 10/24] test_text: fixed some errors --- docx/oxml/__init__.py | 1 + docx/oxml/table.py | 16 ++++++++++++++++ tests/test_text.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index d988daf91..4e3b15308 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -63,6 +63,7 @@ register_custom_element_class('w:dstrike', CT_OnOff) register_custom_element_class('w:emboss', CT_OnOff) register_custom_element_class('w:i', CT_OnOff) +register_custom_element_class('w:u', CT_String) register_custom_element_class('w:iCs', CT_OnOff) register_custom_element_class('w:imprint', CT_OnOff) register_custom_element_class('w:noProof', CT_OnOff) diff --git a/docx/oxml/table.py b/docx/oxml/table.py index de5c42848..7e1301e90 100644 --- a/docx/oxml/table.py +++ b/docx/oxml/table.py @@ -297,6 +297,22 @@ def gridSpan(self): """ return self.find(qn('w:gridSpan')) + def add_gridSpan(self, span): + """ + Return a new element newly inserted in sequence among + the existing child elements.gridSpan + """ + gridSpan = CT_String.new('w:gridSpan', str(span)) + self.append(gridSpan) # append or insert? + return gridSpan + + @property + def gridSpan(self): + """ + Optional child element, or |None| if not present. + """ + return self.find(qn('w:gridSpan')) + \ No newline at end of file diff --git a/tests/test_text.py b/tests/test_text.py index 09d468201..945c844d4 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -8,7 +8,7 @@ absolute_import, division, print_function, unicode_literals ) -from docx.enum.text import WD_BREAK +from docx.enum.text import WD_BREAK, WD_UNDERLINE from docx.oxml.text import CT_P, CT_R from docx.text import Paragraph, Run From 9d7af1b35472ca807cd2da6841591843393c28db Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 14:29:21 -0400 Subject: [PATCH 11/24] table: cell: added Can now set column spans and cell shading properties. --- docx/oxml/__init__.py | 4 +- docx/oxml/shared.py | 89 +++++++++++++++++++++++++++++++++++++++++++ docx/oxml/table.py | 20 +++++----- docx/table.py | 46 +++++++++++++++++++++- tests/test_text.py | 2 +- 5 files changed, 148 insertions(+), 13 deletions(-) diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index 4e3b15308..bd90a423e 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -12,7 +12,7 @@ # custom element class mappings # =========================================================================== -from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String +from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String, CT_Shading from docx.oxml.shape import ( CT_Blip, CT_BlipFillProperties, CT_GraphicalObject, @@ -53,6 +53,8 @@ register_custom_element_class('w:tc', CT_Tc) register_custom_element_class('w:tr', CT_Row) register_custom_element_class('w:tcPr', CT_TcPr) +register_custom_element_class('w:gridSpan', CT_DecimalNumber) +register_custom_element_class('w:shd', CT_Shading) from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text register_custom_element_class('w:b', CT_OnOff) diff --git a/docx/oxml/shared.py b/docx/oxml/shared.py index 8177b79b2..45717db1a 100644 --- a/docx/oxml/shared.py +++ b/docx/oxml/shared.py @@ -340,3 +340,92 @@ def val(self): @val.setter def val(self, val): return self.set(qn('w:val'), val) + + +class CT_Shading(OxmlBaseElement): + @classmethod + def new(cls, nsptagname, valDict): + """ + Return a new ``CT_Shading`` element with tagname *nsptagname* and + attributes set by key, value pairs in *valDict*. + """ + attrsDict = {} + for k, v in valDict.iteritems(): + attrsDict[qn('w:'+str(k))] = v + return OxmlElement(nsptagname, attrs=attrsDict) + + @property + def valDict(self): + valDict={} + for val in ['val','color', 'themeColor','themeTint', 'themeShade', + 'fill', 'themeFill', 'themeFillTint', 'themeFillSHade']: + valDict[val] = self.get(qn('w:'+val)) + return valDict + @valDict.setter + def valDict(self, valDict): + for k, v in valDict: + self.set(qn('w:'+str(k)), v) + return + + @property + def val(self): + return self.get(qn('w:val')) + @val.setter + def val(self, val): + return self.set(qn('w:val'), val) + + @property + def color(self): + return self.get(qn('w:color')) + @color.setter + def color(self, color): + return self.set(qn('w:color'), color) + + @property + def themeColor(self): + return self.get(qn('w:themeColor')) + @themeColor.setter + def themeColor(self, themeColor): + return self.set(qn('w:themeColor'), themeColor) + + @property + def themeTint(self): + return self.get(qn('w:themeTint')) + @themeTint.setter + def themeTint(self, themeTint): + return self.set(qn('w:themeTint'), themeTint) + + @property + def themeShade(self): + return self.get(qn('w:themeShade')) + @themeShade.setter + def themeShade(self, themeShade): + return self.set(qn('w:themeShade'), themeShade) + + @property + def fill(self): + return self.get(qn('w:fill')) + @fill.setter + def fill(self, fill): + return self.set(qn('w:fill'), fill) + + @property + def themeFill(self): + return self.get(qn('w:themFill')) + @themeFill.setter + def themeFill(self, themeFill): + return self.set(qn('w:themeFill'), themeFill) + + @property + def themeFillTint(self): + return self.get(qn('w:themeFillTint')) + @themeFillTint.setter + def themeFillTint(self, themeFillTint): + return self.set(qn('w:themeFillTint'), themeFillTint) + + @property + def themeFillShade(self): + return self.get(qn('w:themeFillShade')) + @themeFillShade.setter + def themeFillShade(self, themeFillShade): + return self.set(qn('w:themeFillShade'), themeFillShade) diff --git a/docx/oxml/table.py b/docx/oxml/table.py index 7e1301e90..043fada40 100644 --- a/docx/oxml/table.py +++ b/docx/oxml/table.py @@ -9,7 +9,7 @@ from docx.oxml.shared import OxmlBaseElement, OxmlElement, qn from .exceptions import ValidationError -from .shared import CT_String +from .shared import CT_String, CT_DecimalNumber, CT_Shading from .text import CT_P @@ -286,7 +286,7 @@ def add_gridSpan(self, span): Return a new element newly inserted in sequence among the existing child elements.gridSpan """ - gridSpan = CT_String.new('w:gridSpan', str(span)) + gridSpan = CT_DecimalNumber.new('w:gridSpan', span) self.append(gridSpan) # append or insert? return gridSpan @@ -297,21 +297,21 @@ def gridSpan(self): """ return self.find(qn('w:gridSpan')) - def add_gridSpan(self, span): + def add_shading(self, argDict): """ - Return a new element newly inserted in sequence among + Return a new element newly inserted in sequence among the existing child elements.gridSpan """ - gridSpan = CT_String.new('w:gridSpan', str(span)) - self.append(gridSpan) # append or insert? - return gridSpan + shading = CT_Shading.new('w:shd', argDict) + self.append(shading) # append or insert? + return shading @property - def gridSpan(self): + def shading(self): """ - Optional child element, or |None| if not present. + Optional child element, or |None| if not present. """ - return self.find(qn('w:gridSpan')) + return self.find(qn('w:shd')) diff --git a/docx/table.py b/docx/table.py index 1ba678b26..3e6a862df 100644 --- a/docx/table.py +++ b/docx/table.py @@ -130,7 +130,51 @@ def colSpan(self, val): if span is None: self._tc.get_or_add_tcPr().add_gridSpan(val) else: - span.val = val + span.val = val + + @property + def shading(self): + """ + Set the cell shading. + """ + shading = self._tc.get_or_add_tcPr().shading + if shading is None: + return None + return shading.valDict + + @shading.setter + def shading(self, valDict): + shading = self._tc.get_or_add_tcPr().shading + if shading is None: + self._tc.get_or_add_tcPr().add_shading(valDict) + else: + shading.valDict = valDict + + def set_shading(self, val = None, color = None, themeColor = None, + themeTint = None, themeShade = None, fill = None, + themeFill = None, themeFillTint = None, + themeFillShade = None, argDict = None): + """ + Convienence method for setting individual values of the shading dict. + It only replaces values that are not `None`. + """ + if self.shading is None: + valDict = {} + + argDict = {'val':val,'color':color, 'themeColor':themeColor, + 'themeTint':themeTint, 'themeShade':themeShade, + 'fill':fill, 'themeFill':themeFill, + 'themeFillTint':themeFillTint, + 'themeFillShade':themeFillShade, + } + + for k, v in argDict.iteritems(): + if v is not None: + valDict[k]=v + + self.shading = valDict + + class _Column(object): """ diff --git a/tests/test_text.py b/tests/test_text.py index 945c844d4..9db57b285 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -269,7 +269,7 @@ def bool_prop_get_fixture(self, request): ('snap_to_grid', True), ('snap_to_grid', False), ('snap_to_grid', None), ('spec_vanish', True), ('spec_vanish', False), ('spec_vanish', None), - ('strike', True), ('strike', False), ('strike', None), + ('strike', True), ('strikregister_custom_element_class('w:tcPr', CT_TcPr)e', False), ('strike', None), ('web_hidden', True), ('web_hidden', False), ('web_hidden', None), ]) def bool_prop_set_fixture(self, request): From 09d88de970426deb25aab8e15acac37dbc05ce06 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 14:56:20 -0400 Subject: [PATCH 12/24] test_text: updated corrected errors in test_text. --- tests/oxml/unitdata/text.py | 4 ++-- tests/test_text.py | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tests/oxml/unitdata/text.py b/tests/oxml/unitdata/text.py index 5915d2d16..3c30fc4eb 100644 --- a/tests/oxml/unitdata/text.py +++ b/tests/oxml/unitdata/text.py @@ -137,8 +137,8 @@ def an_emboss(): def an_i(): return CT_OnOffBuilder('w:i') -def an_underline(): - return CT_OnOffBuilder('w:u') +def an_u(): + return CT_StringBuilder('w:u') def an_iCs(): return CT_OnOffBuilder('w:iCs') diff --git a/tests/test_text.py b/tests/test_text.py index 9db57b285..c66b5f7e2 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -20,7 +20,7 @@ a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, - an_r, an_rPr, an_rtl, an_underline + an_r, an_rPr, an_rtl, an_underline, an_u ) from .unitutil import class_mock, instance_mock @@ -145,6 +145,11 @@ def it_can_add_a_break(self, add_break_fixture): run, break_type, expected_xml = add_break_fixture run.add_break(break_type) assert run._r.xml == expected_xml + + def it_can_underline_text(self, add_underline_fixture): + run, underline_type, expected_xml = add_underline_fixture + run.add_break(underline_type) + assert run._r.xml == expected_xml def it_knows_the_text_it_contains(self, text_prop_fixture): run, expected_text = text_prop_fixture @@ -152,6 +157,37 @@ def it_knows_the_text_it_contains(self, text_prop_fixture): # fixtures ------------------------------------------------------- + @pytest.fixture(params=[ + 'dash','dashHeavy','dashLong','longHeavy','dotDash','dotDashHeavy', + 'dotDotDash','dotDotDashHeavy','dotted','dottedHeavy','double','none', + 'single','thick','wavy','wavyDouble','wavyHeavy','words', + ]) + def add_underline_fixture(self, request, run): + type_, underline_type = { + 'dash':WD_UNDERLINE.DASH, + 'dashHeavy':WD_UNDERLINE.DASH_HEAVY, + 'dashLong':WD_UNDERLINE.DASH_LONG, + 'longHeavy':WD_UNDERLINE.LONG_HEAVY, + 'dotDash':WD_UNDERLINE.DOT_DASH, + 'dotDashHeavy':WD_UNDERLINE.DOT_DASH_HEAVY, + 'dotDotDash':WD_UNDERLINE.DOT_DOT_DASH, + 'dotDotDashHeavy':WD_UNDERLINE.DOT_DOT_DASH_HEAVY, + 'dotted':WD_UNDERLINE.DOTTED, + 'dottedHeavy':WD_UNDERLINE.DOTTED_HEAVY, + 'double':WD_UNDERLINE.DOUBLE, + 'none':WD_UNDERLINE.NONE, + 'single':WD_UNDERLINE.SINGLE, + 'thick':WD_UNDERLINE.THICK, + 'wavy':WD_UNDERLINE.WAVY, + 'wavyDouble':WD_UNDERLINE.WAVY_DOUBLE, + 'wavyHeavy':WD_UNDERLINE.WAVY_HEAVY, + 'words':WD_UNDERLINE.WORDS, + }[request.param] + u_bldr = an_u() + expected_xml = an_r().with_nsdecls().with_child(u_bldr).xml() + return run, underline_type, expected_xml + + @pytest.fixture(params=[ 'line', 'page', 'column', 'clr_lt', 'clr_rt', 'clr_all' ]) @@ -186,7 +222,6 @@ def add_text_fixture(self, request, run, Text_): ('all_caps', True), ('all_caps', False), ('all_caps', None), ('bold', True), ('bold', False), ('bold', None), ('italic', True), ('italic', False), ('italic', None), - ('underline', True), ('underline', False), ('underline', None), ('complex_script', True), ('complex_script', False), ('complex_script', None), ('cs_bold', True), ('cs_bold', False), ('cs_bold', None), @@ -232,7 +267,6 @@ def bool_prop_get_fixture(self, request): 'spec_vanish': a_specVanish, 'strike': a_strike, 'web_hidden': a_webHidden, - 'underline': an_underline, }[bool_prop_name] r_bldr = an_r().with_nsdecls() if expected_state is not None: @@ -249,7 +283,6 @@ def bool_prop_get_fixture(self, request): ('all_caps', True), ('all_caps', False), ('all_caps', None), ('bold', True), ('bold', False), ('bold', None), ('italic', True), ('italic', False), ('italic', None), - ('underline', True), ('underline', False), ('underline', None), ('complex_script', True), ('complex_script', False), ('complex_script', None), ('cs_bold', True), ('cs_bold', False), ('cs_bold', None), @@ -269,7 +302,7 @@ def bool_prop_get_fixture(self, request): ('snap_to_grid', True), ('snap_to_grid', False), ('snap_to_grid', None), ('spec_vanish', True), ('spec_vanish', False), ('spec_vanish', None), - ('strike', True), ('strikregister_custom_element_class('w:tcPr', CT_TcPr)e', False), ('strike', None), + ('strike', True), ('strie', False), ('strike', None), ('web_hidden', True), ('web_hidden', False), ('web_hidden', None), ]) def bool_prop_set_fixture(self, request): @@ -295,7 +328,6 @@ def bool_prop_set_fixture(self, request): 'spec_vanish': a_specVanish, 'strike': a_strike, 'web_hidden': a_webHidden, - 'underline': an_underline, }[bool_prop_name] # run -------------------------- r = an_r().with_nsdecls().element From cfa90ee958512ae6d35034f44e83011c387b6128 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 15:07:28 -0400 Subject: [PATCH 13/24] test_text: hot fix --- tests/test_text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index c66b5f7e2..3585d31d8 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -20,7 +20,7 @@ a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, - an_r, an_rPr, an_rtl, an_underline, an_u + an_r, an_rPr, an_rtl, an_u ) from .unitutil import class_mock, instance_mock @@ -302,7 +302,7 @@ def bool_prop_get_fixture(self, request): ('snap_to_grid', True), ('snap_to_grid', False), ('snap_to_grid', None), ('spec_vanish', True), ('spec_vanish', False), ('spec_vanish', None), - ('strike', True), ('strie', False), ('strike', None), + ('strike', True), ('strike', False), ('strike', None), ('web_hidden', True), ('web_hidden', False), ('web_hidden', None), ]) def bool_prop_set_fixture(self, request): From 48e6bb2ba2a04e57712636b448cde15c026fba1b Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 26 Mar 2014 15:12:59 -0400 Subject: [PATCH 14/24] test_text: hot fix dumb error from copy/paste. --- tests/test_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_text.py b/tests/test_text.py index 3585d31d8..c7653280f 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -163,7 +163,7 @@ def it_knows_the_text_it_contains(self, text_prop_fixture): 'single','thick','wavy','wavyDouble','wavyHeavy','words', ]) def add_underline_fixture(self, request, run): - type_, underline_type = { + underline_type = { 'dash':WD_UNDERLINE.DASH, 'dashHeavy':WD_UNDERLINE.DASH_HEAVY, 'dashLong':WD_UNDERLINE.DASH_LONG, From cca918b27eca91f50f8645dedbb96cbba155654d Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 27 Mar 2014 16:54:41 -0400 Subject: [PATCH 15/24] test_text: Update, cleanup Cleaned up code to follow standards. --- docx/oxml/shared.py | 4 ++-- docx/oxml/table.py | 8 +++---- docx/oxml/text.py | 35 ++++++++++++++++++------------ docx/table.py | 2 +- docx/text.py | 14 ++++++------ tests/oxml/unitdata/shared.py | 12 +++++++++++ tests/oxml/unitdata/text.py | 6 +++--- tests/test_text.py | 40 +++++++++++++++++------------------ 8 files changed, 70 insertions(+), 51 deletions(-) diff --git a/docx/oxml/shared.py b/docx/oxml/shared.py index 45717db1a..588e85dad 100644 --- a/docx/oxml/shared.py +++ b/docx/oxml/shared.py @@ -342,11 +342,11 @@ def val(self, val): return self.set(qn('w:val'), val) -class CT_Shading(OxmlBaseElement): +class CT_Shd(OxmlBaseElement): @classmethod def new(cls, nsptagname, valDict): """ - Return a new ``CT_Shading`` element with tagname *nsptagname* and + Return a new ``CT_Shd`` element with tagname *nsptagname* and attributes set by key, value pairs in *valDict*. """ attrsDict = {} diff --git a/docx/oxml/table.py b/docx/oxml/table.py index 043fada40..c8f88dae5 100644 --- a/docx/oxml/table.py +++ b/docx/oxml/table.py @@ -9,7 +9,7 @@ from docx.oxml.shared import OxmlBaseElement, OxmlElement, qn from .exceptions import ValidationError -from .shared import CT_String, CT_DecimalNumber, CT_Shading +from .shared import CT_String, CT_DecimalNumber, CT_Shd from .text import CT_P @@ -287,7 +287,7 @@ def add_gridSpan(self, span): the existing child elements.gridSpan """ gridSpan = CT_DecimalNumber.new('w:gridSpan', span) - self.append(gridSpan) # append or insert? + self.insert(0, gridSpan) return gridSpan @property @@ -302,8 +302,8 @@ def add_shading(self, argDict): Return a new element newly inserted in sequence among the existing child elements.gridSpan """ - shading = CT_Shading.new('w:shd', argDict) - self.append(shading) # append or insert? + shading = CT_Shd.new('w:shd', argDict) + self.insert(0, shading) return shading @property diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 8802d4db6..192610dbd 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -288,20 +288,7 @@ class CT_RPr(OxmlBaseElement): ```` element, containing the properties for a run. """ - @property - def underline(self): - """ - First ```` child element or None if none are present. - """ - return self.find(qn('w:u')) - - def add_underline(self, style): - """ - Return a newly added child element. - """ - u = CT_String.new('w:u', WD_UNDERLINE.stringDict[style]) - self.insert(0, u) - return u + def add_b(self): """ @@ -462,6 +449,14 @@ def add_webHidden(self): webHidden = OxmlElement('w:webHidden') self.insert(0, webHidden) return webHidden + + def add_underline(self, utype): + """ + Return a newly added child element. + """ + u = CT_String.new('w:u', WD_UNDERLINE.stringDict[utype]) + self.insert(0, u) + return u @property def b(self): @@ -653,6 +648,11 @@ def remove_webHidden(self): webHidden_lst = self.findall(qn('w:webHidden')) for webHidden in webHidden_lst: self.remove(webHidden) + + def remove_underline(self): + underline_lst = self.findall(qn('w:u')) + for underline in underline_lst: + self.remove(underline) @property def rtl(self): @@ -709,6 +709,13 @@ def webHidden(self): First ```` child element or None if none are present. """ return self.find(qn('w:webHidden')) + + @property + def underline(self): + """ + First ```` child element or None if none are present. + """ + return self.find(qn('w:u')) class CT_Text(OxmlBaseElement): diff --git a/docx/table.py b/docx/table.py index 3e6a862df..9c73f37e9 100644 --- a/docx/table.py +++ b/docx/table.py @@ -156,7 +156,7 @@ def set_shading(self, val = None, color = None, themeColor = None, themeFillShade = None, argDict = None): """ Convienence method for setting individual values of the shading dict. - It only replaces values that are not `None`. + It only replaces values that are not None. """ if self.shading is None: valDict = {} diff --git a/docx/text.py b/docx/text.py index f832ac503..84641ffa2 100644 --- a/docx/text.py +++ b/docx/text.py @@ -230,18 +230,18 @@ def underline(self): return u.val @underline.setter - def underline(self, style): + def underline(self, utype): # interperate style - if style is None: - style = WD_UNDERLINE.NONE - if type(style)==bool: - style = WD_UNDERLINE.SINGLE if style else WD_UNDERLINE.NONE + if utype is None: + utype = WD_UNDERLINE.NONE + if type(utype)==bool: + utype = WD_UNDERLINE.SINGLE if utype else WD_UNDERLINE.NONE u = self._r.get_or_add_rPr().underline if u is None: - self._r.get_or_add_rPr().add_underline(style) + self._r.get_or_add_rPr().add_underline(utype) else: - u.val = style + u.val = utype @boolproperty def imprint(self): diff --git a/tests/oxml/unitdata/shared.py b/tests/oxml/unitdata/shared.py index 4bee3ae0f..457370434 100644 --- a/tests/oxml/unitdata/shared.py +++ b/tests/oxml/unitdata/shared.py @@ -31,3 +31,15 @@ def __init__(self, tag): def with_val(self, value): self._set_xmlattr('w:val', str(value)) return self + +class CT_Underline(BaseBuilder): + __nspfxs__ = ('w',) + __attrs__ = ('w:val') + + def __init__(self, tag): + self.__tag__ = tag + super(CT_Underline, self).__init__() + + def with_val(self, value): + self._set_xmlattr('w:val', str(value)) + return self \ No newline at end of file diff --git a/tests/oxml/unitdata/text.py b/tests/oxml/unitdata/text.py index 3c30fc4eb..26b24628f 100644 --- a/tests/oxml/unitdata/text.py +++ b/tests/oxml/unitdata/text.py @@ -5,7 +5,7 @@ """ from ...unitdata import BaseBuilder -from .shared import CT_OnOffBuilder, CT_StringBuilder +from .shared import CT_OnOffBuilder, CT_StringBuilder, CT_Underline class CT_BrBuilder(BaseBuilder): @@ -137,8 +137,8 @@ def an_emboss(): def an_i(): return CT_OnOffBuilder('w:i') -def an_u(): - return CT_StringBuilder('w:u') +def a_u(): + return CT_Underline('w:u') def an_iCs(): return CT_OnOffBuilder('w:iCs') diff --git a/tests/test_text.py b/tests/test_text.py index c7653280f..1b45ef61d 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -20,7 +20,7 @@ a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, - an_r, an_rPr, an_rtl, an_u + an_r, an_rPr, an_rtl, a_u ) from .unitutil import class_mock, instance_mock @@ -164,26 +164,26 @@ def it_knows_the_text_it_contains(self, text_prop_fixture): ]) def add_underline_fixture(self, request, run): underline_type = { - 'dash':WD_UNDERLINE.DASH, - 'dashHeavy':WD_UNDERLINE.DASH_HEAVY, - 'dashLong':WD_UNDERLINE.DASH_LONG, - 'longHeavy':WD_UNDERLINE.LONG_HEAVY, - 'dotDash':WD_UNDERLINE.DOT_DASH, - 'dotDashHeavy':WD_UNDERLINE.DOT_DASH_HEAVY, - 'dotDotDash':WD_UNDERLINE.DOT_DOT_DASH, - 'dotDotDashHeavy':WD_UNDERLINE.DOT_DOT_DASH_HEAVY, - 'dotted':WD_UNDERLINE.DOTTED, - 'dottedHeavy':WD_UNDERLINE.DOTTED_HEAVY, - 'double':WD_UNDERLINE.DOUBLE, - 'none':WD_UNDERLINE.NONE, - 'single':WD_UNDERLINE.SINGLE, - 'thick':WD_UNDERLINE.THICK, - 'wavy':WD_UNDERLINE.WAVY, - 'wavyDouble':WD_UNDERLINE.WAVY_DOUBLE, - 'wavyHeavy':WD_UNDERLINE.WAVY_HEAVY, - 'words':WD_UNDERLINE.WORDS, + 'dash': WD_UNDERLINE.DASH, + 'dashHeavy': WD_UNDERLINE.DASH_HEAVY, + 'dashLong': WD_UNDERLINE.DASH_LONG, + 'longHeavy': WD_UNDERLINE.LONG_HEAVY, + 'dotDash': WD_UNDERLINE.DOT_DASH, + 'dotDashHeavy': WD_UNDERLINE.DOT_DASH_HEAVY, + 'dotDotDash': WD_UNDERLINE.DOT_DOT_DASH, + 'dotDotDashHeavy': WD_UNDERLINE.DOT_DOT_DASH_HEAVY, + 'dotted': WD_UNDERLINE.DOTTED, + 'dottedHeavy': WD_UNDERLINE.DOTTED_HEAVY, + 'double': WD_UNDERLINE.DOUBLE, + 'none': WD_UNDERLINE.NONE, + 'single': WD_UNDERLINE.SINGLE, + 'thick': WD_UNDERLINE.THICK, + 'wavy': WD_UNDERLINE.WAVY, + 'wavyDouble': WD_UNDERLINE.WAVY_DOUBLE, + 'wavyHeavy': WD_UNDERLINE.WAVY_HEAVY, + 'words': WD_UNDERLINE.WORDS, }[request.param] - u_bldr = an_u() + u_bldr = a_u() expected_xml = an_r().with_nsdecls().with_child(u_bldr).xml() return run, underline_type, expected_xml From ca07d1156ec09a939a32cb1aaff86412f59ffd89 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 28 Mar 2014 17:03:24 -0400 Subject: [PATCH 16/24] text: added CT_jc and CT_textDirection added CT_jc and CT_textDirection functions and got them working. --- docx/oxml/__init__.py | 4 +- docx/oxml/shared.py | 109 ++++++++++++++++++++++++++++++ docx/oxml/text.py | 149 +++++++++++++++++++++++++++++++++++++++++- docx/text.py | 12 ++++ 4 files changed, 270 insertions(+), 4 deletions(-) diff --git a/docx/oxml/__init__.py b/docx/oxml/__init__.py index bd90a423e..7aa327f6e 100644 --- a/docx/oxml/__init__.py +++ b/docx/oxml/__init__.py @@ -12,7 +12,7 @@ # custom element class mappings # =========================================================================== -from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String, CT_Shading +from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String, CT_Shd from docx.oxml.shape import ( CT_Blip, CT_BlipFillProperties, CT_GraphicalObject, @@ -54,7 +54,7 @@ register_custom_element_class('w:tr', CT_Row) register_custom_element_class('w:tcPr', CT_TcPr) register_custom_element_class('w:gridSpan', CT_DecimalNumber) -register_custom_element_class('w:shd', CT_Shading) +register_custom_element_class('w:shd', CT_Shd) from docx.oxml.text import CT_Br, CT_P, CT_PPr, CT_R, CT_RPr, CT_Text register_custom_element_class('w:b', CT_OnOff) diff --git a/docx/oxml/shared.py b/docx/oxml/shared.py index 588e85dad..27ef8850d 100644 --- a/docx/oxml/shared.py +++ b/docx/oxml/shared.py @@ -341,6 +341,115 @@ def val(self): def val(self, val): return self.set(qn('w:val'), val) +class CT_Ind(OxmlBaseElement): + @classmethod + def new(cls, nsptagname, valDict): + """ + Return a new ``CT_Ind`` element with tagname *nsptagname* and + attributes set by key, value pairs in *valDict*. + """ + attrsDict = {} + for k, v in valDict.iteritems(): + attrsDict[qn('w:'+str(k))] = v + return OxmlElement(nsptagname, attrs=attrsDict) + + @property + def valDict(self): + valDict={} + for val in ['start','startChars','end','endChars','left','leftChars', + 'right','rightChars','hanging','hangingChars','firstLine', + 'firstLineChars']: + valDict[val] = self.get(qn('w:'+val)) + return valDict + @valDict.setter + def valDict(self, valDict): + for k, v in valDict: + self.set(qn('w:'+str(k)), v) + return + + @property + def start(self): + return self.get(qn('w:start')) + @start.setter + def start(self, start): + return self.set(qn('w:start'), start) + + @property + def startChars(self): + return self.get(qn('w:startChars')) + @startChars.setter + def startChars(self, startChars): + return self.set(qn('w:startChars'), startChars) + + @property + def end(self): + return self.get(qn('w:end')) + @end.setter + def end(self, end): + return self.set(qn('w:end'), end) + + @property + def endChars(self): + return self.get(qn('w:endChars')) + @endChars.setter + def endChars(self, endChars): + return self.set(qn('w:endChars'), endChars) + + @property + def left(self): + return self.get(qn('w:left')) + @left.setter + def left(self, left): + return self.set(qn('w:left'), left) + + @property + def leftChars(self): + return self.get(qn('w:leftChars')) + @leftChars.setter + def leftChars(self, leftChars): + return self.set(qn('w:leftChars'), leftChars) + + @property + def right(self): + return self.get(qn('w:right')) + @right.setter + def right(self, right): + return self.set(qn('w:right'), right) + + @property + def rightChars(self): + return self.get(qn('w:rightChars')) + @rightChars.setter + def rightChars(self, rightChars): + return self.set(qn('w:rightChars'), rightChars) + + @property + def hanging(self): + return self.get(qn('w:hanging')) + @hanging.setter + def hanging(self, hanging): + return self.set(qn('w:hanging'), hanging) + + @property + def hangingChars(self): + return self.get(qn('w:hangingChars')) + @hangingChars.setter + def hangingChars(self, hangingChars): + return self.set(qn('w:hangingChars'), hangingChars) + + @property + def firstLine(self): + return self.get(qn('w:firstLine')) + @firstLine.setter + def firstLine(self, firstLine): + return self.set(qn('w:firstLine'), firstLine) + + @property + def firstLineChars(self): + return self.get(qn('w:firstLineChars')) + @firstLineChars.setter + def firstLineChars(self, firstLineChars): + return self.set(qn('w:firstLineChars'), firstLineChars) class CT_Shd(OxmlBaseElement): @classmethod diff --git a/docx/oxml/text.py b/docx/oxml/text.py index 192610dbd..6ff684fd3 100644 --- a/docx/oxml/text.py +++ b/docx/oxml/text.py @@ -7,7 +7,8 @@ from docx.oxml.parts.numbering import CT_NumPr from docx.oxml.shared import ( - CT_String, nsdecls, OxmlBaseElement, OxmlElement, oxml_fromstring, qn + CT_String, + nsdecls, OxmlBaseElement, OxmlElement, oxml_fromstring, qn ) from docx.enum.text import WD_UNDERLINE @@ -102,6 +103,46 @@ def style(self, style): """ pPr = self.get_or_add_pPr() pPr.style = style + + @property + def jc(self): + """ + String contained in w:val attribute of child, or + None if that element is not present. + """ + pPr = self.pPr + if pPr is None: + return None + return pPr.jc + + @jc.setter + def jc(self, jc): + """ + Set style of this element to *jc*. If *jc* is None, + remove the style element. + """ + pPr = self.get_or_add_pPr() + pPr.jc = jc + + @property + def textDirection(self): + """ + String contained in w:val attribute of child, + or None if that element is not present. + """ + pPr = self.pPr + if pPr is None: + return None + return pPr.textdirection + + @textDirection.setter + def textDirection(self, textdirection): + """ + Set style of this element to *style*. If *style* is None, + remove the style element. + """ + pPr = self.get_or_add_pPr() + pPr.textdirection = textdirection def _add_pPr(self): """ @@ -133,6 +174,15 @@ def get_or_add_pStyle(self): if pStyle is None: pStyle = self._add_pStyle() return pStyle + + def get_or_add_textDirectionElement(self): + """ + Return the textDirection child element, newly added if not present. + """ + textDirection = self.textDirection + if textDirection is None: + textDirection = self._add_textDirection() + return textDirection @staticmethod def new(): @@ -186,6 +236,80 @@ def style(self, style): self._add_pStyle(style) else: self.pStyle.val = style + + @property + def jcElement(self): + """ + ```` child element or None if not present. + """ + return self.find(qn('w:jc')) + + def remove_jcElement(self): + jcElement = self.jcElement + if jcElement is not None: + self.remove(jcElement) + + @property + def jc(self): + """ + String contained in child, or None if that element is + not present. + """ + jc = self.jcElement + if jc is None: + return None + return jc.get(qn('w:val')) + + @jc.setter + def jc(self, jc): + """ + Set val attribute of child element to *jc*, adding a + new element if necessary. If *jc* is |None|, remove the + element if present. + """ + if jc is None: + self.remove_jcDirection() + elif self.jcElement is None: + self._add_jcElement(jc) + else: + self.jcElement.val = jc + + @property + def textDirectionElement(self): + """ + ```` child element or None if not present. + """ + return self.find(qn('w:textDirection')) + + def remove_textDirectionElement(self): + textDirectionElement = self.textDirectionElement + if textDirectionElement is not None: + self.remove(textDirectionElement) + + @property + def textDirection(self): + """ + String contained in child, or None if that element is + not present. + """ + textDirection = self.textDirectionElement + if textDirection is None: + return None + return textDirection.get(qn('w:val')) + + @textDirection.setter + def textDirection(self, textDirection): + """ + Set val attribute of child element to + *textDirection*, adding a new element if necessary. If *textDirection* + is |None|, remove the element if present. + """ + if textDirection is None: + self.remove_textDirectionElement() + elif self.textDirectionElement is None: + self._add_textDirectionElement(textDirection) + else: + self.textDirectionElement.val = textDirection def _add_numPr(self): numPr = CT_NumPr.new() @@ -194,6 +318,14 @@ def _add_numPr(self): def _add_pStyle(self, style): pStyle = CT_String.new_pStyle(style) return self._insert_pStyle(pStyle) + + def _add_jcElement(self, jc): + jcElement = CT_String.new('w:jc', jc) + return self._insert_jcElement(jcElement) + + def _add_textDirectionElement(self, textDirection): + textDirectionElement = CT_String.new('w:textDirection', textDirection) + return self._insert_textDirectionElement(textDirectionElement) def _insert_numPr(self, numPr): return self.insert_element_before( @@ -210,7 +342,20 @@ def _insert_numPr(self, numPr): def _insert_pStyle(self, pStyle): self.insert(0, pStyle) return pStyle - + + def _insert_jcElement(self, jc): + return self.insert_element_before( + jc, 'w:textDirection', 'w:textAlignment', 'w:textboxTightWrap', + 'w:outlineLvl', 'w:divId', 'w:cnfStyle', 'w:rPr', 'w:sectPr', + 'w:pPrChange' + ) + + def _insert_textDirectionElement(self, textDirection): + return self.insert_element_before( + textDirection, 'w:textAlignment', 'w:textboxTightWrap', + 'w:outlineLvl', 'w:divId', 'w:cnfStyle', 'w:rPr', 'w:sectPr', + 'w:pPrChange' + ) class CT_R(OxmlBaseElement): """ diff --git a/docx/text.py b/docx/text.py index 84641ffa2..b84b5d6c1 100644 --- a/docx/text.py +++ b/docx/text.py @@ -87,6 +87,18 @@ def style(self): @style.setter def style(self, style): self._p.style = None if style == 'Normal' else style + + @property + def jc(self): + """ + Justification for this paragraph. Read/Write. + """ + jc = self._p.jc + return jc if jc is not None else 'start' + + @jc.setter + def jc(self, jc): + self._p.jc = jc @property def text(self): From 315d9713470014b7f6a692fa0d83bb4105195886 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 31 Mar 2014 01:51:55 -0400 Subject: [PATCH 17/24] test_text: fixed --- tests/test_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_text.py b/tests/test_text.py index 1b45ef61d..787a99f09 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -148,7 +148,7 @@ def it_can_add_a_break(self, add_break_fixture): def it_can_underline_text(self, add_underline_fixture): run, underline_type, expected_xml = add_underline_fixture - run.add_break(underline_type) + run.underline(underline_type) assert run._r.xml == expected_xml def it_knows_the_text_it_contains(self, text_prop_fixture): From bbec429387fa40349822a9577a0e38758f599128 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 31 Mar 2014 01:57:04 -0400 Subject: [PATCH 18/24] test_text: hot fix --- tests/test_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_text.py b/tests/test_text.py index 787a99f09..f393a5a57 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -148,7 +148,7 @@ def it_can_add_a_break(self, add_break_fixture): def it_can_underline_text(self, add_underline_fixture): run, underline_type, expected_xml = add_underline_fixture - run.underline(underline_type) + run.underline = underline_type assert run._r.xml == expected_xml def it_knows_the_text_it_contains(self, text_prop_fixture): From f8d30ff5ce3f3adef47e66b9b78551842ef7b15d Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 19:47:17 -0400 Subject: [PATCH 19/24] playing with tests trying to get tests working locally. --- tests/test_text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index f393a5a57..fe92f02d7 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -16,13 +16,13 @@ from mock import call, Mock -from .oxml.unitdata.text import ( +from tests.oxml.unitdata.text import ( a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, an_r, an_rPr, an_rtl, a_u ) -from .unitutil import class_mock, instance_mock +from tests.unitutil import class_mock, instance_mock class DescribeParagraph(object): From 913d57d86aad4743b6dcc18a2582001a274e674c Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 19:48:49 -0400 Subject: [PATCH 20/24] Revert "playing with tests" This reverts commit f8d30ff5ce3f3adef47e66b9b78551842ef7b15d. --- tests/test_text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index fe92f02d7..f393a5a57 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -16,13 +16,13 @@ from mock import call, Mock -from tests.oxml.unitdata.text import ( +from .oxml.unitdata.text import ( a_b, a_bCs, a_br, a_caps, a_cs, a_dstrike, a_p, a_shadow, a_smallCaps, a_snapToGrid, a_specVanish, a_strike, a_t, a_vanish, a_webHidden, an_emboss, an_i, an_iCs, an_imprint, an_oMath, a_noProof, an_outline, an_r, an_rPr, an_rtl, a_u ) -from tests.unitutil import class_mock, instance_mock +from .unitutil import class_mock, instance_mock class DescribeParagraph(object): From 22ecc3fd51b328007ca9ab5fe1822f500f3ca5e5 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 19:58:51 -0400 Subject: [PATCH 21/24] test_text: trying to get it to work trying some edits to see if it will work --- tests/oxml/unitdata/shared.py | 12 ------------ tests/oxml/unitdata/text.py | 14 +++++++++++++- tests/test_text.py | 1 + 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/oxml/unitdata/shared.py b/tests/oxml/unitdata/shared.py index 457370434..ce78dec3b 100644 --- a/tests/oxml/unitdata/shared.py +++ b/tests/oxml/unitdata/shared.py @@ -28,18 +28,6 @@ def __init__(self, tag): self.__tag__ = tag super(CT_StringBuilder, self).__init__() - def with_val(self, value): - self._set_xmlattr('w:val', str(value)) - return self - -class CT_Underline(BaseBuilder): - __nspfxs__ = ('w',) - __attrs__ = ('w:val') - - def __init__(self, tag): - self.__tag__ = tag - super(CT_Underline, self).__init__() - def with_val(self, value): self._set_xmlattr('w:val', str(value)) return self \ No newline at end of file diff --git a/tests/oxml/unitdata/text.py b/tests/oxml/unitdata/text.py index 26b24628f..0313b06e1 100644 --- a/tests/oxml/unitdata/text.py +++ b/tests/oxml/unitdata/text.py @@ -5,7 +5,7 @@ """ from ...unitdata import BaseBuilder -from .shared import CT_OnOffBuilder, CT_StringBuilder, CT_Underline +from .shared import CT_OnOffBuilder, CT_StringBuilder class CT_BrBuilder(BaseBuilder): @@ -52,6 +52,18 @@ class CT_TextBuilder(BaseBuilder): def with_space(self, value): self._set_xmlattr('xml:space', str(value)) return self + +class CT_Underline(BaseBuilder): + __nspfxs__ = ('w',) + __attrs__ = ('w:val') + + def __init__(self, tag): + self.__tag__ = tag + super(CT_Underline, self).__init__() + + def with_val(self, value): + self._set_xmlattr('w:val', str(value)) + return self def a_b(): diff --git a/tests/test_text.py b/tests/test_text.py index f393a5a57..14474203e 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -184,6 +184,7 @@ def add_underline_fixture(self, request, run): 'words': WD_UNDERLINE.WORDS, }[request.param] u_bldr = a_u() + u_bldr.with_val(underline_type) expected_xml = an_r().with_nsdecls().with_child(u_bldr).xml() return run, underline_type, expected_xml From fb6cea09ca3b369707e505da2f39f06445485f7f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 20:11:38 -0400 Subject: [PATCH 22/24] test_text: another attempt another attempt at getting it to work. --- tests/test_text.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index 14474203e..87e19db5c 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -183,9 +183,17 @@ def add_underline_fixture(self, request, run): 'wavyHeavy': WD_UNDERLINE.WAVY_HEAVY, 'words': WD_UNDERLINE.WORDS, }[request.param] - u_bldr = a_u() - u_bldr.with_val(underline_type) - expected_xml = an_r().with_nsdecls().with_child(u_bldr).xml() + + r_bldr = an_r().with_nsdecls() + child_bldr = a_u() + child_bldr.with_val(underline_type) + + rPr_bldr = an_rPr().with_child(child_bldr) + r_bldr.with_child(rPr_bldr) + r = r_bldr.element + run = Run(r) + + expected_xml = r_bldr.xml() return run, underline_type, expected_xml From e91ecf9b159954dcfc0111d775113515ed3dc216 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 20:23:33 -0400 Subject: [PATCH 23/24] test_text: trying again I don't really know what I am doing... --- tests/test_text.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index 87e19db5c..b6de19e5d 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -184,16 +184,18 @@ def add_underline_fixture(self, request, run): 'words': WD_UNDERLINE.WORDS, }[request.param] + # run + r = an_r().with_nsdecls().element + run = Run(r) + + # expected xml r_bldr = an_r().with_nsdecls() child_bldr = a_u() child_bldr.with_val(underline_type) - rPr_bldr = an_rPr().with_child(child_bldr) r_bldr.with_child(rPr_bldr) - r = r_bldr.element - run = Run(r) - expected_xml = r_bldr.xml() + return run, underline_type, expected_xml From 7089564e554f224b55cd443c9cb316382280a7a3 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Apr 2014 20:36:51 -0400 Subject: [PATCH 24/24] test_text: trying again This should work.... --- tests/test_text.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tests/test_text.py b/tests/test_text.py index b6de19e5d..8b7d1b8fe 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -163,25 +163,26 @@ def it_knows_the_text_it_contains(self, text_prop_fixture): 'single','thick','wavy','wavyDouble','wavyHeavy','words', ]) def add_underline_fixture(self, request, run): - underline_type = { - 'dash': WD_UNDERLINE.DASH, - 'dashHeavy': WD_UNDERLINE.DASH_HEAVY, - 'dashLong': WD_UNDERLINE.DASH_LONG, - 'longHeavy': WD_UNDERLINE.LONG_HEAVY, - 'dotDash': WD_UNDERLINE.DOT_DASH, - 'dotDashHeavy': WD_UNDERLINE.DOT_DASH_HEAVY, - 'dotDotDash': WD_UNDERLINE.DOT_DOT_DASH, - 'dotDotDashHeavy': WD_UNDERLINE.DOT_DOT_DASH_HEAVY, - 'dotted': WD_UNDERLINE.DOTTED, - 'dottedHeavy': WD_UNDERLINE.DOTTED_HEAVY, - 'double': WD_UNDERLINE.DOUBLE, - 'none': WD_UNDERLINE.NONE, - 'single': WD_UNDERLINE.SINGLE, - 'thick': WD_UNDERLINE.THICK, - 'wavy': WD_UNDERLINE.WAVY, - 'wavyDouble': WD_UNDERLINE.WAVY_DOUBLE, - 'wavyHeavy': WD_UNDERLINE.WAVY_HEAVY, - 'words': WD_UNDERLINE.WORDS, + type_, underline_type = { + 'dash': ('dash', WD_UNDERLINE.DASH), + 'dashHeavy': ('dashHeavy', WD_UNDERLINE.DASH_HEAVY), + 'dashLong': ('dashLong', WD_UNDERLINE.DASH_LONG), + 'longHeavy': ('longHeavy', WD_UNDERLINE.LONG_HEAVY), + 'dotDash': ('dotDash', WD_UNDERLINE.DOT_DASH), + 'dotDashHeavy': ('dotDashHeavy', + WD_UNDERLINE.DOT_DASH_HEAVY), + 'dotDotDash': ('dotDotDash', WD_UNDERLINE.DOT_DOT_DASH), + 'dotDotDashHeavy': ('dotDotDashHeavy', WD_UNDERLINE.DOT_DOT_DASH_HEAVY), + 'dotted': ('dotted', WD_UNDERLINE.DOTTED), + 'dottedHeavy': ('dottedHeavy', WD_UNDERLINE.DOTTED_HEAVY), + 'double': ('double', WD_UNDERLINE.DOUBLE), + 'none': ('none', WD_UNDERLINE.NONE), + 'single': ('single', WD_UNDERLINE.SINGLE), + 'thick': ('thick', WD_UNDERLINE.THICK), + 'wavy': ('wavy', WD_UNDERLINE.WAVY), + 'wavyDouble': ('wavyDouble', WD_UNDERLINE.WAVY_DOUBLE), + 'wavyHeavy': ('wavyHeavy', WD_UNDERLINE.WAVY_HEAVY), + 'words': ('words', WD_UNDERLINE.WORDS), }[request.param] # run @@ -191,7 +192,7 @@ def add_underline_fixture(self, request, run): # expected xml r_bldr = an_r().with_nsdecls() child_bldr = a_u() - child_bldr.with_val(underline_type) + child_bldr.with_val(type_) rPr_bldr = an_rPr().with_child(child_bldr) r_bldr.with_child(rPr_bldr) expected_xml = r_bldr.xml()