diff options
author | Jens Diemer <github.com@jensdiemer.de> | 2014-05-12 22:00:30 +0200 |
---|---|---|
committer | Jens Diemer <github.com@jensdiemer.de> | 2014-05-12 22:00:30 +0200 |
commit | f24b5edc8ca04800c730177c4218381ac929ce53 (patch) | |
tree | 7289cc92c1fa3f6d15569653169f261827d6a79f | |
parent | 9f5a403b68683a43ae6cf8b2bbeb957568dc936b (diff) | |
parent | b01ca17b71ce26dacce79eb9b07fedf5e890b5cc (diff) | |
download | creole-f24b5edc8ca04800c730177c4218381ac929ce53.tar.gz |
Merge pull request #19 from Jenselme/toc
Add a basic support for automatic table of content generation
-rw-r--r-- | creole/creole2html/emitter.py | 62 | ||||
-rw-r--r-- | creole/tests/test_creole2html.py | 102 |
2 files changed, 119 insertions, 45 deletions
diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index ab5b57b..44ca6f4 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -28,6 +28,13 @@ class HtmlEmitter(object): def __init__(self, root, macros=None, verbose=None, stderr=None): self.root = root self.macros = macros + self.has_toc = False + self.toc_max_depth = None + + if self.macros is None: + self.macros = {'toc': self.create_toc} + elif isinstance(self.macros, dict): + self.macros['toc'] = self.create_toc if verbose is None: self.verbose = 1 @@ -52,6 +59,24 @@ class HtmlEmitter(object): def attr_escape(self, text): return self.html_escape(text).replace('"', '"') + def create_toc(self, depth=None, **kwargs): + """Called when if the macro <<toc>> is defined when it is emitted.""" + self.has_toc = True + self.toc_max_depth = depth + self.toc = ['root', []] + + return '<<toc>>' + + def update_toc(self, level, content): + """Add the current header to the toc.""" + if self.toc_max_depth is None or level < self.toc_max_depth: + current_level = 0 + toc_node = self.toc + while current_level != level: + toc_node = toc_node[-1] + current_level += 1 + toc_node.extend([self.html_escape(content), []]) + # *_emit methods for emitting nodes of the document: def document_emit(self, node): @@ -138,8 +163,16 @@ class HtmlEmitter(object): #-------------------------------------------------------------------------- def header_emit(self, node): - return '<h%d>%s</h%d>\n' % ( - node.level, self.html_escape(node.content), node.level) + header = '<h%d>%s</h%d>\n' % ( + node.level, self.html_escape(node.content), node.level) + if self.has_toc: + self.update_toc(node.level, node.content) + # add link attribute for toc navigation + header = '<a name="%s">%s</a>' % ( + self.html_escape(node.content), header) + return header + else: + return header def preformatted_emit(self, node): return '<pre>%s</pre>' % self.html_escape(node.content) @@ -274,9 +307,32 @@ class HtmlEmitter(object): emit = getattr(self, '%s_emit' % node.kind, self.default_emit) return emit(node) + def toc_list2html(self, toc_list): + """Convert a python nested list like the one representing the toc to an html equivalent.""" + if toc_list: + if isinstance(toc_list, TEXT_TYPE): + return '<li><a href="#%s">%s</a> </li>\n' % (toc_list, toc_list) + elif isinstance(toc_list, list): + html = '<ul>' + for elt in toc_list: + html += self.toc_list2html(elt) + html += '</ul>\n' + return html + else: + return '' + + def toc_emit(self, document): + """Emit the toc where the <<toc>> macro was.""" + html_toc = self.toc_list2html(self.toc[-1]) + return document.replace('<p><<toc>></p>', html_toc, 1) + def emit(self): """Emit the document represented by self.root DOM tree.""" - return self.emit_node(self.root).strip() + document = self.emit_node(self.root).strip() + if self.has_toc: + return self.toc_emit(document) + else: + return document def error(self, text, exc_info=None): """ diff --git a/creole/tests/test_creole2html.py b/creole/tests/test_creole2html.py index 9a88e44..45e73dd 100644 --- a/creole/tests/test_creole2html.py +++ b/creole/tests/test_creole2html.py @@ -4,9 +4,9 @@ """ creole2html unittest ~~~~~~~~~~~~~~~~~~~~ - + Here are only some tests witch doesn't work in the cross compare tests. - + Info: There exist some situations with different whitespace handling between creol2html and html2creole. @@ -28,6 +28,7 @@ except ImportError: from creole.tests.utils.base_unittest import BaseCreoleTest from creole.tests import test_macros +from creole.py3compat import PY3 from creole import creole2html from creole.shared import example_macros @@ -57,12 +58,20 @@ class TestCreole2html(unittest.TestCase): error_msg = my_stderr.getvalue() # Check if we get a traceback information into our stderr handler - must_have = ( - "Traceback", - "AttributeError:", - "has no attribute 'notexist1'", - "has no attribute 'notexist2'", - ) + if PY3: + must_have = ( + "Traceback", + "KeyError:", + "KeyError: 'notexist1'", + "KeyError: 'notexist2'", + ) + else: + must_have = ( + "Traceback", + "KeyError:", + "KeyError: u'notexist1'", + "KeyError: u'notexist2'", + ) for part in must_have: self.assertIn(part, error_msg) @@ -194,7 +203,6 @@ class TestCreole2html(unittest.TestCase): - class TestCreole2htmlMarkup(BaseCreoleTest): def test_creole_basic(self): @@ -226,16 +234,16 @@ class TestCreole2htmlMarkup(BaseCreoleTest): self.assert_creole2html(r""" This is a normal Text block witch would escape html chars like < and > ;) - + So you can't insert <html> directly. - + <p>This escaped, too.</p> """, """ <p>This is a normal Text block witch would<br /> escape html chars like < and > ;)</p> - + <p>So you can't insert <html> directly.</p> - + <p><p>This escaped, too.</p></p> """) @@ -258,20 +266,20 @@ class TestCreole2htmlMarkup(BaseCreoleTest): self.assert_creole2html(r""" Bold and italics should //not be... - ...able// to **cross - + ...able// to **cross + paragraphs.** """, """ <p>Bold and italics should //not be...</p> - + <p>...able// to **cross</p> - + <p>paragraphs.**</p> """) def test_list_special(self): """ - optional whitespace before the list + optional whitespace before the list """ self.assert_creole2html(r""" * Item 1 @@ -279,7 +287,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): ** Item 1.2 ** Item 1.3 * Item2 - + # one ## two """, """ @@ -309,7 +317,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): A <<test_macro1 args="foo1">>bar1<</test_macro1>> in a line... ...a single <<test_macro1 foo="bar">> tag, or: <<test_macro1 a=1 b=2 />> closed... - + a macro block: <<test_macro2 char="|">> the @@ -321,7 +329,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): A [test macro1 - kwargs: args='foo1',text='bar1'] in a line...<br /> ...a single [test macro1 - kwargs: foo='bar',text=None] tag,<br /> or: [test macro1 - kwargs: a=1,b=2,text=None] closed...</p> - + <p>a macro block:</p> the|text <p>the end</p> @@ -335,12 +343,12 @@ class TestCreole2htmlMarkup(BaseCreoleTest): <<html>> <p><<this is broken 'html', but it will be pass throu>></p> <</html>> - + inline: <<html>>{...}<</html>> code """, r""" <p>html macro:</p> <p><<this is broken 'html', but it will be pass throu>></p> - + <p>inline: {...} code</p> """, macros=example_macros, @@ -350,7 +358,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): """ not existing macro with creole2html.HtmlEmitter(verbose=1): A error message should be insertet into the generated code - + Two tests: with verbose=1 and verbose=2, witch write a Traceback information to a given "stderr" """ @@ -359,14 +367,14 @@ class TestCreole2htmlMarkup(BaseCreoleTest): <<notexists>> foo bar <</notexists>> - + inline macro: <<notexisttoo foo="bar">> """ should_string = r""" <p>macro block:</p> [Error: Macro 'notexists' doesn't exist] - + <p>inline macro:<br /> [Error: Macro 'notexisttoo' doesn't exist] </p> @@ -391,7 +399,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): def test_macro_not_exist2(self): """ not existing macro with creole2html.HtmlEmitter(verbose=0): - + No error messages should be inserted. """ self.assert_creole2html(r""" @@ -399,17 +407,27 @@ class TestCreole2htmlMarkup(BaseCreoleTest): <<notexists>> foo bar <</notexists>> - + inline macro: <<notexisttoo foo="bar">> """, r""" <p>macro block:</p> - + <p>inline macro:<br /> </p> """, verbose=False ) + + def test_toc(self): + """ + Simple test to check the table of content is correctly generated. + """ + html = creole2html("""<<toc>>\n= Creole""") + self.assertEqual(html, + """<ul><li><a href="#Creole">Creole</a> </li>\n</ul>\n\n<a name="Creole"><h1>Creole</h1>\n</a>""") + + def test_image(self): """ test image tag with different picture text """ self.assert_creole2html(r""" @@ -446,7 +464,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): def test_standalone_hyperlink(self): self.assert_creole2html(r""" - a link to the http://www.pylucid.org page. + a link to the http://www.pylucid.org page. """, """ <p>a link to the <a href="http://www.pylucid.org">http://www.pylucid.org</a> page.</p> """ @@ -457,14 +475,14 @@ class TestCreole2htmlMarkup(BaseCreoleTest): markup_string=self._prepare_text(""" wiki style linebreaks - + ...and not blog styled. """), parser_kwargs={"blog_line_breaks":False}, ) self.assertEqual(html, self._prepare_text(""" <p>wiki style linebreaks</p> - + <p>...and not blog styled.</p> """)) @@ -473,7 +491,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): markup_string=self._prepare_text(""" **one** //two// - + * one * two """), @@ -481,7 +499,7 @@ class TestCreole2htmlMarkup(BaseCreoleTest): ) self.assertEqual(html, self._prepare_text(""" <p><strong>one</strong> <i>two</i></p> - + <ul> \t<li>one</li> \t<li>two</li> @@ -493,29 +511,29 @@ class TestCreole2htmlMarkup(BaseCreoleTest): markup_string=self._prepare_text(""" with blog line breaks, every line break would be convertet into<br /> with wiki style not. - + This is the first line,\\\\and this is the second. - + new line block 1 - + new line block 2 - + end """), parser_kwargs={"blog_line_breaks":False}, ) self.assertEqual(html, self._prepare_text(""" <p>with blog line breaks, every line break would be convertet into<br /> with wiki style not.</p> - + <p>This is the first line,<br /> and this is the second.</p> - + <p>new line block 1</p> - + <p>new line block 2</p> - + <p>end</p> """)) |