├── .gitignore ├── Makefile ├── README.md ├── bin └── start ├── config.js ├── lib ├── container.js ├── crystal.js ├── memory.js ├── mimetype.js ├── rdf.js ├── router.js ├── server.js └── utils.js └── test ├── alice.ttl ├── bob.ttl ├── empty.ttl ├── phil.rdf ├── test-container.js ├── test-memory.js └── test-rdf.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | node_modules 14 | 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CD=cd 2 | NPM=npm install 3 | NODEUNIT=`npm bin -g`/nodeunit 4 | MODULES_DIR=node_modules 5 | TEST_DIR=test 6 | DEPS=connect commander jsonld 7 | EXISTING=$(subst $(MODULES_DIR)/,, $(wildcard $(MODULES_DIR)/*)) 8 | MISSING=$(filter-out $(EXISTING), $(DEPS)) 9 | TESTS=$(wildcard $(TEST_DIR)/test-*.js) 10 | 11 | all: 12 | @echo "" 13 | @echo "Node Linked Data Platform" 14 | @echo "" 15 | @echo "available targets:" 16 | @echo " libs -- locally installs runtime node dependencies via npm" 17 | @echo " test -- run unit test (requires nodeunit to be installed)" 18 | 19 | libs: 20 | ifneq ($(MISSING),) 21 | $(NPM) $(MISSING) 22 | endif 23 | 24 | .PHONY: test 25 | test: 26 | @$(NODEUNIT) $(TESTS) 27 | 28 | server-empty: 29 | @echo "start an empty server" 30 | bin/start -l test/empty.ttl 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `node_ldp` -- Linked Data Platform for Node 2 | =========================================== 3 | 4 | Have you ever wanted to quickly publish Linked Data resources on the Web? 5 | Then node_ldp is your answer. 6 | Since `node_ldp` is an implementation of the [Linked Data Platform specification](http://www.w3.org/TR/ldp/) it additionally allows you to: 7 | 8 | * create resources via `PUT`, 9 | * update/create resources via `POST`, 10 | * delete resources via `DELETE`, 11 | 12 | All of this for plain resources or resources managed in [LDP containers](http://www.w3.org/TR/ldp/#linked-data-platform-containers). 13 | 14 | ## Runtime Dependencies 15 | 16 | You can install most dependencies with 17 | 18 | make libs 19 | 20 | In addition, [`node_raptor`](http://github.com/0xfeedface/node_raptor) is required. 21 | 22 | ## Usage 23 | 24 | You can start LDP with several options. 25 | To see the options run `bin/start --help`. 26 | Fake namespace and initial triples file are currently *required*. 27 | 28 | Usage: start [options] 29 | 30 | Options: 31 | 32 | -h, --help output usage information 33 | -l, --load path to file with triples to serve 34 | -f, --fake-namespace fake namespace of URIs for testing 35 | -p, --port fake host part of URIs for testing 36 | 37 | ## Running Test 38 | 39 | `node_ldp`'s test suite requires' `nodeunit`. 40 | Once you have that installed just type 41 | 42 | make test 43 | 44 | at the command prompt. 45 | 46 | -------------------------------------------------------------------------------- /bin/start: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var util = require('util'), 4 | path = require('path'), 5 | program = require('commander'), 6 | fs = require('fs'); 7 | 8 | program 9 | .option('-l, --load ', 'path to file with triples to serve') 10 | .option('-f, --fake-namespace ', 'fake namespace of URIs for testing') 11 | .option('-p, --port ', 'fake host part of URIs for testing', parseInt) 12 | .parse(process.argv); 13 | 14 | var config; 15 | try { 16 | config = require(path.join(__dirname, '..', 'config')).defaults; 17 | } catch (error) { 18 | console.log('Could not find config.js.'); 19 | console.log('Please copy config.js-dist to config.js, adapt it to your needs and try again.'); 20 | process.exit(); 21 | } 22 | 23 | var options = { 24 | 'port': program.port || config.server.port, 25 | 'load': program.load, 26 | 'fakeNamespace': program.fakeNamespace || config.fakeNamespace 27 | }; 28 | 29 | var server = require(path.join('..', 'lib', 'server.js')); 30 | server.start(options, function (err, server) { 31 | if (err) { 32 | return util.puts('Error starting LDP server: ' + err.message); 33 | } 34 | 35 | process.stdout.write('Linked Data Platform server started.\n'); 36 | process.stdout.write('listening on port ' + options.port + '\n'); 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var defaults = exports.defaults = { 2 | server: { 3 | port: 8001 4 | } 5 | }; 6 | 7 | /* 8 | * JSON-LD context. 9 | * To support JSON-LD define the context for your vocabulary here. 10 | * The example context is for the Bob and Alice examples. 11 | */ 12 | var ldContext = exports.ldContext = { 13 | // properties 14 | name: 'http://xmlns.com/foaf/0.1/name', 15 | firstName: 'http://xmlns.com/foaf/0.1/firstName', 16 | lastName: 'http://xmlns.com/foaf/0.1/lastName', 17 | birthday: 'http://xmlns.com/foaf/0.1/birthday', 18 | gender: 'http://xmlns.com/foaf/0.1/gender', 19 | image: 'http://xmlns.com/foaf/0.1/img', 20 | blankNode: 'http://webid.example.com/blankNode', 21 | location: 'http://webid.example.com/location', 22 | creator: 'http://purl.org/dc/elements/1.1/creator', 23 | // types 24 | Person: 'http://xmlns.com/foaf/0.1/Person', 25 | Image: 'http://xmlns.com/foaf/0.1/Image', 26 | Document: 'http://xmlns.com/foaf/0.1/Document' 27 | }; 28 | 29 | /* 30 | * var ldContext = exports.ldContext = { 31 | * title: 'http://purl.org/dc/terms/title', 32 | * created: 'http://purl.org/dc/terms/created', 33 | * modified: 'http://purl.org/dc/terms/modified', 34 | * content: 'http://rdfs.org/sioc/ns#content', 35 | * creator: 'http://purl.org/dc/terms/creator', 36 | * replies: 'http://rdfs.org/sioc/ns#has_reply', 37 | * Comment: 'http://ns.bioasq.org/Comment', 38 | * User: 'http://ns.bioasq.org/User', 39 | * Question: 'http://ns.bioasq.org/Question' 40 | * }; 41 | */ 42 | 43 | /** 44 | * Mapping of mime types to raptor parser names. 45 | */ 46 | var typeMapping = exports.typeMapping = { 47 | 'application/rdf+xml': 'rdfxml', 48 | 'text/turtle': 'turtle', 49 | 'application/rdf+json': 'json', 50 | 'application/ld+json': 'jsonld', 51 | 'text/plain': 'ntriples', 52 | '*/*': 'turtle' 53 | }; 54 | 55 | /** 56 | * Mapping of mime types to output providers. 57 | */ 58 | var outputMapping = exports.outputMapping = { 59 | 'application/rdf+xml': 'rdf', 60 | 'text/turtle': 'rdf', 61 | 'application/rdf+json': 'rdf', 62 | 'text/plain': 'rdf', 63 | 'text/html': 'html', 64 | 'application/xml': 'html', 65 | '*/*': 'rdf' 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /lib/container.js: -------------------------------------------------------------------------------- 1 | const kRDFType = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; 2 | const kLDPContainer = 'http://www.w3.org/ns/ldp#Container'; 3 | const kMembershipPredicate = 'http://www.w3.org/2000/01/rdf-schema#member'; 4 | 5 | var rdf = require('./rdf'); 6 | 7 | /** 8 | * Adds container interface on top of store interface. 9 | */ 10 | var Container = exports.Container = function (store) { 11 | var _store = store, 12 | _containers = {}; 13 | 14 | function _add(containerURI, uri) { 15 | _containers[containerURI][uri] = true; 16 | } 17 | 18 | function _delete(containerURI) { 19 | delete _containers[containerURI]; 20 | } 21 | 22 | function _remove(containerURI, uri) { 23 | delete _containers[containerURI][uri]; 24 | } 25 | 26 | function _create(containerURI) { 27 | if (!_exists(containerURI)) { _containers[containerURI] = {}; } 28 | } 29 | 30 | function _get(containerURI) { 31 | return _containers[containerURI]; 32 | } 33 | 34 | function _exists(containerURI) { 35 | return (_containers[containerURI] !== undefined); 36 | } 37 | 38 | function _containerDescription(containerURI) { 39 | var containerDescription = {}; 40 | containerDescription[containerURI] = {}; 41 | containerDescription[containerURI][kRDFType] = [{ 42 | 'type': 'uri', 43 | 'value': kLDPContainer 44 | }]; 45 | containerDescription[containerURI][kMembershipPredicate] = []; 46 | Object.keys(_containers[containerURI]).forEach(function (uri) { 47 | containerDescription[containerURI][kMembershipPredicate].push({ 48 | 'type': 'uri', 49 | 'value': uri 50 | }); 51 | }); 52 | 53 | return containerDescription; 54 | } 55 | 56 | function _container(containerURI) { 57 | return { 58 | /** 59 | * Create resource with URI and description inside current container. 60 | */ 61 | create: function (uri, description, cb) { 62 | _store.create(uri, description, function (err) { 63 | if (err) { return cb(err); } 64 | _add(containerURI, uri); 65 | if (cb !== undefined) { cb(null); } 66 | }); 67 | }, 68 | 69 | /** 70 | * Check whether resource exists in current container. 71 | */ 72 | exists: function (uri, cb) { 73 | cb(null, _get(containerURI)[uri] !== undefined); 74 | }, 75 | 76 | get: _store.get, 77 | 78 | /** 79 | * List resources in the current container. 80 | */ 81 | list: function (cb) { 82 | var result = _containerDescription(containerURI); 83 | Object.keys(_get(containerURI)).forEach(function (uri) { 84 | _store.get(uri, function (err, mixin) { 85 | if (!err) { rdf.mixinDescription(result, mixin); } 86 | }); 87 | }); 88 | 89 | cb(null, result); 90 | }, 91 | 92 | /** 93 | * Delete resource from current container. 94 | */ 95 | remove: function (uri, cb) { 96 | _store.remove(uri, function (err) { 97 | if (err) { return cb(err); } 98 | _remove(containerURI, uri); 99 | if (cb !== undefined) { cb(null); } 100 | }); 101 | }, 102 | 103 | update: _store.update 104 | }; 105 | } 106 | 107 | //////////////////////////////////////////////////////////////////////////// 108 | // Container interface 109 | //////////////////////////////////////////////////////////////////////////// 110 | 111 | Container.prototype.createContainer = function (containerURI, cb) { 112 | if (_exists(containerURI)) { return cb(Error('Container exists.')); } 113 | _create(containerURI); 114 | if (cb !== undefined) { cb(null, _container(containerURI)); } 115 | }; 116 | 117 | Container.prototype.getContainer = function (containerURI, cb) { 118 | if (!_exists(containerURI)) { return cb(Error('Not a container URI.')); } 119 | cb(null, _container(containerURI)); 120 | }; 121 | 122 | Container.prototype.isContainer = function (containerURI, cb) { 123 | cb(null, _exists(containerURI)); 124 | }; 125 | 126 | Container.prototype.removeContainer = function (containerURI, cb) { 127 | if (!_exists(containerURI)) { return cb(Error('Not a container URI.')); } 128 | _delete(containerURI); 129 | if (cb !== undefined) { cb(null); } 130 | }; 131 | 132 | //////////////////////////////////////////////////////////////////////////// 133 | // Resource-centric interface with container extension 134 | //////////////////////////////////////////////////////////////////////////// 135 | 136 | Container.prototype.create = function (containerURI, uri, description, cb) { 137 | if (arguments.length === 3) { 138 | // only 3 parameters: interpret as uri, description, cb 139 | return _store.create(containerURI, uri, description); 140 | } 141 | 142 | if (!_exists(containerURI)) { return cb(Error('Not a container URI.')); } 143 | return _container(containerURI).create(uri, description, cb); 144 | }; 145 | 146 | Container.prototype.exists = function (containerURI, uri, cb) { 147 | if (arguments.length === 2) { 148 | // only 2 parameters: interpret as uri, cb 149 | return _store.exists(containerURI, uri); 150 | } 151 | 152 | if (!_exists(containerURI)) { return cb(Error('Not a container URI.')); } 153 | return _container(containerURI).exists(uri, cb); 154 | }; 155 | 156 | Container.prototype.get = _store.get; 157 | 158 | Container.prototype.remove = function (uri, cb) { 159 | var container; 160 | var hasContainer = Object.keys(_containers).some(function (containerURI) { 161 | return Object.keys(_containers[containerURI]).some(function (resourceURI) { 162 | if (resourceURI === uri) { 163 | container = _container(containerURI); 164 | return true; 165 | } 166 | }); 167 | }); 168 | 169 | if (container) { return container.remove(uri, cb); } 170 | return _store.remove(uri, cb); 171 | }; 172 | 173 | Container.prototype.update = _store.update; 174 | }; 175 | -------------------------------------------------------------------------------- /lib/crystal.js: -------------------------------------------------------------------------------- 1 | var url = require('url'), 2 | rdf = require('./rdf'); 3 | 4 | /* 5 | * A simple method-based router, inspired by 6 | * [https://github.com/cloudhead/journey](Journey). 7 | */ 8 | exports.Crystal = function Crystal(options) { 9 | this.handlers = {}; 10 | this.options = options || {}; 11 | }; 12 | 13 | exports.Crystal.prototype = { 14 | // Allow defining method handlers uing `this`. 15 | map: function (routes) { 16 | routes.call(this); 17 | }, 18 | 19 | // Routing shortcuts 20 | get: function (cb) { return this.route('GET', cb); }, 21 | post: function (cb) { return this.route('POST', cb); }, 22 | put: function (cb) { return this.route('PUT', cb); }, 23 | delete: function (cb) { return this.route('DELETE', cb); }, 24 | head: function (cb) { return this.route('HEAD', cb); }, 25 | patch: function (cb) { return this.route('PATCH', cb); }, 26 | 27 | // Store a method handler 28 | route: function (method, cb) { 29 | this.handlers[method] = cb; 30 | }, 31 | 32 | // Handle a request 33 | handle: function (request, cb) { 34 | var handler = this.handlers[request.method]; 35 | var host = this.options.fakeNamespace 36 | ? this.options.fakeNamespace.replace(/\/$/, '') 37 | : request.protocol + '://' + request.headers.host; 38 | this.options.realNamespace = request.protocol + '://' + request.headers.host + '/'; 39 | request.headers.host = host; 40 | request.url = url.parse(request.url); 41 | var self = this; 42 | if (handler) { 43 | var respond = { 44 | send: function (status, headers, body) { 45 | /* 46 | * Mimic Node's ServerResponse while allowing us 47 | * to do stuff before it is actually sent. 48 | */ 49 | return self.respond({ 50 | status: status || 200, 51 | headers: headers || {}, 52 | body: body || '' 53 | }, cb); 54 | } 55 | }; 56 | try { 57 | handler.call(this, request, respond, host + request.url.pathname); 58 | } catch (err) { 59 | console.log('Error while running handler: ' + err.stack); 60 | return self.respond({ 61 | status: 500, 62 | body: 'Internal server error' 63 | }, cb); 64 | } 65 | } else { 66 | return self.respond({ 67 | status: 501, 68 | body: 'Not implemented' 69 | }, cb); 70 | } 71 | }, 72 | 73 | respond: function (result, cb) { 74 | // Add default headers 75 | result.headers = result.headers || {}; 76 | result.headers.Date = (new Date()).toUTCString(); 77 | result.headers.Server = 'Crystal/0.1'; 78 | 79 | // Replace faked host in Location header 80 | if (result.headers.Location) { 81 | result.headers.Location = result.headers.Location.replace( 82 | this.options.fakeNamespace, 83 | this.options.realNamespace); 84 | } 85 | 86 | if (typeof result.body == 'object') { 87 | rdf.hashDescription(result.body, function (err, hash) { 88 | result.headers.ETag = hash; 89 | return cb(result); 90 | }); 91 | } else { 92 | return cb(result); 93 | } 94 | } 95 | }; 96 | 97 | -------------------------------------------------------------------------------- /lib/memory.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | rdf = require('./rdf'); 3 | 4 | var Memory = exports.Memory = function Memory() { 5 | var _descriptions = {}; 6 | 7 | function _exists(uri) { 8 | return (_descriptions[uri] !== undefined); 9 | } 10 | 11 | function _create(uri) { 12 | if (!_exists(uri)) { _descriptions[uri] = {}; } 13 | } 14 | 15 | function _get(uri) { 16 | return _descriptions[uri]; 17 | } 18 | 19 | function _update(uri, description) { 20 | _descriptions[uri] = _descriptions[uri] || {}; 21 | rdf.mixinDescription(_descriptions[uri], description); 22 | } 23 | 24 | function _delete(uri) { 25 | delete _descriptions[uri]; 26 | } 27 | 28 | function _nodeID(key) { 29 | return ('_:' + key); 30 | } 31 | 32 | Memory.prototype.reset = function () { 33 | _descriptions = {}; 34 | }; 35 | 36 | //////////////////////////////////////////////////////////////////////////// 37 | // Resource-centric interface 38 | //////////////////////////////////////////////////////////////////////////// 39 | 40 | Memory.prototype.create = function (uri, description, cb) { 41 | if (_exists(uri)) { return cb(Error('Resource exists.')); } 42 | _create(uri); 43 | _update(uri, description); 44 | if (cb !== undefined) { cb(null); } 45 | }; 46 | 47 | Memory.prototype.exists = function (uri, cb) { 48 | cb(null, _exists(uri)); 49 | }; 50 | 51 | Memory.prototype.get = function (uri, cb) { 52 | if (!_exists(uri)) { return cb(Error('Not found')); } 53 | cb(null, _get(uri)); 54 | }; 55 | 56 | Memory.prototype.patch = function (uri, add, remove, cb) { 57 | cb(Error('Not implemented.')); 58 | }; 59 | 60 | Memory.prototype.remove = function (uri, cb) { 61 | if (!_exists(uri)) { return cb(Error('Not found')); } 62 | _delete(uri); 63 | if (cb !== undefined) { cb(null); } 64 | }; 65 | 66 | Memory.prototype.update = function (uri, description, cb) { 67 | _update(uri, description); 68 | if (cb !== undefined) { cb(null); } 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/mimetype.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Connect middleware interface 3 | */ 4 | 5 | var config = require(require('path').join(__dirname, '..', 'config')); 6 | 7 | exports = module.exports = function (options) { 8 | var mimeTypes = Object.keys(config.typeMapping); 9 | 10 | return function (request, response, next) { 11 | var accept = request.headers['accept']; 12 | var match = Mimeparse.bestMatch(mimeTypes, accept) || config.defaults.type; 13 | response.mimeType = match; 14 | response.type = config.typeMapping[match]; 15 | next(); 16 | } 17 | }; 18 | 19 | // mimeparse.js 20 | // 21 | // This module provides basic functions for handling mime-types. It can 22 | // handle matching mime-types against a list of media-ranges. See section 23 | // 14.1 of the HTTP specification [RFC 2616] for a complete explanation. 24 | // 25 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 26 | // 27 | // A port to JavaScript of Joe Gregorio's MIME-Type Parser: 28 | // 29 | // http://code.google.com/p/mimeparse/ 30 | // 31 | // Ported by J. Chris Anderson , targeting the Spidermonkey runtime. 32 | // 33 | // To run the tests, open mimeparse-js-test.html in a browser. 34 | // Ported from version 0.1.2 35 | // Comments are mostly excerpted from the original. 36 | 37 | var Mimeparse = (function() { 38 | // private helpers 39 | function strip(string) { 40 | return string.replace(/^\s+/, '').replace(/\s+$/, '') 41 | }; 42 | 43 | function parseRanges(ranges) { 44 | var parsedRanges = []; 45 | if (ranges != undefined) { 46 | var rangeParts = ranges.split(","); 47 | for (var i=0; i < rangeParts.length; i++) { 48 | parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i])) 49 | }; 50 | } 51 | return parsedRanges; 52 | }; 53 | 54 | var publicMethods = { 55 | // Carves up a mime-type and returns an Array of the 56 | // [type, subtype, params] where "params" is a Hash of all 57 | // the parameters for the media range. 58 | // 59 | // For example, the media range "application/xhtml;q=0.5" would 60 | // get parsed into: 61 | // 62 | // ["application", "xhtml", { "q" : "0.5" }] 63 | parseMimeType : function(mimeType) { 64 | var fullType, typeParts, params = {}, parts = mimeType.split(';'); 65 | for (var i=0; i < parts.length; i++) { 66 | var p = parts[i].split('='); 67 | if (p.length == 2) { 68 | params[strip(p[0])] = strip(p[1]); 69 | } 70 | }; 71 | fullType = parts[0].replace(/^\s+/, '').replace(/\s+$/, ''); 72 | if (fullType == '*') fullType = '*/*'; 73 | typeParts = fullType.split('/'); 74 | return [typeParts[0], typeParts[1], params]; 75 | }, 76 | 77 | // Carves up a media range and returns an Array of the 78 | // [type, subtype, params] where "params" is a Object with 79 | // all the parameters for the media range. 80 | // 81 | // For example, the media range "application/*;q=0.5" would 82 | // get parsed into: 83 | // 84 | // ["application", "*", { "q" : "0.5" }] 85 | // 86 | // In addition this function also guarantees that there 87 | // is a value for "q" in the params dictionary, filling it 88 | // in with a proper default if necessary. 89 | parseMediaRange : function(range) { 90 | var q, parsedType = this.parseMimeType(range); 91 | if (!parsedType[2]['q']) { 92 | parsedType[2]['q'] = '1'; 93 | } else { 94 | q = parseFloat(parsedType[2]['q']); 95 | if (isNaN(q)) { 96 | parsedType[2]['q'] = '1'; 97 | } else if (q > 1 || q < 0) { 98 | parsedType[2]['q'] = '1'; 99 | } 100 | } 101 | return parsedType; 102 | }, 103 | 104 | // Find the best match for a given mime-type against 105 | // a list of media_ranges that have already been 106 | // parsed by parseMediaRange(). Returns an array of 107 | // the fitness value and the value of the 'q' quality 108 | // parameter of the best match, or (-1, 0) if no match 109 | // was found. Just as for qualityParsed(), 'parsed_ranges' 110 | // must be a list of parsed media ranges. 111 | fitnessAndQualityParsed : function(mimeType, parsedRanges) { 112 | var bestFitness = -1, bestFitQ = 0, target = this.parseMediaRange(mimeType); 113 | var targetType = target[0], targetSubtype = target[1], targetParams = target[2]; 114 | 115 | if (parsedRanges.length < 1 && targetType == "*" && targetSubtype == "*") { 116 | bestFitness = 1; 117 | bestFitQ = 1.0; 118 | } 119 | 120 | for (var i=0; i < parsedRanges.length; i++) { 121 | var parsed = parsedRanges[i]; 122 | var type = parsed[0], subtype = parsed[1], params = parsed[2]; 123 | if ((type == targetType || type == "*" || targetType == "*") && 124 | (subtype == targetSubtype || subtype == "*" || targetSubtype == "*")) { 125 | var matchCount = 0; 126 | for (param in targetParams) { 127 | if (param != 'q' && params[param] && params[param] == targetParams[param]) { 128 | matchCount += 1; 129 | } 130 | } 131 | 132 | var fitness = (type == targetType) ? 100 : 0; 133 | fitness += (subtype == targetSubtype) ? 10 : 0; 134 | fitness += matchCount; 135 | 136 | if (fitness > bestFitness) { 137 | bestFitness = fitness; 138 | bestFitQ = params["q"]; 139 | } 140 | } 141 | }; 142 | return [bestFitness, parseFloat(bestFitQ)]; 143 | }, 144 | 145 | // Find the best match for a given mime-type against 146 | // a list of media_ranges that have already been 147 | // parsed by parseMediaRange(). Returns the 148 | // 'q' quality parameter of the best match, 0 if no 149 | // match was found. This function bahaves the same as quality() 150 | // except that 'parsedRanges' must be a list of 151 | // parsed media ranges. 152 | qualityParsed : function(mimeType, parsedRanges) { 153 | return this.fitnessAndQualityParsed(mimeType, parsedRanges)[1]; 154 | }, 155 | 156 | // Returns the quality 'q' of a mime-type when compared 157 | // against the media-ranges in ranges. For example: 158 | // 159 | // >>> Mimeparse.quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') 160 | // 0.7 161 | quality : function(mimeType, ranges) { 162 | return this.qualityParsed(mimeType, parseRanges(ranges)); 163 | }, 164 | 165 | // Takes a list of supported mime-types and finds the best 166 | // match for all the media-ranges listed in header. The value of 167 | // header must be a string that conforms to the format of the 168 | // HTTP Accept: header. The value of 'supported' is a list of 169 | // mime-types. 170 | // 171 | // >>> bestMatch(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') 172 | // 'text/xml' 173 | bestMatch : function(supported, header) { 174 | var parsedHeader = parseRanges(header); 175 | var weighted = []; 176 | for (var i=0; i < supported.length; i++) { 177 | weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), i, supported[i]]) 178 | }; 179 | weighted.sort(); 180 | return weighted[weighted.length-1][0][1] ? weighted[weighted.length-1][2] : ''; 181 | } 182 | } 183 | return publicMethods; 184 | })(); 185 | 186 | -------------------------------------------------------------------------------- /lib/rdf.js: -------------------------------------------------------------------------------- 1 | var raptor = require('raptor'), 2 | crypto = require('crypto'), 3 | jsonld = require('jsonld'), 4 | config = require(require('path').join(__dirname, '..', 'config')); 5 | 6 | exports.bodyParser = function (options) { 7 | return function (request, response, next) { 8 | var type = request.headers['content-type'], 9 | format = config.typeMapping[type]; 10 | 11 | if (format === 'jsonld') { 12 | var data = ''; 13 | request.on('data', function (chunk) { data += chunk; }); 14 | request.on('end', function () { 15 | toRDFJSON(JSON.parse(data), function (err, rdfJSON) { 16 | if (err) { 17 | next({ status: 400, message: err.message }); 18 | } else { 19 | request.body = rdfJSON; 20 | next(); 21 | } 22 | }); 23 | }); 24 | } else { 25 | // use `guess` parser if we could not detect format 26 | if (!format) { format = 'guess'; } 27 | 28 | parse(request, format, function (err, result) { 29 | if (err) { 30 | next({ status: 400, message: err.message }); 31 | } else { 32 | request.body = result; 33 | next(); 34 | } 35 | }); 36 | } 37 | }; 38 | }; 39 | 40 | var parse = exports.parse = function (stream, format, cb) { 41 | var parser = raptor.newParser(format), 42 | description = {}; 43 | 44 | if (!parser) { cb(new Error('No viable parser found.')); } 45 | 46 | parser.on('statement', function (statement) { mixinStatement(description, statement); }); 47 | parser.on('message', function (type, message, code) { cb(new Error(message)); }); 48 | parser.on('end', function () { cb(null, description); }); 49 | 50 | parser.parseStart('__PARSER__'); 51 | 52 | var flag = false; 53 | stream.on('data', function (chunk) { flag = true; parser.parseBuffer(chunk); }); 54 | stream.on('end', function () { 55 | if (!flag) { return cb(null, description); } 56 | parser.parseBuffer(); 57 | }); 58 | }; 59 | 60 | var serialize = exports.serialize = function (description, format, cb) { 61 | if (format === 'jsonld') { 62 | return toJSONLD(description, config.ldContext, function (err, result) { 63 | if (err) { return cb(err); } 64 | return cb(null, JSON.stringify(result)); 65 | }); 66 | } 67 | 68 | var body = '', 69 | serializer = raptor.newSerializer(format); 70 | 71 | if (!serializer) { return cb('Error'); } 72 | if (!Object.keys(description).length) { return cb(null, ''); } 73 | 74 | serializer.on('data', function (data) { body += data; }); 75 | serializer.on('end', function () { cb(null, body); }); 76 | 77 | serializer.serializeStart(); 78 | 79 | Object.keys(description).forEach(function (subject) { 80 | var type = 'uri', 81 | subjectName = subject; 82 | 83 | var parts = subject.split(':', 2); 84 | if ('_' === parts[0]) { 85 | type = 'bnode'; 86 | subjectName = parts[1]; 87 | } 88 | 89 | Object.keys(description[subject]).forEach(function (predicate) { 90 | description[subject][predicate].forEach(function (object) { 91 | var statement = { 92 | 'subject': { type: type, value: subjectName }, 93 | 'predicate': { type: 'uri', value: predicate }, 94 | 'object': { type: object.type, value: object.value } 95 | }; 96 | 97 | if (object.datatype) { 98 | statement.object.datatype = object.datatype; 99 | statement.object.type = 'typed-literal'; 100 | } else if (object.lang) { 101 | statement.object.lang = object.lang; 102 | } 103 | 104 | serializer.serializeStatement(statement); 105 | }); 106 | }); 107 | }); 108 | 109 | serializer.serializeEnd(); 110 | }; 111 | 112 | var mixinStatement = exports.mixinStatement = function (target, statement) { 113 | var subject = statement.subject.value; 114 | if (statement.subject.type !== 'uri') { subject = '_:' + subject; } 115 | 116 | var predicate = statement.predicate.value; 117 | 118 | target[subject] = target[subject] || {}; 119 | target[subject][predicate] = target[subject][predicate] || []; 120 | 121 | var object = { 122 | value: statement.object.value, 123 | type: statement.object.type.replace('typed-', '') 124 | }; 125 | if (statement.object.type === 'typed-literal') { object.datatype = statement.object.datatype; } 126 | else if (statement.object.lang) { object.lang = statement.object.land; } 127 | 128 | var exists = target[subject][predicate].some(function (existingObject) { 129 | return objectEquals(existingObject, object); 130 | }); 131 | 132 | if (!exists) { 133 | target[subject][predicate].push(object); 134 | } 135 | }; 136 | 137 | var mixinDescription = exports.mixinDescription = function (target, mixin) { 138 | Object.keys(mixin).forEach(function (subject) { 139 | target[subject] = target[subject] || {}; 140 | 141 | Object.keys(mixin[subject]).forEach(function (predicate) { 142 | target[subject][predicate] = target[subject][predicate] || []; 143 | 144 | mixin[subject][predicate].forEach(function (object) { 145 | var exists = target[subject][predicate].some(function (existingObject) { 146 | return objectEquals(existingObject, object); 147 | }); 148 | 149 | if (!exists) { 150 | target[subject][predicate].push(object); 151 | } 152 | }); 153 | }); 154 | }); 155 | }; 156 | 157 | var objectEquals = exports.objectEquals = function (lhs, rhs) { 158 | return ( 159 | lhs.value === rhs.value && 160 | lhs.datatype === rhs.datatype && 161 | lhs.lang === rhs.lang 162 | ); 163 | }; 164 | 165 | var hashDescription = exports.hashDescription = function (description, cb) { 166 | serialize(description, 'ntriples', function (err, triples) { 167 | // sort triples 168 | var sortedTriples = triples.split('\n').sort().join('\n'); 169 | var hasher = crypto.createHash('md5'); 170 | 171 | // hash 172 | hasher.update(sortedTriples); 173 | cb(null, hasher.digest().toString('hex')); 174 | }); 175 | }; 176 | 177 | var toJSONLD = exports.toJSONLD = function (descriptions, context, cb) { 178 | var ldResult = []; 179 | 180 | Object.keys(descriptions).forEach(function (subject) { 181 | var ldItem = { '@id': subject }; 182 | Object.keys(descriptions[subject]).forEach(function (predicate) { 183 | var ldValues = []; 184 | if (predicate === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') { 185 | descriptions[subject][predicate].forEach(function (type) { 186 | ldValues.push(type.value); 187 | }); 188 | ldItem['@type'] = ldValues; 189 | } else { 190 | descriptions[subject][predicate].forEach(function (value) { 191 | var ldValue = {}; 192 | switch (value.type) { 193 | case 'uri': 194 | case 'bnode': 195 | ldValue['@id'] = value.value; 196 | break; 197 | case 'literal': 198 | if (value.datatype) { ldValue['@type'] = value.datatype; } 199 | else if (value.lang) { ldValue['@language'] = value.lang; } 200 | ldValue['@value'] = value.value; 201 | break; 202 | } 203 | ldValues.push(ldValue); 204 | }); 205 | ldItem[predicate] = ldValues; 206 | } 207 | }); 208 | ldResult.push(ldItem); 209 | }); 210 | 211 | jsonld.compact(ldResult, context, {}, cb); 212 | }; 213 | 214 | var toRDFJSON = exports.toRDFJSON = function (ldDescription, cb) { 215 | var context = ldDescription['@context'], 216 | result = {}; 217 | 218 | jsonld.expand(ldDescription, {}, function (err, expanded) { 219 | if (err) { return cb(err); } 220 | 221 | expanded.forEach(function (frame) { 222 | var subject = frame['@id']; 223 | result[subject] = {}; 224 | 225 | if (frame['@type']) { 226 | frame['@type'].forEach(function (type) { 227 | result[subject]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type'] = [{ 228 | 'type': 'uri', 229 | 'value': type 230 | }]; 231 | }); 232 | } 233 | 234 | Object.keys(frame).forEach(function (key) { 235 | // ignore JSON-LD keywords 236 | if (key[0] === '@') { return; } 237 | 238 | result[subject][key] = frame[key].map(function (ldValue) { 239 | var object = {}; 240 | 241 | if (ldValue['@value']) { 242 | object.value = ldValue['@value']; 243 | object.type = 'literal'; 244 | if (ldValue['@type']) { 245 | object.datatype = ldValue['@type']; 246 | } else if (ldValue['@language']) { 247 | object.lang = ldValue['@language']; 248 | } 249 | } else if (ldValue['@id']) { 250 | object.type = ldValue['@id'].search(/^_:/) === 0 ? 'bnode' : 'uri'; 251 | object.value = ldValue['@id']; 252 | } 253 | 254 | return object; 255 | }); 256 | }); 257 | }); 258 | 259 | cb(null, result); 260 | }); 261 | }; 262 | 263 | var invert = function (object) { 264 | var inverted = {}; 265 | Object.keys(object).forEach(function (key) { 266 | inverted[object[key]] = key; 267 | }); 268 | return inverted; 269 | }; 270 | -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | var Crystal = require('./crystal').Crystal; 2 | 3 | exports.createRouter = function (options, store) { 4 | var router = new Crystal(options); 5 | router.map(function () { 6 | /* 7 | * POST to URI returns resource or 404. 8 | */ 9 | this.get(function (request, response, uri) { 10 | store.get (uri, function (err, description) { 11 | if (err) { 12 | store.getContainer(uri, function (err, cont) { 13 | if (err) { 14 | response.send(404); 15 | } else { 16 | cont.list(function (err, list) { 17 | if (err) { 18 | response.send(500); 19 | } else { 20 | response.send(200, {}, list); 21 | } 22 | }); 23 | } 24 | }); 25 | } else { 26 | response.send(200, {}, description); 27 | } 28 | }); 29 | }); 30 | 31 | /* 32 | * HEAD to uri returns same headers as GET w/o content. 33 | */ 34 | this.head(function (request, response, uri) { 35 | store.exists(uri, function (err, exists) { 36 | if (exists) { 37 | response.send(200, { 'Allow': 'GET, HEAD, POST, PUT, DELETE' }); 38 | } else { 39 | store.isContainer(uri, function (err, isContainer) { 40 | if (isContainer) { 41 | response.send(200, { 'Allow': 'GET, HEAD, POST, DELETE' }); 42 | } else { 43 | response.send(404, { 'Allow': 'HEAD, PUT, POST' } ); 44 | } 45 | }); 46 | } 47 | }); 48 | }); 49 | 50 | /* 51 | * DELETE to uri deletes resource. 52 | */ 53 | this.delete(function (request, response, uri) { 54 | store.remove(uri, function (err) { 55 | if (err) { 56 | response.send(404); 57 | } else { 58 | response.send(200); 59 | } 60 | }); 61 | }); 62 | 63 | /* 64 | * PUT to uri creates resource, replacing an old one if necessary. 65 | */ 66 | this.put(function (request, response, uri) { 67 | if (!request.body.hasOwnProperty(uri)) { 68 | return response.send(400); 69 | } 70 | store.exists(uri, function (err, exists) { 71 | if (!exists) { 72 | store.isContainer(uri, function (isContainer) { 73 | if (!isContainer) { 74 | store.create(uri, request.body, function (err) { 75 | if (err) { return response.send(400); } 76 | response.send(201, { 'Location': uri }); 77 | }); 78 | } else { 79 | response.send(405, { 'Allow': 'GET, POST, DELETE' }); 80 | } 81 | }); 82 | } else { 83 | store.remove(uri, function (err) { 84 | store.create(uri, request.body, function (err) { 85 | if (err) { return response.send(500); } 86 | response.send(204); 87 | }); 88 | }); 89 | } 90 | }); 91 | }); 92 | 93 | /* 94 | * POST to uri creates resource, updating an old one if it exists. 95 | */ 96 | this.post(function (request, response, uri) { 97 | store.isContainer(uri, function (err, isContainer) { 98 | if (isContainer) { 99 | // Get the first subject as the resource URI 100 | var resourceURI = Object.keys(request.body).shift(); 101 | store.exists(uri, resourceURI, function (err, exists) { 102 | if (!exists) { 103 | store.create(uri, resourceURI, request.body, function (err) { 104 | if (err) { return response.send(500); } 105 | return response.send(201, { 'Location': resourceURI }); 106 | }); 107 | } else { 108 | store.update(resourceURI, request.body, function (err) { 109 | if (err) { return response.send(500); } 110 | return response.send(204); 111 | }); 112 | } 113 | }); 114 | } else { 115 | if (!request.body.hasOwnProperty(uri)) { 116 | return response.send(400); 117 | } 118 | store.exists(uri, function (err, exists) { 119 | if (!exists) { 120 | store.create(uri, request.body, function (err) { 121 | if (err) { return response.send(500); } 122 | response.send(201, { 'Location': uri }); 123 | }); 124 | } else { 125 | store.update(uri, request.body, function (err) { 126 | if (err) { return response.send(500); } 127 | response.send(204); 128 | }); 129 | } 130 | }); 131 | } 132 | }); 133 | }); 134 | }); 135 | 136 | return router; 137 | }; 138 | 139 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | util = require('util'), 3 | connect = require('connect'), 4 | mime = require('./mimetype'), 5 | rdf = require('./rdf'), 6 | router = require('./router'), 7 | Memory = require('./memory').Memory, 8 | Container = require('./container').Container; 9 | raptor = require('raptor'), 10 | path = require('path'), 11 | fs = require('fs'), 12 | utils = require('./utils'), 13 | config = require(require('path').join(__dirname, '..', 'config')); 14 | 15 | exports.createServer = function (options, store) { 16 | var listener = connect() 17 | .use(mime()) 18 | .use(rdf.bodyParser()) 19 | .use(function (request, response) { 20 | request.protocol = 'http'; 21 | router.createRouter(options, store).handle(request, function (result) { 22 | if (result.status == 200 && typeof result.body == 'object') { 23 | rdf.serialize(result.body, response.type, function (err, body) { 24 | if (err) { 25 | response.writeHead(500, result.headers); 26 | response.end(); 27 | } else { 28 | var headers = utils.merge(result.headers, { 'Content-Length': body.length }); 29 | 30 | for (var key in config.typeMapping) { 31 | if (config.typeMapping.hasOwnProperty(key)) { 32 | if (response.type == config.typeMapping[key]) { 33 | headers['Content-Type'] = key; 34 | break; 35 | } 36 | } 37 | } 38 | 39 | response.writeHead(result.status, headers); 40 | response.end(body); 41 | } 42 | }); 43 | } else { 44 | response.writeHead(result.status, result.headers); 45 | response.end(result.body); 46 | } 47 | }); 48 | }); 49 | var server = http.createServer(listener).listen(options.port); 50 | }; 51 | 52 | exports.start = function (options, cb) { 53 | var filePath = path.normalize(options.load), 54 | parser = raptor.newParser('guess'); 55 | 56 | if (!fs.existsSync(filePath)) { 57 | return cb({ message: 'data file not readable' }); 58 | } 59 | 60 | try { 61 | var descriptions = {}, 62 | blankNodeDescriptions = {}; 63 | parser.on('statement', function (statement) { 64 | if (statement.subject.type == 'uri') { 65 | rdf.mixinStatement(descriptions, statement); 66 | } else { 67 | rdf.mixinStatement(blankNodeDescriptions, statement); 68 | } 69 | }); 70 | parser.on('end', function () { 71 | var store = new Memory(), 72 | container = new Container(store); 73 | container.createContainer('http://webid.example.com/container1/'); 74 | 75 | Object.keys(descriptions).forEach(function (uri) { 76 | var descr = {}; 77 | descr[uri] = descriptions[uri]; 78 | store.create(uri, createDescription(descr, blankNodeDescriptions)); 79 | }); 80 | 81 | cb(null, exports.createServer(options, container)); 82 | }); 83 | parser.parseFile(filePath); 84 | } catch (error) { 85 | cb(error); 86 | } 87 | }; 88 | 89 | var createDescription = function (baseDescription, blankNodeDescriptions) { 90 | var result = baseDescription; 91 | Object.keys(baseDescription).forEach(function (subject) { 92 | Object.keys(baseDescription[subject]).forEach(function (predicate) { 93 | baseDescription[subject][predicate].forEach(function (object) { 94 | // If we hit a blank node, pull in its description (recursively) 95 | if (object.type === 'bnode') { 96 | var nodeName = '_:' + object.value, 97 | bnode = {}; 98 | bnode[nodeName] = blankNodeDescriptions[nodeName]; 99 | rdf.mixinDescription(result, createDescription(bnode, blankNodeDescriptions)); 100 | } 101 | }); 102 | }); 103 | }); 104 | return result; 105 | }; 106 | 107 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var merge = exports.merge = function (target/* variadic arguments */) { 2 | var args = Array.prototype.slice.call(arguments, 1); 3 | args.forEach(function (a) { 4 | var keys = Object.keys(a); 5 | for (var i = 0; i < keys.length; i++) { 6 | target[keys[i]] = a[keys[i]]; 7 | } 8 | }); 9 | return target; 10 | } 11 | -------------------------------------------------------------------------------- /test/alice.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix foaf: . 3 | @prefix dc: . 4 | 5 | a foaf:Person; 6 | foaf:name "Alice Example"; 7 | foaf:firstName "Alice"; 8 | foaf:lastName "Example"; 9 | foaf:birthday "04-01"; 10 | foaf:gender "female" . 11 | -------------------------------------------------------------------------------- /test/bob.ttl: -------------------------------------------------------------------------------- 1 | @base . 2 | @prefix foaf: . 3 | @prefix dc: . 4 | 5 | a foaf:Person; 6 | foaf:name "Bob Example"; 7 | foaf:firstName "Bob"; 8 | foaf:lastName "Example"; 9 | foaf:birthday "11-11"; 10 | foaf:gender "male"; 11 | foaf:img _:node1 . 12 | 13 | _:node1 a foaf:Image; 14 | ; 15 | _:node2 . 16 | 17 | _:node2 a foaf:Document; 18 | dc:creator . 19 | -------------------------------------------------------------------------------- /test/empty.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | 3 | -------------------------------------------------------------------------------- /test/phil.rdf: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ]> 13 | 21 | 22 | 23 | 24 | AKSW demo knowledge base about philosophers. It demonstrates various OntoWiki features such as arbitrary object hierarchies, geoinformation reasoning, semantic plug-ins adn blank node usage. 25 | $Id$ 26 | 27 | 28 | 29 | 30 | Philisopher 31 | Philisoph 32 | 33 | 34 | 35 | 36 | 37 | 38 | Greek Philosopher 39 | Griechischer Philisoph 40 | 41 | 42 | 43 | 44 | 45 | 46 | Classical Greek Philosopher 47 | Klassischer Griechischer Philisoph 48 | 49 | 50 | 51 | 52 | 53 | 54 | Muslim Philosopher 55 | Muslimischer Philisoph 56 | 57 | 58 | 59 | 60 | 61 | 62 | Mediaeval Philosopher 63 | Mittelalterlicher Philisoph 64 | 65 | 66 | 67 | 68 | 69 | 70 | Location 71 | Ort 72 | 73 | 74 | 75 | 76 | 77 | 78 | Ton 79 | Stadt 80 | 81 | 82 | 83 | 84 | 85 | 86 | Street 87 | Straße 88 | 89 | 90 | 91 | 92 | 93 | 94 | Street number 95 | Hausnummer 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | comment 104 | Kommentar 105 | 106 | 107 | 108 | label 109 | Bezeichner 110 | 111 | 112 | 113 | 114 | was influenced by 115 | wurde beeinflusst durch 116 | 117 | 118 | 119 | 120 | 121 | was taught by 122 | wurde gelehrt von 123 | 124 | 125 | 126 | 127 | was born in 128 | wurde geboren in 129 | 130 | 131 | 132 | 133 | 134 | 135 | depiction 136 | Abbildung 137 | 138 | 139 | 140 | 141 | year of birth 142 | Geburtsjahr 143 | 144 | 145 | 146 | 147 | 148 | year of death 149 | Todesjahr 150 | 151 | 152 | 153 | 154 | 155 | latitude 156 | Geografische Breite 157 | 158 | 159 | 160 | 161 | 162 | latitude 163 | Geografische Länge 164 | 165 | 166 | 167 | 168 | 169 | 170 | Socrates 171 | Sokrates 172 | 173 | -470 174 | -399 175 | 176 | 177 | 178 | 179 | Plato 180 | Platon 181 | 182 | -423 183 | -347 184 | 185 | 186 | 187 | 188 | 189 | Aristotle 190 | Aristoteles 191 | 192 | 193 | -384 194 | -322 195 | 196 | 197 | 198 | 199 | 200 | 201 | Averroes 202 | Averroes 203 | 204 | 205 | 206 | 207 | 1126 208 | 1198 209 | 210 | 211 | 212 | 213 | Thomas Aquinas 214 | Thomas von Aquin 215 | 216 | 217 | 1225 218 | 1275 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 40.530278 228 | 23.751389 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 37.883333 238 | -4.766667 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 41.55 247 | 13.666667 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /test/test-container.js: -------------------------------------------------------------------------------- 1 | const kRDFType = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; 2 | const kLDPContainer = 'http://www.w3.org/ns/ldp#Container'; 3 | const kMembershipPredicate = 'http://www.w3.org/2000/01/rdf-schema#member'; 4 | 5 | var nodeunit = require('nodeunit'), 6 | Memory = require('../lib/memory').Memory, 7 | Container = require('../lib/container').Container; 8 | 9 | exports.testCreateContainer = nodeunit.testCase({ 10 | 'create container': function (test) { 11 | test.expect(5); 12 | 13 | var store = new Memory(), 14 | container = new Container(store); 15 | 16 | var containerURI = 'http://example.com/container1/'; 17 | 18 | container.isContainer(containerURI, function (err, exists) { 19 | test.equals(null, err); 20 | test.equals(false, exists); 21 | 22 | container.createContainer(containerURI, function (err) { 23 | test.equals(null, err); 24 | 25 | container.isContainer(containerURI, function (err, exists) { 26 | test.equals(null, err); 27 | test.ok(exists); 28 | test.done(); 29 | }); 30 | }); 31 | }); 32 | }, 33 | 34 | 'create container that exists': function (test) { 35 | test.expect(6); 36 | 37 | var store = new Memory(), 38 | container = new Container(store); 39 | 40 | var containerURI = 'http://example.com/container2/'; 41 | 42 | container.isContainer(containerURI, function (err, exists) { 43 | test.equals(null, err); 44 | test.equals(false, exists); 45 | 46 | container.createContainer(containerURI, function (err) { 47 | test.equals(null, err); 48 | 49 | container.isContainer(containerURI, function (err, exists) { 50 | test.equals(null, err); 51 | test.ok(exists); 52 | 53 | container.createContainer(containerURI, function (err) { 54 | test.notEqual(null, err); 55 | test.done(); 56 | }); 57 | }); 58 | }); 59 | }); 60 | } 61 | }); 62 | 63 | exports.testGetContainer = nodeunit.testCase({ 64 | 'get existing container': function (test) { 65 | test.expect(3); 66 | 67 | var store = new Memory(), 68 | container = new Container(store); 69 | 70 | var containerURI = 'http://example.com/container3/'; 71 | 72 | container.createContainer(containerURI, function (err) { 73 | test.equal(null, err); 74 | 75 | container.getContainer(containerURI, function (err, result) { 76 | test.equal(null, err); 77 | test.notEqual(null, result); 78 | test.done(); 79 | }); 80 | }); 81 | }, 82 | 83 | 'get non-existing container': function (test) { 84 | test.expect(4); 85 | 86 | var store = new Memory(), 87 | container = new Container(store); 88 | 89 | var containerURI = 'http://example.com/container4/'; 90 | 91 | container.getContainer(containerURI, function (err) { 92 | test.notEqual(null, err); 93 | 94 | container.createContainer(containerURI, function (err) { 95 | test.equal(null, err); 96 | 97 | container.getContainer(containerURI, function (err, result) { 98 | test.equal(null, err); 99 | test.notEqual(null, result); 100 | test.done(); 101 | }); 102 | }); 103 | }); 104 | } 105 | }); 106 | 107 | exports.testIsContainer = nodeunit.testCase({ 108 | 'test existing container': function (test) { 109 | test.expect(3); 110 | 111 | var store = new Memory(), 112 | container = new Container(store); 113 | 114 | var containerURI = 'http://example.com/container5/'; 115 | 116 | container.createContainer(containerURI, function (err) { 117 | test.equal(null, err); 118 | 119 | container.isContainer(containerURI, function (err, isContainer) { 120 | test.equal(null, err); 121 | test.ok(isContainer); 122 | test.done(); 123 | }); 124 | }); 125 | }, 126 | 127 | 'test non-existing container': function (test) { 128 | test.expect(4); 129 | 130 | var store = new Memory(), 131 | container = new Container(store); 132 | 133 | var containerURI = 'http://example.com/container6/'; 134 | 135 | container.isContainer(containerURI, function (err, isContainer) { 136 | test.equal(false, isContainer); 137 | 138 | container.createContainer(containerURI, function (err) { 139 | test.equal(null, err); 140 | 141 | container.isContainer(containerURI, function (err, isContainer) { 142 | test.equal(null, err); 143 | test.ok(isContainer); 144 | test.done(); 145 | }); 146 | }); 147 | }); 148 | } 149 | }); 150 | 151 | exports.testRemoveContainer = nodeunit.testCase({ 152 | 'remove existing container': function (test) { 153 | test.expect(2); 154 | 155 | var store = new Memory(), 156 | container = new Container(store); 157 | 158 | var containerURI = 'http://example.com/container7/'; 159 | 160 | container.createContainer(containerURI, function (err) { 161 | test.equal(null, err); 162 | 163 | container.removeContainer(containerURI, function (err) { 164 | test.equal(null, err); 165 | test.done(); 166 | }); 167 | }); 168 | }, 169 | 170 | 'remove non-existing container': function (test) { 171 | test.expect(3); 172 | 173 | var store = new Memory(), 174 | container = new Container(store); 175 | 176 | var containerURI = 'http://example.com/container8/'; 177 | 178 | container.removeContainer(containerURI, function (err, isContainer) { 179 | test.notEqual(null, err); 180 | 181 | container.createContainer(containerURI, function (err) { 182 | test.equal(null, err); 183 | 184 | container.removeContainer(containerURI, function (err) { 185 | test.equal(null, err); 186 | test.done(); 187 | }); 188 | }); 189 | }); 190 | } 191 | }); 192 | 193 | exports.testCreateExists = nodeunit.testCase({ 194 | setUp: function (cb) { 195 | this.container = new Container(new Memory()); 196 | cb(); 197 | }, 198 | 199 | 'create resource in existing container': function (test) { 200 | test.expect(4); 201 | 202 | var containerURI = 'http://example.com/container9/', 203 | resourceURI = 'http://example.com/container9/resource1'; 204 | 205 | var self = this; 206 | 207 | self.container.createContainer(containerURI, function (err) { 208 | test.equal(null, err); 209 | 210 | self.container.create(containerURI, resourceURI, {}, function (err) { 211 | test.equal(null, err); 212 | 213 | self.container.exists(containerURI, resourceURI, function (err, resourceExists) { 214 | test.equal(null, err); 215 | test.ok(resourceExists); 216 | 217 | test.done(); 218 | }); 219 | }); 220 | }); 221 | }, 222 | 223 | 'create resource in non-existing container': function (test) { 224 | test.expect(2); 225 | 226 | var containerURI = 'http://example.com/container10/', 227 | resourceURI = 'http://example.com/container10/resource1'; 228 | 229 | var self = this; 230 | 231 | self.container.create(containerURI, resourceURI, {}, function (err) { 232 | test.notEqual(null, err); 233 | 234 | self.container.exists(null, resourceURI, function (err, resourceExists) { 235 | test.equal(false, resourceExists); 236 | 237 | test.done(); 238 | }); 239 | }); 240 | } 241 | }); 242 | 243 | exports.testRemove = nodeunit.testCase({ 244 | setUp: function (cb) { 245 | this.container = new Container(new Memory()); 246 | cb(); 247 | }, 248 | 249 | 'remove deletes resource from container': function (test) { 250 | test.expect(5); 251 | 252 | var containerURI = 'http://example.com/container11/', 253 | resourceURI = 'http://example.com/container11/resource1'; 254 | 255 | var self = this; 256 | 257 | self.container.createContainer(containerURI, function (err) { 258 | test.equal(null, err); 259 | 260 | self.container.create(containerURI, resourceURI, {}, function (err) { 261 | test.equal(null, err); 262 | 263 | self.container.remove(resourceURI, function (err) { 264 | test.equal(null, err); 265 | 266 | self.container.exists(containerURI, resourceURI, function (err, resourceExists) { 267 | test.equal(null, err); 268 | test.equal(false, resourceExists); 269 | 270 | test.done(); 271 | }); 272 | }); 273 | }); 274 | }); 275 | } 276 | }); 277 | 278 | exports.testContainerInterface = nodeunit.testCase({ 279 | setUp: function (cb) { 280 | this.container = new Container(new Memory()); 281 | this.containerURI = 'http://example.com/container12/'; 282 | var self = this; 283 | this.container.createContainer(this.containerURI, function (err, containerInterface) { 284 | self.containerInterface = containerInterface; 285 | cb(); 286 | }); 287 | }, 288 | 289 | 'list container resources': function (test) { 290 | test.expect(5); 291 | var self = this; 292 | var resourceURI = 'http://example.com/container12/resource1'; 293 | 294 | self.containerInterface.create(resourceURI, {}, function (err) { 295 | test.equal(null, err); 296 | 297 | self.containerInterface.exists(resourceURI, function (err, resourceExists) { 298 | test.equal(null, err); 299 | test.ok(resourceExists); 300 | 301 | self.containerInterface.list(function (err, contents) { 302 | test.equal(null, err); 303 | test.ok(contents[self.containerURI][kMembershipPredicate].map(function (object) { 304 | return object.value; 305 | }).indexOf(resourceURI) > -1); 306 | test.done(); 307 | }); 308 | }); 309 | }); 310 | } 311 | }); 312 | -------------------------------------------------------------------------------- /test/test-memory.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'), 2 | Memory = require('../lib/memory').Memory; 3 | 4 | exports.testCreateGetExists = nodeunit.testCase({ 5 | 'create empty resource': function (test) { 6 | test.expect(7); 7 | 8 | var store = new Memory(), 9 | uri = 'http://example.com/r1'; 10 | 11 | store.exists(uri, function (err, exists) { 12 | test.equal(null, err); 13 | test.equal(false, exists); 14 | 15 | store.create(uri, {}, function (err) { 16 | test.equal(null, err); 17 | 18 | store.exists(uri, function (err, exists) { 19 | test.equal(null, err); 20 | test.equal(true, exists); 21 | 22 | store.get(uri, function (err, result) { 23 | test.equal(null, err); 24 | test.deepEqual({}, result); 25 | test.done(); 26 | }); 27 | }); 28 | }); 29 | }); 30 | }, 31 | 32 | 'create simple resource': function (test) { 33 | test.expect(7); 34 | 35 | var expected = { 36 | 'http://example.com/r1': { 37 | 'http://example.com/p1': [ 38 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 39 | { 'type': 'bnode', 'value': '_:123' }, 40 | { 'type': 'literal', 'value': 'ttt' } 41 | ] 42 | } 43 | }; 44 | 45 | var store = new Memory(), 46 | uri = 'http://example.com/r1'; 47 | 48 | store.exists(uri, function (err, exists) { 49 | test.equal(null, err); 50 | test.equal(false, exists); 51 | 52 | store.create(uri, expected, function (err) { 53 | test.equal(null, err); 54 | 55 | store.exists(uri, function (err, exists) { 56 | test.equal(null, err); 57 | test.equal(true, exists); 58 | 59 | store.get(uri, function (err, result) { 60 | test.equal(null, err); 61 | test.deepEqual(expected, result); 62 | test.done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | } 68 | }); 69 | 70 | exports.testUpdate = nodeunit.testCase({ 71 | 'create empty and update empty': function (test) { 72 | test.expect(8); 73 | var store = new Memory(), 74 | uri = 'http://example.com/r1'; 75 | 76 | store.exists(uri, function (err, exists) { 77 | test.equal(null, err); 78 | test.equal(false, exists); 79 | 80 | store.create(uri, {}, function (err) { 81 | test.equal(null, err); 82 | 83 | store.get(uri, function (err, result) { 84 | test.equal(null, err); 85 | test.deepEqual({}, result); 86 | 87 | store.update(uri, {}, function (err) { 88 | test.equal(null, err); 89 | 90 | store.get(uri, function (err, result) { 91 | test.equal(null, err); 92 | test.deepEqual({}, result); 93 | test.done(); 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }, 100 | 101 | 'create empty and update simple': function (test) { 102 | test.expect(8); 103 | var store = new Memory(), 104 | uri = 'http://example.com/r1'; 105 | 106 | var init = {}; 107 | var update = { 108 | 'http://example.com/r1': { 109 | 'http://example.com/p1': [ 110 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 111 | { 'type': 'bnode', 'value': '_:123' }, 112 | { 'type': 'literal', 'value': 'ttt' } 113 | ] 114 | } 115 | }; 116 | 117 | store.exists(uri, function (err, exists) { 118 | test.equal(null, err); 119 | test.equal(false, exists); 120 | 121 | store.create(uri, init, function (err) { 122 | test.equal(null, err); 123 | 124 | store.get(uri, function (err, result) { 125 | test.equal(null, err); 126 | test.deepEqual(init, result); 127 | 128 | store.update(uri, update, function (err) { 129 | test.equal(null, err); 130 | 131 | store.get(uri, function (err, result) { 132 | test.equal(null, err); 133 | test.deepEqual(update, result); 134 | test.done(); 135 | }); 136 | }); 137 | }); 138 | }); 139 | }); 140 | }, 141 | 142 | 'create simple and update empty': function (test) { 143 | test.expect(8); 144 | var store = new Memory(), 145 | uri = 'http://example.com/r1'; 146 | 147 | var init = { 148 | 'http://example.com/r1': { 149 | 'http://example.com/p1': [ 150 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 151 | { 'type': 'bnode', 'value': '_:123' }, 152 | { 'type': 'literal', 'value': 'ttt' } 153 | ] 154 | } 155 | }; 156 | var update = {}; 157 | 158 | store.exists(uri, function (err, exists) { 159 | test.equal(null, err); 160 | test.equal(false, exists); 161 | 162 | store.create(uri, init, function (err) { 163 | test.equal(null, err); 164 | 165 | store.get(uri, function (err, result) { 166 | test.equal(null, err); 167 | test.deepEqual(init, result); 168 | 169 | store.update(uri, update, function (err) { 170 | test.equal(null, err); 171 | 172 | store.get(uri, function (err, result) { 173 | test.equal(null, err); 174 | test.deepEqual(init, result); 175 | test.done(); 176 | }); 177 | }); 178 | }); 179 | }); 180 | }); 181 | }, 182 | 183 | 'create simple and update simple': function (test) { 184 | test.expect(8); 185 | var store = new Memory(), 186 | uri = 'http://example.com/r1'; 187 | 188 | var init = { 189 | 'http://example.com/r1': { 190 | 'http://example.com/p1': [ 191 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 192 | { 'type': 'bnode', 'value': '_:123' }, 193 | { 'type': 'literal', 'value': 'ttt' } 194 | ] 195 | } 196 | }; 197 | var update = { 198 | 'http://example.com/r1': { 199 | 'http://example.com/p2': [ 200 | { 'type': 'uri', 'value': 'http://example.com/o2' }, 201 | { 'type': 'literal', 'value': 'vvv' }, 202 | { 'type': 'bnode', 'value': '_:abc' } 203 | ] 204 | } 205 | 206 | }; 207 | 208 | var expected = { 209 | 'http://example.com/r1': { 210 | 'http://example.com/p1': [ 211 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 212 | { 'type': 'bnode', 'value': '_:123' }, 213 | { 'type': 'literal', 'value': 'ttt' } 214 | ], 215 | 'http://example.com/p2': [ 216 | { 'type': 'uri', 'value': 'http://example.com/o2' }, 217 | { 'type': 'literal', 'value': 'vvv' }, 218 | { 'type': 'bnode', 'value': '_:abc' } 219 | ] 220 | } 221 | }; 222 | 223 | store.exists(uri, function (err, exists) { 224 | test.equal(null, err); 225 | test.equal(false, exists); 226 | 227 | store.create(uri, init, function (err) { 228 | test.equal(null, err); 229 | 230 | store.get(uri, function (err, result) { 231 | test.equal(null, err); 232 | test.deepEqual(init, result); 233 | 234 | store.update(uri, update, function (err) { 235 | test.equal(null, err); 236 | 237 | store.get(uri, function (err, result) { 238 | test.equal(null, err); 239 | test.deepEqual(expected, result); 240 | test.done(); 241 | }); 242 | }); 243 | }); 244 | }); 245 | }); 246 | } 247 | }); 248 | 249 | exports.testRemove = nodeunit.testCase({ 250 | 'create and remove empty': function (test) { 251 | test.expect(10); 252 | 253 | var store = new Memory(), 254 | uri = 'http://example.com/r1'; 255 | 256 | store.exists(uri, function (err, exists) { 257 | test.equal(null, err); 258 | test.equal(false, exists); 259 | 260 | store.create(uri, {}, function (err) { 261 | test.equal(null, err); 262 | 263 | store.exists(uri, function (err, exists) { 264 | test.equal(null, err); 265 | test.equal(true, exists); 266 | 267 | store.get(uri, function (err, result) { 268 | test.equal(null, err); 269 | test.deepEqual({}, result); 270 | 271 | store.remove(uri, function (err) { 272 | test.equal(null, err); 273 | 274 | store.exists(uri, function (err, exists) { 275 | test.equal(null, err); 276 | test.equal(false, exists); 277 | test.done(); 278 | }); 279 | }); 280 | }); 281 | }); 282 | }); 283 | }); 284 | }, 285 | 286 | 'create and remove simple': function (test) { 287 | test.expect(10); 288 | 289 | var store = new Memory(), 290 | uri = 'http://example.com/r1'; 291 | 292 | var expected = { 293 | 'http://example.com/r1': { 294 | 'http://example.com/p1': [ 295 | { 'type': 'uri', 'value': 'http://example.com/o1' }, 296 | { 'type': 'bnode', 'value': '_:123' }, 297 | { 'type': 'literal', 'value': 'ttt' } 298 | ] 299 | } 300 | }; 301 | 302 | store.exists(uri, function (err, exists) { 303 | test.equal(null, err); 304 | test.equal(false, exists); 305 | 306 | store.create(uri, expected, function (err) { 307 | test.equal(null, err); 308 | 309 | store.exists(uri, function (err, exists) { 310 | test.equal(null, err); 311 | test.equal(true, exists); 312 | 313 | store.get(uri, function (err, result) { 314 | test.equal(null, err); 315 | test.deepEqual(expected, result); 316 | 317 | store.remove(uri, function (err) { 318 | test.equal(null, err); 319 | 320 | store.exists(uri, function (err, exists) { 321 | test.equal(null, err); 322 | test.equal(false, exists); 323 | test.done(); 324 | }); 325 | }); 326 | }); 327 | }); 328 | }); 329 | }); 330 | } 331 | }); 332 | -------------------------------------------------------------------------------- /test/test-rdf.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('nodeunit'), 2 | rdf = require('../lib/rdf'); 3 | 4 | exports.testMixinStatement = nodeunit.testCase({ 5 | 'merge into emtpy description': function (test) { 6 | test.expect(1); 7 | 8 | var base = {}; 9 | var statement = { 10 | subject: { 'type': 'uri', 'value': 'http://example.com/r1' }, 11 | predicate: { 'type': 'uri', 'value': 'http://example.com/p1' }, 12 | object: { 'type': 'literal', 'value': 'foo bar' } 13 | }; 14 | var expected = { 15 | 'http://example.com/r1': { 16 | 'http://example.com/p1': [ 17 | { 'type': 'literal', 'value': 'foo bar' } 18 | ] 19 | } 20 | }; 21 | 22 | rdf.mixinStatement(base, statement); 23 | test.deepEqual(base, expected); 24 | 25 | test.done(); 26 | }, 27 | 'merge into description same statement': function (test) { 28 | test.expect(1); 29 | 30 | var base = { 31 | 'http://example.com/r1': { 32 | 'http://example.com/p1': [ 33 | { 'type': 'literal', 'value': 'foo bar' } 34 | ] 35 | } 36 | }; 37 | var statement = { 38 | subject: { 'type': 'uri', 'value': 'http://example.com/r1' }, 39 | predicate: { 'type': 'uri', 'value': 'http://example.com/p1' }, 40 | object: { 'type': 'literal', 'value': 'foo bar' } 41 | }; 42 | var expected = { 43 | 'http://example.com/r1': { 44 | 'http://example.com/p1': [ 45 | { 'type': 'literal', 'value': 'foo bar' } 46 | ] 47 | } 48 | }; 49 | 50 | rdf.mixinStatement(base, statement); 51 | test.deepEqual(base, expected); 52 | 53 | test.done(); 54 | }, 55 | 'merge into description different object': function (test) { 56 | test.expect(1); 57 | 58 | var base = { 59 | 'http://example.com/r1': { 60 | 'http://example.com/p1': [ 61 | { 'type': 'literal', 'value': 'foo bar' } 62 | ] 63 | } 64 | }; 65 | var statement = { 66 | subject: { 'type': 'uri', 'value': 'http://example.com/r1' }, 67 | predicate: { 'type': 'uri', 'value': 'http://example.com/p1' }, 68 | object: { 'type': 'typed-literal', 69 | 'value': 'foo bar baz', 70 | 'datatype': 'http://www.w3.org/2001/XMLSchema#string' } 71 | }; 72 | var expected = { 73 | 'http://example.com/r1': { 74 | 'http://example.com/p1': [ 75 | { 'type': 'literal', 'value': 'foo bar' }, 76 | { 'type': 'literal', 77 | 'value': 'foo bar baz', 78 | 'datatype': 'http://www.w3.org/2001/XMLSchema#string' } 79 | ] 80 | } 81 | }; 82 | 83 | rdf.mixinStatement(base, statement); 84 | test.deepEqual(base, expected); 85 | 86 | test.done(); 87 | } 88 | }); 89 | 90 | exports.testMixinDescription = nodeunit.testCase({ 91 | 'merge into empty description': function (test) { 92 | test.expect(1); 93 | 94 | var base = {}; 95 | var description = { 96 | 'http://example.com/s1': { 97 | 'http://example.com/p1': [ 98 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 99 | { 'type': 'literal', 'value': 'foo'} 100 | ] 101 | } 102 | }; 103 | 104 | rdf.mixinDescription(base, description); 105 | test.deepEqual(base, description); 106 | 107 | test.done(); 108 | }, 109 | 110 | 'merge descritions with same subject': function (test) { 111 | test.expect(2); 112 | 113 | var base = { 114 | 'http://example.com/s1': { 115 | 'http://example.com/p1': [ 116 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 117 | { 'type': 'literal', 'value': 'foo'} 118 | ] 119 | } 120 | }; 121 | var base2 = base; 122 | var description = { 123 | 'http://example.com/s1': { 124 | 'http://example.com/p2': [ 125 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 126 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'} 127 | ] 128 | } 129 | }; 130 | var expected = { 131 | 'http://example.com/s1': { 132 | 'http://example.com/p1': [ 133 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 134 | { 'type': 'literal', 'value': 'foo'} 135 | ], 136 | 'http://example.com/p2': [ 137 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 138 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'} 139 | ] 140 | } 141 | }; 142 | 143 | rdf.mixinDescription(base, description); 144 | test.deepEqual(base, expected); 145 | 146 | rdf.mixinDescription(description, base2); 147 | test.deepEqual(base2, expected); 148 | 149 | test.done(); 150 | }, 151 | 'merge descritions with same predicate': function (test) { 152 | test.expect(2); 153 | 154 | var base = { 155 | 'http://example.com/s1': { 156 | 'http://example.com/p1': [ 157 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 158 | { 'type': 'literal', 'value': 'foo'} 159 | ] 160 | } 161 | }; 162 | var base2 = base; 163 | var description = { 164 | 'http://example.com/s2': { 165 | 'http://example.com/p1': [ 166 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 167 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'} 168 | ] 169 | } 170 | }; 171 | var expected = { 172 | 'http://example.com/s1': { 173 | 'http://example.com/p1': [ 174 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 175 | { 'type': 'literal', 'value': 'foo'} 176 | ] 177 | }, 178 | 'http://example.com/s2': { 179 | 'http://example.com/p1': [ 180 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 181 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'} 182 | ] 183 | } 184 | }; 185 | 186 | rdf.mixinDescription(base, description); 187 | test.deepEqual(base, expected); 188 | 189 | rdf.mixinDescription(description, base2); 190 | test.deepEqual(base2, expected); 191 | 192 | test.done(); 193 | }, 194 | 'merge descritions with same object': function (test) { 195 | test.expect(2); 196 | 197 | var base = { 198 | 'http://example.com/s1': { 199 | 'http://example.com/p1': [ 200 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 201 | { 'type': 'literal', 'value': 'same'} 202 | ] 203 | } 204 | }; 205 | var base2 = base; 206 | var description = { 207 | 'http://example.com/s1': { 208 | 'http://example.com/p1': [ 209 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 210 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'}, 211 | { 'type': 'literal', 'value': 'same'} 212 | ] 213 | } 214 | }; 215 | var expected = { 216 | 'http://example.com/s1': { 217 | 'http://example.com/p1': [ 218 | { 'type': 'uri', 'value': 'http://example.com/o1'}, 219 | { 'type': 'literal', 'value': 'same'}, 220 | { 'type': 'uri', 'value': 'http://example.com/o2'}, 221 | { 'type': 'literal', 'value': 'bar', 'lang': 'en'} 222 | ] 223 | } 224 | }; 225 | 226 | rdf.mixinDescription(base, description); 227 | test.deepEqual(base, expected); 228 | 229 | rdf.mixinDescription(description, base2); 230 | test.deepEqual(base2, expected); 231 | 232 | test.done(); 233 | } 234 | }); 235 | 236 | exports.testObjectEquals = nodeunit.testCase({ 237 | 'URI object': function (test) { 238 | test.expect(3); 239 | 240 | var o1 = { 241 | 'type': 'uri', 242 | 'value': 'http://example.com/o1' 243 | }; 244 | var o2 = { 245 | 'type': 'uri', 246 | 'value': 'http://example.com/o1' 247 | }; 248 | var o3 = { 249 | 'type': 'uri', 250 | 'value': 'http://example.com/o2' 251 | }; 252 | var o4 = { 253 | 'type': 'uri', 254 | 'value': 'http://example.com/o2', 255 | 'foo': 'bar' 256 | }; 257 | 258 | test.ok(rdf.objectEquals(o1, o2)); 259 | test.ok(!rdf.objectEquals(o1, o3)); 260 | test.ok(rdf.objectEquals(o3, o4)); 261 | 262 | test.done(); 263 | }, 264 | 265 | 'plain literal': function (test) { 266 | test.expect(2); 267 | 268 | var o1 = { 269 | 'type': 'literal', 270 | 'value': 'test text' 271 | }; 272 | var o2 = { 273 | 'type': 'literal', 274 | 'value': 'test text' 275 | }; 276 | var o3 = { 277 | 'type': 'literal', 278 | 'value': 'other text' 279 | }; 280 | 281 | test.ok(rdf.objectEquals(o1, o2)); 282 | test.ok(!rdf.objectEquals(o1, o3)); 283 | 284 | test.done(); 285 | }, 286 | 287 | 'typed literal': function (test) { 288 | test.expect(2); 289 | 290 | var o1 = { 291 | 'type': 'typed-literal', 292 | 'value': 'test text', 293 | 'datatype': 'http://www.w3.org/2001/XMLSchema#string' 294 | }; 295 | var o2 = { 296 | 'type': 'typed-literal', 297 | 'value': 'test text', 298 | 'datatype': 'http://www.w3.org/2001/XMLSchema#string' 299 | }; 300 | var o3 = { 301 | 'type': 'typed-literal', 302 | 'value': 'test text' 303 | }; 304 | 305 | test.ok(rdf.objectEquals(o1, o2)); 306 | test.ok(!rdf.objectEquals(o1, o3)); 307 | 308 | test.done(); 309 | }, 310 | 311 | 'lang-tagged literal': function (test) { 312 | test.expect(2); 313 | 314 | var o1 = { 315 | 'type': 'literal', 316 | 'value': 'test text', 317 | 'lang': 'en' 318 | }; 319 | var o2 = { 320 | 'type': 'literal', 321 | 'value': 'test text', 322 | 'lang': 'en' 323 | }; 324 | var o3 = { 325 | 'type': 'literal', 326 | 'value': 'other text', 327 | 'lang': 'en_US' 328 | }; 329 | 330 | test.ok(rdf.objectEquals(o1, o2)); 331 | test.ok(!rdf.objectEquals(o1, o3)); 332 | 333 | test.done(); 334 | } 335 | }); 336 | 337 | exports.testHashDescription = nodeunit.testCase({ 338 | 'empty descriptions hash to the same value': function (test) { 339 | test.expect(3); 340 | 341 | rdf.hashDescription({}, function (err, h1) { 342 | test.equal(null, err); 343 | 344 | rdf.hashDescription({}, function (err, h2) { 345 | test.equal(null, err); 346 | test.ok(h1 === h1); 347 | 348 | test.done(); 349 | }); 350 | }); 351 | }, 352 | 353 | 'simple descriptions hash to the same value': function (test) { 354 | test.expect(3); 355 | 356 | var d1 = { 357 | 'http://example.com/r1': { 358 | 'http://example.com/p1': [ 359 | { type: 'uri', value: 'http://example.com/o1' }, 360 | { type: 'literal', value: 'value 1' } 361 | ], 362 | 'http://example.com/p2': [ 363 | { value: 'ttt', type: 'literal' } 364 | ] 365 | } 366 | }; 367 | 368 | var d2 = { 369 | 'http://example.com/r1': { 370 | 'http://example.com/p2': [ 371 | { type: 'literal', value: 'ttt' } 372 | ], 373 | 'http://example.com/p1': [ 374 | { type: 'literal', value: 'value 1' }, 375 | { type: 'uri', value: 'http://example.com/o1' } 376 | ] 377 | } 378 | }; 379 | 380 | rdf.hashDescription(d1, function (err, h1) { 381 | test.equal(null, err); 382 | 383 | rdf.hashDescription(d2, function (err, h2) { 384 | test.equal(null, err); 385 | test.ok(h1 === h1); 386 | 387 | test.done(); 388 | }); 389 | }); 390 | }, 391 | 392 | 'descriptions using blank nodes with different IDs hash to the same value': function (test) { 393 | test.expect(3); 394 | 395 | var d1 = { 396 | 'http://example.com/r2': { 397 | 'http://example.com/p3': [ 398 | { type: 'bnode', value: '_:b12345' } 399 | ] 400 | } 401 | }; 402 | 403 | var d2 = { 404 | 'http://example.com/r2': { 405 | 'http://example.com/p3': [ 406 | { type: 'bnode', value: '_:b6789' } 407 | ] 408 | } 409 | }; 410 | 411 | rdf.hashDescription(d1, function (err, h1) { 412 | test.equal(null, err); 413 | 414 | rdf.hashDescription(d2, function (err, h2) { 415 | test.equal(null, err); 416 | test.ok(h1 === h1); 417 | 418 | test.done(); 419 | }); 420 | }); 421 | } 422 | }); 423 | 424 | exports.testToJSONLD = nodeunit.testCase({ 425 | 'simple with multiple literal objects': function (test) { 426 | test.expect(2); 427 | 428 | var context = { 'p1': 'http://example.com/p1' }; 429 | var description = { 430 | 'http://example.com/r1': { 431 | 'http://example.com/p1': [ 432 | { 'type': 'literal', 'value': 'foo bar' }, 433 | { 'type': 'literal', 434 | 'value': 'foo bar baz', 435 | 'datatype': 'http://www.w3.org/2001/XMLSchema#string' } 436 | ] 437 | } 438 | }; 439 | var expected = { 440 | '@id': 'http://example.com/r1', 441 | '@context': context, 442 | 'p1': [ 443 | 'foo bar', 444 | { '@value': 'foo bar baz', '@type': 'http://www.w3.org/2001/XMLSchema#string' } 445 | ] 446 | }; 447 | 448 | rdf.toJSONLD(description, context, function (err, ld) { 449 | test.equal(null, err); 450 | test.deepEqual(expected, ld); 451 | test.done(); 452 | }); 453 | }, 454 | 455 | 'simple with single uri object': function (test) { 456 | test.expect(2); 457 | 458 | var context = { 'p2': 'http://example.com/p2' }; 459 | var description = { 460 | 'http://example.com/r2': { 461 | 'http://example.com/p2': [ 462 | { 'type': 'uri', 'value': 'http://example.com/o2' } 463 | ] 464 | } 465 | }; 466 | var expected = { 467 | '@id': 'http://example.com/r2', 468 | '@context': context, 469 | 'p2': { '@id': 'http://example.com/o2' } 470 | }; 471 | 472 | rdf.toJSONLD(description, context, function (err, ld) { 473 | test.equal(null, err); 474 | test.deepEqual(expected, ld); 475 | test.done(); 476 | }); 477 | }, 478 | 479 | 'multiple resources': function (test) { 480 | test.expect(2); 481 | 482 | var description = { 483 | 'http://example.com/fr2': { 484 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 485 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 486 | ], 487 | 'http://example.com/foo': [ 488 | { 'type': 'literal', 'value': 'bar' }, 489 | { 'type': 'literal', 'value': 'baz' } 490 | ] 491 | }, 492 | 'http://example.com/fr1': { 493 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 494 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 495 | ], 496 | 'http://example.com/foo': [ 497 | { 'type': 'literal', 'value': 'bar' } 498 | ] 499 | }, 500 | 'http://example.com/fr3': { 501 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 502 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 503 | ], 504 | 'http://example.com/foo': [ 505 | { 'type': 'literal', 'value': 'ttt', 'lang': 'en' } 506 | ] 507 | } 508 | }; 509 | 510 | var context = { 511 | 'foo': 'http://example.com/foo', 512 | 'FrameResource': 'http://example.com/FrameResource' 513 | }; 514 | var expected = { 515 | '@context': context, 516 | '@graph': [{ 517 | '@id': 'http://example.com/fr2', 518 | '@type': 'FrameResource', 519 | 'foo': [ 'bar', 'baz' ] 520 | }, { 521 | '@id': 'http://example.com/fr1', 522 | '@type': 'FrameResource', 523 | 'foo': 'bar' 524 | }, { 525 | '@id': 'http://example.com/fr3', 526 | '@type': 'FrameResource', 527 | 'foo': { '@value': 'ttt', '@language': 'en' } 528 | }] 529 | }; 530 | 531 | rdf.toJSONLD(description, context, function (err, ld) { 532 | test.equal(null, err); 533 | test.deepEqual(expected, ld); 534 | test.done(); 535 | }); 536 | } 537 | }); 538 | 539 | exports.testToRDFJSON = nodeunit.testCase({ 540 | 'simple with single @id object': function (test) { 541 | test.expect(2); 542 | 543 | var jsonLD = { 544 | '@context': { 'p3': { '@id': 'http://example.com/p3', '@type': '@id' } }, 545 | '@id': 'http://example.com/r3', 546 | 'p3': 'http://example.com/o3' 547 | }; 548 | 549 | var expected = { 550 | 'http://example.com/r3': { 551 | 'http://example.com/p3': [{ 552 | 'type': 'uri', 553 | 'value': 'http://example.com/o3' 554 | }] 555 | } 556 | }; 557 | 558 | rdf.toRDFJSON(jsonLD, function (err, result) { 559 | test.equal(null, err); 560 | test.deepEqual(result, expected); 561 | test.done(); 562 | }); 563 | }, 564 | 565 | 'simple with multiple @id objects, including blank node': function (test) { 566 | test.expect(2); 567 | 568 | var jsonLD = { 569 | '@context': { 'p3': { '@id': 'http://example.com/p3', '@type': '@id' } }, 570 | '@id': 'http://example.com/r3', 571 | 'p3': [ 'http://example.com/o3', 'http://example.com/o4', '_:objectNode5'] 572 | }; 573 | 574 | var expected = { 575 | 'http://example.com/r3': { 576 | 'http://example.com/p3': [{ 577 | 'type': 'uri', 578 | 'value': 'http://example.com/o3' 579 | }, { 580 | 'type': 'uri', 581 | 'value': 'http://example.com/o4' 582 | }, { 583 | 'type': 'bnode', 584 | 'value': '_:objectNode5' 585 | }] 586 | } 587 | }; 588 | 589 | rdf.toRDFJSON(jsonLD, function (err, result) { 590 | test.equal(null, err); 591 | test.deepEqual(result, expected); 592 | test.done(); 593 | }); 594 | }, 595 | 596 | 'simple with multiple literal objects': function (test) { 597 | test.expect(2); 598 | 599 | var jsonLD = { 600 | '@context': { 601 | 'p4': { '@id': 'http://example.com/p4' }, 602 | 'p5': { '@id': 'http://example.com/p5', '@type': 'xsd:float' }, 603 | 'xsd': 'http://www.w3.org/2001/XMLSchema#' 604 | }, 605 | '@id': 'http://example.com/r4', 606 | 'p4': [ 'value 1', 'value 2' ], 607 | 'p5': 1234.5678 608 | }; 609 | 610 | var expected = { 611 | 'http://example.com/r4': { 612 | 'http://example.com/p5': [{ 613 | 'type': 'literal', 614 | 'value': '1234.5678', 615 | 'datatype': 'http://www.w3.org/2001/XMLSchema#float' 616 | }], 617 | 'http://example.com/p4': [{ 618 | 'type': 'literal', 619 | 'value': 'value 1' 620 | }, { 621 | 'type': 'literal', 622 | 'value': 'value 2' 623 | }] 624 | } 625 | }; 626 | 627 | rdf.toRDFJSON(jsonLD, function (err, result) { 628 | test.equal(null, err); 629 | test.deepEqual(result, expected); 630 | test.done(); 631 | }); 632 | }, 633 | 634 | 'multiple resources': function (test) { 635 | test.expect(2); 636 | 637 | var jsonLDFrame = { 638 | '@context': { 639 | 'foo': 'http://example.com/foo', 640 | 'FrameResource': 'http://example.com/FrameResource' 641 | }, 642 | '@graph': [{ 643 | '@id': 'http://example.com/fr1', 644 | '@type': 'FrameResource', 645 | 'foo': 'bar' 646 | }, { 647 | '@id': 'http://example.com/fr2', 648 | '@type': 'FrameResource', 649 | 'foo': [ 'bar', 'baz' ] 650 | }, { 651 | '@id': 'http://example.com/fr3', 652 | '@type': 'FrameResource', 653 | 'foo': { '@value': 'ttt', '@language': 'en' } 654 | }] 655 | }; 656 | 657 | var expected = { 658 | 'http://example.com/fr2': { 659 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 660 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 661 | ], 662 | 'http://example.com/foo': [ 663 | { 'type': 'literal', 'value': 'bar' }, 664 | { 'type': 'literal', 'value': 'baz' } 665 | ] 666 | 667 | }, 668 | 'http://example.com/fr1': { 669 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 670 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 671 | ], 672 | 'http://example.com/foo': [ 673 | { 'type': 'literal', 'value': 'bar' } 674 | ] 675 | }, 676 | 'http://example.com/fr3': { 677 | 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': [ 678 | { 'type': 'uri', 'value': 'http://example.com/FrameResource' } 679 | ], 680 | 'http://example.com/foo': [ 681 | { 'type': 'literal', 'value': 'ttt', 'lang': 'en' } 682 | ] 683 | } 684 | }; 685 | 686 | rdf.toRDFJSON(jsonLDFrame, function (err, result) { 687 | test.equal(null, err); 688 | test.deepEqual(expected, result); 689 | test.done(); 690 | }); 691 | } 692 | }); 693 | --------------------------------------------------------------------------------