Трансформация дерева токенов

Marked — это высокопроизводительная библиотека для преобразования Markdown в HTML. Помимо простого конвертера, она предоставляет расширенные возможности работы с внутренней структурой документа через дерево токенов. Токены представляют собой промежуточное представление Markdown-документа в виде структурированных объектов, что позволяет производить сложные трансформации содержимого до генерации HTML.

В Marked процесс обработки Markdown делится на несколько этапов:

  1. Лексический анализ (Tokenization) — исходный текст разбивается на токены, каждый из которых представляет отдельный элемент Markdown (заголовок, список, ссылка, параграф и т.д.).
  2. Парсинг (Parsing) — токены превращаются в HTML или могут быть обработаны через пользовательские функции.
  3. Рендеринг (Rendering) — окончательная генерация HTML.

Структура токена

Каждый токен — это объект JavaScript, имеющий обязательное поле type, определяющее тип элемента, и дополнительные поля в зависимости от типа. Например:

{
  type: 'heading',
  depth: 2,
  text: 'Пример заголовка'
}

Типичные поля токена:

  • type — строка с типом элемента (paragraph, heading, list, list_item, code и т.д.).
  • text — текстовое содержимое токена (для заголовков, параграфов).
  • tokens — массив дочерних токенов (например, для списков или вложенных элементов).
  • depth — глубина заголовка (1-6 для h1–h6).
  • lang — язык программирования для токена code.

Создание дерева токенов

Метод Lexer.lex() используется для преобразования Markdown-текста в массив токенов:

import { Lexer } from 'marked';

const markdown = `
# Заголовок 1

- Пункт 1
- Пункт 2
`;

const tokens = Lexer.lex(markdown);
console.log(tokens);

Вывод будет содержать токены типа heading и list, где каждый пункт списка представлен отдельным токеном list_item.

Для более сложных документов структура токенов становится иерархической, где некоторые токены содержат вложенные массивы tokens.

Трансформация токенов

Трансформация дерева токенов позволяет:

  • изменять текст элементов перед генерацией HTML;
  • вставлять дополнительные токены;
  • удалять нежелательные элементы;
  • модифицировать структуру документа.

Пример: увеличение всех заголовков на один уровень

import { Lexer, Parser } from 'marked';

const tokens = Lexer.lex(markdown);

tokens.forEach(token => {
  if (token.type === 'heading' && token.depth < 6) {
    token.depth += 1;
  }
});

const html = Parser.parse(tokens);

Здесь Parser.parse() принимает массив токенов и превращает их в HTML с учетом внесенных изменений.

Рекурсивная обработка токенов

Для сложных вложенных структур (списки, блоки кода, цитаты) необходимо использовать рекурсивный обход:

function transformTokens(tokens) {
  tokens.forEach(token => {
    if (token.type === 'paragraph') {
      token.text = token.text.toUpperCase();
    }
    if (token.tokens) {
      transformTokens(token.tokens);
    }
  });
}

transformTokens(tokens);

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

Кастомные токены

Marked поддерживает добавление собственных типов токенов для специфических целей. Для этого используется объект Lexer.rules и методы расширения. Например, можно добавить токен для блоков с предупреждением:

Lexer.rules.block.warning = /^!!!\s*(.+)$/m;

function customLexer(src) {
  let tokens = Lexer.lex(src);
  tokens.forEach(token => {
    if (token.type === 'warning') {
      token.html = `<div class="warning">${token.text}</div>`;
    }
  });
  return tokens;
}

Преобразование токенов в HTML с кастомным рендерером

Для более тонкой настройки рендеринга используют Renderer:

import { Renderer, Parser } from 'marked';

const renderer = new Renderer();

renderer.heading = (text, level) => `<h${level} class="custom">${text}</h${level}>`;

const html = Parser.parse(tokens, { renderer });

Комбинация кастомного рендерера и модифицированного дерева токенов обеспечивает полный контроль над конечным HTML.

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

  • Для больших документов рекомендуется использовать рекурсивную обработку токенов вместо последовательного обхода, чтобы не нарушать вложенность.
  • Всегда проверять поля токена перед изменением (tokens, depth, text), чтобы избежать ошибок при рендеринге.
  • Кастомные токены полезны для внедрения специфичных блоков, таких как предупреждения, примечания, или блоки с динамическим контентом.
  • Сочетание трансформации дерева и кастомного рендерера позволяет создавать сложные шаблоны документации или блог-постов без изменения исходного Markdown.

Заключение структуры работы

Дерево токенов Marked является мощным инструментом для промежуточной обработки Markdown. Оно открывает возможности для глубоких трансформаций документа, позволяя:

  • изменять структуру контента;
  • внедрять кастомные элементы;
  • управлять рендерингом HTML с высокой точностью.

Эта архитектура делает Marked не только быстрым конвертером, но и гибкой платформой для построения сложных Markdown-приложений.