or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-nodes.mdcore-parsing.mdextensions.mdhtml-rendering.mdindex.mdtext-rendering.md

extensions.mddocs/

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.