0
# Extension System
1
2
Comprehensive extension framework for adding custom syntax support, parsers, and renderers to CommonMark Java. The extension system allows complete customization of parsing and rendering behavior while maintaining compatibility with the core CommonMark specification.
3
4
## Capabilities
5
6
### Extension Interface
7
8
Base interface that all extensions must implement.
9
10
```java { .api }
11
/**
12
* Base marker interface for extensions
13
* Extensions can implement multiple specific extension interfaces
14
*/
15
public interface Extension {
16
// Marker interface - no methods required
17
}
18
```
19
20
### Parser Extensions
21
22
Extensions that modify parsing behavior by adding custom block parsers, inline processors, and post-processors.
23
24
```java { .api }
25
/**
26
* Extension interface for Parser
27
*/
28
public interface Parser.ParserExtension extends Extension {
29
/**
30
* Extend the parser builder with custom functionality
31
* @param parserBuilder the parser builder to extend
32
*/
33
void extend(Parser.Builder parserBuilder);
34
}
35
```
36
37
**Usage Examples:**
38
39
```java
40
import org.commonmark.Extension;
41
import org.commonmark.parser.Parser;
42
43
// Simple parser extension
44
public class MyParserExtension implements Parser.ParserExtension {
45
@Override
46
public void extend(Parser.Builder parserBuilder) {
47
parserBuilder.customBlockParserFactory(new MyBlockParserFactory());
48
parserBuilder.customDelimiterProcessor(new MyDelimiterProcessor());
49
parserBuilder.postProcessor(new MyPostProcessor());
50
}
51
}
52
53
// Use the extension
54
Parser parser = Parser.builder()
55
.extensions(Collections.singletonList(new MyParserExtension()))
56
.build();
57
```
58
59
### Renderer Extensions
60
61
Extensions that modify rendering behavior for both HTML and text output.
62
63
```java { .api }
64
/**
65
* Extension interface for HTML renderer
66
*/
67
public interface HtmlRenderer.HtmlRendererExtension extends Extension {
68
/**
69
* Extend the HTML renderer builder with custom functionality
70
* @param rendererBuilder the renderer builder to extend
71
*/
72
void extend(HtmlRenderer.Builder rendererBuilder);
73
}
74
75
/**
76
* Extension interface for text content renderer
77
*/
78
public interface TextContentRenderer.TextContentRendererExtension extends Extension {
79
/**
80
* Extend the text content renderer builder with custom functionality
81
* @param rendererBuilder the renderer builder to extend
82
*/
83
void extend(TextContentRenderer.Builder rendererBuilder);
84
}
85
```
86
87
**Usage Examples:**
88
89
```java
90
import org.commonmark.renderer.html.HtmlRenderer;
91
import org.commonmark.renderer.text.TextContentRenderer;
92
93
// HTML renderer extension
94
public class MyHtmlRendererExtension implements HtmlRenderer.HtmlRendererExtension {
95
@Override
96
public void extend(HtmlRenderer.Builder rendererBuilder) {
97
rendererBuilder.nodeRendererFactory(context -> new MyHtmlNodeRenderer(context));
98
rendererBuilder.attributeProviderFactory(context -> new MyAttributeProvider());
99
}
100
}
101
102
// Text renderer extension
103
public class MyTextRendererExtension implements TextContentRenderer.TextContentRendererExtension {
104
@Override
105
public void extend(TextContentRenderer.Builder rendererBuilder) {
106
rendererBuilder.nodeRendererFactory(context -> new MyTextNodeRenderer(context));
107
}
108
}
109
110
// Use the extensions
111
HtmlRenderer htmlRenderer = HtmlRenderer.builder()
112
.extensions(Collections.singletonList(new MyHtmlRendererExtension()))
113
.build();
114
115
TextContentRenderer textRenderer = TextContentRenderer.builder()
116
.extensions(Collections.singletonList(new MyTextRendererExtension()))
117
.build();
118
```
119
120
### Block Parser Extensions
121
122
System for adding custom block-level syntax parsing.
123
124
```java { .api }
125
/**
126
* Factory for creating block parsers
127
*/
128
public interface BlockParserFactory {
129
/**
130
* Try to start a block parser for the current line
131
* @param state the parser state
132
* @param matchedBlockParser the matched block parser
133
* @return BlockStart if this factory can handle the line, null otherwise
134
*/
135
BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser);
136
}
137
138
/**
139
* Abstract base class for block parser factories
140
*/
141
public abstract class AbstractBlockParserFactory implements BlockParserFactory {
142
// Base implementation with utility methods
143
}
144
145
/**
146
* Parser for specific block nodes
147
*/
148
public interface BlockParser {
149
/**
150
* Whether this block can contain other blocks
151
* @return true if this is a container block
152
*/
153
boolean isContainer();
154
155
/**
156
* Whether this block can have lazy continuation lines
157
* @return true if lazy continuation is allowed
158
*/
159
boolean canHaveLazyContinuationLines();
160
161
/**
162
* Whether this block can contain the specified child block
163
* @param childBlock the potential child block
164
* @return true if the child block can be contained
165
*/
166
boolean canContain(Block childBlock);
167
168
/**
169
* Get the block node this parser is building
170
* @return the block node
171
*/
172
Block getBlock();
173
174
/**
175
* Try to continue parsing this block with the current line
176
* @param parserState the parser state
177
* @return BlockContinue if parsing should continue, null otherwise
178
*/
179
BlockContinue tryContinue(ParserState parserState);
180
181
/**
182
* Add a line to this block
183
* @param line the source line to add
184
*/
185
void addLine(SourceLine line);
186
187
/**
188
* Add a source span to this block
189
* @param sourceSpan the source span to add
190
*/
191
void addSourceSpan(SourceSpan sourceSpan);
192
193
/**
194
* Close this block (called when block parsing is complete)
195
*/
196
void closeBlock();
197
198
/**
199
* Parse inline content within this block
200
* @param inlineParser the inline parser to use
201
*/
202
void parseInlines(InlineParser inlineParser);
203
}
204
205
/**
206
* Abstract base class for block parsers
207
*/
208
public abstract class AbstractBlockParser implements BlockParser {
209
// Default implementations and utility methods
210
}
211
```
212
213
**Usage Examples:**
214
215
```java
216
import org.commonmark.parser.block.*;
217
import org.commonmark.node.CustomBlock;
218
219
// Custom block for code blocks with special syntax
220
public class MyCodeBlock extends CustomBlock {
221
private String language;
222
private String code;
223
224
// Getters and setters
225
}
226
227
// Block parser for custom code blocks
228
public class MyCodeBlockParser extends AbstractBlockParser {
229
private final MyCodeBlock block = new MyCodeBlock();
230
private final StringBuilder content = new StringBuilder();
231
232
@Override
233
public Block getBlock() {
234
return block;
235
}
236
237
@Override
238
public BlockContinue tryContinue(ParserState state) {
239
// Check if current line continues the block
240
String line = state.getLine().toString();
241
if (line.equals(":::")) {
242
return BlockContinue.finished();
243
}
244
return BlockContinue.atIndex(state.getIndex());
245
}
246
247
@Override
248
public void addLine(SourceLine line) {
249
content.append(line.getContent()).append('\n');
250
}
251
252
@Override
253
public void closeBlock() {
254
block.setCode(content.toString().trim());
255
}
256
}
257
258
// Factory for the custom block parser
259
public class MyCodeBlockParserFactory extends AbstractBlockParserFactory {
260
@Override
261
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
262
String line = state.getLine().toString();
263
if (line.startsWith(":::") && line.length() > 3) {
264
String language = line.substring(3).trim();
265
MyCodeBlockParser parser = new MyCodeBlockParser();
266
parser.getBlock().setLanguage(language);
267
return BlockStart.of(parser).atIndex(state.getNextNonSpaceIndex());
268
}
269
return BlockStart.none();
270
}
271
}
272
```
273
274
### Delimiter Processor Extensions
275
276
System for adding custom inline delimiter processing (like emphasis markers).
277
278
```java { .api }
279
/**
280
* Custom delimiter processor interface
281
*/
282
public interface DelimiterProcessor {
283
/**
284
* Get the opening delimiter character
285
* @return the opening character
286
*/
287
char getOpeningCharacter();
288
289
/**
290
* Get the closing delimiter character
291
* @return the closing character
292
*/
293
char getClosingCharacter();
294
295
/**
296
* Get the minimum length for this delimiter
297
* @return the minimum length
298
*/
299
int getMinLength();
300
301
/**
302
* Process delimiter runs
303
* @param openingRun the opening delimiter run
304
* @param closingRun the closing delimiter run
305
* @return the number of delimiters used, or 0 if not processed
306
*/
307
int process(DelimiterRun openingRun, DelimiterRun closingRun);
308
}
309
310
/**
311
* Information about delimiter runs during parsing
312
*/
313
public interface DelimiterRun {
314
/**
315
* Get the length of this delimiter run
316
* @return the length
317
*/
318
int length();
319
320
/**
321
* Get the text node containing the delimiters
322
* @return the text node
323
*/
324
Node getNode();
325
326
/**
327
* Whether this run can open emphasis
328
* @return true if can open
329
*/
330
boolean canOpen();
331
332
/**
333
* Whether this run can close emphasis
334
* @return true if can close
335
*/
336
boolean canClose();
337
}
338
```
339
340
**Usage Examples:**
341
342
```java
343
import org.commonmark.parser.delimiter.DelimiterProcessor;
344
import org.commonmark.parser.delimiter.DelimiterRun;
345
import org.commonmark.node.CustomNode;
346
347
// Custom inline node for subscript
348
public class Subscript extends CustomNode {
349
@Override
350
public void accept(Visitor visitor) {
351
// Handle visitor
352
}
353
}
354
355
// Delimiter processor for subscript syntax (~text~)
356
public class SubscriptDelimiterProcessor implements DelimiterProcessor {
357
@Override
358
public char getOpeningCharacter() {
359
return '~';
360
}
361
362
@Override
363
public char getClosingCharacter() {
364
return '~';
365
}
366
367
@Override
368
public int getMinLength() {
369
return 1;
370
}
371
372
@Override
373
public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
374
if (openingRun.length() >= 1 && closingRun.length() >= 1) {
375
// Create subscript node
376
Subscript subscript = new Subscript();
377
378
// Move content between delimiters to subscript node
379
Node content = openingRun.getNode().getNext();
380
while (content != null && content != closingRun.getNode()) {
381
Node next = content.getNext();
382
subscript.appendChild(content);
383
content = next;
384
}
385
386
// Insert subscript node
387
openingRun.getNode().insertAfter(subscript);
388
389
return 1; // Used 1 delimiter from each run
390
}
391
return 0;
392
}
393
}
394
```
395
396
### Post-Processor Extensions
397
398
System for processing nodes after parsing is complete.
399
400
```java { .api }
401
/**
402
* Post-processes nodes after parsing
403
*/
404
public interface PostProcessor {
405
/**
406
* Process the document node after parsing is complete
407
* @param node the document node to process
408
* @return the processed node (may be the same or a new node)
409
*/
410
Node process(Node node);
411
}
412
```
413
414
**Usage Examples:**
415
416
```java
417
import org.commonmark.parser.PostProcessor;
418
import org.commonmark.node.*;
419
420
// Post-processor to add IDs to headings
421
public class HeadingIdPostProcessor implements PostProcessor {
422
@Override
423
public Node process(Node node) {
424
node.accept(new AbstractVisitor() {
425
@Override
426
public void visit(Heading heading) {
427
// Generate ID from heading text
428
String id = generateId(getTextContent(heading));
429
heading.setId(id);
430
super.visit(heading);
431
}
432
});
433
return node;
434
}
435
436
private String generateId(String text) {
437
return text.toLowerCase()
438
.replaceAll("[^a-z0-9\\s]", "")
439
.replaceAll("\\s+", "-")
440
.trim();
441
}
442
443
private String getTextContent(Node node) {
444
StringBuilder sb = new StringBuilder();
445
node.accept(new AbstractVisitor() {
446
@Override
447
public void visit(Text text) {
448
sb.append(text.getLiteral());
449
}
450
});
451
return sb.toString();
452
}
453
}
454
455
// Use the post-processor
456
Parser parser = Parser.builder()
457
.postProcessor(new HeadingIdPostProcessor())
458
.build();
459
```
460
461
### Inline Parser Extensions
462
463
System for custom inline content parsing.
464
465
```java { .api }
466
/**
467
* Factory for creating inline parsers
468
*/
469
public interface InlineParserFactory {
470
/**
471
* Create an inline parser with the given context
472
* @param inlineParserContext the context for inline parsing
473
* @return an inline parser instance
474
*/
475
InlineParser create(InlineParserContext inlineParserContext);
476
}
477
478
/**
479
* Parser for inline content
480
*/
481
public interface InlineParser {
482
/**
483
* Parse inline content within the given lines and node
484
* @param lines the source lines containing inline content
485
* @param node the parent node to add inline nodes to
486
*/
487
void parse(SourceLines lines, Node node);
488
}
489
490
/**
491
* Context for inline parsing
492
*/
493
public interface InlineParserContext {
494
/**
495
* Get the delimiter processors
496
* @return list of delimiter processors
497
*/
498
List<DelimiterProcessor> getCustomDelimiterProcessors();
499
500
/**
501
* Get link reference definitions
502
* @return map of link reference definitions
503
*/
504
Map<String, LinkReferenceDefinition> getLinkReferenceDefinitions();
505
}
506
```
507
508
### Complete Extension Example
509
510
Here's a complete example that demonstrates creating a comprehensive extension:
511
512
```java
513
import org.commonmark.Extension;
514
import org.commonmark.parser.Parser;
515
import org.commonmark.renderer.html.HtmlRenderer;
516
import org.commonmark.renderer.text.TextContentRenderer;
517
518
/**
519
* Complete extension for adding alert blocks syntax
520
* Syntax: !!! type "title"
521
* content
522
* !!!
523
*/
524
public class AlertExtension implements
525
Parser.ParserExtension,
526
HtmlRenderer.HtmlRendererExtension,
527
TextContentRenderer.TextContentRendererExtension {
528
529
// Create extension instance
530
public static Extension create() {
531
return new AlertExtension();
532
}
533
534
@Override
535
public void extend(Parser.Builder parserBuilder) {
536
parserBuilder.customBlockParserFactory(new AlertBlockParserFactory());
537
}
538
539
@Override
540
public void extend(HtmlRenderer.Builder rendererBuilder) {
541
rendererBuilder.nodeRendererFactory(context -> new AlertHtmlRenderer(context));
542
}
543
544
@Override
545
public void extend(TextContentRenderer.Builder rendererBuilder) {
546
rendererBuilder.nodeRendererFactory(context -> new AlertTextRenderer(context));
547
}
548
}
549
550
// Use the complete extension
551
List<Extension> extensions = Collections.singletonList(AlertExtension.create());
552
553
Parser parser = Parser.builder()
554
.extensions(extensions)
555
.build();
556
557
HtmlRenderer htmlRenderer = HtmlRenderer.builder()
558
.extensions(extensions)
559
.build();
560
561
TextContentRenderer textRenderer = TextContentRenderer.builder()
562
.extensions(extensions)
563
.build();
564
565
// Parse and render alert blocks
566
String markdown = "!!! warning \"Important\"\nThis is a warning message.\n!!!";
567
Node document = parser.parse(markdown);
568
569
String html = htmlRenderer.render(document);
570
String text = textRenderer.render(document);
571
```
572
573
## Extension Best Practices
574
575
### 1. Implement Multiple Extension Interfaces
576
Create extensions that work with both parser and renderers for complete functionality.
577
578
### 2. Use Abstract Base Classes
579
Extend `AbstractBlockParser`, `AbstractBlockParserFactory` for easier implementation.
580
581
### 3. Handle All Renderers
582
Implement both HTML and text rendering for custom nodes.
583
584
### 4. Follow CommonMark Principles
585
Ensure extensions don't break existing CommonMark functionality.
586
587
### 5. Provide Factory Methods
588
Use static factory methods like `create()` for extension instantiation.
589
590
### 6. Document Extension Syntax
591
Clearly document the syntax and behavior of custom extensions.