Skip to content

Commit 0cca629

Browse files
committed
Add suggestions bindings and usage examples
1 parent 78cc7ab commit 0cca629

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// JavaScript usage examples for SuggestionSearcher in javascript-libzim
2+
// This would be used in the Web Worker or main thread after compilation
3+
4+
// First, load an archive
5+
Module.loadArchive("path/to/your/file.zim");
6+
7+
// APPROACH 1: Simple function approach (easiest to use)
8+
// This mirrors the existing search() function pattern
9+
console.log("=== Simple Function Approach ===");
10+
11+
const query = "kiwix";
12+
const maxResults = 10;
13+
14+
// Get suggestions using the simple function
15+
const suggestions = Module.suggest(query, maxResults);
16+
console.log(`Found ${suggestions.size()} suggestions for "${query}"`);
17+
18+
// Iterate through results
19+
for (let i = 0; i < suggestions.size(); i++) {
20+
const entry = suggestions.get(i);
21+
console.log(`${i}: ${entry.getTitle()} (${entry.getPath()})`);
22+
}
23+
24+
// APPROACH 2: Class-based approach (more similar to Node.js/Python APIs)
25+
// This provides more control and follows the same pattern as the other bindings
26+
console.log("\n=== Class-based Approach ===");
27+
28+
// Create a suggestion searcher
29+
const suggestionSearcher = new Module.SuggestionSearcher();
30+
31+
// Search for suggestions
32+
const suggestionSearch = suggestionSearcher.suggest(query);
33+
34+
// Get the number of matches
35+
const matchCount = suggestionSearch.getEstimatedMatches();
36+
console.log(`Found ${matchCount} total suggestions for "${query}"`);
37+
38+
// Get the first 10 results
39+
const results = suggestionSearch.getResults(0, 10);
40+
console.log("First 10 suggestion results:");
41+
for (let i = 0; i < results.size(); i++) {
42+
const entry = results.get(i);
43+
console.log(` ${i}: ${entry.getTitle()} (${entry.getPath()})`);
44+
}
45+
46+
// Get more results with pagination
47+
if (matchCount > 10) {
48+
const moreResults = suggestionSearch.getResults(10, 10); // Next 10 results
49+
console.log("Next 10 suggestion results:");
50+
for (let i = 0; i < moreResults.size(); i++) {
51+
const entry = moreResults.get(i);
52+
console.log(` ${i + 10}: ${entry.getTitle()} (${entry.getPath()})`);
53+
}
54+
}
55+
56+
// COMPARISON: Both approaches can be used together
57+
console.log("\n=== Comparison with Full-text Search ===");
58+
59+
// Full-text search (existing functionality)
60+
const searchResults = Module.search(query, 5);
61+
console.log(`Full-text search found ${searchResults.size()} results`);
62+
63+
// Suggestion search (new functionality)
64+
const suggestionResults = Module.suggest(query, 5);
65+
console.log(`Suggestion search found ${suggestionResults.size()} results`);
66+
67+
// Compare the results
68+
console.log("Full-text search results:");
69+
for (let i = 0; i < searchResults.size(); i++) {
70+
const entry = searchResults.get(i);
71+
console.log(` ${entry.getTitle()} (${entry.getPath()})`);
72+
}
73+
74+
console.log("Suggestion search results:");
75+
for (let i = 0; i < suggestionResults.size(); i++) {
76+
const entry = suggestionResults.get(i);
77+
console.log(` ${entry.getTitle()} (${entry.getPath()})`);
78+
}
79+
80+
// PRACTICAL EXAMPLE: Building a search dropdown
81+
console.log("\n=== Practical Example: Search Dropdown ===");
82+
83+
function buildSearchDropdown(userInput) {
84+
if (userInput.length < 2) return []; // Don't search for very short queries
85+
86+
try {
87+
// Get quick suggestions (usually faster than full-text search)
88+
const suggestions = Module.suggest(userInput, 8);
89+
const dropdownItems = [];
90+
91+
for (let i = 0; i < suggestions.size(); i++) {
92+
const entry = suggestions.get(i);
93+
dropdownItems.push({
94+
title: entry.getTitle(),
95+
path: entry.getPath(),
96+
type: 'suggestion'
97+
});
98+
}
99+
100+
return dropdownItems;
101+
} catch (error) {
102+
console.error("Error getting suggestions:", error);
103+
return [];
104+
}
105+
}
106+
107+
// Example usage
108+
const userInput = "wik";
109+
const dropdownItems = buildSearchDropdown(userInput);
110+
console.log(`Dropdown for "${userInput}":`, dropdownItems);

libzim_bindings.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <zim/item.h>
33
#include <zim/error.h>
44
#include <zim/search.h>
5+
#include <zim/suggestion.h>
56
#include <iostream>
67
#include <chrono>
78
#include <emscripten/bind.h>
@@ -69,6 +70,9 @@ class EntryWrapper{
6970
std::string getPath() {
7071
return m_entry.getPath();
7172
}
73+
std::string getTitle() {
74+
return m_entry.getTitle();
75+
}
7276
bool isRedirect() {
7377
return m_entry.isRedirect();
7478
}
@@ -80,6 +84,68 @@ class EntryWrapper{
8084
zim::Entry m_entry;
8185
};
8286

87+
// SuggestionSearcher wrapper
88+
class SuggestionSearcherWrapper {
89+
public:
90+
SuggestionSearcherWrapper() {
91+
if (!g_archive) {
92+
throw std::runtime_error("No archive loaded");
93+
}
94+
searcher = std::make_unique<zim::SuggestionSearcher>(*g_archive);
95+
}
96+
97+
SuggestionSearchWrapper suggest(const std::string& query);
98+
99+
private:
100+
std::unique_ptr<zim::SuggestionSearcher> searcher;
101+
};
102+
103+
// SuggestionSearch wrapper
104+
class SuggestionSearchWrapper {
105+
public:
106+
SuggestionSearchWrapper(zim::SuggestionSearch search)
107+
: search_(search) {}
108+
109+
unsigned int getEstimatedMatches() const {
110+
try {
111+
return search_.getEstimatedMatches();
112+
} catch (const std::exception& e) {
113+
std::cout << "getEstimatedMatches error: " << e.what() << std::endl;
114+
return 0;
115+
}
116+
}
117+
118+
std::vector<EntryWrapper> getResults(int start, int count) const {
119+
try {
120+
zim::SuggestionResultSet resultSet = search_.getResults(start, count);
121+
std::vector<EntryWrapper> results;
122+
123+
for (const auto& entry : resultSet) {
124+
results.push_back(EntryWrapper(entry));
125+
}
126+
127+
return results;
128+
} catch (const std::exception& e) {
129+
std::cout << "getResults error: " << e.what() << std::endl;
130+
return std::vector<EntryWrapper>();
131+
}
132+
}
133+
134+
private:
135+
zim::SuggestionSearch search_;
136+
};
137+
138+
// Implement the suggest method (needs to be after SuggestionSearchWrapper definition)
139+
SuggestionSearchWrapper SuggestionSearcherWrapper::suggest(const std::string& query) {
140+
try {
141+
zim::SuggestionSearch search = searcher->suggest(query);
142+
return SuggestionSearchWrapper(search);
143+
} catch (const std::exception& e) {
144+
std::cout << "suggest error: " << e.what() << std::endl;
145+
throw;
146+
}
147+
}
148+
83149
// Get an entry by its path
84150
std::unique_ptr<EntryWrapper> getEntryByPath(std::string url) {
85151
try {
@@ -107,19 +173,38 @@ std::vector<EntryWrapper> search(std::string text, int numResults) {
107173
return ret;
108174
}
109175

176+
// Suggestion search function (alternative to class-based approach)
177+
std::vector<EntryWrapper> suggest(std::string text, int numResults) {
178+
try {
179+
auto suggestionSearcher = zim::SuggestionSearcher(*g_archive);
180+
auto suggestionSearch = suggestionSearcher.suggest(text);
181+
auto resultSet = suggestionSearch.getResults(0, numResults);
182+
std::vector<EntryWrapper> ret;
183+
for(auto entry : resultSet) {
184+
ret.push_back(EntryWrapper(entry));
185+
}
186+
return ret;
187+
} catch(const std::exception& e) {
188+
std::cout << "suggestion error: " << e.what() << std::endl;
189+
return std::vector<EntryWrapper>();
190+
}
191+
}
192+
110193
// Binding code
111194
EMSCRIPTEN_BINDINGS(libzim_module) {
112195
emscripten::function("loadArchive", &loadArchive);
113196
emscripten::function("getEntryByPath", &getEntryByPath);
114197
emscripten::function("getArticleCount", &getArticleCount);
115198
emscripten::function("search", &search);
199+
emscripten::function("suggest", &suggest);
116200
emscripten::register_vector<char>("vector<char>");
117201
emscripten::register_vector<EntryWrapper>("vector(EntryWrapper)");
118202
class_<EntryWrapper>("EntryWrapper")
119203
.function("getItem", &EntryWrapper::getItem)
120204
.function("getPath", &EntryWrapper::getPath)
121205
.function("isRedirect", &EntryWrapper::isRedirect)
122206
.function("getRedirectEntry", &EntryWrapper::getRedirectEntry)
207+
.function("getTitle", &EntryWrapper::getTitle)
123208
;
124209
class_<ItemWrapper>("ItemWrapper")
125210
.function("getData", &ItemWrapper::getData)
@@ -128,4 +213,12 @@ EMSCRIPTEN_BINDINGS(libzim_module) {
128213
class_<BlobWrapper>("BlobWrapper")
129214
.function("getContent", &BlobWrapper::getContent)
130215
;
216+
class_<SuggestionSearcherWrapper>("SuggestionSearcher")
217+
.constructor<>()
218+
.function("suggest", &SuggestionSearcherWrapper::suggest)
219+
;
220+
class_<SuggestionSearchWrapper>("SuggestionSearch")
221+
.function("getEstimatedMatches", &SuggestionSearchWrapper::getEstimatedMatches)
222+
.function("getResults", &SuggestionSearchWrapper::getResults)
223+
;
131224
}

0 commit comments

Comments
 (0)