unist-util-select: CSS-подобные селекторы

unist-util-select — это библиотека для работы с деревьями синтаксиса, совместимая с экосистемой unist, включая remark и rehype. Она позволяет выбирать узлы в дереве с помощью синтаксиса, напоминающего CSS-селекторы. Этот подход значительно упрощает навигацию по дереву Markdown или HTML, делая операции фильтрации и извлечения данных более декларативными.

Основные функции

  • select — возвращает первый найденный узел, соответствующий селектору.
  • selectAll — возвращает массив всех узлов, удовлетворяющих селектору.
  • matches — проверяет, удовлетворяет ли узел заданному селектору.

Синтаксис селекторов

Селекторы в unist-util-select используют CSS-подобную нотацию, включая:

  • Тип узла: указывается именем типа, например paragraph, heading, link.

    selectAll(tree, 'paragraph'); // выбирает все параграфы
  • ID и классы: поддерживается синтаксис #id и .class, если узлы имеют соответствующие свойства.

    sel ect(tree, 'heading#main'); // заголовок с id "main"
    selectAll(tree, 'list.item.special'); // элементы списка с классом "special"
  • Атрибуты: можно использовать селекторы вида [name=value].

    selectAll(tree, 'link[href="https://example.com"]');
  • Комбинации и иерархия: селекторы могут использовать пробелы для обозначения потомков и > для прямых потомков.

    selectAll(tree, 'list > listItem'); // прямые дочерние элементы списка
    selectAll(tree, 'heading paragraph'); // параграфы внутри заголовков

Применение в remark

При работе с Markdown дерево разбивается на узлы paragraph, heading, link, image и другие. unist-util-select позволяет извлекать нужные части документа без ручной рекурсии по дереву.

Пример: извлечение всех ссылок из Markdown:

import { unified } fr om 'unified';
import remarkParse from 'remark-parse';
import { selectAll } from 'unist-util-select';

const markdown = `
# Заголовок

Текст с [ссылкой](https://example.com)
`;

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

const links = selectAll(tree, 'link');
console.log(links.map(node => node.url));
// ['https://example.com']

Применение в rehype

Для HTML-деревьев rehype узлы содержат типы element и text. Атрибуты HTML (id, class, href) доступны в объекте properties. unist-util-select позволяет делать выборки по этим атрибутам, аналогично CSS.

Пример: извлечение всех изображений с определённым классом:

import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import { selectAll } from 'unist-util-select';

const html = `
<div>
  <img src="image1.jpg" class="thumbnail">
  <img src="image2.jpg">
</div>
`;

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

const thumbnails = selectAll(tree, 'img.thumbnail');
console.log(thumbnails.map(node => node.properties.src));
// ['image1.jpg']

Составные селекторы и фильтры

unist-util-select поддерживает сложные выражения:

  • Объединение селекторов: , позволяет выбрать узлы, удовлетворяющие любому из селекторов.

    selectAll(tree, 'paragraph, heading'); // параграфы или заголовки
  • Комбинаторы: + выбирает следующий соседний узел, ~ — любой следующий сосед.

    selectAll(tree, 'heading + paragraph'); // параграф сразу после заголовка
  • Псевдоклассы: поддерживаются :first-child, :last-child, :nth-child(n).

    select(tree, 'listItem:first-child'); // первый элемент списка

Работа с функциями фильтрации

В дополнение к строковым селекторам можно использовать функции фильтрации:

selectAll(tree, node => node.type === 'link' && node.url.includes('example'));

Этот метод даёт максимальную гибкость при выборе узлов по сложным условиям, когда стандартный CSS-подобный синтаксис недостаточен.

Оптимизация и производительность

  • Выборка по типу узла (paragraph, heading) быстрее, чем по атрибутам, поскольку не требуется проверка объекта properties.
  • Для больших деревьев следует использовать selectAll с конкретными селекторами, чтобы минимизировать количество обходов.
  • Комбинированные селекторы (list > listItem) эффективны при правильной структуре дерева, так как обход ограничивается поддеревьями.

Интеграция с плагинами

unist-util-select легко интегрируется с плагинами remark и rehype. Это позволяет:

  • Подсветка синтаксиса и обработка ссылок.
  • Автоматическое создание оглавления из заголовков.
  • Фильтрация и модификация элементов перед генерацией HTML или Markdown.

Примечания по использованию

  • Узлы должны соответствовать стандарту unist: type, children, value или properties.
  • Для HTML через rehype рекомендуется всегда использовать { fragment: true } при разборе кусков кода, чтобы избежать ошибок парсера.
  • Селекторы чувствительны к точности имени типа и свойств; небольшая ошибка в class или id приведёт к пустой выборке.

unist-util-select объединяет удобство CSS-селекторов с мощью обработки деревьев unist, делая навигацию, фильтрацию и трансформацию данных в Markdown и HTML интуитивной и эффективной.