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

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


Основные поля токена

Каждый токен в Markdown-it имеет набор стандартных полей. Рассмотрим их детально:

1. type

Поле type — строка, обозначающая тип токена. Примеры значений:

  • paragraph_open — открытие параграфа
  • inline — текст внутри блока
  • heading_open — открытие заголовка
  • bullet_list_open — открытие маркированного списка

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


2. tag

Поле tag соответствует HTML-тегу, в который будет преобразован токен. Примеры:

  • <p> для параграфа
  • <h1><h6> для заголовков
  • <ul> для списков

Важно понимать, что tag не всегда совпадает с type. Например, токен inline обычно не имеет собственного HTML-тега, а содержит дочерние токены.


3. attrs

attrs — массив атрибутов для HTML-тега. Каждый элемент массива представлен в виде [имя, значение]. Примеры:

token.attrs = [['class', 'highlight'], ['id', 'section-1']];

Это позволяет добавлять CSS-классы, идентификаторы и другие атрибуты к тегам при рендеринге.


4. map

Поле map — массив из двух чисел [startLine, endLine], определяющий диапазон строк исходного Markdown, к которому относится токен.

  • startLine — первая строка блока
  • endLine — последняя строка блока

Это особенно полезно для подсветки синтаксиса или привязки ошибок к исходным строкам Markdown.


5. nesting

nesting показывает, открывает токен блок (1), закрывает блок (-1) или является одиночным (0).

Примеры:

  • paragraph_opennesting: 1
  • paragraph_closenesting: -1
  • inlinenesting: 0

Понимание значения nesting критично при обходе токенов для построения DOM-подобной структуры документа.


6. level

level — числовое значение, отражающее глубину вложенности токена в дереве документа. Например:

  • Корневой параграф → level: 0
  • Содержимое списочного элемента → level: 2

Полезно при создании пользовательских рендереров и при фильтрации токенов по уровню вложенности.


7. children

Поле children используется у токена типа inline и содержит массив дочерних токенов. Каждый дочерний токен имеет те же поля, что и родитель.

Пример структуры:

{
  type: 'inline',
  tag: '',
  content: 'Пример текста',
  children: [
    { type: 'text', tag: '', content: 'Пример', level: 1, nesting: 0 },
    { type: 'strong_open', tag: 'strong', nesting: 1, level: 1 },
    { type: 'text', tag: '', content: 'текста', level: 2, nesting: 0 },
    { type: 'strong_close', tag: 'strong', nesting: -1, level: 1 }
  ]
}

8. content

Поле content хранит текст внутри токена. Для блочных токенов (например, paragraph) это обычно пустая строка, а для inline или text токенов — фактический текст.

Пример:

{
  type: 'text',
  tag: '',
  content: 'Привет, мир!',
  level: 0,
  nesting: 0
}

9. markup

Поле markup используется для хранения символов Markdown, которые формируют блок. Например, для заголовка:

{
  type: 'heading_open',
  tag: 'h2',
  markup: '##',
  nesting: 1
}

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


10. info

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

{
  type: 'fence',
  tag: 'code',
  info: 'javascript',
  content: 'console.log("Hello");'
}

Особенности обхода токенов

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

  • Для блоков с открытием и закрытием нужно отслеживать nesting, чтобы определить диапазон дочерних токенов.
  • Дочерние токены в children уже отсортированы по порядку и соответствуют inline-содержимому.
  • Поля level и map позволяют строить дерево документа и связывать токены с исходными строками Markdown.

Практическое использование

  1. Модификация атрибутов Изменение классов, добавление идентификаторов или стилей:
md.core.ruler.push('add_class', function (state) {
  state.tokens.forEach(token => {
    if (token.type === 'paragraph_open') {
      token.attrs = token.attrs || [];
      token.attrs.push(['class', 'custom-paragraph']);
    }
  });
});
  1. Обход текста для анализа Извлечение всех заголовков документа:
const headings = [];
state.tokens.forEach(token => {
  if (token.type === 'heading_open') {
    const inline = state.tokens[state.tokens.indexOf(token) + 1];
    headings.push(inline.content);
  }
});
  1. Создание собственного рендерера Используя токены, можно полностью контролировать HTML-вывод, изменяя tag, attrs или content.

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