Парсинг HTML в HAST

Парсинг HTML в HAST (Hypertext Abstract Syntax Tree) является ключевым этапом при работе с библиотеками Rehype и Remark, особенно когда необходимо преобразовать HTML-контент в дерево для последующей обработки или анализа. HAST — это абстрактное представление HTML-документа в виде дерева, где каждый узел соответствует определённому элементу HTML, атрибуту или текстовому содержимому. В отличие от DOM, HAST ориентирован на работу в среде Node.js и удобен для трансформаций, плагинов и генерации контента.

Структура HAST

HAST строится на основе следующих типов узлов:

  • Root — корневой узел документа. Содержит массив children, включающий все элементы HTML-документа.

  • Element — узел, представляющий HTML-тег. Основные свойства:

    • tagName — имя тега (div, p, a и т.д.).
    • properties — объект с атрибутами тега.
    • children — массив дочерних узлов.
  • Text — узел текста. Содержит свойство value с текстовым содержимым.

  • Comment — узел комментария HTML. Содержит свойство value.

  • Doctype — узел определения типа документа.

Пример структуры узла Element в HAST:

{
  type: 'element',
  tagName: 'p',
  properties: { className: ['intro'] },
  children: [
    { type: 'text', value: 'Пример текста внутри параграфа.' }
  ]
}

Использование Rehype для парсинга HTML

Rehype предоставляет модуль rehype-parse, который позволяет преобразовать строку HTML в HAST. Пример базового парсинга:

import { unified } from 'unified';
import rehypeParse from 'rehype-parse';

const html = '<div class="container"><p>Текст абзаца</p></div>';

const tree = unified()
  .use(rehypeParse, { fragment: true })
  .parse(html);

console.log(tree);

Параметр fragment: true указывает парсеру, что переданная строка может быть фрагментом HTML, а не полноценным документом с <!DOCTYPE> и html тегами.

Доступ и манипуляция узлами HAST

HAST-дерево можно обходить и изменять с помощью таких инструментов, как unist-util-visit или напрямую через рекурсивные функции. Например, изменение текста всех параграфов:

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

visit(tree, 'element', (node) => {
  if (node.tagName === 'p') {
    node.children.push({ type: 'text', value: ' Дополненный текст.' });
  }
});

Ключевые моменты при работе с HAST:

  • Узлы типа element могут иметь вложенные дочерние элементы, что позволяет строить сложные иерархии.
  • Свойство properties всегда объект, где ключи соответствуют атрибутам HTML.
  • Текстовые узлы не имеют дочерних элементов и всегда содержат value.

Преобразование HAST обратно в HTML

Для генерации HTML из HAST используется rehype-stringify:

import rehypeStringify from 'rehype-stringify';

const htmlOutput = unified()
  .use(rehypeStringify)
  .stringify(tree);

console.log(htmlOutput);
// <div class="container"><p>Текст абзаца Дополненный текст.</p></div>

Таким образом, процесс выглядит как «парсинг → трансформация → генерация», что позволяет гибко обрабатывать контент на уровне синтаксического дерева.

Интеграция с Remark

Remark используется для работы с Markdown, но с помощью remark-rehype можно преобразовывать Markdown в HAST:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

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

const htmlFromMarkdown = unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeStringify)
  .processSync(markdown)
  .toString();

console.log(htmlFromMarkdown);
// <h1>Заголовок</h1><p>Параграф текста.</p>

Этот подход позволяет объединять возможности Markdown и HTML, создавая единое дерево HAST для дальнейшей обработки, добавления плагинов или оптимизации HTML.

Рекомендации по работе с HAST

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

HAST предоставляет мощный и гибкий инструмент для работы с HTML на уровне синтаксического дерева, что делает его основой современных систем генерации и трансформации контента в экосистеме JavaScript.