Навигация по дереву

Библиотеки Remark и Rehype работают с деревьями абстрактного синтаксиса (AST — Abstract Syntax Tree), которые представляют структуру Markdown и HTML соответственно. Каждое дерево состоит из узлов, где каждый узел описывает элемент документа: текст, параграф, заголовок, ссылку или любой другой компонент. Узлы имеют тип (type), могут содержать свойства (data, props) и дочерние элементы (children).

Основные типы узлов:

  • root — корневой узел документа, содержит все остальные элементы.
  • element / html / paragraph / heading / text — базовые строительные блоки Markdown или HTML.
  • list / listItem / link / image / code — структурные узлы для списков, ссылок, изображений и кода.

Дерево строится иерархически: узлы могут содержать другие узлы в children. Понимание структуры и доступ к нужным узлам требует грамотной навигации по дереву.


Обход дерева

Навигация по AST может выполняться рекурсивно или с использованием утилитных функций. В Remark и Rehype есть два основных подхода:

  1. Рекурсивный обход

    Простейший способ — рекурсивно проходить children каждого узла. Это удобно для поиска всех узлов определённого типа или модификации текста.

function walk(node, callback) {
  callback(node);
  if (node.children) {
    node.children.forEach(child => walk(child, callback));
  }
}
  1. Использование библиотек unist-util-visit и unist-util-visit-parents

    • unist-util-visit позволяет посещать каждый узел определённого типа и применять функцию.
    • unist-util-visit-parents дополнительно предоставляет путь от корня до текущего узла, что важно для понимания контекста.

Пример поиска всех заголовков:

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

visit(tree, 'heading', node => {
  console.log(node.depth, node.children[0].value);
});

Типы узлов и свойства

Каждый узел имеет структуру:

{
  type: 'тип_узла',
  children: [...],
  value: 'текстовое содержимое',
  data: { ... },
  position: { start: {...}, end: {...} }
}
  • type — определяет роль узла.
  • children — массив дочерних узлов. Если узел текстовый (text), children отсутствует.
  • value — хранит текстовое содержимое для узлов типа text или code.
  • data — объект для хранения дополнительной информации (метаданные).
  • position — объект с координатами начала и конца узла в исходном документе.

Манипуляции с деревом

Добавление узлов: узел можно добавить в children родительского узла.

const newNode = { type: 'paragraph', children: [{ type: 'text', value: 'Новый параграф' }] };
root.children.push(newNode);

Удаление узлов: узел удаляется из children родителя с использованием методов массива (filter, splice).

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

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

Контекст и путь в дереве

Часто требуется не только обработать узел, но и понимать его окружение. unist-util-visit-parents возвращает массив родителей:

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

visitParents(tree, 'text', (node, parents) => {
  const parentType = parents[parents.length - 1].type;
  console.log(`Текст "${node.value}" находится внутри узла типа ${parentType}`);
});

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


Фильтрация и поиск

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

function findNodesByType(node, type) {
  let result = [];
  if (node.type === type) result.push(node);
  if (node.children) {
    node.children.forEach(child => {
      result = result.concat(findNodesByType(child, type));
    });
  }
  return result;
}

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


Интеграция с плагинами

Remark и Rehype используют плагины для трансформаций. Плагины получают AST и навигируют по нему с помощью методов выше. Понимание дерева и его структуры критично для:

  • Применения изменений только к нужным узлам.
  • Сборки статистики документа (количество заголовков, ссылок, изображений).
  • Создания кастомных рендеров и расширений Markdown/HTML.

Визуализация дерева

Для анализа и отладки можно использовать unist-util-inspect или простые функции печати:

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

console.log(inspect(tree, { colors: true }));

Это позволяет увидеть иерархию, типы узлов и их содержимое, облегчая навигацию и манипуляции с AST.


Ключевые моменты

  • Каждый узел имеет type, children и/или value.
  • Обход дерева можно делать рекурсивно или с помощью visit.
  • visitParents позволяет учитывать контекст и родительские узлы.
  • Для фильтрации узлов часто создаются рекурсивные функции.
  • AST можно модифицировать: добавлять, удалять, изменять узлы.
  • Понимание структуры дерева критично для плагинов, трансформаций и анализа документов.