Update to latest jsonld.js.
authorDave Longley <dlongley@digitalbazaar.com>
Mon, 18 Feb 2013 14:14:33 -0500
changeset 1301 16d7a4bc1309
parent 1300 6cbbbe3b892b
child 1302 f140b3b61344
Update to latest jsonld.js.
playground/jsonld.js
--- a/playground/jsonld.js	Mon Feb 18 10:19:35 2013 -0500
+++ b/playground/jsonld.js	Mon Feb 18 14:14:33 2013 -0500
@@ -56,7 +56,7 @@
  *          [graph] true to always output a top-level graph (default: false).
  *          [skipExpansion] true to assume the input is expanded and skip
  *            expansion, false not to, defaults to false.
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, compacted, ctx) called once the operation completes.
  */
 jsonld.compact = function(input, ctx) {
@@ -90,8 +90,8 @@
   if(!('skipExpansion' in options)) {
     options.skipExpansion = false;
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
 
   var expand = function(input, options, callback) {
@@ -221,7 +221,7 @@
  *            defaults to true.
  *          [keepFreeFloatingNodes] true to keep free-floating nodes,
  *            false not to, defaults to false.
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, expanded) called once the operation completes.
  */
 jsonld.expand = function(input) {
@@ -239,8 +239,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
   if(!('renameBlankNodes' in options)) {
     options.renameBlankNodes = true;
@@ -289,7 +289,7 @@
  * @param ctx the context to use to compact the flattened output, or null.
  * @param [options] the options to use:
  *          [base] the base IRI to use.
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, flattened) called once the operation completes.
  */
 jsonld.flatten = function(input, ctx, options, callback) {
@@ -303,8 +303,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
 
   // expand input
@@ -352,7 +352,7 @@
  *          [explicit] default @explicit flag (default: false).
  *          [omitDefault] default @omitDefault flag (default: false).
  *          [optimize] optimize when compacting (default: false).
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, framed) called once the operation completes.
  */
 jsonld.frame = function(input, frame) {
@@ -369,8 +369,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
   if(!('embed' in options)) {
     options.embed = true;
@@ -434,7 +434,7 @@
  * @param ctx the JSON-LD context to apply.
  * @param [options] the framing options.
  *          [base] the base IRI to use.
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, objectified) called once the operation completes.
  */
 jsonld.objectify = function(input, ctx) {
@@ -451,8 +451,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
 
   // expand input
@@ -567,7 +567,7 @@
  * @param [options] the options to use:
  *          [format] the format if output is a string:
  *            'application/nquads' for N-Quads.
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, normalized) called once the operation completes.
  */
 jsonld.normalize = function(input, callback) {
@@ -585,8 +585,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
 
   // expand input then do normalization
@@ -672,7 +672,7 @@
  *          [collate] true to output all statements at once (in an array
  *            or as a formatted string), false to output one statement at
  *            a time (default).
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, statement) called when a statement is output, with the
  *          last statement as null.
  */
@@ -691,8 +691,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
   if(!('collate' in options)) {
     options.collate = false;
@@ -756,7 +756,7 @@
 
     try {
       // output RDF statements
-      var namer = new UniqueNamer('_:t');
+      var namer = new UniqueNamer('_:b');
       new Processor().toRDF(expanded, namer, null, null, null, callback);
     }
     catch(ex) {
@@ -775,11 +775,11 @@
 };
 
 /**
- * The default URL client for external @context URLs.
- *
- * @param urlClient(url, callback(err, url, result)) the URL client to use.
+ * The default context loader for external @context URLs.
+ *
+ * @param loadContext(url, callback(err, url, result)) the context loader.
  */
-jsonld.urlClient = function(url, callback) {
+jsonld.loadContext = function(url, callback) {
   return callback(new JsonLdError(
     'Could not retrieve @context URL. URL derefencing not implemented.',
     'jsonld.ContextUrlError'), url);
@@ -860,33 +860,33 @@
 };
 
 /**
- * URL clients.
+ * Context loaders.
  */
-jsonld.urlClients = {};
+jsonld.contextLoaders = {};
 
 /**
- * The built-in jquery URL client.
+ * The built-in jquery context loader.
  *
  * @param $ the jquery instance to use.
  * @param options the options to use:
  *          secure: require all URLs to use HTTPS.
  *
- * @return the jquery URL client.
+ * @return the jquery context loader.
  */
-jsonld.urlClients['jquery'] = function($, options) {
+jsonld.contextLoaders['jquery'] = function($, options) {
+  options = options || {};
   var cache = new jsonld.ContextCache();
   return function(url, callback) {
-    var ctx = cache.get(url);
-    if(ctx !== null) {
-      return callback(null, url, ctx);
-    }
-    options = options || {};
     if(options.secure && url.indexOf('https') !== 0) {
       return callback(new JsonLdError(
         'URL could not be dereferenced; secure mode is enabled and ' +
         'the URL\'s scheme is not "https".',
         'jsonld.InvalidUrl', {url: url}), url);
     }
+    var ctx = cache.get(url);
+    if(ctx !== null) {
+      return callback(null, url, ctx);
+    }
     $.ajax({
       url: url,
       dataType: 'json',
@@ -895,72 +895,114 @@
         cache.set(url, data);
         callback(null, url, data);
       },
-      error: function(jqXHR, textStatus, errorThrown) {
-        callback(errorThrown, url);
+      error: function(jqXHR, textStatus, err) {
+        callback(new JsonLdError(
+          'URL could not be dereferenced, an error occurred.',
+          'jsonld.LoadContextError', {url: url, cause: err}), url);
       }
     });
   };
 };
 
 /**
- * The built-in node URL client.
- *
- * @param options the optionst o use:
+ * The built-in node context loader.
+ *
+ * @param options the options to use:
  *          secure: require all URLs to use HTTPS.
- *
- * @return the node URL client.
+ *          maxRedirects: the maximum number of redirects to permit, none by
+ *            default.
+ *
+ * @return the node context loader.
  */
-jsonld.urlClients['node'] = function(options) {
+jsonld.contextLoaders['node'] = function(options) {
+  options = options || {};
+  var maxRedirects = ('maxRedirects' in options) ? options.maxRedirects : -1;
   var request = require('request');
   var http = require('http');
   var cache = new jsonld.ContextCache();
-  return function(url, callback) {
-    var ctx = cache.get(url);
-    if(ctx !== null) {
-      return callback(null, url, ctx);
-    }
-    options = options || {};
+  function loadContext(url, redirects, callback) {
     if(options.secure && url.indexOf('https') !== 0) {
       return callback(new JsonLdError(
         'URL could not be dereferenced; secure mode is enabled and ' +
         'the URL\'s scheme is not "https".',
         'jsonld.InvalidUrl', {url: url}), url);
     }
-    request(url, function(err, res, body) {
-      if(!err && res.statusCode >= 400) {
-        var statusText = http.STATUS_CODES[res.statusCode];
-        err = new JsonLdError(
+    var ctx = cache.get(url);
+    if(ctx !== null) {
+      return callback(null, url, ctx);
+    }
+    request({
+      url: url,
+      strictSSL: true,
+      followRedirect: false
+    }, function(err, res, body) {
+      // handle error
+      if(err) {
+        return callback(new JsonLdError(
+          'URL could not be dereferenced, an error occurred.',
+          'jsonld.LoadContextError', {url: url, cause: err}), url);
+      }
+      var statusText = http.STATUS_CODES[res.statusCode];
+      if(res.statusCode >= 400) {
+        return callback(new JsonLdError(
           'URL could not be dereferenced: ' + statusText,
-          'jsonld.InvalidUrl', {url: url, httpStatusCode: res.statusCode});
-      }
-      if(!err) {
-        cache.set(url, body);
+          'jsonld.InvalidUrl', {url: url, httpStatusCode: res.statusCode}),
+          url);
+      }
+      // handle redirect
+      if(res.statusCode >= 300 && res.statusCode < 400 &&
+        res.headers.location) {
+        if(redirects.length === maxRedirects) {
+          return callback(new JsonLdError(
+            'URL could not be dereferenced; there were too many redirects.',
+            'jsonld.TooManyRedirects',
+            {url: url, httpStatusCode: res.statusCode, redirects: redirects}),
+            url);
+        }
+        if(redirects.indexOf(url) !== -1) {
+          return callback(new JsonLdError(
+            'URL could not be dereferenced; infinite redirection was detected.',
+            'jsonld.InfiniteRedirectDetected',
+            {url: url, httpStatusCode: res.statusCode, redirects: redirects}),
+            url);
+        }
+        redirects.push(url);
+        return loadContext(res.headers.location, redirects, callback);
+      }
+      // cache for each redirected URL
+      redirects.push(url);
+      for(var i = 0; i < redirects.length; ++i) {
+        cache.set(redirects[i], body);
       }
       callback(err, url, body);
     });
+  }
+
+  return function(url, callback) {
+    loadContext(url, [], callback);
   };
 };
 
 /**
- * Assigns the default URL client for external @context URLs to a built-in
+ * Assigns the default context loader for external @context URLs to a built-in
  * default. Supported types currently include: 'jquery'.
  *
- * To use the jquery URL client, the 'data' parameter must be a reference
+ * To use the jquery context loader, the 'data' parameter must be a reference
  * to the main jquery object.
  *
  * @param type the type to set.
- * @param [params] the parameters required to use the client.
+ * @param [params] the parameters required to use the context loader.
  */
-jsonld.useUrlClient = function(type) {
-  if(!(type in jsonld.urlClients)) {
+jsonld.useContextLoader = function(type) {
+  if(!(type in jsonld.contextLoaders)) {
     throw new JsonLdError(
-      'Unknown @context URL client type: "' + type + '"',
-      'jsonld.UnknownUrlClient',
+      'Unknown @context loader type: "' + type + '"',
+      'jsonld.UnknownContextLoader',
       {type: type});
   }
 
-  // set URL client
-  jsonld.urlClient = jsonld.urlClients[type].apply(
+  // set context loader
+  jsonld.loadContext = jsonld.contextLoaders[type].apply(
     jsonld, Array.prototype.slice.call(arguments, 1));
 };
 
@@ -971,7 +1013,7 @@
  * @param activeCtx the current active context.
  * @param localCtx the local context to process.
  * @param [options] the options to use:
- *          [urlClient(url, callback(err, url, result))] the URL client to use.
+ *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, ctx) called once the operation completes.
  */
 jsonld.processContext = function(activeCtx, localCtx) {
@@ -988,8 +1030,8 @@
   if(!('base' in options)) {
     options.base = '';
   }
-  if(!('urlClient' in options)) {
-    options.urlClient = jsonld.urlClient;
+  if(!('loadContext' in options)) {
+    options.loadContext = jsonld.loadContext;
   }
 
   // return initial context early for null context
@@ -1928,7 +1970,7 @@
  */
 Processor.prototype.flatten = function(input) {
   // produce a map of all subjects and name each bnode
-  var namer = new UniqueNamer('_:t');
+  var namer = new UniqueNamer('_:b');
   var graphs = {'@default': {}};
   _createNodeMap(input, graphs, '@default', namer);
 
@@ -1987,7 +2029,7 @@
 
   // produce a map of all graphs and name each bnode
   // FIXME: currently uses subjects from @merged graph only
-  namer = new UniqueNamer('_:t');
+  namer = new UniqueNamer('_:b');
   _createNodeMap(input, state.graphs, '@merged', namer);
   state.subjects = state.graphs['@merged'];
 
@@ -2008,7 +2050,7 @@
   // map bnodes to RDF statements
   var statements = [];
   var bnodes = {};
-  var namer = new UniqueNamer('_:t');
+  var namer = new UniqueNamer('_:b');
   new Processor().toRDF(input, namer, null, null, null, mapStatements);
 
   // maps bnodes to their statements and then start bnode naming
@@ -2144,7 +2186,7 @@
         }
 
         // hash bnode paths
-        var pathNamer = new UniqueNamer('_:t');
+        var pathNamer = new UniqueNamer('_:b');
         pathNamer.getName(bnode);
         _hashPaths(bnode, bnodes, namer, pathNamer,
           function(err, result) {
@@ -3880,7 +3922,7 @@
     }
     else {
       if(_isValue(value)) {
-        if('@language' in value) {
+        if('@language' in value && !('@index' in value)) {
           containers.push('@language');
           typeOrLanguageValue = value['@language'];
         }
@@ -4608,7 +4650,7 @@
 function _getInitialContext(options) {
   var namer = null;
   if(options.renameBlankNodes) {
-    namer = new UniqueNamer('_:t');
+    namer = new UniqueNamer('_:b');
   }
   var base = jsonld.url.parse(options.base || '');
   base.pathname = base.pathname || '';
@@ -4691,9 +4733,6 @@
             '@language': {},
             '@type': {}
           };
-          entry[container]['@language'][defaultLanguage] = {
-            term: null, propertyGenerators: []
-          };
         }
         entry = entry[container];
 
@@ -4777,7 +4816,7 @@
     rval.mappings = this.mappings;
     rval.namer = null;
     if(this.namer) {
-      rval.namer = new UniqueNamer('_:t');
+      rval.namer = new UniqueNamer('_:b');
     }
     rval.clone = this.clone;
     rval.share = this.share;
@@ -5145,13 +5184,13 @@
 }
 
 /**
- * Retrieves external @context URLs using the given URL client. Each
+ * Retrieves external @context URLs using the given context loader. Every
  * instance of @context in the input that refers to a URL will be replaced
  * with the JSON @context found at that URL.
  *
  * @param input the JSON-LD input with possible contexts.
  * @param options the options to use:
- *          urlClient(url, callback(err, url, result)) the URL client to use.
+ *          loadContext(url, callback(err, url, result)) the context loader.
  * @param callback(err, input) called once the operation completes.
  */
 function _retrieveContextUrls(input, options, callback) {
@@ -5159,9 +5198,9 @@
   var error = null;
   var regex = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
 
-  // recursive URL client
-  var urlClient = options.urlClient;
-  var retrieve = function(input, cycles, urlClient, base, callback) {
+  // recursive context loader
+  var loadContext = options.loadContext;
+  var retrieve = function(input, cycles, loadContext, base, callback) {
     if(Object.keys(cycles).length > MAX_CONTEXT_URLS) {
       error = new JsonLdError(
         'Maximum number of @context URLs exceeded.',
@@ -5213,7 +5252,7 @@
         var _cycles = _clone(cycles);
         _cycles[url] = true;
 
-        urlClient(url, function(err, url, ctx) {
+        loadContext(url, function(err, finalUrl, ctx) {
           // short-circuit if there was an error with another URL
           if(error) {
             return;
@@ -5235,7 +5274,8 @@
               'Derefencing a URL did not result in a valid JSON-LD object. ' +
               'Possible causes are an inaccessible URL perhaps due to ' +
               'a same-origin policy (ensure the server uses CORS if you are ' +
-              'using client-side JavaScript) or a non-JSON response.',
+              'using client-side JavaScript), too many redirects, or a ' +
+              'non-JSON response.',
               'jsonld.InvalidUrl', {url: url, cause: err});
           }
           else if(!_isObject(ctx)) {
@@ -5255,7 +5295,7 @@
           }
 
           // recurse
-          retrieve(ctx, _cycles, urlClient, url, function(err, ctx) {
+          retrieve(ctx, _cycles, loadContext, url, function(err, ctx) {
             if(err) {
               return callback(err);
             }
@@ -5269,7 +5309,7 @@
       }(queue[i]));
     }
   };
-  retrieve(input, {}, urlClient, options.base, callback);
+  retrieve(input, {}, loadContext, options.base, callback);
 }
 
 // define js 1.8.5 Object.keys method if not present
@@ -6128,8 +6168,8 @@
 }
 
 if(_nodejs) {
-  // use node URL client by default
-  jsonld.useUrlClient('node');
+  // use node context loader by default
+  jsonld.useContextLoader('node');
 }
 
 // end of jsonld API factory