diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index a71371391b..65e5338b3e 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -13,6 +13,7 @@ from calibre.ebooks.docx.writer.styles import StylesManager, FloatSpec from calibre.ebooks.docx.writer.images import ImagesManager from calibre.ebooks.docx.writer.fonts import FontsManager from calibre.ebooks.docx.writer.tables import Table +from calibre.ebooks.docx.writer.lists import ListsManager from calibre.ebooks.oeb.stylizer import Stylizer as Sz, Style as St from calibre.ebooks.oeb.base import XPath, barename @@ -96,8 +97,9 @@ class TextRun(object): class Block(object): - def __init__(self, namespace, styles_manager, html_block, style, is_table_cell=False, float_spec=None): + def __init__(self, namespace, styles_manager, html_block, style, is_table_cell=False, float_spec=None, is_list_item=False): self.namespace = namespace + self.list_tag = (html_block, style) if is_list_item else None self.parent_items = None self.html_block = html_block self.float_spec = float_spec @@ -116,6 +118,8 @@ class Block(object): return if len(self.html_block) > 0 and self.html_block[0] is next_block.html_block: self.skipped = True + if self.list_tag is not None: + next_block.list_tag = self.list_tag def add_text(self, text, style, ignore_leading_whitespace=False, html_parent=None, is_parent_style=False): ts = self.styles_manager.create_text_style(style, is_parent_style=is_parent_style) @@ -201,9 +205,11 @@ class Blocks(object): self.current_block.parent_items = self.items self.current_block = None - def start_new_block(self, html_block, style, is_table_cell=False, float_spec=None): + def start_new_block(self, html_block, style, is_table_cell=False, float_spec=None, is_list_item=False): self.end_current_block() - self.current_block = Block(self.namespace, self.styles_manager, html_block, style, is_table_cell=is_table_cell, float_spec=float_spec) + self.current_block = Block( + self.namespace, self.styles_manager, html_block, style, + is_table_cell=is_table_cell, float_spec=float_spec, is_list_item=is_list_item) self.open_html_blocks.add(html_block) return self.current_block @@ -286,6 +292,7 @@ class Convert(object): self.styles_manager = StylesManager(self.docx.namespace) self.images_manager = ImagesManager(self.oeb, self.docx.document_relationships) + self.lists_manager = ListsManager(self.docx) self.fonts_manager = FontsManager(self.docx.namespace, self.oeb, self.opts) self.blocks = Blocks(self.docx.namespace, self.styles_manager) @@ -305,6 +312,7 @@ class Convert(object): for pos, block in reversed(remove_blocks): self.blocks.delete_block_at(pos) + self.lists_manager.finalize(all_blocks) self.styles_manager.finalize(all_blocks) self.write() @@ -338,8 +346,7 @@ class Convert(object): else: self.add_inline_tag(tagname, html_tag, tag_style, stylizer) elif display == 'list-item': - # TODO: Implement this - self.add_block_tag(tagname, html_tag, tag_style, stylizer) + self.add_block_tag(tagname, html_tag, tag_style, stylizer, is_list_item=True) elif display.startswith('table') or display == 'inline-table': if display == 'table-cell': self.blocks.start_new_cell(html_tag, tag_style) @@ -374,8 +381,8 @@ class Convert(object): block = self.blocks.current_or_new_block(html_tag.getparent(), stylizer.style(html_tag.getparent())) block.add_text(html_tag.tail, stylizer.style(html_tag.getparent()), is_parent_style=True) - def add_block_tag(self, tagname, html_tag, tag_style, stylizer, is_table_cell=False, float_spec=None): - block = self.blocks.start_new_block(html_tag, tag_style, is_table_cell=is_table_cell, float_spec=float_spec) + def add_block_tag(self, tagname, html_tag, tag_style, stylizer, is_table_cell=False, float_spec=None, is_list_item=False): + block = self.blocks.start_new_block(html_tag, tag_style, is_table_cell=is_table_cell, float_spec=float_spec, is_list_item=is_list_item) if tagname == 'img': self.images_manager.add_image(html_tag, block, stylizer) else: diff --git a/src/calibre/ebooks/docx/writer/lists.py b/src/calibre/ebooks/docx/writer/lists.py new file mode 100644 index 0000000000..e74e756793 --- /dev/null +++ b/src/calibre/ebooks/docx/writer/lists.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2015, Kovid Goyal ' + +from collections import defaultdict + +LIST_STYLES = frozenset( + 'disc circle square decimal decimal-leading-zero lower-roman upper-roman' + ' lower-greek lower-alpha lower-latin upper-alpha upper-latin hiragana hebrew' + ' katakana-iroha cjk-ideographic'.split()) + +STYLE_MAP = { + 'disc': 'bullet', + 'circle': 'o', + 'square': '\uf0a7', + 'decimal': 'decimal', + 'decimal-leading-zero': 'decimalZero', + 'lower-roman': 'lowerRoman', + 'upper-roman': 'upperRoman', + 'lower-alpha': 'lowerLetter', + 'lower-latin': 'lowerLetter', + 'upper-alpha': 'upperLetter', + 'upper-latin': 'upperLetter', + 'hiragana': 'aiueo', + 'hebrew': 'hebrew1', + 'katakana-iroha': 'iroha', + 'cjk-ideographic': 'chineseCounting', +} + + +def find_list_containers(list_tag, tag_style): + node = list_tag + stylizer = tag_style.stylizer + ans = [] + while True: + parent = node.getparent() + if parent is None or parent is node: + break + node = parent + style = stylizer.style(node) + lst = (style._style.get('list-style-type', None) or '').lower() + if lst in LIST_STYLES: + ans.append(node) + return ans + +class NumberingDefinition(object): + + def __init__(self): + pass + +class Level(object): + + def __init__(self, list_type, container, items, ilvl=0): + self.ilvl = ilvl + try: + self.start = int(container.get('start')) + except Exception: + self.start = 1 + if list_type in {'disc', 'circle', 'square'}: + self.num_fmt = 'bullet' + self.lvl_text = '%1' if list_type == 'disc' else STYLE_MAP['list_type'] + else: + self.lvl_text = '%1.' + self.num_fmt = STYLE_MAP.get(list_type, 'decimal') + +class ListManager(object): + + def __init__(self, docx): + self.namespace = docx.namespace + + def finalize(self, all_blocks): + lists = defaultdict(list) + for block in all_blocks: + if block.list_tag is not None: + list_tag, tag_style = block.list_tag + list_type = (tag_style['list-style-type'] or '').lower() + if list_type not in LIST_STYLES: + continue + container_tags = find_list_containers(list_tag, tag_style) + if not container_tags: + continue + lists[(tuple(container_tags), list_type)].append((list_tag, tag_style))