Глава 37 / 51
Том 5 · Расширения, эксплуатация и практика

md4c: рендеринг Markdown

Два символа, делающие одно дело: разбор CommonMark и рендеринг HTML. Малый и правильный выбор — событийный поток кормит свои рендереры, одношаговый ведёт документацию.

Обзор

Позиционирование: образец инструментального расширения — в отличие от sqlite/xllm оно не несёт предметной области, а закрывает кусочек пазла «контролируемого содержимого». Предусловие: вариант build.bat md4c.

37.1 Позиция

md4c — парсер, соответствующий CommonMark: ни DOM, ни строковых аллокаций — разбор есть поток событий. Эта минимальность идеально ложится на скриптовый мир XServer: когда вход — недоверенное содержимое, отрендерить его в контролируемый HTML куда безопаснее, чем исполнять шаблоны (идея контролируемых блоков гл. 19). Два импортируемых символа (md_parse / md_html) — вся поверхность API.

37.2 Модель событийного потока

md_parse колбэчит в порядке документа: вход/выход блоков (DOC/P/LI/CODE/TABLE…), вход/выход span'ов (EM/STRONG/CODE_SPAN/LINK/IMG…) и куски текста. Структуру колбэков заполняют целиком — пустые указатели не проверяются (жёсткое отличие от большинства библиотек); ненулевой возврат любого колбэка прерывает разбор. Свои рендереры (извлечение выжимки, генерация оглавления, очистка до текста) строятся на этом слое.

Каркас минимального рендерера
static int enter_block(MD_BLOCKTYPE t, void* d, void* u)
{
    return 0;  /* продолжаем */
}
/* leave_block / enter_span / leave_span / text той же формы */
MD_PARSER tParser = { 0, NULL, enter_block, leave_block,
                      enter_span, leave_span, text_cb };
md_parse(sDoc, iLen, &tParser, &tCtx);

37.3 Одношаговый рендеринг

md_html несёт полный набор колбэков и рендерит Markdown в HTML-фрагмент, передавая его через ваш колбэк вывода — та же форма мысли, что у md_parse. Флаги включают диалекты: таблицы, зачёркивание, списки задач, вики-ссылки. Экранирование HTML включено по умолчанию (теги исходника экранируются) — ключевое умолчание, превращающее недоверенный вход в безопасный вывод.

37.4 Связка с шаблонами

Типовой конвейер: Markdown из запроса → md_html рендерит в буфер → переменной входит в контролируемый блок шаблона гл. 19. Поля документов храните как Markdown и рендерьте в момент запроса — удобство редактирования и безопасность вместе. Статические сайты документации рендерятся один раз на Init (дисциплина горячего кэша гл. 41).

37.5 Контракт

ВопросГлавное
Памятьразбор без аллокаций; вывод md_html приходит через колбэк, библиотека буферов не держит
Колбэкиструктура заполняется целиком; ненулевой возврат — остановка (канал ошибок)
Символыпо умолчанию char; при _UNICODE MD_CHAR — WCHAR (TCC-скрипты по умолчанию узкие)
Проверка#ifdef XS_USE_MD4C или xsExtensionEnabled("md4c")

Частые ошибки

Нулевую структуру колбэков — в дело
Симптом
md_parse получает парсер {0} — процесс падает по доступу.
Причина
Указатели колбэков не проверяются — библиотека зовёт их напрямик. Заполняйте структуру целиком или берите md_html со своими колбэками.
Вывод вклеивают в страницу как есть
Симптом
HTML рендера напрямую уходит в самодельную страницу — скриптовые инъекции оживают.
Причина
md_html экранирует теги исходника Markdown; всё, что вы дописываете после, подчиняется правилам экранирования шаблона (гл. 19).

Упражнения

БазаСравнение рендеров

Один Markdown с таблицами и блоками кода отрендерить с базовыми флагами и GFM; сравнить вывод.

Приёмка: таблица собирается только в GFM; теги исходника экранированы.
ПродвинутыйГенератор оглавления

Из событий md_parse собрать H1-H3 в дерево оглавления с якорями.

Приёмка: уровни верны на любом документе; глубоко вложенные списки не мешают.
ВызовМаршрут документации

GET /docs/:name читает файл Markdown → рендер → переменная в контролируемый блок шаблона, кэш на Init.

Приёмка: обновления файлов приходят через перезагрузку; враждебный образец не даёт исполняемой инъекции.

Итоги

ПунктВывод
Позицияразбор CommonMark + рендер HTML, 2 символа — весь API
Событияmd_parse без аллокаций, колбэчный — фундамент своих рендереров
Один шагmd_html с готовыми колбэками + диалекты флагов + экранирование
СвязкаMarkdown в контролируемые блоки; статика рендерится на Init
Жёсткоеколбэки заполняются целиком; ненулевой возврат — стоп
Дальнейшее чтение по xrt
  • xrt · template — партнёр по композиции контролируемых блоков