Skip to content

Commit 69fd8b4

Browse files
committed
Issue Python-Markdown#366 Recursion error in toc ext
This reworks the toc ordering to be done in a single pass with no recursion. Very long documents with lots of headers can actually exceed Python’s max recursion limit. By handling the toc ordering with no recursion, large documents can no longer cause toc to fail with recursion erros.
1 parent 609faad commit 69fd8b4

File tree

1 file changed

+47
-48
lines changed

1 file changed

+47
-48
lines changed

markdown/extensions/toc.py

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -27,60 +27,59 @@ def order_toc_list(toc_list):
2727
[{'level': 1}, {'level': 2}]
2828
=>
2929
[{'level': 1, 'children': [{'level': 2, 'children': []}]}]
30-
30+
3131
A wrong list is also converted:
3232
[{'level': 2}, {'level': 1}]
3333
=>
3434
[{'level': 2, 'children': []}, {'level': 1, 'children': []}]
3535
"""
36-
37-
def build_correct(remaining_list, prev_elements=[{'level': 1000}]):
38-
39-
if not remaining_list:
40-
return [], []
41-
42-
current = remaining_list.pop(0)
43-
if not 'children' in current.keys():
44-
current['children'] = []
45-
46-
if not prev_elements:
47-
# This happens for instance with [8, 1, 1], ie. when some
48-
# header level is outside a scope. We treat it as a
49-
# top-level
50-
next_elements, children = build_correct(remaining_list, [current])
51-
current['children'].append(children)
52-
return [current] + next_elements, []
53-
54-
prev_element = prev_elements.pop()
55-
children = []
56-
next_elements = []
57-
# Is current part of the child list or next list?
58-
if current['level'] > prev_element['level']:
59-
#print "%d is a child of %d" % (current['level'], prev_element['level'])
60-
prev_elements.append(prev_element)
61-
prev_elements.append(current)
62-
prev_element['children'].append(current)
63-
next_elements2, children2 = build_correct(remaining_list, prev_elements)
64-
children += children2
65-
next_elements += next_elements2
66-
else:
67-
#print "%d is ancestor of %d" % (current['level'], prev_element['level'])
68-
if not prev_elements:
69-
#print "No previous elements, so appending to the next set"
70-
next_elements.append(current)
71-
prev_elements = [current]
72-
next_elements2, children2 = build_correct(remaining_list, prev_elements)
73-
current['children'].extend(children2)
36+
37+
ordered_list = []
38+
if len(toc_list):
39+
# Initialize everything by processing the first entry
40+
last = toc_list.pop(0)
41+
last['children'] = []
42+
levels = [last['level']]
43+
ordered_list.append(last)
44+
parents = []
45+
46+
# Walk the rest nesting the entries properly
47+
while toc_list:
48+
t = toc_list.pop(0)
49+
current_level = t['level']
50+
t['children'] = []
51+
52+
# Reduce depth if current level < last item's level
53+
if current_level < levels[-1]:
54+
# Pop last level since we know we are less than it
55+
levels.pop()
56+
57+
# Pop parents and levels we are less than or equal to
58+
to_pop = 0
59+
for p in reversed(parents):
60+
if current_level <= p['level']:
61+
to_pop += 1
62+
else:
63+
break
64+
if to_pop:
65+
levels = levels[:-to_pop]
66+
parents = parents[:-to_pop]
67+
68+
# Note current level as last
69+
levels.append(current_level)
70+
71+
# Level is the same, so append to the current parent (if available)
72+
if current_level == levels[-1]:
73+
(parents[-1]['children'] if parents else ordered_list).append(t)
74+
75+
# Current level is > last item's level,
76+
# So make last item a parent and append current as child
7477
else:
75-
#print "Previous elements, comparing to those first"
76-
remaining_list.insert(0, current)
77-
next_elements2, children2 = build_correct(remaining_list, prev_elements)
78-
children.extend(children2)
79-
next_elements += next_elements2
80-
81-
return next_elements, children
82-
83-
ordered_list, __ = build_correct(toc_list)
78+
last['children'].append(t)
79+
parents.append(last)
80+
levels.append(current_level)
81+
last = t
82+
8483
return ordered_list
8584

8685

0 commit comments

Comments
 (0)