Support markup in TextDoc, add implementation in CairoDoc, use in indiv complete report
svn: r11913
This commit is contained in:
parent
0b2394fbd9
commit
ac50776d33
195
src/BaseDoc.py
195
src/BaseDoc.py
@ -1406,12 +1406,41 @@ class BaseDoc:
|
|||||||
# TextDoc
|
# TextDoc
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
|
def noescape(text):
|
||||||
|
return text
|
||||||
|
|
||||||
class TextDoc:
|
class TextDoc:
|
||||||
"""
|
"""
|
||||||
Abstract Interface for text document generators. Output formats for
|
Abstract Interface for text document generators. Output formats for
|
||||||
text reports must implment this interface to be used by the report
|
text reports must implment this interface to be used by the report
|
||||||
system.
|
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):
|
def page_break(self):
|
||||||
"Forces a page break, creating a new page"
|
"Forces a page break, creating a new page"
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -1477,6 +1506,29 @@ class TextDoc:
|
|||||||
"Ends the current table cell"
|
"Ends the current table cell"
|
||||||
raise NotImplementedError
|
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):
|
def write_note(self, text, format, style_name):
|
||||||
"""
|
"""
|
||||||
Writes the note's text and take care of paragraphs,
|
Writes the note's text and take care of paragraphs,
|
||||||
@ -1488,15 +1540,17 @@ class TextDoc:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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
|
Convenience function to write a styledtext to the cairo doc.
|
||||||
start_paragraph and before an end_paragraph.
|
styledtext : assumed a StyledText object to write
|
||||||
|
format : = 0 : Flowed, = 1 : Preformatted
|
||||||
|
style_name : name of the style to use for default presentation
|
||||||
|
|
||||||
@param text: text to write.
|
overwrite this method if the backend supports styled notes
|
||||||
@param mark: IndexMark to use for indexing (if supported)
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
text = str(styledtext)
|
||||||
|
self.write_note(text, format, style_name)
|
||||||
|
|
||||||
def add_media_object(self, name, align, w_cm, h_cm):
|
def add_media_object(self, name, align, w_cm, h_cm):
|
||||||
"""
|
"""
|
||||||
@ -1510,6 +1564,135 @@ class TextDoc:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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 <b>text</b> 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 <b>text<i> here</b> not</i> is assumed invalid
|
||||||
|
as markup. If the s_tags require such a setup, what is returned
|
||||||
|
is <b>text</b><i><b> here</b> not</i>
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# DrawDoc
|
# DrawDoc
|
||||||
|
@ -1065,6 +1065,32 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc):
|
|||||||
page style.
|
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 : ("<b>", "</b>"),
|
||||||
|
BaseDoc.TextDoc.ITALIC : ("<i>", "</i>"),
|
||||||
|
BaseDoc.TextDoc.UNDERLINE : ("<u>", "</u>"),
|
||||||
|
BaseDoc.TextDoc.SUPERSCRIPT : ("<sup>", "</sup>"),
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseDoc.TextDoc.ESCAPE_FUNC = lambda x: escape
|
||||||
|
|
||||||
# BaseDoc implementation
|
# BaseDoc implementation
|
||||||
|
|
||||||
@ -1142,6 +1168,20 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc):
|
|||||||
def end_cell(self):
|
def end_cell(self):
|
||||||
self._active_element = self._active_element.get_parent()
|
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 ('<span %s="%s">' % (self.STYLETAG_TO_PROPERTY[type], value),
|
||||||
|
'</span>')
|
||||||
|
|
||||||
def write_note(self, text, format, style_name):
|
def write_note(self, text, format, style_name):
|
||||||
"""
|
"""
|
||||||
Method to write the note objects text on a
|
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
|
# The markup in the note editor is not in the text so is not
|
||||||
# considered. It must be added by pango too
|
# considered. It must be added by pango too
|
||||||
if format == 1:
|
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.start_paragraph(style_name)
|
||||||
self.write_text(line)
|
self.write_text(line)
|
||||||
self.end_paragraph()
|
self.end_paragraph()
|
||||||
elif format == 0:
|
elif format == 0:
|
||||||
|
#this is bad code, a user can type spaces between paragraph!
|
||||||
for line in text.split('\n\n'):
|
for line in text.split('\n\n'):
|
||||||
self.start_paragraph(style_name)
|
self.start_paragraph(style_name)
|
||||||
line = line.replace('\n',' ')
|
line = line.replace('\n',' ')
|
||||||
@ -1164,14 +1208,46 @@ class CairoDoc(BaseDoc.BaseDoc, BaseDoc.TextDoc, BaseDoc.DrawDoc):
|
|||||||
self.write_text(line)
|
self.write_text(line)
|
||||||
self.end_paragraph()
|
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):
|
def __write_text(self, text, mark=None, markup=False):
|
||||||
"""
|
"""
|
||||||
@param text: text to write.
|
@param text: text to write.
|
||||||
@param mark: IndexMark to use for indexing (if supported)
|
@param mark: IndexMark to use for indexing (if supported)
|
||||||
@param markup: True if text already contains markup info.
|
@param markup: True if text already contains markup info.
|
||||||
Then text will no longer be escaped
|
Then text will no longer be escaped
|
||||||
Private method: reports should not add markup in text to override
|
|
||||||
the style
|
|
||||||
"""
|
"""
|
||||||
if not markup:
|
if not markup:
|
||||||
# We need to escape the text here for later pango.Layout.set_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 text: text to write.
|
||||||
@param mark: IndexMark to use for indexing (if supported)
|
@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):
|
def add_media_object(self, name, pos, x_cm, y_cm):
|
||||||
new_image = GtkDocPicture(pos, name, x_cm, y_cm)
|
new_image = GtkDocPicture(pos, name, x_cm, y_cm)
|
||||||
|
@ -128,9 +128,9 @@ class IndivCompleteReport(Report):
|
|||||||
|
|
||||||
for notehandle in event.get_note_list():
|
for notehandle in event.get_note_list():
|
||||||
note = self.database.get_note_from_handle(notehandle)
|
note = self.database.get_note_from_handle(notehandle)
|
||||||
text = note.get()
|
text = note.get_styledtext()
|
||||||
format = note.get_format()
|
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_cell()
|
||||||
self.doc.end_row()
|
self.doc.end_row()
|
||||||
@ -163,11 +163,12 @@ class IndivCompleteReport(Report):
|
|||||||
|
|
||||||
for notehandle in notelist:
|
for notehandle in notelist:
|
||||||
note = self.database.get_note_from_handle(notehandle)
|
note = self.database.get_note_from_handle(notehandle)
|
||||||
text = note.get()
|
text = note.get_styledtext()
|
||||||
format = note.get_format()
|
format = note.get_format()
|
||||||
self.doc.start_row()
|
self.doc.start_row()
|
||||||
self.doc.start_cell('IDS-NormalCell', 2)
|
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_cell()
|
||||||
self.doc.end_row()
|
self.doc.end_row()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user