Markdown-it предоставляет гибкий механизм для расширения синтаксиса Markdown за счёт плагинов и кастомных правил. Одной из ключевых возможностей является создание кастомных валидаторов, которые позволяют проверять или модифицировать токены на этапе разборки документа.
Markdown-it разбивает исходный Markdown на токены, представляющие отдельные элементы: заголовки, параграфы, ссылки, списки и прочее. Каждый токен имеет структуру:
{
type: 'paragraph_open',
tag: 'p',
attrs: null,
map: [0, 1],
nesting: 1,
level: 0,
children: [...],
content: '',
markup: '',
info: ''
}
Для кастомных валидаторов важно понимать поля type,
tag, attrs, children и
content, так как именно их можно проверять и изменять.
В Markdown-it кастомные проверки реализуются через правила рендеринга или плагины, которые обрабатывают токены после парсинга. Простейший пример проверки текста параграфа:
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
function paragraphValidator(state) {
state.tokens.forEach(token => {
if (token.type === 'paragraph_open') {
const contentToken = state.tokens[state.tokens.indexOf(token) + 1];
if (contentToken && contentToken.type === 'inline') {
if (contentToken.content.includes('запрещённое_слово')) {
console.warn('Найдено запрещённое слово в параграфе:', contentToken.content);
}
}
}
});
}
md.core.ruler.push('paragraph_validator', paragraphValidator);
const result = md.render('Пример параграфа с запрещённое_слово внутри.');
Ключевые моменты:
state.tokens содержит все токены документа.md.core.ruler.push.Для сложных случаев можно проверять атрибуты элементов или ссылки:
function linkValidator(state) {
state.tokens.forEach(token => {
if (token.type === 'link_open') {
const hrefAttr = token.attrs?.find(([name]) => name === 'href');
if (hrefAttr && !hrefAttr[1].startsWith('https://')) {
console.warn('Ссылка должна использовать HTTPS:', hrefAttr[1]);
}
}
});
}
md.core.ruler.push('link_validator', linkValidator);
Особенности:
token.attrs — массив [имя, значение].Иногда нужно валидировать содержимое вложенных токенов. Например, список с обязательным форматом:
function listValidator(state) {
state.tokens.forEach(token => {
if (token.type === 'bullet_list_open') {
const index = state.tokens.indexOf(token);
const nextToken = state.tokens[index + 1];
if (nextToken.type === 'list_item_open') {
const contentToken = state.tokens[index + 2];
if (contentToken.type === 'inline' && !/^\d+\./.test(contentToken.content)) {
console.warn('Элемент списка должен начинаться с номера:', contentToken.content);
}
}
}
});
}
md.core.ruler.push('list_validator', listValidator);
Важные аспекты:
nesting) помогает различать
открытие и закрытие элементов.В дополнение к валидации можно изменять токены, например, автоматически исправлять опечатки:
function contentFixer(state) {
state.tokens.forEach(token => {
if (token.type === 'inline') {
token.content = token.content.replace(/неправильное_слово/g, 'правильное_слово');
}
});
}
md.core.ruler.push('content_fixer', contentFixer);
Замечания:
core до рендеринга
HTML.Кастомные валидаторы удобно использовать совместно с плагинами Markdown-it, создавая единый модуль для проверки и трансформации контента:
function customValidatorPlugin(md) {
md.core.ruler.push('custom_validator', state => {
state.tokens.forEach(token => {
if (token.type === 'inline' && token.content.includes('TODO')) {
console.warn('Найдена TODO пометка:', token.content);
}
});
});
}
md.use(customValidatorPlugin);
Преимущества:
При работе с кастомными валидаторами стоит помнить:
Это создаёт мощный инструмент для построения контролируемого и безопасного Markdown, где можно проверять контент, ссылки, форматирование и структуру документа ещё до генерации HTML.