Skip to content

Commit

Permalink
Improve search functionality (#578)
Browse files Browse the repository at this point in the history
- Resolved issues with the search feature.
- Implemented diacritic-insensitive searching.
- Enhanced search to be insensitive to Unicode categories: Mark (M), Punctuation (P), Symbol (S), and Control (C), as well as the Arabic Tatweel character (U+0640).
- Normalized search strings using Unicode Normalization Form KD.
- Unified search and autocomplete behavior by adopting a consistent matching algorithm.

TODO:
- Integrate Full-Text Search (FTS) for enhanced searching capabilities.
- Extend search functionality to include all downloaded translations and tafseers.
- Implement collapsible search results, allowing users to collapse specific results for easier navigation to subsequent entries.
  • Loading branch information
mohamede1945 authored Nov 5, 2023
1 parent 79fa74e commit a2a6965
Show file tree
Hide file tree
Showing 38 changed files with 1,667 additions and 424 deletions.
25 changes: 13 additions & 12 deletions Data/VerseTextPersistence/GRDBVerseTextPersistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,6 @@ import QuranKit
import SQLitePersistence

public struct GRDBQuranVerseTextPersistence: VerseTextPersistence {
// MARK: Lifecycle

public init(fileURL: URL) {
self.init(mode: .arabic, fileURL: fileURL)
}

public init(mode: Mode, fileURL: URL) {
persistence = GRDBVerseTextPersistence(fileURL: fileURL, textTable: mode.tabelName)
}

// MARK: Public

public enum Mode {
case arabic
case share
Expand All @@ -39,6 +27,18 @@ public struct GRDBQuranVerseTextPersistence: VerseTextPersistence {
}
}

// MARK: Lifecycle

public init(fileURL: URL) {
self.init(mode: .arabic, fileURL: fileURL)
}

public init(mode: Mode, fileURL: URL) {
persistence = GRDBVerseTextPersistence(fileURL: fileURL, textTable: mode.tabelName)
}

// MARK: Public

public func textForVerses(_ verses: [AyahNumber]) async throws -> [AyahNumber: String] {
try await persistence.textForVerses(verses, transform: textFromRow)
}
Expand Down Expand Up @@ -175,6 +175,7 @@ private struct GRDBVerseTextPersistence {
func search(for term: String, quran: Quran) async throws -> [(verse: AyahNumber, text: String)] {
try await db.read { db in
// TODO: Use match for FTS.
// Use like to match "_" in the Arabic regex as `match` treats "_" as a regular character.
let request = SQLRequest<Row>("""
SELECT text, sura, ayah
FROM \(sql: searchTable)
Expand Down
92 changes: 0 additions & 92 deletions Domain/QuranTextKit/Sources/Search/SearchResultsProcessor.swift

This file was deleted.

86 changes: 0 additions & 86 deletions Domain/QuranTextKit/Sources/Search/SearchTermProcessor.swift

This file was deleted.

14 changes: 0 additions & 14 deletions Domain/QuranTextKit/Sources/Search/Searcher.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import TranslationService
import VerseTextPersistence
import VLogging

public struct CompositeSearcher: Searcher {
public struct CompositeSearcher {
// MARK: Lifecycle

init(
Expand Down Expand Up @@ -48,32 +48,39 @@ public struct CompositeSearcher: Searcher {
// MARK: Public

public func autocomplete(term: String, quran: Quran) async throws -> [String] {
logger.info("Autocompleting term: \(term)")
guard let term = SearchTerm(term) else {
return []
}
logger.info("Autocompleting term: \(term.compactQuery)")

let autocompletions = try await simpleSearchers.asyncMap { searcher in
try await searcher.autocomplete(term: term, quran: quran)
}
var results = autocompletions.flatMap { $0 }

if results.isEmpty {
results = try await translationsSearcher.autocomplete(term: term, quran: quran)
if shouldPerformTranslationSearch(simpleSearchResults: results, term: term.compactQuery) {
results += try await translationsSearcher.autocomplete(term: term, quran: quran)
}
if !results.contains(term) {
results.insert(term, at: 0)
if !results.contains(term.compactQuery) {
results.insert(term.compactQuery, at: 0)
}
return results.orderedUnique()
}

public func search(for term: String, quran: Quran) async throws -> [SearchResults] {
logger.info("Search for: \(term)")
guard let term = SearchTerm(term) else {
return []
}
logger.info("Search for: \(term.compactQuery)")

let searchResults = try await simpleSearchers.asyncMap { searcher in
try await searcher.search(for: term, quran: quran)
}
var results = searchResults
.flatMap { $0 }
.filter { !$0.items.isEmpty } // Remove empty search results
if results.isEmpty {
results = try await translationsSearcher.search(for: term, quran: quran)
if shouldPerformTranslationSearch(simpleSearchResults: results, term: term.compactQuery) {
results += try await translationsSearcher.search(for: term, quran: quran)
.filter { !$0.items.isEmpty } // Remove empty search results
}

Expand All @@ -96,4 +103,8 @@ public struct CompositeSearcher: Searcher {
.map { source, items in SearchResults(source: source, items: items) }
.sorted { $0.source < $1.source }
}

private func shouldPerformTranslationSearch(simpleSearchResults: [some Any], term: String) -> Bool {
simpleSearchResults.isEmpty || (!term.containsArabic() && !term.containsOnlyNumbers())
}
}
26 changes: 9 additions & 17 deletions Domain/QuranTextKit/Sources/Search/Searchers/NumberSearcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ struct NumberSearcher: Searcher {

let quranVerseTextPersistence: VerseTextPersistence

func autocomplete(term: String, quran: Quran) throws -> [String] {
if Int(term) != nil {
return resultsProcessor.buildAutocompletions(searchResults: [term], term: term)
func autocomplete(term: SearchTerm, quran: Quran) throws -> [String] {
if Int(term.compactQuery) != nil {
return term.buildAutocompletions(searchResults: [term.compactQuery])
}
return []
}

func search(for term: String, quran: Quran) async throws -> [SearchResults] {
func search(for term: SearchTerm, quran: Quran) async throws -> [SearchResults] {
let items: [SearchResult] = try await search(for: term, quran: quran)
return [SearchResults(source: .quran, items: items)]
}
Expand All @@ -35,10 +35,8 @@ struct NumberSearcher: Searcher {
return formatter
}()

private let resultsProcessor = SearchResultsProcessor()

private func search(for term: String, quran: Quran) async throws -> [SearchResult] {
let components = parseIntArray(term)
private func search(for term: SearchTerm, quran: Quran) async throws -> [SearchResult] {
let components = parseIntArray(term.compactQuery)
guard !components.isEmpty else {
return []
}
Expand Down Expand Up @@ -99,17 +97,11 @@ struct NumberSearcher: Searcher {
guard !components.isEmpty, components.count <= 2 else {
return []
}
guard let first = parseInt(components[0]) else {
let result = components.compactMap { parseInt($0) }
if result.count != components.count {
return []
}
if components.count == 1 {
return [first]
} else {
guard let second = parseInt(components[1]) else {
return []
}
return [first, second]
}
return result
}

private func parseInt(_ value: String) -> Int? {
Expand Down
Loading

0 comments on commit a2a6965

Please sign in to comment.