Обработка неизвестных элементов

Библиотеки Remark и Rehype позволяют работать с синтаксическими деревьями Markdown и HTML соответственно, предоставляя гибкий механизм обработки элементов через плагины. Одной из ключевых задач является корректная работа с неизвестными или нестандартными элементами, которые могут появляться в контенте.


Понимание unknown nodes

В терминологии Remark и Rehype узлы дерева делятся на:

  • Встроенные узлы (text, paragraph, heading, link и т.д.), определенные стандартом Markdown или HTML.
  • Неизвестные узлы (unknown nodes), которые не соответствуют стандартным типам или встречаются в расширениях синтаксиса.

Для Remark узел может быть неизвестным, если он имеет поле type, которое не зарегистрировано в парсере, либо содержит нестандартные свойства. Rehype, обрабатывающий HTML, воспринимает все теги, не входящие в стандартный набор, как обычные элементы, но при трансформации могут возникнуть ошибки при неправильной сериализации или при отсутствии поддержки конкретного тега.


Методы обработки неизвестных узлов

  1. Пропуск узлов

    Простейший подход — игнорировать неизвестные узлы при обходе дерева. В Remark это выглядит так:

    import { visit } from 'unist-util-visit';
    
    visit(tree, (node) => {
      if (node.type === 'unknown') {
        // Просто пропустить или удалить узел
        return;
      }
    });

    Этот метод гарантирует, что дерево останется валидным для дальнейшей обработки стандартными плагинами.

  2. Конвертация в текст

    Если неизвестный узел содержит содержимое, которое имеет смысл сохранить, его можно преобразовать в текст или другой известный узел:

    visit(tree, 'unknown', (node, index, parent) => {
      parent.children[index] = {
        type: 'text',
        value: node.value || ''
      };
    });

    Такой подход предотвращает потерю данных и позволяет сериализовать Markdown без ошибок.

  3. Расширение парсера

    Remark и Rehype поддерживают подключение кастомных парсеров и обработчиков, которые распознают новые типы узлов. В Remark это достигается через плагины:

    import { unified } from 'unified';
    import remarkParse from 'remark-parse';
    
    function customNodePlugin() {
      return (tree) => {
        visit(tree, 'text', (node) => {
          if (node.value.includes('[[custom]]')) {
            node.type = 'customNode';
            node.data = { original: node.value };
          }
        });
      };
    }
    
    const processor = unified()
      .use(remarkParse)
      .use(customNodePlugin);

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

  4. Использование hast-util-raw и rehype-raw

    В Rehype для обработки неизвестных HTML-тегов важно корректно интегрировать “сырые” HTML-блоки. Плагин rehype-raw позволяет встроить их в дерево HAST без потери структуры:

    import rehypeParse from 'rehype-parse';
    import rehypeRaw from 'rehype-raw';
    import { unified } from 'unified';
    
    const processor = unified()
      .use(rehypeParse, { fragment: true })
      .use(rehypeRaw);

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


Важные особенности обработки

  • Сохранение структуры дерева — даже если узел неизвестен, его следует интегрировать в дерево так, чтобы не нарушить последовательность элементов.
  • Безопасность при сериализации — неизвестные HTML-теги могут содержать опасный контент. Использование плагинов типа rehype-sanitize предотвращает потенциальные XSS-уязвимости.
  • Поддержка вложенных узлов — иногда неизвестные узлы содержат дочерние элементы, которые необходимо рекурсивно обработать, чтобы сохранить их содержимое.

Практические рекомендации

  1. Определять тип узла заранее, если контент расширяется нестандартными синтаксисами.
  2. Использовать промежуточные преобразования в текст, когда необходимо сохранить данные без полной поддержки узла.
  3. Создавать кастомные плагины для регулярного и предсказуемого обращения с нестандартными элементами.
  4. Проверять безопасность при интеграции неизвестного HTML в клиентские приложения.

Пример комплексной обработки

Комбинированный подход для Markdown с встроенными неизвестными HTML-тегами:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';
import { visit } from 'unist-util-visit';

function handleUnknownNodes() {
  return (tree) => {
    visit(tree, (node) => {
      if (node.type === 'unknown') {
        node.type = 'paragraph';
        node.children = [{ type: 'text', value: node.value || '' }];
      }
    });
  };
}

const processor = unified()
  .use(remarkParse)
  .use(handleUnknownNodes)
  .use(remarkRehype, { allowDangerousHtml: true })
  .use(rehypeRaw)
  .use(rehypeSanitize);

В результате дерево безопасно сериализуется в HTML, неизвестные узлы преобразуются в валидный контент и не теряются при последующих трансформациях.