Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updates to parsing back/forth between Creole and Tkinter Text widget.…
… Now handles hyperlinks, numbered lists.
- Loading branch information
bcorfman
committed
Jan 18, 2011
1 parent
8d4421b
commit 5c199c0
Showing
1 changed file
with
162 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,124 +1,194 @@ | ||
from Tkconstants import * | ||
from parser import Parser | ||
|
||
from parser import Parser | ||
from creoleparser import Parser | ||
from rules import LinkRules | ||
|
||
|
||
class TextTagEmitter(object): | ||
""" | ||
Generate tagged output compatible with the Tkinter Text widget | ||
""" | ||
|
||
def __init__(self, root, link_rules=None): | ||
def __init__(self, root, txtWidget, hyperMgr, link_rules=None): | ||
self.root = root | ||
self.link_rules = link_rules or LinkRules() | ||
|
||
# *_emit methods for emitting nodes of the document: | ||
def document_emit(self, node): | ||
return self.emit_children(node) | ||
|
||
def text_emit(self, node): | ||
return node.content | ||
|
||
def separator_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def paragraph_emit(self, node): | ||
return u'%s\n' % self.emit_children(node) | ||
|
||
def bullet_list_emit(self, node): | ||
return self.emit_children(node) | ||
|
||
def number_list_emit(self, node): | ||
return self.emit_children(node) | ||
|
||
def list_item_emit(self, node): | ||
return self.emit_children(node) | ||
|
||
def table_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def table_row_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def table_cell_emit(self, node): | ||
self.txtWidget = txtWidget | ||
self.hyperMgr = hyperMgr | ||
self.line = 1 | ||
self.index = 0 | ||
self.number = 1 | ||
self.begin_italic = '' | ||
self.begin_bold = '' | ||
self.begin_list_item = '' | ||
self.list_item = '' | ||
|
||
# visit/leave methods for emitting nodes of the document: | ||
def visit_document(self, node): | ||
pass | ||
|
||
def leave_document(self, node): | ||
# leave_paragraph always leaves two extra carriage returns at the | ||
# end of the text. This deletes them. | ||
txtindex = '%d.%d' % (self.line-1, self.index) | ||
self.txtWidget.delete(txtindex, END) | ||
|
||
def visit_text(self, node): | ||
if self.begin_list_item: | ||
self.list_item = node.content | ||
else: | ||
txtindex = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.insert(txtindex, node.content) | ||
|
||
def leave_text(self, node): | ||
if not self.begin_list_item: | ||
self.index += len(node.content) | ||
|
||
def visit_separator(self, node): | ||
raise NotImplementedError | ||
|
||
def table_head_emit(self, node): | ||
def leave_separator(self, node): | ||
raise NotImplementedError | ||
|
||
def emphasis_emit(self, node): | ||
return [('tagon','italics',node.index), self.emit_children(node), | ||
('tagoff','italics',node.index)] | ||
def visit_paragraph(self, node): | ||
pass | ||
|
||
def strong_emit(self, node): | ||
return [('tagon','bold',node.index), self.emit_children(node), | ||
('tagoff','bold',node.index)] | ||
def leave_paragraph(self, node): | ||
txtindex = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.insert(txtindex, '\n\n') | ||
self.line += 2 | ||
self.index = 0 | ||
self.number = 1 | ||
|
||
def header_emit(self, node): | ||
def visit_bullet_list(self, node): | ||
raise NotImplementedError | ||
|
||
def code_emit(self, node): | ||
def leave_bullet_list(self, node): | ||
raise NotImplementedError | ||
|
||
def link_emit(self, node): | ||
return '' | ||
|
||
def image_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def macro_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def break_emit(self, node): | ||
raise NotImplementedError | ||
|
||
def preformatted_emit(self, node): | ||
raise NotImplementedError | ||
def visit_number_list(self, node): | ||
self.number = 1 | ||
|
||
def leave_number_list(self, node): | ||
txtindex = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.insert(txtindex, '\n') | ||
self.line += 1 | ||
self.number = 0 | ||
|
||
def visit_list_item(self, node): | ||
self.begin_list_item = '%d.%d' % (self.line, self.index) | ||
|
||
def leave_list_item(self, node): | ||
content = '%d. %s\n' % (self.number, self.list_item) | ||
txtindex = '%d.%d' % (self.line, self.index) | ||
end_list_item = '%d.%d' % (self.line, self.index + len(content)) | ||
self.txtWidget.insert(self.begin_list_item, content) | ||
self.txtWidget.tag_add('number', self.begin_list_item, end_list_item) | ||
self.begin_list_item = '' | ||
self.list_item = '' | ||
self.number += 1 | ||
self.line += 1 | ||
self.index = 0 | ||
|
||
def visit_emphasis(self, node): | ||
self.begin_italic = '%d.%d' % (self.line, self.index) | ||
|
||
def leave_emphasis(self, node): | ||
end_italic = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.tag_add('italic', self.begin_italic, end_italic) | ||
|
||
def visit_strong(self, node): | ||
self.begin_bold = '%d.%d' % (self.line, self.index) | ||
|
||
def leave_strong(self, node): | ||
end_bold = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.tag_add('bold', self.begin_bold, end_bold) | ||
|
||
def visit_link(self, node): | ||
self.begin_link = '%d.%d' % (self.line, self.index) | ||
|
||
def leave_link(self, node): | ||
end_link = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.insert(self.begin_link, node.children[0].content, | ||
self.hyperMgr.add(str(node.content))) | ||
|
||
def visit_break(self, node): | ||
txtindex = '%d.%d' % (self.line, self.index) | ||
self.txtWidget.insert(txtindex, '\n') | ||
|
||
def leave_break(self, node): | ||
self.line += 1 | ||
self.index = 0 | ||
|
||
def visit_default(self, node): | ||
"""Fallback function for visiting unknown nodes.""" | ||
raise TypeError | ||
|
||
def default_emit(self, node): | ||
"""Fallback function for emitting unknown nodes.""" | ||
def leave_default(self, node): | ||
"""Fallback function for leaving unknown nodes.""" | ||
raise TypeError | ||
|
||
def emit_children(self, node): | ||
"""Emit all the children of a node.""" | ||
return u''.join([self.emit_node(child) for child in node.children]) | ||
|
||
def dump_text_widget_to_rst(txtWidget, index1='1.0', index2=END): | ||
# outputs contents from Text widget in RestructuredText format. | ||
rst = "" | ||
tags = {} | ||
try: | ||
for key, value, index in txtWidget.dump(index1, index2): | ||
for child in node.children: | ||
self.emit_node(child) | ||
|
||
def emit_node(self, node): | ||
"""Visit/depart a single node and its children.""" | ||
visit = getattr(self, 'visit_%s' % node.kind, self.visit_default) | ||
visit(node) | ||
self.emit_children(node) | ||
leave = getattr(self, 'leave_%s' % node.kind, self.leave_default) | ||
leave(node) | ||
|
||
def emit(self): | ||
"""Emit the document represented by self.root DOM tree.""" | ||
return self.emit_node(self.root) | ||
|
||
class Serializer(object): | ||
def __init__(self, txtWidget, hyperMgr): | ||
self.txt = txtWidget | ||
self.hyperMgr = hyperMgr | ||
self.number = 0 | ||
self.filename = '' | ||
self.link_start = False | ||
|
||
def dump(self, index1='1.0', index2=END): | ||
# outputs contents from Text widget in Creole format. | ||
creole = '' | ||
#try: | ||
for key, value, index in self.txt.dump(index1, index2): | ||
if key == 'tagon': | ||
if value == 'bold': | ||
rst += '**' | ||
creole += '**' | ||
elif value == 'italic': | ||
rst += '*' | ||
creole += '//' | ||
elif value == 'bullet': | ||
rst += '-' | ||
elif value == 'link': | ||
if not tags.get(value): | ||
tagname = value | ||
tags[tagname] = [] | ||
for item in txtWidget.tag_configure(tagname): | ||
value = item[4] | ||
if len(value) > 0: | ||
option = item[0] | ||
tags[tagname].append((option, value)) | ||
creole += '*' | ||
elif value.startswith('hyper-'): | ||
self.filename = self.hyperMgr.filenames[value] | ||
self.link_start = True | ||
elif value == 'number': | ||
creole += '#' | ||
self.number += 1 | ||
elif key == 'tagoff': | ||
if value == 'bold': | ||
rst += '**' | ||
creole += '**' | ||
elif value == 'italic': | ||
rst += '*' | ||
elif value == 'link': | ||
rst += '<link>' | ||
creole += '//' | ||
elif value.startswith('hyper-'): | ||
creole += ']]' | ||
elif key == 'text': | ||
rst += value | ||
finally: | ||
return rst | ||
|
||
def restore_text_widget_from_rst(rst, txtWidget): | ||
document = Parser(unicode(rst, 'utf-8', 'ignore')).parse() | ||
return TextTagEmitter(document).emit().encode('utf-8', 'ignore') | ||
if self.number: | ||
numstr = '%d.' % self.number | ||
if value.startswith(numstr): | ||
value = value.replace(numstr, '', 1) | ||
elif value != '\n': | ||
self.number = 0 | ||
if self.link_start: | ||
value = '[[%s|%s' % (self.filename, value) | ||
self.link_start = False | ||
creole += value | ||
#finally: | ||
return creole | ||
|
||
def restore(self, creole): | ||
self.hyperMgr.reset() | ||
document = Parser(unicode(creole, 'utf-8', 'ignore')).parse() | ||
return TextTagEmitter(document, self.txt, self.hyperMgr).emit() |