Хочу поделиться небольшим расширением для Nokogiri — библиотекой Hairaito для подстветки текста в html-документе.
Тривиальный пример использования:
Под катом описание изначальной проблемы, поиск её решения и ссылки по теме.
На проекте возникла необходимость реализации поиска по тексту html-документа с настройками морфологии. Исторически сложилось, что для этих целей используется Sphinx. Для связки с ruby существует гем Thinking Sphinx. Он поддерживает функцию подстветки результатов поиска
Судя по документации, для подстветки в режиме полного текста используется параметр
Поясню на примере. Есть фрагмент документа:
Если мы хотим искать по фразе целиком «Мама мыла раму» и используем
Если же мы используем
Просмотрев документацию Sphinx и ThinkingSphinx, я не нашел решения этой проблемы «из коробки». Вопрос на официальном форуме поискового движка так же не дал результатов.
Далее возникла следующая идея. Можно использовать
У этого подхода сразу есть очевидный минус — это медленнее нежели бы это сделал сам Sphinx (по наблюдениям, сравнительных тестов не проводил). Однако других рабочих вариантов за короткий срок я не увидел.
Оставалось решить как именно подсвечивать найденные сниппеты. Рассмотрел варианты сделать это на фронте — нагуглил библиотеки jquery-highlight и rangy. Первая не подошла по функционалу, т.к. не умеет подсветку слов «разделенных» inline-тегами. Вторая довольно успешно справилась непосредственно с «качеством» подсветки (метод
Итогом стало решение делать подсветку на бэке. Беглый поиск не дал результатов — готовых решений найдено не было. Поэтому я решил написать собственное расширение для Nokogiri. Гем назвал «Hairaito», что в переводе с японского на английский дает «highlight». На русский переводится как «основной момент». Искренне надеюсь, что знатоки японского не вспомнят какой-нибудь неблагозвучный вариант перевода этого слова.
Hairaito умеет подсвечивать текст в html/xml, есть возможность настройки тегов и css-классов для подстветки, нумерации результатов.
Код проекта и примеры использования доступны на github.
Также гем опубликован на rubygems.
Буду рад услышать комментарии. Возможно, кто-нибудь знает более элегантное решение начальной проблемы.
Ссылки по теме:
Sphinx Search
Thinking Sphinx
Nokogiri
jquery-highlight
rangy
Тривиальный пример использования:
doc = Nokogiri::XML('<body>abc def ghi</body>')
doc.highlight(['def'])
doc.to_html # => '<body>abc <span class="snippet-part snippet-start" data-snippet-id="0">def</span> ghi</body>'
Под катом описание изначальной проблемы, поиск её решения и ссылки по теме.
Предыстория
На проекте возникла необходимость реализации поиска по тексту html-документа с настройками морфологии. Исторически сложилось, что для этих целей используется Sphinx. Для связки с ruby существует гем Thinking Sphinx. Он поддерживает функцию подстветки результатов поиска
BuildExcerpts
через объект ThinkingSphinx::Excerpter
.Судя по документации, для подстветки в режиме полного текста используется параметр
html_strip_mode: :retain
, он сохраняет исходную html структуру. При этом, как оказалось, теряется функционал поиска с параметром html_strip: true
из преднастроенного индекса. Поясню на примере. Есть фрагмент документа:
<div>
Мама <i>мыла</i> раму
</div>
Если мы хотим искать по фразе целиком «Мама мыла раму» и используем
html_strip_mode: :index
для подстветки в тексте, то Sphinx удаляет inline-тег i
, оставляя толькое его содержимое (документация), и подсвечивает весь сниппет. При этом теряется оригинальное выделение слова «мыла» курсивом. Если же мы используем
html_strip_mode: :retain
, то Sphinx ведет себя иначе — не удаляет inline-теги при поиске. При этом он уже не находит фразу целиком. Т.е. имеет место ложно отрицательный результат поиска. Оба варианта посчитали неподходящими.Поиск решения
Просмотрев документацию Sphinx и ThinkingSphinx, я не нашел решения этой проблемы «из коробки». Вопрос на официальном форуме поискового движка так же не дал результатов.
Далее возникла следующая идея. Можно использовать
ThinkingSphinx::Excerpter
с параметром html_strip_mode: :index
для получения полного списка найденных сниппетов с учетом всех настроек морфологии в индексе. После этого нужно найти в оригинальном документе все вхождения каждого сниппета и выделить их, но уже не средствами Sphinx.У этого подхода сразу есть очевидный минус — это медленнее нежели бы это сделал сам Sphinx (по наблюдениям, сравнительных тестов не проводил). Однако других рабочих вариантов за короткий срок я не увидел.
Оставалось решить как именно подсвечивать найденные сниппеты. Рассмотрел варианты сделать это на фронте — нагуглил библиотеки jquery-highlight и rangy. Первая не подошла по функционалу, т.к. не умеет подсветку слов «разделенных» inline-тегами. Вторая довольно успешно справилась непосредственно с «качеством» подсветки (метод
findText
, см. документацию). Однако при росте количества сниппетов время выполнения сильно увеличивалось, браузер начинал подвисать, полностью отжирая одно ядро. Вынести в Web Workers не получилось, т.к. взаимодействие с DOM.Результат
Итогом стало решение делать подсветку на бэке. Беглый поиск не дал результатов — готовых решений найдено не было. Поэтому я решил написать собственное расширение для Nokogiri. Гем назвал «Hairaito», что в переводе с японского на английский дает «highlight». На русский переводится как «основной момент». Искренне надеюсь, что знатоки японского не вспомнят какой-нибудь неблагозвучный вариант перевода этого слова.
Hairaito умеет подсвечивать текст в html/xml, есть возможность настройки тегов и css-классов для подстветки, нумерации результатов.
Код проекта и примеры использования доступны на github.
Также гем опубликован на rubygems.
Буду рад услышать комментарии. Возможно, кто-нибудь знает более элегантное решение начальной проблемы.
Ссылки по теме:
Sphinx Search
Thinking Sphinx
Nokogiri
jquery-highlight
rangy