Модификация узлов

В библиотеках Remark и Rehype основная единица данных — это узел дерева (Node). Узел может представлять как структурный элемент документа (заголовок, параграф, список), так и текст или атрибут. Каждый узел имеет тип (type), а также дополнительные свойства, такие как children, value, position и data.

Пример базовой структуры узла:

{
  type: 'paragraph',
  children: [
    { type: 'text', value: 'Пример текста.' }
  ]
}

Ключевые свойства узлов:

  • type — тип узла (например, 'paragraph', 'heading', 'text').
  • children — массив дочерних узлов. Присутствует у узлов, которые могут содержать другие узлы.
  • value — текстовое содержимое, используется у листовых узлов (text, code).
  • data — объект для хранения дополнительных данных или метаинформации.
  • position — объект с информацией о позиции узла в исходном документе.

Навигация и обход дерева

Для изменения узлов часто требуется обход дерева. Библиотека unist-util-visit предоставляет удобные функции для рекурсивного обхода.

Пример обхода всех параграфов:

import { visit } from 'unist-util-visit';

visit(tree, 'paragraph', (node, index, parent) => {
  node.children.push({ type: 'text', value: ' Добавленный текст.' });
});

Особенности:

  • node — текущий узел.
  • index — индекс узла в массиве children родителя.
  • parent — родительский узел.
  • Позволяет изменять узлы на месте или заменять их новыми.

Изменение содержимого узлов

Текстовые узлы

Для модификации текста узла достаточно изменить свойство value:

visit(tree, 'text', (node) => {
  node.value = node.value.toUpperCase();
});

Узлы с дочерними элементами

Для структурных узлов с children возможны следующие операции:

  • Добавление нового узла:
node.children.push({ type: 'text', value: 'Новый текст' });
  • Удаление узла:
node.children.splice(index, 1);
  • Замена узла:
node.children[index] = { type: 'emphasis', children: [{ type: 'text', value: 'выделение' }] };

Использование плагинов для модификации

Remark и Rehype поддерживают создание плагинов, которые принимают дерево и могут его изменять. Плагин — это функция, возвращающая функцию обработчика:

function myRemarkPlugin() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      node.children.push({ type: 'text', value: ' ?' });
    });
  };
}

Особенности:

  • Плагины позволяют централизованно управлять модификациями.
  • Можно использовать несколько плагинов для последовательной трансформации.
  • Плагины могут быть синхронными и асинхронными, поддерживая промисы.

Добавление и удаление узлов с помощью unist-util-modify

Библиотеки семейства unist-util содержат вспомогательные функции для безопасного изменения дерева:

  • unist-util-remove — удаляет узлы по критерию.
  • unist-util-find — ищет первый узел определённого типа.
  • unist-util-visit-parents — обход с доступом ко всем родителям узла.

Пример удаления всех пустых параграфов:

import { remove } from 'unist-util-remove';

remove(tree, (node) => node.type === 'paragraph' && node.children.length === 0);

Работа с атрибутами узлов в Rehype

В HTML-дереве узлы часто содержат атрибуты (properties). Модификация атрибутов выполняется через объект properties:

visit(tree, 'element', (node) => {
  if (node.tagName === 'img') {
    node.properties.alt = 'Описание изображения';
    node.properties.loading = 'lazy';
  }
});
  • tagName определяет тип HTML-элемента.
  • properties содержит все атрибуты HTML, включая className, id, src и др.

Создание новых узлов

Новые узлы создаются вручную или с помощью вспомогательных функций:

const newNode = {
  type: 'paragraph',
  children: [
    { type: 'text', value: 'Новый параграф' }
  ]
};
parentNode.children.push(newNode);
  • Важно правильно устанавливать type и структуру children.
  • Для Rehype следует также учитывать tagName и properties.

Примеры комплексных модификаций

  1. Добавление префикса ко всем заголовкам:
visit(tree, 'heading', (node) => {
  node.children.unshift({ type: 'text', value: 'Раздел: ' });
});
  1. Удаление всех ссылок без href:
visit(tree, 'link', (node, index, parent) => {
  if (!node.url) parent.children.splice(index, 1);
});
  1. Замена всех <strong> на <em> в Rehype:
visit(tree, 'element', (node) => {
  if (node.tagName === 'strong') node.tagName = 'em';
});

Рекомендации по безопасной модификации

  • Избегать изменения массива children во время обхода visit без использования индекса.
  • Для сложных трансформаций использовать unist-util-visit-parents, чтобы учитывать контекст.
  • Проверять наличие children у узла перед обращением к ним.
  • Для Rehype корректно обрабатывать properties, чтобы не нарушить структуру HTML.

Эти методы позволяют гибко управлять деревом Markdown и HTML, создавать плагины, выполнять массовые трансформации, фильтровать узлы и динамически добавлять или удалять элементы. Мастерство работы с узлами становится ключевым навыком при разработке инструментов для обработки и генерации контента.