|
1 | 1 | import { Store, Parser, DataFactory, type Quad_Object, type Quad_Subject, type Term, type Quad } from "n3"; |
2 | | -import type { PrezLiteral, PrezNode, PrezTerm, PrezProperties, PrezSearchResult, PrezFocusNode, PrezLink, PrezConceptSchemeNode, PrezConceptNode, PrezOntologyNode, PrezConceptSchemeOntologyNode, PrezBBlockNode, PrezLinkParent, PrezNodeList, PrezFacet } from "./types"; |
| 2 | +import type { |
| 3 | + PrezAnySearchResult, |
| 4 | + PrezBBlockNode, |
| 5 | + PrezConceptNode, |
| 6 | + PrezConceptSchemeNode, |
| 7 | + PrezConceptSchemeOntologyNode, |
| 8 | + PrezFacet, |
| 9 | + PrezFlatSearchResult, |
| 10 | + PrezFocusNode, |
| 11 | + PrezLink, |
| 12 | + PrezLinkParent, |
| 13 | + PrezLiteral, |
| 14 | + PrezLuceneShaclSearchResult, |
| 15 | + PrezNode, |
| 16 | + PrezNodeList, |
| 17 | + PrezOntologyNode, |
| 18 | + PrezProperties, |
| 19 | + PrezSearchMatch, |
| 20 | + PrezSearchOptions, |
| 21 | + PrezSearchOptionsDefault, |
| 22 | + PrezSearchOptionsLuceneShacl, |
| 23 | + PrezTerm |
| 24 | +} from "./types"; |
3 | 25 | import { DEFAULT_PREFIXES, PREZ_PREDICATES, SYSTEM_PREDICATES, BBLOCK_TYPES } from "./consts"; |
4 | 26 | import { defaultToIri, defaultFromIri } from "./helpers"; |
5 | 27 | import { node, literal, bnode } from "./factory"; |
@@ -163,6 +185,29 @@ export class RDFStore { |
163 | 185 | return this.getObjects(iri, predicate).map(o => this.toPrezTerm(o) as PrezLiteral)[0] || undefined; |
164 | 186 | } |
165 | 187 |
|
| 188 | + private toSubjectTerm(subject: string | Quad_Subject | Quad_Object): Quad_Subject { |
| 189 | + if (typeof subject === "string") { |
| 190 | + return namedNode(subject); |
| 191 | + } |
| 192 | + |
| 193 | + if (subject.termType === "Literal") { |
| 194 | + throw new Error(`Cannot use literal ${subject.value} as a subject.`); |
| 195 | + } |
| 196 | + |
| 197 | + return subject; |
| 198 | + } |
| 199 | + |
| 200 | + private getRequiredObject(subject: string | Quad_Subject | Quad_Object, predicate: string, description: string): Quad_Object { |
| 201 | + const object = this.getObjects(subject, predicate)[0]; |
| 202 | + |
| 203 | + if (!object) { |
| 204 | + const subjectValue = typeof subject === "string" ? subject : subject.value; |
| 205 | + throw new Error(`Missing ${description} for ${subjectValue}.`); |
| 206 | + } |
| 207 | + |
| 208 | + return object; |
| 209 | + } |
| 210 | + |
166 | 211 | public getProperties(term: Term, options?: {excludePrefix?: string, includePrefix?: string}): PrezProperties { |
167 | 212 | const props: PrezProperties = {}; |
168 | 213 |
|
@@ -326,13 +371,14 @@ export class RDFStore { |
326 | 371 | * @param predicate a string or string array of predicate IRIs |
327 | 372 | * @returns the array of objects |
328 | 373 | */ |
329 | | - public getObjects(subject: string, predicate: string | string[]): Quad_Object[] { |
| 374 | + public getObjects(subject: string | Quad_Subject | Quad_Object, predicate: string | string[]): Quad_Object[] { |
| 375 | + const subjectTerm = this.toSubjectTerm(subject); |
330 | 376 | if (typeof predicate === "string") { |
331 | | - return this.store.getObjects(namedNode(subject), namedNode(predicate), null); |
| 377 | + return this.store.getObjects(subjectTerm, namedNode(predicate), null); |
332 | 378 | } else { |
333 | 379 | const objs: Quad_Object[] = []; |
334 | 380 | predicate.forEach(p => { |
335 | | - objs.push(...this.store.getObjects(namedNode(subject), namedNode(p), null)); |
| 381 | + objs.push(...this.store.getObjects(subjectTerm, namedNode(p), null)); |
336 | 382 | }); |
337 | 383 | return objs; |
338 | 384 | } |
@@ -628,19 +674,89 @@ export class RDFStore { |
628 | 674 | /** |
629 | 675 | * Returns search results |
630 | 676 | */ |
631 | | - public search(): PrezSearchResult[] { |
| 677 | + private extractSearchResultHash(subject: Quad_Subject): string { |
| 678 | + return subject.value.startsWith("urn:hash:") ? subject.value.slice("urn:hash:".length) : subject.value; |
| 679 | + } |
| 680 | + |
| 681 | + private getSearchResultBase(subject: Quad_Subject) { |
| 682 | + const weightObject = this.getRequiredObject(subject, PREZ_PREDICATES.searchResultWeight, "search result weight"); |
| 683 | + const resourceObject = this.getRequiredObject(subject, PREZ_PREDICATES.searchResultURI, "search result URI"); |
| 684 | + |
| 685 | + return { |
| 686 | + hash: this.extractSearchResultHash(subject), |
| 687 | + weight: Number(weightObject.value), |
| 688 | + resource: this.toPrezFocusNode(resourceObject) |
| 689 | + }; |
| 690 | + } |
| 691 | + |
| 692 | + private getOptionalFlatSearchMatch(subject: Quad_Subject): PrezSearchMatch | undefined { |
| 693 | + const predicateObject = this.getObjects(subject, PREZ_PREDICATES.searchResultPredicate)[0]; |
| 694 | + const matchObject = this.getObjects(subject, PREZ_PREDICATES.searchResultMatch)[0]; |
| 695 | + |
| 696 | + if (!predicateObject && !matchObject) { |
| 697 | + return undefined; |
| 698 | + } |
| 699 | + |
| 700 | + if (!predicateObject || !matchObject) { |
| 701 | + throw new Error(`Incomplete flat search match for ${subject.value}.`); |
| 702 | + } |
| 703 | + |
| 704 | + return { |
| 705 | + predicate: this.toPrezTerm(predicateObject) as PrezNode, |
| 706 | + match: this.toPrezTerm(matchObject) as PrezLiteral |
| 707 | + }; |
| 708 | + } |
| 709 | + |
| 710 | + private parseSearchMatch(subject: Quad_Object): PrezSearchMatch { |
| 711 | + const predicateObject = this.getRequiredObject(subject, PREZ_PREDICATES.searchResultPredicate, "search match predicate"); |
| 712 | + const matchObject = this.getRequiredObject(subject, PREZ_PREDICATES.searchResultMatch, "search match literal"); |
| 713 | + |
| 714 | + return { |
| 715 | + predicate: this.toPrezTerm(predicateObject) as PrezNode, |
| 716 | + match: this.toPrezTerm(matchObject) as PrezLiteral |
| 717 | + }; |
| 718 | + } |
| 719 | + |
| 720 | + private parseDefaultSearchResult(subject: Quad_Subject): PrezFlatSearchResult { |
| 721 | + if (this.getObjects(subject, PREZ_PREDICATES.hasSearchMatch).length > 0) { |
| 722 | + throw new Error(`Received Lucene SHACL search matches for ${subject.value} while parserMode is default.`); |
| 723 | + } |
| 724 | + |
| 725 | + const flatMatch = this.getOptionalFlatSearchMatch(subject); |
| 726 | + |
| 727 | + if (!flatMatch) { |
| 728 | + throw new Error(`Missing flat search match data for ${subject.value}.`); |
| 729 | + } |
| 730 | + |
| 731 | + return { |
| 732 | + ...this.getSearchResultBase(subject), |
| 733 | + ...flatMatch |
| 734 | + }; |
| 735 | + } |
| 736 | + |
| 737 | + private parseJenaLuceneShaclSearchResult(subject: Quad_Subject): PrezLuceneShaclSearchResult { |
| 738 | + const nestedMatchNodes = this.getObjects(subject, PREZ_PREDICATES.hasSearchMatch); |
| 739 | + if (this.getOptionalFlatSearchMatch(subject)) { |
| 740 | + throw new Error(`Received flat search match fields for ${subject.value} while parserMode is jena-lucene-shacl.`); |
| 741 | + } |
| 742 | + |
| 743 | + return { |
| 744 | + ...this.getSearchResultBase(subject), |
| 745 | + matches: nestedMatchNodes.map(matchNode => this.parseSearchMatch(matchNode)) |
| 746 | + }; |
| 747 | + } |
| 748 | + |
| 749 | + public search(options?: PrezSearchOptionsDefault): PrezFlatSearchResult[]; |
| 750 | + public search(options: PrezSearchOptionsLuceneShacl): PrezLuceneShaclSearchResult[]; |
| 751 | + public search(options: PrezSearchOptions = {}): PrezAnySearchResult[] { |
| 752 | + const parserMode = options.parserMode ?? "default"; |
632 | 753 | const resultSubjects = this.getSubjects(SYSTEM_PREDICATES.a, PREZ_PREDICATES.searchResult); |
633 | | - const results: PrezSearchResult[] = resultSubjects.map(s => { |
634 | | - const result: PrezSearchResult = { |
635 | | - hash: s.value.split("urn:hash:").slice(-1)[0]!, |
636 | | - weight: Number(this.getObjects(s.value, PREZ_PREDICATES.searchResultWeight)[0]!.value), |
637 | | - predicate: this.toPrezTerm(this.getObjects(s.value, PREZ_PREDICATES.searchResultPredicate)[0]!) as PrezNode, |
638 | | - match: this.toPrezTerm(this.getObjects(s.value, PREZ_PREDICATES.searchResultMatch)[0]!) as PrezLiteral, |
639 | | - resource: this.toPrezFocusNode(this.getObjects(s.value, PREZ_PREDICATES.searchResultURI)[0]!) |
640 | | - }; |
641 | | - return result; |
642 | | - }); |
643 | | - return results; |
| 754 | + |
| 755 | + if (parserMode === "jena-lucene-shacl") { |
| 756 | + return resultSubjects.map(subject => this.parseJenaLuceneShaclSearchResult(subject)); |
| 757 | + } |
| 758 | + |
| 759 | + return resultSubjects.map(subject => this.parseDefaultSearchResult(subject)); |
644 | 760 | } |
645 | 761 |
|
646 | 762 |
|
|
0 commit comments