""" Chris Clark - clach04 -at- sf.net
My markdown extensions for adding:
Table of Contents (aka toc)
"""
import os import sys import re import markdown
DEFAULT_TITLE = None
def extractalphanumeric(instr=None):
"""take alpha-numeric (7bit ascii) and return as a string
"""
# I'm sure this is really inefficient and
# could be done with a lambda/map()
#x.strip().title().replace(' ', "")
out_str=[]
for x in in_str.title():
if x.isalnum(): out_str.append(x)
return ''.join(out_str)
class TitlePostprocessor (markdown.Postprocessor):
def __init__ (self, extension) :
self.extension = extension
def run(self, doc) :
titleElement = self.extension.createTitle(doc)
if titleElement :
doc.documentElement.insertChild(0, titleElement)
class TocExtension (markdown.Extension):
"""Markdown extension: generate a Table Of Contents (aka toc)
toc is returned in a div tag with class='toc'
toc is either:
appended to end of document
OR
replaces first string occurence of "///Table of Contents Goes Here///"
"""
def __init__ (self) :
#maybe add these as parameters to the class init?
self.TOC_INCLUDE_MARKER = "///Table of Contents Goes Here///"
self.TOC_TITLE = "Table Of Contents"
self.include_header_one_in_toc = True
self.include_header_one_in_toc = False
self.toc_heading_type=2
def extendMarkdown(self, md, md_globals) :
# Just insert in the end
md.postprocessors.append(TocPostprocessor(self))
# Stateless extensions do not need to be registered, so we don't
# register.
def findTocPlaceholder(self, doc) :
def findTocPlaceholderFn(node=None, indent=0):
if node.type == 'text':
if node.value.find(self.TOC_INCLUDE_MARKER) > -1 :
return True
toc_div_list = doc.find(findTocPlaceholderFn)
if toc_div_list :
return toc_div_list[0]
def createTocDiv(self, doc) :
"""
Creates Table Of Contents based on headers.
@returns: toc as a single as a dom element
in a <div> tag with class='toc'
"""
# Find headers
headers_compiled_re = compile_obj = re.compile("h[123456]",
re.IGNORECASE)
def findHeadersFn(element=None):
if element.type=='element':
if headers_compiled_re.match(element.nodeName):
return True
headers_doc_list = doc.find(findHeadersFn)
# Insert anchor tags into dom
generated_anchor_id=0
headers_list=[]
min_header_size_found = 6
for element in headers_doc_list:
heading_title = element.childNodes[0].value
if heading_title.strip() !="":
heading_type = int(element.nodeName[-1:])
if heading_type!=1:
min_header_size_found=min(min_header_size_found,
heading_type)
elif self.include_header_one_in_toc:
min_header_size_found=min(min_header_size_found,
heading_type)
html_anchor_name= (extract_alphanumeric(heading_title)
+'__MD_autoTOC_%d' % (generated_anchor_id))
# insert anchor tag inside header tags
html_anchor = doc.createElement("a")
html_anchor.setAttribute('name', html_anchor_name)
element.appendChild(html_anchor)
headers_list.append( (heading_type, heading_title,
html_anchor_name) )
generated_anchor_id = generated_anchor_id + 1
# create dom for toc
if headers_list != []:
# Create list
toc_doc_list = doc.createElement("ul")
for (heading_type, heading_title, html_anchor_name) in headers_list:
if self.include_header_one_in_toc or heading_type !=1 :
toc_doc_entry = doc.createElement("li")
toc_doc_link = doc.createElement("a")
toc_doc_link.setAttribute('href', '#'+html_anchor_name)
toc_doc_text = doc.createTextNode(heading_title)
toc_doc_link.appendChild(toc_doc_text)
toc_doc_entry.appendChild(toc_doc_link)
lowest_toc_doc_indent = doc.createElement("ul")
previous_toc_doc_indent = None
toc_doc_indent=None
indent_amt = heading_type - min_header_size_found
indent_list=range(0, indent_amt)
for x in indent_list:
toc_doc_indent = doc.createElement("ul")
if previous_toc_doc_indent is None:
toc_doc_indent.appendChild(lowest_toc_doc_indent)
else:
toc_doc_indent.appendChild(previous_toc_doc_indent)
previous_toc_doc_indent = toc_doc_indent
if toc_doc_indent is None:
toc_doc_indent = lowest_toc_doc_indent
lowest_toc_doc_indent.appendChild(toc_doc_entry)
toc_doc_list.appendChild(toc_doc_indent)
# Put list into div
div = doc.createElement("div")
div.setAttribute('class', 'toc')
if self.TOC_TITLE:
toc_header = doc.createElement("h%d"%(self.toc_heading_type) )
toc_header_text = doc.createTextNode(self.TOC_TITLE)
toc_header.appendChild(toc_header_text)
div.appendChild(toc_header)
div.appendChild(toc_doc_list)
#hr = doc.createElement("hr")
#div.appendChild(hr)
return div
class TocPostprocessor (markdown.Postprocessor):
def __init__ (self, toc) :
self.toc = toc
def run(self, doc):
tocPlaceholder = self.toc.findTocPlaceholder(doc)
tocDiv = self.toc.createTocDiv(doc)
if tocDiv:
if tocPlaceholder :
# Replace "magic" pattern with toc
tocPlaceholder.parent.replaceChild(tocPlaceholder, tocDiv)
else :
# Dump at the end of the DOM
# Probably want to use CSS to position div
doc.documentElement.appendChild(tocDiv)
def makeExtension(configs=None) :
return TocExtension()