diff --git a/src/data/templates/view_note_detail.html b/src/data/templates/view_note_detail.html index e753ff7b8..643713b2d 100644 --- a/src/data/templates/view_note_detail.html +++ b/src/data/templates/view_note_detail.html @@ -9,12 +9,13 @@ $('.wysiwyg').htmlarea({ toolbar: [ "bold", "italic", "underline", - "|", "forecolor", + "|", "forecolor", "superscript", "|", "link", "unlink", "|", "html" ] }); - // FIXME: easier way? + // FIXME: add font, fontsize, backcolor + // FIXME: find easier way: $('.jHtmlArea').contents().find('iframe').contents().find('body').css({"background-color": "white"}); }); function setnotetext() { diff --git a/src/webapp/grampsdb/models.py b/src/webapp/grampsdb/models.py index ce2996858..dbbd8eeb8 100644 --- a/src/webapp/grampsdb/models.py +++ b/src/webapp/grampsdb/models.py @@ -63,11 +63,17 @@ def get_type(the_type, data, get_or_create=False): def get_default_type(the_type): """ - Gets the default row for a given Type. + Gets the default database object for a given GrampsType. """ val, name = the_type._DEFAULT return the_type.objects.get(val=val, name=name) +def get_default_type_value(the_type): + """ + Gets the default value for a given gen.lib.GrampsType. + """ + return [x for x in the_type._DATAMAP if x[0] == the_type._DEFAULT][0] + def get_datamap(grampsclass): return sorted([(x[0],x[2]) for x in grampsclass._DATAMAP], key=lambda item: item[1]) @@ -97,7 +103,7 @@ class mGrampsType(models.Model): def get_default_type(self): """ return a tuple default (val,name) """ - return self._DATAMAP[self._DEFAULT] + return self._DEFAULT def __len__(self): """ For use as a sequence for getting (val, name) """ @@ -116,35 +122,35 @@ class NameType(mGrampsType): from gen.lib.nametype import NameType _DATAMAP = get_datamap(NameType) _CUSTOM = NameType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == NameType._DEFAULT][0] + _DEFAULT = get_default_type_value(NameType) val = models.IntegerField('name type', choices=_DATAMAP, blank=False) class NameOriginType(mGrampsType): from gen.lib.nameorigintype import NameOriginType _DATAMAP = get_datamap(NameOriginType) _CUSTOM = NameOriginType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == NameOriginType._DEFAULT][0] + _DEFAULT = get_default_type_value(NameOriginType) val = models.IntegerField('name origin type', choices=_DATAMAP, blank=False) class AttributeType(mGrampsType): from gen.lib.attrtype import AttributeType _DATAMAP = get_datamap(AttributeType) _CUSTOM = AttributeType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == AttributeType._DEFAULT][0] + _DEFAULT = get_default_type_value(AttributeType) val = models.IntegerField('attribute type', choices=_DATAMAP, blank=False) class UrlType(mGrampsType): from gen.lib.urltype import UrlType _DATAMAP = get_datamap(UrlType) _CUSTOM = UrlType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == UrlType._DEFAULT][0] + _DEFAULT = get_default_type_value(UrlType) val = models.IntegerField('url type', choices=_DATAMAP, blank=False) class ChildRefType(mGrampsType): from gen.lib.childreftype import ChildRefType _DATAMAP = get_datamap(ChildRefType) _CUSTOM = ChildRefType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == ChildRefType._DEFAULT][0] + _DEFAULT = get_default_type_value(ChildRefType) val = models.IntegerField('child reference type', choices=_DATAMAP, blank=False) @@ -152,14 +158,14 @@ class RepositoryType(mGrampsType): from gen.lib.repotype import RepositoryType _DATAMAP = get_datamap(RepositoryType) _CUSTOM = RepositoryType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == RepositoryType._DEFAULT][0] + _DEFAULT = get_default_type_value(RepositoryType) val = models.IntegerField('repository type', choices=_DATAMAP, blank=False) class EventType(mGrampsType): from gen.lib.eventtype import EventType _DATAMAP = get_datamap(EventType) _CUSTOM = EventType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == EventType._DEFAULT][0] + _DEFAULT = get_default_type_value(EventType) BIRTH = 12 DEATH = 13 val = models.IntegerField('event type', choices=_DATAMAP, blank=False) @@ -168,7 +174,7 @@ class FamilyRelType(mGrampsType): from gen.lib.familyreltype import FamilyRelType _DATAMAP = get_datamap(FamilyRelType) _CUSTOM = FamilyRelType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == FamilyRelType._DEFAULT][0] + _DEFAULT = get_default_type_value(FamilyRelType) val = models.IntegerField('family relation type', choices=_DATAMAP, blank=False) @@ -176,7 +182,7 @@ class SourceMediaType(mGrampsType): from gen.lib.srcmediatype import SourceMediaType _DATAMAP = get_datamap(SourceMediaType) _CUSTOM = SourceMediaType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == SourceMediaType._DEFAULT][0] + _DEFAULT = get_default_type_value(SourceMediaType) val = models.IntegerField('source medium type', choices=_DATAMAP, blank=False) @@ -184,22 +190,22 @@ class EventRoleType(mGrampsType): from gen.lib.eventroletype import EventRoleType _DATAMAP = get_datamap(EventRoleType) _CUSTOM = EventRoleType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == EventRoleType._DEFAULT][0] + _DEFAULT = get_default_type_value(EventRoleType) val = models.IntegerField('event role type', choices=_DATAMAP, blank=False) class NoteType(mGrampsType): from gen.lib.notetype import NoteType _DATAMAP = get_datamap(NoteType) _CUSTOM = NoteType._CUSTOM - _DEFAULT = [x for x in _DATAMAP if x[0] == NoteType._DEFAULT][0] + _DEFAULT = get_default_type_value(NoteType) val = models.IntegerField('note type', choices=_DATAMAP, blank=False) -class MarkupType(mGrampsType): - from gen.lib.notetype import NoteType - _DATAMAP = [(0, "Custom")] - _CUSTOM = 0 - _DEFAULT = _DATAMAP[0] - val = models.IntegerField('note type', choices=_DATAMAP, blank=False) +class StyledTextTagType(mGrampsType): + from gen.lib.styledtexttagtype import StyledTextTagType + _DATAMAP = get_datamap(StyledTextTagType) + _CUSTOM = None + _DEFAULT = None + val = models.IntegerField('styled text tag type', choices=_DATAMAP, blank=False) class GenderType(mGrampsType): _DATAMAP = [(2, 'Unknown'), (1, 'Male'), (0, 'Female')] @@ -716,7 +722,7 @@ class Lds(DateObject, SecondaryObject): class Markup(models.Model): note = models.ForeignKey('Note') - markup_type = models.ForeignKey('MarkupType') + styled_text_tag_type = models.ForeignKey('StyledTextTagType') order = models.PositiveIntegerField() string = models.TextField(blank=True, null=True) start_stop_list = models.TextField(default="[]") @@ -891,6 +897,7 @@ TABLES = [ ("type", LdsType), ("type", LdsStatus), ("type", ThemeType), + ("type", StyledTextTagType), ("abstract", DateObject), ("abstract", PrimaryObject), ("primary", Person), diff --git a/src/webapp/grampsdb/view/note.py b/src/webapp/grampsdb/view/note.py index 4437d4222..8b0295c94 100644 --- a/src/webapp/grampsdb/view/note.py +++ b/src/webapp/grampsdb/view/note.py @@ -22,7 +22,7 @@ """ Views for Person, Name, and Surname """ ## Gramps Modules -from webapp.utils import _, boolean, update_last_changed, StyledNoteFormatter +from webapp.utils import _, boolean, update_last_changed, StyledNoteFormatter, parse_styled_text from webapp.grampsdb.models import Note from webapp.grampsdb.forms import * from webapp.libdjango import DjangoInterface @@ -65,14 +65,15 @@ def process_note(request, context, handle, action, add_to=None): # view, edit, s noteform.model = note elif action == "save": note = Note.objects.get(handle=handle) - genlibnote = db.get_note_from_handle(note.handle) - notetext = snf.format(genlibnote) # FIXME + notetext = "" noteform = NoteForm(request.POST, instance=note, initial={"notetext": notetext}) noteform.model = note - #note.text = noteform.data["notetext"] # FIXME: split text and tags if noteform.is_valid(): update_last_changed(note, request.user.username) + notedata = parse_styled_text(noteform.data["notetext"]) + note.text = notedata[0] note = noteform.save() + dji.save_note_markup(note, notedata[1]) dji.rebuild_cache(note) notetext = noteform.data["notetext"] action = "view" @@ -81,13 +82,15 @@ def process_note(request, context, handle, action, add_to=None): # view, edit, s action = "edit" elif action == "create": note = Note(handle=create_id()) - notetext = "" # FIXME + notetext = "" noteform = NoteForm(request.POST, instance=note, initial={"notetext": notetext}) noteform.model = note - #note.text = noteform.data["notetext"] # FIXME: split text and tags if noteform.is_valid(): update_last_changed(note, request.user.username) + notedata = parse_styled_text(noteform.data["notetext"]) + note.text = notedata[0] note = noteform.save() + dji.save_note_markup(note, notedata[1]) dji.rebuild_cache(note) if add_to: item, handle = add_to diff --git a/src/webapp/init.py b/src/webapp/init.py index cc2a97a18..c6bd90346 100644 --- a/src/webapp/init.py +++ b/src/webapp/init.py @@ -42,6 +42,7 @@ from gen.lib.familyreltype import FamilyRelType from gen.lib.srcmediatype import SourceMediaType from gen.lib.eventroletype import EventRoleType from gen.lib.notetype import NoteType +from gen.lib.styledtexttagtype import StyledTextTagType from grampsdb.models import (GenderType, LdsType, LdsStatus, NameFormatType, NameOriginType, ThemeType) @@ -61,7 +62,7 @@ for table, entries in [("grampsdb.config", (("setting", "\"db_version\""), ("description", "\"database scheme version\""), ("value_type", "\"str\""), - ("value", "\"0.5.1\"")), + ("value", "\"0.6.0\"")), (("setting", "\"db_created\""), ("description", "\"database creation date/time\""), ("value_type", "\"str\""), @@ -174,7 +175,7 @@ for table, entries in [("grampsdb.config", type_models = [NameType, NameOriginType, AttributeType, UrlType, ChildRefType, RepositoryType, EventType, FamilyRelType, SourceMediaType, EventRoleType, NoteType, GenderType, LdsType, LdsStatus, - NameFormatType] + NameFormatType, StyledTextTagType] for type in type_models: count = 1 # Add each code: diff --git a/src/webapp/libdjango.py b/src/webapp/libdjango.py index d9ab20e75..55568b2df 100644 --- a/src/webapp/libdjango.py +++ b/src/webapp/libdjango.py @@ -299,14 +299,13 @@ class DjangoInterface(object): retval = [] markups = models.Markup.objects.filter(note=note).order_by("order") for markup in markups: - # FIXME: not all of these are strings; bummer if markup.string and markup.string.isdigit(): value = int(markup.string) else: value = markup.string start_stop_list = markup.start_stop_list ss_list = eval(start_stop_list) - retval += [(tuple(markup.markup_type), value, ss_list)] + retval += [(tuple(markup.styled_text_tag_type), value, ss_list)] return retval def get_note(self, note): @@ -1257,12 +1256,14 @@ class DjangoInterface(object): count = 1 for markup in markup_list: markup_code, value, start_stop_list = markup - m = models.Markup(note=note, order=count, - markup_type=models.get_type(models.MarkupType, - markup_code, - get_or_create=True), - string=value, - start_stop_list=str(start_stop_list)) + m = models.Markup( + note=note, + order=count, + styled_text_tag_type=models.get_type(models.StyledTextTagType, + markup_code, + get_or_create=False), + string=value, + start_stop_list=str(start_stop_list)) m.save() def add_note(self, data): diff --git a/src/webapp/shell.py b/src/webapp/shell.py index ba7aa9049..ad9e2354d 100644 --- a/src/webapp/shell.py +++ b/src/webapp/shell.py @@ -30,7 +30,7 @@ dp = parser.parse # "/home/dblank/gramps/trunk/example/gramps/data.gramps", # cli.user.User()) -from webapp.utils import StyledNoteFormatter +from webapp.utils import StyledNoteFormatter, parse_styled_text snf = StyledNoteFormatter(db) #for n in Note.objects.all(): # note = db.get_note_from_handle(n.handle) @@ -38,5 +38,9 @@ snf = StyledNoteFormatter(db) note = Note.objects.get(handle="aef30789d3d2090abe2") genlibnote = db.get_note_from_handle(note.handle) -print snf.format(genlibnote) -#st = gen.lib.StyledText(note.text, dji.get_note_markup(note)) +html_text = snf.format(genlibnote) +# FIXME: this looks wrong: +#print html_text +#print parse_styled_text(html_text) + +##st = gen.lib.StyledText(note.text, dji.get_note_markup(note)) diff --git a/src/webapp/utils.py b/src/webapp/utils.py index d1d07e8aa..c9b2f93bd 100644 --- a/src/webapp/utils.py +++ b/src/webapp/utils.py @@ -28,7 +28,9 @@ #------------------------------------------------------------------------ import locale import sys +import re import datetime +from HTMLParser import HTMLParser #------------------------------------------------------------------------ # @@ -744,6 +746,7 @@ class WebAppBackend(HtmlBackend): DocBackend.FONTFACE, DocBackend.FONTSIZE, DocBackend.FONTCOLOR, + DocBackend.SUPERSCRIPT, DocBackend.LINK, ] @@ -751,6 +754,7 @@ class WebAppBackend(HtmlBackend): DocBackend.BOLD : ("", ""), DocBackend.ITALIC : ("", ""), DocBackend.UNDERLINE : ('', ''), + DocBackend.SUPERSCRIPT : ("", ""), } ### Taken from Narrated Web Report @@ -761,53 +765,15 @@ class StyledNoteFormatter(object): self._backend.build_link = self.build_link def format(self, note): - return self.styled_note( - note.get_styledtext(), - note.get_format(), - contains_html=(note.get_type() == gen.lib.NoteType.HTML_CODE)) + return self.styled_note(note.get_styledtext()) - def styled_note(self, styledtext, format, contains_html=False): - """ - styledtext : assumed a StyledText object to write - format : = 0 : Flowed, = 1 : Preformatted - style_name : name of the style to use for default presentation - """ + def styled_note(self, styledtext): text = str(styledtext) if not text: return '' s_tags = styledtext.get_tags() - markuptext = self._backend.add_markup_from_styled(text, s_tags, - split='\n') - htmllist = Html("div") #"div", class_="grampsstylednote") - if contains_html: - htmllist += text - else: - linelist = [] - linenb = 1 - for line in markuptext.split('\n'): - [line, sigcount] = process_spaces(line, format) - if sigcount == 0: - # The rendering of an empty paragraph '

' - # is undefined so we use a non-breaking space - if linenb == 1: - linelist.append(' ') - htmllist.extend(Html('p') + linelist) - linelist = [] - linenb = 1 - else: - if linenb > 1: - linelist[-1] += '
' - linelist.append(line) - linenb += 1 - if linenb > 1: - htmllist.extend(Html('p') + linelist) - # if the last line was blank, then as well as outputting the previous para, - # which we have just done, - # we also output a new blank para - if sigcount == 0: - linelist = [" "] - htmllist.extend(Html('p') + linelist) - return "".join(htmllist) + markuptext = self._backend.add_markup_from_styled(text, s_tags, split='\n').replace("\n", "
") + return markuptext def build_link(self, prop, handle, obj_class): """ @@ -826,3 +792,117 @@ class StyledNoteFormatter(object): "in table name '%s'" % obj_class) # handle, ppl return "/%s/%s" % (obj_class.lower(), handle) + +class WebAppParser(HTMLParser): + BOLD = 0 + ITALIC = 1 + UNDERLINE = 2 + FONTFACE = 3 + FONTSIZE = 4 + FONTCOLOR = 5 + HIGHLIGHT = 6 + SUPERSCRIPT = 7 + LINK = 8 + + def __init__(self): + HTMLParser.__init__(self) + self.__text = "" + self.__tags = {} + self.__stack = [] + + def handle_data(self, data): + self.__text += data + + def push(self, pos, tag, attrs): + self.__stack.append([pos, tag, attrs]) + + def pop(self): + return self.__stack.pop() + + def handle_starttag(self, tag, attrs): + self.push(len(self.__text), tag.lower(), attrs) + + def handle_endtag(self, tag): + tag = tag.lower() + (start_pos, start_tag, attrs) = self.pop() + attrs = {x[0]: x[1] for x in attrs} + if tag != start_tag: return # skip formats + arg = None + tagtype = None + if tag == "span": + # "span": get color, font, size + if "style" in attrs: + style = attrs["style"] + if 'color' in style: + tagtype = self.FONTCOLOR + match = re.match("color:([^;]*);", style) + if match: + arg = match.groups()[0] + else: + print "Unhandled color tag: '%s'" % style + elif "font-family" in style: + tagtype = self.FONTFACE + match = re.match("font-family:'([^;]*)';", style) + if match: + arg = match.groups()[0] + else: + print "Unhandled font-family tag: '%s'" % style + elif "font-size" in style: + tagtype = self.FONTSIZE + match = re.match("font-size:([^;]*)px;", style) + if match: + arg = int(match.groups()[0]) + else: + print "Unhandled font-size tag: '%s'" % style + else: + print "Unhandled span arg: '%s'" % attrs + else: + print "span has no style: '%s'" % attrs + # "b", "i", "u", "sup": direct conversion + elif tag == "b": + tagtype = self.BOLD + elif tag == "i": + tagtype = self.ITALIC + elif tag == "u": + tagtype = self.UNDERLINE + elif tag == "sup": + tagtype = self.SUPERSCRIPT + elif tag == "br": + self.__text += "\n" + return + elif tag == "p": + self.__text += "\n\n" + return + elif tag == "a": + tagtype = self.LINK + # "a": get /object/handle, or url + if "href" in attrs: + href = attrs["href"] + if href.startswith("/"): + parts = href.split("/") + arg = "gramps://%s/handle/%s" % (parts[-2].title(), parts[-1]) + else: + arg = href + else: + print "Unhandled a with no href: '%s'" % attrs + else: + return + print "Unhandled tag: '%s'" % tag + key = ((tagtype, u''), arg) + if key not in self.__tags: + self.__tags[key] = [] + self.__tags[key].append((start_pos, len(self.__text))) + + def tags(self): + # [((code, u''), string/num, [(start, stop), ...]), ...] + return [(key[0], key[1], self.__tags[key]) for key in self.__tags] + + def text(self): + return self.__text + +def parse_styled_text(text): + parser = WebAppParser() + parser.feed(text) + parser.close() + return (parser.text(), parser.tags()) +