PageSourceSearch

https://jonaskress.github.io/wikibase/queryService/ui/queryHelper/SparqlQuery.js

js jonaskress.github.io collected 2026-10-03 09:25:42 UTC 9,482 bytes, 444 lines download raw bytes

1var wikibase = wikibase || {};
2wikibase.queryService = wikibase.queryService || {};
3wikibase.queryService.ui = wikibase.queryService.ui || {};
4wikibase.queryService.ui.queryHelper = wikibase.queryService.ui.queryHelper || {};
5
6wikibase.queryService.ui.queryHelper.SparqlQuery = ( function( $, wikibase, sparqljs ) {
7	'use strict';
8
9	/**
10	 * A SPARQL query representation
11	 *
12	 * @class wikibase.queryService.ui.queryHelper.SparqlQuery
13	 * @license GNU GPL v2+
14	 *
15	 * @author Jonas Kress
16	 * @constructor
17	 * @param {Object} query
18	 */
19	function SELF( query ) {
20		this._query = query;
21	}
22
23	/**
24	 * @property {Object}
25	 * @private
26	 */
27	SELF.prototype._query = null;
28
29	/**
30	 * @property {Array}
31	 * @private
32	 */
33	SELF.prototype._queryComments = null;
34
35	/**
36	 * Set the SPARQL query string
37	 *
38	 * @param {string} query SPARQL query string
39	 * @param {Object} prefixes
40	 */
41	SELF.prototype.parse = function( query, prefixes ) {
42		var parser = new sparqljs.Parser( prefixes ),
43			queryComments = [];
44		this._query = parser.parse( query );
45		$.each( query.split( '\n' ), function( index, line ) {
46			if ( line.indexOf( '#' ) === 0 ) {
47				queryComments.push( line );
48			}
49		} );
50		this._queryComments = queryComments;
51	};
52
53	/**
54	 * Get the SPARQL query string
55	 *
56	 * @return {string|null}
57	 */
58	SELF.prototype.getQueryString = function() {
59		try {
60			var sparql = new sparqljs.Generator().stringify( this._query ),
61				comments = this._queryComments.join( '\n' ).trim();
62
63			if ( comments !== '' ) {
64				return comments + '\n' + sparql;
65			} else {
66				return sparql;
67			}
68		} catch ( e ) {
69			return null;
70		}
71	};
72
73	/**
74	 * @return {number|null}
75	 */
76	SELF.prototype.getLimit = function() {
77		return this._query.limit || null;
78	};
79
80	/**
81	 * @param {number|null} limit
82	 * @return {wikibase.queryService.ui.queryHelper.SparqlQuery}
83	 */
84	SELF.prototype.setLimit = function( limit ) {
85		if ( !limit ) {
86			delete this._query.limit;
87		} else {
88			this._query.limit = limit;
89		}
90
91		return this;
92	};
93
94	/**
95	 * Check whether a variable is used in the SELECT clause of query
96	 *
97	 * @param {string} name of variable e.g. ?foo
98	 */
99	SELF.prototype.hasVariable = function( name ) {
100		if ( this._query.variables.length === 0 ) {
101			return false;
102		}
103
104		return this._query.variables.indexOf( name ) >= 0;
105	};
106
107	/**
108	 * Check whether query uses wildcard SELECT *
109	 */
110	SELF.prototype.isWildcardQuery = function() {
111		if ( this._query.variables && this._query.variables[0] === '*' ) {
112			return true;
113		}
114
115		return false;
116	};
117
118	/**
119	 * Convert wildcard query to use variables
120	 *
121	 * @deprecated workaround for T171194
122	 */
123	SELF.prototype.convertWildcardQueryToUseVariables = function() {
124
125		if ( !this.isWildcardQuery() ) {
126			return;
127		}
128
129		var variables = this.getQueryString().match( /(\?\w+)/g );
130		this._query.variables = $.unique( variables ) || '*';
131	};
132
133	/**
134	 * Add a variable to the query SELECT
135	 *
136	 * @param {string} name
137	 * @return {wikibase.queryService.ui.queryHelper.SparqlQuery}
138	 */
139	SELF.prototype.addVariable = function( name ) {
140		if ( !name || !name.startsWith( '?' ) ) {
141			return this;
142		}
143
144		if ( this._query.variables.indexOf( name ) >= 0 ) {
145			return this;
146		}
147
148		if ( this.isWildcardQuery() ) {
149			return this;
150		}
151
152		this._query.variables.push( name );
153
154		return this;
155	};
156
157	/**
158	 * Remove a variable from the query SELECT
159	 *
160	 * @param {string} name
161	 */
162	SELF.prototype.removeVariable = function( name ) {
163		if ( !name.startsWith( '?' ) ) {
164			return;
165		}
166
167		if ( this.isWildcardQuery() ) {
168			return;
169		}
170
171		var index = this._query.variables.indexOf( name );
172		if ( index >= 0 ) {
173			this._query.variables.splice( index, 1 );
174		}
175
176		if ( this._query.variables.length === 0 ) {
177			this._query.variables.push( '*' );
178		}
179	};
180
181	/**
182	 * Removes unused variables from the query SELECT
183	 * Disclaimer: may remove too much
184	 *
185	 * @param {string[]} [variables] cleanup only variables in this list
186	 */
187	SELF.prototype.cleanupVariables = function( variables ) {
188		var self = this,
189			usedVariables = this.getTripleVariables();
190
191		// TODO check sub queries
192		var toRemove = this._query.variables.filter( function ( variable ) {
193			if ( variables && variables.indexOf( variable ) === -1 ) {
194				return false;
195			}
196
197			if ( usedVariables.indexOf( variable ) === -1 ) {
198				return true;
199			}
200
201			return false;
202		} );
203
204		toRemove.map( function ( v ) {
205			self.removeVariable( v );
206		} );
207	};
208
209	/**
210	 * Get triples defined in this query
211	 *
212	 * @return {Object}
213	 */
214	SELF.prototype.getTriples = function( node, isOptional ) {
215		var triples = [];
216		if ( !node ) {
217			node = this._query.where;
218		}
219		if ( !isOptional ) {
220			isOptional = false;
221		}
222
223		var self = this;
224		$.each( node, function( k, v ) {
225			if ( v.type && v.type === 'bgp' ) {
226				triples = triples.concat( self._createTriples( v.triples, isOptional ) );
227			}
228			if ( v.type && v.type === 'optional' ) {
229				triples = triples.concat( self.getTriples( v.patterns, true ) );
230			}
231			if ( v.type && v.type === 'union' ) {
232				triples = triples.concat( self.getTriples( v.patterns, false ) );
233			}
234		} );
235
236		return triples;
237	};
238
239	/**
240	 * Get bindings defined in this query
241	 *
242	 * @return {Object}
243	 */
244	SELF.prototype.getBindings = function() {
245		var bindings = {};
246
247		$.each( this._query.where, function( k, v ) {
248			if ( v.type && v.type === 'bind' ) {
249				bindings[ v.variable ] = v;
250			}
251		} );
252
253		return bindings;
254	};
255
256	/**
257	 * @private
258	 */
259	SELF.prototype._createTriples = function( triplesData, isOptional ) {
260		var self = this,
261			triples = [];
262
263		$.each( triplesData, function( i, triple ) {
264			triples.push( {
265				optional: isOptional,
266				query: self,
267				triple: triple,
268				remove: function() {
269					triplesData.splice( i, 1 );
270				}
271			} );
272		} );
273
274		return triples;
275	};
276
277	/**
278	 * Get triples defined in this query
279	 *
280	 * @return {wikibase.queryService.ui.queryHelper.SparqlQuery[]}
281	 */
282	SELF.prototype.getSubQueries = function() {
283		var queries = [];
284
285		$.each( this._query.where, function( k, v ) {
286			if ( v.queryType ) {
287				queries.push( new SELF( v ) );
288			}
289		} );
290
291		return queries;
292	};
293
294	/**
295	 * Add a triple to the query
296	 *
297	 * @param {string} subject
298	 * @param {string} predicate
299	 * @param {string} object
300	 * @param {boolean} isOptional
301	 */
302	SELF.prototype.addTriple = function( subject, predicate, object, isOptional ) {
303		var triple = {
304			type: 'bgp',
305			triples: [
306				{
307					subject: subject,
308					predicate: predicate,
309					object: object
310				}
311			]
312		};
313
314		if ( isOptional ) {
315			var optionalTriple = {
316				type: 'optional',
317				patterns: [
318					triple
319				]
320			};
321			this._query.where.push( optionalTriple );
322		} else {
323			this._query.where.push( triple );
324		}
325
326		return this._createTriples( triple.triples, isOptional )[0];
327	};
328
329	/**
330	 * Get all variables used in triples
331	 *
332	 * @return {string[]}
333	 */
334	SELF.prototype.getTripleVariables = function() {
335		var variables = {};
336
337		$.each( this.getTriples(), function( i, t ) {
338			if ( typeof t.triple.subject === 'string' && t.triple.subject.startsWith( '?' ) ) {
339				variables[t.triple.subject] = true;
340			}
341			if ( typeof t.triple.predicate === 'string'  && t.triple.predicate.startsWith( '?' ) ) {
342				variables[t.triple.predicate] = true;
343			}
344			if ( typeof t.triple.object === 'string' && t.triple.object.startsWith( '?' ) ) {
345				variables[t.triple.object] = true;
346			}
347
348		} );
349
350		return Object.keys( variables );
351	};
352
353	/**
354	 * Get variables that are bound to a certain value
355	 *
356	 * @return {string[]}
357	 */
358	SELF.prototype.getBoundVariables = function() {
359		var variables = {};
360
361		$.each( this.getTriples(), function( i, t ) {
362			if ( t.triple.subject.startsWith( '?' ) && !t.triple.object.startsWith( '?' ) ) {
363				variables[t.triple.subject] = true;
364			}
365
366			if ( !t.triple.subject.startsWith( '?' ) && t.triple.object.startsWith( '?' ) ) {
367				variables[t.triple.object] = true;
368			}
369		} );
370
371		return Object.keys( variables );
372	};
373
374	/**
375	 * Get services defined in query
376	 *
377	 * @return {object[]}
378	 */
379	SELF.prototype.getServices = function() {
380		var services = [];
381
382		$.each( this._query.where, function( i, node ) {
383			if ( node && node.type === 'service' ) {
384				services.push( node );
385			}
386
387		} );
388
389		return services;
390	};
391
392	/**
393	 * Remove a certain service from the query
394	 *
395	 * @param {string} serviceId of the service to be removed
396	 * @return {wikibase.queryService.ui.queryHelper.SparqlQuery}
397	 */
398	SELF.prototype.removeService = function( serviceId ) {
399		var self = this;
400
401		$.each( this._query.where, function ( i, node ) {
402			if ( node.type === 'service' && node.name === serviceId ) {
403				delete self._query.where[i];
404			}
405
406		} );
407
408		return this;
409	};
410
411	/**
412	 * Get the content of a content beginning with start.
413	 *
414	 * For example, on a query with '#foo=bar',
415	 * getCommentContent( 'foo=' ) will return 'bar'.
416	 *
417	 * @param {string} start The beginning of the comment, *without* the comment mark ('#').
418	 *
419	 * @return {?string}
420	 */
421	SELF.prototype.getCommentContent = function( start ) {
422		var i, comment;
423		for ( i = 0; i < this._queryComments.length; i++ ) {
424			comment = this._queryComments[ i ];
425			if ( comment.startsWith( '#' + start ) ) {
426				return comment.substring( 1 + start.length );
427			}
428		}
429		return null;
430	};
431
432	/**
433	 * Clone query
434	 *
435	 * @return {wikibase.queryService.ui.queryHelper.SparqlQuery}
436	 */
437	SELF.prototype.clone = function() {
438		var query = new SELF();
439		query.parse( this.getQueryString() );
440		return query;
441	};
442
443	return SELF;
444}( jQuery, wikibase, sparqljs ) );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.