Skip to content

Commit

Permalink
Merge pull request #19 from Jenselme/toc
Browse files Browse the repository at this point in the history
Add a basic support for automatic table of content generation
  • Loading branch information
jedie committed May 12, 2014
2 parents 9f5a403 + b01ca17 commit f24b5ed
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 45 deletions.
62 changes: 59 additions & 3 deletions creole/creole2html/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,6 +59,24 @@ def html_escape(self, text):
def attr_escape(self, text):
return self.html_escape(text).replace('"', '&quot')

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):
Expand Down Expand Up @@ -138,8 +163,16 @@ def delete_emit(self, node):
#--------------------------------------------------------------------------

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)
Expand Down Expand Up @@ -274,9 +307,32 @@ def emit_node(self, node):
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):
"""
Expand Down
102 changes: 60 additions & 42 deletions creole/tests/test_creole2html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -28,6 +28,7 @@

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
Expand Down Expand Up @@ -57,12 +58,20 @@ def test_stderr(self):
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)
Expand Down Expand Up @@ -194,7 +203,6 @@ def test(text, foo):




class TestCreole2htmlMarkup(BaseCreoleTest):

def test_creole_basic(self):
Expand Down Expand Up @@ -226,16 +234,16 @@ def test_html_lines(self):
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 &lt; and &gt; ;)</p>
<p>So you can't insert &lt;html&gt; directly.</p>
<p>&lt;p&gt;This escaped, too.&lt;/p&gt;</p>
""")

Expand All @@ -258,28 +266,28 @@ def test_cross_paragraphs(self):
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
** Item 1.1
** Item 1.2
** Item 1.3
* Item2
# one
## two
""", """
Expand Down Expand Up @@ -309,7 +317,7 @@ def test_macro_basic(self):
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
Expand All @@ -321,7 +329,7 @@ def test_macro_basic(self):
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>
Expand All @@ -335,12 +343,12 @@ def test_macro_html1(self):
<<html>>
<p><<this is broken 'html', but it will be pass throu>></p>
<</html>>
inline: <<html>>&#x7B;...&#x7D;<</html>> code
""", r"""
<p>html macro:</p>
<p><<this is broken 'html', but it will be pass throu>></p>
<p>inline: &#x7B;...&#x7D; code</p>
""",
macros=example_macros,
Expand All @@ -350,7 +358,7 @@ def test_macro_not_exist1(self):
"""
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"
"""
Expand All @@ -359,14 +367,14 @@ def test_macro_not_exist1(self):
<<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>
Expand All @@ -391,25 +399,35 @@ def test_wrong_macro_syntax(self):
def test_macro_not_exist2(self):
"""
not existing macro with creole2html.HtmlEmitter(verbose=0):
No error messages should be inserted.
"""
self.assert_creole2html(r"""
macro block:
<<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"""
Expand Down Expand Up @@ -446,7 +464,7 @@ def test_links(self):

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>
"""
Expand All @@ -457,14 +475,14 @@ def test_wiki_style_line_breaks1(self):
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>
"""))

Expand All @@ -473,15 +491,15 @@ def test_wiki_style_line_breaks2(self):
markup_string=self._prepare_text("""
**one**
//two//
* one
* two
"""),
parser_kwargs={"blog_line_breaks":False},
)
self.assertEqual(html, self._prepare_text("""
<p><strong>one</strong> <i>two</i></p>
<ul>
\t<li>one</li>
\t<li>two</li>
Expand All @@ -493,29 +511,29 @@ def test_wiki_style_line_breaks3(self):
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&lt;br /&gt; 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>
"""))

Expand Down

0 comments on commit f24b5ed

Please sign in to comment.