-
Notifications
You must be signed in to change notification settings - Fork 5
/
FacetUtils.js
347 lines (266 loc) · 12.5 KB
/
FacetUtils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
var Concept = require('../sparql/Concept');
var Relation = require('../sparql/Relation');
var HashMap = require('../util/collection/HashMap');
var NodeFactory = require('../rdf/NodeFactory');
var Triple = require('../rdf/Triple');
var NodeUtils = require('../rdf/NodeUtils');
var ExprVar = require('../sparql/expr/ExprVar');
var E_Equals = require('../sparql/expr/E_Equals');
var NodeValue = require('../sparql/expr/NodeValue');
var NodeValueUtils = require('../sparql/NodeValueUtils');
var E_LogicalNot = require('../sparql/expr/E_LogicalNot');
var E_OneOf = require('../sparql/expr/E_OneOf');
var ElementGroup = require('../sparql/element/ElementGroup');
var ElementFilter = require('../sparql/element/ElementFilter');
var ElementTriplesBlock = require('../sparql/element/ElementTriplesBlock');
var PatternUtils = require('../sparql/PatternUtils');
var ElementUtils = require('../sparql/ElementUtils');
var VarUtils = require('../sparql/VarUtils');
var Step = require('./Step');
var StepUtils = require('./StepUtils');
var StepRelation = require('./StepRelation');
var FacetRelationIndex = require('./FacetRelationIndex');
var PathHead = require('./PathHead');
var FacetUtils = {
/**
* Create a concept for the set of resources at a given path.
* Note that this is distinct from the facets and the facet values:
* If the facets are properties, and the facet values are objects, then
* this this is the subjects.
*
*/
createConceptResources: function(facetConfig, path, excludeSelfConstraints) {
var baseConcept = facetConfig.getBaseConcept();
var rootFacetNode = facetConfig.getRootFacetNode();
var constraintManager = facetConfig.getConstraintManager();
var excludePath = excludeSelfConstraints ? path : null;
var elementsAndExprs = constraintManager.createElementsAndExprs(rootFacetNode, excludePath);
var constraintElements = elementsAndExprs.getElements();
var constraintExprs = elementsAndExprs.getExprs();
var facetNode = rootFacetNode.forPath(path);
var facetVar = facetNode.getVar();
var baseElements = baseConcept.getElements();
var pathElements = facetNode.getElements();
var facetElements = [];
facetElements.push.apply(facetElements, pathElements);
facetElements.push.apply(facetElements, constraintElements);
if(baseConcept.isSubjectConcept()) {
if(facetElements.length === 0) {
facetElements = baseElements;
}
} else {
facetElements.push.apply(facetElements, baseElements);
}
var filterElements = constraintExprs.map(function(expr) {
var element = new ElementFilter(expr);
return element;
});
facetElements.push.apply(facetElements, filterElements);
var finalElement = (new ElementGroup(facetElements)).flatten();
//var finalElements = ElementUtils.flatten(facetElements);
//finalElements = ElementUtils.flattenElements(finalElements);
var result = new Concept(finalElement, facetVar);
return result;
},
/**
* Creates a concept that fetches all facets at a given path
*
* Note that the returned concept does not necessarily
* offer access to the facet's values (see first example).
*
* Examples:
* - ({?s a rdf:Property}, ?s)
* - ({?s a ex:Foo . ?s ?p ?o }, ?p)
*
* TODO We should add arguments to support scanLimit and resourceLimit (such as: only derive facets based on distinct resources within the first 1000000 triples)
*
*/
createConceptFacets: function(facetConfig, pathHead) {
var relation = this.createRelationFacets(facetConfig, pathHead);
//var relation.Concept();
var result = new Concept(relation.getElement(), relation.getSourceVar());
return result;
},
/**
* Creates a relation that relates facets to their values.
* Example ({ ?s a Airport . ?s ?p ?o}, ?p , ?o)
*
* Common method to create concepts for both facets and facet values.
*
* This method is the core for both creating concepts representing the set
* of facets as well as facet values.
*
* TODO Possibly add support for specifying the p ond o base var names
*
* @param path The path for which to describe the set of facets
* @param isInverse Whether at the given path the outgoing or incoming facets should be described
* @param enableOptimization Returns the concept (?p a Property, ?p) in cases where (?s ?p ?o, ?p) would be returned.
* @param singleProperty Optional. Whether to create a concept where only a single property at the given path is selected. Useful for creating concepts for individual properties
*/
createRelationFacets: function(facetConfig, pathHead, singleProperty) {
var path = pathHead.getPath();
var isInverse = pathHead.isInverse();
var baseConcept = facetConfig.getBaseConcept();
var rootFacetNode = facetConfig.getRootFacetNode();
var constraintManager = facetConfig.getConstraintManager();
var singleStep = null;
if(singleProperty) {
singleStep = new Step(singleProperty.getUri(), isInverse);
}
var excludePath = null;
if(singleStep) {
excludePath = path.copyAppendStep(singleStep);
}
var elementsAndExprs = constraintManager.createElementsAndExprs(rootFacetNode, excludePath);
var constraintElements = elementsAndExprs.toElements();
var facetNode = rootFacetNode.forPath(path);
var facetVar = facetNode.getVar();
var baseElements = baseConcept.getElements();
var facetElements;
if(baseConcept.isSubjectConcept()) {
facetElements = constraintElements;
} else {
facetElements = baseElements.concat(constraintElements);
}
var varsMentioned = PatternUtils.getVarsMentioned(facetElements); //.getVarsMentioned();
var propertyVar = VarUtils.freshVar('p', varsMentioned);
var objectVar = VarUtils.freshVar('o', varsMentioned);
//console.log('propertyVar: ' + propertyVar);
var triple = !isInverse
? new Triple(facetVar, propertyVar, objectVar)
: new Triple(objectVar, propertyVar, facetVar);
facetElements.push(new ElementTriplesBlock([triple]));
if(singleStep) {
var exprVar = new ExprVar(propertyVar);
var expr = new E_Equals(exprVar, NodeValueUtils.makeNode(singleProperty));
facetElements.push(new ElementFilter(expr));
}
var pathElements = facetNode.getElements();
facetElements.push.apply(facetElements, pathElements);
var finalElement = (new ElementGroup(facetElements)).flatten();
//var finalElements = ElementUtils.flatten(facetElements);
//finalElements = sparql.ElementUtils.flattenElements(finalElements);
//var facetConcept = new ns.Concept(finalElements, propertyVar);
var result = new Relation(finalElement, propertyVar, objectVar);
return result;
},
/**
* The returned relation holds a reference
* to the facet and facet value variables.
*
* Intended use is to first obtain the set of properties, then use this
* method, and constrain the concept based on the obtained properties.
*
* Examples:
* - ({?p a rdf:Propery . ?s ?p ?o }, ?p, ?o })
* - ({?s a ex:Foo . ?o ?p ?s }, ?p, ?o)
*
*
* @param path
* @param isInverse
* @param properties {jassa.rdf.Node}
* @param isNegated {boolean} True if the properties should be considered blacklisted
*/
createStepRelationsProperties: function(facetConfig, pathHead, properties, isNegated) {
var result = [];
var path = pathHead.getPath();
var isInverse = pathHead.isInverse();
var baseConcept = facetConfig.getBaseConcept();
var rootFacetNode = facetConfig.getRootFacetNode();
var constraintManager = facetConfig.getConstraintManager();
var propertyNames = properties.map(NodeUtils.getUri);
var facetNode = rootFacetNode.forPath(path);
// Set up the concept for fetching facets on constrained paths
// However make sure to filter them by the user supplied array of properties
var rawConstrainedSteps = constraintManager.getConstrainedSteps(path);
var constrainedSteps = rawConstrainedSteps.filter(function(step) {
var isSameDirection = step.isInverse() === isInverse;
if(!isSameDirection) {
return false;
}
var isContained = propertyNames.indexOf(step.getPropertyName()) >= 0;
var r = isNegated ? !isContained : isContained;
return r;
});
var excludePropertyNames = constrainedSteps.map(StepUtils.getPropertyName);
var includeProperties = [];
var excludeProperties = [];
properties.forEach(function(property) {
if(excludePropertyNames.indexOf(property.getUri()) >= 0) {
excludeProperties.push(property);
}
else {
includeProperties.push(property);
}
});
// The first part of the result is formed by the constrained steps
var constrainedStepRelations = this.createStepRelations(facetConfig, path, constrainedSteps);
result.push.apply(result, constrainedStepRelations);
// Set up the concept for fetching facets of all concepts that were NOT constrained
//var genericConcept = facetFacadeNode.createConcept(true);
var genericRelation = this.createRelationFacets(facetConfig, pathHead);
// Combine this with the user specified array of properties
var filterElement = this.createElementFilterBindVar(genericRelation.getSourceVar(), includeProperties, false);
if(filterElement != null) {
genericRelation = new Relation(
new ElementGroup([genericRelation.getElement(), filterElement]), // TODO flatten?
genericRelation.getSourceVar(),
genericRelation.getTargetVar());
}
// Important: If there are no properties to include, we can drop the genericConcept
if(includeProperties.length > 0 || isNegated) {
var genericStepRelation = new StepRelation(null, genericRelation);
result.push(genericStepRelation);
}
return result;
},
createFacetRelationIndex: function(facetConfig, pathHead) {
var stepRelations = FacetUtils.createStepRelationsProperties(facetConfig, pathHead, [], true);
// Retrieve the variable of the step relations
// Note: all relations are assumed to use the same source var
var sourceVar = stepRelations.length > 0 ? stepRelations[0].getRelation().getSourceVar() : null;
// index by step.property
var propertyToRelation = new HashMap();
var defaultRelation = null;
stepRelations.forEach(function(sr) {
var step = sr.getStep();
var relation = sr.getRelation();
var p = step ? NodeFactory.createUri(step.getPropertyName()) : null;
if(p) {
propertyToRelation.put(p, relation);
} else {
defaultRelation = relation;
}
});
var result = new FacetRelationIndex(sourceVar, defaultRelation, propertyToRelation);
return result;
},
createElementFilterBindVar: function(v, nodes, isNegated) {
var result = null;
if(nodes.length > 0) {
var expr = new E_OneOf(new ExprVar(v), nodes);
if(isNegated) {
expr = new E_LogicalNot(expr);
}
result = new ElementFilter(expr);
}
return result;
},
createStepRelations: function(facetConfig, path, constrainedSteps) {
var self = this;
var result = constrainedSteps.map(function(step) {
var r = self.createStepRelation(facetConfig, path, step);
return r;
});
return result;
},
createStepRelation: function(facetConfig, path, step) {
var propertyName = step.getPropertyName();
var property = NodeFactory.createUri(propertyName);
var pathHead = new PathHead(path, step.isInverse());
var targetConcept = this.createRelationFacets(facetConfig, pathHead, property);
var result = new StepRelation(step, targetConcept);
return result;
},
};
module.exports = FacetUtils;