Пользовательские токенайзеры

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


Основы пользовательских токенайзеров

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

  • type — тип токена (например, 'heading', 'link', 'custom').
  • raw — исходный текст, соответствующий токену.
  • text — обработанный текст токена (например, без Markdown-разметки).
  • Дополнительные свойства, специфичные для конкретного токена (например, level для заголовка, href для ссылки).

Метод токенайзера получает один аргумент — строку Markdown для разбора, и возвращает объект токена или undefined, если данный токен не подходит.

const customTokenizer = {
  myTag(src) {
    const match = /^\[mytag\](.+?)\[\/mytag\]/.exec(src);
    if (match) {
      return {
        type: 'myTag',
        raw: match[0],
        text: match[1]
      };
    }
  }
};

Интеграция пользовательского токенайзера в Marked

Для подключения токенайзера к Marked используется свойство tokenizer в опциях парсера. Можно создавать полные копии стандартного токенайзера и расширять его собственными методами или добавлять новые методы к объекту.

const marked = require('marked');

const myTokenizer = {
  ...marked.defaults.tokenizer,
  myTag(src) {
    const match = /^\[mytag\](.+?)\[\/mytag\]/.exec(src);
    if (match) {
      return {
        type: 'myTag',
        raw: match[0],
        text: match[1]
      };
    }
  }
};

marked.use({ tokenizer: myTokenizer });

После этого Marked будет распознавать синтаксис [mytag]...[/mytag] как отдельный токен.


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

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

Пример: токен для пользовательского блока с вложенным Markdown:

const myBlockTokenizer = {
  myBlock(src) {
    const match = /^:::\s*myblock\s*\n([\s\S]+?)\n:::/m.exec(src);
    if (match) {
      // Разбор вложенного Markdown с помощью стандартного токенайзера
      const innerTokens = marked.lexer(match[1]);
      return {
        type: 'myBlock',
        raw: match[0],
        tokens: innerTokens
      };
    }
  }
};

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


Важные моменты при разработке токенайзеров

  1. Порядок методов Marked обходит методы токенайзера в порядке их объявления. Методы с более специфическим синтаксисом должны идти выше общих, иначе стандартные токены могут «поглотить» пользовательский синтаксис.

  2. Возврат undefined Если метод токенайзера не распознает текст, он должен вернуть undefined. Это позволяет парсеру переходить к следующему методу или стандартному токену.

  3. Работа с регулярными выражениями Использование ^ в начале регулярного выражения критично, так как Marked проверяет текст с начала строки. Для многострочных блоков рекомендуется флаг m.

  4. Поддержка вложенности При создании токенов, содержащих Markdown внутри, необходимо использовать marked.lexer или рекурсивные вызовы пользовательских токенайзеров, чтобы корректно создавать дерево токенов.


Примеры пользовательских токенов

Токен для цветного текста:

const colorTokenizer = {
  colorText(src) {
    const match = /\[color=(.+?)\](.+?)\[\/color\]/.exec(src);
    if (match) {
      return {
        type: 'colorText',
        raw: match[0],
        color: match[1],
        text: match[2]
      };
    }
  }
};

Токен для встроенных виджетов:

const widgetTokenizer = {
  widget(src) {
    const match = /\{\{widget:(.+?)\}\}/.exec(src);
    if (match) {
      return {
        type: 'widget',
        raw: match[0],
        id: match[1]
      };
    }
  }
};

Обработка пользовательских токенов в рендерере

После создания токенайзера необходимо обновить рендерер, чтобы Marked корректно отображал новые токены:

const renderer = {
  myTag(token) {
    return `<span class="my-tag">${token.text}</span>`;
  },
  myBlock(token) {
    return `<div class="my-block">${marked.parser(token.tokens)}</div>`;
  }
};

marked.use({ renderer });

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


Резюме по разработке токенайзеров

  • Пользовательский токенайзер расширяет стандартные возможности Marked.
  • Методы токенайзера возвращают объект токена с ключевыми полями type и raw.
  • Вложенный Markdown разбирается через marked.lexer.
  • Порядок методов важен для корректного распознавания специфического синтаксиса.
  • Необходимо обновить рендерер, чтобы токены отображались на странице.

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