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

[WIP] Implement inline-toc extra for TOC HTML insertion after first heading #280

Closed
wants to merge 3 commits into from
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 45 additions & 36 deletions lib/markdown2.py
Expand Up @@ -241,7 +241,7 @@ def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
extras = dict([(e, None) for e in extras])
self.extras.update(extras)
assert isinstance(self.extras, dict)
if "toc" in self.extras and "header-ids" not in self.extras:
if ("toc" in self.extras or "inline-toc" in self.extras) and "header-ids" not in self.extras:
self.extras["header-ids"] = None # "toc" implies "header-ids"
self._instance_extras = self.extras.copy()

Expand Down Expand Up @@ -390,8 +390,16 @@ def convert(self, text):

text += "\n"

# Insert TOC HTML into the MD HTML after the first heading element if any headings are present.
if ("inline-toc" in self.extras and self._toc[0] is not None):
(level, id, name) = self._toc[0]
# Use a regex and rely on the HTML structure as opposed to tracking the heading regex's `end()` across all the HTML transformations (unreliable)
# TODO (Tomas Hubelbauer): Consider looser regex which allows for more attributes in order to to find heading even when more extras add attributes to it (future-proof)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am considering not doing that now because it feels premature. @nicholasserra thoughts?

pattern = r"\<h{} id=[\"\']{}[\"\']\>{}<\/h{}\>".format(level, id, re.escape(name), level)
text = re.sub(pattern, "\g<0>\n" + toc_html(self._toc), text)

rv = UnicodeWithAttrs(text)
if "toc" in self.extras:
if ("toc" in self.extras or "inline-toc" in self.extras):
rv._toc = self._toc
if "metadata" in self.extras:
rv.metadata = self.metadata
Expand Down Expand Up @@ -1540,7 +1548,7 @@ def _h_sub(self, match):
if header_id:
header_id_attr = ' id="%s"' % header_id
html = self._run_span_gamut(header_group)
if "toc" in self.extras and header_id:
if ("toc" in self.extras or "inline-toc" in self.extras) and header_id:
self._toc_add_entry(n, header_id, html)
return "<h%d%s>%s</h%d>\n\n" % (n, header_id_attr, html, n)

Expand Down Expand Up @@ -2222,46 +2230,47 @@ class MarkdownWithExtras(Markdown):

# ---- internal support functions

def toc_html(toc):
"""Return the HTML for the current TOC.

This expects the `_toc` attribute to have been set on this instance.
"""
if toc is None:
return None

def indent():
return ' ' * (len(h_stack) - 1)
lines = []
h_stack = [0] # stack of header-level numbers
for level, id, name in toc:
if level > h_stack[-1]:
lines.append("%s<ul>" % indent())
h_stack.append(level)
elif level == h_stack[-1]:
lines[-1] += "</li>"
else:
while level < h_stack[-1]:
h_stack.pop()
if not lines[-1].endswith("</li>"):
lines[-1] += "</li>"
lines.append("%s</ul></li>" % indent())
lines.append('%s<li><a href="#%s">%s</a>' % (
indent(), id, name))
while len(h_stack) > 1:
h_stack.pop()
if not lines[-1].endswith("</li>"):
lines[-1] += "</li>"
lines.append("%s</ul>" % indent())
return '\n'.join(lines) + '\n'

class UnicodeWithAttrs(unicode):
"""A subclass of unicode used for the return value of conversion to
possibly attach some attributes. E.g. the "toc_html" attribute when
the "toc" extra is used.
"""
metadata = None
_toc = None
def toc_html(self):
"""Return the HTML for the current TOC.

This expects the `_toc` attribute to have been set on this instance.
"""
if self._toc is None:
return None

def indent():
return ' ' * (len(h_stack) - 1)
lines = []
h_stack = [0] # stack of header-level numbers
for level, id, name in self._toc:
if level > h_stack[-1]:
lines.append("%s<ul>" % indent())
h_stack.append(level)
elif level == h_stack[-1]:
lines[-1] += "</li>"
else:
while level < h_stack[-1]:
h_stack.pop()
if not lines[-1].endswith("</li>"):
lines[-1] += "</li>"
lines.append("%s</ul></li>" % indent())
lines.append('%s<li><a href="#%s">%s</a>' % (
indent(), id, name))
while len(h_stack) > 1:
h_stack.pop()
if not lines[-1].endswith("</li>"):
lines[-1] += "</li>"
lines.append("%s</ul>" % indent())
return '\n'.join(lines) + '\n'
toc_html = property(toc_html)
toc_html = property(toc_html(_toc))

## {{{ http://code.activestate.com/recipes/577257/ (r1)
_slugify_strip_re = re.compile(r'[^\w\s-]')
Expand Down