make[1]: Entering directory `/work/src/jsonld.js' Coverage

Coverage

75%
2534
1902
632

jsonld.js

75%
2534
1902
632
LineHitsSource
1/**
2 * A JavaScript implementation of the JSON-LD API.
3 *
4 * @author Dave Longley
5 *
6 * BSD 3-Clause License
7 * Copyright (c) 2011-2013 Digital Bazaar, Inc.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 *
13 * Redistributions of source code must retain the above copyright notice,
14 * this list of conditions and the following disclaimer.
15 *
16 * Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * Neither the name of the Digital Bazaar, Inc. nor the names of its
21 * contributors may be used to endorse or promote products derived from
22 * this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
30 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
361(function() {
37
38// determine if in-browser or using node.js
391var _nodejs = (typeof module === 'object' && module.exports);
401var _browser = !_nodejs && window;
41
42// attaches jsonld API to the given object
431var wrapper = function(jsonld) {
44
45/* Core API */
46
47/**
48 * Performs JSON-LD compaction.
49 *
50 * @param input the JSON-LD input to compact.
51 * @param ctx the context to compact with.
52 * @param [options] options to use:
53 * [base] the base IRI to use.
54 * [strict] use strict mode (default: true).
55 * [compactArrays] true to compact arrays to single values when
56 * appropriate, false not to (default: true).
57 * [graph] true to always output a top-level graph (default: false).
58 * [skipExpansion] true to assume the input is expanded and skip
59 * expansion, false not to, defaults to false.
60 * [loadContext(url, callback(err, url, result))] the context loader.
61 * @param callback(err, compacted, ctx) called once the operation completes.
62 */
632jsonld.compact = function(input, ctx, options, callback) {
64 // get arguments
6586 if(typeof options === 'function') {
660 callback = options;
670 options = {};
68 }
6986 options = options || {};
70
71 // nothing to compact
7286 if(input === null) {
730 return callback(null, null);
74 }
75
76 // set default options
7786 if(!('base' in options)) {
780 options.base = '';
79 }
8086 if(!('strict' in options)) {
8186 options.strict = true;
82 }
8386 if(!('compactArrays' in options)) {
8486 options.compactArrays = true;
85 }
8686 if(!('graph' in options)) {
8765 options.graph = false;
88 }
8986 if(!('skipExpansion' in options)) {
9065 options.skipExpansion = false;
91 }
9286 if(!('loadContext' in options)) {
9365 options.loadContext = jsonld.loadContext;
94 }
95
9686 var expand = function(input, options, callback) {
9786 if(options.skipExpansion) {
9821 return callback(null, input);
99 }
10065 jsonld.expand(input, options, callback);
101 };
102
103 // expand input then do compaction
10486 expand(input, options, function(err, expanded) {
10586 if(err) {
1060 return callback(new JsonLdError(
107 'Could not expand input before compaction.',
108 'jsonld.CompactError', {cause: err}));
109 }
110
111 // process context
11286 var activeCtx = _getInitialContext(options);
11386 jsonld.processContext(activeCtx, ctx, options, function(err, activeCtx) {
11486 if(err) {
1150 return callback(new JsonLdError(
116 'Could not process context before compaction.',
117 'jsonld.CompactError', {cause: err}));
118 }
119
12086 try {
121 // do compaction
12286 var compacted = new Processor().compact(
123 activeCtx, null, expanded, options);
12486 cleanup(null, compacted, activeCtx, options);
125 }
126 catch(ex) {
1270 callback(ex);
128 }
129 });
130 });
131
132 // performs clean up after compaction
13386 function cleanup(err, compacted, activeCtx, options) {
13486 if(err) {
1350 return callback(err);
136 }
137
13886 if(options.compactArrays && !options.graph && _isArray(compacted)) {
139 // simplify to a single item
1408 if(compacted.length === 1) {
1410 compacted = compacted[0];
142 }
143 // simplify to an empty object
1448 else if(compacted.length === 0) {
1452 compacted = {};
146 }
147 }
148 // always use array if graph option is on
14978 else if(options.graph && _isObject(compacted)) {
15013 compacted = [compacted];
151 }
152
153 // follow @context key
15486 if(_isObject(ctx) && '@context' in ctx) {
15559 ctx = ctx['@context'];
156 }
157
158 // build output context
15986 ctx = _clone(ctx);
16086 if(!_isArray(ctx)) {
16185 ctx = [ctx];
162 }
163 // remove empty contexts
16486 var tmp = ctx;
16586 ctx = [];
16686 for(var i in tmp) {
16787 if(!_isObject(tmp[i]) || Object.keys(tmp[i]).length > 0) {
16879 ctx.push(tmp[i]);
169 }
170 }
171
172 // remove array if only one context
17386 var hasContext = (ctx.length > 0);
17486 if(ctx.length === 1) {
17577 ctx = ctx[0];
176 }
177
178 // add context
17986 if(hasContext || options.graph) {
18080 if(_isArray(compacted)) {
181 // use '@graph' keyword
18226 var kwgraph = _compactIri(activeCtx, '@graph');
18326 var graph = compacted;
18426 compacted = {};
18526 if(hasContext) {
18624 compacted['@context'] = ctx;
187 }
18826 compacted[kwgraph] = graph;
189 }
19054 else if(_isObject(compacted)) {
191 // reorder keys so @context is first
19254 var graph = compacted;
19354 compacted = {'@context': ctx};
19454 for(var key in graph) {
195159 compacted[key] = graph[key];
196 }
197 }
198 }
199
20086 callback(null, compacted, activeCtx);
201 }
202};
203
204/**
205 * Performs JSON-LD expansion.
206 *
207 * @param input the JSON-LD input to expand.
208 * @param [options] the options to use:
209 * [base] the base IRI to use.
210 * [keepFreeFloatingNodes] true to keep free-floating nodes,
211 * false not to, defaults to false.
212 * [loadContext(url, callback(err, url, result))] the context loader.
213 * @param callback(err, expanded) called once the operation completes.
214 */
2152jsonld.expand = function(input, options, callback) {
216 // get arguments
217303 if(typeof options === 'function') {
2180 callback = options;
2190 options = {};
220 }
221303 options = options || {};
222
223 // set default options
224303 if(!('base' in options)) {
2250 options.base = '';
226 }
227303 if(!('loadContext' in options)) {
22869 options.loadContext = jsonld.loadContext;
229 }
230303 if(!('keepFreeFloatingNodes' in options)) {
231282 options.keepFreeFloatingNodes = false;
232 }
233
234 // retrieve all @context URLs in the input
235303 input = _clone(input);
236303 _retrieveContextUrls(input, options, function(err, input) {
237303 if(err) {
2380 return callback(err);
239 }
240303 try {
241 // do expansion
242303 var activeCtx = _getInitialContext(options);
243303 var expanded = new Processor().expand(
244 activeCtx, null, input, options, false);
245
246 // optimize away @graph with no other properties
247303 if(_isObject(expanded) && ('@graph' in expanded) &&
248 Object.keys(expanded).length === 1) {
24963 expanded = expanded['@graph'];
250 }
251240 else if(expanded === null) {
25211 expanded = [];
253 }
254
255 // normalize to an array
256303 if(!_isArray(expanded)) {
257181 expanded = [expanded];
258 }
259303 callback(null, expanded);
260 }
261 catch(ex) {
2620 callback(ex);
263 }
264 });
265};
266
267/**
268 * Performs JSON-LD flattening.
269 *
270 * @param input the JSON-LD to flatten.
271 * @param ctx the context to use to compact the flattened output, or null.
272 * @param [options] the options to use:
273 * [base] the base IRI to use.
274 * [loadContext(url, callback(err, url, result))] the context loader.
275 * @param callback(err, flattened) called once the operation completes.
276 */
2772jsonld.flatten = function(input, ctx, options, callback) {
278 // get arguments
27940 if(typeof options === 'function') {
2800 callback = options;
2810 options = {};
282 }
28340 options = options || {};
284
285 // set default options
28640 if(!('base' in options)) {
2870 options.base = '';
288 }
28940 if(!('loadContext' in options)) {
29040 options.loadContext = jsonld.loadContext;
291 }
292
293 // expand input
29440 jsonld.expand(input, options, function(err, _input) {
29540 if(err) {
2960 return callback(new JsonLdError(
297 'Could not expand input before flattening.',
298 'jsonld.FlattenError', {cause: err}));
299 }
300
30140 try {
302 // do flattening
30340 var flattened = new Processor().flatten(_input);
304 }
305 catch(ex) {
3060 return callback(ex);
307 }
308
30940 if(ctx === null) {
31040 return callback(null, flattened);
311 }
312
313 // compact result (force @graph option to true, skip expansion)
3140 options.graph = true;
3150 options.skipExpansion = true;
3160 jsonld.compact(flattened, ctx, options, function(err, compacted) {
3170 if(err) {
3180 return callback(new JsonLdError(
319 'Could not compact flattened output.',
320 'jsonld.FlattenError', {cause: err}));
321 }
3220 callback(null, compacted);
323 });
324 });
325};
326
327/**
328 * Performs JSON-LD framing.
329 *
330 * @param input the JSON-LD input to frame.
331 * @param frame the JSON-LD frame to use.
332 * @param [options] the framing options.
333 * [base] the base IRI to use.
334 * [embed] default @embed flag (default: true).
335 * [explicit] default @explicit flag (default: false).
336 * [omitDefault] default @omitDefault flag (default: false).
337 * [loadContext(url, callback(err, url, result))] the context loader.
338 * @param callback(err, framed) called once the operation completes.
339 */
3402jsonld.frame = function(input, frame, options, callback) {
341 // get arguments
34221 if(typeof options === 'function') {
3430 callback = options;
3440 options = {};
345 }
34621 options = options || {};
347
348 // set default options
34921 if(!('base' in options)) {
3500 options.base = '';
351 }
35221 if(!('loadContext' in options)) {
35321 options.loadContext = jsonld.loadContext;
354 }
35521 if(!('embed' in options)) {
35621 options.embed = true;
357 }
35821 options.explicit = options.explicit || false;
35921 options.omitDefault = options.omitDefault || false;
360
361 // preserve frame context
36221 var ctx = frame['@context'] || {};
363
364 // expand input
36521 jsonld.expand(input, options, function(err, expanded) {
36621 if(err) {
3670 return callback(new JsonLdError(
368 'Could not expand input before framing.',
369 'jsonld.FrameError', {cause: err}));
370 }
371
372 // expand frame
37321 var opts = _clone(options);
37421 opts.keepFreeFloatingNodes = true;
37521 jsonld.expand(frame, opts, function(err, expandedFrame) {
37621 if(err) {
3770 return callback(new JsonLdError(
378 'Could not expand frame before framing.',
379 'jsonld.FrameError', {cause: err}));
380 }
381
38221 try {
383 // do framing
38421 var framed = new Processor().frame(expanded, expandedFrame, options);
385 }
386 catch(ex) {
3870 return callback(ex);
388 }
389
390 // compact result (force @graph option to true, skip expansion)
39121 opts.graph = true;
39221 opts.skipExpansion = true;
39321 jsonld.compact(framed, ctx, opts, function(err, compacted, ctx) {
39421 if(err) {
3950 return callback(new JsonLdError(
396 'Could not compact framed output.',
397 'jsonld.FrameError', {cause: err}));
398 }
399 // get graph alias
40021 var graph = _compactIri(ctx, '@graph');
401 // remove @preserve from results
40221 compacted[graph] = _removePreserve(ctx, compacted[graph], opts);
40321 callback(null, compacted);
404 });
405 });
406 });
407};
408
409/**
410 * Performs JSON-LD objectification.
411 *
412 * @param input the JSON-LD input to objectify.
413 * @param ctx the JSON-LD context to apply.
414 * @param [options] the framing options.
415 * [base] the base IRI to use.
416 * [loadContext(url, callback(err, url, result))] the context loader.
417 * @param callback(err, objectified) called once the operation completes.
418 */
4192jsonld.objectify = function(input, ctx, options, callback) {
420 // get arguments
4210 if(typeof options === 'function') {
4220 callback = options;
4230 options = {};
424 }
4250 options = options || {};
426
427 // set default options
4280 if(!('base' in options)) {
4290 options.base = '';
430 }
4310 if(!('loadContext' in options)) {
4320 options.loadContext = jsonld.loadContext;
433 }
434
435 // expand input
4360 jsonld.expand(input, options, function(err, _input) {
4370 if(err) {
4380 return callback(new JsonLdError(
439 'Could not expand input before framing.',
440 'jsonld.FrameError', {cause: err}));
441 }
442
4430 try {
444 // flatten the graph
4450 var flattened = new Processor().flatten(_input);
446 }
447 catch(ex) {
4480 return callback(ex);
449 }
450
451 // compact result (force @graph option to true, skip expansion)
4520 options.graph = true;
4530 options.skipExpansion = true;
4540 jsonld.compact(flattened, ctx, options, function(err, compacted, ctx) {
4550 if(err) {
4560 return callback(new JsonLdError(
457 'Could not compact flattened output.',
458 'jsonld.FrameError', {cause: err}));
459 }
460 // get graph alias
4610 var graph = _compactIri(ctx, '@graph');
462 // remove @preserve from results (named graphs?)
4630 compacted[graph] = _removePreserve(ctx, compacted[graph], options);
464
4650 var top = compacted[graph][0];
466
4670 var recurse = function(subject) {
468 // can't replace just a string
4690 if(!_isObject(subject) && !_isArray(subject)) {
4700 return;
471 }
472
473 // bottom out recursion on re-visit
4740 if(_isObject(subject)) {
4750 if(recurse.visited[subject['@id']]) {
4760 return;
477 }
4780 recurse.visited[subject['@id']] = true;
479 }
480
481 // each array element *or* object key
4820 for(var k in subject) {
4830 var obj = subject[k];
4840 var isid = (jsonld.getContextValue(ctx, k, '@type') === '@id');
485
486 // can't replace a non-object or non-array unless it's an @id
4870 if(!_isArray(obj) && !_isObject(obj) && !isid) {
4880 continue;
489 }
490
4910 if(_isString(obj) && isid) {
4920 subject[k] = obj = top[obj];
4930 recurse(obj);
494 }
4950 else if(_isArray(obj)) {
4960 for(var i=0; i<obj.length; i++) {
4970 if(_isString(obj[i]) && isid) {
4980 obj[i] = top[obj[i]];
499 }
5000 else if(_isObject(obj[i]) && '@id' in obj[i]) {
5010 obj[i] = top[obj[i]['@id']];
502 }
5030 recurse(obj[i]);
504 }
505 }
5060 else if(_isObject(obj)) {
5070 var sid = obj['@id'];
5080 subject[k] = obj = top[sid];
5090 recurse(obj);
510 }
511 }
512 };
5130 recurse.visited = {};
5140 recurse(top);
515
5160 compacted.of_type = {};
5170 for(var s in top) {
5180 if(!('@type' in top[s])) {
5190 continue;
520 }
5210 var types = top[s]['@type'];
5220 if(!_isArray(types)) {
5230 types = [types];
524 }
5250 for(var t in types) {
5260 if(!(types[t] in compacted.of_type)) {
5270 compacted.of_type[types[t]] = [];
528 }
5290 compacted.of_type[types[t]].push(top[s]);
530 }
531 }
5320 callback(null, compacted);
533 });
534 });
535};
536
537/**
538 * Performs RDF dataset normalization on the given JSON-LD input. The output
539 * is an RDF dataset unless the 'format' option is used.
540 *
541 * @param input the JSON-LD input to normalize.
542 * @param [options] the options to use:
543 * [base] the base IRI to use.
544 * [format] the format if output is a string:
545 * 'application/nquads' for N-Quads.
546 * [loadContext(url, callback(err, url, result))] the context loader.
547 * @param callback(err, normalized) called once the operation completes.
548 */
5492jsonld.normalize = function(input, options, callback) {
550 // get arguments
55157 if(typeof options === 'function') {
5520 callback = options;
5530 options = {};
554 }
55557 options = options || {};
556
557 // set default options
55857 if(!('base' in options)) {
5590 options.base = '';
560 }
56157 if(!('loadContext' in options)) {
56257 options.loadContext = jsonld.loadContext;
563 }
564
565 // convert to RDF dataset then do normalization
56657 var opts = _clone(options);
56757 delete opts.format;
56857 jsonld.toRDF(input, opts, function(err, dataset) {
56957 if(err) {
5700 return callback(new JsonLdError(
571 'Could not convert input to RDF dataset before normalization.',
572 'jsonld.NormalizeError', {cause: err}));
573 }
574
575 // do normalization
57657 new Processor().normalize(dataset, options, callback);
577 });
578};
579
580/**
581 * Converts an RDF dataset to JSON-LD.
582 *
583 * @param dataset a serialized string of RDF in a format specified by the
584 * format option or an RDF dataset to convert.
585 * @param [options] the options to use:
586 * [format] the format if input is not an array:
587 * 'application/nquads' for N-Quads (default).
588 * [useRdfType] true to use rdf:type, false to use @type
589 * (default: false).
590 * [useNativeTypes] true to convert XSD types into native types
591 * (boolean, integer, double), false not to (default: true).
592 *
593 * @param callback(err, output) called once the operation completes.
594 */
5952jsonld.fromRDF = function(dataset, options, callback) {
596 // get arguments
5977 if(typeof options === 'function') {
5980 callback = options;
5990 options = {};
600 }
6017 options = options || {};
602
603 // set default options
6047 if(!('useRdfType' in options)) {
6057 options.useRdfType = false;
606 }
6077 if(!('useNativeTypes' in options)) {
6087 options.useNativeTypes = true;
609 }
610
6117 if(!('format' in options) && _isString(dataset)) {
612 // set default format to nquads
6137 if(!('format' in options)) {
6147 options.format = 'application/nquads';
615 }
616 }
617
618 // handle special format
6197 if(options.format) {
620 // supported formats
6217 if(options.format in _rdfParsers) {
6227 dataset = _rdfParsers[options.format](dataset);
623 }
624 else {
6250 throw new JsonLdError(
626 'Unknown input format.',
627 'jsonld.UnknownFormat', {format: options.format});
628 }
629 }
630
631 // convert from RDF
6327 new Processor().fromRDF(dataset, options, callback);
633};
634
635/**
636 * Outputs the RDF dataset found in the given JSON-LD object.
637 *
638 * @param input the JSON-LD input.
639 * @param [options] the options to use:
640 * [base] the base IRI to use.
641 * [format] the format to use to output a string:
642 * 'application/nquads' for N-Quads (default).
643 * [loadContext(url, callback(err, url, result))] the context loader.
644 * @param callback(err, dataset) called once the operation completes.
645 */
6462jsonld.toRDF = function(input, options, callback) {
647 // get arguments
64887 if(typeof options === 'function') {
6490 callback = options;
6500 options = {};
651 }
65287 options = options || {};
653
654 // set default options
65587 if(!('base' in options)) {
6560 options.base = '';
657 }
65887 if(!('loadContext' in options)) {
65930 options.loadContext = jsonld.loadContext;
660 }
661
662 // expand input
66387 jsonld.expand(input, options, function(err, expanded) {
66487 if(err) {
6650 return callback(new JsonLdError(
666 'Could not expand input before conversion to RDF.',
667 'jsonld.RdfError', {cause: err}));
668 }
669
670 // create node map for default graph (and any named graphs)
67187 var namer = new UniqueNamer('_:b');
67287 var nodeMap = {'@default': {}};
67387 _createNodeMap(expanded, nodeMap, '@default', namer);
674
67587 try {
676 // output RDF dataset
67787 var dataset = Processor.prototype.toRDF(nodeMap);
67887 if(options.format) {
67930 if(options.format === 'application/nquads') {
68030 return callback(null, _toNQuads(dataset));
681 }
6820 throw new JsonLdError(
683 'Unknown output format.',
684 'jsonld.UnknownFormat', {format: options.format});
685 }
68657 callback(null, dataset);
687 }
688 catch(ex) {
6890 callback(ex);
690 }
691 });
692};
693
694/**
695 * Relabels all blank nodes in the given JSON-LD input.
696 *
697 * @param input the JSON-LD input.
698 */
6992jsonld.relabelBlankNodes = function(input) {
7000 _labelBlankNodes(new UniqueNamer('_:b', input));
701};
702
703/**
704 * The default context loader for external @context URLs.
705 *
706 * @param loadContext(url, callback(err, url, result)) the context loader.
707 */
7082jsonld.loadContext = function(url, callback) {
7090 return callback(new JsonLdError(
710 'Could not retrieve @context URL. URL derefencing not implemented.',
711 'jsonld.ContextUrlError'), url);
712};
713
714/* WebIDL API */
715
7164function JsonLdProcessor() {};
717// callback param order unconventional w/WebIDL API
7182JsonLdProcessor.prototype.expand = function(input, callback) {
7190 var options = {};
7200 if(arguments.length > 2) {
7210 options = callback;
7220 callback = arguments[2];
723 }
7240 jsonld.expand(input, options, callback);
725};
726// callback param order unconventional w/WebIDL API
7272JsonLdProcessor.prototype.compact = function(input, ctx, callback) {
7280 var options = {};
7290 if(arguments.length > 3) {
7300 options = callback;
7310 callback = arguments[3];
732 }
7330 jsonld.compact(input, ctx, options, callback);
734};
735// callback param order unconventional w/WebIDL API
7362JsonLdProcessor.prototype.flatten = function(input, ctx, callback) {
7370 var options = {};
7380 if(arguments.length > 3) {
7390 options = callback;
7400 callback = arguments[3];
741 }
7420 jsonld.flatten(input, ctx, options, callback);
743};
7442JsonLdProcessor.prototype.frame = jsonld.frame;
7452JsonLdProcessor.prototype.fromRDF = jsonld.fromRDF;
7462JsonLdProcessor.prototype.toRDF = jsonld.toRDF;
7472JsonLdProcessor.prototype.normalize = jsonld.normalize;
7482jsonld.JsonLdProcessor = JsonLdProcessor;
749
750/* Utility API */
751
752// define nextTick
7532if(typeof process === 'undefined' || !process.nextTick) {
7540 if(typeof setImmediate === 'function') {
7550 jsonld.nextTick = function(callback) {
7560 setImmediate(callback);
757 };
758 }
759 else {
7600 jsonld.nextTick = function(callback) {
7610 setTimeout(callback, 0);
762 };
763 }
764}
765else {
7662 jsonld.nextTick = process.nextTick;
767}
768
769/**
770 * Creates a simple context cache.
771 *
772 * @param size the maximum size of the cache.
773 */
7742jsonld.ContextCache = function(size) {
7752 this.order = [];
7762 this.cache = {};
7772 this.size = size || 50;
7782 this.expires = 30*60*1000;
779};
7802jsonld.ContextCache.prototype.get = function(url) {
7810 if(url in this.cache) {
7820 var entry = this.cache[url];
7830 if(entry.expires >= +new Date()) {
7840 return entry.ctx;
785 }
7860 delete this.cache[url];
7870 this.order.splice(this.order.indexOf(url), 1);
788 }
7890 return null;
790};
7912jsonld.ContextCache.prototype.set = function(url, ctx) {
7920 if(this.order.length === this.size) {
7930 delete this.cache[this.order.shift()];
794 }
7950 this.order.push(url);
7960 this.cache[url] = {ctx: ctx, expires: (+new Date() + this.expires)};
797};
798
799/**
800 * Creates an active context cache.
801 *
802 * @param size the maximum size of the cache.
803 */
8042jsonld.ActiveContextCache = function(size) {
8052 this.order = [];
8062 this.cache = {};
8072 this.size = size || 100;
808};
8092jsonld.ActiveContextCache.prototype.get = function(activeCtx, localCtx) {
810326 var key1 = JSON.stringify(activeCtx);
811326 var key2 = JSON.stringify(localCtx);
812326 var level1 = this.cache[key1];
813326 if(level1 && key2 in level1) {
8149 return level1[key2];
815 }
816317 return null;
817};
8182jsonld.ActiveContextCache.prototype.set = function(
819 activeCtx, localCtx, result) {
820317 if(this.order.length === this.size) {
821217 var entry = this.order.shift();
822217 delete this.cache[entry.activeCtx][entry.localCtx];
823 }
824317 var key1 = JSON.stringify(activeCtx);
825317 var key2 = JSON.stringify(localCtx);
826317 this.order.push({activeCtx: key1, localCtx: key2});
827317 if(!(key1 in this.cache)) {
828269 this.cache[key1] = {};
829 }
830317 this.cache[key1][key2] = result;
831};
832
833/**
834 * Default JSON-LD cache.
835 */
8362jsonld.cache = {
837 activeCtx: new jsonld.ActiveContextCache()
838};
839
840/**
841 * Context loaders.
842 */
8432jsonld.contextLoaders = {};
844
845/**
846 * The built-in jquery context loader.
847 *
848 * @param $ the jquery instance to use.
849 * @param options the options to use:
850 * secure: require all URLs to use HTTPS.
851 *
852 * @return the jquery context loader.
853 */
8542jsonld.contextLoaders['jquery'] = function($, options) {
8550 options = options || {};
8560 var cache = new jsonld.ContextCache();
8570 return function(url, callback) {
8580 if(options.secure && url.indexOf('https') !== 0) {
8590 return callback(new JsonLdError(
860 'URL could not be dereferenced; secure mode is enabled and ' +
861 'the URL\'s scheme is not "https".',
862 'jsonld.InvalidUrl', {url: url}), url);
863 }
8640 var ctx = cache.get(url);
8650 if(ctx !== null) {
8660 return callback(null, url, ctx);
867 }
8680 $.ajax({
869 url: url,
870 dataType: 'json',
871 crossDomain: true,
872 success: function(data, textStatus, jqXHR) {
8730 cache.set(url, data);
8740 callback(null, url, data);
875 },
876 error: function(jqXHR, textStatus, err) {
8770 callback(new JsonLdError(
878 'URL could not be dereferenced, an error occurred.',
879 'jsonld.LoadContextError', {url: url, cause: err}), url);
880 }
881 });
882 };
883};
884
885/**
886 * The built-in node context loader.
887 *
888 * @param options the options to use:
889 * secure: require all URLs to use HTTPS.
890 * maxRedirects: the maximum number of redirects to permit, none by
891 * default.
892 *
893 * @return the node context loader.
894 */
8952jsonld.contextLoaders['node'] = function(options) {
8962 options = options || {};
8972 var maxRedirects = ('maxRedirects' in options) ? options.maxRedirects : -1;
8982 var request = require('request');
8992 var http = require('http');
9002 var cache = new jsonld.ContextCache();
9012 function loadContext(url, redirects, callback) {
9020 if(options.secure && url.indexOf('https') !== 0) {
9030 return callback(new JsonLdError(
904 'URL could not be dereferenced; secure mode is enabled and ' +
905 'the URL\'s scheme is not "https".',
906 'jsonld.InvalidUrl', {url: url}), url);
907 }
9080 var ctx = cache.get(url);
9090 if(ctx !== null) {
9100 return callback(null, url, ctx);
911 }
9120 request({
913 url: url,
914 strictSSL: true,
915 followRedirect: false
916 }, function(err, res, body) {
917 // handle error
9180 if(err) {
9190 return callback(new JsonLdError(
920 'URL could not be dereferenced, an error occurred.',
921 'jsonld.LoadContextError', {url: url, cause: err}), url);
922 }
9230 var statusText = http.STATUS_CODES[res.statusCode];
9240 if(res.statusCode >= 400) {
9250 return callback(new JsonLdError(
926 'URL could not be dereferenced: ' + statusText,
927 'jsonld.InvalidUrl', {url: url, httpStatusCode: res.statusCode}),
928 url);
929 }
930 // handle redirect
9310 if(res.statusCode >= 300 && res.statusCode < 400 &&
932 res.headers.location) {
9330 if(redirects.length === maxRedirects) {
9340 return callback(new JsonLdError(
935 'URL could not be dereferenced; there were too many redirects.',
936 'jsonld.TooManyRedirects',
937 {url: url, httpStatusCode: res.statusCode, redirects: redirects}),
938 url);
939 }
9400 if(redirects.indexOf(url) !== -1) {
9410 return callback(new JsonLdError(
942 'URL could not be dereferenced; infinite redirection was detected.',
943 'jsonld.InfiniteRedirectDetected',
944 {url: url, httpStatusCode: res.statusCode, redirects: redirects}),
945 url);
946 }
9470 redirects.push(url);
9480 return loadContext(res.headers.location, redirects, callback);
949 }
950 // cache for each redirected URL
9510 redirects.push(url);
9520 for(var i = 0; i < redirects.length; ++i) {
9530 cache.set(redirects[i], body);
954 }
9550 callback(err, url, body);
956 });
957 }
958
9592 return function(url, callback) {
9600 loadContext(url, [], callback);
961 };
962};
963
964/**
965 * Assigns the default context loader for external @context URLs to a built-in
966 * default. Supported types currently include: 'jquery' and 'node'.
967 *
968 * To use the jquery context loader, the 'data' parameter must be a reference
969 * to the main jquery object.
970 *
971 * @param type the type to set.
972 * @param [params] the parameters required to use the context loader.
973 */
9742jsonld.useContextLoader = function(type) {
9752 if(!(type in jsonld.contextLoaders)) {
9760 throw new JsonLdError(
977 'Unknown @context loader type: "' + type + '"',
978 'jsonld.UnknownContextLoader',
979 {type: type});
980 }
981
982 // set context loader
9832 jsonld.loadContext = jsonld.contextLoaders[type].apply(
984 jsonld, Array.prototype.slice.call(arguments, 1));
985};
986
987/**
988 * Processes a local context, resolving any URLs as necessary, and returns a
989 * new active context in its callback.
990 *
991 * @param activeCtx the current active context.
992 * @param localCtx the local context to process.
993 * @param [options] the options to use:
994 * [loadContext(url, callback(err, url, result))] the context loader.
995 * @param callback(err, ctx) called once the operation completes.
996 */
9972jsonld.processContext = function(activeCtx, localCtx) {
998 // get arguments
99986 var options = {};
100086 var callbackArg = 2;
100186 if(arguments.length > 3) {
100286 options = arguments[2] || {};
100386 callbackArg += 1;
1004 }
100586 var callback = arguments[callbackArg];
1006
1007 // set default options
100886 if(!('base' in options)) {
10090 options.base = '';
1010 }
101186 if(!('loadContext' in options)) {
10120 options.loadContext = jsonld.loadContext;
1013 }
1014
1015 // return initial context early for null context
101686 if(localCtx === null) {
10170 return callback(null, _getInitialContext(options));
1018 }
1019
1020 // retrieve URLs in localCtx
102186 localCtx = _clone(localCtx);
102286 if(_isString(localCtx) ||
1023 (_isObject(localCtx) && !('@context' in localCtx))) {
102427 localCtx = {'@context': localCtx};
1025 }
102686 _retrieveContextUrls(localCtx, options, function(err, ctx) {
102786 if(err) {
10280 return callback(err);
1029 }
103086 try {
1031 // process context
103286 ctx = new Processor().processContext(activeCtx, ctx, options);
103386 callback(null, ctx);
1034 }
1035 catch(ex) {
10360 callback(ex);
1037 }
1038 });
1039};
1040
1041/**
1042 * Returns true if the given subject has the given property.
1043 *
1044 * @param subject the subject to check.
1045 * @param property the property to look for.
1046 *
1047 * @return true if the subject has the given property, false if not.
1048 */
10492jsonld.hasProperty = function(subject, property) {
1050410 var rval = false;
1051410 if(property in subject) {
1052292 var value = subject[property];
1053292 rval = (!_isArray(value) || value.length > 0);
1054 }
1055410 return rval;
1056};
1057
1058/**
1059 * Determines if the given value is a property of the given subject.
1060 *
1061 * @param subject the subject to check.
1062 * @param property the property to check.
1063 * @param value the value to check.
1064 *
1065 * @return true if the value exists, false if not.
1066 */
10672jsonld.hasValue = function(subject, property, value) {
1068410 var rval = false;
1069410 if(jsonld.hasProperty(subject, property)) {
1070292 var val = subject[property];
1071292 var isList = _isList(val);
1072292 if(_isArray(val) || isList) {
1073292 if(isList) {
10740 val = val['@list'];
1075 }
1076292 for(var i in val) {
1077631 if(jsonld.compareValues(value, val[i])) {
107841 rval = true;
107941 break;
1080 }
1081 }
1082 }
1083 // avoid matching the set of values with an array value parameter
10840 else if(!_isArray(value)) {
10850 rval = jsonld.compareValues(value, val);
1086 }
1087 }
1088410 return rval;
1089};
1090
1091/**
1092 * Adds a value to a subject. If the value is an array, all values in the
1093 * array will be added.
1094 *
1095 * @param subject the subject to add the value to.
1096 * @param property the property that relates the value to the subject.
1097 * @param value the value to add.
1098 * @param [options] the options to use:
1099 * [propertyIsArray] true if the property is always an array, false
1100 * if not (default: false).
1101 * [allowDuplicate] true to allow duplicates, false not to (uses a
1102 * simple shallow comparison of subject ID or value) (default: true).
1103 */
11042jsonld.addValue = function(subject, property, value, options) {
11054992 options = options || {};
11064992 if(!('propertyIsArray' in options)) {
110746 options.propertyIsArray = false;
1108 }
11094992 if(!('allowDuplicate' in options)) {
11103077 options.allowDuplicate = true;
1111 }
1112
11134992 if(_isArray(value)) {
1114553 if(value.length === 0 && options.propertyIsArray &&
1115 !(property in subject)) {
111687 subject[property] = [];
1117 }
1118553 for(var i in value) {
11191148 jsonld.addValue(subject, property, value[i], options);
1120 }
1121 }
11224439 else if(property in subject) {
1123 // check if subject already has value if duplicates not allowed
1124992 var hasValue = (!options.allowDuplicate &&
1125 jsonld.hasValue(subject, property, value));
1126
1127 // make property an array if value not present or always an array
1128992 if(!_isArray(subject[property]) &&
1129 (!hasValue || options.propertyIsArray)) {
113045 subject[property] = [subject[property]];
1131 }
1132
1133 // add new value
1134992 if(!hasValue) {
1135984 subject[property].push(value);
1136 }
1137 }
1138 else {
1139 // add new value as set or single value
11403447 subject[property] = options.propertyIsArray ? [value] : value;
1141 }
1142};
1143
1144/**
1145 * Gets all of the values for a subject's property as an array.
1146 *
1147 * @param subject the subject.
1148 * @param property the property.
1149 *
1150 * @return all of the values for a subject's property as an array.
1151 */
11522jsonld.getValues = function(subject, property) {
11530 var rval = subject[property] || [];
11540 if(!_isArray(rval)) {
11550 rval = [rval];
1156 }
11570 return rval;
1158};
1159
1160/**
1161 * Removes a property from a subject.
1162 *
1163 * @param subject the subject.
1164 * @param property the property.
1165 */
11662jsonld.removeProperty = function(subject, property) {
11670 delete subject[property];
1168};
1169
1170/**
1171 * Removes a value from a subject.
1172 *
1173 * @param subject the subject.
1174 * @param property the property that relates the value to the subject.
1175 * @param value the value to remove.
1176 * @param [options] the options to use:
1177 * [propertyIsArray] true if the property is always an array, false
1178 * if not (default: false).
1179 */
11802jsonld.removeValue = function(subject, property, value, options) {
11810 options = options || {};
11820 if(!('propertyIsArray' in options)) {
11830 options.propertyIsArray = false;
1184 }
1185
1186 // filter out value
11870 var values = jsonld.getValues(subject, property).filter(function(e) {
11880 return !jsonld.compareValues(e, value);
1189 });
1190
11910 if(values.length === 0) {
11920 jsonld.removeProperty(subject, property);
1193 }
11940 else if(values.length === 1 && !options.propertyIsArray) {
11950 subject[property] = values[0];
1196 }
1197 else {
11980 subject[property] = values;
1199 }
1200};
1201
1202/**
1203 * Compares two JSON-LD values for equality. Two JSON-LD values will be
1204 * considered equal if:
1205 *
1206 * 1. They are both primitives of the same type and value.
1207 * 2. They are both @values with the same @value, @type, @language,
1208 * and @index, OR
1209 * 3. They both have @ids they are the same.
1210 *
1211 * @param v1 the first value.
1212 * @param v2 the second value.
1213 *
1214 * @return true if v1 and v2 are considered equal, false if not.
1215 */
12162jsonld.compareValues = function(v1, v2) {
1217 // 1. equal primitives
1218631 if(v1 === v2) {
121935 return true;
1220 }
1221
1222 // 2. equal @values
1223596 if(_isValue(v1) && _isValue(v2) &&
1224 v1['@value'] === v2['@value'] &&
1225 v1['@type'] === v2['@type'] &&
1226 v1['@language'] === v2['@language'] &&
1227 v1['@index'] === v2['@index']) {
12282 return true;
1229 }
1230
1231 // 3. equal @ids
1232594 if(_isObject(v1) && ('@id' in v1) && _isObject(v2) && ('@id' in v2)) {
1233152 return v1['@id'] === v2['@id'];
1234 }
1235
1236442 return false;
1237};
1238
1239/**
1240 * Gets the value for the given active context key and type, null if none is
1241 * set.
1242 *
1243 * @param ctx the active context.
1244 * @param key the context key.
1245 * @param [type] the type of value to get (eg: '@id', '@type'), if not
1246 * specified gets the entire entry for a key, null if not found.
1247 *
1248 * @return the value.
1249 */
12502jsonld.getContextValue = function(ctx, key, type) {
12515451 var rval = null;
1252
1253 // return null for invalid key
12545451 if(key === null) {
125579 return rval;
1256 }
1257
1258 // get default language
12595372 if(type === '@language' && (type in ctx)) {
126073 rval = ctx[type];
1261 }
1262
1263 // get specific entry information
12645372 if(ctx.mappings[key]) {
12652184 var entry = ctx.mappings[key];
1266
1267 // return whole entry
12682184 if(_isUndefined(type)) {
12690 rval = entry;
1270 }
1271 // return entry value for type
12722184 else if(type in entry) {
1273755 rval = entry[type];
1274 }
1275 }
1276
12775372 return rval;
1278};
1279
1280/** Registered RDF dataset parsers hashed by content-type. */
12812var _rdfParsers = {};
1282
1283/**
1284 * Registers an RDF dataset parser by content-type, for use with
1285 * jsonld.fromRDF.
1286 *
1287 * @param contentType the content-type for the parser.
1288 * @param parser(input) the parser function (takes a string as a parameter
1289 * and returns an RDF dataset).
1290 */
12912jsonld.registerRDFParser = function(contentType, parser) {
12924 _rdfParsers[contentType] = parser;
1293};
1294
1295/**
1296 * Unregisters an RDF dataset parser by content-type.
1297 *
1298 * @param contentType the content-type for the parser.
1299 */
13002jsonld.unregisterRDFParser = function(contentType) {
13010 delete _rdfParsers[contentType];
1302};
1303
13042if(_nodejs) {
1305 // needed for serialization of XML literals
13062 if(typeof XMLSerializer === 'undefined') {
13072 var XMLSerializer = null;
1308 }
13092 if(typeof Node === 'undefined') {
13102 var Node = {
1311 ELEMENT_NODE: 1,
1312 ATTRIBUTE_NODE: 2,
1313 TEXT_NODE: 3,
1314 CDATA_SECTION_NODE: 4,
1315 ENTITY_REFERENCE_NODE: 5,
1316 ENTITY_NODE: 6,
1317 PROCESSING_INSTRUCTION_NODE: 7,
1318 COMMENT_NODE: 8,
1319 DOCUMENT_NODE: 9,
1320 DOCUMENT_TYPE_NODE: 10,
1321 DOCUMENT_FRAGMENT_NODE: 11,
1322 NOTATION_NODE:12
1323 };
1324 }
1325}
1326
1327// constants
13282var XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
13292var XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
13302var XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
13312var XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
1332
13332var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
13342var RDF_FIRST = RDF + 'first';
13352var RDF_REST = RDF + 'rest';
13362var RDF_NIL = RDF + 'nil';
13372var RDF_TYPE = RDF + 'type';
13382var RDF_PLAIN_LITERAL = RDF + 'PlainLiteral';
13392var RDF_XML_LITERAL = RDF + 'XMLLiteral';
13402var RDF_OBJECT = RDF + 'object';
13412var RDF_LANGSTRING = RDF + 'langString';
1342
13432var MAX_CONTEXT_URLS = 10;
1344
1345/**
1346 * A JSON-LD Error.
1347 *
1348 * @param msg the error message.
1349 * @param type the error type.
1350 * @param details the error details.
1351 */
13522var JsonLdError = function(msg, type, details) {
13530 if(_nodejs) {
13540 Error.call(this);
13550 Error.captureStackTrace(this, this.constructor);
1356 }
13570 this.name = type || 'jsonld.Error';
13580 this.message = msg || 'An unspecified JSON-LD error occurred.';
13590 this.details = details || {};
1360};
13612if(_nodejs) {
13622 require('util').inherits(JsonLdError, Error);
1363}
1364
1365/**
1366 * Constructs a new JSON-LD Processor.
1367 */
13682var Processor = function() {};
1369
1370/**
1371 * Recursively compacts an element using the given active context. All values
1372 * must be in expanded form before this method is called.
1373 *
1374 * @param activeCtx the active context to use.
1375 * @param activeProperty the compacted property associated with the element
1376 * to compact, null for none.
1377 * @param element the element to compact.
1378 * @param options the compaction options.
1379 *
1380 * @return the compacted value.
1381 */
13822Processor.prototype.compact = function(
1383 activeCtx, activeProperty, element, options) {
1384 // recursively compact array
1385677 if(_isArray(element)) {
1386115 var rval = [];
1387115 for(var i in element) {
1388 // compact, dropping any null values
1389204 var compacted = this.compact(
1390 activeCtx, activeProperty, element[i], options);
1391204 if(compacted !== null) {
1392204 rval.push(compacted);
1393 }
1394 }
1395115 if(options.compactArrays && rval.length === 1) {
1396 // use single element if no container is specified
139776 var container = jsonld.getContextValue(
1398 activeCtx, activeProperty, '@container');
139976 if(container === null) {
140076 rval = rval[0];
1401 }
1402 }
1403115 return rval;
1404 }
1405
1406 // recursively compact object
1407562 if(_isObject(element)) {
1408 // do value compaction on @values and subject references
1409555 if(_isValue(element) || _isSubjectReference(element)) {
1410361 return _compactValue(activeCtx, activeProperty, element);
1411 }
1412
1413 // FIXME: avoid misuse of active property as an expanded property?
1414194 var insideReverse = (activeProperty === '@reverse');
1415
1416 // process element keys in order
1417194 var keys = Object.keys(element).sort();
1418194 var rval = {};
1419194 for(var ki = 0; ki < keys.length; ++ki) {
1420549 var expandedProperty = keys[ki];
1421549 var expandedValue = element[expandedProperty];
1422
1423 // compact @id and @type(s)
1424549 if(expandedProperty === '@id' || expandedProperty === '@type') {
1425231 var compactedValue;
1426
1427 // compact single @id
1428231 if(_isString(expandedValue)) {
1429158 compactedValue = _compactIri(
1430 activeCtx, expandedValue, null,
1431 {vocab: (expandedProperty === '@type')});
1432 }
1433 // expanded value must be a @type array
1434 else {
143573 compactedValue = [];
143673 for(var vi = 0; vi < expandedValue.length; ++vi) {
143787 compactedValue.push(_compactIri(
1438 activeCtx, expandedValue[vi], null, {vocab: true}));
1439 }
1440 }
1441
1442 // use keyword alias and add value
1443231 var alias = _compactIri(activeCtx, expandedProperty);
1444231 var isArray = (_isArray(compactedValue) && expandedValue.length === 0);
1445231 jsonld.addValue(
1446 rval, alias, compactedValue, {propertyIsArray: isArray});
1447231 continue;
1448 }
1449
1450 // handle @reverse
1451318 if(expandedProperty === '@reverse') {
1452 // recursively compact expanded value
14538 var compactedValue = this.compact(
1454 activeCtx, '@reverse', expandedValue, options);
1455
1456 // handle double-reversed properties
14578 for(var compactedProperty in compactedValue) {
145810 if(activeCtx.mappings[compactedProperty] &&
1459 activeCtx.mappings[compactedProperty].reverse) {
14604 if(!(compactedProperty in rval) && !options.compactArrays) {
14610 rval[compactedProperty] = [];
1462 }
14634 jsonld.addValue(
1464 rval, compactedProperty, compactedValue[compactedProperty]);
14654 delete compactedValue[compactedProperty];
1466 }
1467 }
1468
14698 if(Object.keys(compactedValue).length > 0) {
1470 // use keyword alias and add value
14714 var alias = _compactIri(activeCtx, expandedProperty);
14724 jsonld.addValue(rval, alias, compactedValue);
1473 }
1474
14758 continue;
1476 }
1477
1478 // handle @index property
1479310 if(expandedProperty === '@index') {
1480 // drop @index if inside an @index container
148115 var container = jsonld.getContextValue(
1482 activeCtx, activeProperty, '@container');
148315 if(container === '@index') {
148414 continue;
1485 }
1486
1487 // use keyword alias and add value
14881 var alias = _compactIri(activeCtx, expandedProperty);
14891 jsonld.addValue(rval, alias, expandedValue);
14901 continue;
1491 }
1492
1493 // Note: expanded value must be an array due to expansion algorithm.
1494
1495 // preserve empty arrays
1496295 if(expandedValue.length === 0) {
14975 var itemActiveProperty = _compactIri(
1498 activeCtx, expandedProperty, expandedValue, {vocab: true},
1499 insideReverse);
15005 jsonld.addValue(
1501 rval, itemActiveProperty, expandedValue, {propertyIsArray: true});
1502 }
1503
1504 // recusively process array values
1505295 for(var vi = 0; vi < expandedValue.length; ++vi) {
1506379 var expandedItem = expandedValue[vi];
1507
1508 // compact property and get container type
1509379 var itemActiveProperty = _compactIri(
1510 activeCtx, expandedProperty, expandedItem, {vocab: true},
1511 insideReverse);
1512379 var container = jsonld.getContextValue(
1513 activeCtx, itemActiveProperty, '@container');
1514
1515 // get @list value if appropriate
1516379 var isList = _isList(expandedItem);
1517379 var list = null;
1518379 if(isList) {
151929 list = expandedItem['@list'];
1520 }
1521
1522 // recursively compact expanded item
1523379 var compactedItem = this.compact(
1524 activeCtx, itemActiveProperty, isList ? list : expandedItem, options);
1525
1526 // handle @list
1527379 if(isList) {
1528 // ensure @list value is an array
152929 if(!_isArray(compactedItem)) {
15306 compactedItem = [compactedItem];
1531 }
1532
153329 if(container !== '@list') {
1534 // wrap using @list alias
15359 var wrapper = {};
15369 wrapper[_compactIri(activeCtx, '@list')] = compactedItem;
15379 compactedItem = wrapper;
1538
1539 // include @index from expanded @list, if any
15409 if('@index' in expandedItem) {
15412 compactedItem[_compactIri(activeCtx, '@index')] =
1542 expandedItem['@index'];
1543 }
1544 }
1545 // can't use @list container for more than 1 list
154620 else if(itemActiveProperty in rval) {
15470 throw new JsonLdError(
1548 'JSON-LD compact error; property has a "@list" @container ' +
1549 'rule but there is more than a single @list that matches ' +
1550 'the compacted term in the document. Compaction might mix ' +
1551 'unwanted items into the list.',
1552 'jsonld.SyntaxError');
1553 }
1554 }
1555
1556 // handle language and index maps
1557379 if(container === '@language' || container === '@index') {
1558 // get or create the map object
155937 var mapObject;
156037 if(itemActiveProperty in rval) {
156128 mapObject = rval[itemActiveProperty];
1562 }
1563 else {
15649 rval[itemActiveProperty] = mapObject = {};
1565 }
1566
1567 // if container is a language map, simplify compacted value to
1568 // a simple string
156937 if(container === '@language' && _isValue(compactedItem)) {
15707 compactedItem = compactedItem['@value'];
1571 }
1572
1573 // add compact value to map object using key from expanded value
1574 // based on the container type
157537 jsonld.addValue(mapObject, expandedItem[container], compactedItem);
1576 }
1577 else {
1578 // use an array if: compactArrays flag is false,
1579 // @container is @set or @list , value is an empty
1580 // array, or key is @graph
1581342 var isArray = (!options.compactArrays || container === '@set' ||
1582 container === '@list' ||
1583 (_isArray(compactedItem) && compactedItem.length === 0) ||
1584 expandedProperty === '@list' || expandedProperty === '@graph');
1585
1586 // add compact value
1587342 jsonld.addValue(
1588 rval, itemActiveProperty, compactedItem,
1589 {propertyIsArray: isArray});
1590 }
1591 }
1592 }
1593
1594194 return rval;
1595 }
1596
1597 // only primitives remain which are already compact
15987 return element;
1599};
1600
1601/**
1602 * Recursively expands an element using the given context. Any context in
1603 * the element will be removed. All context URLs must have been retrieved
1604 * before calling this method.
1605 *
1606 * @param activeCtx the context to use.
1607 * @param activeProperty the property for the element, null for none.
1608 * @param element the element to expand.
1609 * @param options the expansion options.
1610 * @param insideList true if the element is a list, false if not.
1611 *
1612 * @return the expanded value.
1613 */
16142Processor.prototype.expand = function(
1615 activeCtx, activeProperty, element, options, insideList) {
16163705 var self = this;
1617
16183705 if(typeof element === 'undefined') {
16190 throw new JsonLdError(
1620 'Invalid JSON-LD syntax; undefined element.',
1621 'jsonld.SyntaxError');
1622 }
1623
1624 // nothing to expand
16253705 if(element === null) {
162636 return null;
1627 }
1628
1629 // recursively expand array
16303669 if(_isArray(element)) {
1631494 var rval = [];
1632494 for(var i in element) {
1633 // expand element
16341160 var e = self.expand(
1635 activeCtx, activeProperty, element[i], options, insideList);
16361160 if(insideList && (_isArray(e) || _isList(e))) {
1637 // lists of lists are illegal
16380 throw new JsonLdError(
1639 'Invalid JSON-LD syntax; lists of lists are not permitted.',
1640 'jsonld.SyntaxError');
1641 }
1642 // drop null values
16431160 else if(e !== null) {
16441103 if(_isArray(e)) {
164527 rval = rval.concat(e);
1646 }
1647 else {
16481076 rval.push(e);
1649 }
1650 }
1651 }
1652494 return rval;
1653 }
1654
1655 // recursively expand object
16563175 if(_isObject(element)) {
1657 // if element has a context, process it
16581109 if('@context' in element) {
1659240 activeCtx = self.processContext(activeCtx, element['@context'], options);
1660 }
1661
1662 // expand the active property
16631109 var expandedActiveProperty = _expandIri(
1664 activeCtx, activeProperty, {vocab: true});
1665
16661109 var rval = {};
16671109 var keys = Object.keys(element).sort();
16681109 for(var ki = 0; ki < keys.length; ++ki) {
16692509 var key = keys[ki];
16702509 var value = element[key];
16712509 var expandedValue;
1672
1673 // skip @context
16742509 if(key === '@context') {
1675240 continue;
1676 }
1677
1678 // expand key to IRI
16792269 var expandedProperty = _expandIri(activeCtx, key, {vocab: true});
1680
1681 // drop non-absolute IRI keys that aren't keywords
16822269 if(expandedProperty === null ||
1683 !(_isAbsoluteIri(expandedProperty) || _isKeyword(expandedProperty))) {
168430 continue;
1685 }
1686
16872239 if(_isKeyword(expandedProperty) &&
1688 expandedActiveProperty === '@reverse') {
16890 throw new JsonLdError(
1690 'Invalid JSON-LD syntax; a keyword cannot be used as a @reverse ' +
1691 'property.',
1692 'jsonld.SyntaxError', {value: value});
1693 }
1694
1695 // syntax error if @id is not a string
16962239 if(expandedProperty === '@id' && !_isString(value)) {
16970 throw new JsonLdError(
1698 'Invalid JSON-LD syntax; "@id" value must a string.',
1699 'jsonld.SyntaxError', {value: value});
1700 }
1701
1702 // validate @type value
17032239 if(expandedProperty === '@type') {
1704206 _validateTypeValue(value);
1705 }
1706
1707 // @graph must be an array or an object
17082239 if(expandedProperty === '@graph' &&
1709 !(_isObject(value) || _isArray(value))) {
17100 throw new JsonLdError(
1711 'Invalid JSON-LD syntax; "@value" value must not be an ' +
1712 'object or an array.',
1713 'jsonld.SyntaxError', {value: value});
1714 }
1715
1716 // @value must not be an object or an array
17172239 if(expandedProperty === '@value' &&
1718 (_isObject(value) || _isArray(value))) {
17190 throw new JsonLdError(
1720 'Invalid JSON-LD syntax; "@value" value must not be an ' +
1721 'object or an array.',
1722 'jsonld.SyntaxError', {value: value});
1723 }
1724
1725 // @language must be a string
17262239 if(expandedProperty === '@language') {
172760 if(!_isString(value)) {
17280 throw new JsonLdError(
1729 'Invalid JSON-LD syntax; "@language" value must be a string.',
1730 'jsonld.SyntaxError', {value: value});
1731 }
1732 // ensure language value is lowercase
173360 value = value.toLowerCase();
1734 }
1735
1736 // @index must be a string
17372239 if(expandedProperty === '@index') {
173852 if(!_isString(value)) {
17390 throw new JsonLdError(
1740 'Invalid JSON-LD syntax; "@index" value must be a string.',
1741 'jsonld.SyntaxError', {value: value});
1742 }
1743 }
1744
1745 // @reverse must be an object
17462239 if(expandedProperty === '@reverse') {
174715 if(!_isObject(value)) {
17480 throw new JsonLdError(
1749 'Invalid JSON-LD syntax; "@reverse" value must be an object.',
1750 'jsonld.SyntaxError', {value: value});
1751 }
1752
175315 expandedValue = self.expand(
1754 activeCtx, '@reverse', value, options, insideList);
1755
1756 // properties double-reversed
175715 if('@reverse' in expandedValue) {
17581 for(var property in expandedValue['@reverse']) {
17591 jsonld.addValue(
1760 rval, property, expandedValue['@reverse'][property],
1761 {propertyIsArray: true});
1762 }
1763 }
1764
1765 // FIXME: can this be merged with code below to simplify?
1766 // merge in all reversed properties
176715 var reverseMap = rval['@reverse'] || null;
176815 for(var property in expandedValue) {
176917 if(property === '@reverse') {
17701 continue;
1771 }
177216 if(reverseMap === null) {
177314 reverseMap = rval['@reverse'] = {};
1774 }
177516 jsonld.addValue(reverseMap, property, [], {propertyIsArray: true});
177616 var items = expandedValue[property];
177716 for(var ii = 0; ii < items.length; ++ii) {
177822 var item = items[ii];
177922 if(_isValue(item) || _isList(item)) {
17800 throw new JsonLdError(
1781 'Invalid JSON-LD syntax; "@reverse" value must not be a ' +
1782 '@value or an @list.',
1783 'jsonld.SyntaxError', {value: expandedValue});
1784 }
178522 jsonld.addValue(
1786 reverseMap, property, item, {propertyIsArray: true});
1787 }
1788 }
1789
179015 continue;
1791 }
1792
17932224 var container = jsonld.getContextValue(activeCtx, key, '@container');
1794
1795 // handle language map container (skip if value is not an object)
17962224 if(container === '@language' && _isObject(value)) {
17974 expandedValue = _expandLanguageMap(value);
1798 }
1799 // handle index container (skip if value is not an object)
18002220 else if(container === '@index' && _isObject(value)) {
18017 expandedValue = (function _expandIndexMap(activeProperty) {
18027 var rval = [];
18037 var keys = Object.keys(value).sort();
18047 for(var ki = 0; ki < keys.length; ++ki) {
180514 var key = keys[ki];
180614 var val = value[key];
180714 if(!_isArray(val)) {
18085 val = [val];
1809 }
181014 val = self.expand(activeCtx, activeProperty, val, options, false);
181114 for(var vi = 0; vi < val.length; ++vi) {
181243 var item = val[vi];
181343 if(!('@index' in item)) {
181439 item['@index'] = key;
1815 }
181643 rval.push(item);
1817 }
1818 }
18197 return rval;
1820 })(key);
1821 }
1822 else {
1823 // recurse into @list or @set
18242213 var isList = (expandedProperty === '@list');
18252213 if(isList || expandedProperty === '@set') {
182680 var nextActiveProperty = activeProperty;
182780 if(isList && expandedActiveProperty === '@graph') {
18282 nextActiveProperty = null;
1829 }
183080 expandedValue = self.expand(
1831 activeCtx, nextActiveProperty, value, options, isList);
183280 if(isList && _isList(expandedValue)) {
18330 throw new JsonLdError(
1834 'Invalid JSON-LD syntax; lists of lists are not permitted.',
1835 'jsonld.SyntaxError');
1836 }
1837 }
1838 else {
1839 // recursively expand value with key as new active property
18402133 expandedValue = self.expand(activeCtx, key, value, options, false);
1841 }
1842 }
1843
1844 // drop null values if property is not @value
18452224 if(expandedValue === null && expandedProperty !== '@value') {
18469 continue;
1847 }
1848
1849 // convert expanded value to @list if container specifies it
18502215 if(expandedProperty !== '@list' && !_isList(expandedValue) &&
1851 container === '@list') {
1852 // ensure expanded value is an array
185320 expandedValue = (_isArray(expandedValue) ?
1854 expandedValue : [expandedValue]);
185520 expandedValue = {'@list': expandedValue};
1856 }
1857
1858 // FIXME: can this be merged with code above to simplify?
1859 // merge in reverse properties
18602215 if(activeCtx.mappings[key] && activeCtx.mappings[key].reverse) {
18616 var reverseMap = rval['@reverse'] = {};
18626 if(!_isArray(expandedValue)) {
18631 expandedValue = [expandedValue];
1864 }
18656 for(var ii = 0; ii < expandedValue.length; ++ii) {
186610 var item = expandedValue[ii];
186710 if(_isValue(item) || _isList(item)) {
18680 throw new JsonLdError(
1869 'Invalid JSON-LD syntax; "@reverse" value must not be a ' +
1870 '@value or an @list.',
1871 'jsonld.SyntaxError', {value: expandedValue});
1872 }
187310 jsonld.addValue(
1874 reverseMap, expandedProperty, item, {propertyIsArray: true});
1875 }
18766 continue;
1877 }
1878
1879 // add value for property
1880 // use an array except for certain keywords
18812209 var useArray =
1882 ['@index', '@id', '@type', '@value', '@language'].indexOf(
1883 expandedProperty) === -1;
18842209 jsonld.addValue(
1885 rval, expandedProperty, expandedValue, {propertyIsArray: useArray});
1886 }
1887
1888 // get property count on expanded output
18891109 keys = Object.keys(rval);
18901109 var count = keys.length;
1891
18921109 if('@value' in rval) {
1893 // @value must only have @language or @type
1894184 if('@type' in rval && '@language' in rval) {
18950 throw new JsonLdError(
1896 'Invalid JSON-LD syntax; an element containing "@value" may not ' +
1897 'contain both "@type" and "@language".',
1898 'jsonld.SyntaxError', {element: rval});
1899 }
1900184 var validCount = count - 1;
1901184 if('@type' in rval) {
190257 validCount -= 1;
1903 }
1904184 if('@index' in rval) {
190535 validCount -= 1;
1906 }
1907184 if('@language' in rval) {
190858 validCount -= 1;
1909 }
1910184 if(validCount !== 0) {
19110 throw new JsonLdError(
1912 'Invalid JSON-LD syntax; an element containing "@value" may only ' +
1913 'have an "@index" property and at most one other property ' +
1914 'which can be "@type" or "@language".',
1915 'jsonld.SyntaxError', {element: rval});
1916 }
1917 // drop null @values
1918184 if(rval['@value'] === null) {
19198 rval = null;
1920 }
1921 // drop @language if @value isn't a string
1922176 else if('@language' in rval && !_isString(rval['@value'])) {
19230 delete rval['@language'];
1924 }
1925 }
1926 // convert @type to an array
1927925 else if('@type' in rval && !_isArray(rval['@type'])) {
1928132 rval['@type'] = [rval['@type']];
1929 }
1930 // handle @set and @list
1931793 else if('@set' in rval || '@list' in rval) {
193280 if(count > 1 && (count !== 2 && '@index' in rval)) {
19330 throw new JsonLdError(
1934 'Invalid JSON-LD syntax; if an element has the property "@set" ' +
1935 'or "@list", then it can have at most one other property that is ' +
1936 '"@index".',
1937 'jsonld.SyntaxError', {element: rval});
1938 }
1939 // optimize away @set
194080 if('@set' in rval) {
194134 rval = rval['@set'];
194234 keys = Object.keys(rval);
194334 count = keys.length;
1944 }
1945 }
1946 // drop objects with only @language
1947713 else if(count === 1 && '@language' in rval) {
19482 rval = null;
1949 }
1950
1951 // drop certain top-level objects that do not occur in lists
19521109 if(_isObject(rval) &&
1953 !options.keepFreeFloatingNodes && !insideList &&
1954 (activeProperty === null || expandedActiveProperty === '@graph')) {
1955 // drop empty object or top-level @value
1956570 if(count === 0 || ('@value' in rval)) {
19576 rval = null;
1958 }
1959 else {
1960 // drop nodes that generate no triples
1961564 var hasTriples = false;
1962564 var ignore = ['@graph', '@type'];
1963564 for(var ki = 0; !hasTriples && ki < keys.length; ++ki) {
1964990 if(!_isKeyword(keys[ki]) || ignore.indexOf(keys[ki]) !== -1) {
1965538 hasTriples = true;
1966 }
1967 }
1968564 if(!hasTriples) {
196926 rval = null;
1970 }
1971 }
1972 }
1973
19741109 return rval;
1975 }
1976
1977 // drop top-level scalars that are not in lists
19782066 if(!insideList &&
1979 (activeProperty === null ||
1980 _expandIri(activeCtx, activeProperty, {vocab: true}) === '@graph')) {
19817 return null;
1982 }
1983
1984 // expand element according to value expansion rules
19852059 return _expandValue(activeCtx, activeProperty, element);
1986};
1987
1988/**
1989 * Performs JSON-LD flattening.
1990 *
1991 * @param input the expanded JSON-LD to flatten.
1992 *
1993 * @return the flattened output.
1994 */
19952Processor.prototype.flatten = function(input) {
1996 // produce a map of all subjects and name each bnode
199740 var namer = new UniqueNamer('_:b');
199840 var graphs = {'@default': {}};
199940 _createNodeMap(input, graphs, '@default', namer);
2000
2001 // add all non-default graphs to default graph
200240 var defaultGraph = graphs['@default'];
200340 var graphNames = Object.keys(graphs).sort();
200440 for(var i = 0; i < graphNames.length; ++i) {
200543 var graphName = graphNames[i];
200643 if(graphName === '@default') {
200740 continue;
2008 }
20093 var nodeMap = graphs[graphName];
20103 var subject = defaultGraph[graphName];
20113 if(!subject) {
20121 defaultGraph[graphName] = subject = {
2013 '@id': graphName,
2014 '@graph': []
2015 };
2016 }
20172 else if(!('@graph' in subject)) {
20182 subject['@graph'] = [];
2019 }
20203 var graph = subject['@graph'];
20213 var ids = Object.keys(nodeMap).sort();
20223 for(var ii = 0; ii < ids.length; ++ii) {
202311 var id = ids[ii];
202411 graph.push(nodeMap[id]);
2025 }
2026 }
2027
2028 // produce flattened output
202940 var flattened = [];
203040 var keys = Object.keys(defaultGraph).sort();
203140 for(var ki = 0; ki < keys.length; ++ki) {
2032122 var key = keys[ki];
2033122 flattened.push(defaultGraph[key]);
2034 }
203540 return flattened;
2036};
2037
2038/**
2039 * Performs JSON-LD framing.
2040 *
2041 * @param input the expanded JSON-LD to frame.
2042 * @param frame the expanded JSON-LD frame to use.
2043 * @param options the framing options.
2044 *
2045 * @return the framed output.
2046 */
20472Processor.prototype.frame = function(input, frame, options) {
2048 // create framing state
204921 var state = {
2050 options: options,
2051 graphs: {'@default': {}, '@merged': {}}
2052 };
2053
2054 // produce a map of all graphs and name each bnode
2055 // FIXME: currently uses subjects from @merged graph only
205621 namer = new UniqueNamer('_:b');
205721 _createNodeMap(input, state.graphs, '@merged', namer);
205821 state.subjects = state.graphs['@merged'];
2059
2060 // frame the subjects
206121 var framed = [];
206221 _frame(state, Object.keys(state.subjects).sort(), frame, framed, null);
206321 return framed;
2064};
2065
2066/**
2067 * Performs normalization on the given RDF dataset.
2068 *
2069 * @param dataset the RDF dataset to normalize.
2070 * @param options the normalization options.
2071 * @param callback(err, normalized) called once the operation completes.
2072 */
20732Processor.prototype.normalize = function(dataset, options, callback) {
2074 // create quads and map bnodes to their associated quads
207557 var quads = [];
207657 var bnodes = {};
207757 for(var graphName in dataset) {
207858 var triples = dataset[graphName];
207958 if(graphName === '@default') {
208057 graphName = null;
2081 }
208258 for(var ti = 0; ti < triples.length; ++ti) {
2083306 var quad = triples[ti];
2084306 if(graphName !== null) {
20852 if(graphName.indexOf('_:') === 0) {
20862 quad.name = {type: 'blank node', value: graphName};
2087 }
2088 else {
20890 quad.name = {type: 'IRI', value: graphName};
2090 }
2091 }
2092306 quads.push(quad);
2093
2094306 var attrs = ['subject', 'object', 'name'];
2095306 for(var ai = 0; ai < attrs.length; ++ai) {
2096918 var attr = attrs[ai];
2097918 if(quad[attr] && quad[attr].type === 'blank node') {
2098491 var id = quad[attr].value;
2099491 if(id in bnodes) {
2100325 bnodes[id].quads.push(quad);
2101 }
2102 else {
2103166 bnodes[id] = {quads: [quad]};
2104 }
2105 }
2106 }
2107 }
2108 }
2109
2110 // mapping complete, start canonical naming
211157 var namer = new UniqueNamer('_:c14n');
211257 return hashBlankNodes(Object.keys(bnodes));
2113
2114 // generates unique and duplicate hashes for bnodes
21150 function hashBlankNodes(unnamed) {
211677 var nextUnnamed = [];
211777 var duplicates = {};
211877 var unique = {};
2119
2120 // hash quads for each unnamed bnode
2121154 jsonld.nextTick(function() {hashUnnamed(0);});
212277 function hashUnnamed(i) {
2123269 if(i === unnamed.length) {
2124 // done, name blank nodes
212577 return nameBlankNodes(unique, duplicates, nextUnnamed);
2126 }
2127
2128 // hash unnamed bnode
2129192 var bnode = unnamed[i];
2130192 var hash = _hashQuads(bnode, bnodes, namer);
2131
2132 // store hash as unique or a duplicate
2133192 if(hash in duplicates) {
213457 duplicates[hash].push(bnode);
213557 nextUnnamed.push(bnode);
2136 }
2137135 else if(hash in unique) {
213848 duplicates[hash] = [unique[hash], bnode];
213948 nextUnnamed.push(unique[hash]);
214048 nextUnnamed.push(bnode);
214148 delete unique[hash];
2142 }
2143 else {
214487 unique[hash] = bnode;
2145 }
2146
2147 // hash next unnamed bnode
2148384 jsonld.nextTick(function() {hashUnnamed(i + 1);});
2149 }
2150 }
2151
2152 // names unique hash bnodes
21530 function nameBlankNodes(unique, duplicates, unnamed) {
2154 // name unique bnodes in sorted hash order
215577 var named = false;
215677 var hashes = Object.keys(unique).sort();
215777 for(var i = 0; i < hashes.length; ++i) {
215839 var bnode = unique[hashes[i]];
215939 namer.getName(bnode);
216039 named = true;
2161 }
2162
2163 // continue to hash bnodes if a bnode was assigned a name
216477 if(named) {
216520 hashBlankNodes(unnamed);
2166 }
2167 // name the duplicate hash bnodes
2168 else {
216957 nameDuplicates(duplicates);
2170 }
2171 }
2172
2173 // names duplicate hash bnodes
21740 function nameDuplicates(duplicates) {
2175 // enumerate duplicate hash groups in sorted order
217657 var hashes = Object.keys(duplicates).sort();
2177
2178 // process each group
217957 processGroup(0);
218057 function processGroup(i) {
218197 if(i === hashes.length) {
2182 // done, create JSON-LD array
218357 return createArray();
2184 }
2185
2186 // name each group member
218740 var group = duplicates[hashes[i]];
218840 var results = [];
218940 nameGroupMember(group, 0);
219040 function nameGroupMember(group, n) {
2191167 if(n === group.length) {
2192 // name bnodes in hash order
219340 results.sort(function(a, b) {
219479 a = a.hash;
219579 b = b.hash;
219679 return (a < b) ? -1 : ((a > b) ? 1 : 0);
2197 });
219840 for(var r in results) {
2199 // name all bnodes in path namer in key-entry order
2200 // Note: key-order is preserved in javascript
220190 for(var key in results[r].pathNamer.existing) {
2202353 namer.getName(key);
2203 }
2204 }
220540 return processGroup(i + 1);
2206 }
2207
2208 // skip already-named bnodes
2209127 var bnode = group[n];
2210127 if(namer.isNamed(bnode)) {
221137 return nameGroupMember(group, n + 1);
2212 }
2213
2214 // hash bnode paths
221590 var pathNamer = new UniqueNamer('_:b');
221690 pathNamer.getName(bnode);
221790 _hashPaths(bnode, bnodes, namer, pathNamer,
2218 function(err, result) {
221990 if(err) {
22200 return callback(err);
2221 }
222290 results.push(result);
222390 nameGroupMember(group, n + 1);
2224 });
2225 }
2226 }
2227 }
2228
2229 // creates the sorted array of RDF quads
22300 function createArray() {
223157 var normalized = [];
2232
2233 /* Note: At this point all bnodes in the set of RDF quads have been
2234 assigned canonical names, which have been stored in the 'namer' object.
2235 Here each quad is updated by assigning each of its bnodes its new name
2236 via the 'namer' object. */
2237
2238 // update bnode names in each quad and serialize
223957 for(var i = 0; i < quads.length; ++i) {
2240306 var quad = quads[i];
2241306 var attrs = ['subject', 'object', 'name'];
2242306 for(var ai = 0; ai < attrs.length; ++ai) {
2243918 var attr = attrs[ai];
2244918 if(quad[attr] && quad[attr].type === 'blank node' &&
2245 quad[attr].value.indexOf('_:c14n') !== 0) {
2246479 quad[attr].value = namer.getName(quad[attr].value);
2247 }
2248 }
2249306 normalized.push(_toNQuad(quad, quad.name ? quad.name.value : null));
2250 }
2251
2252 // sort normalized output
225357 normalized.sort();
2254
2255 // handle output format
225657 if(options.format) {
225757 if(options.format === 'application/nquads') {
225857 return callback(null, normalized.join(''));
2259 }
22600 return callback(new JsonLdError(
2261 'Unknown output format.',
2262 'jsonld.UnknownFormat', {format: options.format}));
2263 }
2264
2265 // output RDF dataset
22660 callback(null, _parseNQuads(normalized.join('')));
2267 }
2268};
2269
2270/**
2271 * Converts an RDF dataset to JSON-LD.
2272 *
2273 * @param dataset the RDF dataset.
2274 * @param options the RDF conversion options.
2275 * @param callback(err, output) called once the operation completes.
2276 */
22772Processor.prototype.fromRDF = function(dataset, options, callback) {
2278 // prepare graph map (maps graph name => subjects, lists)
22797 var defaultGraph = {subjects: {}, listMap: {}};
22807 var graphs = {'@default': defaultGraph};
2281
22827 for(var graphName in dataset) {
228311 var triples = dataset[graphName];
228411 for(var ti = 0; ti < triples.length; ++ti) {
228554 var triple = triples[ti];
2286
2287 // get subject, predicate, object
228854 var s = triple.subject.value;
228954 var p = triple.predicate.value;
229054 var o = triple.object;
2291
2292 // create a graph entry as needed
229354 var graph;
229454 if(!(graphName in graphs)) {
22955 graph = graphs[graphName] = {subjects: {}, listMap: {}};
2296 }
2297 else {
229849 graph = graphs[graphName];
2299 }
2300
2301 // handle element in @list
230254 if(p === RDF_FIRST) {
2303 // create list entry as needed
23049 var listMap = graph.listMap;
23059 var entry;
23069 if(!(s in listMap)) {
23077 entry = listMap[s] = {};
2308 }
2309 else {
23102 entry = listMap[s];
2311 }
2312 // set object value
23139 entry.first = _RDFToObject(o, options.useNativeTypes);
23149 continue;
2315 }
2316
2317 // handle other element in @list
231845 if(p === RDF_REST) {
2319 // set next in list
23209 if(o.type === 'blank node') {
2321 // create list entry as needed
23224 var listMap = graph.listMap;
23234 var entry;
23244 if(!(s in listMap)) {
23250 entry = listMap[s] = {};
2326 }
2327 else {
23284 entry = listMap[s];
2329 }
23304 entry.rest = o.value;
2331 }
23329 continue;
2333 }
2334
2335 // add graph subject to default graph as needed
233636 if(graphName !== '@default' && !(graphName in defaultGraph.subjects)) {
23374 defaultGraph.subjects[graphName] = {'@id': graphName};
2338 }
2339
2340 // add subject to graph as needed
234136 var subjects = graph.subjects;
234236 var value;
234336 if(!(s in subjects)) {
234412 value = subjects[s] = {'@id': s};
2345 }
2346 // use existing subject value
2347 else {
234824 value = subjects[s];
2349 }
2350
2351 // convert to @type unless options indicate to treat rdf:type as property
235236 if(p === RDF_TYPE && !options.useRdfType) {
2353 // add value of object as @type
235411 jsonld.addValue(value, '@type', o.value, {propertyIsArray: true});
2355 }
2356 else {
2357 // add property to value as needed
235825 var object = _RDFToObject(o, options.useNativeTypes);
235925 jsonld.addValue(value, p, object, {propertyIsArray: true});
2360
2361 // a bnode might be the beginning of a list, so add it to the list map
236225 if(o.type === 'blank node') {
23636 var id = object['@id'];
23646 var listMap = graph.listMap;
23656 var entry;
23666 if(!(id in listMap)) {
23673 entry = listMap[id] = {};
2368 }
2369 else {
23703 entry = listMap[id];
2371 }
23726 entry.head = object;
2373 }
2374 }
2375 }
2376 }
2377
2378 // build @lists
23797 for(var graphName in graphs) {
238012 var graph = graphs[graphName];
2381
2382 // find list head
238312 var listMap = graph.listMap;
238412 for(var subject in listMap) {
238510 var entry = listMap[subject];
2386
2387 // head found, build lists
238810 if('head' in entry && 'first' in entry) {
2389 // replace bnode @id with @list
23905 delete entry.head['@id'];
23915 var list = entry.head['@list'] = [entry.first];
23925 while('rest' in entry) {
23934 var rest = entry.rest;
23944 entry = listMap[rest];
23954 if(!('first' in entry)) {
23960 throw new JsonLdError(
2397 'Invalid RDF list entry.',
2398 'jsonld.RdfError', {bnode: rest});
2399 }
24004 list.push(entry.first);
2401 }
2402 }
2403 }
2404 }
2405
2406 // build default graph in subject @id order
24077 var output = [];
24087 var subjects = defaultGraph.subjects;
24097 var ids = Object.keys(subjects).sort();
24107 for(var i = 0; i < ids.length; ++i) {
241111 var id = ids[i];
2412
2413 // add subject to default graph
241411 var subject = subjects[id];
241511 output.push(subject);
2416
2417 // output named graph in subject @id order
241811 if(id in graphs) {
24195 var graph = subject['@graph'] = [];
24205 var subjects_ = graphs[id].subjects;
24215 var ids_ = Object.keys(subjects_).sort();
24225 for(var i_ = 0; i_ < ids_.length; ++i_) {
24235 graph.push(subjects_[ids_[i_]]);
2424 }
2425 }
2426 }
24277 callback(null, output);
2428};
2429
2430/**
2431 * Adds RDF triples for each graph in the given node map to an RDF dataset.
2432 *
2433 * @param nodeMap the node map.
2434 *
2435 * @return the RDF dataset.
2436 */
24372Processor.prototype.toRDF = function(nodeMap) {
243887 var namer = new UniqueNamer('_:b');
243987 var dataset = {};
244087 for(var graphName in nodeMap) {
244194 var graph = nodeMap[graphName];
244294 if(graphName.indexOf('_:') === 0) {
24431 graphName = namer.getName(graphName);
2444 }
244594 dataset[graphName] = _graphToRDF(graph, namer);
2446 }
244787 return dataset;
2448};
2449
2450/**
2451 * Processes a local context and returns a new active context.
2452 *
2453 * @param activeCtx the current active context.
2454 * @param localCtx the local context to process.
2455 * @param options the context processing options.
2456 *
2457 * @return the new active context.
2458 */
24592Processor.prototype.processContext = function(activeCtx, localCtx, options) {
2460326 var rval = null;
2461
2462 // get context from cache if available
2463326 if(jsonld.cache.activeCtx) {
2464326 rval = jsonld.cache.activeCtx.get(activeCtx, localCtx);
2465326 if(rval) {
24669 return rval;
2467 }
2468 }
2469
2470 // initialize the resulting context
2471317 rval = activeCtx.clone();
2472
2473 // normalize local context to an array of @context objects
2474317 if(_isObject(localCtx) && '@context' in localCtx &&
2475 _isArray(localCtx['@context'])) {
24761 localCtx = localCtx['@context'];
2477 }
2478317 var ctxs = _isArray(localCtx) ? localCtx : [localCtx];
2479
2480 // process each context in order
2481317 for(var i in ctxs) {
2482321 var ctx = ctxs[i];
2483
2484 // reset to initial context, keeping namer
2485321 if(ctx === null) {
24863 rval = _getInitialContext(options);
24873 continue;
2488 }
2489
2490 // dereference @context key if present
2491318 if(_isObject(ctx) && '@context' in ctx) {
249285 ctx = ctx['@context'];
2493 }
2494
2495 // context must be an object by now, all URLs retrieved before this call
2496318 if(!_isObject(ctx)) {
24970 throw new JsonLdError(
2498 'Invalid JSON-LD syntax; @context must be an object.',
2499 'jsonld.SyntaxError', {context: ctx});
2500 }
2501
2502 // define context mappings for keys in local context
2503318 var defined = {};
2504
2505 // handle @base
2506318 if('@base' in ctx) {
25073 var base = ctx['@base'];
2508
2509 // reset base
25103 if(base === null) {
25111 base = options.base;
2512 }
25132 else if(!_isString(base)) {
25140 throw new JsonLdError(
2515 'Invalid JSON-LD syntax; the value of "@base" in a ' +
2516 '@context must be a string or null.',
2517 'jsonld.SyntaxError', {context: ctx});
2518 }
25192 else if(base !== '' && !_isAbsoluteIri(base)) {
25200 throw new JsonLdError(
2521 'Invalid JSON-LD syntax; the value of "@base" in a ' +
2522 '@context must be an absolute IRI or the empty string.',
2523 'jsonld.SyntaxError', {context: ctx});
2524 }
2525
25263 base = jsonld.url.parse(base || '');
25273 rval['@base'] = base;
25283 defined['@base'] = true;
2529 }
2530
2531 // handle @vocab
2532318 if('@vocab' in ctx) {
253324 var value = ctx['@vocab'];
253424 if(value === null) {
25351 delete rval['@vocab'];
2536 }
253723 else if(!_isString(value)) {
25380 throw new JsonLdError(
2539 'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
2540 '@context must be a string or null.',
2541 'jsonld.SyntaxError', {context: ctx});
2542 }
254323 else if(!_isAbsoluteIri(value)) {
25440 throw new JsonLdError(
2545 'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
2546 '@context must be an absolute IRI.',
2547 'jsonld.SyntaxError', {context: ctx});
2548 }
2549 else {
255023 rval['@vocab'] = value;
2551 }
255224 defined['@vocab'] = true;
2553 }
2554
2555 // handle @language
2556318 if('@language' in ctx) {
255713 var value = ctx['@language'];
255813 if(value === null) {
25591 delete rval['@language'];
2560 }
256112 else if(!_isString(value)) {
25620 throw new JsonLdError(
2563 'Invalid JSON-LD syntax; the value of "@language" in a ' +
2564 '@context must be a string or null.',
2565 'jsonld.SyntaxError', {context: ctx});
2566 }
2567 else {
256812 rval['@language'] = value.toLowerCase();
2569 }
257013 defined['@language'] = true;
2571 }
2572
2573 // process all other keys
2574318 for(var key in ctx) {
2575972 _createTermDefinition(rval, ctx, key, defined);
2576 }
2577 }
2578
2579 // cache result
2580317 if(jsonld.cache.activeCtx) {
2581317 jsonld.cache.activeCtx.set(activeCtx, localCtx, rval);
2582 }
2583
2584317 return rval;
2585};
2586
2587/**
2588 * Expands a language map.
2589 *
2590 * @param languageMap the language map to expand.
2591 *
2592 * @return the expanded language map.
2593 */
25942function _expandLanguageMap(languageMap) {
25954 var rval = [];
25964 var keys = Object.keys(languageMap).sort();
25974 for(var ki = 0; ki < keys.length; ++ki) {
25988 var key = keys[ki];
25998 var val = languageMap[key];
26008 if(!_isArray(val)) {
26014 val = [val];
2602 }
26038 for(var vi = 0; vi < val.length; ++vi) {
260412 var item = val[vi];
260512 if(!_isString(item)) {
26060 throw new JsonLdError(
2607 'Invalid JSON-LD syntax; language map values must be strings.',
2608 'jsonld.SyntaxError', {languageMap: languageMap});
2609 }
261012 rval.push({
2611 '@value': item,
2612 '@language': key.toLowerCase()
2613 });
2614 }
2615 }
26164 return rval;
2617}
2618
2619/**
2620 * Labels the blank nodes in the given value using the given UniqueNamer.
2621 *
2622 * @param namer the UniqueNamer to use.
2623 * @param element the element with blank nodes to rename.
2624 *
2625 * @return the element.
2626 */
26272function _labelBlankNodes(namer, element) {
26280 if(_isArray(element)) {
26290 for(var i = 0; i < element.length; ++i) {
26300 element[i] = _labelBlankNodes(namer, element[i]);
2631 }
2632 }
26330 else if(_isList(element)) {
26340 element['@list'] = _labelBlankNodes(namer, element['@list']);
2635 }
26360 else if(_isObject(element)) {
2637 // rename blank node
26380 if(_isBlankNode(element)) {
26390 element['@id'] = namer.getName(element['@id']);
2640 }
2641
2642 // recursively apply to all keys
26430 var keys = Object.keys(element).sort();
26440 for(var ki = 0; ki < keys.length; ++ki) {
26450 var key = keys[ki];
26460 if(key !== '@id') {
26470 element[key] = _labelBlankNodes(namer, element[key]);
2648 }
2649 }
2650 }
2651
26520 return element;
2653}
2654
2655/**
2656 * Expands the given value by using the coercion and keyword rules in the
2657 * given context.
2658 *
2659 * @param activeCtx the active context to use.
2660 * @param activeProperty the active property the value is associated with.
2661 * @param value the value to expand.
2662 *
2663 * @return the expanded value.
2664 */
26652function _expandValue(activeCtx, activeProperty, value) {
2666 // nothing to expand
26672059 if(value === null) {
26680 return null;
2669 }
2670
2671 // special-case expand @id and @type (skips '@id' expansion)
26722059 var expandedProperty = _expandIri(activeCtx, activeProperty, {vocab: true});
26732059 if(expandedProperty === '@id') {
2674631 return _expandIri(activeCtx, value, {base: true});
2675 }
26761428 else if(expandedProperty === '@type') {
2677270 return _expandIri(activeCtx, value, {vocab: true, base: true});
2678 }
2679
2680 // get type definition from context
26811158 var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
2682
2683 // do @id expansion (automatic for @graph)
26841158 if(type === '@id' || (expandedProperty === '@graph' && _isString(value))) {
2685339 return {'@id': _expandIri(activeCtx, value, {base: true})};
2686 }
2687 // do @id expansion w/vocab
2688819 if(type === '@vocab') {
268910 return {'@id': _expandIri(activeCtx, value, {vocab: true, base: true})};
2690 }
2691
2692 // do not expand keyword values
2693809 if(_isKeyword(expandedProperty)) {
2694302 return value;
2695 }
2696
2697507 rval = {};
2698
2699 // other type
2700507 if(type !== null) {
270138 rval['@type'] = type;
2702 }
2703 // check for language tagging for strings
2704469 else if(_isString(value)) {
2705369 var language = jsonld.getContextValue(
2706 activeCtx, activeProperty, '@language');
2707369 if(language !== null) {
270811 rval['@language'] = language;
2709 }
2710 }
2711507 rval['@value'] = value;
2712
2713507 return rval;
2714}
2715
2716/**
2717 * Creates an array of RDF triples for the given graph.
2718 *
2719 * @param graph the graph to create RDF triples for.
2720 * @param namer a UniqueNamer for assigning blank node names.
2721 *
2722 * @return the array of RDF triples for the given graph.
2723 */
27242function _graphToRDF(graph, namer) {
272594 var rval = [];
2726
272794 for(var id in graph) {
2728274 var node = graph[id];
2729274 for(var property in node) {
2730537 var items = node[property];
2731537 if(property === '@type') {
273221 property = RDF_TYPE;
2733 }
2734516 else if(_isKeyword(property)) {
2735274 continue;
2736 }
2737
2738263 for(var i = 0; i < items.length; ++i) {
2739352 var item = items[i];
2740
2741 // RDF subject
2742352 var subject = {};
2743352 if(id.indexOf('_:') === 0) {
2744256 subject.type = 'blank node';
2745256 subject.value = namer.getName(id);
2746 }
2747 else {
274896 subject.type = 'IRI';
274996 subject.value = id;
2750 }
2751
2752 // RDF predicate
2753352 var predicate = {type: 'IRI'};
2754352 predicate.value = property;
2755
2756 // convert @list to triples
2757352 if(_isList(item)) {
27586 _listToRDF(item['@list'], namer, subject, predicate, rval);
2759 }
2760 // convert value or node object to triple
2761 else {
2762346 var object = _objectToRDF(item, namer);
2763346 rval.push({subject: subject, predicate: predicate, object: object});
2764 }
2765 }
2766 }
2767 }
2768
276994 return rval;
2770}
2771
2772/**
2773 * Converts a @list value into linked list of blank node RDF triples
2774 * (an RDF collection).
2775 *
2776 * @param list the @list value.
2777 * @param namer a UniqueNamer for assigning blank node names.
2778 * @param subject the subject for the head of the list.
2779 * @param predicate the predicate for the head of the list.
2780 * @param triples the array of triples to append to.
2781 */
27822function _listToRDF(list, namer, subject, predicate, triples) {
27836 var first = {type: 'IRI', value: RDF_FIRST};
27846 var rest = {type: 'IRI', value: RDF_REST};
27856 var nil = {type: 'IRI', value: RDF_NIL};
2786
27876 for(var i = 0; i < list.length; ++i) {
278810 var item = list[i];
2789
279010 var blankNode = {type: 'blank node', value: namer.getName()};
279110 triples.push({subject: subject, predicate: predicate, object: blankNode});
2792
279310 subject = blankNode;
279410 predicate = first;
279510 var object = _objectToRDF(item, namer);
279610 triples.push({subject: subject, predicate: predicate, object: object});
2797
279810 predicate = rest;
2799 }
2800
28016 triples.push({subject: subject, predicate: predicate, object: nil});
2802}
2803
2804/**
2805 * Converts a JSON-LD value object to an RDF literal or a JSON-LD string or
2806 * node object to an RDF resource.
2807 *
2808 * @param item the JSON-LD value or node object.
2809 * @param namer the UniqueNamer to use to assign blank node names.
2810 *
2811 * @return the RDF literal or RDF resource.
2812 */
28132function _objectToRDF(item, namer) {
2814356 var object = {};
2815
2816 // convert value object to RDF
2817356 if(_isValue(item)) {
281877 object.type = 'literal';
281977 var value = item['@value'];
282077 var datatype = item['@type'] || null;
2821
2822 // convert to XSD datatypes as appropriate
282377 if(_isBoolean(value)) {
28242 object.value = value.toString();
28252 object.datatype = datatype || XSD_BOOLEAN;
2826 }
282775 else if(_isDouble(value)) {
2828 // canonical double representation
28292 object.value = value.toExponential(15).replace(/(\d)0*e\+?/, '$1E');
28302 object.datatype = datatype || XSD_DOUBLE;
2831 }
283273 else if(_isNumber(value)) {
28334 object.value = value.toFixed(0);
28344 object.datatype = datatype || XSD_INTEGER;
2835 }
283669 else if('@language' in item) {
28373 object.value = value;
28383 object.datatype = datatype || RDF_LANGSTRING;
28393 object.language = item['@language'];
2840 }
2841 else {
284266 object.value = value;
284366 object.datatype = datatype || XSD_STRING;
2844 }
2845 }
2846 // convert string/node object to RDF
2847 else {
2848279 var id = _isObject(item) ? item['@id'] : item;
2849279 if(id.indexOf('_:') === 0) {
2850228 object.type = 'blank node';
2851228 object.value = namer.getName(id);
2852 }
2853 else {
285451 object.type = 'IRI';
285551 object.value = id;
2856 }
2857 }
2858
2859356 return object;
2860}
2861
2862/**
2863 * Converts an RDF triple object to a JSON-LD object.
2864 *
2865 * @param o the RDF triple object to convert.
2866 * @param useNativeTypes true to output native types, false not to.
2867 *
2868 * @return the JSON-LD object.
2869 */
28702function _RDFToObject(o, useNativeTypes) {
2871 // convert empty list
287234 if(o.type === 'IRI' && o.value === RDF_NIL) {
28731 return {'@list': []};
2874 }
2875
2876 // convert IRI/blank node object to JSON-LD
287733 if(o.type === 'IRI' || o.type === 'blank node') {
287813 return {'@id': o.value};
2879 }
2880
2881 // convert literal to JSON-LD
288220 var rval = {'@value': o.value};
2883
2884 // add language
288520 if('language' in o) {
28861 rval['@language'] = o.language;
2887 }
2888 // add datatype
2889 else {
289019 var type = o.datatype;
2891 // use native types for certain xsd types
289219 if(useNativeTypes) {
289319 if(type === XSD_BOOLEAN) {
28942 if(rval['@value'] === 'true') {
28951 rval['@value'] = true;
2896 }
28971 else if(rval['@value'] === 'false') {
28981 rval['@value'] = false;
2899 }
2900 }
290117 else if(_isNumeric(rval['@value'])) {
29024 if(type === XSD_INTEGER) {
29032 var i = parseInt(rval['@value']);
29042 if(i.toFixed(0) === rval['@value']) {
29052 rval['@value'] = i;
2906 }
2907 }
29082 else if(type === XSD_DOUBLE) {
29091 rval['@value'] = parseFloat(rval['@value']);
2910 }
2911 }
2912 // do not add native type
291319 if([XSD_BOOLEAN, XSD_INTEGER, XSD_DOUBLE, XSD_STRING]
2914 .indexOf(type) === -1) {
29152 rval['@type'] = type;
2916 }
2917 }
2918 else {
29190 rval['@type'] = type;
2920 }
2921 }
2922
292320 return rval;
2924}
2925
2926/**
2927 * Compares two RDF triples for equality.
2928 *
2929 * @param t1 the first triple.
2930 * @param t2 the second triple.
2931 *
2932 * @return true if the triples are the same, false if not.
2933 */
29342function _compareRDFTriples(t1, t2) {
2935146 var attrs = ['subject', 'predicate', 'object'];
2936146 for(var i = 0; i < attrs.length; ++i) {
2937211 var attr = attrs[i];
2938211 if(t1[attr].type !== t2[attr].type || t1[attr].value !== t2[attr].value) {
2939146 return false;
2940 }
2941 }
29420 if(t1.object.language !== t2.object.language) {
29430 return false;
2944 }
29450 if(t1.object.datatype !== t2.object.datatype) {
29460 return false;
2947 }
29480 return true;
2949}
2950
2951/**
2952 * Hashes all of the quads about a blank node.
2953 *
2954 * @param id the ID of the bnode to hash quads for.
2955 * @param bnodes the mapping of bnodes to quads.
2956 * @param namer the canonical bnode namer.
2957 *
2958 * @return the new hash.
2959 */
29602function _hashQuads(id, bnodes, namer) {
2961 // return cached hash
29621438 if('hash' in bnodes[id]) {
29631272 return bnodes[id].hash;
2964 }
2965
2966 // serialize all of bnode's quads
2967166 var quads = bnodes[id].quads;
2968166 var nquads = [];
2969166 for(var i = 0; i < quads.length; ++i) {
2970491 nquads.push(_toNQuad(
2971 quads[i], quads[i].name ? quads[i].name.value : null, id));
2972 }
2973 // sort serialized quads
2974166 nquads.sort();
2975 // return hashed quads
2976166 var hash = bnodes[id].hash = sha1.hash(nquads);
2977166 return hash;
2978}
2979
2980/**
2981 * Produces a hash for the paths of adjacent bnodes for a bnode,
2982 * incorporating all information about its subgraph of bnodes. This
2983 * method will recursively pick adjacent bnode permutations that produce the
2984 * lexicographically-least 'path' serializations.
2985 *
2986 * @param id the ID of the bnode to hash paths for.
2987 * @param bnodes the map of bnode quads.
2988 * @param namer the canonical bnode namer.
2989 * @param pathNamer the namer used to assign names to adjacent bnodes.
2990 * @param callback(err, result) called once the operation completes.
2991 */
29922function _hashPaths(id, bnodes, namer, pathNamer, callback) {
2993 // create SHA-1 digest
29941541 var md = sha1.create();
2995
2996 // group adjacent bnodes by hash, keep properties and references separate
29971541 var groups = {};
29981541 var groupHashes;
29991541 var quads = bnodes[id].quads;
30003082 jsonld.nextTick(function() {groupNodes(0);});
30011541 function groupNodes(i) {
300210319 if(i === quads.length) {
3003 // done, hash groups
30041541 groupHashes = Object.keys(groups).sort();
30051541 return hashGroup(0);
3006 }
3007
3008 // get adjacent bnode
30098778 var quad = quads[i];
30108778 var bnode = _getAdjacentBlankNodeName(quad.subject, id);
30118778 var direction = null;
30128778 if(bnode !== null) {
3013 // normal property
30144385 direction = 'p';
3015 }
3016 else {
30174393 bnode = _getAdjacentBlankNodeName(quad.object, id);
30184393 if(bnode !== null) {
3019 // reverse property
30204383 direction = 'r';
3021 }
3022 }
3023
30248778 if(bnode !== null) {
3025 // get bnode name (try canonical, path, then hash)
30268768 var name;
30278768 if(namer.isNamed(bnode)) {
302812 name = namer.getName(bnode);
3029 }
30308756 else if(pathNamer.isNamed(bnode)) {
30317510 name = pathNamer.getName(bnode);
3032 }
3033 else {
30341246 name = _hashQuads(bnode, bnodes, namer);
3035 }
3036
3037 // hash direction, property, and bnode name/hash
30388768 var md = sha1.create();
30398768 md.update(direction);
30408768 md.update(quad.predicate.value);
30418768 md.update(name);
30428768 var groupHash = md.digest();
3043
3044 // add bnode to hash group
30458768 if(groupHash in groups) {
3046432 groups[groupHash].push(bnode);
3047 }
3048 else {
30498336 groups[groupHash] = [bnode];
3050 }
3051 }
3052
305317556 jsonld.nextTick(function() {groupNodes(i + 1);});
3054 }
3055
3056 // hashes a group of adjacent bnodes
30571541 function hashGroup(i) {
30589877 if(i === groupHashes.length) {
3059 // done, return SHA-1 digest and path namer
30601541 return callback(null, {hash: md.digest(), pathNamer: pathNamer});
3061 }
3062
3063 // digest group hash
30648336 var groupHash = groupHashes[i];
30658336 md.update(groupHash);
3066
3067 // choose a path and namer from the permutations
30688336 var chosenPath = null;
30698336 var chosenNamer = null;
30708336 var permutator = new Permutator(groups[groupHash]);
307116672 jsonld.nextTick(function() {permutate();});
30728336 function permutate() {
30738984 var permutation = permutator.next();
30748984 var pathNamerCopy = pathNamer.clone();
3075
3076 // build adjacent path
30778984 var path = '';
30788984 var recurse = [];
30798984 for(var n in permutation) {
308010424 var bnode = permutation[n];
3081
3082 // use canonical name if available
308310424 if(namer.isNamed(bnode)) {
308412 path += namer.getName(bnode);
3085 }
3086 else {
3087 // recurse if bnode isn't named in the path yet
308810412 if(!pathNamerCopy.isNamed(bnode)) {
30891451 recurse.push(bnode);
3090 }
309110412 path += pathNamerCopy.getName(bnode);
3092 }
3093
3094 // skip permutation if path is already >= chosen path
309510424 if(chosenPath !== null && path.length >= chosenPath.length &&
3096 path > chosenPath) {
3097282 return nextPermutation(true);
3098 }
3099 }
3100
3101 // does the next recursion
31028702 nextRecursion(0);
31038702 function nextRecursion(n) {
310410081 if(n === recurse.length) {
3105 // done, do next permutation
31068630 return nextPermutation(false);
3107 }
3108
3109 // do recursion
31101451 var bnode = recurse[n];
31111451 _hashPaths(bnode, bnodes, namer, pathNamerCopy,
3112 function(err, result) {
31131451 if(err) {
31140 return callback(err);
3115 }
31161451 path += pathNamerCopy.getName(bnode) + '<' + result.hash + '>';
31171451 pathNamerCopy = result.pathNamer;
3118
3119 // skip permutation if path is already >= chosen path
31201451 if(chosenPath !== null && path.length >= chosenPath.length &&
3121 path > chosenPath) {
312272 return nextPermutation(true);
3123 }
3124
3125 // do next recursion
31261379 nextRecursion(n + 1);
3127 });
3128 }
3129
3130 // stores the results of this permutation and runs the next
31318702 function nextPermutation(skipped) {
31328984 if(!skipped && (chosenPath === null || path < chosenPath)) {
31338408 chosenPath = path;
31348408 chosenNamer = pathNamerCopy;
3135 }
3136
3137 // do next permutation
31388984 if(permutator.hasNext()) {
31391296 jsonld.nextTick(function() {permutate();});
3140 }
3141 else {
3142 // digest chosen path and update namer
31438336 md.update(chosenPath);
31448336 pathNamer = chosenNamer;
3145
3146 // hash the next group
31478336 hashGroup(i + 1);
3148 }
3149 }
3150 }
3151 }
3152}
3153
3154/**
3155 * A helper function that gets the blank node name from an RDF quad node
3156 * (subject or object). If the node is a blank node and its value
3157 * does not match the given blank node ID, it will be returned.
3158 *
3159 * @param node the RDF quad node.
3160 * @param id the ID of the blank node to look next to.
3161 *
3162 * @return the adjacent blank node name or null if none was found.
3163 */
31642function _getAdjacentBlankNodeName(node, id) {
316513171 return (node.type === 'blank node' && node.value !== id ? node.value : null);
3166}
3167
3168/**
3169 * Recursively flattens the subjects in the given JSON-LD expanded input
3170 * into a node map.
3171 *
3172 * @param input the JSON-LD expanded input.
3173 * @param graphs a map of graph name to subject map.
3174 * @param graph the name of the current graph.
3175 * @param namer the blank node namer.
3176 * @param name the name assigned to the current input if it is a bnode.
3177 * @param list the list to append to, null for none.
3178 */
31792function _createNodeMap(input, graphs, graph, namer, name, list) {
3180 // recurse through array
31811279 if(_isArray(input)) {
3182179 for(var i in input) {
3183354 _createNodeMap(input[i], graphs, graph, namer, undefined, list);
3184 }
3185179 return;
3186 }
3187
3188 // add non-object to list
31891100 if(!_isObject(input)) {
319094 if(list) {
31910 list.push(input);
3192 }
319394 return;
3194 }
3195
3196 // add values to list
31971006 if(_isValue(input)) {
3198316 if('@type' in input) {
319938 var type = input['@type'];
3200 // rename @type blank node
320138 if(type.indexOf('_:') === 0) {
32027 input['@type'] = type = namer.getName(type);
3203 }
320438 if(!(type in graphs[graph])) {
320520 graphs[graph][type] = {'@id': type};
3206 }
3207 }
3208316 if(list) {
320927 list.push(input);
3210 }
3211316 return;
3212 }
3213
3214 // Note: At this point, input must be a subject.
3215
3216 // get name for subject
3217690 if(_isUndefined(name)) {
3218332 name = _isBlankNode(input) ? namer.getName(input['@id']) : input['@id'];
3219 }
3220
3221 // add subject reference to list
3222690 if(list) {
322313 list.push({'@id': name});
3224 }
3225
3226 // create new subject or merge into existing one
3227690 var subjects = graphs[graph];
3228690 var subject = subjects[name] = subjects[name] || {};
3229690 subject['@id'] = name;
3230690 var properties = Object.keys(input).sort();
3231690 for(var pi = 0; pi < properties.length; ++pi) {
32321258 var property = properties[pi];
3233
3234 // skip @id
32351258 if(property === '@id') {
3236652 continue;
3237 }
3238
3239 // handle reverse properties
3240606 if(property === '@reverse') {
32413 var referencedNode = {'@id': name};
32423 var reverseMap = input['@reverse'];
32433 for(var reverseProperty in reverseMap) {
32443 var items = reverseMap[reverseProperty];
32453 for(var ii = 0; ii < items.length; ++ii) {
32465 var item = items[ii];
32475 jsonld.addValue(
3248 item, reverseProperty, referencedNode,
3249 {propertyIsArray: true, allowDuplicate: false});
32505 _createNodeMap(item, graphs, graph, namer);
3251 }
3252 }
32533 continue;
3254 }
3255
3256 // recurse into graph
3257603 if(property === '@graph') {
3258 // add graph subjects map entry
325910 if(!(name in graphs)) {
326010 graphs[name] = {};
3261 }
326210 var g = (graph === '@merged') ? graph : name;
326310 _createNodeMap(input[property], graphs, g, namer);
326410 continue;
3265 }
3266
3267 // copy non-@type keywords
3268593 if(property !== '@type' && _isKeyword(property)) {
32695 if(property === '@index' && '@index' in subject) {
32700 throw new JsonLdError(
3271 'Invalid JSON-LD syntax; conflicting @index property detected.',
3272 'jsonld.SyntaxError', {subject: subject});
3273 }
32745 subject[property] = input[property];
32755 continue;
3276 }
3277
3278 // iterate over objects
3279588 var objects = input[property];
3280
3281 // if property is a bnode, assign it a new id
3282588 if(property.indexOf('_:') === 0) {
32835 property = namer.getName(property);
3284 }
3285
3286 // ensure property is added for empty arrays
3287588 if(objects.length === 0) {
32889 jsonld.addValue(subject, property, [], {propertyIsArray: true});
32899 continue;
3290 }
3291579 for(var oi = 0; oi < objects.length; ++oi) {
3292762 var o = objects[oi];
3293
3294762 if(property === '@type') {
3295 // rename @type blank nodes
329694 o = (o.indexOf('_:') === 0) ? namer.getName(o) : o;
329794 if(!(o in graphs[graph])) {
329886 graphs[graph][o] = {'@id': o};
3299 }
3300 }
3301
3302 // handle embedded subject or subject reference
3303762 if(_isSubject(o) || _isSubjectReference(o)) {
3304 // rename blank node @id
3305358 var id = _isBlankNode(o) ? namer.getName(o['@id']) : o['@id'];
3306
3307 // add reference and recurse
3308358 jsonld.addValue(
3309 subject, property, {'@id': id},
3310 {propertyIsArray: true, allowDuplicate: false});
3311358 _createNodeMap(o, graphs, graph, namer, id);
3312 }
3313 // handle @list
3314404 else if(_isList(o)) {
331521 var _list = [];
331621 _createNodeMap(o['@list'], graphs, graph, namer, name, _list);
331721 o = {'@list': _list};
331821 jsonld.addValue(
3319 subject, property, o,
3320 {propertyIsArray: true, allowDuplicate: false});
3321 }
3322 // handle @value
3323 else {
3324383 _createNodeMap(o, graphs, graph, namer, name);
3325383 jsonld.addValue(
3326 subject, property, o, {propertyIsArray: true, allowDuplicate: false});
3327 }
3328 }
3329 }
3330}
3331
3332/**
3333 * Frames subjects according to the given frame.
3334 *
3335 * @param state the current framing state.
3336 * @param subjects the subjects to filter.
3337 * @param frame the frame.
3338 * @param parent the parent subject or top-level array.
3339 * @param property the parent property, initialized to null.
3340 */
33412function _frame(state, subjects, frame, parent, property) {
3342 // validate the frame
334344 _validateFrame(state, frame);
334444 frame = frame[0];
3345
3346 // filter out subjects that match the frame
334744 var matches = _filterSubjects(state, subjects, frame);
3348
3349 // get flags for current frame
335044 var options = state.options;
335144 var embedOn = _getFrameFlag(frame, options, 'embed');
335244 var explicitOn = _getFrameFlag(frame, options, 'explicit');
3353
3354 // add matches to output
335544 var ids = Object.keys(matches).sort();
335644 for(var idx in ids) {
335760 var id = ids[idx];
3358
3359 /* Note: In order to treat each top-level match as a compartmentalized
3360 result, create an independent copy of the embedded subjects map when the
3361 property is null, which only occurs at the top-level. */
336260 if(property === null) {
336337 state.embeds = {};
3364 }
3365
3366 // start output
336760 var output = {};
336860 output['@id'] = id;
3369
3370 // prepare embed meta info
337160 var embed = {parent: parent, property: property};
3372
3373 // if embed is on and there is an existing embed
337460 if(embedOn && (id in state.embeds)) {
3375 // only overwrite an existing embed if it has already been added to its
3376 // parent -- otherwise its parent is somewhere up the tree from this
3377 // embed and the embed would occur twice once the tree is added
33780 embedOn = false;
3379
3380 // existing embed's parent is an array
33810 var existing = state.embeds[id];
33820 if(_isArray(existing.parent)) {
33830 for(var i in existing.parent) {
33840 if(jsonld.compareValues(output, existing.parent[i])) {
33850 embedOn = true;
33860 break;
3387 }
3388 }
3389 }
3390 // existing embed's parent is an object
33910 else if(jsonld.hasValue(existing.parent, existing.property, output)) {
33920 embedOn = true;
3393 }
3394
3395 // existing embed has already been added, so allow an overwrite
33960 if(embedOn) {
33970 _removeEmbed(state, id);
3398 }
3399 }
3400
3401 // not embedding, add output without any other properties
340260 if(!embedOn) {
34036 _addFrameOutput(state, parent, property, output);
3404 }
3405 else {
3406 // add embed meta info
340754 state.embeds[id] = embed;
3408
3409 // iterate over subject properties
341054 var subject = matches[id];
341154 var props = Object.keys(subject).sort();
341254 for(var i in props) {
3413180 var prop = props[i];
3414
3415 // copy keywords to output
3416180 if(_isKeyword(prop)) {
341791 output[prop] = _clone(subject[prop]);
341891 continue;
3419 }
3420
3421 // if property isn't in the frame
342289 if(!(prop in frame)) {
3423 // if explicit is off, embed values
342464 if(!explicitOn) {
342562 _embedValues(state, subject, prop, output);
3426 }
342764 continue;
3428 }
3429
3430 // add objects
343125 var objects = subject[prop];
343225 for(var i in objects) {
343331 var o = objects[i];
3434
3435 // recurse into list
343631 if(_isList(o)) {
3437 // add empty list
34380 var list = {'@list': []};
34390 _addFrameOutput(state, output, prop, list);
3440
3441 // add list objects
34420 var src = o['@list'];
34430 for(var n in src) {
34440 o = src[n];
3445 // recurse into subject reference
34460 if(_isSubjectReference(o)) {
34470 _frame(state, [o['@id']], frame[prop], list, '@list');
3448 }
3449 // include other values automatically
3450 else {
34510 _addFrameOutput(state, list, '@list', _clone(o));
3452 }
3453 }
34540 continue;
3455 }
3456
3457 // recurse into subject reference
345831 if(_isSubjectReference(o)) {
345923 _frame(state, [o['@id']], frame[prop], output, prop);
3460 }
3461 // include other values automatically
3462 else {
34638 _addFrameOutput(state, output, prop, _clone(o));
3464 }
3465 }
3466 }
3467
3468 // handle defaults
346954 var props = Object.keys(frame).sort();
347054 for(var i in props) {
347174 var prop = props[i];
3472
3473 // skip keywords
347474 if(_isKeyword(prop)) {
347539 continue;
3476 }
3477
3478 // if omit default is off, then include default values for properties
3479 // that appear in the next frame but are not in the matching subject
348035 var next = frame[prop][0];
348135 var omitDefaultOn = _getFrameFlag(next, options, 'omitDefault');
348235 if(!omitDefaultOn && !(prop in output)) {
34837 var preserve = '@null';
34847 if('@default' in next) {
34853 preserve = _clone(next['@default']);
3486 }
34877 if(!_isArray(preserve)) {
34884 preserve = [preserve];
3489 }
34907 output[prop] = [{'@preserve': preserve}];
3491 }
3492 }
3493
3494 // add output to parent
349554 _addFrameOutput(state, parent, property, output);
3496 }
3497 }
3498}
3499
3500/**
3501 * Gets the frame flag value for the given flag name.
3502 *
3503 * @param frame the frame.
3504 * @param options the framing options.
3505 * @param name the flag name.
3506 *
3507 * @return the flag value.
3508 */
35092function _getFrameFlag(frame, options, name) {
3510123 var flag = '@' + name;
3511123 return (flag in frame) ? frame[flag][0] : options[name];
3512}
3513
3514/**
3515 * Validates a JSON-LD frame, throwing an exception if the frame is invalid.
3516 *
3517 * @param state the current frame state.
3518 * @param frame the frame to validate.
3519 */
35202function _validateFrame(state, frame) {
352144 if(!_isArray(frame) || frame.length !== 1 || !_isObject(frame[0])) {
35220 throw new JsonLdError(
3523 'Invalid JSON-LD syntax; a JSON-LD frame must be a single object.',
3524 'jsonld.SyntaxError', {frame: frame});
3525 }
3526}
3527
3528/**
3529 * Returns a map of all of the subjects that match a parsed frame.
3530 *
3531 * @param state the current framing state.
3532 * @param subjects the set of subjects to filter.
3533 * @param frame the parsed frame.
3534 *
3535 * @return all of the matched subjects.
3536 */
35372function _filterSubjects(state, subjects, frame) {
3538 // filter subjects in @id order
353944 var rval = {};
354044 for(var i in subjects) {
3541133 var id = subjects[i];
3542133 var subject = state.subjects[id];
3543133 if(_filterSubject(subject, frame)) {
354460 rval[id] = subject;
3545 }
3546 }
354744 return rval;
3548}
3549
3550/**
3551 * Returns true if the given subject matches the given frame.
3552 *
3553 * @param subject the subject to check.
3554 * @param frame the frame to check.
3555 *
3556 * @return true if the subject matches, false if not.
3557 */
35582function _filterSubject(subject, frame) {
3559 // check @type (object value means 'any' type, fall through to ducktyping)
3560133 if('@type' in frame &&
3561 !(frame['@type'].length === 1 && _isObject(frame['@type'][0]))) {
3562104 var types = frame['@type'];
3563104 for(var i in types) {
3564 // any matching @type is a match
3565221 if(jsonld.hasValue(subject, '@type', types[i])) {
356633 return true;
3567 }
3568 }
356971 return false;
3570 }
3571
3572 // check ducktype
357329 for(var key in frame) {
3574 // only not a duck if @id or non-keyword isn't in subject
357515 if((key === '@id' || !_isKeyword(key)) && !(key in subject)) {
35762 return false;
3577 }
3578 }
357927 return true;
3580}
3581
3582/**
3583 * Embeds values for the given subject and property into the given output
3584 * during the framing algorithm.
3585 *
3586 * @param state the current framing state.
3587 * @param subject the subject.
3588 * @param property the property.
3589 * @param output the output.
3590 */
35912function _embedValues(state, subject, property, output) {
3592 // embed subject properties in output
3593112 var objects = subject[property];
3594112 for(var i in objects) {
3595140 var o = objects[i];
3596
3597 // recurse into @list
3598140 if(_isList(o)) {
35993 var list = {'@list': []};
36003 _addFrameOutput(state, output, property, list);
36013 return _embedValues(state, o, '@list', list['@list']);
3602 }
3603
3604 // handle subject reference
3605137 if(_isSubjectReference(o)) {
360632 var id = o['@id'];
3607
3608 // embed full subject if isn't already embedded
360932 if(!(id in state.embeds)) {
3610 // add embed
361128 var embed = {parent: output, property: property};
361228 state.embeds[id] = embed;
3613
3614 // recurse into subject
361528 o = {};
361628 var s = state.subjects[id];
361728 for(var prop in s) {
3618 // copy keywords
361991 if(_isKeyword(prop)) {
362044 o[prop] = _clone(s[prop]);
362144 continue;
3622 }
362347 _embedValues(state, s, prop, o);
3624 }
3625 }
362632 _addFrameOutput(state, output, property, o);
3627 }
3628 // copy non-subject value
3629 else {
3630105 _addFrameOutput(state, output, property, _clone(o));
3631 }
3632 }
3633}
3634
3635/**
3636 * Removes an existing embed.
3637 *
3638 * @param state the current framing state.
3639 * @param id the @id of the embed to remove.
3640 */
36412function _removeEmbed(state, id) {
3642 // get existing embed
36430 var embeds = state.embeds;
36440 var embed = embeds[id];
36450 var parent = embed.parent;
36460 var property = embed.property;
3647
3648 // create reference to replace embed
36490 var subject = {'@id': id};
3650
3651 // remove existing embed
36520 if(_isArray(parent)) {
3653 // replace subject with reference
36540 for(var i in parent) {
36550 if(jsonld.compareValues(parent[i], subject)) {
36560 parent[i] = subject;
36570 break;
3658 }
3659 }
3660 }
3661 else {
3662 // replace subject with reference
36630 var useArray = _isArray(parent[property]);
36640 jsonld.removeValue(parent, property, subject, {propertyIsArray: useArray});
36650 jsonld.addValue(parent, property, subject, {propertyIsArray: useArray});
3666 }
3667
3668 // recursively remove dependent dangling embeds
36690 var removeDependents = function(id) {
3670 // get embed keys as a separate array to enable deleting keys in map
36710 var ids = Object.keys(embeds);
36720 for(var i in ids) {
36730 var next = ids[i];
36740 if(next in embeds && _isObject(embeds[next].parent) &&
3675 embeds[next].parent['@id'] === id) {
36760 delete embeds[next];
36770 removeDependents(next);
3678 }
3679 }
3680 };
36810 removeDependents(id);
3682}
3683
3684/**
3685 * Adds framing output to the given parent.
3686 *
3687 * @param state the current framing state.
3688 * @param parent the parent to add to.
3689 * @param property the parent property.
3690 * @param output the output to add.
3691 */
36922function _addFrameOutput(state, parent, property, output) {
3693208 if(_isObject(parent)) {
3694150 jsonld.addValue(parent, property, output, {propertyIsArray: true});
3695 }
3696 else {
369758 parent.push(output);
3698 }
3699}
3700
3701/**
3702 * Removes the @preserve keywords as the last step of the framing algorithm.
3703 *
3704 * @param ctx the active context used to compact the input.
3705 * @param input the framed, compacted output.
3706 * @param options the compaction options used.
3707 *
3708 * @return the resulting output.
3709 */
37102function _removePreserve(ctx, input, options) {
3711 // recurse through arrays
3712388 if(_isArray(input)) {
371334 var output = [];
371434 for(var i in input) {
371586 var result = _removePreserve(ctx, input[i], options);
3716 // drop nulls from arrays
371786 if(result !== null) {
371884 output.push(result);
3719 }
3720 }
372134 input = output;
3722 }
3723354 else if(_isObject(input)) {
3724 // remove @preserve
3725105 if('@preserve' in input) {
37267 if(input['@preserve'] === '@null') {
37274 return null;
3728 }
37293 return input['@preserve'];
3730 }
3731
3732 // skip @values
373398 if(_isValue(input)) {
373410 return input;
3735 }
3736
3737 // recurse through @lists
373888 if(_isList(input)) {
37390 input['@list'] = _removePreserve(ctx, input['@list'], options);
37400 return input;
3741 }
3742
3743 // recurse through properties
374488 for(var prop in input) {
3745281 var result = _removePreserve(ctx, input[prop], options);
3746281 var container = jsonld.getContextValue(ctx, prop, '@container');
3747281 if(options.compactArrays && _isArray(result) && result.length === 1 &&
3748 container === null) {
37491 result = result[0];
3750 }
3751281 input[prop] = result;
3752 }
3753 }
3754371 return input;
3755}
3756
3757/**
3758 * Compares two strings first based on length and then lexicographically.
3759 *
3760 * @param a the first string.
3761 * @param b the second string.
3762 *
3763 * @return -1 if a < b, 1 if a > b, 0 if a == b.
3764 */
37652function _compareShortestLeast(a, b) {
3766625 if(a.length < b.length) {
3767196 return -1;
3768 }
3769429 else if(b.length < a.length) {
3770236 return 1;
3771 }
3772193 else if(a === b) {
37730 return 0;
3774 }
3775193 return (a < b) ? -1 : 1;
3776}
3777
3778/**
3779 * Picks the preferred compaction term from the given inverse context entry.
3780 *
3781 * @param activeCtx the active context.
3782 * @param iri the IRI to pick the term for.
3783 * @param value the value to pick the term for.
3784 * @param containers the preferred containers.
3785 * @param typeOrLanguage either '@type' or '@language'.
3786 * @param typeOrLanguageValue the preferred value for '@type' or '@language'.
3787 *
3788 * @return the preferred term.
3789 */
3790function _selectTerm(
37912 activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue) {
3792240 if(typeOrLanguageValue === null) {
37930 typeOrLanguageValue = '@null';
3794 }
3795
3796 // preferences for the value of @type or @language
3797240 var prefs = [];
3798
3799 // determine prefs for @id based on whether or not value compacts to a term
3800240 if((typeOrLanguageValue === '@id' || typeOrLanguageValue === '@reverse') &&
3801 _isSubjectReference(value)) {
3802 // prefer @reverse first
380337 if(typeOrLanguageValue === '@reverse') {
38044 prefs.push('@reverse');
3805 }
3806 // try to compact value to a term
380737 var term = _compactIri(activeCtx, value['@id'], null, {vocab: true});
380837 if(term in activeCtx.mappings &&
3809 activeCtx.mappings[term] &&
3810 activeCtx.mappings[term]['@id'] === value['@id']) {
3811 // prefer @vocab
38128 prefs.push.apply(prefs, ['@vocab', '@id']);
3813 }
3814 else {
3815 // prefer @id
381629 prefs.push.apply(prefs, ['@id', '@vocab']);
3817 }
3818 }
3819 else {
3820203 prefs.push(typeOrLanguageValue);
3821 }
3822240 prefs.push('@none');
3823
3824240 var containerMap = activeCtx.inverse[iri];
3825240 for(var ci = 0; ci < containers.length; ++ci) {
3826 // if container not available in the map, continue
3827422 var container = containers[ci];
3828422 if(!(container in containerMap)) {
3829185 continue;
3830 }
3831
3832237 var typeOrLanguageValueMap = containerMap[container][typeOrLanguage];
3833237 for(var pi = 0; pi < prefs.length; ++pi) {
3834 // if type/language option not available in the map, continue
3835409 var pref = prefs[pi];
3836409 if(!(pref in typeOrLanguageValueMap)) {
3837177 continue;
3838 }
3839
3840 // select term
3841232 return typeOrLanguageValueMap[pref];
3842 }
3843 }
3844
38458 return null;
3846}
3847
3848/**
3849 * Compacts an IRI or keyword into a term or prefix if it can be. If the
3850 * IRI has an associated value it may be passed.
3851 *
3852 * @param activeCtx the active context to use.
3853 * @param iri the IRI to compact.
3854 * @param value the value to check or null.
3855 * @param relativeTo options for how to compact IRIs:
3856 * vocab: true to split after @vocab, false not to.
3857 * @param reverse true if a reverse property is being compacted, false if not.
3858 *
3859 * @return the compacted term, prefix, keyword alias, or the original IRI.
3860 */
38612function _compactIri(activeCtx, iri, value, relativeTo, reverse) {
3862 // can't compact null
38631166 if(iri === null) {
38640 return iri;
3865 }
3866
3867 // default value and parent to null
38681166 if(_isUndefined(value)) {
3869414 value = null;
3870 }
3871 // default reverse to false
38721166 if(_isUndefined(reverse)) {
3873782 reverse = false;
3874 }
38751166 relativeTo = relativeTo || {};
3876
3877 // if term is a keyword, default vocab to true
38781166 if(_isKeyword(iri)) {
3879424 relativeTo.vocab = true;
3880 }
3881
3882 // use inverse context to pick a term if iri is relative to vocab
38831166 if(relativeTo.vocab && iri in activeCtx.getInverse()) {
3884240 var defaultLanguage = activeCtx['@language'] || '@none';
3885
3886 // prefer @index if available in value
3887240 var containers = [];
3888240 if(_isObject(value) && '@index' in value) {
388939 containers.push('@index');
3890 }
3891
3892 // defaults for term selection based on type/language
3893240 var typeOrLanguage = '@language';
3894240 var typeOrLanguageValue = '@null';
3895
3896240 if(reverse) {
389711 typeOrLanguage = '@type';
389811 typeOrLanguageValue = '@reverse';
389911 containers.push('@set');
3900 }
3901 // choose the most specific term that works for all elements in @list
3902229 else if(_isList(value)) {
3903 // only select @list containers if @index is NOT in value
390424 if(!('@index' in value)) {
390523 containers.push('@list');
3906 }
390724 var list = value['@list'];
390824 var commonLanguage = (list.length === 0) ? defaultLanguage : null;
390924 var commonType = null;
391024 for(var i = 0; i < list.length; ++i) {
391187 var item = list[i];
391287 var itemLanguage = '@none';
391387 var itemType = '@none';
391487 if(_isValue(item)) {
391570 if('@language' in item) {
391612 itemLanguage = item['@language'];
3917 }
391858 else if('@type' in item) {
391918 itemType = item['@type'];
3920 }
3921 // plain literal
3922 else {
392340 itemLanguage = '@null';
3924 }
3925 }
3926 else {
392717 itemType = '@id';
3928 }
392987 if(commonLanguage === null) {
393023 commonLanguage = itemLanguage;
3931 }
393264 else if(itemLanguage !== commonLanguage && _isValue(item)) {
39331 commonLanguage = '@none';
3934 }
393587 if(commonType === null) {
393623 commonType = itemType;
3937 }
393864 else if(itemType !== commonType) {
39391 commonType = '@none';
3940 }
3941 // there are different languages and types in the list, so choose
3942 // the most generic term, no need to keep iterating the list
394387 if(commonLanguage === '@none' && commonType === '@none') {
39442 break;
3945 }
3946 }
394724 commonLanguage = commonLanguage || '@none';
394824 commonType = commonType || '@none';
394924 if(commonType !== '@none') {
39507 typeOrLanguage = '@type';
39517 typeOrLanguageValue = commonType;
3952 }
3953 else {
395417 typeOrLanguageValue = commonLanguage;
3955 }
3956 }
3957 else {
3958205 if(_isValue(value)) {
3959102 if('@language' in value && !('@index' in value)) {
396020 containers.push('@language');
396120 typeOrLanguageValue = value['@language'];
3962 }
396382 else if('@type' in value) {
396410 typeOrLanguage = '@type';
396510 typeOrLanguageValue = value['@type'];
3966 }
3967 }
3968 else {
3969103 typeOrLanguage = '@type';
3970103 typeOrLanguageValue = '@id';
3971 }
3972205 containers.push('@set');
3973 }
3974
3975 // do term selection
3976240 containers.push('@none');
3977240 var term = _selectTerm(
3978 activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue);
3979240 if(term !== null) {
3980232 return term;
3981 }
3982 }
3983
3984 // no term match, use @vocab if available
3985934 if(relativeTo.vocab) {
3986720 if('@vocab' in activeCtx) {
3987 // determine if vocab is a prefix of the iri
398839 var vocab = activeCtx['@vocab'];
398939 if(iri.indexOf(vocab) === 0 && iri !== vocab) {
3990 // use suffix as relative iri if it is not a term in the active context
399111 var suffix = iri.substr(vocab.length);
399211 if(!(suffix in activeCtx.mappings)) {
399310 return suffix;
3994 }
3995 }
3996 }
3997 }
3998
3999 // no term or @vocab match, check for possible CURIEs
4000924 var choice = null;
4001924 for(var term in activeCtx.mappings) {
4002 // skip terms with colons, they can't be prefixes
40036125 if(term.indexOf(':') !== -1) {
40043965 continue;
4005 }
4006 // skip entries with @ids that are not partial matches
40072160 var definition = activeCtx.mappings[term];
40082160 if(!definition ||
4009 definition['@id'] === iri || iri.indexOf(definition['@id']) !== 0) {
40101909 continue;
4011 }
4012
4013 // a CURIE is usable if:
4014 // 1. it has no mapping, OR
4015 // 2. value is null, which means we're not compacting an @value, AND
4016 // the mapping matches the IRI)
4017251 var curie = term + ':' + iri.substr(definition['@id'].length);
4018251 var isUsableCurie = (!(curie in activeCtx.mappings) ||
4019 (value === null && activeCtx.mappings[curie] &&
4020 activeCtx.mappings[curie]['@id'] === iri));
4021
4022 // select curie if it is shorter or the same length but lexicographically
4023 // less than the current choice
4024251 if(isUsableCurie && (choice === null ||
4025 _compareShortestLeast(curie, choice) < 0)) {
4026250 choice = curie;
4027 }
4028 }
4029
4030 // return chosen curie
4031924 if(choice !== null) {
4032230 return choice;
4033 }
4034
4035 // compact IRI relative to base
4036694 if(!relativeTo.vocab) {
4037174 return _removeBase(activeCtx['@base'], iri);
4038 }
4039
4040 // return IRI as is
4041520 return iri;
4042}
4043
4044/**
4045 * Performs value compaction on an object with '@value' or '@id' as the only
4046 * property.
4047 *
4048 * @param activeCtx the active context.
4049 * @param activeProperty the active property that points to the value.
4050 * @param value the value to compact.
4051 *
4052 * @return the compaction result.
4053 */
40542function _compactValue(activeCtx, activeProperty, value) {
4055 // value is a @value
4056361 if(_isValue(value)) {
4057 // get context rules
4058294 var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
4059294 var language = jsonld.getContextValue(
4060 activeCtx, activeProperty, '@language');
4061294 var container = jsonld.getContextValue(
4062 activeCtx, activeProperty, '@container');
4063
4064 // whether or not the value has an @index that must be preserved
4065294 var preserveIndex = (('@index' in value) &&
4066 container !== '@index');
4067
4068 // if there's no @index to preserve ...
4069294 if(!preserveIndex) {
4070 // matching @type or @language specified in context, compact value
4071286 if(value['@type'] === type || value['@language'] === language) {
407237 return value['@value'];
4073 }
4074 }
4075
4076 // return just the value of @value if all are true:
4077 // 1. @value is the only key or @index isn't being preserved
4078 // 2. there is no default language or @value is not a string or
4079 // the key has a mapping with a null @language
4080257 var keyCount = Object.keys(value).length;
4081257 var isValueOnlyKey = (keyCount === 1 ||
4082 (keyCount === 2 && ('@index' in value) && !preserveIndex));
4083257 var hasDefaultLanguage = ('@language' in activeCtx);
4084257 var isValueString = _isString(value['@value']);
4085257 var hasNullMapping = (activeCtx.mappings[activeProperty] &&
4086 activeCtx.mappings[activeProperty]['@language'] === null);
4087257 if(isValueOnlyKey &&
4088 (!hasDefaultLanguage || !isValueString || hasNullMapping)) {
4089208 return value['@value'];
4090 }
4091
409249 var rval = {};
4093
4094 // preserve @index
409549 if(preserveIndex) {
40968 rval[_compactIri(activeCtx, '@index')] = value['@index'];
4097 }
4098
4099 // compact @type IRI
410049 if('@type' in value) {
410119 rval[_compactIri(activeCtx, '@type')] = _compactIri(
4102 activeCtx, value['@type'], null, {vocab: true});
4103 }
4104 // alias @language
410530 else if('@language' in value) {
410624 rval[_compactIri(activeCtx, '@language')] = value['@language'];
4107 }
4108
4109 // alias @value
411049 rval[_compactIri(activeCtx, '@value')] = value['@value'];
4111
411249 return rval;
4113 }
4114
4115 // value is a subject reference
411667 var expandedProperty = _expandIri(activeCtx, activeProperty, {vocab: true});
411767 var type = jsonld.getContextValue(activeCtx, activeProperty, '@type');
411867 var compacted = _compactIri(
4119 activeCtx, value['@id'], null, {vocab: type === '@vocab'});
4120
4121 // compact to scalar
412267 if(type === '@id' || type === '@vocab' || expandedProperty === '@graph') {
412347 return compacted;
4124 }
4125
412620 var rval = {};
412720 rval[_compactIri(activeCtx, '@id')] = compacted;
412820 return rval;
4129}
4130
4131/**
4132 * Creates a term definition during context processing.
4133 *
4134 * @param activeCtx the current active context.
4135 * @param localCtx the local context being processed.
4136 * @param term the term in the local context to define the mapping for.
4137 * @param defined a map of defining/defined keys to detect cycles and prevent
4138 * double definitions.
4139 */
41402function _createTermDefinition(activeCtx, localCtx, term, defined) {
41411271 if(term in defined) {
4142 // term already defined
4143339 if(defined[term]) {
4144339 return;
4145 }
4146 // cycle detected
41470 throw new JsonLdError(
4148 'Cyclical context definition detected.',
4149 'jsonld.CyclicalContext', {context: localCtx, term: term});
4150 }
4151
4152 // now defining term
4153932 defined[term] = false;
4154
4155932 if(_isKeyword(term)) {
41560 throw new JsonLdError(
4157 'Invalid JSON-LD syntax; keywords cannot be overridden.',
4158 'jsonld.SyntaxError', {context: localCtx});
4159 }
4160
4161 // remove old mapping
4162932 if(activeCtx.mappings[term]) {
41631 delete activeCtx.mappings[term];
4164 }
4165
4166 // get context term value
4167932 var value = localCtx[term];
4168
4169 // clear context entry
4170932 if(value === null || (_isObject(value) && value['@id'] === null)) {
41715 activeCtx.mappings[term] = null;
41725 defined[term] = true;
41735 return;
4174 }
4175
4176927 if(_isString(value)) {
4177 // expand value to a full IRI
4178451 var id = _expandIri(
4179 activeCtx, value, {vocab: true, base: true}, localCtx, defined);
4180
4181451 if(_isKeyword(id)) {
4182 // disallow aliasing @context and @preserve
418329 if(id === '@context' || id === '@preserve') {
41840 throw new JsonLdError(
4185 'Invalid JSON-LD syntax; @context and @preserve cannot be aliased.',
4186 'jsonld.SyntaxError');
4187 }
4188 }
4189
4190 // define term to expanded IRI/keyword
4191451 activeCtx.mappings[term] = {'@id': id, reverse: false};
4192451 defined[term] = true;
4193451 return;
4194 }
4195
4196476 if(!_isObject(value)) {
41970 throw new JsonLdError(
4198 'Invalid JSON-LD syntax; @context property values must be ' +
4199 'strings or objects.',
4200 'jsonld.SyntaxError', {context: localCtx});
4201 }
4202
4203 // create new mapping
4204476 var mapping = {};
4205476 mapping.reverse = false;
4206
4207476 if('@reverse' in value) {
420811 if('@id' in value || '@type' in value || '@language' in value) {
42090 throw new JsonLdError(
4210 'Invalid JSON-LD syntax; a @reverse term definition must not ' +
4211 'contain @id, @type, or @language.',
4212 'jsonld.SyntaxError', {context: localCtx});
4213 }
421411 var reverse = value['@reverse'];
421511 if(!_isString(reverse)) {
42160 throw new JsonLdError(
4217 'Invalid JSON-LD syntax; a @context @reverse value must be a string.',
4218 'jsonld.SyntaxError', {context: localCtx});
4219 }
4220
4221 // expand and add @id mapping, set @type to @id
422211 mapping['@id'] = _expandIri(
4223 activeCtx, reverse, {vocab: true, base: true}, localCtx, defined);
422411 mapping['@type'] = '@id';
422511 mapping.reverse = true;
4226 }
4227465 else if('@id' in value) {
4228200 var id = value['@id'];
4229200 if(!_isString(id)) {
42300 throw new JsonLdError(
4231 'Invalid JSON-LD syntax; a @context @id value must be an array ' +
4232 'of strings or a string.',
4233 'jsonld.SyntaxError', {context: localCtx});
4234 }
4235 // expand and add @id mapping
4236200 mapping['@id'] = _expandIri(
4237 activeCtx, id, {vocab: true, base: true}, localCtx, defined);
4238 }
4239 else {
4240 // see if the term has a prefix
4241265 var colon = term.indexOf(':');
4242265 if(colon !== -1) {
4243253 var prefix = term.substr(0, colon);
4244253 if(prefix in localCtx) {
4245 // define parent prefix
4246243 _createTermDefinition(activeCtx, localCtx, prefix, defined);
4247 }
4248
4249 // set @id based on prefix parent
4250253 if(activeCtx.mappings[prefix]) {
4251243 var suffix = term.substr(colon + 1);
4252243 mapping['@id'] = activeCtx.mappings[prefix]['@id'] + suffix;
4253 }
4254 // term is an absolute IRI
4255 else {
425610 mapping['@id'] = term;
4257 }
4258 }
4259 else {
4260 // non-IRIs *must* define @ids if @vocab is not available
426112 if(!('@vocab' in activeCtx)) {
42620 throw new JsonLdError(
4263 'Invalid JSON-LD syntax; @context terms must define an @id.',
4264 'jsonld.SyntaxError', {context: localCtx, term: term});
4265 }
4266 // prepend vocab to term
426712 mapping['@id'] = activeCtx['@vocab'] + term;
4268 }
4269 }
4270
4271476 if('@type' in value) {
4272347 var type = value['@type'];
4273347 if(!_isString(type)) {
42740 throw new JsonLdError(
4275 'Invalid JSON-LD syntax; @context @type values must be strings.',
4276 'jsonld.SyntaxError', {context: localCtx});
4277 }
4278
4279347 if(type !== '@id') {
4280 // expand @type to full IRI
428163 type = _expandIri(
4282 activeCtx, type, {vocab: true, base: true}, localCtx, defined);
4283 }
4284
4285 // add @type to mapping
4286347 mapping['@type'] = type;
4287 }
4288
4289476 if('@container' in value) {
4290104 var container = value['@container'];
4291104 if(container !== '@list' && container !== '@set' &&
4292 container !== '@index' && container !== '@language') {
42930 throw new JsonLdError(
4294 'Invalid JSON-LD syntax; @context @container value must be ' +
4295 'one of the following: @list, @set, @index, or @language.',
4296 'jsonld.SyntaxError', {context: localCtx});
4297 }
4298104 if(mapping.reverse && container !== '@index') {
42990 throw new JsonLdError(
4300 'Invalid JSON-LD syntax; @context @container value for a @reverse ' +
4301 'type definition must be @index.',
4302 'jsonld.SyntaxError', {context: localCtx});
4303 }
4304
4305 // add @container to mapping
4306104 mapping['@container'] = container;
4307 }
4308
4309476 if('@language' in value && !('@type' in value)) {
431016 var language = value['@language'];
431116 if(language !== null && !_isString(language)) {
43120 throw new JsonLdError(
4313 'Invalid JSON-LD syntax; @context @language value must be ' +
4314 'a string or null.',
4315 'jsonld.SyntaxError', {context: localCtx});
4316 }
4317
4318 // add @language to mapping
431916 if(language !== null) {
432010 language = language.toLowerCase();
4321 }
432216 mapping['@language'] = language;
4323 }
4324
4325 // define term mapping
4326476 activeCtx.mappings[term] = mapping;
4327476 defined[term] = true;
4328}
4329
4330/**
4331 * Expands a string to a full IRI. The string may be a term, a prefix, a
4332 * relative IRI, or an absolute IRI. The associated absolute IRI will be
4333 * returned.
4334 *
4335 * @param activeCtx the current active context.
4336 * @param value the string to expand.
4337 * @param relativeTo options for how to resolve relative IRIs:
4338 * base: true to resolve against the base IRI, false not to.
4339 * vocab: true to concatenate after @vocab, false not to.
4340 * @param localCtx the local context being processed (only given if called
4341 * during context processing).
4342 * @param defined a map for tracking cycles in context definitions (only given
4343 * if called during context processing).
4344 *
4345 * @return the expanded value.
4346 */
43472function _expandIri(activeCtx, value, relativeTo, localCtx, defined) {
4348 // already expanded
43499504 if(value === null || _isKeyword(value)) {
43504270 return value;
4351 }
4352
4353 // define term dependency if not defined
43545234 if(localCtx && value in localCtx && defined[value] !== true) {
43551 _createTermDefinition(activeCtx, localCtx, value, defined);
4356 }
4357
43585234 relativeTo = relativeTo || {};
43595234 if(relativeTo.vocab) {
43604264 var mapping = activeCtx.mappings[value];
4361
4362 // value is explicitly ignored with a null mapping
43634264 if(mapping === null) {
43644 return null;
4365 }
4366
43674260 if(mapping) {
4368 // value is a term
43692167 return mapping['@id'];
4370 }
4371 }
4372
4373 // split value into prefix:suffix
43743063 var colon = value.indexOf(':');
43753063 if(colon !== -1) {
43762879 var prefix = value.substr(0, colon);
43772879 var suffix = value.substr(colon + 1);
4378
4379 // do not expand blank nodes (prefix of '_') or already-absolute
4380 // IRIs (suffix of '//')
43812879 if(prefix === '_' || suffix.indexOf('//') === 0) {
43822149 return value;
4383 }
4384
4385 // prefix dependency not defined, define it
4386730 if(localCtx && prefix in localCtx) {
438755 _createTermDefinition(activeCtx, localCtx, prefix, defined);
4388 }
4389
4390 // use mapping if prefix is defined
4391730 var mapping = activeCtx.mappings[prefix];
4392730 if(mapping) {
4393728 return mapping['@id'] + suffix;
4394 }
4395
4396 // already absolute IRI
43972 return value;
4398 }
4399
4400 // prepend vocab
4401184 if(relativeTo.vocab && '@vocab' in activeCtx) {
440249 return activeCtx['@vocab'] + value;
4403 }
4404
4405 // prepend base
4406135 var rval = value;
4407135 if(relativeTo.base) {
4408105 rval = _prependBase(activeCtx['@base'], rval);
4409 }
4410
4411135 if(localCtx) {
4412 // value must now be an absolute IRI
44130 if(!_isAbsoluteIri(rval)) {
44140 throw new JsonLdError(
4415 'Invalid JSON-LD syntax; a @context value does not expand to ' +
4416 'an absolute IRI.',
4417 'jsonld.SyntaxError', {context: localCtx, value: value});
4418 }
4419 }
4420
4421135 return rval;
4422}
4423
4424/**
4425 * Prepends a base IRI to the given relative IRI.
4426 *
4427 * @param base the base IRI.
4428 * @param iri the relative IRI.
4429 *
4430 * @return the absolute IRI.
4431 */
44322function _prependBase(base, iri) {
4433 // already an absolute IRI
4434105 if(iri.indexOf(':') !== -1) {
44350 return iri;
4436 }
4437
4438 // parse base if it is a string
4439105 if(_isString(base)) {
44400 base = jsonld.url.parse(base || '');
4441 }
4442
4443 // parse given IRI
4444105 var rel = jsonld.url.parse(iri);
4445
4446 // start hierarchical part
4447105 var hierPart = (base.protocol || '');
4448105 if(rel.authority) {
44498 hierPart += '//' + rel.authority;
4450 }
445197 else if(base.href !== '') {
445297 hierPart += '//' + base.authority;
4453 }
4454
4455 // per RFC3986 normalize
4456105 var path;
4457
4458 // IRI represents an absolute path
4459105 if(rel.pathname.indexOf('/') === 0) {
446016 path = rel.pathname;
4461 }
4462 else {
446389 path = base.pathname;
4464
4465 // append relative path to the end of the last directory from base
446689 if(rel.pathname !== '') {
446769 path = path.substr(0, path.lastIndexOf('/') + 1);
446869 if(path.length > 0 && path.substr(-1) !== '/') {
44690 path += '/';
4470 }
447169 path += rel.pathname;
4472 }
4473 }
4474
4475 // remove slashes and dots in path
4476105 path = _removeDotSegments(path, hierPart !== '');
4477
4478 // add query and hash
4479105 if(rel.query) {
44806 path += '?' + rel.query;
4481 }
4482105 if(rel.hash) {
448313 path += rel.hash;
4484 }
4485
4486105 var rval = hierPart + path;
4487
4488105 if(rval === '') {
44890 rval = './';
4490 }
4491
4492105 return rval;
4493}
4494
4495/**
4496 * Removes a base IRI from the given absolute IRI.
4497 *
4498 * @param base the base IRI.
4499 * @param iri the absolute IRI.
4500 *
4501 * @return the relative IRI if relative to base, otherwise the absolute IRI.
4502 */
45032function _removeBase(base, iri) {
4504174 if(_isString(base)) {
45050 base = jsonld.url.parse(base || '');
4506 }
4507
4508 // establish base root
4509174 var root = '';
4510174 if(base.href !== '') {
4511174 root += (base.protocol || '') + '//' + base.authority;
4512 }
4513 // support network-path reference with empty base
45140 else if(iri.indexOf('//')) {
45150 root += '//';
4516 }
4517
4518 // IRI not relative to base
4519174 if(iri.indexOf(root) !== 0) {
4520158 return iri;
4521 }
4522
4523 // remove root from IRI and parse remainder
452416 var rel = jsonld.url.parse(iri.substr(root.length));
4525
4526 // remove path segments that match
452716 var baseSegments = base.normalizedPath.split('/');
452816 var iriSegments = rel.normalizedPath.split('/');
4529
453016 while(baseSegments.length > 0 && iriSegments.length > 0) {
453152 if(baseSegments[0] !== iriSegments[0]) {
453214 break;
4533 }
453438 baseSegments.shift();
453538 iriSegments.shift();
4536 }
4537
4538 // use '../' for each non-matching base segment
453916 var rval = '';
454016 if(baseSegments.length > 0) {
4541 // do not count the last segment if it isn't a path (doesn't end in '/')
454214 if(base.normalizedPath.substr(-1) !== '/') {
454314 baseSegments.pop();
4544 }
454514 for(var i = 0; i < baseSegments.length; ++i) {
454612 rval += '../';
4547 }
4548 }
4549
4550 // prepend remaining segments
455116 rval += iriSegments.join('/');
4552
4553 // add query and hash
455416 if(rel.query) {
45551 rval += '?' + rel.query;
4556 }
455716 if(rel.hash) {
45582 rval += rel.hash;
4559 }
4560
456116 if(rval === '') {
45621 rval = './';
4563 }
4564
456516 return rval;
4566}
4567
4568/**
4569 * Gets the initial context.
4570 *
4571 * @param options the options to use.
4572 * base the document base IRI.
4573 *
4574 * @return the initial context.
4575 */
45762function _getInitialContext(options) {
4577392 var base = jsonld.url.parse(options.base || '');
4578392 return {
4579 '@base': base,
4580 mappings: {},
4581 inverse: null,
4582 getInverse: _createInverseContext,
4583 clone: _cloneActiveContext
4584 };
4585
4586 /**
4587 * Generates an inverse context for use in the compaction algorithm, if
4588 * not already generated for the given active context.
4589 *
4590 * @return the inverse context.
4591 */
45920 function _createInverseContext() {
4593952 var activeCtx = this;
4594
4595 // lazily create inverse
4596952 if(activeCtx.inverse) {
4597868 return activeCtx.inverse;
4598 }
459984 var inverse = activeCtx.inverse = {};
4600
4601 // handle default language
460284 var defaultLanguage = activeCtx['@language'] || '@none';
4603
4604 // create term selections for each mapping in the context, ordered by
4605 // shortest and then lexicographically least
460684 var mappings = activeCtx.mappings;
460784 var terms = Object.keys(mappings).sort(_compareShortestLeast);
460884 for(var i = 0; i < terms.length; ++i) {
4609273 var term = terms[i];
4610273 var mapping = mappings[term];
4611273 if(mapping === null) {
46121 continue;
4613 }
4614
4615272 var container = mapping['@container'] || '@none';
4616
4617 // iterate over every IRI in the mapping
4618272 var ids = mapping['@id'];
4619272 if(!_isArray(ids)) {
4620272 ids = [ids];
4621 }
4622272 for(var ii = 0; ii < ids.length; ++ii) {
4623272 var iri = ids[ii];
4624272 var entry = inverse[iri];
4625
4626 // initialize entry
4627272 if(!entry) {
4628248 inverse[iri] = entry = {};
4629 }
4630
4631 // add new entry
4632272 if(!entry[container]) {
4633254 entry[container] = {
4634 '@language': {},
4635 '@type': {}
4636 };
4637 }
4638272 entry = entry[container];
4639
4640 // term is preferred for values using @reverse
4641272 if(mapping.reverse) {
46425 _addPreferredTerm(mapping, term, entry['@type'], '@reverse');
4643 }
4644 // term is preferred for values using specific type
4645267 else if('@type' in mapping) {
4646109 _addPreferredTerm(mapping, term, entry['@type'], mapping['@type']);
4647 }
4648 // term is preferred for values using specific language
4649158 else if('@language' in mapping) {
46509 var language = mapping['@language'] || '@null';
46519 _addPreferredTerm(mapping, term, entry['@language'], language);
4652 }
4653 // term is preferred for values w/default language or no type and
4654 // no language
4655 else {
4656 // add an entry for the default language
4657149 _addPreferredTerm(mapping, term, entry['@language'], defaultLanguage);
4658
4659 // add entries for no type and no language
4660149 _addPreferredTerm(mapping, term, entry['@type'], '@none');
4661149 _addPreferredTerm(mapping, term, entry['@language'], '@none');
4662 }
4663 }
4664 }
4665
466684 return inverse;
4667 }
4668
4669 /**
4670 * Adds the term for the given entry if not already added.
4671 *
4672 * @param mapping the term mapping.
4673 * @param term the term to add.
4674 * @param entry the inverse context typeOrLanguage entry to add to.
4675 * @param typeOrLanguageValue the key in the entry to add to.
4676 */
46770 function _addPreferredTerm(mapping, term, entry, typeOrLanguageValue) {
4678570 if(!(typeOrLanguageValue in entry)) {
4679438 entry[typeOrLanguageValue] = term;
4680 }
4681 }
4682
4683 /**
4684 * Clones an active context, creating a child active context.
4685 *
4686 * @return a clone (child) of the active context.
4687 */
46880 function _cloneActiveContext() {
4689317 var child = {};
4690317 child['@base'] = this['@base'];
4691317 child.mappings = _clone(this.mappings);
4692317 child.clone = this.clone;
4693317 child.inverse = null;
4694317 child.getInverse = this.getInverse;
4695317 if('@language' in this) {
46961 child['@language'] = this['@language'];
4697 }
4698317 if('@vocab' in this) {
46991 child['@vocab'] = this['@vocab'];
4700 }
4701317 return child;
4702 }
4703}
4704
4705/**
4706 * Returns whether or not the given value is a keyword.
4707 *
4708 * @param v the value to check.
4709 *
4710 * @return true if the value is a keyword, false if not.
4711 */
47122function _isKeyword(v) {
471318497 if(!_isString(v)) {
47142 return false;
4715 }
471618495 switch(v) {
4717 case '@base':
4718 case '@context':
4719 case '@container':
4720 case '@default':
4721 case '@embed':
4722 case '@explicit':
4723 case '@graph':
4724 case '@id':
4725 case '@index':
4726 case '@language':
4727 case '@list':
4728 case '@omitDefault':
4729 case '@preserve':
4730 case '@reverse':
4731 case '@set':
4732 case '@type':
4733 case '@value':
4734 case '@vocab':
47358418 return true;
4736 }
473710077 return false;
4738}
4739
4740/**
4741 * Returns true if the given value is an Object.
4742 *
4743 * @param v the value to check.
4744 *
4745 * @return true if the value is an Object, false if not.
4746 */
47472function _isObject(v) {
474828580 return (Object.prototype.toString.call(v) === '[object Object]');
4749}
4750
4751/**
4752 * Returns true if the given value is an empty Object.
4753 *
4754 * @param v the value to check.
4755 *
4756 * @return true if the value is an empty Object, false if not.
4757 */
47582function _isEmptyObject(v) {
475930 return _isObject(v) && Object.keys(v).length === 0;
4760}
4761
4762/**
4763 * Returns true if the given value is an Array.
4764 *
4765 * @param v the value to check.
4766 *
4767 * @return true if the value is an Array, false if not.
4768 */
47692function _isArray(v) {
477037211 return Array.isArray(v);
4771}
4772
4773/**
4774 * Throws an exception if the given value is not a valid @type value.
4775 *
4776 * @param v the value to check.
4777 */
47782function _validateTypeValue(v) {
4779 // can be a string or an empty object
4780206 if(_isString(v) || _isEmptyObject(v)) {
4781177 return;
4782 }
4783
4784 // must be an array
478529 var isValid = false;
478629 if(_isArray(v)) {
4787 // must contain only strings
478829 isValid = true;
478929 for(var i in v) {
479094 if(!(_isString(v[i]))) {
47910 isValid = false;
47920 break;
4793 }
4794 }
4795 }
4796
479729 if(!isValid) {
47980 throw new JsonLdError(
4799 'Invalid JSON-LD syntax; "@type" value must a string, an array of ' +
4800 'strings, or an empty object.', 'jsonld.SyntaxError', {value: v});
4801 }
4802}
4803
4804/**
4805 * Returns true if the given value is a String.
4806 *
4807 * @param v the value to check.
4808 *
4809 * @return true if the value is a String, false if not.
4810 */
48112function _isString(v) {
481225421 return (typeof v === 'string' ||
4813 Object.prototype.toString.call(v) === '[object String]');
4814}
4815
4816/**
4817 * Returns true if the given value is a Number.
4818 *
4819 * @param v the value to check.
4820 *
4821 * @return true if the value is a Number, false if not.
4822 */
48232function _isNumber(v) {
4824148 return (typeof v === 'number' ||
4825 Object.prototype.toString.call(v) === '[object Number]');
4826}
4827
4828/**
4829 * Returns true if the given value is a double.
4830 *
4831 * @param v the value to check.
4832 *
4833 * @return true if the value is a double, false if not.
4834 */
48352function _isDouble(v) {
483675 return _isNumber(v) && String(v).indexOf('.') !== -1;
4837}
4838
4839/**
4840 * Returns true if the given value is numeric.
4841 *
4842 * @param v the value to check.
4843 *
4844 * @return true if the value is numeric, false if not.
4845 */
48462function _isNumeric(v) {
484717 return !isNaN(parseFloat(v)) && isFinite(v);
4848}
4849
4850/**
4851 * Returns true if the given value is a Boolean.
4852 *
4853 * @param v the value to check.
4854 *
4855 * @return true if the value is a Boolean, false if not.
4856 */
48572function _isBoolean(v) {
485877 return (typeof v === 'boolean' ||
4859 Object.prototype.toString.call(v) === '[object Boolean]');
4860}
4861
4862/**
4863 * Returns true if the given value is undefined.
4864 *
4865 * @param v the value to check.
4866 *
4867 * @return true if the value is undefined, false if not.
4868 */
48692function _isUndefined(v) {
48705461 return (typeof v === 'undefined');
4871}
4872
4873/**
4874 * Returns true if the given value is a subject with properties.
4875 *
4876 * @param v the value to check.
4877 *
4878 * @return true if the value is a subject with properties, false if not.
4879 */
48802function _isSubject(v) {
4881 // Note: A value is a subject if all of these hold true:
4882 // 1. It is an Object.
4883 // 2. It is not a @value, @set, or @list.
4884 // 3. It has more than 1 key OR any existing key is not @id.
4885762 var rval = false;
4886762 if(_isObject(v) &&
4887 !(('@value' in v) || ('@set' in v) || ('@list' in v))) {
4888358 var keyCount = Object.keys(v).length;
4889358 rval = (keyCount > 1 || !('@id' in v));
4890 }
4891762 return rval;
4892}
4893
4894/**
4895 * Returns true if the given value is a subject reference.
4896 *
4897 * @param v the value to check.
4898 *
4899 * @return true if the value is a subject reference, false if not.
4900 */
49012function _isSubjectReference(v) {
4902 // Note: A value is a subject reference if all of these hold true:
4903 // 1. It is an Object.
4904 // 2. It has a single key: @id.
49051256 return (_isObject(v) && Object.keys(v).length === 1 && ('@id' in v));
4906}
4907
4908/**
4909 * Returns true if the given value is a @value.
4910 *
4911 * @param v the value to check.
4912 *
4913 * @return true if the value is a @value, false if not.
4914 */
49152function _isValue(v) {
4916 // Note: A value is a @value if all of these hold true:
4917 // 1. It is an Object.
4918 // 2. It has the @value property.
49193581 return _isObject(v) && ('@value' in v);
4920}
4921
4922/**
4923 * Returns true if the given value is a @list.
4924 *
4925 * @param v the value to check.
4926 *
4927 * @return true if the value is a @list, false if not.
4928 */
49292function _isList(v) {
4930 // Note: A value is a @list if all of these hold true:
4931 // 1. It is an Object.
4932 // 2. It has the @list property.
49334257 return _isObject(v) && ('@list' in v);
4934}
4935
4936/**
4937 * Returns true if the given value is a blank node.
4938 *
4939 * @param v the value to check.
4940 *
4941 * @return true if the value is a blank node, false if not.
4942 */
49432function _isBlankNode(v) {
4944 // Note: A value is a blank node if all of these hold true:
4945 // 1. It is an Object.
4946 // 2. If it has an @id key its value begins with '_:'.
4947 // 3. It has no keys OR is not a @value, @set, or @list.
4948690 var rval = false;
4949690 if(_isObject(v)) {
4950690 if('@id' in v) {
4951652 rval = (v['@id'].indexOf('_:') === 0);
4952 }
4953 else {
495438 rval = (Object.keys(v).length === 0 ||
4955 !(('@value' in v) || ('@set' in v) || ('@list' in v)));
4956 }
4957 }
4958690 return rval;
4959}
4960
4961/**
4962 * Returns true if the given value is an absolute IRI, false if not.
4963 *
4964 * @param v the value to check.
4965 *
4966 * @return true if the value is an absolute IRI, false if not.
4967 */
49682function _isAbsoluteIri(v) {
49692290 return _isString(v) && v.indexOf(':') !== -1;
4970}
4971
4972/**
4973 * Clones an object, array, or string/number.
4974 *
4975 * @param value the value to clone.
4976 *
4977 * @return the cloned value.
4978 */
49792function _clone(value) {
498067273 if(value && typeof value === 'object') {
498112365 var rval = _isArray(value) ? [] : {};
498212365 for(var i in value) {
498357168 rval[i] = _clone(value[i]);
4984 }
498512365 return rval;
4986 }
498754908 return value;
4988}
4989
4990/**
4991 * Finds all @context URLs in the given JSON-LD input.
4992 *
4993 * @param input the JSON-LD input.
4994 * @param urls a map of URLs (url => false/@contexts).
4995 * @param replace true to replace the URLs in the given input with the
4996 * @contexts from the urls map, false not to.
4997 * @param base the base IRI to use to resolve relative IRIs.
4998 *
4999 * @return true if new URLs to retrieve were found, false if not.
5000 */
50012function _findContextUrls(input, urls, replace, base) {
50027830 var count = Object.keys(urls).length;
50037830 if(_isArray(input)) {
50041058 for(var i in input) {
50052406 _findContextUrls(input[i], urls, replace, base);
5006 }
50071058 return (count < Object.keys(urls).length);
5008 }
50096772 else if(_isObject(input)) {
50102460 for(var key in input) {
50115298 if(key !== '@context') {
50124646 _findContextUrls(input[key], urls, replace, base);
50134646 continue;
5014 }
5015
5016 // get @context
5017652 var ctx = input[key];
5018
5019 // array @context
5020652 if(_isArray(ctx)) {
50218 var length = ctx.length;
50228 for(var i = 0; i < length; ++i) {
502316 var _ctx = ctx[i];
502416 if(_isString(_ctx)) {
50250 _ctx = _prependBase(base, _ctx);
5026 // replace w/@context if requested
50270 if(replace) {
50280 _ctx = urls[_ctx];
50290 if(_isArray(_ctx)) {
5030 // add flattened context
50310 Array.prototype.splice.apply(ctx, [i, 1].concat(_ctx));
50320 i += _ctx.length;
50330 length += _ctx.length;
5034 }
5035 else {
50360 ctx[i] = _ctx;
5037 }
5038 }
5039 // @context URL found
50400 else if(!(_ctx in urls)) {
50410 urls[_ctx] = false;
5042 }
5043 }
5044 }
5045 }
5046 // string @context
5047644 else if(_isString(ctx)) {
50480 ctx = _prependBase(base, ctx);
5049 // replace w/@context if requested
50500 if(replace) {
50510 input[key] = urls[ctx];
5052 }
5053 // @context URL found
50540 else if(!(ctx in urls)) {
50550 urls[ctx] = false;
5056 }
5057 }
5058 }
50592460 return (count < Object.keys(urls).length);
5060 }
50614312 return false;
5062}
5063
5064/**
5065 * Retrieves external @context URLs using the given context loader. Every
5066 * instance of @context in the input that refers to a URL will be replaced
5067 * with the JSON @context found at that URL.
5068 *
5069 * @param input the JSON-LD input with possible contexts.
5070 * @param options the options to use:
5071 * loadContext(url, callback(err, url, result)) the context loader.
5072 * @param callback(err, input) called once the operation completes.
5073 */
50742function _retrieveContextUrls(input, options, callback) {
5075 // if any error occurs during URL resolution, quit
5076389 var error = null;
5077389 var regex = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
5078
5079 // recursive context loader
5080389 var loadContext = options.loadContext;
5081389 var retrieve = function(input, cycles, loadContext, base, callback) {
5082389 if(Object.keys(cycles).length > MAX_CONTEXT_URLS) {
50830 error = new JsonLdError(
5084 'Maximum number of @context URLs exceeded.',
5085 'jsonld.ContextUrlError', {max: MAX_CONTEXT_URLS});
50860 return callback(error);
5087 }
5088
5089 // for tracking the URLs to retrieve
5090389 var urls = {};
5091
5092 // finished will be called once the URL queue is empty
5093389 var finished = function() {
5094 // replace all URLs in the input
5095389 _findContextUrls(input, urls, true, base);
5096389 callback(null, input);
5097 };
5098
5099 // find all URLs in the given input
5100389 if(!_findContextUrls(input, urls, false, base)) {
5101 // no new URLs in input
5102389 finished();
5103 }
5104
5105 // queue all unretrieved URLs
5106389 var queue = [];
5107389 for(var url in urls) {
51080 if(urls[url] === false) {
5109 // validate URL
51100 if(!regex.test(url)) {
51110 error = new JsonLdError(
5112 'Malformed URL.', 'jsonld.InvalidUrl', {url: url});
51130 return callback(error);
5114 }
51150 queue.push(url);
5116 }
5117 }
5118
5119 // retrieve URLs in queue
5120389 var count = queue.length;
5121389 for(var i in queue) {
51220 (function(url) {
5123 // check for context URL cycle
51240 if(url in cycles) {
51250 error = new JsonLdError(
5126 'Cyclical @context URLs detected.',
5127 'jsonld.ContextUrlError', {url: url});
51280 return callback(error);
5129 }
51300 var _cycles = _clone(cycles);
51310 _cycles[url] = true;
5132
51330 loadContext(url, function(err, finalUrl, ctx) {
5134 // short-circuit if there was an error with another URL
51350 if(error) {
51360 return;
5137 }
5138
5139 // parse string context as JSON
51400 if(!err && _isString(ctx)) {
51410 try {
51420 ctx = JSON.parse(ctx);
5143 }
5144 catch(ex) {
51450 err = ex;
5146 }
5147 }
5148
5149 // ensure ctx is an object
51500 if(err) {
51510 err = new JsonLdError(
5152 'Derefencing a URL did not result in a valid JSON-LD object. ' +
5153 'Possible causes are an inaccessible URL perhaps due to ' +
5154 'a same-origin policy (ensure the server uses CORS if you are ' +
5155 'using client-side JavaScript), too many redirects, or a ' +
5156 'non-JSON response.',
5157 'jsonld.InvalidUrl', {url: url, cause: err});
5158 }
51590 else if(!_isObject(ctx)) {
51600 err = new JsonLdError(
5161 'Derefencing a URL did not result in a JSON object. The ' +
5162 'response was valid JSON, but it was not a JSON object.',
5163 'jsonld.InvalidUrl', {url: url, cause: err});
5164 }
51650 if(err) {
51660 error = err;
51670 return callback(error);
5168 }
5169
5170 // use empty context if no @context key is present
51710 if(!('@context' in ctx)) {
51720 ctx = {'@context': {}};
5173 }
5174
5175 // recurse
51760 retrieve(ctx, _cycles, loadContext, url, function(err, ctx) {
51770 if(err) {
51780 return callback(err);
5179 }
51800 urls[url] = ctx['@context'];
51810 count -= 1;
51820 if(count === 0) {
51830 finished();
5184 }
5185 });
5186 });
5187 }(queue[i]));
5188 }
5189 };
5190389 retrieve(input, {}, loadContext, options.base, callback);
5191}
5192
5193// define js 1.8.5 Object.keys method if not present
51942if(!Object.keys) {
51950 Object.keys = function(o) {
51960 if(o !== Object(o)) {
51970 throw new TypeError('Object.keys called on non-object');
5198 }
51990 var rval = [];
52000 for(var p in o) {
52010 if(Object.prototype.hasOwnProperty.call(o, p)) {
52020 rval.push(p);
5203 }
5204 }
52050 return rval;
5206 };
5207}
5208
5209/**
5210 * Parses RDF in the form of N-Quads.
5211 *
5212 * @param input the N-Quads input to parse.
5213 *
5214 * @return an RDF dataset.
5215 */
52162function _parseNQuads(input) {
5217 // define partial regexes
52187 var iri = '(?:<([^:]+:[^>]*)>)';
52197 var bnode = '(_:(?:[A-Za-z][A-Za-z0-9]*))';
52207 var plain = '"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"';
52217 var datatype = '(?:\\^\\^' + iri + ')';
52227 var language = '(?:@([a-z]+(?:-[a-z0-9]+)*))';
52237 var literal = '(?:' + plain + '(?:' + datatype + '|' + language + ')?)';
52247 var ws = '[ \\t]+';
52257 var wso = '[ \\t]*';
52267 var eoln = /(?:\r\n)|(?:\n)|(?:\r)/g;
52277 var empty = new RegExp('^' + wso + '$');
5228
5229 // define quad part regexes
52307 var subject = '(?:' + iri + '|' + bnode + ')' + ws;
52317 var property = iri + ws;
52327 var object = '(?:' + iri + '|' + bnode + '|' + literal + ')' + wso;
52337 var graphName = '(?:\\.|(?:(?:' + iri + '|' + bnode + ')' + wso + '\\.))';
5234
5235 // full quad regex
52367 var quad = new RegExp(
5237 '^' + wso + subject + property + object + graphName + wso + '$');
5238
5239 // build RDF dataset
52407 var dataset = {};
5241
5242 // split N-Quad input into lines
52437 var lines = input.split(eoln);
52447 var lineNumber = 0;
52457 for(var li = 0; li < lines.length; ++li) {
524661 var line = lines[li];
524761 lineNumber++;
5248
5249 // skip empty lines
525061 if(empty.test(line)) {
52517 continue;
5252 }
5253
5254 // parse quad
525554 var match = line.match(quad);
525654 if(match === null) {
52570 throw new JsonLdError(
5258 'Error while parsing N-Quads; invalid quad.',
5259 'jsonld.ParseError', {line: lineNumber});
5260 }
5261
5262 // create RDF triple
526354 var triple = {};
5264
5265 // get subject
526654 if(!_isUndefined(match[1])) {
526735 triple.subject = {type: 'IRI', value: match[1]};
5268 }
5269 else {
527019 triple.subject = {type: 'blank node', value: match[2]};
5271 }
5272
5273 // get predicate
527454 triple.predicate = {type: 'IRI', value: match[3]};
5275
5276 // get object
527754 if(!_isUndefined(match[4])) {
527824 triple.object = {type: 'IRI', value: match[4]};
5279 }
528030 else if(!_isUndefined(match[5])) {
528110 triple.object = {type: 'blank node', value: match[5]};
5282 }
5283 else {
528420 triple.object = {type: 'literal'};
528520 if(!_isUndefined(match[7])) {
52867 triple.object.datatype = match[7];
5287 }
528813 else if(!_isUndefined(match[8])) {
52891 triple.object.datatype = RDF_LANGSTRING;
52901 triple.object.language = match[8];
5291 }
5292 else {
529312 triple.object.datatype = XSD_STRING;
5294 }
529520 var unescaped = match[6]
5296 .replace(/\\"/g, '"')
5297 .replace(/\\t/g, '\t')
5298 .replace(/\\n/g, '\n')
5299 .replace(/\\r/g, '\r')
5300 .replace(/\\\\/g, '\\');
530120 triple.object.value = unescaped;
5302 }
5303
5304 // get graph name ('@default' is used for the default graph)
530554 var name = '@default';
530654 if(!_isUndefined(match[9])) {
530724 name = match[9];
5308 }
530930 else if(!_isUndefined(match[10])) {
53100 name = match[10];
5311 }
5312
5313 // initialize graph in dataset
531454 if(!(name in dataset)) {
531511 dataset[name] = [triple];
5316 }
5317 // add triple if unique to its graph
5318 else {
531943 var unique = true;
532043 var triples = dataset[name];
532143 for(var ti = 0; unique && ti < triples.length; ++ti) {
5322146 if(_compareRDFTriples(triples[ti], triple)) {
53230 unique = false;
5324 }
5325 }
532643 if(unique) {
532743 triples.push(triple);
5328 }
5329 }
5330 }
5331
53327 return dataset;
5333}
5334
5335// register the N-Quads RDF parser
53362jsonld.registerRDFParser('application/nquads', _parseNQuads);
5337
5338/**
5339 * Converts an RDF dataset to N-Quads.
5340 *
5341 * @param dataset the RDF dataset to convert.
5342 *
5343 * @return the N-Quads string.
5344 */
53452function _toNQuads(dataset) {
534630 var quads = [];
534730 for(var graphName in dataset) {
534836 var triples = dataset[graphName];
534936 for(var ti = 0; ti < triples.length; ++ti) {
535066 var triple = triples[ti];
535166 if(graphName === '@default') {
535230 graphName = null;
5353 }
535466 quads.push(_toNQuad(triple, graphName));
5355 }
5356 }
535730 quads.sort();
535830 return quads.join('');
5359}
5360
5361/**
5362 * Converts an RDF triple and graph name to an N-Quad string (a single quad).
5363 *
5364 * @param triple the RDF triple to convert.
5365 * @param graphName the name of the graph containing the triple, null for
5366 * the default graph.
5367 * @param bnode the bnode the quad is mapped to (optional, for use
5368 * during normalization only).
5369 *
5370 * @return the N-Quad string.
5371 */
53722function _toNQuad(triple, graphName, bnode) {
5373863 var s = triple.subject;
5374863 var p = triple.predicate;
5375863 var o = triple.object;
5376863 var g = graphName;
5377
5378863 var quad = '';
5379
5380 // subject is an IRI or bnode
5381863 if(s.type === 'IRI') {
5382107 quad += '<' + s.value + '>';
5383 }
5384 // normalization mode
5385756 else if(bnode) {
5386480 quad += (s.value === bnode) ? '_:a' : '_:z';
5387 }
5388 // normal mode
5389 else {
5390276 quad += s.value;
5391 }
5392
5393 // predicate is always an IRI
5394863 quad += ' <' + p.value + '> ';
5395
5396 // object is IRI, bnode, or literal
5397863 if(o.type === 'IRI') {
539871 quad += '<' + o.value + '>';
5399 }
5400792 else if(o.type === 'blank node') {
5401 // normalization mode
5402691 if(bnode) {
5403453 quad += (o.value === bnode) ? '_:a' : '_:z';
5404 }
5405 // normal mode
5406 else {
5407238 quad += o.value;
5408 }
5409 }
5410 else {
5411101 var escaped = o.value
5412 .replace(/\\/g, '\\\\')
5413 .replace(/\t/g, '\\t')
5414 .replace(/\n/g, '\\n')
5415 .replace(/\r/g, '\\r')
5416 .replace(/\"/g, '\\"');
5417101 quad += '"' + escaped + '"';
5418101 if(o.datatype === RDF_LANGSTRING) {
54193 quad += '@' + o.language;
5420 }
542198 else if(o.datatype !== XSD_STRING) {
542216 quad += '^^<' + o.datatype + '>';
5423 }
5424 }
5425
5426 // graph
5427863 if(g !== null) {
542818 if(g.indexOf('_:') !== 0) {
542912 quad += ' <' + g + '>';
5430 }
54316 else if(bnode) {
54324 quad += ' _:g';
5433 }
5434 else {
54352 quad += ' ' + g;
5436 }
5437 }
5438
5439863 quad += ' .\n';
5440863 return quad;
5441}
5442
5443/**
5444 * Parses the RDF dataset found via the data object from the RDFa API.
5445 *
5446 * @param data the RDFa API data object.
5447 *
5448 * @return the RDF dataset.
5449 */
54502function _parseRdfaApiData(data) {
54510 var dataset = {};
54520 dataset['@default'] = [];
5453
54540 var subjects = data.getSubjects();
54550 for(var si = 0; si < subjects.length; ++si) {
54560 var subject = subjects[si];
54570 if(subject === null) {
54580 continue;
5459 }
5460
5461 // get all related triples
54620 var triples = data.getSubjectTriples(subject);
54630 if(triples === null) {
54640 continue;
5465 }
54660 var predicates = triples.predicates;
54670 for(var predicate in predicates) {
5468 // iterate over objects
54690 var objects = predicates[predicate].objects;
54700 for(var oi = 0; oi < objects.length; ++oi) {
54710 var object = objects[oi];
5472
5473 // create RDF triple
54740 var triple = {};
5475
5476 // add subject
54770 if(subject.indexOf('_:') === 0) {
54780 triple.subject = {type: 'blank node', value: subject};
5479 }
5480 else {
54810 triple.subject = {type: 'IRI', value: subject};
5482 }
5483
5484 // add predicate
54850 triple.predicate = {type: 'IRI', value: predicate};
5486
5487 // serialize XML literal
54880 var value = object.value;
54890 if(object.type === RDF_XML_LITERAL) {
5490 // initialize XMLSerializer
54910 if(!XMLSerializer) {
54920 _defineXMLSerializer();
5493 }
54940 var serializer = new XMLSerializer();
54950 value = '';
54960 for(var x = 0; x < object.value.length; x++) {
54970 if(object.value[x].nodeType === Node.ELEMENT_NODE) {
54980 value += serializer.serializeToString(object.value[x]);
5499 }
55000 else if(object.value[x].nodeType === Node.TEXT_NODE) {
55010 value += object.value[x].nodeValue;
5502 }
5503 }
5504 }
5505
5506 // add object
55070 triple.object = {};
5508
5509 // object is an IRI
55100 if(object.type === RDF_OBJECT) {
55110 if(object.value.indexOf('_:') === 0) {
55120 triple.object.type = 'blank node';
5513 }
5514 else {
55150 triple.object.type = 'IRI';
5516 }
5517 }
5518 // literal
5519 else {
55200 triple.object.type = 'literal';
55210 if(object.type === RDF_PLAIN_LITERAL) {
55220 if(object.language) {
55230 triple.object.datatype = RDF_LANGSTRING;
55240 triple.object.language = object.language;
5525 }
5526 else {
55270 triple.object.datatype = XSD_STRING;
5528 }
5529 }
5530 else {
55310 triple.object.datatype = object.type;
5532 }
5533 }
55340 triple.object.value = value;
5535
5536 // add triple to dataset in default graph
55370 dataset['@default'].push(triple);
5538 }
5539 }
5540 }
5541
55420 return dataset;
5543}
5544
5545// register the RDFa API RDF parser
55462jsonld.registerRDFParser('rdfa-api', _parseRdfaApiData);
5547
5548/**
5549 * Creates a new UniqueNamer. A UniqueNamer issues unique names, keeping
5550 * track of any previously issued names.
5551 *
5552 * @param prefix the prefix to use ('<prefix><counter>').
5553 */
55542function UniqueNamer(prefix) {
55559366 this.prefix = prefix;
55569366 this.counter = 0;
55579366 this.existing = {};
55582};
5559
5560/**
5561 * Copies this UniqueNamer.
5562 *
5563 * @return a copy of this UniqueNamer.
5564 */
55652UniqueNamer.prototype.clone = function() {
55668984 var copy = new UniqueNamer(this.prefix);
55678984 copy.counter = this.counter;
55688984 copy.existing = _clone(this.existing);
55698984 return copy;
5570};
5571
5572/**
5573 * Gets the new name for the given old name, where if no old name is given
5574 * a new name will be generated.
5575 *
5576 * @param [oldName] the old name to get the new name for.
5577 *
5578 * @return the new name.
5579 */
55802UniqueNamer.prototype.getName = function(oldName) {
5581 // return existing old name
558221283 if(oldName && oldName in this.existing) {
558319200 return this.existing[oldName];
5584 }
5585
5586 // get next name
55872083 var name = this.prefix + this.counter;
55882083 this.counter += 1;
5589
5590 // save mapping
55912083 if(oldName) {
55922035 this.existing[oldName] = name;
5593 }
5594
55952083 return name;
5596};
5597
5598/**
5599 * Returns true if the given oldName has already been assigned a new name.
5600 *
5601 * @param oldName the oldName to check.
5602 *
5603 * @return true if the oldName has been assigned a new name, false if not.
5604 */
56052UniqueNamer.prototype.isNamed = function(oldName) {
560638487 return (oldName in this.existing);
5607};
5608
5609/**
5610 * A Permutator iterates over all possible permutations of the given array
5611 * of elements.
5612 *
5613 * @param list the array of elements to iterate over.
5614 */
56152Permutator = function(list) {
5616 // original array
56178336 this.list = list.sort();
5618 // indicates whether there are more permutations
56198336 this.done = false;
5620 // directional info for permutation algorithm
56218336 this.left = {};
56228336 for(var i in list) {
56238768 this.left[list[i]] = true;
5624 }
5625};
5626
5627/**
5628 * Returns true if there is another permutation.
5629 *
5630 * @return true if there is another permutation, false if not.
5631 */
56322Permutator.prototype.hasNext = function() {
56338984 return !this.done;
5634};
5635
5636/**
5637 * Gets the next permutation. Call hasNext() to ensure there is another one
5638 * first.
5639 *
5640 * @return the next permutation.
5641 */
56422Permutator.prototype.next = function() {
5643 // copy current permutation
56448984 var rval = this.list.slice();
5645
5646 /* Calculate the next permutation using the Steinhaus-Johnson-Trotter
5647 permutation algorithm. */
5648
5649 // get largest mobile element k
5650 // (mobile: element is greater than the one it is looking at)
56518984 var k = null;
56528984 var pos = 0;
56538984 var length = this.list.length;
56548984 for(var i = 0; i < length; ++i) {
565510424 var element = this.list[i];
565610424 var left = this.left[element];
565710424 if((k === null || element > k) &&
5658 ((left && i > 0 && element > this.list[i - 1]) ||
5659 (!left && i < (length - 1) && element > this.list[i + 1]))) {
5660720 k = element;
5661720 pos = i;
5662 }
5663 }
5664
5665 // no more permutations
56668984 if(k === null) {
56678336 this.done = true;
5668 }
5669 else {
5670 // swap k and the element it is looking at
5671648 var swap = this.left[k] ? pos - 1 : pos + 1;
5672648 this.list[pos] = this.list[swap];
5673648 this.list[swap] = k;
5674
5675 // reverse the direction of all elements larger than k
5676648 for(var i = 0; i < length; ++i) {
56771656 if(this.list[i] > k) {
567872 this.left[this.list[i]] = !this.left[this.list[i]];
5679 }
5680 }
5681 }
5682
56838984 return rval;
5684};
5685
5686// SHA-1 API
56872var sha1 = jsonld.sha1 = {};
5688
56892if(_nodejs) {
56902 var crypto = require('crypto');
56912 sha1.create = function() {
569210475 var md = crypto.createHash('sha1');
569310475 return {
5694 update: function(data) {
569543467 md.update(data, 'utf8');
5696 },
5697 digest: function() {
569810475 return md.digest('hex');
5699 }
5700 };
5701 };
5702}
5703else {
57040 sha1.create = function() {
57050 return new sha1.MessageDigest();
5706 };
5707}
5708
5709/**
5710 * Hashes the given array of quads and returns its hexadecimal SHA-1 message
5711 * digest.
5712 *
5713 * @param nquads the list of serialized quads to hash.
5714 *
5715 * @return the hexadecimal SHA-1 message digest.
5716 */
57172sha1.hash = function(nquads) {
5718166 var md = sha1.create();
5719166 for(var i in nquads) {
5720491 md.update(nquads[i]);
5721 }
5722166 return md.digest();
5723};
5724
5725// only define sha1 MessageDigest for non-nodejs
57262if(!_nodejs) {
5727
5728/**
5729 * Creates a simple byte buffer for message digest operations.
5730 */
57310sha1.Buffer = function() {
57320 this.data = '';
57330 this.read = 0;
5734};
5735
5736/**
5737 * Puts a 32-bit integer into this buffer in big-endian order.
5738 *
5739 * @param i the 32-bit integer.
5740 */
57410sha1.Buffer.prototype.putInt32 = function(i) {
57420 this.data += (
5743 String.fromCharCode(i >> 24 & 0xFF) +
5744 String.fromCharCode(i >> 16 & 0xFF) +
5745 String.fromCharCode(i >> 8 & 0xFF) +
5746 String.fromCharCode(i & 0xFF));
5747};
5748
5749/**
5750 * Gets a 32-bit integer from this buffer in big-endian order and
5751 * advances the read pointer by 4.
5752 *
5753 * @return the word.
5754 */
57550sha1.Buffer.prototype.getInt32 = function() {
57560 var rval = (
5757 this.data.charCodeAt(this.read) << 24 ^
5758 this.data.charCodeAt(this.read + 1) << 16 ^
5759 this.data.charCodeAt(this.read + 2) << 8 ^
5760 this.data.charCodeAt(this.read + 3));
57610 this.read += 4;
57620 return rval;
5763};
5764
5765/**
5766 * Gets the bytes in this buffer.
5767 *
5768 * @return a string full of UTF-8 encoded characters.
5769 */
57700sha1.Buffer.prototype.bytes = function() {
57710 return this.data.slice(this.read);
5772};
5773
5774/**
5775 * Gets the number of bytes in this buffer.
5776 *
5777 * @return the number of bytes in this buffer.
5778 */
57790sha1.Buffer.prototype.length = function() {
57800 return this.data.length - this.read;
5781};
5782
5783/**
5784 * Compacts this buffer.
5785 */
57860sha1.Buffer.prototype.compact = function() {
57870 this.data = this.data.slice(this.read);
57880 this.read = 0;
5789};
5790
5791/**
5792 * Converts this buffer to a hexadecimal string.
5793 *
5794 * @return a hexadecimal string.
5795 */
57960sha1.Buffer.prototype.toHex = function() {
57970 var rval = '';
57980 for(var i = this.read; i < this.data.length; ++i) {
57990 var b = this.data.charCodeAt(i);
58000 if(b < 16) {
58010 rval += '0';
5802 }
58030 rval += b.toString(16);
5804 }
58050 return rval;
5806};
5807
5808/**
5809 * Creates a SHA-1 message digest object.
5810 *
5811 * @return a message digest object.
5812 */
58130sha1.MessageDigest = function() {
5814 // do initialization as necessary
58150 if(!_sha1.initialized) {
58160 _sha1.init();
5817 }
5818
58190 this.blockLength = 64;
58200 this.digestLength = 20;
5821 // length of message so far (does not including padding)
58220 this.messageLength = 0;
5823
5824 // input buffer
58250 this.input = new sha1.Buffer();
5826
5827 // for storing words in the SHA-1 algorithm
58280 this.words = new Array(80);
5829
5830 // SHA-1 state contains five 32-bit integers
58310 this.state = {
5832 h0: 0x67452301,
5833 h1: 0xEFCDAB89,
5834 h2: 0x98BADCFE,
5835 h3: 0x10325476,
5836 h4: 0xC3D2E1F0
5837 };
5838};
5839
5840/**
5841 * Updates the digest with the given string input.
5842 *
5843 * @param msg the message input to update with.
5844 */
58450sha1.MessageDigest.prototype.update = function(msg) {
5846 // UTF-8 encode message
58470 msg = unescape(encodeURIComponent(msg));
5848
5849 // update message length and input buffer
58500 this.messageLength += msg.length;
58510 this.input.data += msg;
5852
5853 // process input
58540 _sha1.update(this.state, this.words, this.input);
5855
5856 // compact input buffer every 2K or if empty
58570 if(this.input.read > 2048 || this.input.length() === 0) {
58580 this.input.compact();
5859 }
5860};
5861
5862/**
5863 * Produces the digest.
5864 *
5865 * @return the digest as a hexadecimal string.
5866 */
58670sha1.MessageDigest.prototype.digest = function() {
5868 /* Determine the number of bytes that must be added to the message
5869 to ensure its length is congruent to 448 mod 512. In other words,
5870 a 64-bit integer that gives the length of the message will be
5871 appended to the message and whatever the length of the message is
5872 plus 64 bits must be a multiple of 512. So the length of the
5873 message must be congruent to 448 mod 512 because 512 - 64 = 448.
5874
5875 In order to fill up the message length it must be filled with
5876 padding that begins with 1 bit followed by all 0 bits. Padding
5877 must *always* be present, so if the message length is already
5878 congruent to 448 mod 512, then 512 padding bits must be added. */
5879
5880 // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
5881 // _padding starts with 1 byte with first bit is set in it which
5882 // is byte value 128, then there may be up to 63 other pad bytes
58830 var len = this.messageLength;
58840 var padBytes = new sha1.Buffer();
58850 padBytes.data += this.input.bytes();
58860 padBytes.data += _sha1.padding.substr(0, 64 - ((len + 8) % 64));
5887
5888 /* Now append length of the message. The length is appended in bits
5889 as a 64-bit number in big-endian order. Since we store the length
5890 in bytes, we must multiply it by 8 (or left shift by 3). So here
5891 store the high 3 bits in the low end of the first 32-bits of the
5892 64-bit number and the lower 5 bits in the high end of the second
5893 32-bits. */
58940 padBytes.putInt32((len >>> 29) & 0xFF);
58950 padBytes.putInt32((len << 3) & 0xFFFFFFFF);
58960 _sha1.update(this.state, this.words, padBytes);
58970 var rval = new sha1.Buffer();
58980 rval.putInt32(this.state.h0);
58990 rval.putInt32(this.state.h1);
59000 rval.putInt32(this.state.h2);
59010 rval.putInt32(this.state.h3);
59020 rval.putInt32(this.state.h4);
59030 return rval.toHex();
5904};
5905
5906// private SHA-1 data
59070var _sha1 = {
5908 padding: null,
5909 initialized: false
5910};
5911
5912/**
5913 * Initializes the constant tables.
5914 */
59150_sha1.init = function() {
5916 // create padding
59170 _sha1.padding = String.fromCharCode(128);
59180 var c = String.fromCharCode(0x00);
59190 var n = 64;
59200 while(n > 0) {
59210 if(n & 1) {
59220 _sha1.padding += c;
5923 }
59240 n >>>= 1;
59250 if(n > 0) {
59260 c += c;
5927 }
5928 }
5929
5930 // now initialized
59310 _sha1.initialized = true;
5932};
5933
5934/**
5935 * Updates a SHA-1 state with the given byte buffer.
5936 *
5937 * @param s the SHA-1 state to update.
5938 * @param w the array to use to store words.
5939 * @param input the input byte buffer.
5940 */
59410_sha1.update = function(s, w, input) {
5942 // consume 512 bit (64 byte) chunks
59430 var t, a, b, c, d, e, f, i;
59440 var len = input.length();
59450 while(len >= 64) {
5946 // the w array will be populated with sixteen 32-bit big-endian words
5947 // and then extended into 80 32-bit words according to SHA-1 algorithm
5948 // and for 32-79 using Max Locktyukhin's optimization
5949
5950 // initialize hash value for this chunk
59510 a = s.h0;
59520 b = s.h1;
59530 c = s.h2;
59540 d = s.h3;
59550 e = s.h4;
5956
5957 // round 1
59580 for(i = 0; i < 16; ++i) {
59590 t = input.getInt32();
59600 w[i] = t;
59610 f = d ^ (b & (c ^ d));
59620 t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
59630 e = d;
59640 d = c;
59650 c = (b << 30) | (b >>> 2);
59660 b = a;
59670 a = t;
5968 }
59690 for(; i < 20; ++i) {
59700 t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
59710 t = (t << 1) | (t >>> 31);
59720 w[i] = t;
59730 f = d ^ (b & (c ^ d));
59740 t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
59750 e = d;
59760 d = c;
59770 c = (b << 30) | (b >>> 2);
59780 b = a;
59790 a = t;
5980 }
5981 // round 2
59820 for(; i < 32; ++i) {
59830 t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
59840 t = (t << 1) | (t >>> 31);
59850 w[i] = t;
59860 f = b ^ c ^ d;
59870 t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
59880 e = d;
59890 d = c;
59900 c = (b << 30) | (b >>> 2);
59910 b = a;
59920 a = t;
5993 }
59940 for(; i < 40; ++i) {
59950 t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
59960 t = (t << 2) | (t >>> 30);
59970 w[i] = t;
59980 f = b ^ c ^ d;
59990 t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
60000 e = d;
60010 d = c;
60020 c = (b << 30) | (b >>> 2);
60030 b = a;
60040 a = t;
6005 }
6006 // round 3
60070 for(; i < 60; ++i) {
60080 t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
60090 t = (t << 2) | (t >>> 30);
60100 w[i] = t;
60110 f = (b & c) | (d & (b ^ c));
60120 t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
60130 e = d;
60140 d = c;
60150 c = (b << 30) | (b >>> 2);
60160 b = a;
60170 a = t;
6018 }
6019 // round 4
60200 for(; i < 80; ++i) {
60210 t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
60220 t = (t << 2) | (t >>> 30);
60230 w[i] = t;
60240 f = b ^ c ^ d;
60250 t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
60260 e = d;
60270 d = c;
60280 c = (b << 30) | (b >>> 2);
60290 b = a;
60300 a = t;
6031 }
6032
6033 // update hash state
60340 s.h0 += a;
60350 s.h1 += b;
60360 s.h2 += c;
60370 s.h3 += d;
60380 s.h4 += e;
6039
60400 len -= 64;
6041 }
6042};
6043
6044} // end non-nodejs
6045
60462if(!XMLSerializer) {
6047
60482function _defineXMLSerializer() {
60490 XMLSerializer = require('xmldom').XMLSerializer;
6050}
6051
6052} // end _defineXMLSerializer
6053
6054// define URL parser
60552jsonld.url = {};
60562if(_nodejs) {
60572 var parse = require('url').parse;
60582 jsonld.url.parse = function(url) {
6059516 var parsed = parse(url);
6060516 parsed.pathname = parsed.pathname || '';
6061516 _parseAuthority(parsed);
6062516 parsed.normalizedPath = _removeDotSegments(
6063 parsed.pathname, parsed.authority !== '');
6064516 return parsed;
6065 };
6066}
6067else {
6068 // parseUri 1.2.2
6069 // (c) Steven Levithan <stevenlevithan.com>
6070 // MIT License
60710 var parseUri = {};
60720 parseUri.options = {
6073 key: ['href','protocol','host','auth','user','password','hostname','port','relative','path','directory','file','query','hash'],
6074 parser: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/
6075 };
60760 jsonld.url.parse = function(str) {
60770 var o = parseUri.options;
60780 var m = o.parser.exec(str);
60790 var uri = {};
60800 var i = 14;
60810 while(i--) {
60820 uri[o.key[i]] = m[i] || '';
6083 }
6084 // normalize to node.js API
60850 if(uri.host && uri.path === '') {
60860 uri.path = '/';
6087 }
60880 uri.pathname = uri.path || '';
60890 _parseAuthority(uri);
60900 uri.normalizedPath = _removeDotSegments(uri.pathname, uri.authority !== '');
60910 if(uri.query) {
60920 uri.path = uri.path + '?' + uri.query;
6093 }
60940 if(uri.protocol) {
60950 uri.protocol += ':';
6096 }
60970 if(uri.hash) {
60980 uri.hash = '#' + uri.hash;
6099 }
61000 return uri;
6101 };
6102}
6103
6104/**
6105 * Parses the authority for the pre-parsed given URL.
6106 *
6107 * @param parsed the pre-parsed URL.
6108 */
61092function _parseAuthority(parsed) {
6110 // parse authority for unparsed relative network-path reference
6111516 if(parsed.href.indexOf(':') === -1 && parsed.href.indexOf('//') === 0 &&
6112 !parsed.host) {
6113 // must parse authority from pathname
61148 parsed.pathname = parsed.pathname.substr(2);
61158 var idx = parsed.pathname.indexOf('/');
61168 if(idx === -1) {
61170 parsed.authority = parsed.pathname;
61180 parsed.pathname = '';
6119 }
6120 else {
61218 parsed.authority = parsed.pathname.substr(0, idx);
61228 parsed.pathname = parsed.pathname.substr(idx);
6123 }
6124 }
6125 else {
6126 // construct authority
6127508 parsed.authority = parsed.host || '';
6128508 if(parsed.auth) {
61290 parsed.authority = parsed.auth + '@' + parsed.authority;
6130 }
6131 }
6132}
6133
6134/**
6135 * Removes dot segments from a URL path.
6136 *
6137 * @param path the path to remove dot segments from.
6138 * @param hasAuthority true if the URL has an authority, false if not.
6139 */
61402function _removeDotSegments(path, hasAuthority) {
6141621 var rval = '';
6142
6143621 if(path.indexOf('/') === 0) {
6144532 rval = '/';
6145 }
6146
6147 // RFC 3986 5.2.4 (reworked)
6148621 var input = path.split('/');
6149621 var output = [];
6150621 while(input.length > 0) {
61512424 if(input[0] === '.' || (input[0] === '' && input.length > 1)) {
6152576 input.shift();
6153576 continue;
6154 }
61551848 if(input[0] === '..') {
6156190 input.shift();
6157190 if(hasAuthority ||
6158 (output.length > 0 && output[output.length - 1] !== '..')) {
6159101 output.pop();
6160 }
6161 // leading relative URL '..'
6162 else {
616389 output.push('..');
6164 }
6165190 continue;
6166 }
61671658 output.push(input.shift());
6168 }
6169
6170621 return rval + output.join('/');
6171}
6172
61732if(_nodejs) {
6174 // use node context loader by default
61752 jsonld.useContextLoader('node');
6176}
6177
61782if(_nodejs) {
61792 jsonld.use = function(extension) {
61800 switch(extension) {
6181 case 'request':
6182 // use node JSON-LD request extension
61830 jsonld.request = require('./request');
61840 break;
6185 default:
61860 throw new JsonLdError(
6187 'Unknown extension.',
6188 'jsonld.UnknownExtension', {extension: extension});
6189 }
6190 }
6191}
6192
6193// end of jsonld API factory
61942return jsonld;
6195};
6196
6197// external APIs:
6198
6199// used to generate a new jsonld API instance
62001var factory = function() {
62011 return wrapper(function() {
62020 return factory();
6203 });
6204};
6205// the shared global jsonld API instance
62061wrapper(factory);
6207
6208// export nodejs API
62091if(_nodejs) {
62101 module.exports = factory;
6211}
6212// export AMD API
62130else if(typeof define === 'function' && define.amd) {
62140 define('jsonld', [], function() {
62150 return factory;
6216 });
6217}
6218// export simple browser API
62190else if(_browser) {
62200 window.jsonld = window.jsonld || factory;
6221}
6222
6223})();
make[1]: Leaving directory `/work/src/jsonld.js'