Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Major refactoring of VIE.Entity. It is now possible to extend it

  • Loading branch information...
commit 6c1eead3bfe9354ee95a07fd65926d20c661bf09 1 parent c3a8eac
@bergie authored
Showing with 539 additions and 563 deletions.
  1. +516 −549 src/Entity.js
  2. +9 −12 src/VIE.js
  3. +9 −0 test/core/vie.js
  4. +5 −2 test/service/stanbol.js
View
1,065 src/Entity.js
@@ -24,561 +24,528 @@
// the type 'owl:Thing'. Read more about Types in <a href="Type.html">VIE.Type</a>.
// * `@context` stores namespace definitions used in the entity. Read more about
// Namespaces in <a href="Namespace.html">VIE Namespaces</a>.
-VIE.prototype.Entity = function(attrs, opts) {
+VIE.prototype.Entity = Backbone.Model.extend({
+ idAttribute: '@subject',
+ isEntity: true,
- attrs = (attrs)? attrs : {};
- opts = (opts)? opts : {};
+ defaults: {
+ '@type': 'owl:Thing'
+ },
- var self = this;
-
- if (attrs['@type'] !== undefined) {
- attrs['@type'] = (_.isArray(attrs['@type']))? attrs['@type'] : [ attrs['@type'] ];
- attrs['@type'] = _.map(attrs['@type'], function(val){
- if (!self.vie.types.get(val)) {
- //if there is no such type -> add it and let it inherit from "owl:Thing"
- self.vie.types.add(val).inherit("owl:Thing");
- }
- return self.vie.types.get(val).id;
- });
- attrs['@type'] = (attrs['@type'].length === 1)? attrs['@type'][0] : attrs['@type'];
+ initialize: function(attributes, options) {
+ if (!attributes) {
+ attributes = {};
+ }
+ if (!options) {
+ options = {};
+ }
+ if (attributes['@subject']) {
+ this.id = this['@subject'] = this.toReference(attributes['@subject']);
} else {
- // provide "owl:Thing" as the default type if none was given
- attrs['@type'] = self.vie.types.get("owl:Thing").id;
+ this.id = this['@subject'] = attributes['@subject'] = this.cid.replace('c', '_:bnode');
+ }
+ return this;
+ },
+
+ schema: function() {
+ return VIE.Util.getFormSchema(this);
+ },
+
+ // ### Getter, Has, Setter
+ // #### `.get(attr)`
+ // To be able to communicate to a VIE Entity you can use a simple get(property)
+ // command as in `entity.get('rdfs:label')` which will give you one or more literals.
+ // If the property points to a collection, its entities can be browsed further.
+ get: function (attr) {
+ attr = VIE.Util.mapAttributeNS(attr, this.vie.namespaces);
+ var value = Backbone.Model.prototype.get.call(this, attr);
+
+ value = (_.isArray(value)) ? value : [ value ];
+ if (value.length === 0) {
+ return undefined;
+ }
+
+ // Handle value conversions
+ value = _.map(value, function(v) {
+ if (v !== undefined && attr === '@type') {
+ // Reference to a type. Return actual type instance
+ if (!this.vie.types.get(v)) {
+ // if there is no such type -> add it and let it inherit from
+ // "owl:Thing"
+ this.vie.types.add(v).inherit("owl:Thing");
+ }
+
+ return this.vie.types.get(v);
+ } else if (v !== undefined && this.vie.entities.get(v)) {
+ // Reference to another entity
+ return this.vie.entities.get(v);
+ } else {
+ // Literal value, return as-is
+ return v;
+ }
+ }, this);
+
+ // if there is only one element, just return that one
+ value = (value.length === 1)? value[0] : value;
+ return value;
+ },
+
+ // #### `.has(attr)`
+ // Sometimes you'd like to determine if a specific attribute is set
+ // in an entity. For this reason you can call for example `person.has('friend')`
+ // to determine if a person entity has friends.
+ has: function(attr) {
+ attr = VIE.Util.mapAttributeNS(attr, this.vie.namespaces);
+ return Backbone.Model.prototype.has.call(this, attr);
+ },
+
+ hasRelations: function() {
+ var found = false;
+ _.each(this.attributes, function (value) {
+ if (value && value.isCollection) {
+ found = true;
+ }
+ });
+ return found;
+ },
+
+ // #### `.set(attrName, value, opts)`,
+ // The `options` parameter always refers to a `Backbone.Model.set` `options` object.
+ //
+ // **`.set(attributes, options)`** is the most universal way of calling the
+ // `.set` method. In this case the `attributes` object is a map of all
+ // attributes to be changed.
+ set: function(attrs, options, opts) {
+ if (!attrs) {
+ return this;
+ }
+
+ if (attrs['@subject']) {
+ attrs['@subject'] = this.toReference(attrs['@subject']);
}
- //the following provides full seamless namespace support
- //for attributes. It should not matter, if you
- //query for `model.get('name')` or `model.get('foaf:name')`
- //or even `model.get('http://xmlns.com/foaf/0.1/name');`
- //However, if we just overwrite `set()` and `get()`, this
- //raises a lot of side effects, so we need to expand
- //the attributes before we create the model.
+ // Use **`.set(attrName, value, options)`** for setting or changing exactly one
+ // entity attribute.
+ if (_.isString(attrs)) {
+ var obj = {};
+ obj[attrs] = options;
+ return this.set(obj, opts);
+ }
+ // **`.set(entity)`**: In case you'd pass a VIE entity,
+ // the passed entities attributes are being set for the entity.
+ if (attrs.attributes) {
+ attrs = attrs.attributes;
+ }
+ var coll;
+ // resolve shortened URIs like rdfs:label..
_.each (attrs, function (value, key) {
- var newKey = VIE.Util.mapAttributeNS(key, this.namespaces);
- if (key !== newKey) {
- delete attrs[key];
- attrs[newKey] = value;
+ var newKey = VIE.Util.mapAttributeNS(key, this.vie.namespaces);
+ if (key !== newKey) {
+ delete attrs[key];
+ attrs[newKey] = value;
+ }
+ }, this);
+
+ // Finally iterate through the *attributes* to be set and prepare
+ // them for the Backbone.Model.set method.
+ _.each (attrs, function (value, key) {
+ if (!value) { return; }
+ if (key.indexOf('@') === -1) {
+ if (value.isCollection) {
+ // ignore
+ value.each(function (child) {
+ this.vie.entities.addOrUpdate(child);
+ }, this);
+ } else if (value.isEntity) {
+ this.vie.entities.addOrUpdate(value);
+ coll = new this.vie.Collection(value, {
+ vie: this.vie,
+ predicate: key
+ });
+ attrs[key] = coll;
+ } else if (_.isArray(value)) {
+ if (this.attributes[key] && this.attributes[key].isCollection) {
+ var newEntities = this.attributes[key].addOrUpdate(value);
+ attrs[key] = this.attributes[key];
+ attrs[key].reset(newEntities);
+ }
+ } else if (value["@value"]) {
+ // The value is a literal object, ignore
+ } else if (_.isObject(value) && !_.isDate(value)) {
+ // The value is another VIE Entity
+ var child = new this.vie.Entity(value, options);
+ // which is being stored in `v.entities`
+ this.vie.entities.addOrUpdate(child);
+ // and set as VIE Collection attribute on the original entity
+ coll = new this.vie.Collection(value, {
+ vie: this.vie,
+ predicate: key
+ });
+ attrs[key] = coll;
+ } else {
+ // ignore
+ }
+ }
+ }, this);
+ var ret = Backbone.Model.prototype.set.call(this, attrs, options);
+ if (options && options.ignoreChanges) {
+ // TODO: This will need to be changed to reflect now change
+ // tracking mechanisms in Backbone.js 1.0.0
+ this.changed = {};
+ this._previousAttributes = _.clone(this.attributes);
+ }
+ return ret;
+ },
+
+ // **`.unset(attr, opts)` ** removes an attribute from the entity.
+ unset: function (attr, opts) {
+ attr = VIE.Util.mapAttributeNS(attr, this.vie.namespaces);
+ return Backbone.Model.prototype.unset.call(this, attr, opts);
+ },
+
+ // Validation based on type rules.
+ //
+ // There are two ways to skip validation for entity operations:
+ //
+ // * `options.silent = true`
+ // * `options.validate = false`
+ validate: function (attrs, opts) {
+ if (opts && opts.validate === false) {
+ return;
+ }
+ var types = this.get('@type');
+ if (_.isArray(types)) {
+ var results = [];
+ _.each(types, function (type) {
+ var res = this.validateByType(type, attrs, opts);
+ if (res) {
+ results.push(res);
}
- }, self.vie);
-
- var Model = Backbone.Model.extend({
- idAttribute: '@subject',
-
- initialize: function(attributes, options) {
- if (attributes['@subject']) {
- this.id = this['@subject'] = this.toReference(attributes['@subject']);
- } else {
- this.id = this['@subject'] = attributes['@subject'] = this.cid.replace('c', '_:bnode');
- }
- return this;
- },
-
- schema: function() {
- return VIE.Util.getFormSchema(this);
- },
-
- // ### Getter, Has, Setter
- // #### `.get(attr)`
- // To be able to communicate to a VIE Entity you can use a simple get(property)
- // command as in `entity.get('rdfs:label')` which will give you one or more literals.
- // If the property points to a collection, its entities can be browsed further.
- get: function (attr) {
- attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
- var value = Backbone.Model.prototype.get.call(this, attr);
- value = (_.isArray(value))? value : [ value ];
-
- value = _.map(value, function(v) {
- if (v !== undefined && attr === '@type' && self.vie.types.get(v)) {
- return self.vie.types.get(v);
- } else if (v !== undefined && self.vie.entities.get(v)) {
- return self.vie.entities.get(v);
- } else {
- return v;
- }
- }, this);
- if(value.length === 0) {
- return undefined;
- }
- // if there is only one element, just return that one
- value = (value.length === 1)? value[0] : value;
- return value;
- },
-
- // #### `.has(attr)`
- // Sometimes you'd like to determine if a specific attribute is set
- // in an entity. For this reason you can call for example `person.has('friend')`
- // to determine if a person entity has friends.
- has: function(attr) {
- attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
- return Backbone.Model.prototype.has.call(this, attr);
- },
-
- hasRelations: function() {
- var found = false;
- _.each(this.attributes, function (value) {
- if (value && value.isCollection) {
- found = true;
- }
- });
- return found;
- },
-
- // #### `.set(attrName, value, opts)`,
- // The `options` parameter always refers to a `Backbone.Model.set` `options` object.
- //
- // **`.set(attributes, options)`** is the most universal way of calling the
- // `.set` method. In this case the `attributes` object is a map of all
- // attributes to be changed.
- set : function(attrs, options, opts) {
- if (!attrs) {
- return this;
- }
-
- if (attrs['@subject']) {
- attrs['@subject'] = this.toReference(attrs['@subject']);
- }
-
- // Use **`.set(attrName, value, options)`** for setting or changing exactly one
- // entity attribute.
- if (typeof attrs === "string") {
- var obj = {};
- obj[attrs] = options;
- return this.set(obj, opts);
- }
- // **`.set(entity)`**: In case you'd pass a VIE entity,
- // the passed entities attributes are being set for the entity.
- if (attrs.attributes) {
- attrs = attrs.attributes;
- }
- var self = this;
- var coll;
- // resolve shortened URIs like rdfs:label..
- _.each (attrs, function (value, key) {
- var newKey = VIE.Util.mapAttributeNS(key, self.vie.namespaces);
- if (key !== newKey) {
- delete attrs[key];
- attrs[newKey] = value;
- }
- }, this);
- // Finally iterate through the *attributes* to be set and prepare
- // them for the Backbone.Model.set method.
- _.each (attrs, function (value, key) {
- if (!value) { return; }
- if (key.indexOf('@') === -1) {
- if (value.isCollection) {
- // ignore
- value.each(function (child) {
- self.vie.entities.addOrUpdate(child);
- });
- } else if (value.isEntity) {
- self.vie.entities.addOrUpdate(value);
- coll = new self.vie.Collection(value, {
- vie: self.vie,
- predicate: key
- });
- attrs[key] = coll;
- } else if (_.isArray(value)) {
- if (this.attributes[key] && this.attributes[key].isCollection) {
- var newEntities = this.attributes[key].addOrUpdate(value);
- attrs[key] = this.attributes[key];
- attrs[key].reset(newEntities);
- }
- } else if (value["@value"]) {
- // The value is a literal object, ignore
- } else if (_.isObject(value) && !_.isDate(value)) {
- // The value is another VIE Entity
- var child = new self.vie.Entity(value, options);
- // which is being stored in `v.entities`
- self.vie.entities.addOrUpdate(child);
- // and set as VIE Collection attribute on the original entity
- coll = new self.vie.Collection(value, {
- vie: self.vie,
- predicate: key
- });
- attrs[key] = coll;
- } else {
- // ignore
- }
- }
- }, this);
- var ret = Backbone.Model.prototype.set.call(this, attrs, options);
- if (options && options.ignoreChanges) {
- // TODO: This will need to be changed to reflect now change tracking mechanisms
- // in Backbone.js 1.0.0
- this.changed = {};
- this._previousAttributes = _.clone(this.attributes);
- }
- return ret;
- },
-
- // **`.unset(attr, opts)` ** removes an attribute from the entity.
- unset: function (attr, opts) {
- attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
- return Backbone.Model.prototype.unset.call(this, attr, opts);
- },
-
- // Validation based on type rules.
- //
- // There are two ways to skip validation for entity operations:
- //
- // * `options.silent = true`
- // * `options.validate = false`
- validate: function (attrs, opts) {
- if (opts && opts.validate === false) {
- return;
- }
- var types = this.get('@type');
- if (_.isArray(types)) {
- var results = [];
- _.each(types, function (type) {
- var res = this.validateByType(type, attrs, opts);
- if (res) {
- results.push(res);
- }
- }, this);
- if (_.isEmpty(results)) {
- return;
- }
- return _.flatten(results);
- }
-
- return this.validateByType(types, attrs, opts);
- },
-
- validateByType: function (type, attrs, opts) {
- var messages = {
- max: '<%= property %> cannot contain more than <%= num %> items',
- min: '<%= property %> must contain at least <%= num %> items',
- required: '<%= property %> is required'
- };
-
- if (!type.attributes) {
- return;
- }
-
- var toError = function (definition, constraint, messageValues) {
- return {
- property: definition.id,
- constraint: constraint,
- message: _.template(messages[constraint], _.extend({
- property: definition.id
- }, messageValues))
- };
- };
-
- var checkMin = function (definition, attrs) {
- if (!attrs[definition.id] || _.isEmpty(attrs[definition.id])) {
- return toError(definition, 'required', {});
- }
- };
-
- // Check the number of items in attr against max
- var checkMax = function (definition, attrs) {
- if (!attrs[definition.id]) {
- return;
- }
-
- if (!attrs[definition.id].isCollection && !_.isArray(attrs[definition.id])) {
- return;
- }
-
- if (attrs[definition.id].length > definition.max) {
- return toError(definition, 'max', {
- num: definition.max
- });
- }
- };
-
- var results = [];
- _.each(type.attributes.list(), function (definition) {
- var res;
- if (definition.max && definition.max != -1) {
- res = checkMax(definition, attrs);
- if (res) {
- results.push(res);
- }
- }
-
- if (definition.min && definition.min > 0) {
- res = checkMin(definition, attrs);
- if (res) {
- results.push(res);
- }
- }
- });
-
- if (_.isEmpty(results)) {
- return;
- }
- return results;
- },
-
- isNew: function() {
- if (this.getSubjectUri().substr(0, 7) === '_:bnode') {
- return true;
- }
- return false;
- },
-
- hasChanged: function(attr) {
- if (this.markedChanged) {
- return true;
- }
-
- return Backbone.Model.prototype.hasChanged.call(this, attr);
- },
-
- // Force hasChanged to return true
- forceChanged: function(changed) {
- this.markedChanged = changed ? true : false;
- },
-
- // **`getSubject()`** is the getter for the entity identifier.
- getSubject: function(){
- if (typeof this.id === "undefined") {
- this.id = this.attributes[this.idAttribute];
- }
- if (typeof this.id === 'string') {
- if (this.id.substr(0, 7) === 'http://' || this.id.substr(0, 4) === 'urn:') {
- return this.toReference(this.id);
- }
- return this.id;
- }
- return this.cid.replace('c', '_:bnode');
- },
-
- // TODO describe
- getSubjectUri: function(){
- return this.fromReference(this.getSubject());
- },
-
- isReference: function(uri){
- var matcher = new RegExp("^\\<([^\\>]*)\\>$");
- if (matcher.exec(uri)) {
- return true;
- }
- return false;
- },
-
- toReference: function(uri){
- if (_.isArray(uri)) {
- var self = this;
- return _.map(uri, function(part) {
- return self.toReference(part);
- });
- }
- var ns = this.vie.namespaces;
- var ret = uri;
- if (uri.substring(0, 2) === "_:") {
- ret = uri;
- }
- else if (ns.isCurie(uri)) {
- ret = ns.uri(uri);
- if (ret === "<" + ns.base() + uri + ">") {
- /* no base namespace extension with IDs */
- ret = '<' + uri + '>';
- }
- } else if (!ns.isUri(uri)) {
- ret = '<' + uri + '>';
- }
- return ret;
- },
-
- fromReference: function(uri){
- var ns = this.vie.namespaces;
- if (!ns.isUri(uri)) {
- return uri;
- }
- return uri.substring(1, uri.length - 1);
- },
-
- as: function(encoding){
- if (encoding === "JSON") {
- return this.toJSON();
- }
- if (encoding === "JSONLD") {
- return this.toJSONLD();
- }
- throw new Error("Unknown encoding " + encoding);
- },
-
- toJSONLD: function(){
- var instanceLD = {};
- var instance = this;
- _.each(instance.attributes, function(value, name){
- var entityValue = value; //instance.get(name);
-
- if (value instanceof instance.vie.Collection) {
- entityValue = value.map(function(instance) {
- return instance.getSubject();
- });
- }
-
- // TODO: Handle collections separately
- instanceLD[name] = entityValue;
- });
-
- instanceLD['@subject'] = instance.getSubject();
-
- return instanceLD;
- },
-
- // **`.setOrAdd(arg1, arg2)`** similar to `.set(..)`, `.setOrAdd(..)` can
- // be used for setting one or more attributes of an entity, but in
- // this case it's a collection of values, not just one. That means, if the
- // entity already has the attribute set, make the value to a VIE Collection
- // and use the collection as value. The collection can contain entities
- // or literals, but not both at the same time.
- setOrAdd: function (arg1, arg2, option) {
- var entity = this;
- if (typeof arg1 === "string" && arg2) {
- // calling entity.setOrAdd("rdfs:type", "example:Musician")
- entity._setOrAddOne(arg1, arg2, option);
- }
- else
- if (typeof arg1 === "object") {
- // calling entity.setOrAdd({"rdfs:type": "example:Musician", ...})
- _(arg1).each(function(val, key){
- entity._setOrAddOne(key, val, arg2);
- });
- }
- return this;
- },
-
-
- /* attr is always of type string */
- /* value can be of type: string,int,double,object,VIE.Entity,VIE.Collection */
- /* val can be of type: undefined,string,int,double,array,VIE.Collection */
-
- /* depending on the type of value and the type of val, different actions need to be made */
- _setOrAddOne: function (attr, value, options) {
- if (!attr || !value)
- return;
- options = (options)? options : {};
- var v;
-
- attr = VIE.Util.mapAttributeNS(attr, self.vie.namespaces);
-
- if (_.isArray(value)) {
- for (v = 0; v < value.length; v++) {
- this._setOrAddOne(attr, value[v], options);
- }
- return;
- }
-
- if (attr === "@type" && value instanceof self.vie.Type) {
- value = value.id;
- }
-
- var obj = {};
- var existing = Backbone.Model.prototype.get.call(this, attr);
-
- if (!existing) {
- obj[attr] = value;
- this.set(obj, options);
- } else if (existing.isCollection) {
- if (value.isCollection) {
- value.each(function (model) {
- existing.add(model);
- });
- } else if (value.isEntity) {
- existing.add(value);
- } else if (typeof value === "object") {
- value = new this.vie.Entity(value);
- existing.add(value);
- } else {
- throw new Error("you cannot add a literal to a collection of entities!");
- }
- this.trigger('change:' + attr, this, value, {});
- this.change({});
- } else if (_.isArray(existing)) {
- if (value.isCollection) {
- for (v = 0; v < value.size(); v++) {
- this._setOrAddOne(attr, value.at(v).getSubject(), options);
- }
- } else if (value.isEntity) {
- this._setOrAddOne(attr, value.getSubject(), options);
- } else if (typeof value === "object") {
- value = new this.vie.Entity(value);
- this._setOrAddOne(attr, value, options);
- } else {
- /* yes, we (have to) allow multiple equal values */
- existing.push(value);
- obj[attr] = existing;
- this.set(obj);
- }
- } else {
- var arr = [ existing ];
- arr.push(value);
- obj[attr] = arr;
- return this.set(obj, options);
- }
- },
-
- // **`.hasType(type)`** determines if the entity has the explicit `type` set.
- hasType: function(type){
- type = self.vie.types.get(type);
- return this.hasPropertyValue("@type", type);
- },
-
- // TODO describe
- hasPropertyValue: function(property, value) {
- var t = this.get(property);
- if (!(value instanceof Object)) {
- value = self.vie.entities.get(value);
- }
- if (t instanceof Array) {
- return t.indexOf(value) !== -1;
- }
- else {
- return t === value;
- }
- },
-
- // **`.isof(type)`** determines if the entity is of `type` by explicit or implicit
- // declaration. E.g. if Employee is a subtype of Person and e Entity has
- // explicitly set type Employee, e.isof(Person) will evaluate to true.
- isof: function (type) {
- var types = this.get('@type');
-
- if (types === undefined) {
- return false;
- }
- types = (_.isArray(types))? types : [ types ];
-
- type = (self.vie.types.get(type))? self.vie.types.get(type) : new self.vie.Type(type);
- for (var t = 0; t < types.length; t++) {
- if (self.vie.types.get(types[t])) {
- if (self.vie.types.get(types[t]).isof(type)) {
- return true;
- }
- } else {
- var typeTmp = new self.vie.Type(types[t]);
- if (typeTmp.id === type.id) {
- return true;
- }
- }
- }
- return false;
- },
- // TODO describe
- addTo : function (collection, update) {
- var self = this;
- if (collection instanceof self.vie.Collection) {
- if (update) {
- collection.addOrUpdate(self);
- } else {
- collection.add(self);
- }
- return this;
- }
- throw new Error("Please provide a proper collection of type VIE.Collection as argument!");
- },
-
- isEntity: true,
-
- vie: self.vie
+ }, this);
+ if (_.isEmpty(results)) {
+ return;
+ }
+ return _.flatten(results);
+ }
+
+ return this.validateByType(types, attrs, opts);
+ },
+
+ validateByType: function (type, attrs, opts) {
+ var messages = {
+ max: '<%= property %> cannot contain more than <%= num %> items',
+ min: '<%= property %> must contain at least <%= num %> items',
+ required: '<%= property %> is required'
+ };
+
+ if (!type.attributes) {
+ return;
+ }
+
+ var toError = function (definition, constraint, messageValues) {
+ return {
+ property: definition.id,
+ constraint: constraint,
+ message: _.template(messages[constraint], _.extend({
+ property: definition.id
+ }, messageValues))
+ };
+ };
+
+ var checkMin = function (definition, attrs) {
+ if (!attrs[definition.id] || _.isEmpty(attrs[definition.id])) {
+ return toError(definition, 'required', {});
+ }
+ };
+
+ // Check the number of items in attr against max
+ var checkMax = function (definition, attrs) {
+ if (!attrs[definition.id]) {
+ return;
+ }
+
+ if (!attrs[definition.id].isCollection && !_.isArray(attrs[definition.id])) {
+ return;
+ }
+
+ if (attrs[definition.id].length > definition.max) {
+ return toError(definition, 'max', {
+ num: definition.max
+ });
+ }
+ };
+
+ var results = [];
+ _.each(type.attributes.list(), function (definition) {
+ var res;
+ if (definition.max && definition.max != -1) {
+ res = checkMax(definition, attrs);
+ if (res) {
+ results.push(res);
+ }
+ }
+
+ if (definition.min && definition.min > 0) {
+ res = checkMin(definition, attrs);
+ if (res) {
+ results.push(res);
+ }
+ }
});
- return new Model(attrs, opts);
-};
+ if (_.isEmpty(results)) {
+ return;
+ }
+ return results;
+ },
+
+ isNew: function() {
+ if (this.getSubjectUri().substr(0, 7) === '_:bnode') {
+ return true;
+ }
+ return false;
+ },
+
+ hasChanged: function(attr) {
+ if (this.markedChanged) {
+ return true;
+ }
+
+ return Backbone.Model.prototype.hasChanged.call(this, attr);
+ },
+
+ // Force hasChanged to return true
+ forceChanged: function(changed) {
+ this.markedChanged = changed ? true : false;
+ },
+
+ // **`getSubject()`** is the getter for the entity identifier.
+ getSubject: function(){
+ if (typeof this.id === "undefined") {
+ this.id = this.attributes[this.idAttribute];
+ }
+ if (typeof this.id === 'string') {
+ if (this.id.substr(0, 7) === 'http://' || this.id.substr(0, 4) === 'urn:') {
+ return this.toReference(this.id);
+ }
+ return this.id;
+ }
+ return this.cid.replace('c', '_:bnode');
+ },
+
+ // TODO describe
+ getSubjectUri: function(){
+ return this.fromReference(this.getSubject());
+ },
+
+ isReference: function(uri){
+ var matcher = new RegExp("^\\<([^\\>]*)\\>$");
+ if (matcher.exec(uri)) {
+ return true;
+ }
+ return false;
+ },
+
+ toReference: function(uri){
+ if (_.isArray(uri)) {
+ return _.map(uri, function(part) {
+ return this.toReference(part);
+ }, this);
+ }
+ var ns = this.vie.namespaces;
+ var ret = uri;
+ if (uri.substring(0, 2) === "_:") {
+ ret = uri;
+ } else if (ns.isCurie(uri)) {
+ ret = ns.uri(uri);
+ if (ret === "<" + ns.base() + uri + ">") {
+ // no base namespace extension with IDs
+ ret = '<' + uri + '>';
+ }
+ } else if (!ns.isUri(uri)) {
+ ret = '<' + uri + '>';
+ }
+ return ret;
+ },
+
+ fromReference: function(uri){
+ var ns = this.vie.namespaces;
+ if (!ns.isUri(uri)) {
+ return uri;
+ }
+ return uri.substring(1, uri.length - 1);
+ },
+
+ as: function(encoding){
+ if (encoding === "JSON") {
+ return this.toJSON();
+ }
+ if (encoding === "JSONLD") {
+ return this.toJSONLD();
+ }
+ throw new Error("Unknown encoding " + encoding);
+ },
+
+ toJSONLD: function(){
+ var instanceLD = {};
+ _.each(this.attributes, function(value, name){
+ var entityValue = this.get(name);
+
+ if (value instanceof this.vie.Collection) {
+ entityValue = value.map(function(instance) {
+ return instance.getSubject();
+ });
+ }
+
+ instanceLD[name] = entityValue;
+ }, this);
+
+ instanceLD['@subject'] = this.getSubject();
+
+ return instanceLD;
+ },
+
+ // **`.setOrAdd(arg1, arg2)`** similar to `.set(..)`, `.setOrAdd(..)` can
+ // be used for setting one or more attributes of an entity, but in
+ // this case it's a collection of values, not just one. That means, if the
+ // entity already has the attribute set, make the value to a VIE Collection
+ // and use the collection as value. The collection can contain entities
+ // or literals, but not both at the same time.
+ setOrAdd: function (arg1, arg2, option) {
+ if (_.isString(arg1) && arg2) {
+ // calling entity.setOrAdd("rdfs:type", "example:Musician")
+ this._setOrAddOne(arg1, arg2, option);
+ } else if (_.isObject(arg1)) {
+ // calling entity.setOrAdd({"rdfs:type": "example:Musician", ...})
+ _.each(arg1, function(val, key){
+ this._setOrAddOne(key, val, arg2);
+ }, this);
+ }
+ return this;
+ },
+
+
+ /* attr is always of type string */
+ /* value can be of type: string,int,double,object,VIE.Entity,VIE.Collection */
+ /* val can be of type: undefined,string,int,double,array,VIE.Collection */
+
+ /* depending on the type of value and the type of val, different actions need to be made */
+ _setOrAddOne: function (attr, value, options) {
+ if (!attr || !value) {
+ return;
+ }
+ options = (options)? options : {};
+
+ attr = VIE.Util.mapAttributeNS(attr, this.vie.namespaces);
+
+ if (_.isArray(value)) {
+ _.each(value, function (v) {
+ this._setOrAddOne(attr, value[v], options);
+ }, this);
+ return;
+ }
+
+ if (attr === "@type" && value instanceof this.vie.Type) {
+ value = value.id;
+ }
+
+ var obj = {};
+ var existing = Backbone.Model.prototype.get.call(this, attr);
+
+ if (!existing) {
+ obj[attr] = value;
+ this.set(obj, options);
+ } else if (existing.isCollection) {
+ if (value.isCollection) {
+ value.each(function (model) {
+ existing.add(model);
+ });
+ } else if (value.isEntity) {
+ existing.add(value);
+ } else if (_.isObject(value)) {
+ value = new this.vie.Entity(value);
+ existing.add(value);
+ } else {
+ throw new Error("you cannot add a literal to a collection of entities!");
+ }
+ this.trigger('change:' + attr, this, value, {});
+ this.change({});
+ } else if (_.isArray(existing)) {
+ if (value.isCollection) {
+ value.each(function (v) {
+ this._setOrAddOne(attr, value.at(v).getSubject(), options);
+ }, this);
+ } else if (value.isEntity) {
+ this._setOrAddOne(attr, value.getSubject(), options);
+ } else if (_.isObject(value)) {
+ value = new this.vie.Entity(value);
+ this._setOrAddOne(attr, value, options);
+ } else {
+ /* yes, we (have to) allow multiple equal values */
+ var newArray = existing.slice(0);
+ newArray.push(value);
+ this.set(attr, newArray);
+ }
+ } else {
+ var arr = [ existing ];
+ arr.push(value);
+ obj[attr] = arr;
+ return this.set(obj, options);
+ }
+ },
+
+ // **`.hasType(type)`** determines if the entity has the explicit `type` set.
+ hasType: function(type){
+ type = this.vie.types.get(type);
+ return this.hasPropertyValue("@type", type);
+ },
+
+ // TODO describe
+ hasPropertyValue: function(property, value) {
+ var t = this.get(property);
+ if (!_.isObject(value)) {
+ value = this.vie.entities.get(value);
+ }
+ if (_.isArray(t)) {
+ return t.indexOf(value) !== -1;
+ } else {
+ return t === value;
+ }
+ },
+
+ // **`.isof(type)`** determines if the entity is of `type` by explicit or implicit
+ // declaration. E.g. if Employee is a subtype of Person and e Entity has
+ // explicitly set type Employee, e.isof(Person) will evaluate to true.
+ isof: function (type) {
+ var types = this.get('@type');
+
+ if (types === undefined) {
+ return false;
+ }
+ types = (_.isArray(types))? types : [ types ];
+
+ type = (this.vie.types.get(type)) ? this.vie.types.get(type) : new this.vie.Type(type);
+
+ var isof = false;
+ _.each(types, function (t) {
+ if (this.vie.types.get(t).isof(type)) {
+ isof = true;
+ }
+ }, this);
+ return isof;
+ },
+
+ // TODO describe
+ addTo : function (collection, update) {
+ if (collection instanceof this.vie.Collection) {
+ if (update) {
+ collection.addOrUpdate(this);
+ } else {
+ collection.add(this);
+ }
+ return this;
+ }
+ throw new Error("Please provide a proper collection of type VIE.Collection as argument!");
+ }
+
+});
View
21 src/VIE.js
@@ -416,19 +416,16 @@ VIE.prototype.getTypedEntityClass = function (type) {
if (!typeType) {
throw new Error("Unknown type " + type);
}
- var TypedEntityClass = function (attrs, opts) {
- if (!attrs) {
- attrs = {};
- }
- attrs["@type"] = type;
- this.set(attrs, opts);
- };
- TypedEntityClass.prototype = new this.Entity();
- TypedEntityClass.prototype.schema = function () {
- return VIE.Util.getFormSchemaForType(typeType);
- };
- return TypedEntityClass;
+ if (!this.typeEntityClasses[type.id]) {
+ this.typeEntityClasses[type.id] = this.Entity.extend({
+ defaults: {
+ '@type': type
+ }
+ });
+ }
+ return this.typeEntityClasses[type.id];
};
+VIE.prototype.typeEntityClasses = {};
// ## Running VIE on Node.js
//
View
9 test/core/vie.js
@@ -560,7 +560,16 @@ test("vie.js Generate Typed Entity Classes", function () {
var Person = new TypedEntityClass({"name": "Sebastian"});
ok(Person.isEntity);
+ ok(Person instanceof TypedEntityClass);
ok(Person.isof("Person"));
equal(Person.get("@type").id, "<" + v.namespaces.base() + "Person" + ">");
ok(Person instanceof Backbone.Model);
+
+ var SecondPerson = new TypedEntityClass({"name": "Henri"});
+ ok(SecondPerson.isEntity);
+ ok(SecondPerson instanceof TypedEntityClass);
+ ok(SecondPerson.isof("Person"));
+ equal(Person.get('name'), 'Sebastian');
+ equal(SecondPerson.get('name'), 'Henri');
+ ok(Person.cid !== SecondPerson.cid);
});
View
7 test/service/stanbol.js
@@ -136,8 +136,11 @@ test("VIE.js StanbolService - Analyze - Default", function () {
}
ok(allEntities, "All result elements are VIE entities.");
var firstTextAnnotation = _(entities).filter(function(e){return e.isof("enhancer:TextAnnotation") && e.get("enhancer:selected-text");})[0];
- var s = firstTextAnnotation.get("enhancer:selected-text").toString();
- ok(s.substring(s.length-4, s.length-2) != "\"@", "Selected text should be converted into a normal string.");
+ ok(firstTextAnnotation);
+ if (firstTextAnnotation) {
+ var s = firstTextAnnotation.get("enhancer:selected-text").toString();
+ ok(s.substring(s.length-4, s.length-2) != "\"@", "Selected text should be converted into a normal string.");
+ }
}
start();
})
Please sign in to comment.
Something went wrong with that request. Please try again.