|
1
|
|
|
from __future__ import unicode_literals |
|
2
|
|
|
from __future__ import absolute_import |
|
3
|
|
|
from . import util |
|
4
|
|
|
from . import odict |
|
5
|
|
|
|
|
6
|
|
|
|
|
7
|
|
|
class State(list): |
|
8
|
|
|
""" Track the current and nested state of the parser. |
|
9
|
|
|
|
|
10
|
|
|
This utility class is used to track the state of the BlockParser and |
|
11
|
|
|
support multiple levels if nesting. It's just a simple API wrapped around |
|
12
|
|
|
a list. Each time a state is set, that state is appended to the end of the |
|
13
|
|
|
list. Each time a state is reset, that state is removed from the end of |
|
14
|
|
|
the list. |
|
15
|
|
|
|
|
16
|
|
|
Therefore, each time a state is set for a nested block, that state must be |
|
17
|
|
|
reset when we back out of that level of nesting or the state could be |
|
18
|
|
|
corrupted. |
|
19
|
|
|
|
|
20
|
|
|
While all the methods of a list object are available, only the three |
|
21
|
|
|
defined below need be used. |
|
22
|
|
|
|
|
23
|
|
|
""" |
|
24
|
|
|
|
|
25
|
|
|
def set(self, state): |
|
26
|
|
|
""" Set a new state. """ |
|
27
|
|
|
self.append(state) |
|
28
|
|
|
|
|
29
|
|
|
def reset(self): |
|
30
|
|
|
""" Step back one step in nested state. """ |
|
31
|
|
|
self.pop() |
|
32
|
|
|
|
|
33
|
|
|
def isstate(self, state): |
|
34
|
|
|
""" Test that top (current) level is of given state. """ |
|
35
|
|
|
if len(self): |
|
36
|
|
|
return self[-1] == state |
|
37
|
|
|
else: |
|
38
|
|
|
return False |
|
39
|
|
|
|
|
40
|
|
|
|
|
41
|
|
|
class BlockParser: |
|
42
|
|
|
""" Parse Markdown blocks into an ElementTree object. |
|
43
|
|
|
|
|
44
|
|
|
A wrapper class that stitches the various BlockProcessors together, |
|
45
|
|
|
looping through them and creating an ElementTree object. |
|
46
|
|
|
""" |
|
47
|
|
|
|
|
48
|
|
|
def __init__(self, markdown): |
|
49
|
|
|
self.blockprocessors = odict.OrderedDict() |
|
50
|
|
|
self.state = State() |
|
51
|
|
|
self.markdown = markdown |
|
52
|
|
|
|
|
53
|
|
|
def parseDocument(self, lines): |
|
54
|
|
|
""" Parse a markdown document into an ElementTree. |
|
55
|
|
|
|
|
56
|
|
|
Given a list of lines, an ElementTree object (not just a parent |
|
57
|
|
|
Element) is created and the root element is passed to the parser |
|
58
|
|
|
as the parent. The ElementTree object is returned. |
|
59
|
|
|
|
|
60
|
|
|
This should only be called on an entire document, not pieces. |
|
61
|
|
|
|
|
62
|
|
|
""" |
|
63
|
|
|
# Create a ElementTree from the lines |
|
64
|
|
|
self.root = util.etree.Element(self.markdown.doc_tag) |
|
65
|
|
|
self.parseChunk(self.root, '\n'.join(lines)) |
|
66
|
|
|
return util.etree.ElementTree(self.root) |
|
67
|
|
|
|
|
68
|
|
|
def parseChunk(self, parent, text): |
|
69
|
|
|
""" Parse a chunk of markdown text and attach to given etree node. |
|
70
|
|
|
|
|
71
|
|
|
While the ``text`` argument is generally assumed to contain multiple |
|
72
|
|
|
blocks which will be split on blank lines, it could contain only one |
|
73
|
|
|
block. Generally, this method would be called by extensions when |
|
74
|
|
|
block parsing is required. |
|
75
|
|
|
|
|
76
|
|
|
The ``parent`` etree Element passed in is altered in place. |
|
77
|
|
|
Nothing is returned. |
|
78
|
|
|
|
|
79
|
|
|
""" |
|
80
|
|
|
self.parseBlocks(parent, text.split('\n\n')) |
|
81
|
|
|
|
|
82
|
|
|
def parseBlocks(self, parent, blocks): |
|
83
|
|
|
""" Process blocks of markdown text and attach to given etree node. |
|
84
|
|
|
|
|
85
|
|
|
Given a list of ``blocks``, each blockprocessor is stepped through |
|
86
|
|
|
until there are no blocks left. While an extension could potentially |
|
87
|
|
|
call this method directly, it's generally expected to be used |
|
88
|
|
|
internally. |
|
89
|
|
|
|
|
90
|
|
|
This is a public method as an extension may need to add/alter |
|
91
|
|
|
additional BlockProcessors which call this method to recursively |
|
92
|
|
|
parse a nested block. |
|
93
|
|
|
|
|
94
|
|
|
""" |
|
95
|
|
|
while blocks: |
|
96
|
|
|
for processor in self.blockprocessors.values(): |
|
97
|
|
|
if processor.test(parent, blocks[0]): |
|
98
|
|
|
if processor.run(parent, blocks) is not False: |
|
99
|
|
|
# run returns True or None |
|
100
|
|
|
break |
|
101
|
|
|
|