1 /* 2 * jQuery RDF Rules @VERSION 3 * 4 * Copyright (c) 2008 Jeni Tennison 5 * Licensed under the MIT (MIT-LICENSE.txt) 6 * 7 * Depends: 8 * jquery.uri.js 9 * jquery.xmlns.js 10 * jquery.datatype.js 11 * jquery.curie.js 12 * jquery.rdf.js 13 */ 14 /** 15 * @fileOverview jQuery RDF Rules 16 * @author <a href="mailto:jeni@jenitennison.com">Jeni Tennison</a> 17 * @copyright (c) 2008,2009 Jeni Tennison 18 * @license MIT license (MIT-LICENSE.txt) 19 * @version 1.0 20 */ 21 /** 22 * @exports $ as jQuery 23 */ 24 /** 25 * @ignore 26 */ 27 (function ($) { 28 29 var 30 blankNodeNum = 1; 31 32 /** 33 * <p>Creates a new jQuery.rdf.ruleset object. This should be invoked as a method rather than constructed using new.</p> 34 * @class A jQuery.rdf.ruleset object represents a set of {@link jQuery.rdf.rule}s that can be run over a databank. 35 * @param {jQuery.rdf.rule[]} [rules=[]] An array of rules with which the ruleset is initialised. 36 * @param {Object} [options] Initialisation options for the ruleset. 37 * @param {Object} [options.namespaces] An object representing a set of namespace bindings which are stored and used whenever a CURIE is used within a rule. 38 * @param {String|jQuery.uri} [options.base] The base URI used to interpret any relative URIs used within the rules. 39 * @returns {jQuery.rdf.ruleset} 40 * @example rules = jQuery.rdf.ruleset(); 41 * @see jQuery.rdf.rule 42 */ 43 $.rdf.ruleset = function (rules, options) { 44 return new $.rdf.ruleset.fn.init(rules, options); 45 }; 46 47 $.rdf.ruleset.fn = $.rdf.ruleset.prototype = { 48 init: function (rules, options) { 49 var i, 50 opts = $.extend({}, $.rdf.ruleset.defaults, options); 51 rules = rules || []; 52 this.baseURI = opts.base; 53 this.namespaces = $.extend({}, opts.namespaces); 54 this.rules = []; 55 for (i = 0; i < rules.length; i += 1) { 56 this.add.apply(this, rules[i]); 57 } 58 return this; 59 }, 60 61 /** 62 * Sets or returns the base URI of the {@link jQuery.rdf.ruleset}. 63 * @param {String|jQuery.uri} [base] 64 * @returns A {@link jQuery.uri} if no base URI is specified, otherwise returns this {@link jQuery.rdf.ruleset} object. 65 * @example 66 * rules = $.rdf.ruleset() 67 * .base('http://www.example.org/'); 68 */ 69 base: function (uri) { 70 if (uri === undefined) { 71 return this.baseURI; 72 } else { 73 this.baseURI = uri; 74 return this; 75 } 76 }, 77 78 /** 79 * Sets or returns a namespace binding on the {@link jQuery.rdf.ruleset}. 80 * @param {String} [prefix] 81 * @param {String} [namespace] 82 * @returns {Object|jQuery.uri|jQuery.rdf} If no prefix or namespace is specified, returns an object providing all namespace bindings on the {@link jQuery.rdf.ruleset}. If a prefix is specified without a namespace, returns the {@link jQuery.uri} associated with that prefix. Otherwise returns this {@link jQuery.rdf} object after setting the namespace binding. 83 */ 84 prefix: function (prefix, uri) { 85 if (prefix === undefined) { 86 return this.namespaces; 87 } else if (uri === undefined) { 88 return this.namespaces[prefix]; 89 } else { 90 this.namespaces[prefix] = uri; 91 return this; 92 } 93 }, 94 95 /** 96 * Returns the number of rules in this ruleset. 97 * @returns {Integer} 98 */ 99 size: function () { 100 return this.rules.length; 101 }, 102 103 /** 104 * Adds a rule or set of rules to this ruleset. 105 * @param {String|Array|Function|jQuery.rdf.pattern|jQuery.rdf.rule|jQuery.rdf.ruleset} lhs A {@link jQuery.rdf.rule} will be added directly. If a {@link jQuery.rdf.ruleset} is provided then all its rules will be added to this one. Otherwise, specifies the left hand side of the rule to be added, as in {@link jQuery.rdf.rule}. 106 * @param {String|Function|jQuery.rdf.pattern} rhs The right hand side of the rule to be added. 107 * @returns {jQuery.rdf.ruleset} Returns this {@link jQuery.rdf.ruleset} 108 * @see jQuery.rdf.rule 109 * @example 110 * rules = $.rdf.ruleset() 111 * .prefix('foaf', ns.foaf) 112 * .add('?person a foaf:Person', '?person a foaf:Agent'); 113 */ 114 add: function (lhs, rhs) { 115 var rule; 116 if (rhs === undefined && lhs.rules) { 117 this.rules = this.rules.concat(lhs.rules); 118 } else { 119 if (rhs === undefined && lhs.lhs) { 120 rule = lhs; 121 } else { 122 rule = $.rdf.rule(lhs, rhs, { namespaces: this.prefix(), base: this.base() }); 123 } 124 if ($.inArray(rule, this.rules) === -1) { 125 this.rules.push(rule); 126 } 127 } 128 return this; 129 }, 130 131 /** 132 * Runs the rules held in this ruleset on the data passed as the first argument. 133 * @param {jQuery.rdf.databank} data A databank containing data to be reasoned over and added to. 134 * @param {Object} [options] 135 * @param {Integer} [options.limit=50] The rules in this ruleset are generally run over the {@link jQuery.rdf.databank} until it stops growing. In some situations, notably when creating blank nodes, this can lead to an infinite loop. The limit option indicates the maximum number of times the ruleset will be run before halting. 136 * @returns {jQuery.rdf.ruleset} Returns this ruleset. 137 * @example 138 * rules = $.rdf.ruleset() 139 * .prefix('foaf', ns.foaf) 140 * .add('?person a foaf:Person', '?person a foaf:Agent') 141 * .run(data); 142 * @see jQuery.rdf#reason 143 * @see jQuery.rdf.databank#reason 144 */ 145 run: function (data, options) { 146 var i, r, ntriples, 147 opts = $.extend({ limit: 50 }, options), 148 limit = opts.limit; 149 do { 150 ntriples = data.size(); 151 for (i = 0; i < this.rules.length; i += 1) { 152 r = this.rules[i]; 153 r.run(data); 154 } 155 limit -= 1; 156 } while (data.size() > ntriples && limit > 0); 157 return this; 158 } 159 }; 160 161 $.rdf.ruleset.fn.init.prototype = $.rdf.ruleset.fn; 162 163 $.rdf.ruleset.defaults = { 164 base: $.uri.base(), 165 namespaces: {} 166 }; 167 168 /* Rules */ 169 170 /** 171 * <p>Creates a new jQuery.rdf.rule object. This should be invoked as a method rather than constructed using new.</p> 172 * @class A jQuery.rdf.rule object represents a rule that can be run over a {@link jQuery.rdf.databank}. 173 * @param {Object[]} lhs The left-hand side of the rule. This can be an array containing multiple conditions, or a single condition on its own. Each condition is one of: 174 * <ul> 175 * <li>A {@link jQuery.rdf.pattern} or a string which is interpreted as a {@link jQuery.rdf.pattern}, which is used to match triples as with the {@link jQuery.rdf#where} method.</li> 176 * <li>A function which must return true for the rule to be satisfied. The arguments for the function are as described in the documentation for {@link jQuery.rdf#filter}.</li> 177 * <li>An array of two items: a variable name and either a regular expression or a value that it matches against (as used in the two arguments to {@link jQuery.rdf#filter}).</li> 178 * </ul> 179 * @param {Function|String[]} rhs The right-hand side of the rule. This can be an array of strings which are interpreted as patterns and used to create new triples when the rule is fired. If the patterns contain references to blank nodes, new blank nodes are created each time the rule is fired. Alternatively, it can be a function which is executed when the rule is fired. The function needs to have the same signature as that used for {@link jQuery.rdf#map}. 180 * @param {Object} [options] Initialisation options for the rules. 181 * @param {Object} [options.namespaces] An object representing a set of namespace bindings which are stored and used whenever a CURIE is used within the left or right-hand sides of the rule. 182 * @param {String|jQuery.uri} [options.base] The base URI used to interpret any relative URIs used within the rule. 183 * @returns {jQuery.rdf.rule} 184 * @example $.rdf.rule('?person a foaf:Person', '?person a foaf:Agent', { namespaces: ns }); 185 * @example 186 * var rule = $.rdf.rule( 187 * ['?person a vcard:VCard', 188 * '?person vcard:fn ?name'], 189 * ['?person a foaf:Person', 190 * '?person foaf:name ?name'], 191 * { namespaces: ns } 192 * ); 193 * @example 194 * var rule = $.rdf.rule( 195 * ['?person a foaf:Person', 196 * '?person foaf:firstName ?fn'], 197 * ['?person a vcard:VCard', 198 * '?person vcard:n _:name', 199 * '_:name a vcard:Name', 200 * '_:name vcard:given-name ?fn'], 201 * { namespaces: ns } 202 * ); 203 * @example 204 * var rule = $.rdf.rule( 205 * ['?person foaf:name ?name', 206 * ['name', /^J.+/]], 207 * function () { name = this.name }, 208 * { namespaces: ns }); 209 * @see jQuery.rdf.rule 210 */ 211 $.rdf.rule = function (lhs, rhs, options) { 212 return new $.rdf.rule.fn.init(lhs, rhs, options); 213 }; 214 215 $.rdf.rule.fn = $.rdf.rule.prototype = { 216 init: function (lhs, rhs, options) { 217 var opts = $.extend({}, $.rdf.rule.defaults, options), 218 lhsWildcards = [], rhsBlanks = false; 219 if (typeof lhs === 'string') { 220 lhs = [lhs]; 221 } 222 if (typeof rhs === 'string') { 223 rhs = [rhs]; 224 } 225 this.lhs = $.map(lhs, function (p) { 226 if ($.isArray(p)) { 227 return [p]; 228 } else if ($.isFunction(p)) { 229 return p; 230 } else { 231 p = $.rdf.pattern(p, opts); 232 if (typeof p.subject === 'string') { 233 lhsWildcards.push(p.subject); 234 } 235 if (typeof p.property === 'string') { 236 lhsWildcards.push(p.property); 237 } 238 if (typeof p.object === 'string') { 239 lhsWildcards.push(p.object); 240 } 241 return p; 242 } 243 }); 244 lhsWildcards = $.unique(lhsWildcards); 245 if ($.isFunction(rhs)) { 246 this.rhs = rhs; 247 } else { 248 this.rhs = $.map(rhs, function (p) { 249 p = $.rdf.pattern(p, opts); 250 if ((typeof p.subject === 'string' && $.inArray(p.subject, lhsWildcards) === -1) || 251 (typeof p.property === 'string' && $.inArray(p.property, lhsWildcards) === -1) || 252 (typeof p.object === 'string' && $.inArray(p.object, lhsWildcards) === -1)) { 253 throw "Bad Rule: Right-hand side of the rule contains a reference to an unbound wildcard"; 254 } else if (p.subject.type === 'bnode' || p.property.type === 'bnode' || p.object.type === 'bnode') { 255 rhsBlanks = true; 256 } 257 return p; 258 }); 259 } 260 this.rhsBlanks = rhsBlanks; 261 this.cache = {}; 262 return this; 263 }, 264 265 /** 266 * Runs the rule on the data passed as the first argument. 267 * @param {jQuery.rdf.databank} data A databank containing data to be reasoned over and added to. 268 * @param {Object} [options] 269 * @param {Integer} [options.limit=50] The rule isArray generally run over the {@link jQuery.rdf.databank} until it stops growing. In some situations, notably when creating blank nodes, this can lead to an infinite loop. The limit option indicates the maximum number of times the rule will be run before halting. 270 * @returns {jQuery.rdf.rule} Returns this rule. 271 * @example 272 * $.rdf.rule('?person a foaf:Person', '?person a foaf:Agent', { namespaces: ns }) 273 * .run(data); 274 * @see jQuery.rdf.ruleset#run 275 * @see jQuery.rdf#reason 276 * @see jQuery.rdf.databank#reason 277 */ 278 run: function (data, options) { 279 var query = $.rdf({ databank: data }), 280 condition, 281 opts = $.extend({ limit: 50 }, options), limit = opts.limit, 282 ntriples, 283 i, j, pattern, s, p, o, q, 284 blanks = this.rhsBlanks, 285 cache, sources, triples, add; 286 if (this.cache[data.id] === undefined) { 287 this.cache[data.id] = {}; 288 } 289 for (i = 0; i < this.lhs.length; i += 1) { 290 condition = this.lhs[i]; 291 if ($.isArray(condition)) { 292 query = query.filter.apply(query, condition); 293 } else if ($.isFunction(condition)) { 294 query = query.filter.call(query, condition); 295 } else { 296 query = query.where(this.lhs[i]); 297 } 298 } 299 do { 300 ntriples = query.length; 301 sources = query.sources(); 302 for (i = 0; i < ntriples; i += 1) { 303 triples = sources[i]; 304 add = true; 305 cache = this.cache[data.id]; 306 for (j = 0; j < triples.length; j += 1) { 307 if (cache[triples[j]] === undefined) { 308 cache[triples[j]] = {}; 309 } else if (j === triples.length - 1) { 310 add = false; 311 } 312 cache = cache[triples[j]]; 313 } 314 if (add) { 315 q = query.eq(i); 316 if (blanks) { 317 for (j = 0; j < this.rhs.length; j += 1) { 318 pattern = this.rhs[j]; 319 s = pattern.subject; 320 p = pattern.property; 321 o = pattern.object; 322 if (s.type === 'bnode') { 323 s = $.rdf.blank('' + s + blankNodeNum); 324 } 325 if (p.type === 'bnode') { 326 p = $.rdf.blank('' + p + blankNodeNum); 327 } 328 if (o.type === 'bnode') { 329 o = $.rdf.blank('' + o + blankNodeNum); 330 } 331 pattern = $.rdf.pattern(s, p, o); 332 q.add(pattern); 333 } 334 blankNodeNum += 1; 335 } else if ($.isFunction(this.rhs)) { 336 q.map(this.rhs); 337 } else { 338 for (j = 0; j < this.rhs.length; j += 1) { 339 q.add(this.rhs[j]); 340 } 341 } 342 } 343 } 344 limit -= 1; 345 } while (query.length > ntriples && limit > 0); 346 return this; 347 } 348 }; 349 350 $.rdf.rule.fn.init.prototype = $.rdf.rule.fn; 351 352 $.rdf.rule.defaults = { 353 base: $.uri.base(), 354 namespaces: {} 355 }; 356 357 $.extend($.rdf.databank.fn, { 358 /** 359 * @methodOf jQuery.rdf.databank# 360 * @name jQuery.rdf.databank#reason 361 * @description Reasons over this databank using the {@link jQuery.rdf.rule} or {@link jQuery.rdf.ruleset} given as the first argument. 362 * @param {jQuery.rdf.rule|jQuery.rdf.ruleset} rules The rules to run over the databank. 363 * @param {Object} [options] 364 * @param {Integer} [options.limit=50] The rules in this ruleset are generally run over the {@link jQuery.rdf.databank} until it stops growing. In some situations, notably when creating blank nodes, this can lead to an infinite loop. The limit option indicates the maximum number of times the ruleset will be run before halting. 365 * @returns {jQuery.rdf.databank} The original {@link jQuery.rdf.databank}, although it may now contain more triples. 366 * @see jQuery.rdf.ruleset#run 367 * @see jQuery.rdf.rule#run 368 */ 369 reason: function (rule, options) { 370 rule.run(this, options); 371 return this; 372 } 373 }); 374 375 $.extend($.rdf.fn, { 376 /** 377 * @methodOf jQuery.rdf# 378 * @name jQuery.rdf#reason 379 * @description Reasons over the {@link jQuery.rdf#databank} associated with this {@link jQuery.rdf} object using the {@link jQuery.rdf.rule} or {@link jQuery.rdf.ruleset} given as the first argument. 380 * @param {jQuery.rdf.rule|jQuery.rdf.ruleset} rules The rules to run over the databank. 381 * @param {Object} [options] 382 * @param {Integer} [options.limit=50] The rules in this ruleset are generally run over the {@link jQuery.rdf.databank} until it stops growing. In some situations, notably when creating blank nodes, this can lead to an infinite loop. The limit option indicates the maximum number of times the ruleset will be run before halting. 383 * @returns {jQuery.rdf} The original {@link jQuery.rdf} object, although it may now contain more matches because of the new triples added to its underlying databank. 384 * @see jQuery.rdf.ruleset#run 385 * @see jQuery.rdf.rule#run 386 */ 387 reason: function (rule, options) { 388 rule.run(this.databank, options); 389 return this; 390 } 391 }); 392 393 })(jQuery); 394