Skip to content

Commit ce8480c

Browse files
committed
Skohub: Implement search and suggest (#29)
1 parent 9ce0b22 commit ce8480c

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
},
6969
"dependencies": {
7070
"axios": "~0.26.1",
71+
"flexsearch": "~0.6.32",
7172
"jskos-tools": "^1.0.26",
7273
"localforage": "^1.10.0",
7374
"lodash": "^4.17.21",

src/providers/skohub-provider.js

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import BaseProvider from "./base-provider.js"
22
import * as _ from "../utils/lodash.js"
33
import * as errors from "../errors/index.js"
44
import { listOfCapabilities } from "../utils/index.js"
5+
import jskos from "jskos-tools"
6+
import FlexSearch from "flexsearch"
7+
import axios from "axios"
58

69
// from https://stackoverflow.com/a/22021709
710
function unicodeToChar(text) {
@@ -33,8 +36,8 @@ export default class SkohubProvider extends BaseProvider {
3336
this.has.concepts = true
3437
this.has.narrower = true
3538
this.has.ancestors = true
36-
this.has.suggest = false
37-
this.has.search = false
39+
this.has.suggest = true
40+
this.has.search = true
3841
// Explicitly set other capabilities to false
3942
listOfCapabilities.filter(c => !this.has[c]).forEach(c => {
4043
this.has[c] = false
@@ -43,6 +46,7 @@ export default class SkohubProvider extends BaseProvider {
4346

4447
_setup() {
4548
this._jskos.schemes = this.schemes || []
49+
this._index = {}
4650
this._cache = {}
4751
}
4852

@@ -184,6 +188,82 @@ export default class SkohubProvider extends BaseProvider {
184188
return concept.narrower
185189
}
186190

191+
/**
192+
* Returns concept search results.
193+
*
194+
* @param {Object} config
195+
* @param {string} config.search search string
196+
* @param {Object} [config.scheme] concept scheme to search in
197+
* @param {number} [config.limit=100] maximum number of search results
198+
* @returns {Array} array of JSKOS concept objects
199+
*/
200+
async search({ search, scheme, limit = 100 }) {
201+
if (!scheme || !scheme.uri) {
202+
throw new errors.InvalidOrMissingParameterError({ parameter: "scheme" })
203+
}
204+
if (!search) {
205+
throw new errors.InvalidOrMissingParameterError({ parameter: "search" })
206+
}
207+
// 1. Load index file if necessary
208+
let index
209+
if (!this._index[scheme.uri]) {
210+
this._index[scheme.uri] = {}
211+
}
212+
// Iterate over languages and use the first one that has an index
213+
for (const lang of this.languages) {
214+
if (this._index[scheme.uri][lang]) {
215+
index = this._index[scheme.uri][lang]
216+
break
217+
}
218+
try {
219+
const { data } = await axios.get(`${scheme.uri}.${lang}.index`)
220+
index = FlexSearch.create()
221+
index.import(data)
222+
this._index[scheme.uri][lang] = index
223+
break
224+
} catch (error) {
225+
// Ignore error
226+
}
227+
}
228+
if (!index) {
229+
throw new errors.InvalidRequestError({ message: "Could not find search index for any of the available languages " + this.languages.join(",") })
230+
}
231+
// 2. Use Flexsearch to get result URIs from index
232+
const result = index.search(search)
233+
// 3. Load concept data for results
234+
const concepts = await this.getConcepts({ concepts: result.map(uri => ({ uri, inScheme: [scheme] })) })
235+
return concepts.slice(0, limit)
236+
}
237+
238+
/**
239+
* Returns suggestion result in OpenSearch Suggest Format.
240+
*
241+
* @param {Object} config
242+
* @param {string} config.search search string
243+
* @param {Object} [config.scheme] concept scheme to search in
244+
* @param {number} [config.limit=100] maximum number of search results
245+
* @returns {Array} result in OpenSearch Suggest Format
246+
*/
247+
async suggest(config) {
248+
config._raw = true
249+
const concepts = await this.search(config)
250+
const result = [config.search, [], [], []]
251+
for (let concept of concepts) {
252+
const notation = jskos.notation(concept)
253+
const label = jskos.prefLabel(concept)
254+
result[1].push((notation ? notation + " " : "") + label)
255+
result[2].push("")
256+
result[3].push(concept.uri)
257+
}
258+
if (concepts._totalCount != undefined) {
259+
result._totalCount = concepts._totalCount
260+
} else {
261+
result._totalCount = concepts.length
262+
}
263+
return result
264+
}
265+
266+
187267
async _loadConcept(uri, config) {
188268
const data = await this.axios({ ...config, url: `${uri}.json` })
189269

0 commit comments

Comments
 (0)