Update to latest jsonld.js.
authorDave Longley <dlongley@digitalbazaar.com>
Mon, 11 Mar 2013 14:21:50 -0400
changeset 1406 4734f5796236
parent 1405 8be04258d819
child 1407 483c4f47ea44
Update to latest jsonld.js.
playground/jsonld.js
--- a/playground/jsonld.js	Mon Mar 11 19:04:49 2013 +0100
+++ b/playground/jsonld.js	Mon Mar 11 14:21:50 2013 -0400
@@ -1403,6 +1403,9 @@
       return _compactValue(activeCtx, activeProperty, element);
     }
 
+    // FIXME: avoid misuse of active property as an expanded property?
+    var insideReverse = (activeProperty === '@reverse');
+
     // process element keys in order
     var keys = Object.keys(element).sort();
     var rval = {};
@@ -1437,6 +1440,32 @@
         continue;
       }
 
+      // handle @reverse
+      if(expandedProperty === '@reverse') {
+        // recursively compact expanded value
+        var compactedValue = this.compact(
+          activeCtx, '@reverse', expandedValue, options);
+
+        // handle double-reversed properties
+        for(var compactedProperty in compactedValue) {
+          if(activeCtx.mappings[compactedProperty] &&
+            activeCtx.mappings[compactedProperty].reverse) {
+            jsonld.addValue(
+              rval, compactedProperty, compactedValue,
+              {propertyIsArray: options.compactArrays});
+            delete compactedValue[compactedProperty];
+          }
+        }
+
+        if(Object.keys(compactedValue).length > 0) {
+          // use keyword alias and add value
+          var alias = _compactIri(activeCtx, expandedProperty);
+          jsonld.addValue(rval, alias, compactedValue);
+        }
+
+        continue;
+      }
+
       // handle @index property
       if(expandedProperty === '@index') {
         // drop @index if inside an @index container
@@ -1644,6 +1673,14 @@
         continue;
       }
 
+      if(_isKeyword(expandedProperty) &&
+        expandedActiveProperty === '@reverse') {
+        throw new JsonLdError(
+          'Invalid JSON-LD syntax; a keyword cannot be used as a @reverse ' +
+          'property.',
+          'jsonld.SyntaxError', {value: value});
+      }
+
       // syntax error if @id is not a string
       if(expandedProperty === '@id' && !_isString(value)) {
         throw new JsonLdError(
@@ -1685,7 +1722,7 @@
         value = value.toLowerCase();
       }
 
-      // preserve @index
+      // @index must be a string
       if(expandedProperty === '@index') {
         if(!_isString(value)) {
           throw new JsonLdError(
@@ -1694,6 +1731,54 @@
         }
       }
 
+      // @reverse must be an object
+      if(expandedProperty === '@reverse') {
+        if(!_isObject(value)) {
+          throw new JsonLdError(
+            'Invalid JSON-LD syntax; "@reverse" value must be an object.',
+            'jsonld.SyntaxError', {value: value});
+        }
+
+        expandedValue = self.expand(
+          activeCtx, '@reverse', value, options, insideList);
+
+        // properties double-reversed
+        if('@reverse' in expandedValue) {
+          for(var property in expandedValue['@reverse']) {
+            jsonld.addValue(
+              rval, property, expandedValue['@reverse'][property],
+              {propertyIsArray: true});
+          }
+        }
+
+        // FIXME: can this be merged with code below to simplify?
+        // merge in all reversed properties
+        var reverseMap = rval['@reverse'] || null;
+        for(var property in expandedValue) {
+          if(property === '@reverse') {
+            continue;
+          }
+          if(reverseMap === null) {
+            reverseMap = rval['@reverse'] = {};
+          }
+          jsonld.addValue(reverseMap, property, [], {propertyIsArray: true});
+          var items = expandedValue[property];
+          for(var ii = 0; ii < items.length; ++ii) {
+            var item = items[ii];
+            if(_isValue(item) || _isList(item)) {
+              throw new JsonLdError(
+                'Invalid JSON-LD syntax; "@reverse" value must not be a ' +
+                '@value or an @list.',
+                'jsonld.SyntaxError', {value: expandedValue});
+            }
+            jsonld.addValue(
+              reverseMap, property, item, {propertyIsArray: true});
+          }
+        }
+
+        continue;
+      }
+
       var container = jsonld.getContextValue(activeCtx, key, '@container');
 
       // handle language map container (skip if value is not an object)
@@ -1759,6 +1844,27 @@
         expandedValue = {'@list': expandedValue};
       }
 
+      // FIXME: can this be merged with code above to simplify?
+      // merge in reverse properties
+      if(activeCtx.mappings[key] && activeCtx.mappings[key].reverse) {
+        var reverseMap = rval['@reverse'] = {};
+        if(!_isArray(expandedValue)) {
+          expandedValue = [expandedValue];
+        }
+        for(var ii = 0; ii < expandedValue.length; ++ii) {
+          var item = expandedValue[ii];
+          if(_isValue(item) || _isList(item)) {
+            throw new JsonLdError(
+              'Invalid JSON-LD syntax; "@reverse" value must not be a ' +
+              '@value or an @list.',
+              'jsonld.SyntaxError', {value: expandedValue});
+          }
+          jsonld.addValue(
+            reverseMap, expandedProperty, item, {propertyIsArray: true});
+        }
+        continue;
+      }
+
       // add value for property
       // use an array except for certain keywords
       var useArray =
@@ -3130,6 +3236,23 @@
       continue;
     }
 
+    // handle reverse properties
+    if('@reverse' in input) {
+      var referencedNode = {'@id': name};
+      var reverseMap = input['@reverse'];
+      for(var reverseProperty in reverseMap) {
+        var items = reverseMap[reverseProperty];
+        for(var ii = 0; ii < items; ++ii) {
+          var item = items[ii];
+          jsonld.addValue(
+            item, reverseProperty, referencedNode,
+            {propertyIsArray: true});
+          _createNodeMap(item, graphs, graph, namer);
+        }
+      }
+      continue;
+    }
+
     // recurse into graph
     if(property === '@graph') {
       // add graph subjects map entry
@@ -3143,6 +3266,11 @@
 
     // copy non-@type keywords
     if(property !== '@type' && _isKeyword(property)) {
+      if(property === '@index' && '@index' in subject) {
+        throw new JsonLdError(
+          'Invalid JSON-LD syntax; conflicting @index property detected.',
+          'jsonld.SyntaxError', {subject: subject});
+      }
       subject[property] = input[property];
       continue;
     }
@@ -3646,32 +3774,38 @@
  */
 function _selectTerm(
   activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue) {
-  containers.push('@none');
   if(typeOrLanguageValue === null) {
     typeOrLanguageValue = '@null';
   }
 
-  // options for the value of @type or @language
-  var options;
-
-  // determine options for @id based on whether or not value compacts to a term
-  if(typeOrLanguageValue === '@id' && _isSubjectReference(value)) {
+  // preferences for the value of @type or @language
+  var prefs = [];
+
+  // prefer @reverse first
+  if(typeOrLanguageValue === '@reverse') {
+    prefs.push('@reverse');
+  }
+
+  // determine prefs for @id based on whether or not value compacts to a term
+  if((typeOrLanguageValue === '@id' || typeOrLanguageValue === '@reverse') &&
+    _isSubjectReference(value)) {
     // try to compact value to a term
     var term = _compactIri(activeCtx, value['@id'], null, {vocab: true});
     if(term in activeCtx.mappings &&
       activeCtx.mappings[term] &&
       activeCtx.mappings[term]['@id'] === value['@id']) {
       // prefer @vocab
-      options = ['@vocab', '@id', '@none'];
+      prefs.push.apply(prefs, ['@vocab', '@id']);
     }
     else {
       // prefer @id
-      options = ['@id', '@vocab', '@none'];
-    }
-  }
-  else {
-    options = [typeOrLanguageValue, '@none'];
-  }
+      prefs.push.apply(prefs, ['@id', '@vocab']);
+    }
+  }
+  else if(typeOrLanguageValue !== '@reverse') {
+    prefs.push(typeOrLanguageValue);
+  }
+  prefs.push('@none');
 
   var term = null;
   var containerMap = activeCtx.inverse[iri];
@@ -3683,15 +3817,15 @@
     }
 
     var typeOrLanguageValueMap = containerMap[container][typeOrLanguage];
-    for(var oi = 0; term === null && oi < options.length; ++oi) {
+    for(var pi = 0; term === null && pi < prefs.length; ++pi) {
       // if type/language option not available in the map, continue
-      var option = options[oi];
-      if(!(option in typeOrLanguageValueMap)) {
+      var pref = prefs[pi];
+      if(!(pref in typeOrLanguageValueMap)) {
         continue;
       }
 
       // select term
-      term = typeOrLanguageValueMap[option];
+      term = typeOrLanguageValueMap[pref];
     }
   }
 
@@ -3708,10 +3842,11 @@
  * @param relativeTo options for how to compact IRIs:
  *          base: true to resolve against the base IRI, false not to.
  *          vocab: true to split after @vocab, false not to.
+ * @param reverse true if a reverse property is being compacted, false if not.
  *
  * @return the compacted term, prefix, keyword alias, or the original IRI.
  */
-function _compactIri(activeCtx, iri, value, relativeTo) {
+function _compactIri(activeCtx, iri, value, relativeTo, reverse) {
   // can't compact null
   if(iri === null) {
     return iri;
@@ -3728,6 +3863,10 @@
   if(_isUndefined(value)) {
     value = null;
   }
+  // default reverse to false
+  if(_isUndefined(reverse)) {
+    reverse = false;
+  }
   relativeTo = relativeTo || {};
 
   // use inverse context to pick a term if iri is relative to vocab
@@ -3744,8 +3883,13 @@
     var typeOrLanguage = '@language';
     var typeOrLanguageValue = '@null';
 
+    if(reverse) {
+      typeOrLanguage = '@reverse';
+      typeOrLanguageValue = '@reverse';
+      containers.push('@set');
+    }
     // choose the most specific term that works for all elements in @list
-    if(_isList(value)) {
+    else if(_isList(value)) {
       // only select @list containers if @index is NOT in value
       if(!('@index' in value)) {
         containers.push('@list');
@@ -3819,6 +3963,7 @@
     }
 
     // do term selection
+    containers.push('@none');
     var term = _selectTerm(
       activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue);
     if(term !== null) {
@@ -4057,8 +4202,29 @@
 
   // create new mapping
   var mapping = {};
-
-  if('@id' in value) {
+  mapping.reverse = false;
+
+  if('@reverse' in value) {
+    if('@id' in value || '@type' in value || '@language' in value) {
+      throw new JsonLdError(
+      'Invalid JSON-LD syntax; a @reverse term definition must not ' +
+      'contain @id, @type, or @language.',
+      'jsonld.SyntaxError', {context: localCtx});
+    }
+    var reverse = value['@reverse'];
+    if(!_isString(reverse)) {
+      throw new JsonLdError(
+        'Invalid JSON-LD syntax; a @context @reverse value must be a string.',
+        'jsonld.SyntaxError', {context: localCtx});
+    }
+
+    // expand and add @id mapping, set @type to @id
+    mapping['@id'] = _expandIri(
+      activeCtx, reverse, {vocab: true, base: true}, localCtx, defined);
+    mapping['@type'] = '@id';
+    mapping.reverse = true;
+  }
+  else if('@id' in value) {
     var id = value['@id'];
     if(!_isString(id)) {
       throw new JsonLdError(
@@ -4066,11 +4232,9 @@
         'of strings or a string.',
         'jsonld.SyntaxError', {context: localCtx});
     }
-    else {
-      // expand and add @id mapping
-      mapping['@id'] = _expandIri(
-        activeCtx, id, {vocab: true, base: true}, localCtx, defined);
-    }
+    // expand and add @id mapping
+    mapping['@id'] = _expandIri(
+      activeCtx, id, {vocab: true, base: true}, localCtx, defined);
   }
   else {
     // see if the term has a prefix
@@ -4131,6 +4295,12 @@
         'one of the following: @list, @set, @index, or @language.',
         'jsonld.SyntaxError', {context: localCtx});
     }
+    if(mapping.reverse && container !== '@index') {
+      throw new JsonLdError(
+        'Invalid JSON-LD syntax; @context @container value for a @reverse ' +
+        'type definition must be @index.',
+        'jsonld.SyntaxError', {context: localCtx});
+    }
 
     // add @container to mapping
     mapping['@container'] = container;
@@ -4503,6 +4673,11 @@
         }
         entry = entry[container];
 
+        // special case reverse mapping
+        if(mapping.reverse) {
+          _addPreferredTerm(mapping, term, entry['@type'], '@reverse');
+        }
+
         // consider updating @language entry if @type is not specified
         if(!('@type' in mapping)) {
           // if a @language is specified, update its specific entry
@@ -4622,6 +4797,7 @@
   case '@list':
   case '@omitDefault':
   case '@preserve':
+  case '@reverse':
   case '@set':
   case '@type':
   case '@value':