Use latest jsonld.js.
authorDave Longley <dlongley@digitalbazaar.com>
Fri, 01 Feb 2013 14:40:49 -0500
changeset 1170 92c975749461
parent 1169 6a3d1cfc3be4
child 1171 a4d2c2b12fc1
Use latest jsonld.js.
playground/jsonld.js
--- a/playground/jsonld.js	Wed Jan 30 20:28:21 2013 +0100
+++ b/playground/jsonld.js	Fri Feb 01 14:40:49 2013 -0500
@@ -259,16 +259,6 @@
       else if(expanded === null) {
         expanded = [];
       }
-      else if(_isArray(expanded)) {
-        // remove nulls (they represent free-floating nodes)
-        var output = [];
-        for(var i = 0; i < expanded.length; ++i) {
-          if(expanded[i] !== null) {
-            output.push(expanded[i]);
-          }
-        }
-        expanded = output;
-      }
 
       // normalize to an array
       if(!_isArray(expanded)) {
@@ -1248,7 +1238,7 @@
  *
  * @param contentType the content-type for the parser.
  * @param parser(input) the parser function (takes a string as a parameter
- *           and returns an array of RDF statements).
+ *          and returns an array of RDF statements).
  */
 jsonld.registerRDFParser = function(contentType, parser) {
   _rdfParsers[contentType] = parser;
@@ -1353,27 +1343,30 @@
  * must be in expanded form before this method is called.
  *
  * @param activeCtx the active context to use.
- * @param property the compact property that points to the element, null for
- *          none.
+ * @param activeProperty the compacted property assoicated with the element
+ *          to compact, null for none.
  * @param element the element to compact.
  * @param options the compaction options.
  *
  * @return the compacted value.
  */
-Processor.prototype.compact = function(activeCtx, property, element, options) {
+Processor.prototype.compact = function(
+  activeCtx, activeProperty, element, options) {
   // recursively compact array
   if(_isArray(element)) {
     var rval = [];
     for(var i in element) {
       // compact, dropping any null values
-      var compacted = this.compact(activeCtx, property, element[i], options);
+      var compacted = this.compact(
+        activeCtx, activeProperty, element[i], options);
       if(compacted !== null) {
         rval.push(compacted);
       }
     }
     if(rval.length === 1) {
       // use single element if no container is specified
-      var container = jsonld.getContextValue(activeCtx, property, '@container');
+      var container = jsonld.getContextValue(
+        activeCtx, activeProperty, '@container');
       if(container === null) {
         rval = rval[0];
       }
@@ -1385,18 +1378,18 @@
   if(_isObject(element)) {
     // do value compaction on @values and subject references
     if(_isValue(element) || _isSubjectReference(element)) {
-      return _compactValue(activeCtx, property, element);
+      return _compactValue(activeCtx, activeProperty, element);
     }
 
     // shallow copy element and arrays so keys and values can be removed
     // during property generator compaction
     var shallow = {};
-    for(var key in element) {
-      if(_isArray(element[key])) {
-        shallow[key] = element[key].slice();
+    for(var expandedProperty in element) {
+      if(_isArray(element[expandedProperty])) {
+        shallow[expandedProperty] = element[expandedProperty].slice();
       }
       else {
-        shallow[key] = element[key];
+        shallow[expandedProperty] = element[expandedProperty];
       }
     }
     element = shallow;
@@ -1404,112 +1397,117 @@
     // process element keys in order
     var keys = Object.keys(element).sort();
     var rval = {};
-    while(keys.length > 0) {
-      var key = keys.shift();
+    for(var ki = 0; ki < keys.length; ++ki) {
+      var expandedProperty = keys[ki];
 
       // skip key if removed during property generator duplicate handling
-      if(!(key in element)) {
+      if(!(expandedProperty in element)) {
         continue;
       }
 
-      var value = element[key];
+      var expandedValue = element[expandedProperty];
 
       // compact @id and @type(s)
-      if(key === '@id' || key === '@type') {
+      if(expandedProperty === '@id' || expandedProperty === '@type') {
+        var compactedValue;
+
         // compact single @id
-        if(_isString(value)) {
-          value = _compactIri(
-            activeCtx, value, null, {base: true, vocab: (key === '@type')});
+        if(_isString(expandedValue)) {
+          compactedValue = _compactIri(
+            activeCtx, expandedValue, null,
+            {base: true, vocab: (expandedProperty === '@type')});
         }
-        // value must be a @type array
+        // expanded value must be a @type array
         else {
-          var types = [];
-          for(var i in value) {
-            types.push(_compactIri(
-              activeCtx, value[i], null, {base: true, vocab: true}));
+          compactedValue = [];
+          for(var vi = 0; vi < expandedValue.length; ++vi) {
+            compactedValue.push(_compactIri(
+              activeCtx, expandedValue[vi], null, {base: true, vocab: true}));
           }
-          value = types;
         }
 
         // use keyword alias and add value
-        var alias = _compactIri(activeCtx, key);
-        var isArray = (_isArray(value) && value.length === 0);
-        jsonld.addValue(rval, alias, value, {propertyIsArray: isArray});
+        var alias = _compactIri(activeCtx, expandedProperty);
+        var isArray = (_isArray(compactedValue) && expandedValue.length === 0);
+        jsonld.addValue(
+          rval, alias, compactedValue, {propertyIsArray: isArray});
         continue;
       }
 
       // handle @annotation property
-      if(key === '@annotation') {
+      if(expandedProperty === '@annotation') {
         // drop @annotation if inside an @annotation container
         var container = jsonld.getContextValue(
-          activeCtx, property, '@container');
+          activeCtx, activeProperty, '@container');
         if(container === '@annotation') {
           continue;
         }
 
         // use keyword alias and add value
-        var alias = _compactIri(activeCtx, key);
-        jsonld.addValue(rval, alias, value);
+        var alias = _compactIri(activeCtx, expandedProperty);
+        jsonld.addValue(rval, alias, expandedValue);
         continue;
       }
 
-      // Note: value must be an array due to expansion algorithm.
+      // Note: expanded value must be an array due to expansion algorithm.
 
       // preserve empty arrays
-      if(value.length === 0) {
-        var term = _compactIri(activeCtx, key, null, {vocab: true}, element);
-        jsonld.addValue(rval, term, [], {propertyIsArray: true});
+      if(expandedValue.length === 0) {
+        var activeProperty = _compactIri(
+          activeCtx, expandedProperty, null, {vocab: true}, element);
+        jsonld.addValue(rval, activeProperty, [], {propertyIsArray: true});
       }
 
       // recusively process array values
-      while(value.length > 0) {
-        var expanded = value.shift();
+      for(var vi = 0; vi < expandedValue.length; ++vi) {
+        var expandedItem = expandedValue[vi];
 
         // compact property and get container type
-        var term = _compactIri(
-          activeCtx, key, expanded, {vocab: true}, element);
-        var container = jsonld.getContextValue(activeCtx, term, '@container');
+        var activeProperty = _compactIri(
+          activeCtx, expandedProperty, expandedItem, {vocab: true}, element);
+        var container = jsonld.getContextValue(
+          activeCtx, activeProperty, '@container');
 
         // remove any duplicates that were (presumably) generated by a
         // property generator
-        var mapping = activeCtx.mappings[term];
+        var mapping = activeCtx.mappings[activeProperty];
         if(mapping && mapping.propertyGenerator) {
           _findAndRemovePropertyGeneratorDuplicates(
-            activeCtx, element, key, expanded, term);
+            activeCtx, element, expandedProperty, expandedItem, activeProperty);
         }
 
         // get @list value if appropriate
-        var isList = _isList(expanded);
+        var isList = _isList(expandedItem);
         var list = null;
         if(isList) {
-          list = expanded['@list'];
+          list = expandedItem['@list'];
         }
 
-        // recursively compact value
-        var compacted = this.compact(
-          activeCtx, term, isList ? list : expanded, options);
+        // recursively compact expanded item
+        var compactedItem = this.compact(
+          activeCtx, activeProperty, isList ? list : expandedItem, options);
 
         // handle @list
         if(isList) {
           // ensure @list value is an array
-          if(!_isArray(compacted)) {
-            compacted = [compacted];
+          if(!_isArray(compactedItem)) {
+            compactedItem = [compactedItem];
           }
 
           if(container !== '@list') {
             // wrap using @list alias
             var wrapper = {};
-            wrapper[_compactIri(activeCtx, '@list')] = compacted;
-            compacted = wrapper;
+            wrapper[_compactIri(activeCtx, '@list')] = compactedItem;
+            compactedItem = wrapper;
 
             // include @annotation from expanded @list, if any
-            if('@annotation' in expanded) {
-              compacted[_compactIri(activeCtx, '@annotation')] =
-                expanded['@annotation'];
+            if('@annotation' in expandedItem) {
+              compactedItem[_compactIri(activeCtx, '@annotation')] =
+                expandedItem['@annotation'];
             }
           }
           // can't use @list container for more than 1 list
-          else if(term in rval) {
+          else if(activeProperty in rval) {
             throw new JsonLdError(
               'JSON-LD compact error; property has a "@list" @container ' +
               'rule but there is more than a single @list that matches ' +
@@ -1523,32 +1521,33 @@
         if(container === '@language' || container === '@annotation') {
           // get or create the map object
           var mapObject;
-          if(term in rval) {
-            mapObject = rval[term];
+          if(activeProperty in rval) {
+            mapObject = rval[activeProperty];
           }
           else {
-            rval[term] = mapObject = {};
+            rval[activeProperty] = mapObject = {};
           }
 
           // if container is a language map, simplify compacted value to
           // a simple string
-          if(container === '@language' && _isValue(compacted)) {
-            compacted = compacted['@value'];
+          if(container === '@language' && _isValue(compactedItem)) {
+            compactedItem = compactedItem['@value'];
           }
 
           // add compact value to map object using key from expanded value
           // based on the container type
-          jsonld.addValue(mapObject, expanded[container], compacted);
+          jsonld.addValue(mapObject, expandedItem[container], compactedItem);
         }
         else {
           // use an array if: @container is @set or @list , value is an empty
           // array, or key is @graph
           var isArray = (container === '@set' || container === '@list' ||
-            (_isArray(compacted) && compacted.length === 0) ||
-            key === '@graph');
+            (_isArray(compactedItem) && compactedItem.length === 0) ||
+            expandedProperty === '@graph');
 
           // add compact value
-          jsonld.addValue(rval, term, compacted, {propertyIsArray: isArray});
+          jsonld.addValue(
+            rval, activeProperty, compactedItem, {propertyIsArray: isArray});
         }
       }
     }
@@ -1566,15 +1565,15 @@
  * before calling this method.
  *
  * @param activeCtx the context to use.
- * @param property the property for the element, null for none.
+ * @param activeProperty the property for the element, null for none.
  * @param element the element to expand.
  * @param options the expansion options.
- * @param propertyIsList true if the property is a list, false if not.
+ * @param insideList true if the element is a list, false if not.
  *
  * @return the expanded value.
  */
 Processor.prototype.expand = function(
-  activeCtx, property, element, options, propertyIsList) {
+  activeCtx, activeProperty, element, options, insideList) {
   var self = this;
 
   if(typeof element === 'undefined') {
@@ -1583,14 +1582,19 @@
       'jsonld.SyntaxError');
   }
 
+  // nothing to expand
+  if(element === null) {
+    return null;
+  }
+
   // recursively expand array
   if(_isArray(element)) {
     var rval = [];
     for(var i in element) {
       // expand element
       var e = self.expand(
-        activeCtx, property, element[i], options, propertyIsList);
-      if(_isArray(e) && propertyIsList) {
+        activeCtx, activeProperty, element[i], options, insideList);
+      if(insideList && (_isArray(e) || _isList(e))) {
         // lists of lists are illegal
         throw new JsonLdError(
           'Invalid JSON-LD syntax; lists of lists are not permitted.',
@@ -1598,7 +1602,12 @@
       }
       // drop null values
       else if(e !== null) {
-        rval.push(e);
+        if(_isArray(e)) {
+          rval = rval.concat(e);
+        }
+        else {
+          rval.push(e);
+        }
       }
     }
     return rval;
@@ -1612,12 +1621,16 @@
       delete element['@context'];
     }
 
+    // expand the active property
+    var expandedActiveProperty = _expandIri(activeCtx, activeProperty);
+
     var rval = {};
     var keys = Object.keys(element).sort();
     for(var ki = 0; ki < keys.length; ++ki) {
       var key = keys[ki];
       var value = element[key];
       var expandedProperty;
+      var expandedValue;
 
       // expand key using property generator
       var mapping = activeCtx.mappings[key];
@@ -1631,14 +1644,9 @@
 
       // drop non-absolute IRI keys that aren't keywords
       if(expandedProperty === null ||
-        (!_isArray(expandedProperty) &&
-        !_isAbsoluteIri(expandedProperty) &&
-        !_isKeyword(expandedProperty, activeCtx))) {
-        continue;
-      }
-
-      // if value is null and property is not @value, continue
-      if(value === null && expandedProperty !== '@value') {
+        !(_isArray(expandedProperty) ||
+        _isAbsoluteIri(expandedProperty) ||
+        _isKeyword(expandedProperty, activeCtx))) {
         continue;
       }
 
@@ -1680,7 +1688,7 @@
             'jsonld.SyntaxError', {value: value});
         }
         // ensure language value is lowercase
-        value = value.toLowerCase();
+        expandedValue = value.toLowerCase();
       }
 
       // preserve @annotation
@@ -1696,11 +1704,11 @@
 
       // handle language map container (skip if value is not an object)
       if(container === '@language' && _isObject(value)) {
-        value = _expandLanguageMap(value);
+        expandedValue = _expandLanguageMap(value);
       }
       // handle annotation container (skip if value is not an object)
       else if(container === '@annotation' && _isObject(value)) {
-        value = (function _expandAnnotation(property) {
+        expandedValue = (function _expandAnnotation(activeProperty) {
           var rval = [];
           var keys = Object.keys(value).sort();
           for(var ki = 0; ki < keys.length; ++ki) {
@@ -1709,7 +1717,7 @@
             if(!_isArray(val)) {
               val = [val];
             }
-            val = self.expand(activeCtx, property, val, options, false);
+            val = self.expand(activeCtx, activeProperty, val, options, false);
             for(var vi = 0; vi < val.length; ++vi) {
               var item = val[vi];
               if(!('@annotation' in item)) {
@@ -1725,18 +1733,13 @@
         // recurse into @list or @set
         var isList = (expandedProperty === '@list');
         if(isList || expandedProperty === '@set') {
-          var activeProperty;
-          if(isList && (property === null || property === '@graph')) {
-            // use '@list' as the active property for top-level lists
-            activeProperty = '@list';
+          var nextActiveProperty = activeProperty;
+          if(isList && expandedActiveProperty === '@graph') {
+            nextActiveProperty = null;
           }
-          else {
-            // keep the current active property
-            activeProperty = property;
-          }
-          value = self.expand(
-            activeCtx, activeProperty, value, options, isList);
-          if(isList && _isList(value)) {
+          expandedValue = self.expand(
+            activeCtx, nextActiveProperty, value, options, isList);
+          if(isList && _isList(expandedValue)) {
             throw new JsonLdError(
               'Invalid JSON-LD syntax; lists of lists are not permitted.',
               'jsonld.SyntaxError');
@@ -1744,50 +1747,30 @@
         }
         else {
           // recursively expand value with key as new active property
-          value = self.expand(activeCtx, key, value, options, false);
+          expandedValue = self.expand(activeCtx, key, value, options, false);
         }
       }
 
       // drop null values if property is not @value
-      if(value === null && expandedProperty !== '@value') {
+      if(expandedValue === null && expandedProperty !== '@value') {
         continue;
       }
 
-      // convert value to @list if container specifies it
-      if(expandedProperty !== '@list' && !_isList(value)) {
-        if(container === '@list') {
-          // ensure value is an array
-          value = _isArray(value) ? value : [value];
-          value = {'@list': value};
-        }
-      }
-
-      // optimize away @id for @type
-      if(expandedProperty === '@type') {
-        if(_isSubjectReference(value)) {
-          value = value['@id'];
-        }
-        else if(_isArray(value)) {
-          var val = [];
-          for(var vi = 0; vi < value.length; ++vi) {
-            var v = value[vi];
-            if(_isSubjectReference(v)) {
-              val.push(v['@id']);
-            }
-            else {
-              val.push(v);
-            }
-          }
-          value = val;
-        }
+      // convert expanded value to @list if container specifies it
+      if(expandedProperty !== '@list' && !_isList(expandedValue) &&
+        container === '@list') {
+        // ensure expanded value is an array
+        expandedValue = (_isArray(expandedValue) ?
+          expandedValue : [expandedValue]);
+        expandedValue = {'@list': expandedValue};
       }
 
       // add copy of value for each property from property generator
       if(_isArray(expandedProperty)) {
-        value = _labelBlankNodes(activeCtx.namer, value);
+        expandedValue = _labelBlankNodes(activeCtx.namer, expandedValue);
         for(var i = 0; i < expandedProperty.length; ++i) {
           jsonld.addValue(
-            rval, expandedProperty[i], _clone(value),
+            rval, expandedProperty[i], _clone(expandedValue),
             {propertyIsArray: true});
         }
       }
@@ -1798,7 +1781,7 @@
           ['@annotation', '@id', '@type', '@value', '@language'].indexOf(
             expandedProperty) === -1;
         jsonld.addValue(
-          rval, expandedProperty, value, {propertyIsArray: useArray});
+          rval, expandedProperty, expandedValue, {propertyIsArray: useArray});
       }
     }
 
@@ -1807,28 +1790,32 @@
     var count = keys.length;
 
     if('@value' in rval) {
-      // do not count @annotation
+      // @value must only have @language or @type
+      if('@type' in rval && '@language' in rval) {
+        throw new JsonLdError(
+          'Invalid JSON-LD syntax; an element containing "@value" may not ' +
+          'contain both "@type" and "@language".',
+          'jsonld.SyntaxError', {element: rval});
+      }
+      var validCount = count - 1;
+      if('@type' in rval) {
+        validCount -= 1;
+      }
       if('@annotation' in rval) {
-        count -= 1;
+        validCount -= 1;
       }
-      // @value must only have @language or @type
-      if((count === 2 && !('@type' in rval) && !('@language' in rval)) ||
-        count > 2) {
+      if('@language' in rval) {
+        validCount -= 1;
+      }
+      if(validCount !== 0) {
         throw new JsonLdError(
           'Invalid JSON-LD syntax; an element containing "@value" may only ' +
           'have an "@annotation" property and at most one other property ' +
           'which can be "@type" or "@language".',
           'jsonld.SyntaxError', {element: rval});
       }
-      // value @type must be a string
-      if('@type' in rval && !_isString(rval['@type'])) {
-        throw new JsonLdError(
-          'Invalid JSON-LD syntax; the "@type" value of an element ' +
-          'containing "@value" must be a string.',
-          'jsonld.SyntaxError', {element: rval});
-      }
       // drop null @values
-      else if(rval['@value'] === null) {
+      if(rval['@value'] === null) {
         rval = null;
       }
       // drop @language if @value isn't a string
@@ -1861,31 +1848,40 @@
       rval = null;
     }
 
-    // drop certain top-level objects
-    if(!options.keepFreeFloatingNodes &&
-      (property === null || property === '@graph')) {
-      // drop empty object or @value not in a list
-      if(count === 0 || ('@value' in rval && !propertyIsList)) {
+    // drop certain top-level objects that do not occur in lists
+    if(!options.keepFreeFloatingNodes && !insideList &&
+      (activeProperty === null || expandedActiveProperty === '@graph')) {
+      // drop empty object or top-level @value
+      if(count === 0 || ('@value' in rval)) {
         rval = null;
       }
-      // drop subjects that generate no triples that are not in a list
-      else if(count === 1 && _isKeyword(keys[0]) &&
-        !('@graph' in rval || '@type' in rval || '@list' in rval ||
-        propertyIsList)) {
-        rval = null;
+      else {
+        // drop subjects that generate no triples
+        var hasTriples = false;
+        for(var ki = 0; !hasTriples && ki < keys.length; ++ki) {
+          if(!_isKeyword(keys[ki]) ||
+            ['@graph', '@type', '@list'].indexOf(keys[ki]) !== -1) {
+            hasTriples = true;
+          }
+        }
+        if(!hasTriples) {
+          rval = null;
+        }
       }
     }
 
     return rval;
   }
 
-  // drop top-level scalars
-  if(property === null || property === '@graph') {
+  // drop top-level scalars that are not in lists
+  if(!insideList &&
+    (activeProperty === null ||
+    _expandIri(activeCtx, activeProperty) === '@graph')) {
     return null;
   }
 
   // expand element according to value expansion rules
-  return _expandValue(activeCtx, property, element, options.base);
+  return _expandValue(activeCtx, activeProperty, element);
 };
 
 /**
@@ -2430,14 +2426,14 @@
           'jsonld.SyntaxError', {context: ctx});
       }
       else {
-        rval['@language'] = value;
+        rval['@language'] = value.toLowerCase();
       }
       defined['@language'] = true;
     }
 
     // process all other keys
     for(var key in ctx) {
-      _defineContextMapping(rval, ctx, key, '@vocab', defined);
+      _createTermDefinition(rval, ctx, key, defined);
     }
   }
 
@@ -2485,32 +2481,32 @@
  * Labels the blank nodes in the given value using the given UniqueNamer.
  *
  * @param namer the UniqueNamer to use.
- * @param value the value with blank nodes to rename.
+ * @param element the element with blank nodes to rename.
  *
  * @return a copy of value with renamed blank nodes.
  */
-function _labelBlankNodes(namer, value) {
-  if(value && typeof value === 'object') {
-    if(_isArray(value)) {
+function _labelBlankNodes(namer, element) {
+  if(element && typeof element === 'object') {
+    if(_isArray(element)) {
       var rval = [];
-      for(var i = 0; i < value.length; ++i) {
-        rval[i] = _labelBlankNodes(namer, value[i]);
+      for(var i = 0; i < element.length; ++i) {
+        rval[i] = _labelBlankNodes(namer, element[i]);
       }
       return rval;
     }
 
     // recursively apply to @list
     var rval = {};
-    if('@list' in value) {
-      rval['@list'] = _labelBlankNodes(namer, value['@list']);
+    if('@list' in element) {
+      rval['@list'] = _labelBlankNodes(namer, element['@list']);
       return rval;
     }
 
     // recursively apply to all keys
-    var keys = Object.keys(value).sort();
+    var keys = Object.keys(element).sort();
     for(var i in keys) {
       var key = keys[i];
-      rval[key] = _labelBlankNodes(namer, value[key]);
+      rval[key] = _labelBlankNodes(namer, element[key]);
     }
     // rename blank node
     if(_isBlankNode(rval)) {
@@ -2518,69 +2514,66 @@
     }
     return rval;
   }
-  return value;
+  return element;
 }
 
 /**
  * Expands the given value by using the coercion and keyword rules in the
  * given context.
  *
- * @param ctx the active context to use.
- * @param property the property the value is associated with.
+ * @param activeCtx the active context to use.
+ * @param activeProperty the active property the value is associated with.
  * @param value the value to expand.
- * @param relativeTo options for how to resolve relative IRIs:
- *          base: true to resolve against the base IRI, false not to.
- *          vocab: true to concatenate after @vocab, false not to.
  *
  * @return the expanded value.
  */
-function _expandValue(activeCtx, property, value, relativeTo) {
+function _expandValue(activeCtx, activeProperty, value) {
   // nothing to expand
   if(value === null) {
     return null;
   }
 
-  // default to simple string return value
-  var rval = value;
-
   // special-case expand @id and @type (skips '@id' expansion)
-  var prop = _expandIri(activeCtx, property, {vocab: true});
-  if(prop === '@id') {
-    rval = _expandIri(activeCtx, value, {base: true});
-  }
-  else if(prop === '@type') {
-    rval = _expandIri(activeCtx, value, {vocab: true, base: true});
-  }
-  else {
-    // get type definition from context
-    var type = jsonld.getContextValue(activeCtx, property, '@type');
-
-    // do @id expansion (automatic for @graph)
-    if(type === '@id' || prop === '@graph') {
-      rval = {'@id': _expandIri(activeCtx, value, {base: true})};
-    }
-    // do not expand @value, @language, etc. values, but @list is special
-    // and must be processed
-    else if(prop === '@list' || !_isKeyword(prop)) {
-      rval = {};
-      // other type
-      if(type !== null) {
-        // rename blank node if requested
-        if(activeCtx.namer && type.indexOf('_:') === 0) {
-          type = activeCtx.namer.getName(type);
-        }
-        rval['@type'] = type;
-      }
-      // check for language tagging
-      else {
-        var language = jsonld.getContextValue(activeCtx, property, '@language');
-        if(language !== null && _isString(value)) {
-          rval['@language'] = language;
-        }
-      }
-      rval['@value'] = value;
-    }
-  }
+  var expandedProperty = _expandIri(activeCtx, activeProperty, {vocab: true});
+  if(expandedProperty === '@id') {
+    return _expandIri(activeCtx, value, {base: true});
+  }
+  else if(expandedProperty === '@type') {
+    return _expandIri(activeCtx, value, {vocab: true, base: true});
+  }
+
+  // get type definition from context
+  var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
+
+  // do @id expansion (automatic for @graph)
+  if(type === '@id' || expandedProperty === '@graph') {
+    return {'@id': _expandIri(activeCtx, value, {base: true})};
+  }
+
+  // do not expand keyword values
+  if(_isKeyword(expandedProperty)) {
+    return value;
+  }
+
+  rval = {};
+
+  // other type
+  if(type !== null) {
+    // rename blank node if requested
+    if(activeCtx.namer && type.indexOf('_:') === 0) {
+      type = activeCtx.namer.getName(type);
+    }
+    rval['@type'] = type;
+  }
+  // check for language tagging for strings
+  else if(_isString(value)) {
+    var language = jsonld.getContextValue(
+      activeCtx, activeProperty, '@language');
+    if(language !== null) {
+      rval['@language'] = language;
+    }
+  }
+  rval['@value'] = value;
 
   return rval;
 }
@@ -3918,18 +3911,20 @@
  * property.
  *
  * @param activeCtx the active context.
- * @param property the compact property that points to the element.
+ * @param activeProperty the active property that points to the element.
  * @param element the element to compact.
  *
  * @return the compaction result.
  */
-function _compactValue(activeCtx, property, element) {
+function _compactValue(activeCtx, activeProperty, element) {
   // element is a @value
   if(_isValue(element)) {
     // get context rules
-    var type = jsonld.getContextValue(activeCtx, property, '@type');
-    var language = jsonld.getContextValue(activeCtx, property, '@language');
-    var container = jsonld.getContextValue(activeCtx, property, '@container');
+    var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
+    var language = jsonld.getContextValue(
+      activeCtx, activeProperty, '@language');
+    var container = jsonld.getContextValue(
+      activeCtx, activeProperty, '@container');
 
     // whether or not the element has an @annotation that must be preserved
     var preserveAnnotation = (('@annotation' in element) &&
@@ -3950,14 +3945,14 @@
     // return just the value of @value if all are true:
     // 1. @value is the only key or @annotation isn't being preserved
     // 2. there is no default language or @value is not a string or
-    //   the property has a mapping with a null @language
+    //   the key has a mapping with a null @language
     var keyCount = Object.keys(element).length;
     var isValueOnlyKey = (keyCount === 1 ||
       (keyCount === 2 && ('@annotation' in element) && !preserveAnnotation));
     var hasDefaultLanguage = ('@language' in activeCtx);
     var isValueString = _isString(element['@value']);
-    var hasNullMapping = (activeCtx.mappings[property] &&
-      activeCtx.mappings[property]['@language'] === null);
+    var hasNullMapping = (activeCtx.mappings[activeProperty] &&
+      activeCtx.mappings[activeProperty]['@language'] === null);
     if(isValueOnlyKey &&
       (!hasDefaultLanguage || !isValueString || hasNullMapping)) {
       return element['@value'];
@@ -3987,8 +3982,8 @@
   }
 
   // element is a subject reference
-  var expandedProperty = _expandIri(activeCtx, property);
-  var type = jsonld.getContextValue(activeCtx, property, '@type');
+  var expandedProperty = _expandIri(activeCtx, activeProperty);
+  var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
   var term = _compactIri(activeCtx, element['@id'], null, {base: true});
 
   // compact to scalar
@@ -4007,20 +4002,20 @@
  *
  * @param activeCtx the active context.
  * @param element the element to remove duplicates from.
- * @param key the key that is being mapped to a property generator.
+ * @param expandedProperty the property to map to a property generator.
  * @param value the value to compare against when duplicate checking.
- * @param term the property generator term.
+ * @param activeProperty the property generator term.
  */
 function _findAndRemovePropertyGeneratorDuplicates(
-  activeCtx, element, key, value, term) {
+  activeCtx, element, expandedProperty, value, activeProperty) {
   // get property generator IRIs
-  var iris = activeCtx.mappings[term]['@id'];
-
-  // for each IRI that isn't 'key', remove a single duplicate from element,
-  // if found
+  var iris = activeCtx.mappings[activeProperty]['@id'];
+
+  // for each IRI that isn't 'expandedProperty', remove a single duplicate
+  // from element, if found
   for(var i = 0; i < iris.length; ++i) {
     var iri = iris[i];
-    if(iri === key) {
+    if(iri === expandedProperty) {
       continue;
     }
     var prospects = element[iri];
@@ -4039,47 +4034,44 @@
 }
 
 /**
- * Defines a context mapping during context processing.
+ * Creates a term definition during context processing.
  *
  * @param activeCtx the current active context.
  * @param localCtx the local context being processed.
- * @param key the key in the local context to define the mapping for.
- * @param relativeTo options for how to resolve relative IRIs:
- *          base: true to resolve against the base IRI, false not to.
- *          vocab: true to concatenate after @vocab, false not to.
+ * @param term the term in the local context to define the mapping for.
  * @param defined a map of defining/defined keys to detect cycles and prevent
  *          double definitions.
  */
-function _defineContextMapping(activeCtx, localCtx, key, relativeTo, defined) {
-  if(key in defined) {
-    // key already defined
-    if(defined[key]) {
+function _createTermDefinition(activeCtx, localCtx, term, defined) {
+  if(term in defined) {
+    // term already defined
+    if(defined[term]) {
       return;
     }
     // cycle detected
     throw new JsonLdError(
       'Cyclical context definition detected.',
-      'jsonld.CyclicalContext', {context: localCtx, key: key});
-  }
-
-  // now defining key
-  defined[key] = false;
-
-  // if key has a prefix, define it first
-  var colon = key.indexOf(':');
+      'jsonld.CyclicalContext', {context: localCtx, term: term});
+  }
+
+  // now defining term
+  defined[term] = false;
+
+  // if term has a prefix, define it first
+  var colon = term.indexOf(':');
   var prefix = null;
   if(colon !== -1) {
-    prefix = key.substr(0, colon);
+    prefix = term.substr(0, colon);
     if(prefix in localCtx) {
       // define parent prefix
-      _defineContextMapping(activeCtx, localCtx, prefix, {base: true}, defined);
-    }
-  }
-
-  // get context key value
-  var value = localCtx[key];
-
-  if(_isKeyword(key)) {
+      _createTermDefinition(activeCtx, localCtx, prefix, defined);
+    }
+  }
+
+  // get context term value
+  var value = localCtx[term];
+
+  if(_isKeyword(term)) {
     throw new JsonLdError(
       'Invalid JSON-LD syntax; keywords cannot be overridden.',
       'jsonld.SyntaxError', {context: localCtx});
@@ -4087,16 +4079,16 @@
 
   // clear context entry
   if(value === null || (_isObject(value) && value['@id'] === null)) {
-    if(activeCtx.mappings[key]) {
-      // if key is a keyword alias, remove it
-      var kw = activeCtx.mappings[key]['@id'];
+    if(activeCtx.mappings[term]) {
+      // if term is a keyword alias, remove it
+      var kw = activeCtx.mappings[term]['@id'];
       if(_isKeyword(kw)) {
         var aliases = activeCtx.keywords[kw];
-        aliases.splice(aliases.indexOf(key), 1);
+        aliases.splice(aliases.indexOf(term), 1);
       }
     }
-    activeCtx.mappings[key] = null;
-    defined[key] = true;
+    activeCtx.mappings[term] = null;
+    defined[term] = true;
     return;
   }
 
@@ -4109,10 +4101,10 @@
           'jsonld.SyntaxError');
       }
 
-      // uniquely add key as a keyword alias and resort
+      // uniquely add term as a keyword alias and resort
       var aliases = activeCtx.keywords[value];
-      if(aliases.indexOf(key) === -1) {
-        aliases.push(key);
+      if(aliases.indexOf(term) === -1) {
+        aliases.push(term);
         aliases.sort(_compareShortestLeast);
       }
     }
@@ -4121,9 +4113,9 @@
       value = _expandIri(activeCtx, value, {base: true}, localCtx, defined);
     }
 
-    // define/redefine key to expanded IRI/keyword
-    activeCtx.mappings[key] = {'@id': value};
-    defined[key] = true;
+    // define/redefine term to expanded IRI/keyword
+    activeCtx.mappings[term] = {'@id': value};
+    defined[term] = true;
     return;
   }
 
@@ -4138,6 +4130,12 @@
   var mapping = {};
   mapping.propertyGenerator = false;
 
+  // merge onto parent mapping if one exists for a prefix
+  if(prefix !== null && activeCtx.mappings[prefix]) {
+    // FIXME: check to see if additional clone is necessary here
+    mapping = _clone(activeCtx.mappings[prefix]);
+  }
+
   if('@id' in value) {
     var id = value['@id'];
     // handle property generator
@@ -4190,19 +4188,19 @@
       if(!('@vocab' in activeCtx)) {
         throw new JsonLdError(
           'Invalid JSON-LD syntax; @context terms must define an @id.',
-          'jsonld.SyntaxError', {context: localCtx, key: key});
+          'jsonld.SyntaxError', {context: localCtx, term: term});
       }
       // prepend vocab to term
-      mapping['@id'] = activeCtx['@vocab'] + key;
+      mapping['@id'] = activeCtx['@vocab'] + term;
     }
     // set @id based on prefix parent
     else if(prefix in activeCtx.mappings) {
-      var suffix = key.substr(colon + 1);
+      var suffix = term.substr(colon + 1);
       mapping['@id'] = activeCtx.mappings[prefix]['@id'] + suffix;
     }
-    // key is an absolute IRI
+    // term is an absolute IRI
     else {
-      mapping['@id'] = key;
+      mapping['@id'] = term;
     }
   }
 
@@ -4248,6 +4246,9 @@
     }
 
     // add @language to mapping
+    if(language !== null) {
+      language = language.toLowerCase();
+    }
     mapping['@language'] = language;
   }
 
@@ -4261,9 +4262,9 @@
     }
   }
 
-  // define key mapping
-  activeCtx.mappings[key] = mapping;
-  defined[key] = true;
+  // define term mapping
+  activeCtx.mappings[term] = mapping;
+  defined[term] = true;
 }
 
 /**
@@ -4291,11 +4292,16 @@
 
   // define term dependency if not defined
   if(localCtx && value in localCtx && defined[value] !== true) {
-    _defineContextMapping(activeCtx, localCtx, value, {vocab: true}, defined);
+    _createTermDefinition(activeCtx, localCtx, value, defined);
   }
 
   var mapping = activeCtx.mappings[value];
 
+  // value is explicitly ignored with a null mapping
+  if(mapping === null) {
+    return null;
+  }
+
   // term dependency cannot be a property generator
   if(localCtx && mapping && mapping.propertyGenerator) {
     throw new JsonLdError(
@@ -4304,44 +4310,41 @@
       'jsonld.SyntaxError', {context: localCtx, value: value});
   }
 
-  // value is explicitly ignored with a null mapping
-  if(mapping === null) {
-    return null;
-  }
-
   var isAbsolute = false;
+  var rval = value;
 
   // value is a term
   if(mapping && !mapping.propertyGenerator) {
     isAbsolute = true;
-    value = mapping['@id'];
+    rval = mapping['@id'];
   }
 
   // keywords need no expanding (aliasing already handled by now)
-  if(_isKeyword(value)) {
-    return value;
-  }
-
-  // split value into prefix:suffix
-  var colon = value.indexOf(':');
-  if(colon !== -1) {
-    isAbsolute = true;
-    var prefix = value.substr(0, colon);
-    var suffix = value.substr(colon + 1);
-
-    // do not expand blank nodes (prefix of '_') or already-absolute
-    // IRIs (suffix of '//')
-    if(prefix !== '_' && suffix.indexOf('//') !== 0) {
-      // prefix dependency not defined, define it
-      if(localCtx && prefix in localCtx && defined[prefix] !== true) {
-        _defineContextMapping(
-          activeCtx, localCtx, prefix, {base: true}, defined);
-      }
-
-      // use mapping if prefix is defined and not a property generator
-      mapping = activeCtx.mappings[prefix];
-      if(mapping && !mapping.propertyGenerator) {
-        value = activeCtx.mappings[prefix]['@id'] + suffix;
+  if(_isKeyword(rval)) {
+    return rval;
+  }
+
+  if(!isAbsolute) {
+    // split value into prefix:suffix
+    var colon = rval.indexOf(':');
+    if(colon !== -1) {
+      isAbsolute = true;
+      var prefix = rval.substr(0, colon);
+      var suffix = rval.substr(colon + 1);
+
+      // do not expand blank nodes (prefix of '_') or already-absolute
+      // IRIs (suffix of '//')
+      if(prefix !== '_' && suffix.indexOf('//') !== 0) {
+        // prefix dependency not defined, define it
+        if(localCtx && prefix in localCtx && defined[prefix] !== true) {
+          _createTermDefinition(activeCtx, localCtx, prefix, defined);
+        }
+
+        // use mapping if prefix is defined and not a property generator
+        mapping = activeCtx.mappings[prefix];
+        if(mapping && !mapping.propertyGenerator) {
+          rval = activeCtx.mappings[prefix]['@id'] + suffix;
+        }
       }
     }
   }
@@ -4349,22 +4352,22 @@
   relativeTo = relativeTo || {};
   if(isAbsolute) {
     // rename blank node if requested
-    if(!localCtx && value.indexOf('_:') === 0 && activeCtx.namer) {
-      value = activeCtx.namer.getName(value);
+    if(!localCtx && rval.indexOf('_:') === 0 && activeCtx.namer) {
+      rval = activeCtx.namer.getName(rval);
     }
   }
   // prepend vocab
   else if(relativeTo.vocab && '@vocab' in activeCtx) {
-    value = activeCtx['@vocab'] + value;
+    rval = activeCtx['@vocab'] + rval;
   }
   // prepend base
   else if(relativeTo.base) {
-    value = _prependBase(activeCtx['@base'], value);
+    rval = _prependBase(activeCtx['@base'], rval);
   }
 
   if(localCtx) {
     // value must now be an absolute IRI
-    if(!_isAbsoluteIri(value)) {
+    if(!_isAbsoluteIri(rval)) {
       throw new JsonLdError(
         'Invalid JSON-LD syntax; a @context value does not expand to ' +
         'an absolute IRI.',
@@ -4372,7 +4375,7 @@
     }
   }
 
-  return value;
+  return rval;
 }
 
 /**