16516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
26516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queruimport markdown
36516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
46516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queruclass State(list):
56516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    """ Track the current and nested state of the parser.
66516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
76516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    This utility class is used to track the state of the BlockParser and
86516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    support multiple levels if nesting. It's just a simple API wrapped around
96516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    a list. Each time a state is set, that state is appended to the end of the
106516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    list. Each time a state is reset, that state is removed from the end of
116516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    the list.
126516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
136516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    Therefore, each time a state is set for a nested block, that state must be
146516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    reset when we back out of that level of nesting or the state could be
156516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    corrupted.
166516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
176516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    While all the methods of a list object are available, only the three
186516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    defined below need be used.
196516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
206516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    """
216516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
226516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def set(self, state):
236516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Set a new state. """
246516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.append(state)
256516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
266516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def reset(self):
276516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Step back one step in nested state. """
286516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.pop()
296516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
306516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def isstate(self, state):
316516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Test that top (current) level is of given state. """
326516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        if len(self):
336516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru            return self[-1] == state
346516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        else:
356516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru            return False
366516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
376516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queruclass BlockParser:
386516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    """ Parse Markdown blocks into an ElementTree object.
396516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
406516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    A wrapper class that stitches the various BlockProcessors together,
416516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    looping through them and creating an ElementTree object.
426516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    """
436516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
446516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def __init__(self):
456516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.blockprocessors = markdown.odict.OrderedDict()
466516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.state = State()
476516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
486516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def parseDocument(self, lines):
496516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Parse a markdown document into an ElementTree.
506516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
516516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        Given a list of lines, an ElementTree object (not just a parent Element)
526516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        is created and the root element is passed to the parser as the parent.
536516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        The ElementTree object is returned.
546516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
556516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        This should only be called on an entire document, not pieces.
566516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
576516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """
586516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        # Create a ElementTree from the lines
596516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.root = markdown.etree.Element(markdown.DOC_TAG)
606516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.parseChunk(self.root, '\n'.join(lines))
616516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        return markdown.etree.ElementTree(self.root)
626516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
636516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def parseChunk(self, parent, text):
646516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Parse a chunk of markdown text and attach to given etree node.
656516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
666516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        While the ``text`` argument is generally assumed to contain multiple
676516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        blocks which will be split on blank lines, it could contain only one
686516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        block. Generally, this method would be called by extensions when
696516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        block parsing is required.
706516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
716516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        The ``parent`` etree Element passed in is altered in place.
726516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        Nothing is returned.
736516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
746516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """
756516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        self.parseBlocks(parent, text.split('\n\n'))
766516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
776516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru    def parseBlocks(self, parent, blocks):
786516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """ Process blocks of markdown text and attach to given etree node.
796516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
806516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        Given a list of ``blocks``, each blockprocessor is stepped through
816516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        until there are no blocks left. While an extension could potentially
826516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        call this method directly, it's generally expected to be used internally.
836516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
846516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        This is a public method as an extension may need to add/alter additional
856516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        BlockProcessors which call this method to recursively parse a nested
866516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        block.
876516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
886516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        """
896516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru        while blocks:
906516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru           for processor in self.blockprocessors.values():
916516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru               if processor.test(parent, blocks[0]):
926516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru                   processor.run(parent, blocks)
936516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru                   break
946516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
956516b99bb74dfb7187a08f7090bf7ca22a006f15Jean-Baptiste Queru
96