Визуализация синтаксических деревьев

Remark и Rehype — это мощные инструменты для работы с текстом в формате Markdown и HTML, основанные на концепции абстрактного синтаксического дерева (AST, Abstract Syntax Tree). AST представляет собой иерархическую структуру, где каждый узел соответствует элементу документа: заголовку, параграфу, ссылке, изображению и так далее. Понимание структуры AST критически важно для визуализации и трансформации документов.

В Remark узлы AST имеют тип MDAST (Markdown AST), а в Rehype — HAST (HTML AST). Типичный узел включает следующие свойства:

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

Пример структуры MDAST для заголовка:

{
  "type": "heading",
  "depth": 2,
  "children": [
    {
      "type": "text",
      "value": "Пример заголовка"
    }
  ]
}

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


Использование unist-util-visit для обхода дерева

Для визуализации синтаксических деревьев необходимо обойти все узлы AST. Для этого применяют библиотеку unist-util-visit, которая обеспечивает рекурсивный проход по дереву:

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

visit(tree, (node) => {
  console.log(`${node.type}: ${node.value || ''}`);
});

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

  • visit(tree, type, callback) позволяет фильтровать узлы по типу.
  • Функция callback получает каждый узел и его индекс, что удобно для построения иерархической визуализации.
  • Можно модифицировать узлы во время обхода, что полезно для подсветки элементов в дереве.

Преобразование Markdown в AST с Remark

Для визуализации дерева Markdown сначала преобразуют текст в AST:

import { unified } from 'unified';
import remarkParse from 'remark-parse';

const markdown = `# Заголовок\n\nПараграф текста.`;

const tree = unified()
  .use(remarkParse)
  .parse(markdown);

console.log(JSON.stringify(tree, null, 2));

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

  • Метод parse возвращает MDAST, который можно визуализировать как дерево.
  • Узлы сохраняют полную структуру документа, включая вложенные параграфы и списки.
  • Для более сложной визуализации удобно использовать depth или рекурсивный обход.

Преобразование AST в HTML с Rehype

После работы с Markdown часто требуется преобразовать AST в HTML, где HAST представляет собой структуру аналогичную MDAST, но с HTML-тегами. Пример:

import rehype from 'rehype';
import rehypeStringify from 'rehype-stringify';
import remarkRehype from 'remark-rehype';

const html = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown);

console.log(String(html));

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

  • remarkRehype выполняет конвертацию MDAST → HAST.
  • rehypeStringify сериализует HAST в HTML.
  • В HAST узлы имеют тип element, text, comment, и свойства tagName и children.

Визуализация дерева с помощью графических библиотек

Для наглядного представления AST можно использовать графические библиотеки, такие как D3.js или Cytoscape. Основная задача — преобразовать AST в формат узлы + связи:

function buildGraph(node, parentId = null, nodes = [], edges = []) {
  const nodeId = nodes.length;
  nodes.push({ id: nodeId, label: node.type });
  if (parentId !== null) edges.push({ from: parentId, to: nodeId });
  
  if (node.children) {
    node.children.forEach(child => buildGraph(child, nodeId, nodes, edges));
  }
  
  return { nodes, edges };
}

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

  • Узлы AST превращаются в объекты с id и label.
  • Связи (ребра) создаются между родителем и дочерними узлами.
  • Полученные массивы можно передавать в D3.js для отрисовки интерактивного дерева.

Подсветка и аннотации узлов

Для визуального анализа полезно добавлять к узлам:

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

Пример добавления цвета узлу:

const nodeColor = (type) => {
  switch(type) {
    case 'heading': return 'blue';
    case 'paragraph': return 'green';
    case 'link': return 'orange';
    default: return 'gray';
  }
};

nodes.forEach(n => n.color = nodeColor(n.label));

Работа с большими документами

При визуализации больших AST:

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

Интеграция с редакторами и средами разработки

Remark и Rehype позволяют строить интерактивные редакторы Markdown/HTML, где AST используется для:

  • Реализации Live Preview.
  • Подсветки синтаксиса.
  • Автоматического исправления ошибок структуры документа.

Использование AST как промежуточного слоя обеспечивает гибкость: можно динамически изменять контент, визуализировать структуру и сразу видеть результат в браузере.