Groovy has an optional groovy-markdown module which provides support for parsing CommonMark Markdown. The classes are found in the groovy.markdown package.

1. MarkdownSlurper

MarkdownSlurper parses Markdown text into a MarkdownDocument backed by nested lists and maps. Each node is a Map with a type key plus type-specific fields:

def doc = new MarkdownSlurper().parseText('# Hello World')
def h = doc.headings[0]
assert h.level == 1
assert h.text == 'Hello World'

The raw structure supports all standard Groovy list/map operations. The document also exposes convenience properties — headings, codeBlocks, links, tables — that recursively walk the tree.

1.1. Extracting code blocks

A common pattern when consuming LLM output is to pull fenced code blocks by language:

def md = '''
    Some intro text.

    ```groovy
    println 'hi'
    ```

    ```sql
    SELECT 1
    ```
    '''.stripIndent()
def doc = new MarkdownSlurper().parseText(md)
def groovySnippets = doc.codeBlocks.findAll { it.lang == 'groovy' }*.text
assert groovySnippets == ["println 'hi'\n"]

codeBlocks walks the entire tree, so blocks nested inside block quotes or list items are included.

def doc = new MarkdownSlurper().parseText('See [docs](https://example.com/docs).')
def link = doc.links[0]
assert link.href == 'https://example.com/docs'
assert link.text == 'docs'

1.3. Sections

section(headingText) returns the nodes between the given heading and the next heading of equal or higher level — useful for parsing structured agent replies:

def md = '''
    # Title

    ## Summary

    The work is on track.

    ## Next Steps

    - Item one
    - Item two
    '''.stripIndent()
def doc = new MarkdownSlurper().parseText(md)
def summary = doc.section('Summary')
assert summary[0].type == 'paragraph'
assert summary[0].children[0].value == 'The work is on track.'

def next = doc.section('Next Steps')
assert next[0].type == 'list'
assert next[0].items*.text == ['Item one', 'Item two']

1.4. Tables (optional)

GFM-style tables are supported when the org.commonmark:commonmark-ext-gfm-tables jar is on the classpath. Call enableTables(true) on the slurper:

def md = '''
    | name  | age |
    |-------|-----|
    | Alice | 30  |
    | Bob   | 25  |
    '''.stripIndent()
def doc = new MarkdownSlurper().enableTables(true).parseText(md)
def rows = doc.tables[0].rows
assert rows[0].name == 'Alice'
assert rows[0].age == '30'
assert rows[1].name == 'Bob'

Each row is returned as a Map keyed by header.

1.5. Node types

type fields

heading

level, text, children

paragraph

children

code_block

lang, text

list

ordered, items (start for ordered lists)

list_item

text, children

block_quote

children

link

href, title, text, children

image

src, title, alt

text

value

inline_code

text

emphasis, strong

children

html_block, html_inline

text

thematic_break

(no fields)

hard_line_break, soft_line_break

(no fields)

table

headers, alignments, rows