From 63974a9b4d026eed3b7a8b8c0ef2278ff28c4d18 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 3 Jul 2016 14:55:21 +0100 Subject: Add initial support for comments --- mammoth/conversion.py | 74 ++++++++++++++++++++++++++++++++++++++++++----- mammoth/html_paths.py | 3 +- tests/conversion_tests.py | 30 +++++++++++++++++++ 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/mammoth/conversion.py b/mammoth/conversion.py index 5644852..b449dd6 100644 --- a/mammoth/conversion.py +++ b/mammoth/conversion.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals +import collections from functools import partial import base64 @@ -25,6 +26,14 @@ def convert_document_element_to_html(element, if convert_image is None: convert_image = images.img_element(_generate_image_attributes) + if isinstance(element, documents.Document): + comments = dict( + (comment.comment_id, comment) + for comment in element.comments + ) + else: + comments = {} + messages = [] converter = _DocumentConverter( messages=messages, @@ -32,7 +41,9 @@ def convert_document_element_to_html(element, convert_image=convert_image, id_prefix=id_prefix, ignore_empty_paragraphs=ignore_empty_paragraphs, - note_references=[]) + note_references=[], + comments=comments, + ) nodes = converter.visit(element) writer = writers.writer(output_format) @@ -50,13 +61,16 @@ def _generate_image_attributes(image): class _DocumentConverter(documents.ElementVisitor): - def __init__(self, messages, style_map, convert_image, id_prefix, ignore_empty_paragraphs, note_references): + def __init__(self, messages, style_map, convert_image, id_prefix, ignore_empty_paragraphs, note_references, comments): self._messages = messages self._style_map = style_map self._id_prefix = id_prefix self._ignore_empty_paragraphs = ignore_empty_paragraphs self._note_references = note_references + self._referenced_comments = [] + self._comment_count_by_initials = collections.defaultdict(lambda: 0) self._convert_image = convert_image + self._comments = comments def visit_image(self, image): try: @@ -72,7 +86,8 @@ class _DocumentConverter(documents.ElementVisitor): for reference in self._note_references ] notes_list = html.element("ol", {}, self._visit_all(notes)) - return nodes + [notes_list] + comments = html.element("dl", {}, lists.flat_map(self.visit_comment, self._referenced_comments)) + return nodes + [notes_list, comments] def visit_paragraph(self, paragraph): @@ -195,7 +210,44 @@ class _DocumentConverter(documents.ElementVisitor): def visit_comment_reference(self, reference): - return [] + def nodes(): + comment = self._comments[reference.comment_id] + self._comment_count_by_initials[comment.author_initials] += 1 + count = self._comment_count_by_initials[comment.author_initials] + label = "{0} {1}".format(comment.author_initials, count) + self._referenced_comments.append((label, comment)) + return [ + # TODO: remove duplication with note references + html.element("a", { + "href": "#" + self._referent_html_id("comment", reference.comment_id), + "id": self._reference_html_id("comment", reference.comment_id), + # TODO: handle comments without initials + }, [html.text("[{0}]".format(label))]) + ] + + html_path = self._find_html_path( + None, + "comment_reference", + default=html_paths.ignore, + ) + + return html_path.wrap(nodes) + + def visit_comment(self, referenced_comment): + label, comment = referenced_comment + # TODO remove duplication with notes + body = self._visit_all(comment.body) + [ + html.collapsible_element("p", {}, [ + html.text(" "), + html.element("a", {"href": "#" + self._reference_html_id("comment", comment.comment_id)}, [ + html.text(_up_arrow) + ]), + ]) + ] + return [ + html.element("dt", {"id": self._referent_html_id("comment", comment.comment_id)}, [html.text(label)]), + html.element("dd", {}, body), + ] def _visit_all(self, elements): @@ -215,7 +267,7 @@ class _DocumentConverter(documents.ElementVisitor): if style is not None: return style.html_path - if element.style_id is not None: + if getattr(element, "style_id", None) is not None: self._messages.append(results.warning( "Unrecognised {0} style: {1} (Style ID: {2})".format( element_type, element.style_name, element.style_id) @@ -230,17 +282,23 @@ class _DocumentConverter(documents.ElementVisitor): return style def _note_html_id(self, note): - return self._html_id("{0}-{1}".format(note.note_type, note.note_id)) + return self._referent_html_id(note.note_type, note.note_id) def _note_ref_html_id(self, note): - return self._html_id("{0}-ref-{1}".format(note.note_type, note.note_id)) + return self._reference_html_id(note.note_type, note.note_id) + + def _referent_html_id(self, reference_type, reference_id): + return self._html_id("{0}-{1}".format(reference_type, reference_id)) + + def _reference_html_id(self, reference_type, reference_id): + return self._html_id("{0}-ref-{1}".format(reference_type, reference_id)) def _html_id(self, suffix): return "{0}{1}".format(self._id_prefix, suffix) def _document_matcher_matches(matcher, element, element_type): - if matcher.element_type in ["underline", "strikethrough", "bold", "italic"]: + if matcher.element_type in ["underline", "strikethrough", "bold", "italic", "comment_reference"]: return matcher.element_type == element_type else: return ( diff --git a/mammoth/html_paths.py b/mammoth/html_paths.py index 53580ed..e201fef 100644 --- a/mammoth/html_paths.py +++ b/mammoth/html_paths.py @@ -52,5 +52,6 @@ empty = path([]) class ignore(object): - def wrap(self, generate_nodes): + @staticmethod + def wrap(generate_nodes): return [] diff --git a/tests/conversion_tests.py b/tests/conversion_tests.py index b045cac..300d1a9 100644 --- a/tests/conversion_tests.py +++ b/tests/conversion_tests.py @@ -445,6 +445,36 @@ def comments_are_ignored_by_default(): assert_equal(expected_html, result.value) +@istest +def comment_references_are_linked_to_comment_after_main_body(): + reference = documents.comment_reference("4") + comment = documents.comment( + comment_id="4", + body=[_paragraph_with_text("Who's there?")], + author_name="The Piemaker", + author_initials="TP", + ) + document = documents.document( + [documents.paragraph([ + _run_with_text("Knock knock"), + documents.run([reference]) + ])], + comments=[comment], + ) + result = convert_document_element_to_html( + document, + id_prefix="doc-42-", + style_map=[ + _style_mapping("comment-reference => sup") + ], + ) + expected_html = ( + '

Knock knock[TP 1]

' + + '
TP 1

Who\'s there? ↑

' + ) + assert_equal(expected_html, result.value) + + def _paragraph_with_text(text): return documents.paragraph(children=[_run_with_text(text)]) -- cgit v1.2.3