Интеграция токенайзеров с рендерерами

В библиотеке Marked процесс обработки Markdown текста делится на два ключевых этапа: токенизация и рендеринг. Токенайзер преобразует исходный текст в структурированный набор токенов, каждый из которых описывает отдельный элемент Markdown (заголовок, список, ссылка, код и т.д.). Эти токены затем передаются рендереру, который преобразует их в HTML или другой целевой формат.

Применение токенайзеров позволяет более гибко контролировать парсинг текста, добавлять кастомные правила или модифицировать стандартное поведение Markdown.


Создание кастомного токенайзера

В Marked можно создавать собственные токенайзеры, наследуя стандартное поведение или полностью переопределяя его. Основная структура кастомного токенайзера:

const { Lexer, Parser } = require('marked');

class CustomTokenizer {
  constructor() {}

  // Пример обработки заголовков
  heading(src) {
    const match = /^#{1,6}\s*(.+)/.exec(src);
    if (match) {
      return {
        type: 'heading',
        raw: match[0],
        depth: match[0].trim().split(' ')[0].length,
        text: match[1]
      };
    }
    return false;
  }

  // Пример обработки жирного текста
  strong(src) {
    const match = /^\*\*(.+?)\*\*/.exec(src);
    if (match) {
      return {
        type: 'strong',
        raw: match[0],
        text: match[1]
      };
    }
    return false;
  }
}

Ключевые моменты:

  • Метод токенайзера возвращает объект токена или false, если правило не сработало.
  • raw — исходная строка, которая была распознана.
  • Другие поля (text, depth, href, title и т.д.) описывают содержание токена и его свойства.

Настройка рендерера

Рендерер принимает токены и преобразует их в HTML. Можно использовать стандартный Renderer или создавать кастомный класс:

const { Renderer } = require('marked');

class CustomRenderer extends Renderer {
  heading(token) {
    return `<h${token.depth} class="custom-heading">${token.text}</h${token.depth}>`;
  }

  strong(token) {
    return `<strong class="highlight">${token.text}</strong>`;
  }
}

Особенности интеграции:

  • Рендерер обрабатывает каждый токен по его типу (heading, paragraph, list, link и т.д.).
  • Возвращаемая строка становится частью итогового HTML.
  • Можно добавлять дополнительные атрибуты, классы и элементы для стилизации или расширенной функциональности.

Интеграция токенайзера и рендерера

После создания кастомных токенайзера и рендерера их интеграция выполняется через экземпляр marked:

const { marked } = require('marked');

const lexer = new Lexer({ tokenizer: new CustomTokenizer() });
const renderer = new CustomRenderer();

const markdown = `
# Заголовок уровня 1
Это **жирный** текст.
`;

const tokens = lexer.lex(markdown);
const html = marked.parser(tokens, { renderer });

console.log(html);

Пошаговая логика:

  1. Lexer разбивает Markdown на токены.
  2. Токены сохраняют информацию о типе, содержимом и исходной строке.
  3. Parser с кастомным рендерером преобразует токены в HTML.

Этот подход позволяет точно контролировать как разбор текста, так и итоговую разметку.


Кастомные правила токенизации

Можно добавлять нестандартные правила для специфичных Markdown-синтаксисов:

class ExtendedTokenizer extends CustomTokenizer {
  highlight(src) {
    const match = /==(.+?)==/.exec(src);
    if (match) {
      return {
        type: 'highlight',
        raw: match[0],
        text: match[1]
      };
    }
    return false;
  }
}

class ExtendedRenderer extends CustomRenderer {
  highlight(token) {
    return `<mark>${token.text}</mark>`;
  }
}

Особенности:

  • Новые токены интегрируются в общий поток обработки.
  • Parser автоматически вызывает соответствующие методы рендерера по типу токена.
  • Возможность создавать собственный синтаксис без модификации исходного Marked.

Совмещение стандартных и кастомных правил

Можно комбинировать стандартные правила Marked и свои кастомные токенайзеры:

const { Lexer, marked } = require('marked');

const lexer = new Lexer({ tokenizer: new ExtendedTokenizer() });
const renderer = new ExtendedRenderer();

const html = marked.parse(markdown, { lexer, renderer });

Примечания:

  • Кастомные токенайзеры вызываются после стандартных.
  • Если токен не распознан кастомным правилом, парсер использует стандартное поведение.
  • Такой подход минимизирует риск поломки стандартного Markdown.

Практические рекомендации

  • Выделять отдельные классы для токенайзера и рендерера при сложной логике.
  • Для новых токенов всегда определять raw и text, а при необходимости — дополнительные поля (depth, href).
  • Использовать наследование, чтобы переопределять только нужные правила, не дублируя стандартный функционал.
  • Тестировать на разнообразных Markdown-примерах, чтобы убедиться, что новые правила не конфликтуют со стандартными.

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