Update to latest jsonld.js.
authorDave Longley <dlongley@digitalbazaar.com>
Fri, 27 Apr 2012 16:22:09 -0400
changeset 576 367a7194b1a0
parent 575 5bcbcd8ffd16
child 581 8b78e95e9b06
Update to latest jsonld.js.
playground/jsonld.js
--- a/playground/jsonld.js	Thu Apr 26 13:34:03 2012 +0200
+++ b/playground/jsonld.js	Fri Apr 27 16:22:09 2012 -0400
@@ -566,7 +566,7 @@
   var rval = false;
   if(jsonld.hasProperty(subject, property)) {
     var val = subject[property];
-    var isList = _isListValue(val);
+    var isList = _isList(val);
     if(_isArray(val) || isList) {
       if(isList) {
         val = val['@list'];
@@ -949,7 +949,7 @@
     // compact subject references
     if(_isSubjectReference(element)) {
       var type = jsonld.getContextValue(ctx, property, '@type');
-      if(type === '@id') {
+      if(type === '@id' || property === '@graph') {
         element = _compactIri(ctx, element['@id']);
         return element;
       }
@@ -993,7 +993,7 @@
       // recusively process array values
       for(var i in value) {
         var v = value[i];
-        var isList = _isListValue(v);
+        var isList = _isList(v);
 
         // compact property
         var prop = _compactIri(ctx, key, v);
@@ -1109,14 +1109,9 @@
           'jsonld.SyntaxError', {value: value});
       }
 
-      // @type must be a string, array of strings, or an empty JSON object
-      if(prop === '@type' &&
-        !(_isString(value) || _isArrayOfStrings(value) ||
-        _isEmptyObject(value))) {
-        throw new JsonLdError(
-          'Invalid JSON-LD syntax; "@type" value must a string, an array ' +
-          'of strings, or an empty object.',
-          'jsonld.SyntaxError', {value: value});
+      // validate @type value
+      if(prop === '@type') {
+        _validateTypeValue(value);
       }
 
       // @graph must be an array or an object
@@ -1146,7 +1141,7 @@
       var isList = (prop === '@list');
       if(isList || prop === '@set' || prop === '@graph') {
         value = this.expand(ctx, property, value, options, isList);
-        if(isList && _isListValue(value)) {
+        if(isList && _isList(value)) {
           throw new JsonLdError(
             'Invalid JSON-LD syntax; lists of lists are not permitted.',
             'jsonld.SyntaxError');
@@ -1161,7 +1156,7 @@
       // drop null values if property is not @value (dropped below)
       if(value !== null || prop === '@value') {
         // convert value to @list if container specifies it
-        if(prop !== '@list' && !_isListValue(value)) {
+        if(prop !== '@list' && !_isList(value)) {
           var container = jsonld.getContextValue(ctx, property, '@container');
           if(container === '@list') {
             // ensure value is an array
@@ -1170,6 +1165,26 @@
           }
         }
 
+        // optimize away @id for @type
+        if(prop === '@type') {
+          if(_isSubjectReference(value)) {
+            value = value['@id'];
+          }
+          else if(_isArray(value)) {
+            var val = [];
+            for(var i in value) {
+              var v = value[i];
+              if(_isSubjectReference(v)) {
+                val.push(v['@id']);
+              }
+              else {
+                val.push(v);
+              }
+            }
+            value = val;
+          }
+        }
+
         // add value, use an array if not @id, @type, @value, or @language
         var useArray = !(prop === '@id' || prop === '@type' ||
           prop === '@value' || prop === '@language');
@@ -1527,8 +1542,8 @@
     // get type definition from context
     var type = jsonld.getContextValue(ctx, property, '@type');
 
-    // do @id expansion
-    if(type === '@id') {
+    // do @id expansion (automatic for @graph)
+    if(type === '@id' || prop === '@graph') {
       rval = {'@id': _expandTerm(ctx, value, base)};
     }
     // other type
@@ -1562,96 +1577,96 @@
     for(var i in input) {
       _getStatements(input[i], namer, bnodes, subjects);
     }
+    return;
   }
-  // safe to assume input is a subject/blank node
+
+  // Note: safe to assume input is a subject/blank node
+  var isBnode = _isBlankNode(input);
+
+  // name blank node if appropriate, use passed name if given
+  if(_isUndefined(name)) {
+    name = isBnode ? namer.getName(input['@id']) : input['@id'];
+  }
+
+  // use a subject of '_:a' for blank node statements
+  var s = isBnode ? '_:a' : name;
+
+  // get statements for the blank node
+  var entries;
+  if(isBnode) {
+    entries = bnodes[name] = bnodes[name] || [];
+  }
   else {
-    var isBnode = _isBlankNode(input);
-
-    // name blank node if appropriate, use passed name if given
-    if(_isUndefined(name)) {
-      name = isBnode ? namer.getName(input['@id']) : input['@id'];
-    }
-
-    // use a subject of '_:a' for blank node statements
-    var s = isBnode ? '_:a' : name;
-
-    // get statements for the blank node
-    var entries;
-    if(isBnode) {
-      entries = bnodes[name] = bnodes[name] || [];
+    entries = subjects[name] = subjects[name] || [];
+  }
+
+  // add all statements in input
+  for(var p in input) {
+    // skip @id
+    if(p === '@id') {
+      continue;
     }
-    else {
-      entries = subjects[name] = subjects[name] || [];
+
+    var objects = input[p];
+
+    // convert @lists into embedded blank node linked lists
+    for(var i in objects) {
+      var o = objects[i];
+      if(_isList(o)) {
+        objects[i] = _makeLinkedList(o);
+      }
     }
 
-    // add all statements in input
-    for(var p in input) {
-      // skip @id
-      if(p === '@id') {
-        continue;
+    for(var i in objects) {
+      var o = objects[i];
+
+      // convert boolean to @value
+      if(_isBoolean(o)) {
+        o = {'@value': String(o), '@type': XSD['boolean']};
       }
-
-      var objects = input[p];
-
-      // convert @lists into embedded blank node linked lists
-      for(var i in objects) {
-        var o = objects[i];
-        if(_isListValue(o)) {
-          objects[i] = _makeLinkedList(o);
+      // convert double to @value
+      else if(_isDouble(o)) {
+        // do special JSON-LD double format, printf('%1.16e') JS equivalent
+        o = o.toExponential(16).replace(/(e(?:\+|-))([0-9])$/, '$10$2');
+        o = {'@value': o, '@type': XSD['double']};
+      }
+      // convert integer to @value
+      else if(_isNumber(o)) {
+        o = {'@value': String(o), '@type': XSD['integer']};
+      }
+
+      // object is a blank node
+      if(_isBlankNode(o)) {
+        // name object position blank node
+        var oName = namer.getName(o['@id']);
+
+        // add property statement
+        _addStatement(entries, {s: s, p: p, o: {'@id': oName}});
+
+        // add reference statement
+        var oEntries = bnodes[oName] = bnodes[oName] || [];
+        _addStatement(oEntries, {s: name, p: p, o: {'@id': '_:a'}});
+
+        // recurse into blank node
+        _getStatements(o, namer, bnodes, subjects, oName);
+      }
+      // object is a string, @value, subject reference
+      else if(_isString(o) || _isValue(o) || _isSubjectReference(o)) {
+        // add property statement
+        _addStatement(entries, {s: s, p: p, o: o});
+
+        // ensure a subject entry exists for subject reference
+        if(_isSubjectReference(o)) {
+          subjects[o['@id']] = subjects[o['@id']] || [];
         }
       }
-
-      for(var i in objects) {
-        var o = objects[i];
-
-        // convert boolean to @value
-        if(_isBoolean(o)) {
-          o = {'@value': String(o), '@type': XSD['boolean']};
-        }
-        // convert double to @value
-        else if(_isDouble(o)) {
-          // do special JSON-LD double format, printf('%1.16e') JS equivalent
-          o = o.toExponential(16).replace(/(e(?:\+|-))([0-9])$/, '$10$2');
-          o = {'@value': o, '@type': XSD['double']};
-        }
-        // convert integer to @value
-        else if(_isNumber(o)) {
-          o = {'@value': String(o), '@type': XSD['integer']};
-        }
-
-        // object is a blank node
-        if(_isBlankNode(o)) {
-          // name object position blank node
-          var oName = namer.getName(o['@id']);
-
-          // add property statement
-          _addStatement(entries, {s: s, p: p, o: {'@id': oName}});
-
-          // add reference statement
-          var oEntries = bnodes[oName] = bnodes[oName] || [];
-          _addStatement(oEntries, {s: name, p: p, o: {'@id': '_:a'}});
-
-          // recurse into blank node
-          _getStatements(o, namer, bnodes, subjects, oName);
-        }
-        // object is a string, @value, subject reference
-        else if(_isString(o) || _isValue(o) || _isSubjectReference(o)) {
-          // add property statement
-          _addStatement(entries, {s: s, p: p, o: o});
-
-          // ensure a subject entry exists for subject reference
-          if(_isSubjectReference(o)) {
-            subjects[o['@id']] = subjects[o['@id']] || [];
-          }
-        }
-        // object must be an embedded subject
-        else {
-          // add property statement
-          _addStatement(entries, {s: s, p: p, o: {'@id': o['@id']}});
-
-          // recurse into subject
-          _getStatements(o, namer, bnodes, subjects);
-        }
+      // object must be an embedded subject
+      else {
+        // add property statement
+        _addStatement(entries, {s: s, p: p, o: {'@id': o['@id']}});
+
+        // recurse into subject
+        _getStatements(o, namer, bnodes, subjects);
       }
     }
   }
@@ -2034,7 +2049,7 @@
         }
         else {
           // recurse into list
-          if(_isListValue(o)) {
+          if(_isList(o)) {
             var l = [];
             _flatten(subjects, o['@list'], namer, name, l);
             o = {'@list': l};
@@ -2075,7 +2090,10 @@
   var explicitOn = _getFrameFlag(frame, options, 'explicit');
 
   // add matches to output
-  for(var id in matches) {
+  var ids = Object.keys(matches).sort();
+  for(var idx in ids) {
+    var id = ids[idx];
+
     /* Note: In order to treat each top-level match as a compartmentalized
     result, create an independent copy of the embedded subjects map when the
     property is null, which only occurs at the top-level. */
@@ -2153,7 +2171,7 @@
           var o = objects[i];
 
           // recurse into list
-          if(_isListValue(o)) {
+          if(_isList(o)) {
             // add empty list
             var list = {'@list': []};
             _addFrameOutput(state, output, prop, list);
@@ -2254,7 +2272,6 @@
 function _filterSubjects(state, subjects, frame) {
   // filter subjects in @id order
   var rval = {};
-  subjects.sort();
   for(var i in subjects) {
     var id = subjects[i];
     var subject = state.subjects[id];
@@ -2313,7 +2330,7 @@
     var o = objects[i];
 
     // recurse into @list
-    if(_isListValue(o)) {
+    if(_isList(o)) {
       var list = {'@list': []};
       _addFrameOutput(state, output, property, list);
       return _embedValues(state, o, '@list', list['@list']);
@@ -2452,7 +2469,7 @@
     }
 
     // recurse through @lists
-    if(_isListValue(input)) {
+    if(_isList(input)) {
       input['@list'] = _removePreserve(ctx, input['@list']);
       return input;
     }
@@ -2512,7 +2529,7 @@
   var hasDefaultLanguage = ('@language' in ctx);
 
   // @list rank is the sum of its values' ranks
-  if(_isListValue(value)) {
+  if(_isList(value)) {
     var list = value['@list'];
     if(list.length === 0) {
       return (entry['@container'] === '@list') ? 1 : 0;
@@ -2597,11 +2614,6 @@
     return iri;
   }
 
-  // compact rdf:type
-  if(iri === RDF['type']) {
-    return '@type';
-  }
-
   // term is a keyword
   if(_isKeyword(iri)) {
     // return alias if available
@@ -2624,7 +2636,7 @@
   var terms = [];
   var highest = 0;
   var listContainer = false;
-  var isList = _isListValue(value);
+  var isList = _isList(value);
   for(var term in ctx.mappings) {
     // skip terms with non-matching iris
     var entry = ctx.mappings[term];
@@ -2825,8 +2837,11 @@
         'jsonld.SyntaxError', {context: ctx});
     }
 
-    // expand @id to full IRI
-    id = _expandContextIri(activeCtx, ctx, id, base, defined);
+    // expand @id if it is not @type
+    if(id !== '@type') {
+      // expand @id to full IRI
+      id = _expandContextIri(activeCtx, ctx, id, base, defined);
+    }
 
     // add @id to mapping
     mapping['@id'] = id;
@@ -3070,25 +3085,25 @@
 /**
  * Returns whether or not the given value is a keyword (or a keyword alias).
  *
- * @param value the value to check.
+ * @param v the value to check.
  * @param [ctx] the active context to check against.
  *
  * @return true if the value is a keyword, false if not.
  */
-function _isKeyword(value, ctx) {
+function _isKeyword(v, ctx) {
   if(ctx) {
-    if(value in ctx.keywords) {
+    if(v in ctx.keywords) {
       return true;
     }
     for(var key in ctx.keywords) {
       var aliases = ctx.keywords[key];
-      if(aliases.indexOf(value) !== -1) {
+      if(aliases.indexOf(v) !== -1) {
         return true;
       }
     }
   }
   else {
-    switch(value) {
+    switch(v) {
     case '@context':
     case '@container':
     case '@default':
@@ -3110,217 +3125,208 @@
 }
 
 /**
- * Returns true if the given input is an Object.
- *
- * @param input the input to check.
- *
- * @return true if the input is an Object, false if not.
- */
-function _isObject(input) {
-  return (input !== null && !_isUndefined(input) &&
-    input.constructor === Object);
-}
-
-/**
- * Returns true if the given input is an empty Object.
- *
- * @param input the input to check.
- *
- * @return true if the input is an empty Object, false if not.
- */
-function _isEmptyObject(input) {
-  return _isObject(input) && Object.keys(input).length === 0;
-}
-
-/**
- * Returns true if the given input is an Array.
+ * Returns true if the given value is an Object.
  *
- * @param input the input to check.
- *
- * @return true if the input is an Array, false if not.
- */
-function _isArray(input) {
-  return (input !== null && !_isUndefined(input) &&
-    input.constructor === Array);
-}
-
-/**
- * Returns true if the given input is an Array of Strings.
+ * @param v the value to check.
  *
- * @param input the input to check.
- *
- * @return true if the input is an Array of Strings, false if not.
+ * @return true if the value is an Object, false if not.
  */
-function _isArrayOfStrings(input) {
-  if(!_isArray(input)) {
-    return false;
-  }
-  for(var i in input) {
-    if(!_isString(input[i])) {
-      return false;
-    }
-  }
-  return true;
+function _isObject(v) {
+  return (v !== null && !_isUndefined(v) && v.constructor === Object);
 }
 
 /**
- * Returns true if the given input is a String.
+ * Returns true if the given value is an empty Object.
  *
- * @param input the input to check.
+ * @param v the value to check.
  *
- * @return true if the input is a String, false if not.
+ * @return true if the value is an empty Object, false if not.
  */
-function _isString(input) {
-  return (input !== null && !_isUndefined(input) &&
-    input.constructor === String);
-}
-
-/**
- * Returns true if the given input is a Number.
- *
- * @param input the input to check.
- *
- * @return true if the input is a Number, false if not.
- */
-function _isNumber(input) {
-  return (input !== null && !_isUndefined(input) &&
-    input.constructor === Number);
+function _isEmptyObject(v) {
+  return _isObject(v) && Object.keys(v).length === 0;
 }
 
 /**
- * Returns true if the given input is a double.
- *
- * @param input the input to check.
+ * Returns true if the given value is an Array.
  *
- * @return true if the input is a double, false if not.
+ * @param v the value to check.
+ *
+ * @return true if the value is an Array, false if not.
  */
-function _isDouble(input) {
-  return _isNumber(input) && String(input).indexOf('.') !== -1;
+function _isArray(v) {
+  return (v !== null && !_isUndefined(v) && v.constructor === Array);
 }
 
 /**
- * Returns true if the given input is a Boolean.
- *
- * @param input the input to check.
+ * Throws an exception if the given value is not a valid @type value.
  *
- * @return true if the input is a Boolean, false if not.
+ * @param v the value to check.
  */
-function _isBoolean(input) {
-  return (input !== null && !_isUndefined(input) &&
-    input.constructor === Boolean);
+function _validateTypeValue(v) {
+  // must be a string, subject reference, or empty object
+  if(_isString(v) || _isSubjectReference(v) || _isEmptyObject(v)) {
+    return;
+  }
+
+  // must be an array
+  var isValid = false;
+  if(_isArray(v)) {
+    // must contain only strings or subject references
+    isValid = true;
+    for(var i in v) {
+      if(!(_isString(v[i]) || _isSubjectReference(v[i]))) {
+        isValid = false;
+        break;
+      }
+    }
+  }
+
+  if(!isValid) {
+    throw new JsonLdError(
+      'Invalid JSON-LD syntax; "@type" value must a string, a subject ' +
+      'reference, an array of strings or subject references, or an ' +
+      'empty object.', 'jsonld.SyntaxError', {value: v});
+  }
 }
 
 /**
- * Returns true if the given input is undefined.
- *
- * @param input the input to check.
+ * Returns true if the given value is a String.
  *
- * @return true if the input is undefined, false if not.
+ * @param v the value to check.
+ *
+ * @return true if the value is a String, false if not.
  */
-function _isUndefined(input) {
-  return (typeof input === 'undefined');
+function _isString(v) {
+  return (v !== null && !_isUndefined(v) && v.constructor === String);
+}
+
+/**
+ * Returns true if the given value is a Number.
+ *
+ * @param v the value to check.
+ *
+ * @return true if the value is a Number, false if not.
+ */
+function _isNumber(v) {
+  return (v !== null && !_isUndefined(v) && v.constructor === Number);
+}
+
+/**
+ * Returns true if the given value is a double.
+ *
+ * @param v the value to check.
+ *
+ * @return true if the value is a double, false if not.
+ */
+function _isDouble(v) {
+  return _isNumber(v) && String(v).indexOf('.') !== -1;
+}
+
+/**
+ * Returns true if the given value is a Boolean.
+ *
+ * @param v the value to check.
+ *
+ * @return true if the value is a Boolean, false if not.
+ */
+function _isBoolean(v) {
+  return (v !== null && !_isUndefined(v) && v.constructor === Boolean);
+}
+
+/**
+ * Returns true if the given value is undefined.
+ *
+ * @param v the value to check.
+ *
+ * @return true if the value is undefined, false if not.
+ */
+function _isUndefined(v) {
+  return (typeof v === 'undefined');
 }
 
 /**
  * Returns true if the given value is a subject with properties.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is a subject with properties, false if not.
  */
-function _isSubject(value) {
-  var rval = false;
-
+function _isSubject(v) {
   // Note: A value is a subject if all of these hold true:
   // 1. It is an Object.
   // 2. It is not a @value, @set, or @list.
   // 3. It has more than 1 key OR any existing key is not @id.
-  if(_isObject(value) &&
-    !(('@value' in value) || ('@set' in value) || ('@list' in value))) {
-    var keyCount = Object.keys(value).length;
-    rval = (keyCount > 1 || !('@id' in value));
+  var rval = false;
+  if(_isObject(v) &&
+    !(('@value' in v) || ('@set' in v) || ('@list' in v))) {
+    var keyCount = Object.keys(v).length;
+    rval = (keyCount > 1 || !('@id' in v));
   }
-
   return rval;
 }
 
 /**
  * Returns true if the given value is a subject reference.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is a subject reference, false if not.
  */
-function _isSubjectReference(value) {
+function _isSubjectReference(v) {
   // Note: A value is a subject reference if all of these hold true:
   // 1. It is an Object.
   // 2. It has a single key: @id.
-  return (_isObject(value) && Object.keys(value).length === 1 &&
-    ('@id' in value));
+  return (_isObject(v) && Object.keys(v).length === 1 && ('@id' in v));
 }
 
 /**
  * Returns true if the given value is a @value.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is a @value, false if not.
  */
-function _isValue(value) {
+function _isValue(v) {
   // Note: A value is a @value if all of these hold true:
   // 1. It is an Object.
   // 2. It has the @value property.
-  return _isObject(value) && ('@value' in value);
-}
-
-/**
- * Returns true if the given value is a @set.
- *
- * @param value the value to check.
- *
- * @return true if the value is a @set, false if not.
- */
-function _isSetValue(value) {
-  // Note: A value is a @set if all of these hold true:
-  // 1. It is an Object.
-  // 2. It has the @set property.
-  return _isObject(value) && ('@set' in value);
+  return _isObject(v) && ('@value' in v);
 }
 
 /**
  * Returns true if the given value is a @list.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is a @list, false if not.
  */
-function _isListValue(value) {
+function _isList(v) {
   // Note: A value is a @list if all of these hold true:
   // 1. It is an Object.
   // 2. It has the @list property.
-  return _isObject(value) && ('@list' in value);
+  return _isObject(v) && ('@list' in v);
 }
 
 /**
  * Returns true if the given value is a blank node.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is a blank node, false if not.
  */
-function _isBlankNode(value) {
-  var rval = false;
+function _isBlankNode(v) {
   // Note: A value is a blank node if all of these hold true:
   // 1. It is an Object.
   // 2. If it has an @id key its value begins with '_:'.
   // 3. It has no keys OR is not a @value, @set, or @list.
-  if(_isObject(value)) {
-    if('@id' in value) {
-      rval = (value['@id'].indexOf('_:') === 0);
+  var rval = false;
+  if(_isObject(v)) {
+    if('@id' in v) {
+      rval = (v['@id'].indexOf('_:') === 0);
     }
     else {
-      rval = (Object.keys(value).length === 0 ||
-        !(('@value' in value) || ('@set' in value) || ('@list' in value)));
+      rval = (Object.keys(v).length === 0 ||
+        !(('@value' in v) || ('@set' in v) || ('@list' in v)));
     }
   }
   return rval;
@@ -3329,12 +3335,12 @@
 /**
  * Returns true if the given value is an absolute IRI, false if not.
  *
- * @param value the value to check.
+ * @param v the value to check.
  *
  * @return true if the value is an absolute IRI, false if not.
  */
-function _isAbsoluteIri(value) {
-  return value.indexOf(':') !== -1;
+function _isAbsoluteIri(v) {
+  return v.indexOf(':') !== -1;
 }
 
 /**