Files
myep/src/Chatbot/Search/Excerpt.php
T

54 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Chatbot\Search;
/**
* Cuts a window of about LENGTH characters out of a document, around its first match.
*
* The excerpt keeps search results small; get_document fetches the rest.
*/
final class Excerpt
{
public const LENGTH = 600;
/**
* @param list<string> $terms lower-cased, matched as prefixes like the full-text search
*/
public static function around(string $text, array $terms, int $length = self::LENGTH): string
{
$text = trim((string) preg_replace('/\s+/u', ' ', $text));
$textLength = mb_strlen($text);
if ($textLength <= $length) {
return $text;
}
$position = null;
$lower = mb_strtolower($text);
foreach ($terms as $term) {
$found = mb_strpos($lower, $term);
if (false !== $found && (null === $position || $found < $position)) {
$position = $found;
}
}
// Start a third of the window before the match, so it is read in context.
$start = max(0, min(($position ?? 0) - intdiv($length, 3), $textLength - $length));
$excerpt = mb_substr($text, $start, $length);
if ($start > 0) {
$space = mb_strpos($excerpt, ' ');
$excerpt = '…'.(false !== $space && $space < 40 ? mb_substr($excerpt, $space + 1) : $excerpt);
}
if ($start + $length < $textLength) {
$space = mb_strrpos($excerpt, ' ');
$excerpt = (false !== $space && mb_strlen($excerpt) - $space < 40 ? mb_substr($excerpt, 0, $space) : $excerpt).'…';
}
return $excerpt;
}
}