Update to latest jsonld.js, related fixes.
authorDave Longley <dlongley@digitalbazaar.com>
Wed, 20 Feb 2013 01:16:28 -0500
changeset 1316 2b7d281fcb0b
parent 1315 24cba2a4fddc
child 1317 fb5b4fc612e5
Update to latest jsonld.js, related fixes.
playground/jsonld-turtle.js
playground/jsonld.js
playground/playground.js
--- a/playground/jsonld-turtle.js	Tue Feb 19 21:20:44 2013 -0500
+++ b/playground/jsonld-turtle.js	Wed Feb 20 01:16:28 2013 -0500
@@ -4,7 +4,7 @@
  * @author Manu Sporny <[email protected]>
  * @author Dave Longley <[email protected]>
  *
- * Copyright (c) 2011-2012 Digital Bazaar, Inc. All rights reserved.
+ * Copyright (c) 2011-2013 Digital Bazaar, Inc. All rights reserved.
  */
 (function() {
 
@@ -61,7 +61,7 @@
  * @return the string representation of the object.
  */
 function objectToString(obj) {
-  var rval;
+  var rval = '';
 
   if(obj instanceof Array) {
     // if the object is an array, convert each object in the list
--- a/playground/jsonld.js	Tue Feb 19 21:20:44 2013 -0500
+++ b/playground/jsonld.js	Wed Feb 20 01:16:28 2013 -0500
@@ -558,28 +558,24 @@
 };
 
 /**
- * Performs RDF normalization on the given JSON-LD input. The output is
- * a sorted array of RDF statements unless the 'format' option is used.
+ * Performs RDF dataset normalization on the given JSON-LD input. The output
+ * is an RDF dataset unless the 'format' option is used.
  *
  * @param input the JSON-LD input to normalize.
  * @param [options] the options to use:
  *          [base] the base IRI to use.
- * @param [options] the options to use:
  *          [format] the format if output is a string:
  *            'application/nquads' for N-Quads.
  *          [loadContext(url, callback(err, url, result))] the context loader.
  * @param callback(err, normalized) called once the operation completes.
  */
-jsonld.normalize = function(input, callback) {
+jsonld.normalize = function(input, options, callback) {
   // get arguments
-  var options = {};
-  var callback;
-  var callbackArg = 1;
-  if(arguments.length > 2) {
-    options = arguments[1] || {};
-    callbackArg += 1;
-  }
-  callback = arguments[callbackArg];
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
 
   // set default options
   if(!('base' in options)) {
@@ -589,25 +585,26 @@
     options.loadContext = jsonld.loadContext;
   }
 
-  // expand input then do normalization
-  jsonld.expand(input, options, function(err, expanded) {
+  // convert to RDF dataset then do normalization
+  var opts = _clone(options);
+  delete opts.format;
+  jsonld.toRDF(input, opts, function(err, dataset) {
     if(err) {
       return callback(new JsonLdError(
-        'Could not expand input before normalization.',
+        'Could not convert input to RDF dataset before normalization.',
         'jsonld.NormalizeError', {cause: err}));
     }
 
     // do normalization
-    new Processor().normalize(expanded, options, callback);
+    new Processor().normalize(dataset, options, callback);
   });
 };
 
 /**
- * Converts RDF statements into JSON-LD.
- *
- * @param statements a serialized string of RDF statements in a format
- *          specified by the format option or an array of the RDF statements
- *          to convert.
+ * Converts an RDF dataset to JSON-LD.
+ *
+ * @param dataset a serialized string of RDF in a format specified by the
+ *          format option or an RDF dataset to convert.
  * @param [options] the options to use:
  *          [format] the format if input is not an array:
  *            'application/nquads' for N-Quads (default).
@@ -618,16 +615,13 @@
  *
  * @param callback(err, output) called once the operation completes.
  */
-jsonld.fromRDF = function(statements) {
+jsonld.fromRDF = function(dataset, options, callback) {
   // get arguments
-  var options = {};
-  var callback;
-  var callbackArg = 1;
-  if(arguments.length > 2) {
-    options = arguments[1] || {};
-    callbackArg += 1;
-  }
-  callback = arguments[callbackArg];
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
 
   // set default options
   if(!('useRdfType' in options)) {
@@ -637,7 +631,7 @@
     options.useNativeTypes = true;
   }
 
-  if(!('format' in options) && !_isArray(statements)) {
+  if(!('format' in options) && _isString(dataset)) {
     // set default format to nquads
     if(!('format' in options)) {
       options.format = 'application/nquads';
@@ -645,10 +639,10 @@
   }
 
   // handle special format
-  if('format' in options) {
+  if(options.format) {
     // supported formats
     if(options.format in _rdfParsers) {
-      statements = _rdfParsers[options.format](statements);
+      dataset = _rdfParsers[options.format](dataset);
     }
     else {
       throw new JsonLdError(
@@ -658,34 +652,27 @@
   }
 
   // convert from RDF
-  new Processor().fromRDF(statements, options, callback);
+  new Processor().fromRDF(dataset, options, callback);
 };
 
 /**
- * Outputs the RDF statements found in the given JSON-LD object.
+ * Outputs the RDF dataset found in the given JSON-LD object.
  *
  * @param input the JSON-LD input.
  * @param [options] the options to use:
  *          [base] the base IRI to use.
  *          [format] the format to use to output a string:
  *            'application/nquads' for N-Quads (default).
- *          [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).
  *          [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.
+ * @param callback(err, dataset) called once the operation completes.
  */
-jsonld.toRDF = function(input) {
+jsonld.toRDF = function(input, options, callback) {
   // get arguments
-  var options = {};
-  var callback;
-  var callbackArg = 1;
-  if(arguments.length > 2) {
-    options = arguments[1] || {};
-    callbackArg += 1;
-  }
-  callback = arguments[callbackArg];
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
 
   // set default options
   if(!('base' in options)) {
@@ -694,57 +681,6 @@
   if(!('loadContext' in options)) {
     options.loadContext = jsonld.loadContext;
   }
-  if(!('collate' in options)) {
-    options.collate = false;
-  }
-
-  if(options.collate) {
-    // output array/string of statements all at once
-    var statements = [];
-    var collateCallback = callback;
-    callback = function(err, statement) {
-      if(err) {
-        return collateCallback(err);
-      }
-      if(statement !== null) {
-        // do not allow duplicate statements
-        for(var i in statements) {
-          if(_compareRdfStatements(statements[i], statement)) {
-            return;
-          }
-        }
-        statements.push(statement);
-      }
-      else {
-        // if outputting a string, sort and join statements
-        if('format' in options) {
-          statements = statements.sort().join('');
-        }
-        collateCallback(null, statements);
-      }
-    };
-  }
-
-  if('format' in options) {
-    // supported formats
-    if(options.format === 'application/nquads') {
-      var statementCallback = callback;
-      callback = function(err, statement) {
-        if(err) {
-          return statementCallback(err);
-        }
-        if(statement !== null) {
-          statement = _toNQuad(statement);
-        }
-        statementCallback(null, statement);
-      };
-    }
-    else {
-      throw new JsonLdError(
-        'Unknown output format.',
-        'jsonld.UnknownFormat', {format: options.format});
-    }
-  }
 
   // expand input
   jsonld.expand(input, options, function(err, expanded) {
@@ -754,10 +690,23 @@
         'jsonld.RdfError', {cause: err}));
     }
 
+    // create node map for default graph (and any named graphs)
+    var namer = new UniqueNamer('_:b');
+    var nodeMap = {'@default': {}};
+    _createNodeMap(expanded, nodeMap, '@default', namer);
+
     try {
-      // output RDF statements
-      var namer = new UniqueNamer('_:b');
-      new Processor().toRDF(expanded, namer, null, null, null, callback);
+      // output RDF dataset
+      var dataset = Processor.prototype.toRDF(nodeMap);
+      if(options.format) {
+        if(options.format === 'application/nquads') {
+          return callback(null, _toNQuads(dataset));
+        }
+        throw new JsonLdError(
+          'Unknown output format.',
+          'jsonld.UnknownFormat', {format: options.format});
+      }
+      callback(null, dataset);
     }
     catch(ex) {
       callback(ex);
@@ -787,6 +736,23 @@
 
 /* Utility API */
 
+// define nextTick
+if(typeof process === 'undefined' || !process.nextTick) {
+  if(typeof setImmediate === 'function') {
+    jsonld.nextTick = function(callback) {
+      setImmediate(callback);
+    };
+  }
+  else {
+    jsonld.nextTick = function(callback) {
+      setTimeout(callback, 0);
+    };
+  }
+}
+else {
+  jsonld.nextTick = process.nextTick;
+}
+
 /**
  * Creates a simple context cache.
  *
@@ -1317,23 +1283,23 @@
   return rval;
 };
 
-/** Registered RDF Statement parsers hashed by content-type. */
+/** Registered RDF dataset parsers hashed by content-type. */
 var _rdfParsers = {};
 
 /**
- * Registers an RDF Statement parser by content-type, for use with
+ * Registers an RDF dataset parser by content-type, for use with
  * jsonld.fromRDF.
  *
  * @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 RDF dataset).
  */
 jsonld.registerRDFParser = function(contentType, parser) {
   _rdfParsers[contentType] = parser;
 };
 
 /**
- * Unregisters an RDF Statement parser by content-type.
+ * Unregisters an RDF dataset parser by content-type.
  *
  * @param contentType the content-type for the parser.
  */
@@ -1378,6 +1344,7 @@
 var RDF_PLAIN_LITERAL = RDF + 'PlainLiteral';
 var RDF_XML_LITERAL = RDF + 'XMLLiteral';
 var RDF_OBJECT = RDF + 'object';
+var RDF_LANGSTRING = RDF + 'langString';
 
 var MAX_CONTEXT_URLS = 10;
 
@@ -2040,50 +2007,52 @@
 };
 
 /**
- * Performs RDF normalization on the given JSON-LD input.
- *
- * @param input the expanded JSON-LD object to normalize.
+ * Performs normalization on the given RDF dataset.
+ *
+ * @param dataset the RDF dataset to normalize.
  * @param options the normalization options.
  * @param callback(err, normalized) called once the operation completes.
  */
-Processor.prototype.normalize = function(input, options, callback) {
-  // map bnodes to RDF statements
-  var statements = [];
+Processor.prototype.normalize = function(dataset, options, callback) {
+  // create quads and map bnodes to their associated quads
+  var quads = [];
   var bnodes = {};
-  var namer = new UniqueNamer('_:b');
-  new Processor().toRDF(input, namer, null, null, null, mapStatements);
-
-  // maps bnodes to their statements and then start bnode naming
-  function mapStatements(err, statement) {
-    if(err) {
-      return callback(err);
-    }
-    if(statement === null) {
-      // mapping complete, start canonical naming
-      namer = new UniqueNamer('_:c14n');
-      return hashBlankNodes(Object.keys(bnodes));
-    }
-    // add statement and do mapping
-    for(var i in statements) {
-      if(_compareRdfStatements(statements[i], statement)) {
-        return;
-      }
-    }
-    statements.push(statement);
-    var nodes = ['subject', 'object', 'name'];
-    for(var n in nodes) {
-      var node = nodes[n];
-      if(statement[node] && statement[node].interfaceName === 'BlankNode') {
-        var id = statement[node].nominalValue;
-        if(id in bnodes) {
-          bnodes[id].statements.push(statement);
+  for(var graphName in dataset) {
+    var triples = dataset[graphName];
+    if(graphName === '@default') {
+      graphName = null;
+    }
+    for(var ti = 0; ti < triples.length; ++ti) {
+      var quad = triples[ti];
+      if(graphName !== null) {
+        if(graphName.indexOf('_:') === 0) {
+          quad.name = {type: 'blank node', value: graphName};
         }
         else {
-          bnodes[id] = {statements: [statement]};
+          quad.name = {type: 'IRI', value: graphName};
         }
       }
-    }
-  }
+      quads.push(quad);
+
+      var attrs = ['subject', 'object', 'name'];
+      for(var ai = 0; ai < attrs.length; ++ai) {
+        var attr = attrs[ai];
+        if(quad[attr] && quad[attr].type === 'blank node') {
+          var id = quad[attr].value;
+          if(id in bnodes) {
+            bnodes[id].quads.push(quad);
+          }
+          else {
+            bnodes[id] = {quads: [quad]};
+          }
+        }
+      }
+    }
+  }
+
+  // mapping complete, start canonical naming
+  var namer = new UniqueNamer('_:c14n');
+  return hashBlankNodes(Object.keys(bnodes));
 
   // generates unique and duplicate hashes for bnodes
   function hashBlankNodes(unnamed) {
@@ -2091,8 +2060,8 @@
     var duplicates = {};
     var unique = {};
 
-    // hash statements for each unnamed bnode
-    setTimeout(function() {hashUnnamed(0);}, 0);
+    // hash quads for each unnamed bnode
+    jsonld.nextTick(function() {hashUnnamed(0);});
     function hashUnnamed(i) {
       if(i === unnamed.length) {
         // done, name blank nodes
@@ -2101,7 +2070,7 @@
 
       // hash unnamed bnode
       var bnode = unnamed[i];
-      var hash = _hashStatements(bnode, bnodes, namer);
+      var hash = _hashQuads(bnode, bnodes, namer);
 
       // store hash as unique or a duplicate
       if(hash in duplicates) {
@@ -2119,7 +2088,7 @@
       }
 
       // hash next unnamed bnode
-      setTimeout(function() {hashUnnamed(i + 1);}, 0);
+      jsonld.nextTick(function() {hashUnnamed(i + 1);});
     }
   }
 
@@ -2128,7 +2097,7 @@
     // name unique bnodes in sorted hash order
     var named = false;
     var hashes = Object.keys(unique).sort();
-    for(var i in hashes) {
+    for(var i = 0; i < hashes.length; ++i) {
       var bnode = unique[hashes[i]];
       namer.getName(bnode);
       named = true;
@@ -2200,95 +2169,75 @@
     }
   }
 
-  // creates the sorted array of RDF statements
+  // creates the sorted array of RDF quads
   function createArray() {
     var normalized = [];
 
-    // update bnode names in each statement and serialize
-    for(var i in statements) {
-      var statement = statements[i];
-      var nodes = ['subject', 'object', 'name'];
-      for(var n in nodes) {
-        var node = nodes[n];
-        if(statement[node] && statement[node].interfaceName === 'BlankNode' &&
-          statement[node].nominalValue.indexOf('_:c14n') !== 0) {
-          statement[node].nominalValue = namer.getName(
-            statement[node].nominalValue);
+    // update bnode names in each quad and serialize
+    for(var i = 0; i < quads.length; ++i) {
+      var quad = quads[i];
+      var attrs = ['subject', 'object', 'name'];
+      for(var ai = 0; ai < attrs.length; ++ai) {
+        var attr = attrs[ai];
+        if(quad[attr] && quad[attr].type === 'blank node' &&
+          quad[attr].value.indexOf('_:c14n') !== 0) {
+          quad[attr].value = namer.getName(quad[attr].value);
         }
       }
-      normalized.push(_toNQuad(statement));
+      normalized.push(_toNQuad(quad, quad.name ? quad.name.value : null));
     }
 
     // sort normalized output
     normalized.sort();
 
     // handle output format
-    if('format' in options) {
+    if(options.format) {
       if(options.format === 'application/nquads') {
         return callback(null, normalized.join(''));
       }
-      else {
-        return callback(new JsonLdError(
-          'Unknown output format.',
-          'jsonld.UnknownFormat', {format: options.format}));
-      }
-    }
-
-    // output parsed RDF statements
+      return callback(new JsonLdError(
+        'Unknown output format.',
+        'jsonld.UnknownFormat', {format: options.format}));
+    }
+
+    // output RDF dataset
     callback(null, _parseNQuads(normalized.join('')));
   }
 };
 
 /**
- * Converts RDF statements into JSON-LD.
- *
- * @param statements the RDF statements.
+ * Converts an RDF dataset to JSON-LD.
+ *
+ * @param dataset the RDF dataset.
  * @param options the RDF conversion options.
  * @param callback(err, output) called once the operation completes.
  */
-Processor.prototype.fromRDF = function(statements, options, callback) {
+Processor.prototype.fromRDF = function(dataset, options, callback) {
   // prepare graph map (maps graph name => subjects, lists)
   var defaultGraph = {subjects: {}, listMap: {}};
-  var graphs = {'': defaultGraph};
-
-  for(var i in statements) {
-    var statement = statements[i];
-
-    // get subject, property, object, and graph name (default to '')
-    var s = statement.subject.nominalValue;
-    var p = statement.property.nominalValue;
-    var o = statement.object;
-    var name = ('name' in statement) ? statement.name.nominalValue : '';
-
-    // create a graph entry as needed
-    var graph;
-    if(!(name in graphs)) {
-      graph = graphs[name] = {subjects: {}, listMap: {}};
-    }
-    else {
-      graph = graphs[name];
-    }
-
-    // handle element in @list
-    if(p === RDF_FIRST) {
-      // create list entry as needed
-      var listMap = graph.listMap;
-      var entry;
-      if(!(s in listMap)) {
-        entry = listMap[s] = {};
+  var graphs = {'@default': defaultGraph};
+
+  for(var graphName in dataset) {
+    var triples = dataset[graphName];
+    for(var ti = 0; ti < triples.length; ++ti) {
+      var triple = triples[ti];
+
+      // get subject, predicate, object
+      var s = triple.subject.value;
+      var p = triple.predicate.value;
+      var o = triple.object;
+
+      // create a graph entry as needed
+      var graph;
+      if(!(graphName in graphs)) {
+        graph = graphs[graphName] = {subjects: {}, listMap: {}};
       }
       else {
-        entry = listMap[s];
-      }
-      // set object value
-      entry.first = _rdfToObject(o, options.useNativeTypes);
-      continue;
-    }
-
-    // handle other element in @list
-    if(p === RDF_REST) {
-      // set next in list
-      if(o.interfaceName === 'BlankNode') {
+        graph = graphs[graphName];
+      }
+
+      // handle element in @list
+      if(p === RDF_FIRST) {
         // create list entry as needed
         var listMap = graph.listMap;
         var entry;
@@ -2298,56 +2247,75 @@
         else {
           entry = listMap[s];
         }
-        entry.rest = o.nominalValue;
-      }
-      continue;
-    }
-
-    // add graph subject to default graph as needed
-    if(name !== '' && !(name in defaultGraph.subjects)) {
-      defaultGraph.subjects[name] = {'@id': name};
-    }
-
-    // add subject to graph as needed
-    var subjects = graph.subjects;
-    var value;
-    if(!(s in subjects)) {
-      value = subjects[s] = {'@id': s};
-    }
-    // use existing subject value
-    else {
-      value = subjects[s];
-    }
-
-    // convert to @type unless options indicate to treat rdf:type as property
-    if(p === RDF_TYPE && !options.useRdfType) {
-      // add value of object as @type
-      jsonld.addValue(value, '@type', o.nominalValue, {propertyIsArray: true});
-    }
-    else {
-      // add property to value as needed
-      var object = _rdfToObject(o, options.useNativeTypes);
-      jsonld.addValue(value, p, object, {propertyIsArray: true});
-
-      // a bnode might be the beginning of a list, so add it to the list map
-      if(o.interfaceName === 'BlankNode') {
-        var id = object['@id'];
-        var listMap = graph.listMap;
-        var entry;
-        if(!(id in listMap)) {
-          entry = listMap[id] = {};
+        // set object value
+        entry.first = _RDFToObject(o, options.useNativeTypes);
+        continue;
+      }
+
+      // handle other element in @list
+      if(p === RDF_REST) {
+        // set next in list
+        if(o.type === 'blank node') {
+          // create list entry as needed
+          var listMap = graph.listMap;
+          var entry;
+          if(!(s in listMap)) {
+            entry = listMap[s] = {};
+          }
+          else {
+            entry = listMap[s];
+          }
+          entry.rest = o.value;
         }
-        else {
-          entry = listMap[id];
+        continue;
+      }
+
+      // add graph subject to default graph as needed
+      if(graphName !== '@default' && !(graphName in defaultGraph.subjects)) {
+        defaultGraph.subjects[graphName] = {'@id': graphName};
+      }
+
+      // add subject to graph as needed
+      var subjects = graph.subjects;
+      var value;
+      if(!(s in subjects)) {
+        value = subjects[s] = {'@id': s};
+      }
+      // use existing subject value
+      else {
+        value = subjects[s];
+      }
+
+      // convert to @type unless options indicate to treat rdf:type as property
+      if(p === RDF_TYPE && !options.useRdfType) {
+        // add value of object as @type
+        jsonld.addValue(value, '@type', o.value, {propertyIsArray: true});
+      }
+      else {
+        // add property to value as needed
+        var object = _RDFToObject(o, options.useNativeTypes);
+        jsonld.addValue(value, p, object, {propertyIsArray: true});
+
+        // a bnode might be the beginning of a list, so add it to the list map
+        if(o.type === 'blank node') {
+          var id = object['@id'];
+          var listMap = graph.listMap;
+          var entry;
+          if(!(id in listMap)) {
+            entry = listMap[id] = {};
+          }
+          else {
+            entry = listMap[id];
+          }
+          entry.head = object;
         }
-        entry.head = object;
       }
     }
   }
 
   // build @lists
-  for(var name in graphs) {
-    var graph = graphs[name];
+  for(var graphName in graphs) {
+    var graph = graphs[graphName];
 
     // find list head
     var listMap = graph.listMap;
@@ -2398,20 +2366,23 @@
 };
 
 /**
- * Outputs the RDF statements found in the given JSON-LD element.
- *
- * @param element the JSON-LD element.
- * @param namer the UniqueNamer for assigning bnode names.
- * @param subject the active subject.
- * @param property the active property.
- * @param graph the graph name.
- * @param callback(err, statement) called when a statement is output, with the
- *          last statement as null.
+ * Adds RDF triples for each graph in the given node map to an RDF dataset.
+ *
+ * @param nodeMap the node map.
+ *
+ * @return the RDF dataset.
  */
-Processor.prototype.toRDF = function(
-  element, namer, subject, property, graph, callback) {
-  _toRDF(element, namer, subject, property, graph, callback);
-  callback(null, null);
+Processor.prototype.toRDF = function(nodeMap) {
+  var namer = new UniqueNamer('_:b');
+  var dataset = {};
+  for(var graphName in nodeMap) {
+    var graph = nodeMap[graphName];
+    if(graphName.indexOf('_:') === 0) {
+      graphName = namer.getName(graphName);
+    }
+    dataset[graphName] = _graphToRDF(graph, namer);
+  }
+  return dataset;
 };
 
 /**
@@ -2661,181 +2632,179 @@
 }
 
 /**
- * Recursively outputs the RDF statements found in the given JSON-LD element.
- *
- * @param element the JSON-LD element.
- * @param namer the UniqueNamer for assigning bnode names.
- * @param subject the active subject.
- * @param property the active property.
- * @param graph the graph name.
- * @param callback(err, statement) called when a statement is output, with the
- *          last statement as null.
+ * Creates an array of RDF triples for the given graph.
+ *
+ * @param graph the graph to create RDF triples for.
+ * @param namer a UniqueNamer for assigning blank node names.
+ *
+ * @return the array of RDF triples for the given graph.
  */
-function _toRDF(element, namer, subject, property, graph, callback) {
-  // recurse into arrays
-  if(_isArray(element)) {
-    for(var i in element) {
-      _toRDF(element[i], namer, subject, property, graph, callback);
-    }
-    return;
-  }
-
-  // element must be an rdf:type IRI (@values covered above)
-  if(_isString(element)) {
-    // emit IRI
-    var statement = {
-      subject: _clone(subject),
-      property: _clone(property),
-      object: {
-        nominalValue: element,
-        interfaceName: 'IRI'
-      }
-    };
-    if(graph !== null) {
-      statement.name = graph;
-    }
-    return callback(null, statement);
-  }
-
-  // convert @list
-  if(_isList(element)) {
-    var list = _makeLinkedList(element);
-    return _toRDF(list, namer, subject, property, graph, callback);
-  }
-
-  // convert @value to object
-  if(_isValue(element)) {
-    var value = element['@value'];
-    var datatype = element['@type'] || null;
+function _graphToRDF(graph, namer) {
+  var rval = [];
+
+  for(var id in graph) {
+    var node = graph[id];
+    for(var property in node) {
+      if(property !== '@type' && _isKeyword(property)) {
+        continue;
+      }
+
+      var items = node[property];
+      for(var i = 0; i < items.length; ++i) {
+        var item = items[i];
+
+        // RDF subject
+        var subject = {};
+        if(id.indexOf('_:') === 0) {
+          subject.type = 'blank node';
+          subject.value = namer.getName(id);
+        }
+        else {
+          subject.type = 'IRI';
+          subject.value = id;
+        }
+
+        // RDF predicate
+        var predicate = {type: 'IRI'};
+        predicate.value = (property === '@type') ? RDF_TYPE : property;
+
+        // convert @list to triples
+        if(_isList(item)) {
+          _listToRDF(item['@list'], namer, subject, predicate, rval);
+        }
+        // convert value or node object to triple
+        else {
+          var object = _objectToRDF(item, namer);
+          rval.push({subject: subject, predicate: predicate, object: object});
+        }
+      }
+    }
+  }
+
+  return rval;
+}
+
+/**
+ * Converts a @list value into linked list of blank node RDF triples
+ * (an RDF collection).
+ *
+ * @param list the @list value.
+ * @param namer a UniqueNamer for assigning blank node names.
+ * @param subject the subject for the head of the list.
+ * @param predicate the predicate for the head of the list.
+ * @param triples the array of triples to append to.
+ */
+function _listToRDF(list, namer, subject, predicate, triples) {
+  var first = {type: 'IRI', value: RDF_FIRST};
+  var rest = {type: 'IRI', value: RDF_REST};
+  var nil = {type: 'IRI', value: RDF_NIL};
+
+  for(var vi = 0; vi < list.length; ++vi) {
+    var value = list[vi];
+
+    var blankNode = {type: 'blank node', value: namer.getName()};
+    triples.push({subject: subject, predicate: predicate, object: blankNode});
+
+    subject = blankNode;
+    predicate = first;
+    var object = _objectToRDF(value, namer);
+    triples.push({subject: subject, predicate: predicate, object: object});
+
+    predicate = rest;
+  }
+
+  triples.push({subject: subject, predicate: predicate, object: nil});
+}
+
+/**
+ * Converts a JSON-LD value object to an RDF literal or a JSON-LD string or
+ * node object to an RDF resource.
+ *
+ * @param item the JSON-LD value or node object.
+ * @param namer the UniqueNamer to use to assign blank node names.
+ *
+ * @return the RDF literal or RDF resource.
+ */
+function _objectToRDF(item, namer) {
+  var object = {};
+
+  // convert value object to RDF
+  if(_isValue(item)) {
+    object.type = 'literal';
+    var value = item['@value'];
+    var datatype = item['@type'] || null;
 
     // convert to XSD datatypes as appropriate
     if(_isBoolean(value)) {
-      value = value.toString();
-      datatype = datatype || XSD_BOOLEAN;
+      object.value = value.toString();
+      object.datatype = datatype || XSD_BOOLEAN;
     }
     else if(_isDouble(value)) {
       // canonical double representation
-      value = value.toExponential(15).replace(/(\d)0*e\+?/, '$1E');
-      datatype = datatype || XSD_DOUBLE;
+      object.value = value.toExponential(15).replace(/(\d)0*e\+?/, '$1E');
+      object.datatype = datatype || XSD_DOUBLE;
     }
     else if(_isNumber(value)) {
-      value = value.toFixed(0);
-      datatype = datatype || XSD_INTEGER;
-    }
-
-    // default to xsd:string datatype
-    datatype = datatype || XSD_STRING;
-
-    var object = {
-      nominalValue: value,
-      interfaceName: 'LiteralNode',
-      datatype: {
-        nominalValue: datatype,
-        interfaceName: 'IRI'
-      }
-    };
-    if('@language' in element && datatype === XSD_STRING) {
-      object.language = element['@language'];
-    }
-
-    // emit literal
-    var statement = {
-      subject: _clone(subject),
-      property: _clone(property),
-      object: object
-    };
-    if(graph !== null) {
-      statement.name = graph;
-    }
-    return callback(null, statement);
-  }
-
-  // Note: element must be a subject
-
-  // get subject @id (generate one if it is a bnode)
-  var isBnode = _isBlankNode(element);
-  var id = isBnode ? namer.getName(element['@id']) : element['@id'];
-
-  // create object
-  var object = {
-    nominalValue: id,
-    interfaceName: isBnode ? 'BlankNode' : 'IRI'
-  };
-
-  // emit statement if subject isn't null
-  if(subject !== null) {
-    var statement = {
-      subject: _clone(subject),
-      property: _clone(property),
-      object: _clone(object)
-    };
-    if(graph !== null) {
-      statement.name = graph;
-    }
-    callback(null, statement);
-  }
-
-  // set new active subject to object
-  subject = object;
-
-  // recurse over subject properties in order
-  var props = Object.keys(element).sort();
-  for(var pi in props) {
-    var prop = props[pi];
-    var e = element[prop];
-
-    // convert @type to rdf:type
-    if(prop === '@type') {
-      prop = RDF_TYPE;
-    }
-
-    // recurse into @graph
-    if(prop === '@graph') {
-      _toRDF(e, namer, null, null, subject, callback);
-      continue;
-    }
-
-    // skip keywords
-    if(_isKeyword(prop)) {
-      continue;
-    }
-
-    // create new active property
-    property = {
-      nominalValue: prop,
-      interfaceName: 'IRI'
-    };
-
-    // recurse into value
-    _toRDF(e, namer, subject, property, graph, callback);
-  }
+      object.value = value.toFixed(0);
+      object.datatype = datatype || XSD_INTEGER;
+    }
+    else if('@language' in item) {
+      object.value = value;
+      object.datatype = datatype || RDF_LANGSTRING;
+      object.language = item['@language'];
+    }
+    else {
+      object.value = value;
+      object.datatype = datatype || XSD_STRING;
+    }
+  }
+  // convert string/node object to RDF
+  else {
+    var id = _isObject(item) ? item['@id'] : item;
+
+    var isBnode = (id.indexOf('_:') === 0);
+    if(isBnode) {
+      object.type = 'blank node';
+      object.value = namer.getName(id);
+    }
+    else {
+      object.type = 'IRI';
+      object.value = id;
+    }
+  }
+
+  return object;
 }
 
 /**
- * Converts an RDF statement object to a JSON-LD object.
- *
- * @param o the RDF statement object to convert.
+ * Converts an RDF triple object to a JSON-LD object.
+ *
+ * @param o the RDF triple object to convert.
  * @param useNativeTypes true to output native types, false not to.
  *
  * @return the JSON-LD object.
  */
-function _rdfToObject(o, useNativeTypes) {
+function _RDFToObject(o, useNativeTypes) {
   // convert empty list
-  if(o.interfaceName === 'IRI' && o.nominalValue === RDF_NIL) {
+  if(o.type === 'IRI' && o.value === RDF_NIL) {
     return {'@list': []};
   }
 
   // convert IRI/BlankNode object to JSON-LD
-  if(o.interfaceName === 'IRI' || o.interfaceName === 'BlankNode') {
-    return {'@id': o.nominalValue};
-  }
-
-  // convert literal object to JSON-LD
-  var rval = {'@value': o.nominalValue};
-
+  if(o.type === 'IRI' || o.type === 'blank node') {
+    return {'@id': o.value};
+  }
+
+  // convert literal to JSON-LD
+  var rval = {'@value': o.value};
+
+  // add language
+  if('language' in o) {
+    rval['@language'] = o.language;
+  }
   // add datatype
-  if('datatype' in o) {
-    var type = o.datatype.nominalValue;
+  else {
+    var type = o.datatype;
     if(useNativeTypes) {
       // use native types for certain xsd types
       if(type === XSD_BOOLEAN) {
@@ -2867,96 +2836,56 @@
       rval['@type'] = type;
     }
   }
-  // add language
-  if('language' in o) {
-    rval['@language'] = o.language;
-  }
 
   return rval;
 }
 
 /**
- * Compares two RDF statements for equality.
- *
- * @param s1 the first statement.
- * @param s2 the second statement.
- *
- * @return true if the statements are the same, false if not.
+ * Compares two RDF triples for equality.
+ *
+ * @param t1 the first triple.
+ * @param t2 the second triple.
+ *
+ * @return true if the triples are the same, false if not.
  */
-function _compareRdfStatements(s1, s2) {
-  if(_isString(s1) || _isString(s2)) {
-    return s1 === s2;
-  }
-
-  var attrs = ['subject', 'property', 'object'];
-  for(var i in attrs) {
+function _compareRDFTriples(t1, t2) {
+  var attrs = ['subject', 'predicate', 'object'];
+  for(var i = 0; i < attrs.length; ++i) {
     var attr = attrs[i];
-    if(s1[attr].interfaceName !== s2[attr].interfaceName ||
-      s1[attr].nominalValue !== s2[attr].nominalValue) {
+    if(t1[attr].type !== t2[attr].type || t1[attr].value !== t2[attr].value) {
       return false;
     }
   }
-  if(s1.object.language !== s2.object.language) {
+  if(t1.object.language !== t2.object.language) {
     return false;
   }
-  if(('datatype' in s1.object) !== ('datatype' in s2.object)) {
-    return false;
-  }
-  if('datatype' in s1.object) {
-    if(s1.object.datatype.interfaceName !== s2.object.datatype.interfaceName ||
-      s1.object.datatype.nominalValue !== s2.object.datatype.nominalValue) {
-      return false;
-    }
-  }
-  if(s1.name !== s2.name) {
+  if(t1.object.datatype !== t2.object.datatype) {
     return false;
   }
   return true;
 }
 
 /**
- * Converts a @list value into an embedded linked list of blank nodes in
- * expanded form. The resulting array can be used as an RDF-replacement for
- * a property that used a @list.
- *
- * @param value the @list value.
- *
- * @return the head of the linked list of blank nodes.
- */
-function _makeLinkedList(value) {
-  // convert @list array into embedded blank node linked list in reverse
-  var list = value['@list'];
-  var len = list.length;
-  var tail = {'@id': RDF_NIL};
-  for(var i = len - 1; i >= 0; --i) {
-    var e = {};
-    e[RDF_FIRST] = [list[i]];
-    e[RDF_REST] = [tail];
-    tail = e;
-  }
-  return tail;
-}
-
-/**
- * Hashes all of the statements about a blank node.
- *
- * @param id the ID of the bnode to hash statements for.
- * @param bnodes the mapping of bnodes to statements.
+ * Hashes all of the quads about a blank node.
+ *
+ * @param id the ID of the bnode to hash quads for.
+ * @param bnodes the mapping of bnodes to quads.
  * @param namer the canonical bnode namer.
  *
  * @return the new hash.
  */
-function _hashStatements(id, bnodes, namer) {
+function _hashQuads(id, bnodes, namer) {
   // return cached hash
   if('hash' in bnodes[id]) {
     return bnodes[id].hash;
   }
 
-  // serialize all of bnode's statements
-  var statements = bnodes[id].statements;
+  // serialize all of bnode's quads
+  var quads = bnodes[id].quads;
   var nquads = [];
-  for(var i in statements) {
-    nquads.push(_toNQuad(statements[i], id));
+  for(var i = 0; i < quads.length; ++i) {
+    nquads.push(_toNQuad(
+      quads[i], quads[i].name ? quads[i].name.value : null, id));
   }
   // sort serialized quads
   nquads.sort();
@@ -2972,7 +2901,7 @@
  * lexicographically-least 'path' serializations.
  *
  * @param id the ID of the bnode to hash paths for.
- * @param bnodes the map of bnode statements.
+ * @param bnodes the map of bnode quads.
  * @param namer the canonical bnode namer.
  * @param pathNamer the namer used to assign names to adjacent bnodes.
  * @param callback(err, result) called once the operation completes.
@@ -2984,25 +2913,27 @@
   // group adjacent bnodes by hash, keep properties and references separate
   var groups = {};
   var groupHashes;
-  var statements = bnodes[id].statements;
-  setTimeout(function() {groupNodes(0);}, 0);
+  var quads = bnodes[id].quads;
+  jsonld.nextTick(function() {groupNodes(0);});
   function groupNodes(i) {
-    if(i === statements.length) {
+    if(i === quads.length) {
       // done, hash groups
       groupHashes = Object.keys(groups).sort();
       return hashGroup(0);
     }
 
     // get adjacent bnode
-    var statement = statements[i];
-    var bnode = _getAdjacentBlankNodeName(statement.subject, id);
+    var quad = quads[i];
+    var bnode = _getAdjacentBlankNodeName(quad.subject, id);
     var direction = null;
     if(bnode !== null) {
+      // normal property
       direction = 'p';
     }
     else {
-      bnode = _getAdjacentBlankNodeName(statement.object, id);
+      bnode = _getAdjacentBlankNodeName(quad.object, id);
       if(bnode !== null) {
+        // reverse property
         direction = 'r';
       }
     }
@@ -3017,13 +2948,13 @@
         name = pathNamer.getName(bnode);
       }
       else {
-        name = _hashStatements(bnode, bnodes, namer);
+        name = _hashQuads(bnode, bnodes, namer);
       }
 
       // hash direction, property, and bnode name/hash
       var md = sha1.create();
       md.update(direction);
-      md.update(statement.property.nominalValue);
+      md.update(quad.predicate.value);
       md.update(name);
       var groupHash = md.digest();
 
@@ -3036,7 +2967,7 @@
       }
     }
 
-    setTimeout(function() {groupNodes(i + 1);}, 0);
+    jsonld.nextTick(function() {groupNodes(i + 1);});
   }
 
   // hashes a group of adjacent bnodes
@@ -3054,7 +2985,7 @@
     var chosenPath = null;
     var chosenNamer = null;
     var permutator = new Permutator(groups[groupHash]);
-    setTimeout(function() {permutate();}, 0);
+    jsonld.nextTick(function() {permutate();});
     function permutate() {
       var permutation = permutator.next();
       var pathNamerCopy = pathNamer.clone();
@@ -3122,7 +3053,7 @@
 
         // do next permutation
         if(permutator.hasNext()) {
-          setTimeout(function() {permutate();}, 0);
+          jsonld.nextTick(function() {permutate();});
         }
         else {
           // digest chosen path and update namer
@@ -3138,18 +3069,17 @@
 }
 
 /**
- * A helper function that gets the blank node name from an RDF statement node
- * (subject or object). If the node is a blank node and its nominal value
+ * A helper function that gets the blank node name from an RDF quad node
+ * (subject or object). If the node is a blank node and its value
  * does not match the given blank node ID, it will be returned.
  *
- * @param node the RDF statement node.
+ * @param node the RDF quad node.
  * @param id the ID of the blank node to look next to.
  *
  * @return the adjacent blank node name or null if none was found.
  */
 function _getAdjacentBlankNodeName(node, id) {
-  return (node.interfaceName === 'BlankNode' && node.nominalValue !== id ?
-    node.nominalValue : null);
+  return (node.type === 'blank node' && node.value !== id ? node.value : null);
 }
 
 /**
@@ -5329,11 +5259,11 @@
 }
 
 /**
- * Parses statements in the form of N-Quads.
+ * Parses RDF in the form of N-Quads.
  *
  * @param input the N-Quads input to parse.
  *
- * @return an array of RDF statements.
+ * @return an RDF dataset.
  */
 function _parseNQuads(input) {
   // define partial regexes
@@ -5352,20 +5282,20 @@
   var subject = '(?:' + iri + '|' + bnode + ')' + ws;
   var property = iri + ws;
   var object = '(?:' + iri + '|' + bnode + '|' + literal + ')' + wso;
-  var graph = '(?:\\.|(?:(?:' + iri + '|' + bnode + ')' + wso + '\\.))';
+  var graphName = '(?:\\.|(?:(?:' + iri + '|' + bnode + ')' + wso + '\\.))';
 
   // full quad regex
   var quad = new RegExp(
-    '^' + wso + subject + property + object + graph + wso + '$');
-
-  // build RDF statements
-  var statements = [];
+    '^' + wso + subject + property + object + graphName + wso + '$');
+
+  // build RDF dataset
+  var dataset = {};
 
   // split N-Quad input into lines
   var lines = input.split(eoln);
   var lineNumber = 0;
-  for(var i in lines) {
-    var line = lines[i];
+  for(var li = 0; li < lines.length; ++li) {
+    var line = lines[li];
     lineNumber++;
 
     // skip empty lines
@@ -5381,139 +5311,185 @@
         'jsonld.ParseError', {line: lineNumber});
     }
 
-    // create RDF statement
-    var s = {};
+    // create RDF triple
+    var triple = {};
 
     // get subject
     if(!_isUndefined(match[1])) {
-      s.subject = {nominalValue: match[1], interfaceName: 'IRI'};
+      triple.subject = {type: 'IRI', value: match[1]};
     }
     else {
-      s.subject = {nominalValue: match[2], interfaceName: 'BlankNode'};
-    }
-
-    // get property
-    s.property = {nominalValue: match[3], interfaceName: 'IRI'};
+      triple.subject = {type: 'blank node', value: match[2]};
+    }
+
+    // get predicate
+    triple.predicate = {type: 'IRI', value: match[3]};
 
     // get object
     if(!_isUndefined(match[4])) {
-      s.object = {nominalValue: match[4], interfaceName: 'IRI'};
+      triple.object = {type: 'IRI', value: match[4]};
     }
     else if(!_isUndefined(match[5])) {
-      s.object = {nominalValue: match[5], interfaceName: 'BlankNode'};
+      triple.object = {type: 'blank node', value: match[5]};
     }
     else {
+      triple.object = {type: 'literal'};
+      if(!_isUndefined(match[7])) {
+        triple.object.datatype = match[7];
+      }
+      else if(!_isUndefined(match[8])) {
+        triple.object.datatype = RDF_LANGSTRING;
+        triple.object.language = match[8];
+      }
+      else {
+        triple.object.datatype = XSD_STRING;
+      }
       var unescaped = match[6]
         .replace(/\\"/g, '"')
         .replace(/\\t/g, '\t')
         .replace(/\\n/g, '\n')
         .replace(/\\r/g, '\r')
         .replace(/\\\\/g, '\\');
-      s.object = {nominalValue: unescaped, interfaceName: 'LiteralNode'};
-      if(!_isUndefined(match[7])) {
-        s.object.datatype = {nominalValue: match[7], interfaceName: 'IRI'};
-      }
-      else if(!_isUndefined(match[8])) {
-        s.object.language = match[8];
-      }
-    }
-
-    // get graph
+      triple.object.value = unescaped;
+    }
+
+    // get graph name
+    var name = null;
     if(!_isUndefined(match[9])) {
-      s.name = {nominalValue: match[9], interfaceName: 'IRI'};
+      name = match[9];
     }
     else if(!_isUndefined(match[10])) {
-      s.name = {nominalValue: match[10], interfaceName: 'BlankNode'};
-    }
-
-    // add unique statement
-    for(var si in statements) {
-      if(_compareRdfStatements(statements[si], s)) {
-        continue;
-      }
-    }
-    statements.push(s);
-  }
-
-  return statements;
+      name = match[10];
+    }
+
+    // use '@default' key for default graph in dataset
+    if(name === null) {
+      name = '@default';
+    }
+
+    // initialize graph in dataset
+    if(!(name in dataset)) {
+      dataset[name] = [triple];
+    }
+    // add triple if unique to its graph
+    else {
+      var unique = true;
+      var triples = dataset[name];
+      for(var ti = 0; unique && ti < triples.length; ++ti) {
+        if(_compareRDFTriples(triples[ti], triple)) {
+          unique = false;
+        }
+      }
+      if(unique) {
+        triples.push(triple);
+      }
+    }
+  }
+
+  return dataset;
 }
 
 // register the N-Quads RDF parser
 jsonld.registerRDFParser('application/nquads', _parseNQuads);
 
 /**
- * Converts an RDF statement to an N-Quad string (a single quad).
- *
- * @param statement the RDF statement to convert.
- * @param bnode the bnode the statement is mapped to (optional, for use
- *           during normalization only).
+ * Converts an RDF dataset to N-Quads.
+ *
+ * @param dataset the RDF dataset to convert.
+ *
+ * @return the N-Quads string.
+ */
+function _toNQuads(dataset) {
+  var quads = [];
+  for(var graphName in dataset) {
+    var triples = dataset[graphName];
+    for(var ti = 0; ti < triples.length; ++ti) {
+      var triple = triples[ti];
+      if(graphName === '@default') {
+        graphName = null;
+      }
+      quads.push(_toNQuad(triple, graphName));
+    }
+  }
+  quads.sort();
+  return quads.join('');
+}
+
+/**
+ * Converts an RDF triple and graph name to an N-Quad string (a single quad).
+ *
+ * @param triple the RDF triple to convert.
+ * @param graphName the name of the graph containing the triple, null for
+ *          the default graph.
+ * @param bnode the bnode the quad is mapped to (optional, for use
+ *          during normalization only).
  *
  * @return the N-Quad string.
  */
-function _toNQuad(statement, bnode) {
-  var s = statement.subject;
-  var p = statement.property;
-  var o = statement.object;
-  var g = statement.name || null;
+function _toNQuad(triple, graphName, bnode) {
+  var s = triple.subject;
+  var p = triple.predicate;
+  var o = triple.object;
+  var g = graphName;
 
   var quad = '';
 
   // subject is an IRI or bnode
-  if(s.interfaceName === 'IRI') {
-    quad += '<' + s.nominalValue + '>';
+  if(s.type === 'IRI') {
+    quad += '<' + s.value + '>';
   }
   // normalization mode
   else if(bnode) {
-    quad += (s.nominalValue === bnode) ? '_:a' : '_:z';
+    quad += (s.value === bnode) ? '_:a' : '_:z';
   }
   // normal mode
   else {
-    quad += s.nominalValue;
+    quad += s.value;
   }
 
   // property is always an IRI
-  quad += ' <' + p.nominalValue + '> ';
+  quad += ' <' + p.value + '> ';
 
   // object is IRI, bnode, or literal
-  if(o.interfaceName === 'IRI') {
-    quad += '<' + o.nominalValue + '>';
-  }
-  else if(o.interfaceName === 'BlankNode') {
+  if(o.type === 'IRI') {
+    quad += '<' + o.value + '>';
+  }
+  else if(o.type === 'blank node') {
     // normalization mode
     if(bnode) {
-      quad += (o.nominalValue === bnode) ? '_:a' : '_:z';
+      quad += (o.value === bnode) ? '_:a' : '_:z';
     }
     // normal mode
     else {
-      quad += o.nominalValue;
+      quad += o.value;
     }
   }
   else {
-    var escaped = o.nominalValue
+    var escaped = o.value
       .replace(/\\/g, '\\\\')
       .replace(/\t/g, '\\t')
       .replace(/\n/g, '\\n')
       .replace(/\r/g, '\\r')
       .replace(/\"/g, '\\"');
     quad += '"' + escaped + '"';
-    if('datatype' in o && o.datatype.nominalValue !== XSD_STRING) {
-      quad += '^^<' + o.datatype.nominalValue + '>';
-    }
-    else if('language' in o) {
+    if(o.datatype === RDF_LANGSTRING) {
       quad += '@' + o.language;
     }
+    else if(o.datatype !== XSD_STRING) {
+      quad += '^^<' + o.datatype + '>';
+    }
   }
 
   // graph
   if(g !== null) {
-    if(g.interfaceName === 'IRI') {
-      quad += ' <' + g.nominalValue + '>';
+    if(g.indexOf('_:') !== 0) {
+      quad += ' <' + g + '>';
     }
     else if(bnode) {
       quad += ' _:g';
     }
     else {
-      quad += ' ' + g.nominalValue;
+      quad += ' ' + g;
     }
   }
 
@@ -5522,17 +5498,18 @@
 }
 
 /**
- * Parses statements found via the data object from the RDFa API.
+ * Parses the RDF dataset found via the data object from the RDFa API.
  *
  * @param data the RDFa API data object.
  *
- * @return an array of RDF statements.
+ * @return the RDF dataset.
  */
 function _parseRdfaApiData(data) {
-  var statements = [];
+  var dataset = {};
+  dataset['@default'] = [];
 
   var subjects = data.getSubjects();
-  for(var si in subjects) {
+  for(var si = 0; si < subjects.length; ++si) {
     var subject = subjects[si];
     if(subject === null) {
       continue;
@@ -5544,25 +5521,25 @@
       continue;
     }
     var predicates = triples.predicates;
-    for(var p in predicates) {
+    for(var predicate in predicates) {
       // iterate over objects
-      var objects = predicates[p].objects;
-      for(var oi in objects) {
+      var objects = predicates[predicate].objects;
+      for(var oi = 0; oi < objects.length; ++oi) {
         var object = objects[oi];
 
-        // create RDF statement
-        var s = {};
+        // create RDF triple
+        var triple = {};
 
         // add subject
         if(subject.indexOf('_:') === 0) {
-          s.subject = {nominalValue: subject, interfaceName: 'BlankNode'};
+          triple.subject = {type: 'blank node', value: subject};
         }
         else {
-          s.subject = {nominalValue: subject, interfaceName: 'IRI'};
+          triple.subject = {type: 'IRI', value: subject};
         }
 
-        // add property
-        s.property = {nominalValue: p, interfaceName: 'IRI'};
+        // add predicate
+        triple.predicate = {type: 'IRI', value: predicate};
 
         // serialize XML literal
         var value = object.value;
@@ -5584,40 +5561,42 @@
         }
 
         // add object
-        s.object = {nominalValue: value};
+        triple.object = {};
 
         // object is an IRI
         if(object.type === RDF_OBJECT) {
           if(object.value.indexOf('_:') === 0) {
-            s.object.interfaceName = 'BlankNode';
+            triple.object.type = 'blank node';
           }
           else {
-            s.object.interfaceName = 'IRI';
+            triple.object.type = 'IRI';
           }
         }
         // literal
         else {
-          s.object.interfaceName = 'LiteralNode';
+          triple.object.type = 'literal';
           if(object.type === RDF_PLAIN_LITERAL) {
             if(object.language) {
-              s.object.language = object.language;
+              triple.object.datatype = RDF_LANGSTRING;
+              triple.object.language = object.language;
+            }
+            else {
+              triple.object.datatype = XSD_STRING;
             }
           }
           else {
-            s.object.datatype = {
-              nominalValue: object.type,
-              interfaceName: 'IRI'
-            };
+            triple.object.datatype = object.type;
           }
         }
-
-        // add statement
-        statements.push(s);
-      }
-    }
-  }
-
-  return statements;
+        triple.object.value = value;
+
+        // add triple to dataset in default graph
+        dataset['@default'].push(triple);
+      }
+    }
+  }
+
+  return dataset;
 }
 
 // register the RDFa API RDF parser
--- a/playground/playground.js	Tue Feb 19 21:20:44 2013 -0500
+++ b/playground/playground.js	Wed Feb 20 01:16:28 2013 -0500
@@ -242,7 +242,6 @@
     }
     else if(playground.activeTab === 'tab-nquads') {
       options.format = 'application/nquads';
-      options.collate = true;
       jsonld.toRDF(input, options, function(err, nquads) {
         if(err) {
           return callback(err);