--- a/playground/jsonld.js Tue Jun 26 23:49:05 2012 -0400
+++ b/playground/jsonld.js Sat Jun 30 21:37:44 2012 -0400
@@ -473,6 +473,12 @@
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 {
@@ -746,44 +752,44 @@
};
/**
- * Adds a value to a subject. If the subject already has the value, it will
- * not be added. If the value is an array, all values in the array will be
- * added.
- *
- * Note: If the value is a subject that already exists as a property of the
- * given subject, this method makes no attempt to deeply merge properties.
- * Instead, the value will not be added.
+ * Adds a value to a subject. If the value is an array, all values in the
+ * array will be added.
*
* @param subject the subject to add the value to.
* @param property the property that relates the value to the subject.
* @param value the value to add.
- * @param [propertyIsArray] true if the property is always an array, false
+ * @param [options] the options to use:
+ * [propertyIsArray] true if the property is always an array, false
* if not (default: false).
- * @param [propertyIsList] true if the property is a @list, false
- * if not (default: false).
+ * [allowDuplicate] true to allow duplicates, false not to (uses a
+ * simple shallow comparison of subject ID or value) (default: true).
*/
-jsonld.addValue = function(
- subject, property, value, propertyIsArray, propertyIsList) {
- propertyIsArray = _isUndefined(propertyIsArray) ? false : propertyIsArray;
- propertyIsList = (_isUndefined(propertyIsList) ?
- (property === '@list') : propertyIsList);
+jsonld.addValue = function(subject, property, value, options) {
+ options = options || {};
+ if(!('propertyIsArray' in options)) {
+ options.propertyIsArray = false;
+ }
+ if(!('allowDuplicate' in options)) {
+ options.allowDuplicate = true;
+ }
if(_isArray(value)) {
- if(value.length === 0 && propertyIsArray && !(property in subject)) {
+ if(value.length === 0 && options.propertyIsArray &&
+ !(property in subject)) {
subject[property] = [];
}
for(var i in value) {
- jsonld.addValue(
- subject, property, value[i], propertyIsArray, propertyIsList);
+ jsonld.addValue(subject, property, value[i], options);
}
}
else if(property in subject) {
- // check if subject already has value unless property is list
- var hasValue = (!propertyIsList &&
+ // check if subject already has value if duplicates not allowed
+ var hasValue = (!options.allowDuplicate &&
jsonld.hasValue(subject, property, value));
// make property an array if value not present or always an array
- if(!_isArray(subject[property]) && (!hasValue || propertyIsArray)) {
+ if(!_isArray(subject[property]) &&
+ (!hasValue || options.propertyIsArray)) {
subject[property] = [subject[property]];
}
@@ -794,7 +800,7 @@
}
else {
// add new value as set or single value
- subject[property] = propertyIsArray ? [value] : value;
+ subject[property] = options.propertyIsArray ? [value] : value;
}
};
@@ -830,11 +836,15 @@
* @param subject the subject.
* @param property the property that relates the value to the subject.
* @param value the value to remove.
- * @param [propertyIsArray] true if the property is always an array, false
- * if not (default: false).
+ * @param [options] the options to use:
+ * [propertyIsArray] true if the property is always an array, false
+ * if not (default: false).
*/
-jsonld.removeValue = function(subject, property, value, propertyIsArray) {
- propertyIsArray = _isUndefined(propertyIsArray) ? false : propertyIsArray;
+jsonld.removeValue = function(subject, property, value, options) {
+ options = options || {};
+ if(!('propertyIsArray' in options)) {
+ options.propertyIsArray = false;
+ }
// filter out value
var values = jsonld.getValues(subject, property).filter(function(e) {
@@ -844,7 +854,7 @@
if(values.length === 0) {
jsonld.removeProperty(subject, property);
}
- else if(values.length === 1 && !propertyIsArray) {
+ else if(values.length === 1 && !options.propertyIsArray) {
subject[property] = values[0];
}
else {
@@ -1068,8 +1078,9 @@
if(_isValue(element)) {
// if @value is the only key
if(Object.keys(element).length === 1) {
- // if there is no default language, return value of @value
- if(!('@language' in ctx)) {
+ // if there is no default language or @value is not a string,
+ // return value of @value
+ if(!('@language' in ctx) || !_isString(element['@value'])) {
return element['@value'];
}
// return full element, alias @value
@@ -1139,7 +1150,7 @@
// compact property and add value
var prop = _compactIri(ctx, key);
var isArray = (_isArray(value) && value.length === 0);
- jsonld.addValue(rval, prop, value, isArray);
+ jsonld.addValue(rval, prop, value, {propertyIsArray: isArray});
continue;
}
@@ -1148,7 +1159,7 @@
// preserve empty arrays
if(value.length === 0) {
var prop = _compactIri(ctx, key);
- jsonld.addValue(rval, prop, [], true);
+ jsonld.addValue(rval, prop, [], {propertyIsArray: true});
}
// recusively process array values
@@ -1194,7 +1205,7 @@
(_isArray(v) && v.length === 0));
// add compact value
- jsonld.addValue(rval, prop, v, isArray, (container === '@list'));
+ jsonld.addValue(rval, prop, v, {propertyIsArray: isArray});
}
}
return rval;
@@ -1349,7 +1360,7 @@
// add value, use an array if not @id, @type, @value, or @language
var useArray = !(prop === '@id' || prop === '@type' ||
prop === '@value' || prop === '@language');
- jsonld.addValue(rval, prop, value, useArray);
+ jsonld.addValue(rval, prop, value, {propertyIsArray: useArray});
}
}
@@ -1461,6 +1472,11 @@
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'];
for(var n in nodes) {
@@ -1652,22 +1668,24 @@
var name = ('name' in statement) ? statement.name.nominalValue : '';
// create a graph entry as needed
+ var graph;
if(!(name in graphs)) {
- var graph = graphs[name] = {subjects: {}, listMap: {}};
+ graph = graphs[name] = {subjects: {}, listMap: {}};
}
else {
- var graph = graphs[name];
+ 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)) {
- var entry = listMap[s] = {};
+ entry = listMap[s] = {};
}
else {
- var entry = listMap[s];
+ entry = listMap[s];
}
// set object value
entry.first = _rdfToObject(o);
@@ -1680,11 +1698,12 @@
if(o.interfaceName === 'BlankNode') {
// create list entry as needed
var listMap = graph.listMap;
+ var entry;
if(!(s in listMap)) {
- var entry = listMap[s] = {};
+ entry = listMap[s] = {};
}
else {
- var entry = listMap[s];
+ entry = listMap[s];
}
entry.rest = o.nominalValue;
}
@@ -1698,33 +1717,35 @@
// add subject to graph as needed
var subjects = graph.subjects;
+ var value;
if(!(s in subjects)) {
- var value = subjects[s] = {'@id': s};
+ value = subjects[s] = {'@id': s};
}
// use existing subject value
else {
- var value = subjects[s];
+ value = subjects[s];
}
// convert to @type unless options indicate to treat rdf:type as property
if(p === RDF_TYPE && !options.notType) {
// add value of object as @type
- jsonld.addValue(value, '@type', o.nominalValue, true);
+ jsonld.addValue(value, '@type', o.nominalValue, {propertyIsArray: true});
}
else {
// add property to value as needed
var object = _rdfToObject(o);
- jsonld.addValue(value, p, object, true);
+ 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)) {
- var entry = listMap[id] = {};
+ entry = listMap[id] = {};
}
else {
- var entry = listMap[id];
+ entry = listMap[id];
}
entry.head = object;
}
@@ -2112,6 +2133,45 @@
}
/**
+ * 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.
+ */
+function _compareRdfStatements(s1, s2) {
+ if(_isString(s1) || _isString(s2)) {
+ return s1 === s2;
+ }
+
+ var attrs = ['subject', 'property', 'object'];
+ for(var i in attrs) {
+ var attr = attrs[i];
+ if(s1[attr].interfaceName !== s2[attr].interfaceName ||
+ s1[attr].nominalValue !== s2[attr].nominalValue) {
+ return false;
+ }
+ }
+ if(s1.object.language !== s2.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) {
+ 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.
@@ -2430,7 +2490,7 @@
var id = _isBlankNode(o) ? namer.getName(o['@id']) : o['@id'];
// add reference and recurse
- jsonld.addValue(subject, prop, {'@id': id}, true);
+ jsonld.addValue(subject, prop, {'@id': id}, {propertyIsArray: true});
_flatten(o, graphs, graph, namer, id);
}
else {
@@ -2446,7 +2506,7 @@
}
// add non-subject
- jsonld.addValue(subject, prop, o, true);
+ jsonld.addValue(subject, prop, o, {propertyIsArray: true});
}
}
}
@@ -2781,8 +2841,8 @@
else {
// replace subject with reference
var useArray = _isArray(parent[property]);
- jsonld.removeValue(parent, property, subject, useArray);
- jsonld.addValue(parent, property, subject, useArray);
+ jsonld.removeValue(parent, property, subject, {propertyIsArray: useArray});
+ jsonld.addValue(parent, property, subject, {propertyIsArray: useArray});
}
// recursively remove dependent dangling embeds
@@ -2811,7 +2871,7 @@
*/
function _addFrameOutput(state, parent, property, output) {
if(_isObject(parent)) {
- jsonld.addValue(parent, property, output, true);
+ jsonld.addValue(parent, property, output, {propertyIsArray: true});
}
else {
parent.push(output);
@@ -3021,7 +3081,7 @@
continue;
}
// skip @list containers for non-@lists
- if(!isList && entry['@container'] === '@list') {
+ if(!isList && entry['@container'] === '@list' && value !== null) {
continue;
}
// for @lists, if listContainer is set, skip non-list containers
@@ -3285,7 +3345,7 @@
// merge onto parent mapping if one exists for a prefix
if(prefix !== null && prefix in activeCtx.mappings) {
var child = mapping;
- var mapping = _clone(activeCtx.mappings[prefix]);
+ mapping = _clone(activeCtx.mappings[prefix]);
for(var k in child) {
mapping[k] = child[k];
}
@@ -4069,7 +4129,12 @@
s.name = {nominalValue: match[10], interfaceName: 'BlankNode'};
}
- // add statement
+ // add unique statement
+ for(var si in statements) {
+ if(_compareRdfStatements(statements[si], s)) {
+ continue;
+ }
+ }
statements.push(s);
}