Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a basic support for automatic table of content generation #19

Merged
merged 4 commits into from
May 12, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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