Библиотеки Remark и Rehype позволяют работать с синтаксическими деревьями Markdown и HTML соответственно, предоставляя гибкий механизм обработки элементов через плагины. Одной из ключевых задач является корректная работа с неизвестными или нестандартными элементами, которые могут появляться в контенте.
В терминологии Remark и Rehype узлы дерева делятся на:
text,
paragraph, heading, link и т.д.),
определенные стандартом Markdown или HTML.unknown nodes),
которые не соответствуют стандартным типам или встречаются в расширениях
синтаксиса.Для Remark узел может быть неизвестным, если он имеет поле
type, которое не зарегистрировано в
парсере, либо содержит нестандартные свойства. Rehype,
обрабатывающий HTML, воспринимает все теги, не входящие в стандартный
набор, как обычные элементы, но при трансформации могут возникнуть
ошибки при неправильной сериализации или при отсутствии поддержки
конкретного тега.
Пропуск узлов
Простейший подход — игнорировать неизвестные узлы при обходе дерева. В Remark это выглядит так:
import { visit } from 'unist-util-visit';
visit(tree, (node) => {
if (node.type === 'unknown') {
// Просто пропустить или удалить узел
return;
}
});
Этот метод гарантирует, что дерево останется валидным для дальнейшей обработки стандартными плагинами.
Конвертация в текст
Если неизвестный узел содержит содержимое, которое имеет смысл сохранить, его можно преобразовать в текст или другой известный узел:
visit(tree, 'unknown', (node, index, parent) => {
parent.children[index] = {
type: 'text',
value: node.value || ''
};
});
Такой подход предотвращает потерю данных и позволяет сериализовать Markdown без ошибок.
Расширение парсера
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, который может
быть обработан позже при трансформации.
Использование 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);
Этот метод обеспечивает сохранение неизвестных элементов, чтобы они могли быть обработаны или преобразованы на следующем этапе.
rehype-sanitize предотвращает потенциальные
XSS-уязвимости.Комбинированный подход для 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, неизвестные узлы преобразуются в валидный контент и не теряются при последующих трансформациях.