Сайт Романа ПарпалакаБлог20230121

Мастер костылей, или сущности в DOMDocument

21 января 2023 года, 01:19

Я тут решил потратить некоторое время на собственные проекты. Сейчас перерабатываю движок для поиска Rose. На нем работает поиск на этом сайте, также его использует Илья Бирман в Эгее.

Для извлечения текста из html-исходников решил взять DOM API, как советует народ. Вообще, конечно, регулярки — наше всё :) Но HTML нормально распарсить регулярками нельзя. А мне надо распарсить, чтобы удалить из исходника стили, скрипты и комментарии (попробуйте на досуге написать регулярку, которая будет определять, находится ли разделитель в контексте комментария или нет). Кроме того, парсинг поможет точно разбить текст на абзацы, чтобы их дальше разбить на предложения. Поэтому я взял волю в кулак, посмотрел, как использовать DOM API, и рекурсивно обошел dom-дерево на PHP.

Как оказалось, DOM API не поддерживает HTML5. Это выливается в практическую проблему с непоследовательной обработкой сущностей HTML. Например, если в исходнике написать & ★, при получении текстового содержимого из dom-узла получим & ★. Видим, что первая сущность из HTML предыдущих версий распознается и раскодируется, а вторая из HTML5 — нет. Такие ошибки приведут к искажениям при выводе сниппетов.

Моя первая идея — прогнать текстовое содержимое через html_entity_decode($a, ENT_HTML5);. Действительно, что не доделал встроенный парсер, доделает эта функция. Но проблема в том, что раскодирование неидемпотентно. Если на вход подать ★, то после DOM API мы получим ★. И на этапе повторного раскодирования мы не будем знать, нужно ли раскодировать ★ еще раз, или нет.

Поиск проблемы в гугле разумного решения не выявил. В классе DOMDocument есть какое-то свойство substituteEntities, но в моем случае оно ни на что не влияло. Я уже было подумывал о возврате к регуляркам. Но всё-таки смог придумать обходное решение.

Для начала нужно все вхождения амперсанда заменить на его сущность:

$text = str_replace('&', '&', $text);

После этого DOM API раскодирует копии только одой сущности — этого самого амперсанда. При рекурсивном обходе в текстовом содержимом узлов dom-дерева тегов (то есть символов < и >) не будет, а все сущности будут закодированными. Если их нужно раскодировать, можно вызвать html_entity_decode($a, ENT_HTML5);.

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

    5 комментариев
Поделиться
Записи