1 /**
  2  * Toby Inkster's Utility Functions for SPARQL
  3  *
  4  * Copyright (c) 2011 Toby Inkster <http://tobyinkster.co.uk/>.
  5  * Portions copyright Alexandru Marasteanu <http://alexei.417.ro/>,
  6  * and Robert Kieffer.
  7  *
  8  * This document defines a set of functions suitable for using
  9  * within SPARQL implementations. It provides reference implementations
 10  * in ECMAScript, though SPARQL engines supporting this set of
 11  * functions are expected to provide native implementations for them.
 12  *
 13  * Each function defined in this document can be identified with its
 14  * URI <http://buzzword.org.uk/2011/functions/util#xxxx> where "xxxx"
 15  * is the function name.
 16  *
 17  * The initial argument passed to each function is assumed to be an
 18  * object providing the RDFEnvironment interface as defined by the
 19  * draft W3C RDF API.
 20  *
 21  * http://www.w3.org/2010/02/rdfa/sources/rdf-api/#idl-def-RDFEnvironment
 22  *
 23  * Further arguments are taken to provide the RDFNode interface.
 24  * 
 25  * http://www.w3.org/2010/02/rdfa/sources/rdf-api/#idl-def-RDFNode
 26  *
 27  * This program is free software; you can redistribute it and/or modify it under
 28  * the terms of the GNU General Public License as published by the Free Software
 29  * Foundation; either version 2 of the License, or (at your option) any later
 30  * version.
 31  *
 32  * This program is distributed in the hope that it will be useful, but WITHOUT
 33  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 34  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 35  * details.
 36  *
 37  * You should have received a copy of the GNU General Public License along with
 38  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 39  * Place, Suite 330, Boston, MA 02111-1307 USA
 40  */
 41 
 42 /**
 43  * Convert a literal to majuscule
 44  * @param {RDFEnvironment} $environment The environment
 45  * @param {Literal} $node The literal to be uppercased
 46  * @returns An uppercase literal
 47  * @type Literal
 48  */
 49 function uc ($environment, $node)
 50 {
 51 	if ($node.interfaceName != "Literal")
 52 		throw new Error("Argument be a literal.");
 53 	
 54 	return $environment.createLiteral(
 55 		$node.value.toUpperCase(),
 56 		$node.language,
 57 		$node.datatype
 58 		);
 59 }
 60 
 61 /**
 62  * Convert a literal to minuscule
 63  * @param {RDFEnvironment} $environment The environment
 64  * @param {Literal} $node The literal to be lowercased
 65  * @returns A lowercase literal
 66  * @type Literal
 67  */
 68 function lc ($environment, $node)
 69 {
 70 	if ($node.interfaceName != "Literal")
 71 		throw new Error("Argument be a literal.");
 72 	
 73 	return $environment.createLiteral(
 74 		$node.value.toLowerCase(),
 75 		$node.language,
 76 		$node.datatype
 77 		);
 78 }
 79 
 80 /**
 81  * Strip leading whitespace
 82  * @param {RDFEnvironment} $environment The environment
 83  * @param {Literal} $node The literal to be trimmed
 84  * @returns A literal
 85  * @type Literal
 86  */
 87 function ltrim ($environment, $node)
 88 {
 89 	if ($node.interfaceName != "Literal")
 90 		throw new Error("Argument be a literal.");
 91 	
 92 	return $environment.createLiteral(
 93 		$node.value.replace(/^\s+/,""),
 94 		$node.language,
 95 		$node.datatype
 96 		);
 97 }
 98 
 99 /**
100  * Strip trailing whitespace
101  * @param {RDFEnvironment} $environment The environment
102  * @param {Literal} $node The literal to be trimmed
103  * @returns A literal
104  * @type Literal
105  */
106 function rtrim ($environment, $node)
107 {
108 	if ($node.interfaceName != "Literal")
109 		throw new Error("Argument be a literal.");
110 	
111 	return $environment.createLiteral(
112 		$node.value.replace(/\s+$/,""),
113 		$node.language,
114 		$node.datatype
115 		);
116 }
117 
118 /**
119  * Strip leading and trailing whitespace
120  * @param {RDFEnvironment} $environment The environment
121  * @param {Literal} $node The literal to be trimmed
122  * @returns A literal
123  * @type Literal
124  */
125 function trim ($environment, $node)
126 {
127 	if ($node.interfaceName != "Literal")
128 		throw new Error("Argument be a literal.");
129 	
130 	return $environment.createLiteral(
131 		$node.value.replace(/^\s+|\s+$/g,""),
132 		$node.language,
133 		$node.datatype
134 		);
135 }
136 
137 /**
138  * Interpolate values into a string pattern
139  * @param {RDFEnvironment} $environment The environment
140  * @param {Literal} $pattern The pattern into which values should be interpolated
141  * @returns A literal
142  * @type Literal
143  */
144 function sprintf ($environment, $pattern)
145 {
146 	if ($pattern.interfaceName != "Literal")
147 		throw new Error("Pattern be a literal.");
148 	
149 	var str_repeat  = function (i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }
150 	var _sprintf    = function (f, params)
151 	{
152 		var i = 0, a, o = [], m, p, c, x;
153 		while (f)
154 		{
155 			if (m = /^[^\x25]+/.exec(f))
156 				o.push(m[0]);
157 			else if (m = /^\x25{2}/.exec(f))
158 				o.push('%');
159 			else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f))
160 			{
161 				if (((a = params[m[1] || i++]) == null) || (a == undefined))
162 					throw new Error("Too few arguments.");
163 				if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
164 					throw new Error("Expecting number but found " + typeof(a));
165 				switch (m[7])
166 				{
167 					case 'b': a = a.toString(2); break;
168 					case 'c': a = String.fromCharCode(a); break;
169 					case 'd': a = parseInt(a); break;
170 					case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
171 					case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
172 					case 'o': a = a.toString(8); break;
173 					case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
174 					case 'u': a = Math.abs(a); break;
175 					case 'x': a = a.toString(16); break;
176 					case 'X': a = a.toString(16).toUpperCase(); break;
177 				}
178 				a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
179 				c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
180 				x = m[5] - String(a).length;
181 				p = m[5] ? str_repeat(c, x) : '';
182 				o.push(m[4] ? a + p : p + a);
183 			}
184 			else throw ("Huh ?!");
185 			f = f.substring(m[0].length);
186 		}
187 		return o.join('');
188 	}
189 	
190 	var argList = new Array();
191 	for (var i=2; arguments[i] != undefined; i++)
192 	{
193 		argList.push(arguments[i].value);
194 	}
195 	
196 	return $environment.createLiteral(
197 		_sprintf($pattern.value, argList),
198 		$pattern.language,
199 		$pattern.datatype
200 		);
201 }
202 
203 /**
204  * Generate a UUID
205  * @param {RDFEnvironment} $environment The environment
206  * @returns A literal
207  * @type Literal
208  */
209 function uuid ($environment)
210 {
211 	// via http://www.broofa.com/Tools/Math.uuid.js
212 	var _uuidCompact = function() {
213 		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
214 			var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
215 			return v.toString(16);
216 		}).toUpperCase();
217 	};
218 	
219 	return $environment.createLiteral(_uuidCompact(), null, null);
220 }
221 
222 /**
223  * Generate a UUID, and format as a URI
224  * @param {RDFEnvironment} $environment The environment
225  * @returns A named node (URI)
226  * @type NamedNode
227  */
228 function uuid_uri ($environment)
229 {
230 	return $environment.createNamedNode("urn:uuid:" + uuid($environment).value);
231 }
232 
233 /**
234  * Generate an OID
235  * @param {RDFEnvironment} $environment The environment
236  * @returns A literal
237  * @type Literal
238  */
239 function oid ($environment)
240 {
241 	var $oid  = "1.3.6.1.4.1.33926.9";
242 	var $uuid = uuid($environment).value.replace(/\-/g, "");
243 	
244 	while ($uuid.length)
245 	{
246 		var $chunk = $uuid.substr(0, 4);
247 		$uuid = $uuid.substr(4);
248 		$oid += "." + parseInt($chunk, 16);
249 	}
250 	
251 	return $environment.createLiteral($oid, null, null);
252 }
253 
254 /**
255  * Generate an OID, and format as a URI
256  * @param {RDFEnvironment} $environment The environment
257  * @returns A named node (URI)
258  * @type NamedNode
259  */
260 function oid_uri ($environment)
261 {
262 	return $environment.createNamedNode("urn:oid:" + oid($environment).value);
263 }
264 
265 /**
266  * Generate a random number
267  * @param {RDFEnvironment} $environment The environment
268  * @param {Literal} $upper The upper bound
269  * @param {Literal} $lower The lower bound (optional, defaults to 0)
270  * @returns A literal with same datatype as $upper.
271  * @type Literal
272  */
273 function rand ($environment, $upper, $lower)
274 {
275 	if ($lower == undefined)
276 		$lower = $environment.createLiteral(0, null, $upper.datatype);
277 
278 	if ($lower.interfaceName != "Literal")
279 		throw new Error("Lower bound be a literal.");
280 	
281 	if ($upper.interfaceName != "Literal")
282 		throw new Error("Upper bound be a literal.");
283 	
284 	if ($lower.datatype.value != $upper.datatype.value)
285 		throw new Error("Lower and upper bound must have same datatype.");
286 	
287 	if ($upper.interfaceName != "Literal"
288 	|| !$upper.datatype.value.match(/^http:\/\/www.w3.org\/2001\/XMLSchema#(decimal|float|double|integer|non(Positive|Negative)Integer|(positive|negative)Integer|long|int|short|byte|unsigned(Long|Int|Short|Byte))$/))
289 		throw new Error("Upper bound must have numeric datatype.");
290 	
291 	var $rand = $lower.value + (Math.random() * ($upper.value - $lower.value));
292 	
293 	if ($upper.datatype.value.match(/^http:\/\/www.w3.org\/2001\/XMLSchema#(integer|non(Positive|Negative)Integer|(positive|negative)Integer|long|int|short|byte|unsigned(Long|Int|Short|Byte))$/))
294 		$rand = Math.floor($rand);
295 	
296 	return $environment.createLiteral($rand, null, $upper.datatype);
297 }
298 
299 /**
300  * Split an IRI on "#" and return the first half
301  * @param {RDFEnvironment} $environment The environment
302  * @param {NamedNode} $node The URI to split
303  * @returns The first part of the URI
304  * @type NamedNode
305  */
306 function defragment ($environment, $node)
307 {
308 	if ($node.interfaceName != "NamedNode")
309 		throw new Error("Argument be a named node.");
310 	
311 	var $parts = $node.value.split("#");
312 	
313 	return $environment.createNamedNode($parts[0]);
314 }
315 
316 /**
317  * Replace all occurances of a given string within another.
318  * @param {RDFEnvironment} $environment The environment
319  * @param {Literal} $needle The string to search for
320  * @param {Literal} $replacement The string with which to replace matches
321  * @param {Literal} $haystack The literal to search within
322  * @returns The $haystack literal with all instances of $needle replaced
323  * @type Literal
324  */
325 function str_replace ($environment, $needle, $replacement, $haystack)
326 {
327 	throw new Error("Not implemented yet.");
328 }
329 
330 /**
331  * Replace all matches for a given expression within a string.
332  * @param {RDFEnvironment} $environment The environment
333  * @param {Literal} $needle The regular expression to search for
334  * @param {Literal} $replacement The string with which to replace matches
335  * @param {Literal} $haystack The literal to search within
336  * @param {Literal} $modes Defaults to "g"
337  * @returns The $haystack literal with all substrings matching $needle replaced
338  * @type Literal
339  */
340 function preg_replace ($environment, $needle, $replacement, $haystack, $modes)
341 {
342 	throw new Error("Not implemented yet.");
343 }
344 
345 /**
346  * Names blank nodes.
347  * 
348  * Given a node, if it's named or a literal, returns as is. If blank,
349  * returns a URI for the node. Idempotent for any particular RDFEnvironment.
350  * That is, within an execution, given the same blank node multiple
351  * times will always return the same URI.
352  *
353  * @param {RDFEnvironment} $environment The environment
354  * @param {RDFNode} $node The node to name
355  * @param {Literal} $scheme "OID" or "UUID" (default). Optional.
356  * @returns A named node or literal
357  * @type RDFNode
358  */
359 function skolem ($environment, $node, $scheme)
360 {
361 	throw new Error("Not implemented yet.");
362 }
363 
364 /**
365  * Searches an XMLLiteral for an XPath.
366  * @param {RDFEnvironment} $environment The environment
367  * @param {Literal} $xpath The path to search for
368  * @param {Literal} $xmlliteral The literal to search within
369  * @param {Literal} $index If multiple matches found, which to return. Defaults to 0.
370  * @returns An XMLLiteral or plain literal
371  * @type Literal
372  */
373 function find_xpath ($environment, $xpath, $xmlliteral, $index)
374 {
375 	throw new Error("Not implemented yet.");
376 }
377