-
-
Notifications
You must be signed in to change notification settings - Fork 6
C API
Apex provides a simple C API for programmatic Markdown to HTML conversion.
If you use Apex via Swift Package Manager in an Xcode Swift target, add the
ApexC product to your target dependencies, then use:
import ApexCfor direct C API calls like apex_options_default() and apex_markdown_to_html(...).
import Apex exposes the high-level Swift wrapper API (Apex, ApexOptions, ApexMode), which is recommended for most Swift use cases.
import ApexC
let markdown = "# Hello from C API\n"
var opts = apex_options_default()
opts.mode = APEX_MODE_UNIFIED
let html: String = markdown.withCString { cstr in
guard let out = apex_markdown_to_html(cstr, strlen(cstr), &opts) else {
return ""
}
defer { apex_free_string(out) }
return String(cString: out)
}The Apex C API is thread-safe and designed for integration into C and C++ applications. All functions are declared in apex/apex.h.
Processor compatibility modes:
typedef enum {
APEX_MODE_COMMONMARK = 0, /* Pure CommonMark spec */
APEX_MODE_GFM = 1, /* GitHub Flavored Markdown */
APEX_MODE_MULTIMARKDOWN = 2, /* MultiMarkdown compatibility */
APEX_MODE_KRAMDOWN = 3, /* Kramdown compatibility */
APEX_MODE_UNIFIED = 4 /* All features enabled */
} apex_mode_t;Configuration structure for processing:
typedef struct {
apex_mode_t mode;
/* Feature flags */
bool enable_tables;
bool enable_footnotes;
bool enable_definition_lists;
bool enable_smart_typography;
bool enable_math;
bool enable_critic_markup;
bool enable_wiki_links;
bool enable_task_lists;
bool enable_attributes;
bool enable_callouts;
bool enable_marked_extensions;
/* Critic markup mode */
int critic_mode; /* 0=accept, 1=reject, 2=markup (default) */
/* Metadata handling */
bool strip_metadata;
bool enable_metadata_variables;
/* File inclusion */
bool enable_file_includes;
int max_include_depth;
const char *base_directory;
/* Output options */
bool unsafe; /* Allow raw HTML */
bool validate_utf8;
bool github_pre_lang;
bool standalone; /* Generate complete HTML document */
bool pretty; /* Pretty-print HTML */
const char *stylesheet_path;
const char *document_title;
/* Line break handling */
bool hardbreaks; /* Treat newlines as hard breaks */
bool nobreaks; /* Render soft breaks as spaces */
/* Header ID generation */
bool generate_header_ids;
bool header_anchors; /* Generate <a> tags instead of id attributes */
int id_format; /* 0=GFM, 1=MMD, 2=Kramdown */
/* Table options */
bool relaxed_tables;
/* List options */
bool allow_mixed_list_markers; /* Allow mixed list markers at same level (inherit type from first item) */
bool allow_alpha_lists; /* Support alpha list markers (a., b., c. and A., B., C.) */
} apex_options;Get default options with all features enabled (unified mode).
apex_options apex_options_default(void);Returns: Default options structure
Example:
apex_options opts = apex_options_default();
opts.enable_math = true;
opts.pretty = true;Get options configured for a specific processor mode.
apex_options apex_options_for_mode(apex_mode_t mode);Parameters:
-
mode- Desired processor mode
Returns: Options configured for the specified mode
Example:
apex_options gfm_opts = apex_options_for_mode(APEX_MODE_GFM);
apex_options mmd_opts = apex_options_for_mode(APEX_MODE_MULTIMARKDOWN);Main conversion function - converts Markdown to HTML.
char *apex_markdown_to_html(const char *markdown, size_t len,
const apex_options *options);Parameters:
-
markdown- Input Markdown text (UTF-8) -
len- Length of input text (usestrlen()if null-terminated) -
options- Processing options (NULL for defaults)
Returns: Newly allocated HTML string (must be freed with apex_free_string), or NULL on error
Example:
const char *markdown = "# Hello\n\nThis is **bold**.";
apex_options opts = apex_options_default();
char *html = apex_markdown_to_html(markdown, strlen(markdown), &opts);
if (html) {
printf("%s\n", html);
apex_free_string(html);
}Free a string allocated by Apex.
void apex_free_string(char *str);Parameters:
-
str- String to free (can be NULL, safe to call on NULL)
Example:
char *html = apex_markdown_to_html(markdown, len, NULL);
// Use html...
apex_free_string(html);Wrap HTML content in a complete HTML5 document structure.
char *apex_wrap_html_document(const char *content, const char *title,
const char *stylesheet_path);Parameters:
-
content- HTML content to wrap -
title- Document title (NULL for default "Document") -
stylesheet_path- Path to CSS file to link (NULL for none)
Returns: Newly allocated HTML document string (must be freed with apex_free_string)
Example:
char *fragment = apex_markdown_to_html(markdown, len, &opts);
char *document = apex_wrap_html_document(fragment, "My Document", "style.css");
apex_free_string(fragment);
// Use document...
apex_free_string(document);Pretty-print HTML with proper indentation.
char *apex_pretty_print_html(const char *html);Parameters:
-
html- HTML to format
Returns: Newly allocated formatted HTML string (must be freed with apex_free_string)
Example:
char *html = apex_markdown_to_html(markdown, len, &opts);
char *pretty = apex_pretty_print_html(html);
apex_free_string(html);
// Use pretty...
apex_free_string(pretty);Get version string.
const char *apex_version_string(void);Returns: Version string (e.g., "0.1.0")
Get individual version components.
int apex_version_major(void);
int apex_version_minor(void);
int apex_version_patch(void);Returns: Version component as integer
When allow_mixed_list_markers is enabled, lists with different marker types at the same indentation level will inherit the type from the first item. This is enabled by default in MultiMarkdown and Unified modes.
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
// opts.allow_mixed_list_markers is true by default
// To enable in other modes:
opts.allow_mixed_list_markers = true;
// To disable in unified/multimarkdown modes:
opts.allow_mixed_list_markers = false;Example markdown:
1. First numbered item
* Second item (becomes numbered)
* Third item (becomes numbered)When allow_alpha_lists is enabled, alphabetic markers (a., b., c. for lower-alpha and A., B., C. for upper-alpha) are converted to HTML lists with appropriate list-style-type CSS. This is enabled by default in Unified mode only.
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
// opts.allow_alpha_lists is true by default
// To enable in other modes:
opts.allow_alpha_lists = true;
// To disable in unified mode:
opts.allow_alpha_lists = false;Example markdown:
a. First item
b. Second item
c. Third itemThis produces HTML with style="list-style-type: lower-alpha" on the <ol> tag.
All feature flags control whether specific Markdown extensions are enabled:
-
enable_tables- GFM-style tables -
enable_footnotes- Reference and inline footnotes -
enable_definition_lists- Definition lists (term: definition) -
enable_smart_typography- Smart quotes, dashes, ellipsis -
enable_math- LaTeX math (inline$...$and display$$...$$) -
enable_critic_markup- Critic Markup for change tracking -
enable_wiki_links-[[WikiLink]]syntax -
enable_task_lists- GFM task lists (- [ ]and- [x]) -
enable_attributes- Inline Attribute Lists (IAL) -
enable_callouts- Bear/Obsidian-style callouts -
enable_marked_extensions- Marked app extensions (TOC, page breaks, etc.)
These are typically enabled/disabled based on the selected mode, but can be overridden.
-
strip_metadata- Remove metadata from output (default: true) -
enable_metadata_variables- Enable[%key]variable replacement (default: true in MMD/Kramdown/Unified modes)
-
enable_file_includes- Enable file inclusion syntax (<<[file.md],{{file.md}},/file.md) -
max_include_depth- Maximum nesting depth for includes (default: 10) -
base_directory- Base directory for resolving relative include paths (NULL = current directory)
-
unsafe- Allow raw HTML in output (default: true) -
validate_utf8- Validate UTF-8 encoding (default: true) -
github_pre_lang- Use GitHub-style code block language format (default: true) -
standalone- Generate complete HTML document with<html>,<head>,<body>(default: false) -
pretty- Pretty-print HTML with indentation (default: false) -
stylesheet_path- Path to CSS file to link in<head>(requiresstandalone) -
document_title- Title for HTML document (requiresstandalone)
-
hardbreaks- Treat newlines as hard breaks (<br>) like GFM (default: false, true in GFM mode) -
nobreaks- Render soft breaks as spaces instead of newlines (default: false)
-
generate_header_ids- Automatically generate IDs for headers (default: true) -
header_anchors- Generate<a>anchor tags instead ofidattributes (default: false) -
id_format- ID format:0= GFM (with dashes),1= MMD (no dashes),2= Kramdown
-
relaxed_tables- Support tables without separator rows (Kramdown-style, default: true in Unified/Kramdown modes) -
per_cell_alignment- Enable per-cell alignment markers using colons at start/end of cells (default: true in Unified mode, false in other modes)
#include <apex/apex.h>
#include <stdio.h>
#include <string.h>
int main() {
const char *markdown =
"---\n"
"title: Test Document\n"
"author: John Doe\n"
"---\n"
"\n"
"# [%title]\n"
"\n"
"By [%author]\n"
"\n"
"This has **bold** and a [[WikiLink]].\n"
"\n"
"Math: $E = mc^2$\n"
"\n"
"1. Mixed markers\n"
"* Second item\n"
"\n"
"a. Alpha list\n"
"b. Second alpha\n";
// Use unified mode with all features
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
// Mixed markers and alpha lists are enabled by default in unified mode
opts.pretty = true;
opts.standalone = true;
opts.document_title = "Test Document";
// Convert to HTML
char *html = apex_markdown_to_html(markdown, strlen(markdown), &opts);
if (html) {
printf("%s\n", html);
apex_free_string(html);
return 0;
} else {
fprintf(stderr, "Conversion failed\n");
return 1;
}
}Plugin listing and installation for app UIs is declared in apex/plugins.h (also included by apexc.h).
typedef struct apex_plugin_info {
char *id;
char *title;
char *description;
char *author;
char *homepage;
char *repo;
} apex_plugin_info;
typedef struct apex_plugin_catalog {
apex_plugin_info *items;
size_t count;
} apex_plugin_catalog;#include <apex/plugins.h>
apex_plugin_catalog *catalog = apex_plugin_catalog_fetch_default();
if (catalog) {
for (size_t i = 0; i < catalog->count; i++) {
apex_plugin_info *p = &catalog->items[i];
printf("%s - %s (%s)\n", p->id, p->title ? p->title : "", p->repo);
}
apex_plugin_catalog_free(catalog);
}The default catalog URL is APEX_PLUGIN_DIRECTORY_URL (ApexMarkdown/apex-plugins on GitHub).
Uses the same discovery order as runtime plugin loading (project .apex/plugins, then user-global ~/.config/apex/plugins):
apex_plugin_discovery_options opts = {
.base_directory = "/path/to/document",
.include_project = true,
.include_user_global = true,
};
apex_plugin_catalog *installed = apex_plugins_list_installed(&opts);Installs into the user-global plugins directory. Requires git and curl on PATH (same as the CLI).
apex_plugin_install_options install_opts = {
.allow_untrusted_repo = false,
.run_post_install = true,
};
char err[512];
if (apex_plugin_install("kbd", &install_opts, err, sizeof(err)) != 0) {
fprintf(stderr, "Install failed: %s\n", err);
}
if (apex_plugin_uninstall("kbd", err, sizeof(err)) != 0) {
fprintf(stderr, "Uninstall failed: %s\n", err);
}Set allow_untrusted_repo to true when installing from a Git URL or user/repo shorthand instead of a directory id.
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
opts.enable_plugins = true;
char *html = apex_markdown_to_html(markdown, len, &opts);# In your CMakeLists.txt
find_package(apex REQUIRED)
target_link_libraries(your_app apex)# Compile
gcc -c -I/usr/local/include your_app.c
# Link
gcc your_app.o -L/usr/local/lib -lapex -o your_app
# Run (may need library path)
DYLD_LIBRARY_PATH=/usr/local/lib ./your_app # macOS
LD_LIBRARY_PATH=/usr/local/lib ./your_app # Linuxgcc your_app.c -I/usr/local/include -L/usr/local/lib -lapex -static -o your_appApex is thread-safe as long as:
- Each thread uses its own
apex_optionsstructure - Document nodes are not shared between threads
- The conversion function can be called from multiple threads simultaneously
Example:
// Thread-safe: each thread has its own options
void *thread_func(void *arg) {
apex_options opts = apex_options_default();
char *html = apex_markdown_to_html(markdown, len, &opts);
// Use html...
apex_free_string(html);
return NULL;
}- All strings returned by Apex must be freed with
apex_free_string() -
apex_optionsstructures are typically stack-allocated (no freeing needed) - Safe to call
apex_free_string(NULL) - Always check return values before use
- Functions return
NULLor empty strings on error - Check return values before use
- Errors are generally due to:
- Memory allocation failures
- Invalid input
- Missing dependencies
Example:
char *html = apex_markdown_to_html(markdown, len, &opts);
if (!html) {
fprintf(stderr, "Conversion failed\n");
return 1;
}
// Use html...
apex_free_string(html);-
Reuse options: Create
apex_optionsonce and reuse for multiple conversions -
Provide accurate length: Avoid unnecessary
strlen()calls - Disable unused features: Turn off extensions you don't need
- Batch processing: Process multiple documents in parallel (thread-safe)
Example:
// Good: reuse options
apex_options opts = apex_options_default();
for (int i = 0; i < count; i++) {
char *html = apex_markdown_to_html(docs[i], lens[i], &opts);
// Process...
apex_free_string(html);
}- Xcode Integration - Using Apex in Xcode projects
- Modes - Understanding processor modes
- Header IDs - Header ID generation options
Copyright 2025 Brett Terpstra, All Rights Reserved | MIT License
- Getting Started - Your first steps with Apex
- Installation - How to build and install Apex
- Usage - Basic usage examples
- Syntax - Complete syntax reference for unified mode
- Callouts - All supported callout formats and flag requirements
- Tables - Complete table syntax reference including rowspan, colspan, alignment, and captions
- Inline Attribute Lists - IALs and ALDs guide with examples
- Modes - Understanding processor modes
-
Quarto Mode - Pandoc/Quarto markdown (
--mode quarto) - Command Line Options - All CLI flags explained
-
Rendering Markdown in the Terminal - Terminal output formats (
-t terminal,-t terminal256),--width,--theme, and theming -
Generating Man Pages - Generate man pages from Markdown using
apex -t man -
Multi-file Documents - Combining multiple files with
--combine,--mmd-merge, and includes - Citations - Citations and bibliography guide
- Indices - Index generation with mmark and TextIndex syntax
-
Metadata Transforms - Transform metadata values with
[%key:transform] - Integrating with Pandoc - Use Apex with Pandoc for DOCX, PDF, and more
- Using Apex with Jekyll - Use the apex-ruby gem as Jekyll’s Markdown converter (untested; feedback welcome)
- Header IDs - How header IDs are generated
- C API - Programmatic API documentation
- Writing Tests - How to add tests for new features
- Xcode Integration - Using Apex in Xcode projects
- Examples - Practical usage examples
- Plugins - Plugin system, examples, and recipes
- Filters - AST filters (Pandoc-style JSON), examples, and usage
- Troubleshooting - Common issues and solutions
- Credits - Acknowledgments and links to related projects