22Preprocessor that transforms markdown cells: Insert numbering in from of heading
33"""
44
5- import re
5+ from traitlets . log import get_logger
66
77from nbconvert .preprocessors .base import Preprocessor
88
9+ logger = get_logger ()
10+
11+ try : # for Mistune >= 3.0
12+ import mistune
13+ from mistune .core import BlockState
14+ from mistune .renderers .markdown import MarkdownRenderer
15+ except ImportError : # for Mistune >= 2.0
16+ logger .error ("NumberedHeadingsPreprocessor requires mistune >= 3" )
17+
918
1019class NumberedHeadingsPreprocessor (Preprocessor ):
1120 """Pre-processor that will rewrite markdown headings to include numberings."""
1221
1322 def __init__ (self , * args , ** kwargs ):
1423 """Init"""
1524 super ().__init__ (* args , ** kwargs )
25+ self .md_parser = mistune .create_markdown (renderer = None )
26+ self .md_renderer = MarkdownRenderer ()
1627 self .current_numbering = [0 ]
1728
1829 def format_numbering (self ):
@@ -29,23 +40,24 @@ def _inc_current_numbering(self, level):
2940 self .current_numbering = self .current_numbering [:level ]
3041 self .current_numbering [level - 1 ] += 1
3142
32- def _transform_markdown_line (self , line , resources ):
33- """Rewrites one markdown line, if needed"""
34- if m := re .match (r"^(?P<level>#+) (?P<heading>.*)" , line ):
35- level = len (m .group ("level" ))
36- self ._inc_current_numbering (level )
37- old_heading = m .group ("heading" ).strip ()
38- new_heading = self .format_numbering () + " " + old_heading
39- return "#" * level + " " + new_heading
40-
41- return line
42-
4343 def preprocess_cell (self , cell , resources , index ):
4444 """Rewrites all the headings in the cell if it is markdown"""
45- if cell ["cell_type" ] == "markdown" :
46- cell ["source" ] = "\n " .join (
47- self ._transform_markdown_line (line , resources )
48- for line in cell ["source" ].splitlines ()
49- )
50-
51- return cell , resources
45+ if cell ["cell_type" ] != "markdown" :
46+ return cell , resources
47+ try :
48+ md_ast = self .md_parser (cell ["source" ])
49+ assert not isinstance (md_ast , str ) # type guard ; str is not returned by ast parser
50+ for element in md_ast :
51+ if element ["type" ] == "heading" :
52+ level = element ["attrs" ]["level" ]
53+ self ._inc_current_numbering (level )
54+ if len (element ["children" ]) > 0 :
55+ child = element ["children" ][0 ]
56+ if child ["type" ] == "text" :
57+ child ["raw" ] = self .format_numbering () + " " + child ["raw" ]
58+ new_source = self .md_renderer (md_ast , BlockState ())
59+ cell ["source" ] = new_source
60+ return cell , resources
61+ except Exception :
62+ logger .warning ("Failed processing cell headings" , exc_info = True )
63+ return cell , resources
0 commit comments