CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-commonmark--commonmark

Java library for parsing and rendering Markdown text according to the CommonMark specification

Pending
Overview
Eval results
Files

extensions.mddocs/

Extension System

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.

Capabilities

Extension Interface

Base interface that all extensions must implement.

/**
 * Base marker interface for extensions
 * Extensions can implement multiple specific extension interfaces
 */
public interface Extension {
    // Marker interface - no methods required
}

Parser Extensions

Extensions that modify parsing behavior by adding custom block parsers, inline processors, and post-processors.

/**
 * Extension interface for Parser
 */
public interface Parser.ParserExtension extends Extension {
    /**
     * Extend the parser builder with custom functionality
     * @param parserBuilder the parser builder to extend
     */
    void extend(Parser.Builder parserBuilder);
}

Usage Examples:

import org.commonmark.Extension;
import org.commonmark.parser.Parser;

// Simple parser extension
public class MyParserExtension implements Parser.ParserExtension {
    @Override
    public void extend(Parser.Builder parserBuilder) {
        parserBuilder.customBlockParserFactory(new MyBlockParserFactory());
        parserBuilder.customDelimiterProcessor(new MyDelimiterProcessor());
        parserBuilder.postProcessor(new MyPostProcessor());
    }
}

// Use the extension
Parser parser = Parser.builder()
    .extensions(Collections.singletonList(new MyParserExtension()))
    .build();

Renderer Extensions

Extensions that modify rendering behavior for both HTML and text output.

/**
 * Extension interface for HTML renderer
 */
public interface HtmlRenderer.HtmlRendererExtension extends Extension {
    /**
     * Extend the HTML renderer builder with custom functionality
     * @param rendererBuilder the renderer builder to extend
     */
    void extend(HtmlRenderer.Builder rendererBuilder);
}

/**
 * Extension interface for text content renderer
 */
public interface TextContentRenderer.TextContentRendererExtension extends Extension {
    /**
     * Extend the text content renderer builder with custom functionality
     * @param rendererBuilder the renderer builder to extend
     */
    void extend(TextContentRenderer.Builder rendererBuilder);
}

Usage Examples:

import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.text.TextContentRenderer;

// HTML renderer extension
public class MyHtmlRendererExtension implements HtmlRenderer.HtmlRendererExtension {
    @Override
    public void extend(HtmlRenderer.Builder rendererBuilder) {
        rendererBuilder.nodeRendererFactory(context -> new MyHtmlNodeRenderer(context));
        rendererBuilder.attributeProviderFactory(context -> new MyAttributeProvider());
    }
}

// Text renderer extension
public class MyTextRendererExtension implements TextContentRenderer.TextContentRendererExtension {
    @Override
    public void extend(TextContentRenderer.Builder rendererBuilder) {
        rendererBuilder.nodeRendererFactory(context -> new MyTextNodeRenderer(context));
    }
}

// Use the extensions
HtmlRenderer htmlRenderer = HtmlRenderer.builder()
    .extensions(Collections.singletonList(new MyHtmlRendererExtension()))
    .build();

TextContentRenderer textRenderer = TextContentRenderer.builder()
    .extensions(Collections.singletonList(new MyTextRendererExtension()))
    .build();

Block Parser Extensions

System for adding custom block-level syntax parsing.

/**
 * Factory for creating block parsers
 */
public interface BlockParserFactory {
    /**
     * Try to start a block parser for the current line
     * @param state the parser state
     * @param matchedBlockParser the matched block parser
     * @return BlockStart if this factory can handle the line, null otherwise
     */
    BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser);
}

/**
 * Abstract base class for block parser factories
 */
public abstract class AbstractBlockParserFactory implements BlockParserFactory {
    // Base implementation with utility methods
}

/**
 * Parser for specific block nodes
 */
public interface BlockParser {
    /**
     * Whether this block can contain other blocks
     * @return true if this is a container block
     */
    boolean isContainer();
    
    /**
     * Whether this block can have lazy continuation lines
     * @return true if lazy continuation is allowed
     */
    boolean canHaveLazyContinuationLines();
    
    /**
     * Whether this block can contain the specified child block
     * @param childBlock the potential child block
     * @return true if the child block can be contained
     */
    boolean canContain(Block childBlock);
    
    /**
     * Get the block node this parser is building
     * @return the block node
     */
    Block getBlock();
    
    /**
     * Try to continue parsing this block with the current line
     * @param parserState the parser state
     * @return BlockContinue if parsing should continue, null otherwise
     */
    BlockContinue tryContinue(ParserState parserState);
    
    /**
     * Add a line to this block
     * @param line the source line to add
     */
    void addLine(SourceLine line);
    
    /**
     * Add a source span to this block
     * @param sourceSpan the source span to add
     */
    void addSourceSpan(SourceSpan sourceSpan);
    
    /**
     * Close this block (called when block parsing is complete)
     */
    void closeBlock();
    
    /**
     * Parse inline content within this block
     * @param inlineParser the inline parser to use
     */
    void parseInlines(InlineParser inlineParser);
}

/**
 * Abstract base class for block parsers
 */
public abstract class AbstractBlockParser implements BlockParser {
    // Default implementations and utility methods
}

Usage Examples:

import org.commonmark.parser.block.*;
import org.commonmark.node.CustomBlock;

// Custom block for code blocks with special syntax
public class MyCodeBlock extends CustomBlock {
    private String language;
    private String code;
    
    // Getters and setters
}

// Block parser for custom code blocks
public class MyCodeBlockParser extends AbstractBlockParser {
    private final MyCodeBlock block = new MyCodeBlock();
    private final StringBuilder content = new StringBuilder();
    
    @Override
    public Block getBlock() {
        return block;
    }
    
    @Override
    public BlockContinue tryContinue(ParserState state) {
        // Check if current line continues the block
        String line = state.getLine().toString();
        if (line.equals(":::")) {
            return BlockContinue.finished();
        }
        return BlockContinue.atIndex(state.getIndex());
    }
    
    @Override
    public void addLine(SourceLine line) {
        content.append(line.getContent()).append('\n');
    }
    
    @Override
    public void closeBlock() {
        block.setCode(content.toString().trim());
    }
}

// Factory for the custom block parser
public class MyCodeBlockParserFactory extends AbstractBlockParserFactory {
    @Override
    public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
        String line = state.getLine().toString();
        if (line.startsWith(":::") && line.length() > 3) {
            String language = line.substring(3).trim();
            MyCodeBlockParser parser = new MyCodeBlockParser();
            parser.getBlock().setLanguage(language);
            return BlockStart.of(parser).atIndex(state.getNextNonSpaceIndex());
        }
        return BlockStart.none();
    }
}

Delimiter Processor Extensions

System for adding custom inline delimiter processing (like emphasis markers).

/**
 * Custom delimiter processor interface
 */
public interface DelimiterProcessor {
    /**
     * Get the opening delimiter character
     * @return the opening character
     */
    char getOpeningCharacter();
    
    /**
     * Get the closing delimiter character
     * @return the closing character
     */
    char getClosingCharacter();
    
    /**
     * Get the minimum length for this delimiter
     * @return the minimum length
     */
    int getMinLength();
    
    /**
     * Process delimiter runs
     * @param openingRun the opening delimiter run
     * @param closingRun the closing delimiter run
     * @return the number of delimiters used, or 0 if not processed
     */
    int process(DelimiterRun openingRun, DelimiterRun closingRun);
}

/**
 * Information about delimiter runs during parsing
 */
public interface DelimiterRun {
    /**
     * Get the length of this delimiter run
     * @return the length
     */
    int length();
    
    /**
     * Get the text node containing the delimiters
     * @return the text node
     */
    Node getNode();
    
    /**
     * Whether this run can open emphasis
     * @return true if can open
     */
    boolean canOpen();
    
    /**
     * Whether this run can close emphasis
     * @return true if can close
     */
    boolean canClose();
}

Usage Examples:

import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
import org.commonmark.node.CustomNode;

// Custom inline node for subscript
public class Subscript extends CustomNode {
    @Override
    public void accept(Visitor visitor) {
        // Handle visitor
    }
}

// Delimiter processor for subscript syntax (~text~)
public class SubscriptDelimiterProcessor implements DelimiterProcessor {
    @Override
    public char getOpeningCharacter() {
        return '~';
    }
    
    @Override
    public char getClosingCharacter() {
        return '~';
    }
    
    @Override
    public int getMinLength() {
        return 1;
    }
    
    @Override
    public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
        if (openingRun.length() >= 1 && closingRun.length() >= 1) {
            // Create subscript node
            Subscript subscript = new Subscript();
            
            // Move content between delimiters to subscript node
            Node content = openingRun.getNode().getNext();
            while (content != null && content != closingRun.getNode()) {
                Node next = content.getNext();
                subscript.appendChild(content);
                content = next;
            }
            
            // Insert subscript node
            openingRun.getNode().insertAfter(subscript);
            
            return 1; // Used 1 delimiter from each run
        }
        return 0;
    }
}

Post-Processor Extensions

System for processing nodes after parsing is complete.

/**
 * Post-processes nodes after parsing
 */
public interface PostProcessor {
    /**
     * Process the document node after parsing is complete
     * @param node the document node to process
     * @return the processed node (may be the same or a new node)
     */
    Node process(Node node);
}

Usage Examples:

import org.commonmark.parser.PostProcessor;
import org.commonmark.node.*;

// Post-processor to add IDs to headings
public class HeadingIdPostProcessor implements PostProcessor {
    @Override
    public Node process(Node node) {
        node.accept(new AbstractVisitor() {
            @Override
            public void visit(Heading heading) {
                // Generate ID from heading text
                String id = generateId(getTextContent(heading));
                heading.setId(id);
                super.visit(heading);
            }
        });
        return node;
    }
    
    private String generateId(String text) {
        return text.toLowerCase()
                   .replaceAll("[^a-z0-9\\s]", "")
                   .replaceAll("\\s+", "-")
                   .trim();
    }
    
    private String getTextContent(Node node) {
        StringBuilder sb = new StringBuilder();
        node.accept(new AbstractVisitor() {
            @Override
            public void visit(Text text) {
                sb.append(text.getLiteral());
            }
        });
        return sb.toString();
    }
}

// Use the post-processor
Parser parser = Parser.builder()
    .postProcessor(new HeadingIdPostProcessor())
    .build();

Inline Parser Extensions

System for custom inline content parsing.

/**
 * Factory for creating inline parsers
 */
public interface InlineParserFactory {
    /**
     * Create an inline parser with the given context
     * @param inlineParserContext the context for inline parsing
     * @return an inline parser instance
     */
    InlineParser create(InlineParserContext inlineParserContext);
}

/**
 * Parser for inline content
 */
public interface InlineParser {
    /**
     * Parse inline content within the given lines and node
     * @param lines the source lines containing inline content
     * @param node the parent node to add inline nodes to
     */
    void parse(SourceLines lines, Node node);
}

/**
 * Context for inline parsing
 */
public interface InlineParserContext {
    /**
     * Get the delimiter processors
     * @return list of delimiter processors
     */
    List<DelimiterProcessor> getCustomDelimiterProcessors();
    
    /**
     * Get link reference definitions
     * @return map of link reference definitions
     */
    Map<String, LinkReferenceDefinition> getLinkReferenceDefinitions();
}

Complete Extension Example

Here's a complete example that demonstrates creating a comprehensive extension:

import org.commonmark.Extension;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.text.TextContentRenderer;

/**
 * Complete extension for adding alert blocks syntax
 * Syntax: !!! type "title"
 *         content
 *         !!!
 */
public class AlertExtension implements 
    Parser.ParserExtension, 
    HtmlRenderer.HtmlRendererExtension, 
    TextContentRenderer.TextContentRendererExtension {
    
    // Create extension instance
    public static Extension create() {
        return new AlertExtension();
    }
    
    @Override
    public void extend(Parser.Builder parserBuilder) {
        parserBuilder.customBlockParserFactory(new AlertBlockParserFactory());
    }
    
    @Override
    public void extend(HtmlRenderer.Builder rendererBuilder) {
        rendererBuilder.nodeRendererFactory(context -> new AlertHtmlRenderer(context));
    }
    
    @Override
    public void extend(TextContentRenderer.Builder rendererBuilder) {
        rendererBuilder.nodeRendererFactory(context -> new AlertTextRenderer(context));
    }
}

// Use the complete extension
List<Extension> extensions = Collections.singletonList(AlertExtension.create());

Parser parser = Parser.builder()
    .extensions(extensions)
    .build();

HtmlRenderer htmlRenderer = HtmlRenderer.builder()
    .extensions(extensions)
    .build();

TextContentRenderer textRenderer = TextContentRenderer.builder()
    .extensions(extensions)
    .build();

// Parse and render alert blocks
String markdown = "!!! warning \"Important\"\nThis is a warning message.\n!!!";
Node document = parser.parse(markdown);

String html = htmlRenderer.render(document);
String text = textRenderer.render(document);

Extension Best Practices

1. Implement Multiple Extension Interfaces

Create extensions that work with both parser and renderers for complete functionality.

2. Use Abstract Base Classes

Extend AbstractBlockParser, AbstractBlockParserFactory for easier implementation.

3. Handle All Renderers

Implement both HTML and text rendering for custom nodes.

4. Follow CommonMark Principles

Ensure extensions don't break existing CommonMark functionality.

5. Provide Factory Methods

Use static factory methods like create() for extension instantiation.

6. Document Extension Syntax

Clearly document the syntax and behavior of custom extensions.

Install with Tessl CLI

npx tessl i tessl/maven-org-commonmark--commonmark

docs

ast-nodes.md

core-parsing.md

extensions.md

html-rendering.md

index.md

text-rendering.md

tile.json