md4c: рендеринг Markdown
Два символа, делающие одно дело: разбор CommonMark и рендеринг HTML. Малый и правильный выбор — событийный поток кормит свои рендереры, одношаговый ведёт документацию.
Обзор
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; сравнить вывод.
Из событий md_parse собрать H1-H3 в дерево оглавления с якорями.
GET /docs/:name читает файл Markdown → рендер → переменная в контролируемый блок шаблона, кэш на Init.
Итоги
| Пункт | Вывод |
|---|---|
| Позиция | разбор CommonMark + рендер HTML, 2 символа — весь API |
| События | md_parse без аллокаций, колбэчный — фундамент своих рендереров |
| Один шаг | md_html с готовыми колбэками + диалекты флагов + экранирование |
| Связка | Markdown в контролируемые блоки; статика рендерится на Init |
| Жёсткое | колбэки заполняются целиком; ненулевой возврат — стоп |
- xrt · template — партнёр по композиции контролируемых блоков