Добавление новых правил парсинга

Markdown-it предоставляет мощный механизм расширения синтаксиса через пользовательские правила парсинга. Эти правила позволяют обрабатывать нестандартные конструкции Markdown, модифицировать токены или внедрять новую логику в процесс преобразования текста в HTML.

Основные концепции

В основе Markdown-it лежит система токенов, которые формируются в процессе лексического анализа. Каждое правило парсинга получает текстовый поток и токены, которые оно может создавать, изменять или удалять. Для добавления нового правила используется метод md.inline.ruler для инлайновых токенов или md.block.ruler для блочных элементов.

Пример создания правила для инлайнового синтаксиса:

const md = require('markdown-it')();

function insertCustomToken(state, silent) {
  const start = state.pos;
  
  if (state.src.charCodeAt(start) !== 0x40) { // проверка на символ "@"
    return false;
  }

  let match = state.src.slice(start).match(/^@(\w+)/);
  if (!match) return false;
  
  if (!silent) {
    const token = state.push('custom_mention', '', 0);
    token.content = match[1];
  }

  state.pos += match[0].length;
  return true;
}

md.inline.ruler.before('emphasis', 'mention', insertCustomToken);
md.renderer.rules.custom_mention = function(tokens, idx) {
  return `<span class="mention">@${tokens[idx].content}</span>`;
};

console.log(md.render('Привет, @user!'));

В этом примере создается инлайновое правило, которое ищет упоминания в формате @username и преобразует их в HTML-элемент с классом mention.


Блочные правила

Блочные правила работают с целыми абзацами или строками Markdown и чаще всего применяются для расширения синтаксиса заголовков, списков или специальных блоков.

Пример правила для блока “alert”:

function alertBlock(state, startLine, endLine, silent) {
  const start = state.bMarks[startLine] + state.tShift[startLine];
  const max = state.eMarks[startLine];

  if (state.src.slice(start, start + 6) !== '!!!alert') {
    return false;
  }

  let nextLine = startLine + 1;
  while (nextLine < endLine && state.src.slice(state.bMarks[nextLine], state.eMarks[nextLine]).trim() !== '') {
    nextLine++;
  }

  if (!silent) {
    const token = state.push('alert_open', 'div', 1);
    token.attrs = [['class', 'alert']];
    token.map = [startLine, nextLine];

    const contentToken = state.push('inline', '', 0);
    contentToken.content = state.getLines(startLine + 1, nextLine, state.tShift[startLine + 1], true);
    contentToken.map = [startLine + 1, nextLine];

    state.push('alert_close', 'div', -1);
  }

  state.line = nextLine;
  return true;
}

md.block.ruler.before('paragraph', 'alert', alertBlock);
md.renderer.rules.alert_open = () => '<div class="alert">';
md.renderer.rules.alert_close = () => '</div>';

Это правило позволяет использовать конструкцию:

!!!alert
Это сообщение об ошибке или предупреждении.

и преобразует её в HTML-блок <div class="alert">.


Управление порядком правил

Markdown-it обрабатывает правила в том порядке, в котором они зарегистрированы. Для точного контроля доступны методы:

  • before(name, ruleName, ruleFunc) — вставляет новое правило перед существующим.
  • after(name, ruleName, ruleFunc) — вставляет правило после указанного.
  • push(name, ruleFunc) — добавляет правило в конец списка.

Понимание порядка критично, когда новые правила могут пересекаться с существующими синтаксическими конструкциями.


Работа с состоянием парсера

Каждое правило получает объект state, который содержит:

  • src — исходный текст.
  • pos — текущая позиция при обработке инлайнов.
  • bMarks и eMarks — массивы с границами строк.
  • tShift — массив с количеством пробелов до текста.
  • push(type, tag, nesting) — метод создания токена.
  • getLines(start, end, indent, keepLastLF) — извлечение текста из нескольких строк.

Грамотная работа с этим объектом позволяет создавать сложные конструкции, такие как вложенные блоки, кастомные списки и таблицы.


Настройка рендеринга токенов

После того как токен создан, его визуальное представление управляется через renderer.rules. Каждый тип токена можно преобразовать в HTML по собственным правилам:

md.renderer.rules.my_token_type = function(tokens, idx, options, env, self) {
  return `<span class="my-class">${tokens[idx].content}</span>`;
};

Если правило не указано, Markdown-it использует default renderer, который выводит содержимое без изменений.


Примеры сложных расширений

  1. Поддержка кастомных эмодзи с текстовым синтаксисом :emoji:.
  2. Расширенные таблицы с объединением ячеек и цветовым оформлением.
  3. Встроенные виджеты на основе пользовательских блоков для документации.

Все эти расширения строятся на комбинации:

  • Парсинг нестандартного синтаксиса
  • Создание токенов
  • Определение правил рендеринга

Эта система делает Markdown-it гибкой платформой для любых задач преобразования Markdown в HTML, сохраняя при этом высокую скорость работы и предсказуемое поведение при обработке стандартного синтаксиса.


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