Build, transform, validate, and extract content from structured documents using the duck_block type
Installing and Loading
INSTALL duck_block_utils FROM community;
LOAD duck_block_utils;
Example
-- Build a document programmatically
SELECT duck_blocks_assemble([
duck_block_heading(1, 'Hello World'),
duck_block_paragraph([
duck_block_text('This is '),
duck_block_bold('important'),
duck_block_text(' content.')
]),
duck_block_code('sql', 'SELECT * FROM documents')
]);
-- Render it as plain text
SELECT duck_blocks_to_text(duck_blocks_assemble([
duck_block_heading(1, 'Hello World'),
duck_block_paragraph('Some prose.')
]));
-- Works with the markdown extension for reading and writing files
LOAD markdown;
LOAD duck_block_utils;
-- Table of contents from a markdown file
SELECT * FROM duck_blocks_toc_rows(
(SELECT list(b) FROM read_markdown_blocks('README.md') b)
);
-- Validate document structure
SELECT duck_blocks_validate(
(SELECT list(b) FROM read_markdown_blocks('doc.md') b)
);
About duck_block_utils
The Duck Block Utils extension provides tools for building, transforming, validating, and
extracting content from structured documents. All functions use the duck_block type - a
unified representation for document elements that works across formats.
Documentation: duck-block-utils.readthedocs.io
2.0.0 is a breaking rename
Every db_* function was renamed to duck_block_* (builders) or duck_blocks_*
(operations on a list). 54 names present in 1.6.1 are gone and there is no alias
path, so 1.6.1 queries must be updated. The mapping is mechanical:
db_heading(1, 'Title') -> duck_block_heading(1, 'Title')
db_assemble(blocks) -> duck_blocks_assemble(blocks)
db_blocks_to_text(blocks) -> duck_blocks_to_text(blocks)
db_page -> page_break
The duck_block Type
A unified struct for block-level, inline and metadata-value elements:
STRUCT(
kind VARCHAR, -- 'block', 'inline' or 'value'
element_type VARCHAR, -- 'heading', 'paragraph', 'bold', 'link', etc.
content VARCHAR, -- text, iff the element has a single text child
level INTEGER, -- depth in a depth-first ordering; top is 1
encoding VARCHAR, -- 'text', 'json', 'yaml', 'html', 'xml',
-- 'latex', 'markdown', 'toml'
attributes MAP(VARCHAR, VARCHAR), -- element-specific metadata
element_order INTEGER -- position in the document
)
level is structural depth, never a heading rank - a heading's rank lives in
attributes['heading_level']. content is populated if and only if an element has
a single text child; a heading additionally keeps a flattened title alongside its
rich children.
Builders
All builder functions return LIST(duck_block) so they compose by nesting:
SELECT duck_block_paragraph([
duck_block_text('Click '),
duck_block_link('https://example.com', 'here'),
duck_block_text(' for more.')
]);
| Function | Description |
|---|---|
duck_block_heading(level, content) |
Heading (h1-h6) |
duck_block_paragraph(content) |
Paragraph |
duck_block_code(language, content) |
Fenced code block |
duck_block_blockquote(content) |
Block quote |
duck_block_list_block(ordered, items[]) |
Ordered or unordered list |
duck_block_hr() |
Horizontal rule |
duck_block_image(src, alt, title) |
Image |
duck_block_metadata(content) |
Metadata blob (YAML frontmatter) |
duck_block_raw(format, content) |
Raw content block |
duck_block_text(content) |
Plain text |
duck_block_bold(content) |
Bold |
duck_block_italic(content) |
Italic |
duck_block_link(href, content) |
Hyperlink |
duck_block_inline_code(content) |
Inline code |
duck_block_math(content) |
Math |
duck_block_strikethrough(content) |
Strikethrough |
duck_block_superscript(content) / duck_block_subscript(content) |
Super/subscript |
Assembly
| Function | Description |
|---|---|
duck_blocks_assemble(blocks[]) |
Combine blocks into a document |
duck_blocks_document(blocks[]) |
Alias for assemble |
duck_block_section(level, title, children[]) |
Section with a heading |
duck_blocks_concat(a, b) |
Concatenate block lists |
duck_blocks_rebase_levels(blocks, offset) |
Shift heading levels |
Query and extraction
| Function | Description |
|---|---|
duck_blocks_to_text(blocks) |
Plain text |
duck_blocks_headings(blocks) |
Heading hierarchy |
duck_blocks_toc_rows(blocks) |
Table of contents, one row per entry |
duck_blocks_get_section(blocks, pattern) |
One section by title or id |
duck_blocks_sections_like(blocks, term) |
Sections whose text matches |
duck_blocks_page_rows(blocks) |
Page boundaries, separate from the outline |
duck_blocks_get_pages(blocks, first, last) |
A page range |
duck_blocks_links(blocks) |
Every link |
duck_blocks_stats(blocks) / duck_blocks_structure(blocks) |
Document shape |
duck_blocks_diff(before, after) |
ADDED / REMOVED / MOVED between two versions |
Validation
| Function | Description |
|---|---|
duck_blocks_validate(blocks) |
Spec conformance: valid, plus errors |
duck_blocks_lint(blocks) |
Advisory conformance warnings |
duck_blocks_quality(blocks) |
Document quality, which is NOT conformance |
duck_block_spec_version() |
The spec version this build implements |
Rendering
| Function | Description |
|---|---|
duck_blocks_render_ansi(blocks, width) |
Terminal rendering with themes |
Pandoc integration
Convert between Pandoc's JSON AST and duck_block without invoking Pandoc:
| Function | Description |
|---|---|
pandoc_ast_to_blocks(json) |
Parse a Pandoc AST |
duck_blocks_to_pandoc_ast(blocks) |
Emit a full Pandoc document |
duck_blocks_to_pandoc_blocks(blocks) |
Emit just the block list |
read_pandoc_ast(path) / write_pandoc_ast(path, blocks) |
Read and write AST files |
Ecosystem
- duckdb_markdown -
read_markdown_blocks(), andCOPY ... TO ... (FORMAT markdown, MARKDOWN_MODE duck_block) - duckdb_webbed - HTML parsing and generation
-- Read markdown, append to it, write it back
LOAD markdown;
LOAD duck_block_utils;
COPY (
SELECT unnest(duck_blocks_concat(
(SELECT list(b) FROM read_markdown_blocks('doc.md') b),
duck_blocks_assemble([
duck_block_hr(),
duck_block_paragraph('Generated by DuckDB')
])
)) AS block
) TO 'output.md' (FORMAT markdown, MARKDOWN_MODE duck_block);
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| duck_block | scalar | NULL | NULL | |
| duck_block_aliases | pragma | NULL | NULL | |
| duck_block_attr | scalar | NULL | NULL | |
| duck_block_blockquote | scalar | NULL | NULL | |
| duck_block_bold | scalar | NULL | NULL | |
| duck_block_cite | scalar | NULL | NULL | |
| duck_block_code | scalar | NULL | NULL | |
| duck_block_content | scalar | NULL | NULL | |
| duck_block_div | scalar | NULL | NULL | |
| duck_block_doc_macros | pragma | NULL | NULL | |
| duck_block_encoding | scalar | NULL | NULL | |
| duck_block_encoding_names | scalar | NULL | NULL | |
| duck_block_ensure_extension | scalar | NULL | NULL | |
| duck_block_heading | scalar | NULL | NULL | |
| duck_block_hr | scalar | NULL | NULL | |
| duck_block_image | scalar | NULL | NULL | |
| duck_block_inline_code | scalar | NULL | NULL | |
| duck_block_inline_image | scalar | NULL | NULL | |
| duck_block_italic | scalar | NULL | NULL | |
| duck_block_kind_names | scalar | NULL | NULL | |
| duck_block_level | scalar | NULL | NULL | |
| duck_block_linebreak | scalar | NULL | NULL | |
| duck_block_link | scalar | NULL | NULL | |
| duck_block_list | scalar | NULL | NULL | |
| duck_block_list_block | scalar | NULL | NULL | |
| duck_block_list_item | scalar | NULL | NULL | |
| duck_block_math | scalar | NULL | NULL | |
| duck_block_metadata | scalar | NULL | NULL | |
| duck_block_note | scalar | NULL | NULL | |
| duck_block_order | scalar | NULL | NULL | |
| duck_block_paragraph | scalar | NULL | NULL | |
| duck_block_plain | scalar | NULL | NULL | |
| duck_block_quoted | scalar | NULL | NULL | |
| duck_block_raw | scalar | NULL | NULL | |
| duck_block_raw_inline | scalar | NULL | NULL | |
| duck_block_render | pragma | NULL | NULL | |
| duck_block_section | scalar | NULL | NULL | |
| duck_block_set_content | scalar | NULL | NULL | |
| duck_block_set_level | scalar | NULL | NULL | |
| duck_block_set_order | scalar | NULL | NULL | |
| duck_block_smallcaps | scalar | NULL | NULL | |
| duck_block_softbreak | scalar | NULL | NULL | |
| duck_block_space | scalar | NULL | NULL | |
| duck_block_span | scalar | NULL | NULL | |
| duck_block_spec_version | scalar | NULL | NULL | |
| duck_block_strikethrough | scalar | NULL | NULL | |
| duck_block_subscript | scalar | NULL | NULL | |
| duck_block_superscript | scalar | NULL | NULL | |
| duck_block_terminal_width | scalar | NULL | NULL | |
| duck_block_text | scalar | NULL | NULL | |
| duck_block_type | scalar | NULL | NULL | |
| duck_block_type_names | scalar | NULL | NULL | |
| duck_block_underline | scalar | NULL | NULL | |
| duck_block_valid | scalar | NULL | NULL | |
| duck_blocks_assemble | scalar | NULL | NULL | |
| duck_blocks_code_blocks | scalar | NULL | NULL | |
| duck_blocks_code_blocks_structs | scalar | NULL | NULL | |
| duck_blocks_concat | scalar | NULL | NULL | |
| duck_blocks_diff | table_macro | NULL | NULL | |
| duck_blocks_document | scalar | NULL | NULL | |
| duck_blocks_exclude | scalar | NULL | NULL | |
| duck_blocks_filter | scalar | NULL | NULL | |
| duck_blocks_get_pages | macro | NULL | NULL | |
| duck_blocks_get_pages_text | macro | NULL | NULL | |
| duck_blocks_get_section | macro | NULL | NULL | |
| duck_blocks_get_section_text | macro | NULL | NULL | |
| duck_blocks_headings | scalar | NULL | NULL | |
| duck_blocks_headings_structs | scalar | NULL | NULL | |
| duck_blocks_inlines_to_pandoc | scalar | NULL | NULL | |
| duck_blocks_links | scalar | NULL | NULL | |
| duck_blocks_links_structs | scalar | NULL | NULL | |
| duck_blocks_lint | scalar | NULL | NULL | |
| duck_blocks_merge | scalar | NULL | NULL | |
| duck_blocks_normalize | scalar | NULL | NULL | |
| duck_blocks_page_rows | table_macro | NULL | NULL | |
| duck_blocks_quality | table_macro | NULL | NULL | |
| duck_blocks_rebase_levels | scalar | NULL | NULL | |
| duck_blocks_render_ansi | scalar | NULL | NULL | |
| duck_blocks_reorder | scalar | NULL | NULL | |
| duck_blocks_sections_like | table_macro | NULL | NULL | |
| duck_blocks_sections_like_text | table_macro | NULL | NULL | |
| duck_blocks_slice | scalar | NULL | NULL | |
| duck_blocks_stamp | scalar | NULL | NULL | |
| duck_blocks_stats | scalar | NULL | NULL | |
| duck_blocks_structure | scalar | NULL | NULL | |
| duck_blocks_to_match_text | macro | NULL | NULL | |
| duck_blocks_to_pandoc_ast | scalar | NULL | NULL | |
| duck_blocks_to_pandoc_blocks | scalar | NULL | NULL | |
| duck_blocks_to_text | scalar | NULL | NULL | |
| duck_blocks_toc | scalar | NULL | NULL | |
| duck_blocks_toc_rows | table_macro | NULL | NULL | |
| duck_blocks_toc_structs | scalar | NULL | NULL | |
| duck_blocks_validate | scalar | NULL | NULL | |
| duck_blocks_version | scalar | NULL | NULL | |
| pandoc_ast | table | NULL | NULL | |
| pandoc_ast_to_blocks | scalar | NULL | NULL | |
| pandoc_inlines_to_db_inlines | scalar | NULL | NULL | |
| pandoc_inlines_to_text | scalar | NULL | NULL | |
| read_pandoc_ast | scalar | NULL | NULL | |
| to_duck_block | scalar | NULL | NULL | |
| write_pandoc_ast | scalar | NULL | NULL |
Overloaded Functions
This extension does not add any function overloads.
Added Types
| type_name | type_size | logical_type | type_category | internal |
|---|---|---|---|---|
| duck_block | 0 | STRUCT | COMPOSITE | true |
| duck_block_ext | 0 | STRUCT | COMPOSITE | true |
Added Settings
This extension does not add any settings.