UNPKG

sparnatural

Version:

Visual client-side SPARQL query builder and knowledge graph exploration tool

414 lines (379 loc) 13.8 kB
import { DataFactory } from "rdf-data-factory"; import { Pattern, Variable } from "sparqljs"; import ISparnaturalSpecification from "../../../spec-providers/ISparnaturalSpecification"; import SparqlFactory from "../SparqlFactory"; import ValueBuilderIfc, { ValueBuilderFactory } from "../ValueBuilder"; import { Branch, ISparJson, VariableExpression, VariableTerm, } from "../../json/ISparJson"; import TypedVariableTranslator from "./TypedVariableTranslator"; const factory = new DataFactory(); /** * Translates a Branch JSON structure into SparqlJs structure */ export default class BranchTranslator { // The JSON branch to convert to SPARQL #branch: Branch; // The full query from which the branch is extracted #fullQuery: ISparJson; #specProvider: ISparnaturalSpecification; // whether this branch is a children of another branch #isVeryFirst: boolean; #isInOption: boolean; // to translate widget values to SPARQL #valueBuilder: ValueBuilderIfc; // intermediate patterns #startClassPtrn: Pattern[] = []; #endClassPtrn: Pattern[] = []; #intersectionPtrn: Pattern[] = []; #whereChildPtrns: Pattern[] = []; #valuePtrns: Pattern[] = []; #executedAfterPtrns: Pattern[] = []; // final result #resultPtrns: Pattern[] = []; // default vars gathered from children #defaultVars: Variable[] = []; settings: any; constructor( // the branch to convert branch: Branch, // the full query (we need it to test if variables are selected) fullQuery: ISparJson, // the sparnatural spec specProvider: ISparnaturalSpecification, // true if this branch is the very first of the query (not a child of another, not a sibling of the first one) isVeryFirst: boolean, // true if this branch is under (recursively) of another that is itself in an option isInOption: boolean, settings: any ) { this.#branch = branch; this.#fullQuery = fullQuery; this.#specProvider = specProvider; this.#isVeryFirst = isVeryFirst; this.#isInOption = isInOption; this.settings = settings; // create the object to convert widget values to SPARQL let endClassValue = this.#branch.line.oType; // this is because the query generation may be triggered while the end class is not there yet if (endClassValue != null) { this.#valueBuilder = new ValueBuilderFactory().buildValueBuilder( this.#specProvider .getProperty(this.#branch.line.p) .getPropertyType(endClassValue) ); // pass everything needed to generate SPARQL this.#valueBuilder.init( this.#specProvider, { variable: this.#branch.line.s, type: this.#branch.line.sType }, { variable: null, type: this.#branch.line.p }, { variable: this.#branch.line.o, type: this.#branch.line.oType }, BranchTranslator.isVarSelected(fullQuery, this.#branch.line.o), this.#branch.line.values ); } } build() { this.#buildChildrenPatterns(); this.#buildSubjectClassPtrn(); this.#buildIntersectionPtrn(); this.#buildObjectClassPtrn(); this.#buildValuePtrn(); this.#buildFinalResultPtrn(); } //! //! //! //! //! //! //verifier cette partie la pour la generation des childrens /** * Converts all children branches, and gather their patterns at this level */ #buildChildrenPatterns() { this.#branch.children.forEach((branch) => { const builder = new BranchTranslator( branch, this.#fullQuery, this.#specProvider, // children are never the very first false, // children branch will be in option if this one is optional or not exists this.#branch.optional || this.#branch.notExists, this.settings ); builder.build(); this.#whereChildPtrns.push(...builder.getResultPtrns()); // gather default vars from children this.#defaultVars.push(...builder.getDefaultVars()); // gather patterns to be executed after this.#executedAfterPtrns.push(...builder.getExecutedAfterPtrns()); }); } #buildValuePtrn() { if (this.#branch.line.values?.length > 0) { this.#valuePtrns = this.#valueBuilder.build(); } } /** * Generates the triple of the type of subject ("s" and "sType" in the branch JSON structure) */ #buildSubjectClassPtrn() { let typeTranslator: TypedVariableTranslator = new TypedVariableTranslator( this.#branch.line.s, this.#branch.line.sType, // Note : on subject position, the only variable that can be selected is the very first one // Otherwise it can be selected in the object position, but not inside a WHERE clause // Anyway if it not the very first, all the startClassPtrn is ignored when building the final query BranchTranslator.isVarSelected(this.#fullQuery, this.#branch.line.s) && this.#isVeryFirst, this.#valueBuilder?.isBlockingStart(), this.#specProvider, this.settings ); typeTranslator.build(); this.#startClassPtrn = typeTranslator.resultPtrns; // if there was any default label patterns generated, gather the variable names of the default label if (typeTranslator.defaultLblPatterns.length > 0) { this.#defaultVars.push( factory.variable(typeTranslator.defaultLabelVarName) ); } } #buildObjectClassPtrn() { let typeTranslator: TypedVariableTranslator = new TypedVariableTranslator( this.#branch.line.o, this.#branch.line.oType, BranchTranslator.isVarSelected(this.#fullQuery, this.#branch.line.o), this.#valueBuilder?.isBlockingEnd(), this.#specProvider, this.settings ); typeTranslator.build(); this.#endClassPtrn = typeTranslator.resultPtrns; if (typeTranslator.defaultLblPatterns.length > 0) { this.#defaultVars.push( factory.variable(typeTranslator.defaultLabelVarName) ); } } #buildIntersectionPtrn() { // Vérification des conditions de génération de l'intersection triple if ( this.#branch.line.s && this.#branch.line.o && !this.#valueBuilder?.isBlockingObjectProp() ) { const specProperty = this.#specProvider.getProperty(this.#branch.line.p); if ( specProperty.getBeginDateProperty() && specProperty.getEndDateProperty() ) { // Génération des triples pour les dates de début, de fin et exactes, si disponibles if (specProperty.getBeginDateProperty()) { this.#intersectionPtrn.push( SparqlFactory.buildOptionalPattern([ SparqlFactory.buildBgpPattern([ SparqlFactory.buildIntersectionTriple( factory.variable(this.#branch.line.s), specProperty.getBeginDateProperty(), factory.variable(`${this.#branch.line.o}_begin`) ), ]), ]) ); } if (specProperty.getEndDateProperty()) { this.#intersectionPtrn.push( SparqlFactory.buildOptionalPattern([ SparqlFactory.buildBgpPattern([ SparqlFactory.buildIntersectionTriple( factory.variable(this.#branch.line.s), specProperty.getEndDateProperty(), factory.variable(`${this.#branch.line.o}_end`) ), ]), ]) ); } if (specProperty.getExactDateProperty()) { this.#intersectionPtrn.push( SparqlFactory.buildOptionalPattern([ SparqlFactory.buildBgpPattern([ SparqlFactory.buildIntersectionTriple( factory.variable(this.#branch.line.s), specProperty.getExactDateProperty(), factory.variable(`${this.#branch.line.o}_exact`) ), ]), ]) ); } } else { // Génération du triple d'intersection normal si aucune propriété de date n'est spécifiée this.#intersectionPtrn.push( SparqlFactory.buildBgpPattern([ SparqlFactory.buildIntersectionTriple( factory.variable(this.#branch.line.s), this.#branch.line.p, factory.variable(this.#branch.line.o) ), ]) ); } // Ajout du filtre de langue si la propriété est multilingue if (specProperty.isMultilingual()) { this.#intersectionPtrn.push( SparqlFactory.buildFilterLangEquals( factory.variable(this.#branch.line.o), factory.literal(this.settings.language) ) ); } } } //----------------------------------------------------------- //old version #buildIntersectionPtrn1() { // the intersection triple can very well be generated even if no rdf:type triple is generated for the end class. if ( this.#branch.line.s && this.#branch.line.o && !this.#valueBuilder?.isBlockingObjectProp() ) { this.#intersectionPtrn.push( SparqlFactory.buildBgpPattern([ SparqlFactory.buildIntersectionTriple( factory.variable(this.#branch.line.s), this.#branch.line.p, factory.variable(this.#branch.line.o) ), ]) ); // add language filter if property is set to be multilingual if ( this.#specProvider.getProperty(this.#branch.line.p).isMultilingual() ) { this.#intersectionPtrn.push( SparqlFactory.buildFilterLangEquals( factory.variable(this.#branch.line.o), factory.literal(this.settings.language) ) ); } } } //----------------------------------------------------------- /** * Translates the line to SPARQL */ #buildFinalResultPtrn() { // always store the startClassPattern in the final result pattern (no OPTIONAL, no NOT EXISTS, no SERVICE) // the subject type criteria is generated only for the first criteria, // not if it is inside a WHERE or an AND if (this.#isVeryFirst) this.#resultPtrns.push(...this.#startClassPtrn); // concat all the patterns together, except the start pattern which is handled differntly let exceptStartPtrn: Pattern[] = []; if (this.#intersectionPtrn) { exceptStartPtrn.push(...this.#intersectionPtrn); } if ( this.#branch.line.o && !this.#specProvider .getEntity(this.#branch.line.oType) .isLiteralEntity() && !this.#specProvider.getProperty(this.#branch.line.p).omitClassCriteria() ) { exceptStartPtrn.push(...this.#endClassPtrn); } exceptStartPtrn.push(...this.#valuePtrns); exceptStartPtrn.push(...this.#whereChildPtrns); // this will wrap everything except start inside the OPTIONAL or NOT EXISTS this.#createOptionStatePtrn(exceptStartPtrn); } #createOptionStatePtrn(exceptStartPtrn: Pattern[]) { // create a SERVICE clause if needed const sparqlService = this.#specProvider .getProperty(this.#branch.line.p) ?.getServiceEndpoint(); let servicePtrn = null; if (sparqlService) { const endpoint = factory.namedNode(sparqlService); // to be on the safe side : when rollback an endclass group, we may come here with only the start class group criteria // and nothing in this array if (exceptStartPtrn.length > 0) { servicePtrn = SparqlFactory.buildServicePattern( exceptStartPtrn, endpoint ); } } let normalOrServicePatterns: Pattern[] = servicePtrn ? [servicePtrn] : exceptStartPtrn; // produce the generated patterns, maybe wrapped in OPTIONAL or NOT EXISTS let finalResultPtrns: Pattern[] = []; // if this branch is optional and is not inside an optional branch if (this.#branch.optional && !this.#isInOption) { finalResultPtrns.push( SparqlFactory.buildOptionalPattern(normalOrServicePatterns) ); } else if (this.#branch.notExists && !this.#isInOption) { finalResultPtrns.push( SparqlFactory.buildNotExistsPattern( SparqlFactory.buildGroupPattern(normalOrServicePatterns) ) ); } else { // nothing special, just retain the patterns in the final result pattern finalResultPtrns.push(...normalOrServicePatterns); } // then decide where to store the generated patterns : either in "normal" patterns // or in patterns that shall be executed after the rest of the query if ( servicePtrn && this.#specProvider .getProperty(this.#branch.line.p) ?.isLogicallyExecutedAfter() ) { this.#executedAfterPtrns.push(...finalResultPtrns); } else { this.#resultPtrns.push(...finalResultPtrns); } } /** * @param query The query to test * @param varName The variable to test the selection for * @returns true if the varName is selected in the query */ static isVarSelected(query: ISparJson, varName: string): boolean { return ( query.variables.filter((v) => { // if it is an aggregated variable... if ((v as VariableExpression).expression) { let vExpression: VariableExpression = v as VariableExpression; if (vExpression.expression.expression.value == varName) { return true; } } else { if ((v as VariableTerm).value == varName) { return true; } } return false; }).length == 1 ); } getResultPtrns() { return this.#resultPtrns; } getDefaultVars() { return this.#defaultVars; } getExecutedAfterPtrns() { return this.#executedAfterPtrns; } }