0
# Rendering System
1
2
HTML rendering components including syntax highlighting, heading management, and output formatting processors. The rendering system transforms collected documentation data into properly formatted HTML with consistent styling and structure.
3
4
## Capabilities
5
6
### Syntax Highlighting
7
8
Code syntax highlighter that matches Markdown configuration and provides consistent code formatting.
9
10
```python { .api }
11
class Highlighter(Highlight):
12
"""Code highlighter that matches Markdown configuration."""
13
14
_highlight_config_keys: frozenset[str]
15
"""Configuration keys supported by the highlighter."""
16
17
def __init__(self, md: Markdown) -> None:
18
"""
19
Configure to match a Markdown instance.
20
21
Args:
22
md: The Markdown instance to read configs from
23
"""
24
25
def highlight(
26
self,
27
src: str,
28
language: str | None = None,
29
*,
30
inline: bool = False,
31
dedent: bool = True,
32
linenums: bool | None = None,
33
**kwargs: Any
34
) -> str:
35
"""
36
Highlight a code-snippet.
37
38
Args:
39
src: The code to highlight
40
language: Explicitly tell what language to use for highlighting
41
inline: Whether to highlight as inline
42
dedent: Whether to dedent the code before highlighting it or not
43
linenums: Whether to add line numbers in the result
44
**kwargs: Pass on to pymdownx.highlight.Highlight.highlight
45
46
Returns:
47
The highlighted code as HTML text, marked safe (not escaped for HTML)
48
"""
49
```
50
51
**Usage Examples:**
52
53
Basic syntax highlighting:
54
```python
55
from mkdocstrings import Highlighter
56
from markdown import Markdown
57
58
md = Markdown(extensions=['codehilite'])
59
highlighter = Highlighter(md)
60
61
# Highlight Python code
62
python_code = """
63
def hello_world():
64
print("Hello, World!")
65
return True
66
"""
67
68
highlighted = highlighter.highlight(python_code, language="python")
69
print(highlighted) # Returns HTML with syntax highlighting
70
```
71
72
Inline code highlighting:
73
```python
74
# Highlight inline code
75
inline_code = "print('hello')"
76
highlighted_inline = highlighter.highlight(
77
inline_code,
78
language="python",
79
inline=True
80
)
81
```
82
83
With line numbers:
84
```python
85
# Highlight with line numbers
86
highlighted_with_lines = highlighter.highlight(
87
python_code,
88
language="python",
89
linenums=True
90
)
91
```
92
93
### Heading Management
94
95
Tree processor that shifts heading levels in rendered HTML content to maintain proper document hierarchy.
96
97
```python { .api }
98
class HeadingShiftingTreeprocessor(Treeprocessor):
99
"""Shift levels of all Markdown headings according to the configured base level."""
100
101
name: str = "mkdocstrings_headings"
102
"""The name of the treeprocessor."""
103
104
regex: re.Pattern = re.compile(r"([Hh])([1-6])")
105
"""The regex to match heading tags."""
106
107
shift_by: int
108
"""The number of heading 'levels' to add to every heading. <h2> with shift_by = 3 becomes <h5>."""
109
110
def __init__(self, md: Markdown, shift_by: int) -> None:
111
"""
112
Initialize the object.
113
114
Args:
115
md: A markdown.Markdown instance
116
shift_by: The number of heading 'levels' to add to every heading
117
"""
118
119
def run(self, root: Element) -> None:
120
"""
121
Shift the levels of all headings in the document.
122
123
Args:
124
root: Root element of the tree to process
125
"""
126
```
127
128
**Usage Examples:**
129
130
Shift headings in handler rendering:
131
```python
132
from mkdocstrings import HeadingShiftingTreeprocessor
133
from markdown import Markdown
134
from xml.etree.ElementTree import Element, fromstring
135
136
# Create processor that shifts headings down by 2 levels
137
md = Markdown()
138
processor = HeadingShiftingTreeprocessor(md, shift_by=2)
139
140
# Process HTML content
141
html_content = """
142
<h1>Main Title</h1>
143
<h2>Subtitle</h2>
144
<h3>Section</h3>
145
"""
146
147
root = fromstring(f"<div>{html_content}</div>")
148
processor.run(root)
149
150
# Now h1 becomes h3, h2 becomes h4, h3 becomes h5
151
```
152
153
Integration in handlers:
154
```python
155
class MyHandler(BaseHandler):
156
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
157
# Get heading level from options
158
heading_level = options.get("heading_level", 1)
159
160
# Create inner Markdown with heading shifter
161
inner_md = Markdown()
162
if heading_level > 1:
163
shifter = HeadingShiftingTreeprocessor(inner_md, shift_by=heading_level - 1)
164
inner_md.treeprocessors.register(shifter, "heading_shift", 5)
165
166
# Process docstring with proper heading levels
167
docstring_html = inner_md.convert(data.docstring)
168
169
return template.render(data=data, docstring_html=docstring_html)
170
```
171
172
### ID Management
173
174
Tree processor that prepends prefixes to HTML element IDs to avoid conflicts in the generated documentation.
175
176
```python { .api }
177
class IdPrependingTreeprocessor(Treeprocessor):
178
"""Prepend the configured prefix to IDs of all HTML elements."""
179
180
name: str = "mkdocstrings_ids"
181
"""The name of the treeprocessor."""
182
183
id_prefix: str
184
"""The prefix to add to every ID. It is prepended without any separator; specify your own separator if needed."""
185
186
def __init__(self, md: Markdown, id_prefix: str) -> None:
187
"""
188
Initialize the object.
189
190
Args:
191
md: A markdown.Markdown instance
192
id_prefix: The prefix to add to every ID. It is prepended without any separator
193
"""
194
195
def run(self, root: Element) -> None:
196
"""
197
Prepend the configured prefix to all IDs in the document.
198
199
Args:
200
root: Root element of the tree to process
201
"""
202
```
203
204
**Usage Examples:**
205
206
Prevent ID conflicts between multiple autodoc blocks:
207
```python
208
from mkdocstrings import IdPrependingTreeprocessor
209
210
# Create processor with prefix
211
processor = IdPrependingTreeprocessor(md, id_prefix="module1-")
212
213
# Process HTML with IDs
214
html_with_ids = """
215
<h2 id="function">My Function</h2>
216
<div id="example">Example</div>
217
"""
218
219
root = fromstring(f"<div>{html_with_ids}</div>")
220
processor.run(root)
221
222
# IDs become: "module1-function", "module1-example"
223
```
224
225
Handler integration:
226
```python
227
class MyHandler(BaseHandler):
228
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
229
# Create unique ID prefix for this object
230
id_prefix = data.name.replace(".", "-") + "-"
231
232
# Process with ID prefixing
233
inner_md = Markdown()
234
id_processor = IdPrependingTreeprocessor(inner_md, id_prefix)
235
inner_md.treeprocessors.register(id_processor, "id_prefix", 10)
236
237
# Convert content with prefixed IDs
238
content_html = inner_md.convert(data.content)
239
240
return template.render(data=data, content_html=content_html)
241
```
242
243
### Paragraph Stripping
244
245
Tree processor that unwraps single paragraph elements from output for cleaner formatting.
246
247
```python { .api }
248
class ParagraphStrippingTreeprocessor(Treeprocessor):
249
"""Unwraps the <p> element around the whole output."""
250
251
name: str = "mkdocstrings_strip_paragraph"
252
"""The name of the treeprocessor."""
253
254
strip: bool = False
255
"""Whether to strip <p> elements or not."""
256
257
def run(self, root: Element) -> Element | None:
258
"""
259
Unwrap the root element if it's a single <p> element.
260
261
Args:
262
root: Root element of the tree to process
263
264
Returns:
265
Modified root element or None
266
"""
267
```
268
269
**Usage Examples:**
270
271
Strip single paragraphs from docstring content:
272
```python
273
from mkdocstrings import ParagraphStrippingTreeprocessor
274
275
# Create processor
276
processor = ParagraphStrippingTreeprocessor()
277
processor.strip = True
278
279
# Process content that has unnecessary paragraph wrapper
280
html_content = "<p>Simple text content</p>"
281
root = fromstring(html_content)
282
result = processor.run(root)
283
284
# Result: "Simple text content" (paragraph wrapper removed)
285
```
286
287
Handler usage for inline content:
288
```python
289
def render_inline_docstring(self, docstring: str) -> str:
290
"""Render docstring as inline content without paragraph wrapper."""
291
inner_md = Markdown()
292
293
# Add paragraph stripper for inline rendering
294
stripper = ParagraphStrippingTreeprocessor()
295
stripper.strip = True
296
inner_md.treeprocessors.register(stripper, "strip_p", 15)
297
298
return inner_md.convert(docstring)
299
```
300
301
## Integration with Handlers
302
303
### Template Integration
304
305
Rendering components integrate with Jinja2 templates:
306
307
```python
308
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
309
# Set up rendering environment
310
highlighter = Highlighter(self.md)
311
312
# Configure template globals
313
template_globals = {
314
"highlight": highlighter.highlight,
315
"shift_headings": self.shift_headings,
316
"config": options
317
}
318
319
# Render template with rendering utilities
320
template = self.env.get_template("handler.html")
321
return template.render(
322
data=data,
323
**template_globals
324
)
325
```
326
327
### Markdown Configuration
328
329
Rendering components respect Markdown configuration:
330
331
```python
332
# Handler initialization
333
def __init__(self, *args, **kwargs):
334
super().__init__(*args, **kwargs)
335
336
# Create highlighter that matches main Markdown config
337
self.highlighter = Highlighter(self.md)
338
339
# Configure processors based on options
340
self.configure_processors()
341
342
def configure_processors(self):
343
"""Configure tree processors based on handler options."""
344
# Add heading shifter if needed
345
if self.config.get("heading_level", 1) > 1:
346
shift_by = self.config["heading_level"] - 1
347
shifter = HeadingShiftingTreeprocessor(self.inner_md, shift_by)
348
self.inner_md.treeprocessors.register(shifter, "shift_headings", 5)
349
350
# Add ID prefixer for unique IDs
351
if self.config.get("unique_ids", True):
352
id_prefix = self.get_id_prefix()
353
prefixer = IdPrependingTreeprocessor(self.inner_md, id_prefix)
354
self.inner_md.treeprocessors.register(prefixer, "prefix_ids", 10)
355
```
356
357
### Custom Rendering
358
359
Extend rendering for custom needs:
360
361
```python
362
class CustomHandler(BaseHandler):
363
def setup_rendering(self):
364
"""Set up custom rendering pipeline."""
365
# Create custom highlighter with additional languages
366
self.highlighter = Highlighter(self.md)
367
368
# Add custom tree processors
369
self.add_custom_processors()
370
371
def add_custom_processors(self):
372
"""Add custom tree processors."""
373
# Custom processor for special formatting
374
custom_processor = CustomTreeProcessor(self.inner_md)
375
self.inner_md.treeprocessors.register(custom_processor, "custom", 20)
376
377
def render_with_custom_formatting(self, content: str, language: str) -> str:
378
"""Render content with custom formatting."""
379
# Highlight code
380
highlighted = self.highlighter.highlight(content, language=language)
381
382
# Apply custom processing
383
processed = self.apply_custom_processing(highlighted)
384
385
return processed
386
```
387
388
### Markdown Inner Extension
389
390
Extension that should always be added to Markdown sub-documents that handlers request.
391
392
```python { .api }
393
class MkdocstringsInnerExtension(Extension):
394
"""Extension that should always be added to Markdown sub-documents that handlers request (and only them)."""
395
396
headings: list[Element]
397
"""The list that will be populated with all HTML heading elements encountered in the document."""
398
399
def __init__(self, headings: list[Element]) -> None:
400
"""
401
Initialize the object.
402
403
Args:
404
headings: A list that will be populated with all HTML heading elements encountered in the document
405
"""
406
407
def extendMarkdown(self, md: Markdown) -> None:
408
"""
409
Register the extension.
410
411
Args:
412
md: A markdown.Markdown instance
413
"""
414
```
415
416
**Usage Examples:**
417
418
Using the inner extension for handler sub-documents:
419
```python
420
from mkdocstrings import MkdocstringsInnerExtension
421
from markdown import Markdown
422
from xml.etree.ElementTree import Element
423
424
# Create list to collect headings
425
headings: list[Element] = []
426
427
# Create inner extension
428
inner_extension = MkdocstringsInnerExtension(headings)
429
430
# Create Markdown instance for sub-document processing
431
inner_md = Markdown(extensions=[inner_extension])
432
433
# Process handler content
434
content = """
435
# Function Documentation
436
437
## Parameters
438
439
### param1
440
Description of parameter 1.
441
442
### param2
443
Description of parameter 2.
444
"""
445
446
result = inner_md.convert(content)
447
448
# headings list now contains all heading elements found
449
print(f"Found {len(headings)} headings")
450
for heading in headings:
451
print(f"- {heading.tag}: {heading.text}")
452
```
453
454
Handler integration:
455
```python
456
class MyHandler(BaseHandler):
457
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
458
# Create headings collector
459
headings: list[Element] = []
460
461
# Set up inner Markdown with extension
462
inner_extension = MkdocstringsInnerExtension(headings)
463
inner_md = Markdown(extensions=[
464
inner_extension,
465
'toc',
466
'codehilite'
467
])
468
469
# Process docstring content
470
docstring_html = inner_md.convert(data.docstring)
471
472
# Use collected headings for navigation or TOC
473
toc_data = [(h.tag, h.text, h.get('id')) for h in headings]
474
475
# Render template with processed content
476
template = self.env.get_template('function.html')
477
return template.render(
478
data=data,
479
docstring_html=docstring_html,
480
toc_data=toc_data
481
)
482
```
483
484
This rendering system provides consistent, high-quality HTML output while maintaining flexibility for customization and integration with different themes and styling approaches.