searchBlocks method
Future<List<UEpubSearchHit> >
searchBlocks(
- String query, {
- UDocSearchOptions options = const UDocSearchOptions(),
Searches block by block so every hit knows its exact block and offsets.
Implementation
Future<List<UEpubSearchHit>> searchBlocks(String query, {UDocSearchOptions options = const UDocSearchOptions()}) async {
final UEpubBook? book = _book;
final String trimmed = query.trim();
if (book == null || trimmed.isEmpty) {
clearSearch();
return const <UEpubSearchHit>[];
}
final int token = ++_searchToken;
emit(value.copyWith(searchQuery: trimmed, isSearching: true, searchHits: const <UDocSearchHit>[], searchHitIndex: -1));
final String needle = options.normalizePersian ? UDocText.forSearch(trimmed) : (options.caseSensitive ? trimmed : trimmed.toLowerCase());
final List<UEpubSearchHit> hits = <UEpubSearchHit>[];
for (int index = 0; index < book.chapterCount; index++) {
if (token != _searchToken || isDisposed) return hits;
final UEpubChapter chapter = await this.chapter(index);
for (int blockIndex = 0; blockIndex < chapter.blocks.length; blockIndex++) {
final UEpubBlock block = chapter.blocks[blockIndex];
if (!block.isText) continue;
final String text = block.text;
final UDocNormalizedText normalized = options.normalizePersian
? UDocNormalizedText.build(text)
: UDocNormalizedText(options.caseSensitive ? text : text.toLowerCase(), List<int>.generate(text.length, (int i) => i));
for (final int position in UDocText.findAll(normalized.text, needle, wholeWord: options.wholeWord)) {
final int start = normalized.sourceAt(position);
final int end = normalized.sourceAt(position + needle.length - 1) + 1;
final int snippetStart = max(0, start - 48);
final int snippetEnd = min(text.length, end + 48);
hits.add(UEpubSearchHit(pageIndex: index, blockIndex: blockIndex, start: start, end: end, snippet: text.substring(snippetStart, snippetEnd).replaceAll("\n", " "), matchIndex: hits.length));
if (hits.length >= options.maxHits) break;
}
if (hits.length >= options.maxHits) break;
}
liveHits.value = List<UDocSearchHit>.from(hits);
if (hits.length >= options.maxHits) break;
}
if (token != _searchToken || isDisposed) return hits;
emit(value.copyWith(searchHits: hits, isSearching: false, searchHitIndex: hits.isEmpty ? -1 : 0));
return hits;
}