В работе с Markdown и HTML через Remark и Rehype часто возникает необходимость модифицировать дерево синтаксического разбора (AST), удаляя определённые узлы. Операции удаления узлов требуют понимания структуры AST и методов обхода, предоставляемых этими библиотеками.
Remark работает с деревом Markdown, где каждый узел
имеет тип type и может содержать массив
children. Типичные узлы:
root — корневой узел документа.paragraph — абзац.text — текстовое содержимое.heading — заголовок, с уровнем depth.Rehype работает с HTML-деревом. Узлы имеют типы:
root — корень документа.element — HTML-элемент, с полями tagName,
properties, children.text — текстовый узел.Общее для обеих библиотек: для обхода дерева используются функции
unist-util-visit или unist-util-visit-parents,
а для изменения дерева — unist-util-modify-children и
прямое изменение массива children.
Удаление узлов может быть выполнено несколькими способами:
children
Наиболее простой способ. Для каждого узла с дочерними элементами:import { visit } from 'unist-util-visit';
visit(tree, 'paragraph', (node, index, parent) => {
if (node.children.some(child => child.type === 'text' && child.value.includes('удалить'))) {
parent.children.splice(index, 1);
}
});
Объяснение:
visit(tree, 'paragraph', callback) обходит все узлы
типа paragraph.index — позиция текущего узла в
parent.children.splice(index, 1) удаляет узел из дерева.unist-util-visit-parents для
удаления с доступом к родителюimport { visitParents } from 'unist-util-visit-parents';
visitParents(tree, 'heading', (node, ancestors) => {
if (node.depth === 3) {
const parent = ancestors[ancestors.length - 1];
const index = parent.children.indexOf(node);
if (index !== -1) parent.children.splice(index, 1);
}
});
Пояснение:
visitParents предоставляет полный путь от корня до
текущего узла.children
рекурсивноДля удаления узлов по сложным условиям удобно написать рекурсивную функцию:
function removeNodes(node, predicate) {
if (!node.children) return;
node.children = node.children.filter(child => !predicate(child));
node.children.forEach(child => removeNodes(child, predicate));
}
removeNodes(tree, node => node.type === 'text' && node.value.match(/удалить/));
Преимущества:
В HTML-документах удаление работает аналогично:
import { visit } from 'unist-util-visit';
visit(tree, 'element', (node, index, parent) => {
if (node.tagName === 'script' || node.tagName === 'style') {
parent.children.splice(index, 1);
}
});
Особенности:
tagName указывает на тип HTML-элемента.id, наличие
дочерних текстов.parent перед удалением узла.
Корневой узел root не имеет родителя.visit с
фильтром по типу, а не обходить все узлы без разбора.h2 и h3:visit(tree, 'heading', (node, index, parent) => {
if (node.depth === 2 || node.depth === 3) {
parent.children.splice(index, 1);
}
});
visit(tree, 'paragraph', (node, index, parent) => {
if (!node.children || node.children.length === 0) {
parent.children.splice(index, 1);
}
});
visit(tree, 'element', (node, index, parent) => {
if (node.properties && node.properties.className?.includes('remove-me')) {
parent.children.splice(index, 1);
}
});
visit или
visitParents).Удаление узлов из AST Remark и Rehype является мощным инструментом для трансформации Markdown и HTML. Грамотное использование обхода и фильтрации позволяет модифицировать структуру документа без нарушения целостности дерева.