Nested parsing

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


Основные принципы вложенного разбора

1. Токенизация и поток токенов

Marked работает в два этапа: лексический анализ (tokenization) и парсинг (parsing). При обработке Markdown библиотека сначала разбивает текст на токены — объекты, представляющие отдельные элементы Markdown (заголовки, списки, цитаты, ссылки, текст).

Вложенный разбор реализуется через рекурсивное прохождение токенов: если токен содержит другой Markdown-контент, он передаётся на повторную обработку. Например, внутри цитаты может быть список, а внутри списка — параграф с выделением.

const marked = require('marked');

const markdown = `
> Список внутри цитаты:
> - Пункт 1
> - Пункт 2 с **жирным текстом**
`;

console.log(marked.parse(markdown));

В этом примере токен цитаты содержит токены списка, которые в свою очередь содержат токены текста с выделением. Marked автоматически обрабатывает все уровни вложенности.


Настройка вложенного парсинга через Renderer

Для контроля рендеринга вложенных структур можно использовать собственный объект Renderer. Каждый метод Renderer получает на вход HTML или токенизированный объект и возвращает строку HTML.

Пример кастомного рендеринга списков внутри цитат:

const renderer = {
  blockquote(quote) {
    return `<blockquote class="custom-quote">${quote}</blockquote>`;
  },
  list(body, ordered) {
    const type = ordered ? 'ol' : 'ul';
    return `<${type} class="custom-list">${body}</${type}>`;
  }
};

const options = {
  renderer,
  breaks: true
};

console.log(marked.parse(markdown, options));

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


Обработка сложных вложенных элементов

  1. Списки внутри списков Marked различает ordered (нумерованные) и unordered (маркированные) списки и поддерживает произвольную вложенность:
- Пункт 1
  - Подпункт 1.1
  - Подпункт 1.2
- Пункт 2

Результат:

<ul>
  <li>Пункт 1
    <ul>
      <li>Подпункт 1.1</li>
      <li>Подпункт 1.2</li>
    </ul>
  </li>
  <li>Пункт 2</li>
</ul>
  1. Цитаты с параграфами и списками
> Это цитата
> 
> - Список 1
> - Список 2

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

  1. Код внутри списков и цитат
> - Пункт с кодом
> 
>   ```js
>   console.log("Hello, Marked!");
>   ```

Внутри списка Marked создаёт отдельный блок <pre><code> и правильно сохраняет форматирование кода.


Использование токенов напрямую

Для продвинутого контроля можно использовать API токенизации:

const tokens = marked.lexer(markdown);

console.log(tokens);

Токены имеют свойства:

  • type — тип элемента (paragraph, list, list_item, blockquote и т.д.)
  • text — содержимое текста
  • tokens — массив вложенных токенов

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


Советы по работе с вложенной разметкой

  • Избегать чрезмерной рекурсии: глубоко вложенные структуры могут вызвать рост потребления памяти.
  • Использовать кастомный Renderer для специальных случаев, когда стандартный HTML вывод не подходит.
  • Всегда проверять токены через marked.lexer, чтобы понимать, как библиотека интерпретирует сложные структуры.
  • Сохранять порядок вложенности, особенно при комбинировании списков и цитат, чтобы HTML соответствовал семантике Markdown.

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