Skip to content

Commit 98c6aa6

Browse files
authored
Merge pull request #225 from botzill/fix-list-items-processing
Fixed item listing, moved margin-left to <li>
2 parents 92b151e + 3ae50cb commit 98c6aa6

19 files changed

Lines changed: 1528 additions & 129 deletions

pydocx/export/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ def yield_numbering_spans(self, items):
286286
for item in items:
287287
yield item
288288
return
289-
builder = self.numbering_span_builder_class(items)
289+
builder = self.numbering_span_builder_class(items, process_components=True)
290290
numbering_spans = builder.get_numbering_spans()
291291
for item in numbering_spans:
292292
yield item

pydocx/export/html.py

Lines changed: 143 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
POINTS_PER_EM,
1818
PYDOCX_STYLES,
1919
TWIPS_PER_POINT,
20-
EMUS_PER_PIXEL,
20+
EMUS_PER_PIXEL
2121
)
2222
from pydocx.export.base import PyDocXExporter
2323
from pydocx.export.numbering_span import NumberingItem
@@ -96,12 +96,12 @@ class HtmlTag(object):
9696
closed_tag_format = '</{tag}>'
9797

9898
def __init__(
99-
self,
100-
tag,
101-
allow_self_closing=False,
102-
closed=False,
103-
allow_whitespace=False,
104-
**attrs
99+
self,
100+
tag,
101+
allow_self_closing=False,
102+
closed=False,
103+
allow_whitespace=False,
104+
**attrs
105105
):
106106
self.tag = tag
107107
self.allow_self_closing = allow_self_closing
@@ -311,46 +311,45 @@ def export_paragraph_property_justification(self, paragraph, results):
311311
def export_paragraph_property_indentation(self, paragraph, results):
312312
# TODO these classes should be applied on the paragraph, and not as
313313
# inline styles
314+
314315
properties = paragraph.effective_properties
315316

316317
style = {}
317318

318-
if properties.indentation_right:
319-
# TODO would be nice if this integer conversion was handled
320-
# implicitly by the model somehow
321-
try:
322-
right = int(properties.indentation_right)
323-
except ValueError:
324-
right = None
319+
# Numbering properties can define a text indentation on a paragraph
320+
if properties.numbering_properties:
321+
indentation_left = None
322+
indentation_first_line = None
325323

326-
if right:
327-
right = convert_twips_to_ems(right)
328-
style['margin-right'] = '{0:.2f}em'.format(right)
324+
paragraph_num_level = paragraph.get_numbering_level()
329325

330-
if properties.indentation_left:
331-
# TODO would be nice if this integer conversion was handled
332-
# implicitly by the model somehow
333-
try:
334-
left = int(properties.indentation_left)
335-
except ValueError:
336-
left = None
326+
if paragraph_num_level:
327+
listing_style = self.export_listing_paragraph_property_indentation(
328+
paragraph,
329+
paragraph_num_level.paragraph_properties,
330+
include_text_indent=True
331+
)
332+
if 'text-indent' in listing_style and listing_style['text-indent'] != '0.00em':
333+
style['text-indent'] = listing_style['text-indent']
334+
style['display'] = 'inline-block'
335+
else:
336+
indentation_left = properties.to_int('indentation_left')
337+
indentation_first_line = properties.to_int('indentation_first_line')
337338

338-
if left:
339-
left = convert_twips_to_ems(left)
340-
style['margin-left'] = '{0:.2f}em'.format(left)
339+
indentation_right = properties.to_int('indentation_right')
341340

342-
if properties.indentation_first_line:
343-
# TODO would be nice if this integer conversion was handled
344-
# implicitly by the model somehow
345-
try:
346-
first_line = int(properties.indentation_first_line)
347-
except ValueError:
348-
first_line = None
341+
if indentation_right:
342+
right = convert_twips_to_ems(indentation_right)
343+
style['margin-right'] = '{0:.2f}em'.format(right)
344+
345+
if indentation_left:
346+
left = convert_twips_to_ems(indentation_left)
347+
style['margin-left'] = '{0:.2f}em'.format(left)
349348

350-
if first_line:
351-
first_line = convert_twips_to_ems(first_line)
352-
# TODO text-indent doesn't work with inline elements like span
353-
style['text-indent'] = '{0:.2f}em'.format(first_line)
349+
if indentation_first_line:
350+
first_line = convert_twips_to_ems(indentation_first_line)
351+
style['text-indent'] = '{0:.2f}em'.format(first_line)
352+
style['display'] = 'inline-block'
354353

355354
if style:
356355
attrs = {
@@ -361,6 +360,93 @@ def export_paragraph_property_indentation(self, paragraph, results):
361360

362361
return results
363362

363+
def export_listing_paragraph_property_indentation(
364+
self,
365+
paragraph,
366+
level_properties,
367+
include_text_indent=False
368+
):
369+
style = {}
370+
371+
if not level_properties or not paragraph.has_numbering_properties:
372+
return style
373+
374+
level_indentation_step = \
375+
paragraph.numbering_definition.get_indentation_between_levels()
376+
377+
paragraph_properties = paragraph.properties
378+
379+
level_ind_left = level_properties.to_int('indentation_left', default=0)
380+
level_ind_hanging = level_properties.to_int('indentation_hanging', default=0)
381+
382+
paragraph_ind_left = paragraph_properties.to_int('indentation_left', default=0)
383+
paragraph_ind_hanging = paragraph_properties.to_int('indentation_hanging', default=0)
384+
paragraph_ind_first_line = paragraph_properties.to_int('indentation_first_line',
385+
default=0)
386+
387+
left = paragraph_ind_left or level_ind_left
388+
hanging = paragraph_ind_hanging or level_ind_hanging
389+
# At this point we have no info about indentation, so we keep the default one
390+
if not left and not hanging:
391+
return style
392+
393+
# All the bellow left margin calculation is done because html ul/ol/li elements have
394+
# their default indentations and we need to make sure that we migrate as near as
395+
# possible solution to html.
396+
margin_left = left
397+
398+
# Because hanging can be set independently, we remove it from left margin and will
399+
# be added as text-indent later on
400+
margin_left -= hanging
401+
402+
# Take into account that current span can have custom left margin
403+
if level_indentation_step > level_ind_hanging:
404+
margin_left -= (level_indentation_step - level_ind_hanging)
405+
else:
406+
margin_left -= level_indentation_step
407+
408+
# First line are added to left margins
409+
margin_left += paragraph_ind_first_line
410+
411+
if isinstance(paragraph.parent, NumberingItem):
412+
try:
413+
# In case of nested lists elements, we need to adjust left margin
414+
# based on the parent item
415+
parent_paragraph = paragraph.parent.numbering_span.parent.get_first_child()
416+
417+
parent_ind_left = parent_paragraph.get_indentation('indentation_left')
418+
parent_ind_hanging = parent_paragraph.get_indentation('indentation_hanging')
419+
parent_lvl_ind_hanging = parent_paragraph.get_indentation(
420+
'indentation_hanging')
421+
422+
margin_left -= (parent_ind_left - parent_ind_hanging)
423+
margin_left -= parent_lvl_ind_hanging
424+
# To mimic the word way of setting first line, we need to move back(left) all
425+
# elements by first_line value
426+
margin_left -= parent_paragraph.get_indentation('indentation_first_line')
427+
except AttributeError:
428+
pass
429+
430+
# Here as well, we remove the default hanging which word adds
431+
# because <li> tag will provide it's own
432+
hanging -= level_ind_hanging
433+
434+
if margin_left:
435+
margin_left = convert_twips_to_ems(margin_left)
436+
style['margin-left'] = '{0:.2f}em'.format(margin_left)
437+
438+
# we don't allow negative hanging
439+
if hanging < 0:
440+
hanging = 0
441+
442+
if include_text_indent:
443+
if hanging is not None:
444+
# Now, here we add the hanging as text-indent for the paragraph
445+
hanging = convert_twips_to_ems(hanging)
446+
style['text-indent'] = '{0:.2f}em'.format(hanging)
447+
448+
return style
449+
364450
def get_run_styles_to_apply(self, run):
365451
parent_paragraph = run.get_first_ancestor(wordprocessing.Paragraph)
366452
if parent_paragraph and parent_paragraph.heading_style:
@@ -737,7 +823,25 @@ def export_numbering_item(self, numbering_item):
737823
numbering_item.children,
738824
self.export_node,
739825
)
740-
tag = HtmlTag('li')
826+
827+
style = None
828+
829+
if numbering_item.children:
830+
level_properties = numbering_item.numbering_span.\
831+
numbering_level.paragraph_properties
832+
# get the first paragraph properties which will contain information
833+
# on how to properly indent listing item
834+
paragraph = numbering_item.children[0]
835+
836+
style = self.export_listing_paragraph_property_indentation(paragraph,
837+
level_properties)
838+
839+
attrs = {}
840+
841+
if style:
842+
attrs['style'] = convert_dictionary_to_style_fragment(style)
843+
844+
tag = HtmlTag('li', **attrs)
741845
return tag.apply(results)
742846

743847
def export_field_hyperlink(self, simple_field, field_args):

0 commit comments

Comments
 (0)