Атрибуты токенов

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


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

Каждый токен в Markdown-it имеет базовые поля:

  • type — тип токена, например 'paragraph_open', 'heading_close', 'inline'.
  • tag — HTML-тег, который будет сгенерирован ('p', 'h1', 'strong' и т.д.).
  • attrs — массив атрибутов, представленных в виде массива [ключ, значение].
  • content — текст внутри токена (актуально для inline-токенов).
  • children — массив дочерних токенов (для inline-структур).
  • level — глубина вложенности токена.
  • markup — оригинальный Markdown-разметочный символ, например '#' для заголовков.
  • info — дополнительная информация (например, язык в блоках кода).

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

{
  type: 'link_open',
  tag: 'a',
  attrs: [['href', 'https://example.com'], ['title', 'Пример ссылки']],
  content: '',
  children: [],
  level: 0,
  markup: '[]()'
}

В этом примере токен link_open содержит два атрибута: href и title.


Добавление атрибутов

Добавление атрибутов осуществляется путем изменения массива attrs. Если массив пуст, его необходимо инициализировать:

token.attrs = token.attrs || [];
token.attrs.push(['target', '_blank']);

Это добавляет атрибут target="_blank" к HTML-элементу, создаваемому токеном. Если атрибут с таким именем уже существует, следует обновлять значение вместо добавления дубликата.


Изменение атрибутов

Для безопасного изменения атрибутов применяется поиск по ключу:

function setAttr(token, name, value) {
  token.attrs = token.attrs || [];
  const index = token.attrs.findIndex(attr => attr[0] === name);
  if (index !== -1) {
    token.attrs[index][1] = value;
  } else {
    token.attrs.push([name, value]);
  }
}

// Пример:
setAttr(token, 'class', 'highlight');

Такая функция гарантирует уникальность каждого атрибута.


Удаление атрибутов

Удаление выполняется через фильтрацию массива:

function removeAttr(token, name) {
  token.attrs = token.attrs || [];
  token.attrs = token.attrs.filter(attr => attr[0] !== name);
}

// Пример:
removeAttr(token, 'title');

После вызова данного метода указанный атрибут будет полностью удалён из токена.


Работа с плагинами и атрибутами

Большинство плагинов Markdown-it используют токены для модификации HTML. Примеры распространённых операций:

  1. Добавление классов к заголовкам
md.core.ruler.push('add_heading_class', state => {
  state.tokens.forEach(token => {
    if (token.type === 'heading_open') {
      setAttr(token, 'class', `heading-level-${token.tag.slice(1)}`);
    }
  });
});
  1. Добавление атрибутов к ссылкам
md.core.ruler.push('external_links', state => {
  state.tokens.forEach(token => {
    if (token.type === 'link_open') {
      setAttr(token, 'rel', 'noopener noreferrer');
      setAttr(token, 'target', '_blank');
    }
  });
});
  1. Применение атрибутов к изображениям
md.core.ruler.push('image_class', state => {
  state.tokens.forEach(token => {
    if (token.type === 'image') {
      setAttr(token, 'class', 'responsive-image');
    }
  });
});

Доступ к атрибутам в рендерерах

При кастомизации рендеринга HTML можно напрямую использовать поле attrs. Пример функции рендера для ссылок:

md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
  const token = tokens[idx];
  const attrs = token.attrs ? token.attrs.map(a => `${a[0]}="${a[1]}"`).join(' ') : '';
  return `<a ${attrs}>`;
};

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


Особенности работы с inline-токенами

Inline-токены часто содержат вложенные элементы (children), у которых также могут быть атрибуты. Для массового изменения атрибутов внутри inline-токена необходимо рекурсивно обходить children:

function setClassRecursively(tokens, className) {
  tokens.forEach(token => {
    setAttr(token, 'class', className);
    if (token.children) {
      setClassRecursively(token.children, className);
    }
  });
}

Это особенно важно при работе с форматированным текстом (strong, em, code), где атрибуты могут добавляться на всех уровнях вложенности.


Рекомендации по использованию

  • Инициализировать attrs перед добавлением: многие токены по умолчанию имеют null.
  • Использовать уникальные ключи атрибутов: избегать дублирования, чтобы HTML был корректным.
  • Рекурсивно обходить inline-токены, если необходимо добавить атрибуты ко вложенным элементам.
  • Применять плагиновые правила на стадии core.ruler для глобальной модификации токенов до рендеринга.

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