7
7
8
8
from pydocx .models import XmlModel , XmlCollection , XmlChild
9
9
from pydocx .openxml .wordprocessing .bookmark import Bookmark
10
- from pydocx .openxml .wordprocessing .br import Break
11
10
from pydocx .openxml .wordprocessing .deleted_run import DeletedRun
12
11
from pydocx .openxml .wordprocessing .hyperlink import Hyperlink
13
12
from pydocx .openxml .wordprocessing .inserted_run import InsertedRun
18
17
from pydocx .openxml .wordprocessing .smart_tag_run import SmartTagRun
19
18
from pydocx .openxml .wordprocessing .tab_char import TabChar
20
19
from pydocx .openxml .wordprocessing .text import Text
21
- from pydocx .openxml .wordprocessing .table_cell import TableCell
22
20
from pydocx .util .memoize import memoized
23
21
24
22
@@ -38,10 +36,6 @@ class Paragraph(XmlModel):
38
36
Bookmark
39
37
)
40
38
41
- def __init__ (self , ** kwargs ):
42
- super (Paragraph , self ).__init__ (** kwargs )
43
- self ._effective_properties = None
44
-
45
39
@property
46
40
def is_empty (self ):
47
41
if not self .children :
@@ -52,20 +46,73 @@ def is_empty(self):
52
46
if len (self .children ) == 1 :
53
47
first_child = self .children [0 ]
54
48
if isinstance (first_child , Bookmark ) and \
55
- first_child .name in ('_GoBack' ,):
49
+ first_child .name in ('_GoBack' ,):
56
50
return True
57
51
# We can have cases when only run properties are defined and no text
58
52
elif not first_child .children :
59
53
return True
60
54
return False
61
55
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
+
62
107
@property
108
+ @memoized
63
109
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 )
69
116
70
117
@property
71
118
def numbering_definition (self ):
@@ -76,12 +123,9 @@ def has_structured_document_parent(self):
76
123
return self .has_ancestor (SdtBlock )
77
124
78
125
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 )
85
129
86
130
# TODO the getattr is necessary because of footnotes. From the context
87
131
# of a footnote, a paragraph's container is the footnote part, which
@@ -117,9 +161,9 @@ def get_numbering_definition(self):
117
161
part = getattr (self .container , 'numbering_definitions_part' , None )
118
162
if not part :
119
163
return
120
- if not self .effective_properties :
164
+ if not self .properties :
121
165
return
122
- numbering_properties = self .effective_properties .numbering_properties
166
+ numbering_properties = self .properties .numbering_properties
123
167
if not numbering_properties :
124
168
return
125
169
return part .numbering .get_numbering_definition (
@@ -131,9 +175,9 @@ def get_numbering_level(self):
131
175
numbering_definition = self .get_numbering_definition ()
132
176
if not numbering_definition :
133
177
return
134
- if not self .effective_properties :
178
+ if not self .properties :
135
179
return
136
- numbering_properties = self .effective_properties .numbering_properties
180
+ numbering_properties = self .properties .numbering_properties
137
181
if not numbering_properties :
138
182
return
139
183
return numbering_definition .get_level (
@@ -231,70 +275,26 @@ def get_spacing(self):
231
275
"""Get paragraph spacing according to:
232
276
ECMA-376, 3rd Edition (June, 2011),
233
277
Fundamentals and Markup Language Reference § 17.3.1.33.
234
-
235
- Note: Partial implementation for now.
236
278
"""
237
279
results = {
238
280
'line' : None ,
239
281
'after' : None ,
240
282
'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
243
285
}
244
286
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
251
288
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 :
279
290
return results
280
291
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
-
287
292
spacing_line = spacing_properties .to_int ('line' )
288
293
spacing_after = spacing_properties .to_int ('after' )
289
294
spacing_before = spacing_properties .to_int ('before' )
290
295
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
-
296
296
if spacing_line :
297
- line = spacing_line / 240.0
297
+ line = float ( "%.2f" % ( spacing_line / 240.0 ))
298
298
# default line spacing is 1 so no need to add attribute
299
299
if line != 1.0 :
300
300
results ['line' ] = line
0 commit comments