From ac50776d3352b8fe21904bfe64ee8e8f0ab64a89 Mon Sep 17 00:00:00 2001 From: Benny Malengier Date: Sun, 8 Feb 2009 09:59:36 +0000 Subject: [PATCH] Support markup in TextDoc, add implementation in CairoDoc, use in indiv complete report svn: r11913 --- src/BaseDoc.py | 201 ++++++++++++++++++++++-- src/plugins/docgen/CairoDoc.py | 96 ++++++++++- src/plugins/textreport/IndivComplete.py | 9 +- 3 files changed, 289 insertions(+), 17 deletions(-) diff --git a/src/BaseDoc.py b/src/BaseDoc.py index c21d1b0cc..c0e8bde0f 100644 --- a/src/BaseDoc.py +++ b/src/BaseDoc.py @@ -1406,12 +1406,41 @@ class BaseDoc: # TextDoc # #------------------------------------------------------------------------ +def noescape(text): + return text + class TextDoc: """ Abstract Interface for text document generators. Output formats for text reports must implment this interface to be used by the report system. - """ + """ + BOLD = 0 + ITALIC = 1 + UNDERLINE = 2 + FONTFACE = 3 + FONTSIZE = 4 + FONTCOLOR = 5 + HIGHLIGHT = 6 + SUPERSCRIPT = 7 + + SUPPORTED_MARKUP = [] + + ESCAPE_FUNC = lambda x: noescape + #Map between styletypes and internally used values. This map is needed + # to make TextDoc officially independant of gen.lib.styledtexttag + STYLETYPE_MAP = { + } + CLASSMAP = None + + #STYLETAGTABLE to store markup for write_markup associated with style tags + STYLETAG_MARKUP = { + BOLD : ("", ""), + ITALIC : ("", ""), + UNDERLINE : ("", ""), + SUPERSCRIPT : ("", ""), + } + def page_break(self): "Forces a page break, creating a new page" raise NotImplementedError @@ -1477,6 +1506,29 @@ class TextDoc: "Ends the current table cell" raise NotImplementedError + def write_text(self, text, mark=None): + """ + Writes the text in the current paragraph. Should only be used after a + start_paragraph and before an end_paragraph. + + @param text: text to write. + @param mark: IndexMark to use for indexing (if supported) + """ + raise NotImplementedError + + def write_markup(self, text, s_tags): + """ + Writes the text in the current paragraph. Should only be used after a + start_paragraph and before an end_paragraph. Not all backends support + s_tags, then the same happens as with write_text. Backends supporting + write_markup will overwrite this method + + @param text: text to write. The text is assumed to be _not_ escaped + @param s_tags: assumed to be list of styledtexttags to apply to the + text + """ + self.write_text(text) + def write_note(self, text, format, style_name): """ Writes the note's text and take care of paragraphs, @@ -1488,16 +1540,18 @@ class TextDoc: """ raise NotImplementedError - def write_text(self, text, mark=None): + def write_styled_note(self, styledtext, format, style_name): """ - Writes the text in the current paragraph. Should only be used after a - start_paragraph and before an end_paragraph. + Convenience function to write a styledtext to the cairo doc. + styledtext : assumed a StyledText object to write + format : = 0 : Flowed, = 1 : Preformatted + style_name : name of the style to use for default presentation + + overwrite this method if the backend supports styled notes + """ + text = str(styledtext) + self.write_note(text, format, style_name) - @param text: text to write. - @param mark: IndexMark to use for indexing (if supported) - """ - raise NotImplementedError - def add_media_object(self, name, align, w_cm, h_cm): """ Add a photo of the specified width (in centimeters) @@ -1509,6 +1563,135 @@ class TextDoc: @param h_cm: height in centimeters """ raise NotImplementedError + + def find_tag_by_stag(self, s_tag): + """ + @param s_tag: object: assumed styledtexttag + @param s_tagvalue: None/int/str: value associated with the tag + + A styled tag is type with a value. + Every styled tag must be converted to the tags used in the corresponding + markup for the backend, eg text for bold in html. + These markups are stored in STYLETAG_MARKUP. They are tuples for begin + and end tag + If a markup is not present yet, it is created, using the + _create_xmltag method you can overwrite + """ + type = s_tag.name + + if not self.STYLETYPE_MAP or \ + self.CLASSMAP <> type.__class__.__name__ : + self.CLASSMAP == type.__class__.__name__ + self.STYLETYPE_MAP[type.__class__.BOLD] = self.BOLD + self.STYLETYPE_MAP[type.ITALIC] = self.ITALIC + self.STYLETYPE_MAP[type.UNDERLINE] = self.UNDERLINE + self.STYLETYPE_MAP[type.FONTFACE] = self.FONTFACE + self.STYLETYPE_MAP[type.FONTSIZE] = self.FONTSIZE + self.STYLETYPE_MAP[type.FONTCOLOR] = self.FONTCOLOR + self.STYLETYPE_MAP[type.HIGHLIGHT] = self.HIGHLIGHT + self.STYLETYPE_MAP[type.SUPERSCRIPT] = self.SUPERSCRIPT + + typeval = int(s_tag.name) + s_tagvalue = s_tag.value + tag_name = None + if type.STYLE_TYPE[typeval] == bool: + return self.STYLETAG_MARKUP[self.STYLETYPE_MAP[typeval]] + elif type.STYLE_TYPE[typeval] == str: + tag_name = "%d %s" % (typeval, s_tagvalue) + elif type.STYLE_TYPE[typeval] == int: + tag_name = "%d %d" % (typeval, s_tagvalue) + if not tag_name: + return None + + tags = self.STYLETAG_MARKUP.get(tag_name) + if tags is not None: + return tags + #no tag known yet, create the markup, add to lookup, and return + tags = self._create_xmltag(self.STYLETYPE_MAP[typeval], s_tagvalue) + self.STYLETAG_MARKUP[tag_name] = tags + return tags + + def _create_xmltag(self, type, value): + """ + Create the xmltags for the backend. + Overwrite this method to create functionality with a backend + """ + if type not in self.SUPPORTED_MARKUP: + return None + return ('', '') + + def _add_markup_from_styled(self, text, s_tags): + """ + Input is plain text, output is text with markup added according to the + s_tags which are assumed to be styledtexttags. + As adding markup means original text must be escaped, ESCAPE_FUNC is + used + This can be used to convert the text of a styledtext to the format + needed for a document backend + Do not call this method in a report, use the write_markup method + + @note: the algorithm is complex as it assumes mixing of tags is not + allowed: eg text here not is assumed invalid + as markup. If the s_tags require such a setup, what is returned + is text here not + overwrite this method if this complexity is not needed. + """ + FIRST = 0 + LAST = 1 + tagspos = {} + for s_tag in s_tags: + tag = self.find_tag_by_stag(s_tag) + if tag is not None: + for (start, end) in s_tag.ranges: + if start in tagspos: + tagspos[start] += [(tag, FIRST)] + else: + tagspos[start] = [(tag, FIRST)] + if end in tagspos: + tagspos[end] = [(tag, LAST)] + tagspos[end] + else: + tagspos[end] = [(tag, LAST)] + start = 0 + end = len(text) + keylist = tagspos.keys() + keylist.sort() + keylist = [x for x in keylist if x<=len(text)] + opentags = [] + otext = u"" #the output, text with markup + for pos in keylist: + #write text up to tag + if pos > start: + otext += self.ESCAPE_FUNC()(text[start:pos]) + #write out tags + for tag in tagspos[pos]: + #close open tags starting from last open + opentags.reverse() + for opentag in opentags: + otext += opentag[1] + opentags.reverse() + #if start, add to opentag in beginning as first to open + if tag[1] == FIRST: + opentags = [tag[0]] + opentags + else: + #end tag, is closed already, remove from opentag + opentags = [x for x in opentags if not x == tag[0] ] + #now all tags are closed, open the ones that should open + for opentag in opentags: + otext += opentag[0] + start = pos + otext += self.ESCAPE_FUNC()(text[start:end]) + + #opentags should be empty. If not, user gave tags on positions that + # are over the end of the text. Just close the tags still open + if opentags: + print 'WARNING: TextDoc : More style tags in text than length '\ + 'of text allows.\n', opentags + opentags.reverse() + for opentag in opentags: + otext += opentag[1] + + return otext + #------------------------------------------------------------------------ # diff --git a/src/plugins/docgen/CairoDoc.py b/src/plugins/docgen/CairoDoc.py index 13411370e..1a0381e31 100644 --- a/src/plugins/docgen/CairoDoc.py +++ b/src/plugins/docgen/CairoDoc.py @@ -1065,6 +1065,32 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc): page style. """ + STYLETAG_TO_PROPERTY = { + BaseDoc.TextDoc.FONTCOLOR : 'foreground', + BaseDoc.TextDoc.HIGHLIGHT : 'background', + BaseDoc.TextDoc.FONTFACE : 'face', + BaseDoc.TextDoc.FONTSIZE : 'size', + } + + # overwrite base class attributes + BaseDoc.TextDoc.SUPPORTED_MARKUP = [ + BaseDoc.TextDoc.BOLD, + BaseDoc.TextDoc.ITALIC, + BaseDoc.TextDoc.UNDERLINE, + BaseDoc.TextDoc.FONTFACE, + BaseDoc.TextDoc.FONTSIZE, + BaseDoc.TextDoc.FONTCOLOR, + BaseDoc.TextDoc.HIGHLIGHT, + BaseDoc.TextDoc.SUPERSCRIPT ] + + BaseDoc.TextDoc.STYLETAG_MARKUP = { + BaseDoc.TextDoc.BOLD : ("", ""), + BaseDoc.TextDoc.ITALIC : ("", ""), + BaseDoc.TextDoc.UNDERLINE : ("", ""), + BaseDoc.TextDoc.SUPERSCRIPT : ("", ""), + } + + BaseDoc.TextDoc.ESCAPE_FUNC = lambda x: escape # BaseDoc implementation @@ -1142,6 +1168,20 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc): def end_cell(self): self._active_element = self._active_element.get_parent() + def _create_xmltag(self, type, value): + """ + overwrites the method in BaseDoc.TextDoc. + creates the pango xml tags needed for non bool style types + """ + if type not in self.SUPPORTED_MARKUP: + return None + if type == BaseDoc.TextDoc.FONTSIZE: + #size is in thousandths of a point in pango + value = str(1000 * value) + + return ('' % (self.STYLETAG_TO_PROPERTY[type], value), + '') + def write_note(self, text, format, style_name): """ Method to write the note objects text on a @@ -1152,11 +1192,15 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc): # The markup in the note editor is not in the text so is not # considered. It must be added by pango too if format == 1: - for line in text.split('\n'): + #preformatted, retain whitespace. Cairo retains \n automatically, + #so use \n\n for paragraph detection + #this is bad code, a user can type spaces between paragraph! + for line in text.split('\n\n'): self.start_paragraph(style_name) self.write_text(line) self.end_paragraph() elif format == 0: + #this is bad code, a user can type spaces between paragraph! for line in text.split('\n\n'): self.start_paragraph(style_name) line = line.replace('\n',' ') @@ -1164,14 +1208,46 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc): self.write_text(line) self.end_paragraph() + def write_styled_note(self, styledtext, format, style_name): + """ + Convenience function to write a styledtext to the cairo doc. + styledtext : assumed a StyledText object to write + format : = 0 : Flowed, = 1 : Preformatted + style_name : name of the style to use for default presentation + + @note: text=normal text, p_text=text with pango markup, s_tags=styled + text tags, p + """ + text = str(styledtext) + + s_tags = styledtext.get_tags() + markuptext = self._add_markup_from_styled(text, s_tags) + + if format == 1: + #preformatted, retain whitespace. Cairo retains \n automatically, + #so use \n\n for paragraph detection + #FIXME: following split should be regex to match \n\s*\n instead? + for line in markuptext.split('\n\n'): + self.start_paragraph(style_name) + self.__write_text(line, markup=True) + self.end_paragraph() + elif format == 0: + #flowed + #FIXME: following split should be regex to match \n\s*\n instead? + for line in markuptext.split('\n\n'): + self.start_paragraph(style_name) + #flowed, make normal whitespace go away + line = line.replace('\n',' ') + line = ' '.join(line.split()) + self.__write_text(line, markup=True) + self.end_paragraph() + def __write_text(self, text, mark=None, markup=False): """ @param text: text to write. @param mark: IndexMark to use for indexing (if supported) @param markup: True if text already contains markup info. Then text will no longer be escaped - Private method: reports should not add markup in text to override - the style """ if not markup: # We need to escape the text here for later pango.Layout.set_markup @@ -1187,7 +1263,19 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc): @param text: text to write. @param mark: IndexMark to use for indexing (if supported) """ - self. __write_text(text, mark) + self.__write_text(text, mark) + + def write_markup(self, text, s_tags): + """ + Writes the text in the current paragraph. Should only be used after a + start_paragraph and before an end_paragraph. + + @param text: text to write. The text is assumed to be _not_ escaped + @param s_tags: assumed to be list of styledtexttags to apply to the + text + """ + markuptext = self._add_markup_from_styled(text, s_tags) + self.__write_text(text, markup=True) def add_media_object(self, name, pos, x_cm, y_cm): new_image = GtkDocPicture(pos, name, x_cm, y_cm) diff --git a/src/plugins/textreport/IndivComplete.py b/src/plugins/textreport/IndivComplete.py index 5b8434fe7..0b0613472 100644 --- a/src/plugins/textreport/IndivComplete.py +++ b/src/plugins/textreport/IndivComplete.py @@ -128,9 +128,9 @@ class IndivCompleteReport(Report): for notehandle in event.get_note_list(): note = self.database.get_note_from_handle(notehandle) - text = note.get() + text = note.get_styledtext() format = note.get_format() - self.doc.write_note(text,format, 'IDS-Normal') + self.doc.write_styled_note(text, format, 'IDS-Normal') self.doc.end_cell() self.doc.end_row() @@ -163,11 +163,12 @@ class IndivCompleteReport(Report): for notehandle in notelist: note = self.database.get_note_from_handle(notehandle) - text = note.get() + text = note.get_styledtext() format = note.get_format() self.doc.start_row() self.doc.start_cell('IDS-NormalCell', 2) - self.doc.write_note(text,format, 'IDS-Normal') + self.doc.write_styled_note(text, format, 'IDS-Normal') + self.doc.end_cell() self.doc.end_row()