Login or register Large RSS Icon

Writing Extensions

The functionality of the script can be extended without changing the code, by inserting additional pre-processors, post-processors or inline patterns into Markdown's pipeline.

Pre-processors operate on lines of source text and are run in the beginning. It is sufficient to write a class with a run() method that takes a list of text lines as a parameter and returns the same or a new list. An instance of the preprocessor can then be inserted into the markdown pipeline. Preprocessor classes must extend markdown.Preprocessor.

class SamplePreprocessor (markdown.Preprocessor) :
    def run(self, lines) :
        for i in range(len(lines)) :
            if lines[i].startswith(SOMETHING) :
                lines[i] = do_something(lines[i])

        return lines

md_instance.preprocessors.insert(SOME_INDEX, SamplePreprocessor())

Post-processors operate on a NanoDom tree and run at the very end. They need to implement a "run" method that takes a pointer to a NanoDom document. Postprocessor classes must extend markdown.Postprocessor.

class SamplePostprocessor (markdown.Postprocessor):
    def run(self, doc) :
        doc.documentElement.appendChild(doc.createElement("new"))


md_instance.postprocessors.insert(SOME_INDEX, SamplePostprocessor())

Finally, inline patterns can be added. Each inline pattern includes a regular expression pattern and a method for turning a match object into a DOM fragment

class MyCustomPattern (markdown.Pattern) :
    def handleMatch(self, m, doc) :
        el = doc.createElement('custom')
        el.setAttribute('stuff', m.group(3)) 
        return el

new_pattern = MyCustomPattern(r'your regular expression here')
md_instance.inlinePatterns.insert(SOME_INDEX, new_pattern)

Preprocessors, patterns and postprocessors need to then be wrapped together into an extension, which should be implemented as a class that extends markdown.Extension and should over-ride the extendMarkdown() method. extendMarkdown() is called during markdown construction, giving the extension a pointer to the markdown object (the md parameters) and markdown's global variables (md_globals), which in theory gives the extension an option of changing anything it cares to change. However, what it really should be doing is inserting preprocessors, postprocessors and patterns into the markdown pipeline:

def extendMarkdown(self, md, md_globals) :

    self.md = md

    # Insert a preprocessor before ReferencePreprocessor
    index = md.preprocessors.index(md_globals['REFERENCE_PREPROCESSOR'])
    preprocessor = FootnotePreprocessor(self)
    preprocessor.md = md
    md.preprocessors.insert(index, preprocessor)

If the extension uses any parameters that the use may want to change, they should be stored in self.config in the following format:

self.config = {parameter_1_name : [value1, description1],
               parameter_2_name : [value2, description2] }

When stored this way the config parameters can be over-riden from the command line or at the time Markdown is instantiated:

markdown.py -x myextension(SOME_PARAM=2) inputfile.txt > output.txt

Note that parameters should always be assumed to be set to string values, and should be converted at the run time, e.g.

i += int(self.getConfig("SOME_PARAM"))

Each extension should ideally be placed in its own module starting with mdx_ prefix (e.g. mdx_footnotes.py). The module must provide a module-level function called makeExtensions that takes as an optional parameter a list of configuration over-rides and returns an instance of the extension. E.g.:

def makeExtension(configs=None) :
return FootnoteExtension(configs=configs)

See the implementation of footnote support distributed with the markdown script (mdxfootnotes.py) for an example of using all three in combination to provide reasonably complex additional functionality. (I use a pre-processor to collect footnote definitions, an inline pattern to handle uses of footnotes inside the text and a post-processor to attach the HTML of the actual footnotes at the end of the document) The RSS extension (mdxrss.py, also included), which generates an RSS file from markdown source is a simpler extension but provides an example of extending markdown to generate arbitrary XML rather than HTML.

See Extensions for the list of known extensions.

Powered by Sputnik | XHTML 1.1