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.
1.2. Links
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(no fields) |
|
(no fields) |
|
|