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