77
88from pydocx .models import XmlModel , XmlCollection , XmlChild
99from pydocx .openxml .wordprocessing .bookmark import Bookmark
10- from pydocx .openxml .wordprocessing .br import Break
1110from pydocx .openxml .wordprocessing .deleted_run import DeletedRun
1211from pydocx .openxml .wordprocessing .hyperlink import Hyperlink
1312from pydocx .openxml .wordprocessing .inserted_run import InsertedRun
1817from pydocx .openxml .wordprocessing .smart_tag_run import SmartTagRun
1918from pydocx .openxml .wordprocessing .tab_char import TabChar
2019from pydocx .openxml .wordprocessing .text import Text
21- from pydocx .openxml .wordprocessing .table_cell import TableCell
2220from pydocx .util .memoize import memoized
2321
2422
@@ -38,10 +36,6 @@ class Paragraph(XmlModel):
3836 Bookmark
3937 )
4038
41- def __init__ (self , ** kwargs ):
42- super (Paragraph , self ).__init__ (** kwargs )
43- self ._effective_properties = None
44-
4539 @property
4640 def is_empty (self ):
4741 if not self .children :
@@ -52,20 +46,73 @@ def is_empty(self):
5246 if len (self .children ) == 1 :
5347 first_child = self .children [0 ]
5448 if isinstance (first_child , Bookmark ) and \
55- first_child .name in ('_GoBack' ,):
49+ first_child .name in ('_GoBack' ,):
5650 return True
5751 # We can have cases when only run properties are defined and no text
5852 elif not first_child .children :
5953 return True
6054 return False
6155
56+ def _get_properties_inherited_from_parent_table (self ):
57+ from pydocx .openxml .wordprocessing .table import Table
58+
59+ inherited_properties = {}
60+
61+ parent_table = self .get_first_ancestor (Table )
62+ if parent_table :
63+ style_stack = parent_table .get_style_chain_stack ()
64+ for style in reversed (list (style_stack )):
65+ if style .paragraph_properties :
66+ inherited_properties .update (
67+ dict (style .paragraph_properties .fields ),
68+ )
69+ return inherited_properties
70+
71+ def _get_inherited_properties_from_parent_style (self ):
72+ inherited_properties = {}
73+ style_stack = self .get_style_chain_stack ()
74+ for style in reversed (list (style_stack )):
75+ if style .paragraph_properties :
76+ inherited_properties .update (
77+ dict (style .paragraph_properties .fields ),
78+ )
79+ return inherited_properties
80+
81+ @property
82+ def inherited_properties (self ):
83+ properties = {}
84+
85+ if self .default_doc_styles and \
86+ getattr (self .default_doc_styles .paragraph , 'properties' ):
87+ properties .update (
88+ dict (self .default_doc_styles .paragraph .properties .fields ),
89+ )
90+ properties .update (
91+ self ._get_inherited_properties_from_parent_style (),
92+ )
93+ # Tables can also define custom paragraph pr
94+ properties .update (
95+ self ._get_properties_inherited_from_parent_table (),
96+ )
97+
98+ # TODO When enable this make sure that you check the paragraph margins logic
99+ # numbering_level = self.get_numbering_level()
100+ # if numbering_level and numbering_level.paragraph_properties:
101+ # properties.update(
102+ # dict(numbering_level.paragraph_properties.fields),
103+ # )
104+
105+ return ParagraphProperties (** properties )
106+
62107 @property
108+ @memoized
63109 def effective_properties (self ):
64- # TODO need to calculate effective properties like Run
65- if not self ._effective_properties :
66- properties = self .properties
67- self ._effective_properties = properties
68- return self ._effective_properties
110+ inherited_properties = self .inherited_properties
111+ effective_properties = {}
112+ effective_properties .update (dict (inherited_properties .fields ))
113+ if self .properties :
114+ effective_properties .update (dict (self .properties .fields ))
115+ return ParagraphProperties (** effective_properties )
69116
70117 @property
71118 def numbering_definition (self ):
@@ -76,12 +123,9 @@ def has_structured_document_parent(self):
76123 return self .has_ancestor (SdtBlock )
77124
78125 def get_style_chain_stack (self ):
79- if not self .properties :
80- return
81-
82- parent_style = self .properties .parent_style
83- if not parent_style :
84- return
126+ # Even if parent style is not defined we still need to check the default style
127+ # properties applied
128+ parent_style = getattr (self .properties , 'parent_style' , None )
85129
86130 # TODO the getattr is necessary because of footnotes. From the context
87131 # of a footnote, a paragraph's container is the footnote part, which
@@ -117,9 +161,9 @@ def get_numbering_definition(self):
117161 part = getattr (self .container , 'numbering_definitions_part' , None )
118162 if not part :
119163 return
120- if not self .effective_properties :
164+ if not self .properties :
121165 return
122- numbering_properties = self .effective_properties .numbering_properties
166+ numbering_properties = self .properties .numbering_properties
123167 if not numbering_properties :
124168 return
125169 return part .numbering .get_numbering_definition (
@@ -131,9 +175,9 @@ def get_numbering_level(self):
131175 numbering_definition = self .get_numbering_definition ()
132176 if not numbering_definition :
133177 return
134- if not self .effective_properties :
178+ if not self .properties :
135179 return
136- numbering_properties = self .effective_properties .numbering_properties
180+ numbering_properties = self .properties .numbering_properties
137181 if not numbering_properties :
138182 return
139183 return numbering_definition .get_level (
@@ -231,70 +275,26 @@ def get_spacing(self):
231275 """Get paragraph spacing according to:
232276 ECMA-376, 3rd Edition (June, 2011),
233277 Fundamentals and Markup Language Reference § 17.3.1.33.
234-
235- Note: Partial implementation for now.
236278 """
237279 results = {
238280 'line' : None ,
239281 'after' : None ,
240282 'before' : None ,
241- 'contextual_spacing' : False ,
242- 'parent_style' : None
283+ 'contextual_spacing' : bool ( self . effective_properties . contextual_spacing ) ,
284+ 'parent_style' : self . effective_properties . parent_style
243285 }
244286
245- # Get the paragraph_properties from the parent styles
246- style_paragraph_properties = None
247- for style in self .get_style_chain_stack ():
248- if style .paragraph_properties :
249- style_paragraph_properties = style .paragraph_properties
250- break
287+ spacing_properties = self .effective_properties .spacing_properties
251288
252- if style_paragraph_properties :
253- results ['contextual_spacing' ] = bool (style_paragraph_properties .contextual_spacing )
254-
255- default_paragraph_properties = None
256- if self .default_doc_styles and self .default_doc_styles .paragraph :
257- default_paragraph_properties = self .default_doc_styles .paragraph .properties
258-
259- # Spacing properties can be defined in multiple places and we need to get some
260- # kind of order of check
261- properties_order = [None , None , None ]
262- if self .properties :
263- properties_order [0 ] = self .properties
264- if isinstance (self .parent , TableCell ):
265- properties_order [1 ] = self .parent .parent_table .get_paragraph_properties ()
266- if not self .properties or not self .properties .spacing_properties :
267- properties_order [2 ] = default_paragraph_properties
268-
269- spacing_properties = None
270- contextual_spacing = None
271-
272- for properties in properties_order :
273- if spacing_properties is None :
274- spacing_properties = getattr (properties , 'spacing_properties' , None )
275- if contextual_spacing is None :
276- contextual_spacing = getattr (properties , 'contextual_spacing' , None )
277-
278- if not spacing_properties :
289+ if spacing_properties is None :
279290 return results
280291
281- if contextual_spacing is not None :
282- results ['contextual_spacing' ] = bool (contextual_spacing )
283-
284- if self .properties :
285- results ['parent_style' ] = self .properties .parent_style
286-
287292 spacing_line = spacing_properties .to_int ('line' )
288293 spacing_after = spacing_properties .to_int ('after' )
289294 spacing_before = spacing_properties .to_int ('before' )
290295
291- if default_paragraph_properties and spacing_line is None \
292- and bool (spacing_properties .after_auto_spacing ):
293- # get the spacing_line from the default definition
294- spacing_line = default_paragraph_properties .spacing_properties .to_int ('line' )
295-
296296 if spacing_line :
297- line = spacing_line / 240.0
297+ line = float ( "%.2f" % ( spacing_line / 240.0 ))
298298 # default line spacing is 1 so no need to add attribute
299299 if line != 1.0 :
300300 results ['line' ] = line
0 commit comments