Кастомные валидаторы

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-it.
  • Логика проверки централизована и повторно используется.

Подведение к практике

При работе с кастомными валидаторами стоит помнить:

  • Валидировать можно любой токен и его вложенные элементы.
  • Модификация токена на этапе ядра безопасна и отразится на рендеринге.
  • Регулярные выражения и проверка атрибутов расширяют возможности контроля над контентом.
  • Создание плагинов позволяет объединять валидацию, трансформацию и дополнительные проверки в один модуль.

Это создаёт мощный инструмент для построения контролируемого и безопасного Markdown, где можно проверять контент, ссылки, форматирование и структуру документа ещё до генерации HTML.