1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const util = require('./util');
|
7 | const JsonLdError = require('./JsonLdError');
|
8 |
|
9 | const {
|
10 | isArray: _isArray,
|
11 | isObject: _isObject,
|
12 | isString: _isString,
|
13 | isUndefined: _isUndefined
|
14 | } = require('./types');
|
15 |
|
16 | const {
|
17 | isAbsolute: _isAbsoluteIri,
|
18 | isRelative: _isRelativeIri,
|
19 | prependBase
|
20 | } = require('./url');
|
21 |
|
22 | const {
|
23 | asArray: _asArray,
|
24 | compareShortestLeast: _compareShortestLeast
|
25 | } = require('./util');
|
26 |
|
27 | const INITIAL_CONTEXT_CACHE = new Map();
|
28 | const INITIAL_CONTEXT_CACHE_MAX_SIZE = 10000;
|
29 | const KEYWORD_PATTERN = /^@[a-zA-Z]+$/;
|
30 |
|
31 | const api = {};
|
32 | module.exports = api;
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | api.process = async ({
|
47 | activeCtx, localCtx, options,
|
48 | propagate = true,
|
49 | overrideProtected = false,
|
50 | cycles = new Set()
|
51 | }) => {
|
52 |
|
53 | if(_isObject(localCtx) && '@context' in localCtx &&
|
54 | _isArray(localCtx['@context'])) {
|
55 | localCtx = localCtx['@context'];
|
56 | }
|
57 | const ctxs = _asArray(localCtx);
|
58 |
|
59 |
|
60 | if(ctxs.length === 0) {
|
61 | return activeCtx;
|
62 | }
|
63 |
|
64 |
|
65 | const resolved = await options.contextResolver.resolve({
|
66 | activeCtx,
|
67 | context: localCtx,
|
68 | documentLoader: options.documentLoader,
|
69 | base: options.base
|
70 | });
|
71 |
|
72 |
|
73 | if(_isObject(resolved[0].document) &&
|
74 | typeof resolved[0].document['@propagate'] === 'boolean') {
|
75 |
|
76 | propagate = resolved[0].document['@propagate'];
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 | let rval = activeCtx;
|
82 |
|
83 |
|
84 |
|
85 | if(!propagate && !rval.previousContext) {
|
86 |
|
87 | rval = rval.clone();
|
88 | rval.previousContext = activeCtx;
|
89 | }
|
90 |
|
91 | for(const resolvedContext of resolved) {
|
92 | let {document: ctx} = resolvedContext;
|
93 |
|
94 |
|
95 | activeCtx = rval;
|
96 |
|
97 |
|
98 | if(ctx === null) {
|
99 |
|
100 |
|
101 | if(!overrideProtected &&
|
102 | Object.keys(activeCtx.protected).length !== 0) {
|
103 | const protectedMode = (options && options.protectedMode) || 'error';
|
104 | if(protectedMode === 'error') {
|
105 | throw new JsonLdError(
|
106 | 'Tried to nullify a context with protected terms outside of ' +
|
107 | 'a term definition.',
|
108 | 'jsonld.SyntaxError',
|
109 | {code: 'invalid context nullification'});
|
110 | } else if(protectedMode === 'warn') {
|
111 |
|
112 | console.warn('WARNING: invalid context nullification');
|
113 |
|
114 |
|
115 | const processed = resolvedContext.getProcessed(activeCtx);
|
116 | if(processed) {
|
117 | rval = activeCtx = processed;
|
118 | continue;
|
119 | }
|
120 |
|
121 | const oldActiveCtx = activeCtx;
|
122 |
|
123 | rval = activeCtx = api.getInitialContext(options).clone();
|
124 | for(const [term, _protected] of
|
125 | Object.entries(oldActiveCtx.protected)) {
|
126 | if(_protected) {
|
127 | activeCtx.mappings[term] =
|
128 | util.clone(oldActiveCtx.mappings[term]);
|
129 | }
|
130 | }
|
131 | activeCtx.protected = util.clone(oldActiveCtx.protected);
|
132 |
|
133 |
|
134 | resolvedContext.setProcessed(oldActiveCtx, rval);
|
135 | continue;
|
136 | }
|
137 | throw new JsonLdError(
|
138 | 'Invalid protectedMode.',
|
139 | 'jsonld.SyntaxError',
|
140 | {code: 'invalid protected mode', context: localCtx, protectedMode});
|
141 | }
|
142 | rval = activeCtx = api.getInitialContext(options).clone();
|
143 | continue;
|
144 | }
|
145 |
|
146 |
|
147 | const processed = resolvedContext.getProcessed(activeCtx);
|
148 | if(processed) {
|
149 | rval = activeCtx = processed;
|
150 | continue;
|
151 | }
|
152 |
|
153 |
|
154 | if(_isObject(ctx) && '@context' in ctx) {
|
155 | ctx = ctx['@context'];
|
156 | }
|
157 |
|
158 |
|
159 | if(!_isObject(ctx)) {
|
160 | throw new JsonLdError(
|
161 | 'Invalid JSON-LD syntax; @context must be an object.',
|
162 | 'jsonld.SyntaxError', {code: 'invalid local context', context: ctx});
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | rval = rval.clone();
|
170 |
|
171 |
|
172 | const defined = new Map();
|
173 |
|
174 |
|
175 | if('@version' in ctx) {
|
176 | if(ctx['@version'] !== 1.1) {
|
177 | throw new JsonLdError(
|
178 | 'Unsupported JSON-LD version: ' + ctx['@version'],
|
179 | 'jsonld.UnsupportedVersion',
|
180 | {code: 'invalid @version value', context: ctx});
|
181 | }
|
182 | if(activeCtx.processingMode &&
|
183 | activeCtx.processingMode === 'json-ld-1.0') {
|
184 | throw new JsonLdError(
|
185 | '@version: ' + ctx['@version'] + ' not compatible with ' +
|
186 | activeCtx.processingMode,
|
187 | 'jsonld.ProcessingModeConflict',
|
188 | {code: 'processing mode conflict', context: ctx});
|
189 | }
|
190 | rval.processingMode = 'json-ld-1.1';
|
191 | rval['@version'] = ctx['@version'];
|
192 | defined.set('@version', true);
|
193 | }
|
194 |
|
195 |
|
196 | rval.processingMode =
|
197 | rval.processingMode || activeCtx.processingMode;
|
198 |
|
199 |
|
200 | if('@base' in ctx) {
|
201 | let base = ctx['@base'];
|
202 |
|
203 | if(base === null || _isAbsoluteIri(base)) {
|
204 |
|
205 | } else if(_isRelativeIri(base)) {
|
206 | base = prependBase(rval['@base'], base);
|
207 | } else {
|
208 | throw new JsonLdError(
|
209 | 'Invalid JSON-LD syntax; the value of "@base" in a ' +
|
210 | '@context must be an absolute IRI, a relative IRI, or null.',
|
211 | 'jsonld.SyntaxError', {code: 'invalid base IRI', context: ctx});
|
212 | }
|
213 |
|
214 | rval['@base'] = base;
|
215 | defined.set('@base', true);
|
216 | }
|
217 |
|
218 |
|
219 | if('@vocab' in ctx) {
|
220 | const value = ctx['@vocab'];
|
221 | if(value === null) {
|
222 | delete rval['@vocab'];
|
223 | } else if(!_isString(value)) {
|
224 | throw new JsonLdError(
|
225 | 'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
226 | '@context must be a string or null.',
|
227 | 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx});
|
228 | } else if(!_isAbsoluteIri(value) && api.processingMode(rval, 1.0)) {
|
229 | throw new JsonLdError(
|
230 | 'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
231 | '@context must be an absolute IRI.',
|
232 | 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx});
|
233 | } else {
|
234 | rval['@vocab'] = _expandIri(rval, value, {vocab: true, base: true},
|
235 | undefined, undefined, options);
|
236 | }
|
237 | defined.set('@vocab', true);
|
238 | }
|
239 |
|
240 |
|
241 | if('@language' in ctx) {
|
242 | const value = ctx['@language'];
|
243 | if(value === null) {
|
244 | delete rval['@language'];
|
245 | } else if(!_isString(value)) {
|
246 | throw new JsonLdError(
|
247 | 'Invalid JSON-LD syntax; the value of "@language" in a ' +
|
248 | '@context must be a string or null.',
|
249 | 'jsonld.SyntaxError',
|
250 | {code: 'invalid default language', context: ctx});
|
251 | } else {
|
252 | rval['@language'] = value.toLowerCase();
|
253 | }
|
254 | defined.set('@language', true);
|
255 | }
|
256 |
|
257 |
|
258 | if('@direction' in ctx) {
|
259 | const value = ctx['@direction'];
|
260 | if(activeCtx.processingMode === 'json-ld-1.0') {
|
261 | throw new JsonLdError(
|
262 | 'Invalid JSON-LD syntax; @direction not compatible with ' +
|
263 | activeCtx.processingMode,
|
264 | 'jsonld.SyntaxError',
|
265 | {code: 'invalid context member', context: ctx});
|
266 | }
|
267 | if(value === null) {
|
268 | delete rval['@direction'];
|
269 | } else if(value !== 'ltr' && value !== 'rtl') {
|
270 | throw new JsonLdError(
|
271 | 'Invalid JSON-LD syntax; the value of "@direction" in a ' +
|
272 | '@context must be null, "ltr", or "rtl".',
|
273 | 'jsonld.SyntaxError',
|
274 | {code: 'invalid base direction', context: ctx});
|
275 | } else {
|
276 | rval['@direction'] = value;
|
277 | }
|
278 | defined.set('@direction', true);
|
279 | }
|
280 |
|
281 |
|
282 |
|
283 | if('@propagate' in ctx) {
|
284 | const value = ctx['@propagate'];
|
285 | if(activeCtx.processingMode === 'json-ld-1.0') {
|
286 | throw new JsonLdError(
|
287 | 'Invalid JSON-LD syntax; @propagate not compatible with ' +
|
288 | activeCtx.processingMode,
|
289 | 'jsonld.SyntaxError',
|
290 | {code: 'invalid context entry', context: ctx});
|
291 | }
|
292 | if(typeof value !== 'boolean') {
|
293 | throw new JsonLdError(
|
294 | 'Invalid JSON-LD syntax; @propagate value must be a boolean.',
|
295 | 'jsonld.SyntaxError',
|
296 | {code: 'invalid @propagate value', context: localCtx});
|
297 | }
|
298 | defined.set('@propagate', true);
|
299 | }
|
300 |
|
301 |
|
302 | if('@import' in ctx) {
|
303 | const value = ctx['@import'];
|
304 | if(activeCtx.processingMode === 'json-ld-1.0') {
|
305 | throw new JsonLdError(
|
306 | 'Invalid JSON-LD syntax; @import not compatible with ' +
|
307 | activeCtx.processingMode,
|
308 | 'jsonld.SyntaxError',
|
309 | {code: 'invalid context entry', context: ctx});
|
310 | }
|
311 | if(!_isString(value)) {
|
312 | throw new JsonLdError(
|
313 | 'Invalid JSON-LD syntax; @import must be a string.',
|
314 | 'jsonld.SyntaxError',
|
315 | {code: 'invalid @import value', context: localCtx});
|
316 | }
|
317 |
|
318 |
|
319 | const resolvedImport = await options.contextResolver.resolve({
|
320 | activeCtx,
|
321 | context: value,
|
322 | documentLoader: options.documentLoader,
|
323 | base: options.base
|
324 | });
|
325 | if(resolvedImport.length !== 1) {
|
326 | throw new JsonLdError(
|
327 | 'Invalid JSON-LD syntax; @import must reference a single context.',
|
328 | 'jsonld.SyntaxError',
|
329 | {code: 'invalid remote context', context: localCtx});
|
330 | }
|
331 | const processedImport = resolvedImport[0].getProcessed(activeCtx);
|
332 | if(processedImport) {
|
333 |
|
334 |
|
335 |
|
336 | ctx = processedImport;
|
337 | } else {
|
338 | const importCtx = resolvedImport[0].document;
|
339 | if('@import' in importCtx) {
|
340 | throw new JsonLdError(
|
341 | 'Invalid JSON-LD syntax: ' +
|
342 | 'imported context must not include @import.',
|
343 | 'jsonld.SyntaxError',
|
344 | {code: 'invalid context entry', context: localCtx});
|
345 | }
|
346 |
|
347 |
|
348 | for(const key in importCtx) {
|
349 | if(!ctx.hasOwnProperty(key)) {
|
350 | ctx[key] = importCtx[key];
|
351 | }
|
352 | }
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 | resolvedImport[0].setProcessed(activeCtx, ctx);
|
359 | }
|
360 |
|
361 | defined.set('@import', true);
|
362 | }
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | defined.set('@protected', ctx['@protected'] || false);
|
368 |
|
369 |
|
370 | for(const key in ctx) {
|
371 | api.createTermDefinition({
|
372 | activeCtx: rval,
|
373 | localCtx: ctx,
|
374 | term: key,
|
375 | defined,
|
376 | options,
|
377 | overrideProtected
|
378 | });
|
379 |
|
380 | if(_isObject(ctx[key]) && '@context' in ctx[key]) {
|
381 | const keyCtx = ctx[key]['@context'];
|
382 | let process = true;
|
383 | if(_isString(keyCtx)) {
|
384 | const url = prependBase(options.base, keyCtx);
|
385 |
|
386 | if(cycles.has(url)) {
|
387 | process = false;
|
388 | } else {
|
389 | cycles.add(url);
|
390 | }
|
391 | }
|
392 |
|
393 | if(process) {
|
394 | try {
|
395 | await api.process({
|
396 | activeCtx: rval.clone(),
|
397 | localCtx: ctx[key]['@context'],
|
398 | overrideProtected: true,
|
399 | options,
|
400 | cycles
|
401 | });
|
402 | } catch(e) {
|
403 | throw new JsonLdError(
|
404 | 'Invalid JSON-LD syntax; invalid scoped context.',
|
405 | 'jsonld.SyntaxError',
|
406 | {
|
407 | code: 'invalid scoped context',
|
408 | context: ctx[key]['@context'],
|
409 | term: key
|
410 | });
|
411 | }
|
412 | }
|
413 | }
|
414 | }
|
415 |
|
416 |
|
417 | resolvedContext.setProcessed(activeCtx, rval);
|
418 | }
|
419 |
|
420 | return rval;
|
421 | };
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 | api.createTermDefinition = ({
|
438 | activeCtx,
|
439 | localCtx,
|
440 | term,
|
441 | defined,
|
442 | options,
|
443 | overrideProtected = false,
|
444 | }) => {
|
445 | if(defined.has(term)) {
|
446 |
|
447 | if(defined.get(term)) {
|
448 | return;
|
449 | }
|
450 |
|
451 | throw new JsonLdError(
|
452 | 'Cyclical context definition detected.',
|
453 | 'jsonld.CyclicalContext',
|
454 | {code: 'cyclic IRI mapping', context: localCtx, term});
|
455 | }
|
456 |
|
457 |
|
458 | defined.set(term, false);
|
459 |
|
460 |
|
461 | let value;
|
462 | if(localCtx.hasOwnProperty(term)) {
|
463 | value = localCtx[term];
|
464 | }
|
465 |
|
466 | if(term === '@type' &&
|
467 | _isObject(value) &&
|
468 | (value['@container'] || '@set') === '@set' &&
|
469 | api.processingMode(activeCtx, 1.1)) {
|
470 |
|
471 | const validKeys = ['@container', '@id', '@protected'];
|
472 | const keys = Object.keys(value);
|
473 | if(keys.length === 0 || keys.some(k => !validKeys.includes(k))) {
|
474 | throw new JsonLdError(
|
475 | 'Invalid JSON-LD syntax; keywords cannot be overridden.',
|
476 | 'jsonld.SyntaxError',
|
477 | {code: 'keyword redefinition', context: localCtx, term});
|
478 | }
|
479 | } else if(api.isKeyword(term)) {
|
480 | throw new JsonLdError(
|
481 | 'Invalid JSON-LD syntax; keywords cannot be overridden.',
|
482 | 'jsonld.SyntaxError',
|
483 | {code: 'keyword redefinition', context: localCtx, term});
|
484 | } else if(term.match(KEYWORD_PATTERN)) {
|
485 |
|
486 | console.warn('WARNING: terms beginning with "@" are reserved' +
|
487 | ' for future use and ignored', {term});
|
488 | return;
|
489 | } else if(term === '') {
|
490 | throw new JsonLdError(
|
491 | 'Invalid JSON-LD syntax; a term cannot be an empty string.',
|
492 | 'jsonld.SyntaxError',
|
493 | {code: 'invalid term definition', context: localCtx});
|
494 | }
|
495 |
|
496 |
|
497 | const previousMapping = activeCtx.mappings.get(term);
|
498 |
|
499 |
|
500 | if(activeCtx.mappings.has(term)) {
|
501 | activeCtx.mappings.delete(term);
|
502 | }
|
503 |
|
504 |
|
505 | let simpleTerm = false;
|
506 | if(_isString(value) || value === null) {
|
507 | simpleTerm = true;
|
508 | value = {'@id': value};
|
509 | }
|
510 |
|
511 | if(!_isObject(value)) {
|
512 | throw new JsonLdError(
|
513 | 'Invalid JSON-LD syntax; @context term values must be ' +
|
514 | 'strings or objects.',
|
515 | 'jsonld.SyntaxError',
|
516 | {code: 'invalid term definition', context: localCtx});
|
517 | }
|
518 |
|
519 |
|
520 | const mapping = {};
|
521 | activeCtx.mappings.set(term, mapping);
|
522 | mapping.reverse = false;
|
523 |
|
524 |
|
525 | const validKeys = ['@container', '@id', '@language', '@reverse', '@type'];
|
526 |
|
527 |
|
528 | if(api.processingMode(activeCtx, 1.1)) {
|
529 | validKeys.push(
|
530 | '@context', '@direction', '@index', '@nest', '@prefix', '@protected');
|
531 | }
|
532 |
|
533 | for(const kw in value) {
|
534 | if(!validKeys.includes(kw)) {
|
535 | throw new JsonLdError(
|
536 | 'Invalid JSON-LD syntax; a term definition must not contain ' + kw,
|
537 | 'jsonld.SyntaxError',
|
538 | {code: 'invalid term definition', context: localCtx});
|
539 | }
|
540 | }
|
541 |
|
542 |
|
543 |
|
544 | const colon = term.indexOf(':');
|
545 | mapping._termHasColon = (colon > 0);
|
546 |
|
547 | if('@reverse' in value) {
|
548 | if('@id' in value) {
|
549 | throw new JsonLdError(
|
550 | 'Invalid JSON-LD syntax; a @reverse term definition must not ' +
|
551 | 'contain @id.', 'jsonld.SyntaxError',
|
552 | {code: 'invalid reverse property', context: localCtx});
|
553 | }
|
554 | if('@nest' in value) {
|
555 | throw new JsonLdError(
|
556 | 'Invalid JSON-LD syntax; a @reverse term definition must not ' +
|
557 | 'contain @nest.', 'jsonld.SyntaxError',
|
558 | {code: 'invalid reverse property', context: localCtx});
|
559 | }
|
560 | const reverse = value['@reverse'];
|
561 | if(!_isString(reverse)) {
|
562 | throw new JsonLdError(
|
563 | 'Invalid JSON-LD syntax; a @context @reverse value must be a string.',
|
564 | 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});
|
565 | }
|
566 |
|
567 | if(!api.isKeyword(reverse) && reverse.match(KEYWORD_PATTERN)) {
|
568 |
|
569 | console.warn('WARNING: values beginning with "@" are reserved' +
|
570 | ' for future use and ignored', {reverse});
|
571 | if(previousMapping) {
|
572 | activeCtx.mappings.set(term, previousMapping);
|
573 | } else {
|
574 | activeCtx.mappings.delete(term);
|
575 | }
|
576 | return;
|
577 | }
|
578 |
|
579 |
|
580 | const id = _expandIri(
|
581 | activeCtx, reverse, {vocab: true, base: false}, localCtx, defined,
|
582 | options);
|
583 | if(!_isAbsoluteIri(id)) {
|
584 | throw new JsonLdError(
|
585 | 'Invalid JSON-LD syntax; a @context @reverse value must be an ' +
|
586 | 'absolute IRI or a blank node identifier.',
|
587 | 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});
|
588 | }
|
589 |
|
590 | mapping['@id'] = id;
|
591 | mapping.reverse = true;
|
592 | } else if('@id' in value) {
|
593 | let id = value['@id'];
|
594 | if(id && !_isString(id)) {
|
595 | throw new JsonLdError(
|
596 | 'Invalid JSON-LD syntax; a @context @id value must be an array ' +
|
597 | 'of strings or a string.',
|
598 | 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});
|
599 | }
|
600 | if(id === null) {
|
601 |
|
602 | mapping['@id'] = null;
|
603 | } else if(!api.isKeyword(id) && id.match(KEYWORD_PATTERN)) {
|
604 |
|
605 | console.warn('WARNING: values beginning with "@" are reserved' +
|
606 | ' for future use and ignored', {id});
|
607 | if(previousMapping) {
|
608 | activeCtx.mappings.set(term, previousMapping);
|
609 | } else {
|
610 | activeCtx.mappings.delete(term);
|
611 | }
|
612 | return;
|
613 | } else if(id !== term) {
|
614 |
|
615 | id = _expandIri(
|
616 | activeCtx, id, {vocab: true, base: false}, localCtx, defined, options);
|
617 | if(!_isAbsoluteIri(id) && !api.isKeyword(id)) {
|
618 | throw new JsonLdError(
|
619 | 'Invalid JSON-LD syntax; a @context @id value must be an ' +
|
620 | 'absolute IRI, a blank node identifier, or a keyword.',
|
621 | 'jsonld.SyntaxError',
|
622 | {code: 'invalid IRI mapping', context: localCtx});
|
623 | }
|
624 |
|
625 |
|
626 | if(term.match(/(?::[^:])|\//)) {
|
627 | const termDefined = new Map(defined).set(term, true);
|
628 | const termIri = _expandIri(
|
629 | activeCtx, term, {vocab: true, base: false},
|
630 | localCtx, termDefined, options);
|
631 | if(termIri !== id) {
|
632 | throw new JsonLdError(
|
633 | 'Invalid JSON-LD syntax; term in form of IRI must ' +
|
634 | 'expand to definition.',
|
635 | 'jsonld.SyntaxError',
|
636 | {code: 'invalid IRI mapping', context: localCtx});
|
637 | }
|
638 | }
|
639 |
|
640 | mapping['@id'] = id;
|
641 |
|
642 | mapping._prefix = (simpleTerm &&
|
643 | !mapping._termHasColon &&
|
644 | id.match(/[:\/\?#\[\]@]$/));
|
645 | }
|
646 | }
|
647 |
|
648 | if(!('@id' in mapping)) {
|
649 |
|
650 | if(mapping._termHasColon) {
|
651 | const prefix = term.substr(0, colon);
|
652 | if(localCtx.hasOwnProperty(prefix)) {
|
653 |
|
654 | api.createTermDefinition({
|
655 | activeCtx, localCtx, term: prefix, defined, options
|
656 | });
|
657 | }
|
658 |
|
659 | if(activeCtx.mappings.has(prefix)) {
|
660 |
|
661 | const suffix = term.substr(colon + 1);
|
662 | mapping['@id'] = activeCtx.mappings.get(prefix)['@id'] + suffix;
|
663 | } else {
|
664 |
|
665 | mapping['@id'] = term;
|
666 | }
|
667 | } else if(term === '@type') {
|
668 |
|
669 | mapping['@id'] = term;
|
670 | } else {
|
671 |
|
672 | if(!('@vocab' in activeCtx)) {
|
673 | throw new JsonLdError(
|
674 | 'Invalid JSON-LD syntax; @context terms must define an @id.',
|
675 | 'jsonld.SyntaxError',
|
676 | {code: 'invalid IRI mapping', context: localCtx, term});
|
677 | }
|
678 |
|
679 | mapping['@id'] = activeCtx['@vocab'] + term;
|
680 | }
|
681 | }
|
682 |
|
683 |
|
684 | if(value['@protected'] === true ||
|
685 | (defined.get('@protected') === true && value['@protected'] !== false)) {
|
686 | activeCtx.protected[term] = true;
|
687 | mapping.protected = true;
|
688 | }
|
689 |
|
690 |
|
691 | defined.set(term, true);
|
692 |
|
693 | if('@type' in value) {
|
694 | let type = value['@type'];
|
695 | if(!_isString(type)) {
|
696 | throw new JsonLdError(
|
697 | 'Invalid JSON-LD syntax; an @context @type value must be a string.',
|
698 | 'jsonld.SyntaxError',
|
699 | {code: 'invalid type mapping', context: localCtx});
|
700 | }
|
701 |
|
702 | if((type === '@json' || type === '@none')) {
|
703 | if(api.processingMode(activeCtx, 1.0)) {
|
704 | throw new JsonLdError(
|
705 | 'Invalid JSON-LD syntax; an @context @type value must not be ' +
|
706 | `"${type}" in JSON-LD 1.0 mode.`,
|
707 | 'jsonld.SyntaxError',
|
708 | {code: 'invalid type mapping', context: localCtx});
|
709 | }
|
710 | } else if(type !== '@id' && type !== '@vocab') {
|
711 |
|
712 | type = _expandIri(
|
713 | activeCtx, type, {vocab: true, base: false}, localCtx, defined,
|
714 | options);
|
715 | if(!_isAbsoluteIri(type)) {
|
716 | throw new JsonLdError(
|
717 | 'Invalid JSON-LD syntax; an @context @type value must be an ' +
|
718 | 'absolute IRI.',
|
719 | 'jsonld.SyntaxError',
|
720 | {code: 'invalid type mapping', context: localCtx});
|
721 | }
|
722 | if(type.indexOf('_:') === 0) {
|
723 | throw new JsonLdError(
|
724 | 'Invalid JSON-LD syntax; an @context @type value must be an IRI, ' +
|
725 | 'not a blank node identifier.',
|
726 | 'jsonld.SyntaxError',
|
727 | {code: 'invalid type mapping', context: localCtx});
|
728 | }
|
729 | }
|
730 |
|
731 |
|
732 | mapping['@type'] = type;
|
733 | }
|
734 |
|
735 | if('@container' in value) {
|
736 |
|
737 | const container = _isString(value['@container']) ?
|
738 | [value['@container']] : (value['@container'] || []);
|
739 | const validContainers = ['@list', '@set', '@index', '@language'];
|
740 | let isValid = true;
|
741 | const hasSet = container.includes('@set');
|
742 |
|
743 |
|
744 | if(api.processingMode(activeCtx, 1.1)) {
|
745 | validContainers.push('@graph', '@id', '@type');
|
746 |
|
747 |
|
748 | if(container.includes('@list')) {
|
749 | if(container.length !== 1) {
|
750 | throw new JsonLdError(
|
751 | 'Invalid JSON-LD syntax; @context @container with @list must ' +
|
752 | 'have no other values',
|
753 | 'jsonld.SyntaxError',
|
754 | {code: 'invalid container mapping', context: localCtx});
|
755 | }
|
756 | } else if(container.includes('@graph')) {
|
757 | if(container.some(key =>
|
758 | key !== '@graph' && key !== '@id' && key !== '@index' &&
|
759 | key !== '@set')) {
|
760 | throw new JsonLdError(
|
761 | 'Invalid JSON-LD syntax; @context @container with @graph must ' +
|
762 | 'have no other values other than @id, @index, and @set',
|
763 | 'jsonld.SyntaxError',
|
764 | {code: 'invalid container mapping', context: localCtx});
|
765 | }
|
766 | } else {
|
767 |
|
768 | isValid &= container.length <= (hasSet ? 2 : 1);
|
769 | }
|
770 |
|
771 | if(container.includes('@type')) {
|
772 |
|
773 |
|
774 | mapping['@type'] = mapping['@type'] || '@id';
|
775 |
|
776 |
|
777 | if(!['@id', '@vocab'].includes(mapping['@type'])) {
|
778 | throw new JsonLdError(
|
779 | 'Invalid JSON-LD syntax; container: @type requires @type to be ' +
|
780 | '@id or @vocab.',
|
781 | 'jsonld.SyntaxError',
|
782 | {code: 'invalid type mapping', context: localCtx});
|
783 | }
|
784 | }
|
785 | } else {
|
786 |
|
787 |
|
788 | isValid &= !_isArray(value['@container']);
|
789 |
|
790 |
|
791 | isValid &= container.length <= 1;
|
792 | }
|
793 |
|
794 |
|
795 | isValid &= container.every(c => validContainers.includes(c));
|
796 |
|
797 |
|
798 | isValid &= !(hasSet && container.includes('@list'));
|
799 |
|
800 | if(!isValid) {
|
801 | throw new JsonLdError(
|
802 | 'Invalid JSON-LD syntax; @context @container value must be ' +
|
803 | 'one of the following: ' + validContainers.join(', '),
|
804 | 'jsonld.SyntaxError',
|
805 | {code: 'invalid container mapping', context: localCtx});
|
806 | }
|
807 |
|
808 | if(mapping.reverse &&
|
809 | !container.every(c => ['@index', '@set'].includes(c))) {
|
810 | throw new JsonLdError(
|
811 | 'Invalid JSON-LD syntax; @context @container value for a @reverse ' +
|
812 | 'type definition must be @index or @set.', 'jsonld.SyntaxError',
|
813 | {code: 'invalid reverse property', context: localCtx});
|
814 | }
|
815 |
|
816 |
|
817 | mapping['@container'] = container;
|
818 | }
|
819 |
|
820 |
|
821 | if('@index' in value) {
|
822 | if(!('@container' in value) || !mapping['@container'].includes('@index')) {
|
823 | throw new JsonLdError(
|
824 | 'Invalid JSON-LD syntax; @index without @index in @container: ' +
|
825 | `"${value['@index']}" on term "${term}".`, 'jsonld.SyntaxError',
|
826 | {code: 'invalid term definition', context: localCtx});
|
827 | }
|
828 | if(!_isString(value['@index']) || value['@index'].indexOf('@') === 0) {
|
829 | throw new JsonLdError(
|
830 | 'Invalid JSON-LD syntax; @index must expand to an IRI: ' +
|
831 | `"${value['@index']}" on term "${term}".`, 'jsonld.SyntaxError',
|
832 | {code: 'invalid term definition', context: localCtx});
|
833 | }
|
834 | mapping['@index'] = value['@index'];
|
835 | }
|
836 |
|
837 |
|
838 | if('@context' in value) {
|
839 | mapping['@context'] = value['@context'];
|
840 | }
|
841 |
|
842 | if('@language' in value && !('@type' in value)) {
|
843 | let language = value['@language'];
|
844 | if(language !== null && !_isString(language)) {
|
845 | throw new JsonLdError(
|
846 | 'Invalid JSON-LD syntax; @context @language value must be ' +
|
847 | 'a string or null.', 'jsonld.SyntaxError',
|
848 | {code: 'invalid language mapping', context: localCtx});
|
849 | }
|
850 |
|
851 |
|
852 | if(language !== null) {
|
853 | language = language.toLowerCase();
|
854 | }
|
855 | mapping['@language'] = language;
|
856 | }
|
857 |
|
858 |
|
859 | if('@prefix' in value) {
|
860 | if(term.match(/:|\//)) {
|
861 | throw new JsonLdError(
|
862 | 'Invalid JSON-LD syntax; @context @prefix used on a compact IRI term',
|
863 | 'jsonld.SyntaxError',
|
864 | {code: 'invalid term definition', context: localCtx});
|
865 | }
|
866 | if(api.isKeyword(mapping['@id'])) {
|
867 | throw new JsonLdError(
|
868 | 'Invalid JSON-LD syntax; keywords may not be used as prefixes',
|
869 | 'jsonld.SyntaxError',
|
870 | {code: 'invalid term definition', context: localCtx});
|
871 | }
|
872 | if(typeof value['@prefix'] === 'boolean') {
|
873 | mapping._prefix = value['@prefix'] === true;
|
874 | } else {
|
875 | throw new JsonLdError(
|
876 | 'Invalid JSON-LD syntax; @context value for @prefix must be boolean',
|
877 | 'jsonld.SyntaxError',
|
878 | {code: 'invalid @prefix value', context: localCtx});
|
879 | }
|
880 | }
|
881 |
|
882 | if('@direction' in value) {
|
883 | const direction = value['@direction'];
|
884 | if(direction !== null && direction !== 'ltr' && direction !== 'rtl') {
|
885 | throw new JsonLdError(
|
886 | 'Invalid JSON-LD syntax; @direction value must be ' +
|
887 | 'null, "ltr", or "rtl".',
|
888 | 'jsonld.SyntaxError',
|
889 | {code: 'invalid base direction', context: localCtx});
|
890 | }
|
891 | mapping['@direction'] = direction;
|
892 | }
|
893 |
|
894 | if('@nest' in value) {
|
895 | const nest = value['@nest'];
|
896 | if(!_isString(nest) || (nest !== '@nest' && nest.indexOf('@') === 0)) {
|
897 | throw new JsonLdError(
|
898 | 'Invalid JSON-LD syntax; @context @nest value must be ' +
|
899 | 'a string which is not a keyword other than @nest.',
|
900 | 'jsonld.SyntaxError',
|
901 | {code: 'invalid @nest value', context: localCtx});
|
902 | }
|
903 | mapping['@nest'] = nest;
|
904 | }
|
905 |
|
906 |
|
907 | const id = mapping['@id'];
|
908 | if(id === '@context' || id === '@preserve') {
|
909 | throw new JsonLdError(
|
910 | 'Invalid JSON-LD syntax; @context and @preserve cannot be aliased.',
|
911 | 'jsonld.SyntaxError', {code: 'invalid keyword alias', context: localCtx});
|
912 | }
|
913 |
|
914 |
|
915 | if(previousMapping && previousMapping.protected && !overrideProtected) {
|
916 |
|
917 |
|
918 | activeCtx.protected[term] = true;
|
919 | mapping.protected = true;
|
920 | if(!_deepCompare(previousMapping, mapping)) {
|
921 | const protectedMode = (options && options.protectedMode) || 'error';
|
922 | if(protectedMode === 'error') {
|
923 | throw new JsonLdError(
|
924 | `Invalid JSON-LD syntax; tried to redefine "${term}" which is a ` +
|
925 | 'protected term.',
|
926 | 'jsonld.SyntaxError',
|
927 | {code: 'protected term redefinition', context: localCtx, term});
|
928 | } else if(protectedMode === 'warn') {
|
929 |
|
930 | console.warn('WARNING: protected term redefinition', {term});
|
931 | return;
|
932 | }
|
933 | throw new JsonLdError(
|
934 | 'Invalid protectedMode.',
|
935 | 'jsonld.SyntaxError',
|
936 | {code: 'invalid protected mode', context: localCtx, term,
|
937 | protectedMode});
|
938 | }
|
939 | }
|
940 | };
|
941 |
|
942 |
|
943 |
|
944 |
|
945 |
|
946 |
|
947 |
|
948 |
|
949 |
|
950 |
|
951 |
|
952 |
|
953 |
|
954 |
|
955 |
|
956 | api.expandIri = (activeCtx, value, relativeTo, options) => {
|
957 | return _expandIri(activeCtx, value, relativeTo, undefined, undefined,
|
958 | options);
|
959 | };
|
960 |
|
961 |
|
962 |
|
963 |
|
964 |
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 |
|
971 |
|
972 |
|
973 |
|
974 |
|
975 |
|
976 |
|
977 |
|
978 |
|
979 | function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) {
|
980 |
|
981 | if(value === null || !_isString(value) || api.isKeyword(value)) {
|
982 | return value;
|
983 | }
|
984 |
|
985 |
|
986 | if(value.match(KEYWORD_PATTERN)) {
|
987 | return null;
|
988 | }
|
989 |
|
990 |
|
991 | if(localCtx && localCtx.hasOwnProperty(value) &&
|
992 | defined.get(value) !== true) {
|
993 | api.createTermDefinition({
|
994 | activeCtx, localCtx, term: value, defined, options
|
995 | });
|
996 | }
|
997 |
|
998 | relativeTo = relativeTo || {};
|
999 | if(relativeTo.vocab) {
|
1000 | const mapping = activeCtx.mappings.get(value);
|
1001 |
|
1002 |
|
1003 | if(mapping === null) {
|
1004 | return null;
|
1005 | }
|
1006 |
|
1007 | if(_isObject(mapping) && '@id' in mapping) {
|
1008 |
|
1009 | return mapping['@id'];
|
1010 | }
|
1011 | }
|
1012 |
|
1013 |
|
1014 | const colon = value.indexOf(':');
|
1015 | if(colon > 0) {
|
1016 | const prefix = value.substr(0, colon);
|
1017 | const suffix = value.substr(colon + 1);
|
1018 |
|
1019 |
|
1020 |
|
1021 | if(prefix === '_' || suffix.indexOf('//') === 0) {
|
1022 | return value;
|
1023 | }
|
1024 |
|
1025 |
|
1026 | if(localCtx && localCtx.hasOwnProperty(prefix)) {
|
1027 | api.createTermDefinition({
|
1028 | activeCtx, localCtx, term: prefix, defined, options
|
1029 | });
|
1030 | }
|
1031 |
|
1032 |
|
1033 | const mapping = activeCtx.mappings.get(prefix);
|
1034 | if(mapping && mapping._prefix) {
|
1035 | return mapping['@id'] + suffix;
|
1036 | }
|
1037 |
|
1038 |
|
1039 | if(_isAbsoluteIri(value)) {
|
1040 | return value;
|
1041 | }
|
1042 | }
|
1043 |
|
1044 |
|
1045 | if(relativeTo.vocab && '@vocab' in activeCtx) {
|
1046 | return activeCtx['@vocab'] + value;
|
1047 | }
|
1048 |
|
1049 |
|
1050 | if(relativeTo.base && '@base' in activeCtx) {
|
1051 | if(activeCtx['@base']) {
|
1052 |
|
1053 | return prependBase(prependBase(options.base, activeCtx['@base']), value);
|
1054 | }
|
1055 | } else if(relativeTo.base) {
|
1056 | return prependBase(options.base, value);
|
1057 | }
|
1058 |
|
1059 | return value;
|
1060 | }
|
1061 |
|
1062 |
|
1063 |
|
1064 |
|
1065 |
|
1066 |
|
1067 |
|
1068 |
|
1069 |
|
1070 | api.getInitialContext = options => {
|
1071 | const key = JSON.stringify({processingMode: options.processingMode});
|
1072 | const cached = INITIAL_CONTEXT_CACHE.get(key);
|
1073 | if(cached) {
|
1074 | return cached;
|
1075 | }
|
1076 |
|
1077 | const initialContext = {
|
1078 | processingMode: options.processingMode,
|
1079 | mappings: new Map(),
|
1080 | inverse: null,
|
1081 | getInverse: _createInverseContext,
|
1082 | clone: _cloneActiveContext,
|
1083 | revertToPreviousContext: _revertToPreviousContext,
|
1084 | protected: {}
|
1085 | };
|
1086 |
|
1087 | if(INITIAL_CONTEXT_CACHE.size === INITIAL_CONTEXT_CACHE_MAX_SIZE) {
|
1088 |
|
1089 |
|
1090 | INITIAL_CONTEXT_CACHE.clear();
|
1091 | }
|
1092 | INITIAL_CONTEXT_CACHE.set(key, initialContext);
|
1093 | return initialContext;
|
1094 |
|
1095 | |
1096 |
|
1097 |
|
1098 |
|
1099 |
|
1100 |
|
1101 | function _createInverseContext() {
|
1102 | const activeCtx = this;
|
1103 |
|
1104 |
|
1105 | if(activeCtx.inverse) {
|
1106 | return activeCtx.inverse;
|
1107 | }
|
1108 | const inverse = activeCtx.inverse = {};
|
1109 |
|
1110 |
|
1111 | const fastCurieMap = activeCtx.fastCurieMap = {};
|
1112 | const irisToTerms = {};
|
1113 |
|
1114 |
|
1115 | const defaultLanguage = (activeCtx['@language'] || '@none').toLowerCase();
|
1116 |
|
1117 |
|
1118 | const defaultDirection = activeCtx['@direction'];
|
1119 |
|
1120 |
|
1121 |
|
1122 | const mappings = activeCtx.mappings;
|
1123 | const terms = [...mappings.keys()].sort(_compareShortestLeast);
|
1124 | for(const term of terms) {
|
1125 | const mapping = mappings.get(term);
|
1126 | if(mapping === null) {
|
1127 | continue;
|
1128 | }
|
1129 |
|
1130 | let container = mapping['@container'] || '@none';
|
1131 | container = [].concat(container).sort().join('');
|
1132 |
|
1133 | if(mapping['@id'] === null) {
|
1134 | continue;
|
1135 | }
|
1136 |
|
1137 | const ids = _asArray(mapping['@id']);
|
1138 | for(const iri of ids) {
|
1139 | let entry = inverse[iri];
|
1140 | const isKeyword = api.isKeyword(iri);
|
1141 |
|
1142 | if(!entry) {
|
1143 |
|
1144 | inverse[iri] = entry = {};
|
1145 |
|
1146 | if(!isKeyword && !mapping._termHasColon) {
|
1147 |
|
1148 | irisToTerms[iri] = [term];
|
1149 | const fastCurieEntry = {iri, terms: irisToTerms[iri]};
|
1150 | if(iri[0] in fastCurieMap) {
|
1151 | fastCurieMap[iri[0]].push(fastCurieEntry);
|
1152 | } else {
|
1153 | fastCurieMap[iri[0]] = [fastCurieEntry];
|
1154 | }
|
1155 | }
|
1156 | } else if(!isKeyword && !mapping._termHasColon) {
|
1157 |
|
1158 | irisToTerms[iri].push(term);
|
1159 | }
|
1160 |
|
1161 |
|
1162 | if(!entry[container]) {
|
1163 | entry[container] = {
|
1164 | '@language': {},
|
1165 | '@type': {},
|
1166 | '@any': {}
|
1167 | };
|
1168 | }
|
1169 | entry = entry[container];
|
1170 | _addPreferredTerm(term, entry['@any'], '@none');
|
1171 |
|
1172 | if(mapping.reverse) {
|
1173 |
|
1174 | _addPreferredTerm(term, entry['@type'], '@reverse');
|
1175 | } else if(mapping['@type'] === '@none') {
|
1176 | _addPreferredTerm(term, entry['@any'], '@none');
|
1177 | _addPreferredTerm(term, entry['@language'], '@none');
|
1178 | _addPreferredTerm(term, entry['@type'], '@none');
|
1179 | } else if('@type' in mapping) {
|
1180 |
|
1181 | _addPreferredTerm(term, entry['@type'], mapping['@type']);
|
1182 | } else if('@language' in mapping && '@direction' in mapping) {
|
1183 |
|
1184 | const language = mapping['@language'];
|
1185 | const direction = mapping['@direction'];
|
1186 | if(language && direction) {
|
1187 | _addPreferredTerm(term, entry['@language'],
|
1188 | `${language}_${direction}`.toLowerCase());
|
1189 | } else if(language) {
|
1190 | _addPreferredTerm(term, entry['@language'], language.toLowerCase());
|
1191 | } else if(direction) {
|
1192 | _addPreferredTerm(term, entry['@language'], `_${direction}`);
|
1193 | } else {
|
1194 | _addPreferredTerm(term, entry['@language'], '@null');
|
1195 | }
|
1196 | } else if('@language' in mapping) {
|
1197 | _addPreferredTerm(term, entry['@language'],
|
1198 | (mapping['@language'] || '@null').toLowerCase());
|
1199 | } else if('@direction' in mapping) {
|
1200 | if(mapping['@direction']) {
|
1201 | _addPreferredTerm(term, entry['@language'],
|
1202 | `_${mapping['@direction']}`);
|
1203 | } else {
|
1204 | _addPreferredTerm(term, entry['@language'], '@none');
|
1205 | }
|
1206 | } else if(defaultDirection) {
|
1207 | _addPreferredTerm(term, entry['@language'], `_${defaultDirection}`);
|
1208 | _addPreferredTerm(term, entry['@language'], '@none');
|
1209 | _addPreferredTerm(term, entry['@type'], '@none');
|
1210 | } else {
|
1211 |
|
1212 | _addPreferredTerm(term, entry['@language'], defaultLanguage);
|
1213 | _addPreferredTerm(term, entry['@language'], '@none');
|
1214 | _addPreferredTerm(term, entry['@type'], '@none');
|
1215 | }
|
1216 | }
|
1217 | }
|
1218 |
|
1219 |
|
1220 | for(const key in fastCurieMap) {
|
1221 | _buildIriMap(fastCurieMap, key, 1);
|
1222 | }
|
1223 |
|
1224 | return inverse;
|
1225 | }
|
1226 |
|
1227 | |
1228 |
|
1229 |
|
1230 |
|
1231 |
|
1232 |
|
1233 |
|
1234 |
|
1235 | function _buildIriMap(iriMap, key, idx) {
|
1236 | const entries = iriMap[key];
|
1237 | const next = iriMap[key] = {};
|
1238 |
|
1239 | let iri;
|
1240 | let letter;
|
1241 | for(const entry of entries) {
|
1242 | iri = entry.iri;
|
1243 | if(idx >= iri.length) {
|
1244 | letter = '';
|
1245 | } else {
|
1246 | letter = iri[idx];
|
1247 | }
|
1248 | if(letter in next) {
|
1249 | next[letter].push(entry);
|
1250 | } else {
|
1251 | next[letter] = [entry];
|
1252 | }
|
1253 | }
|
1254 |
|
1255 | for(const key in next) {
|
1256 | if(key === '') {
|
1257 | continue;
|
1258 | }
|
1259 | _buildIriMap(next, key, idx + 1);
|
1260 | }
|
1261 | }
|
1262 |
|
1263 | |
1264 |
|
1265 |
|
1266 |
|
1267 |
|
1268 |
|
1269 |
|
1270 | function _addPreferredTerm(term, entry, typeOrLanguageValue) {
|
1271 | if(!entry.hasOwnProperty(typeOrLanguageValue)) {
|
1272 | entry[typeOrLanguageValue] = term;
|
1273 | }
|
1274 | }
|
1275 |
|
1276 | |
1277 |
|
1278 |
|
1279 |
|
1280 |
|
1281 | function _cloneActiveContext() {
|
1282 | const child = {};
|
1283 | child.mappings = util.clone(this.mappings);
|
1284 | child.clone = this.clone;
|
1285 | child.inverse = null;
|
1286 | child.getInverse = this.getInverse;
|
1287 | child.protected = util.clone(this.protected);
|
1288 | if(this.previousContext) {
|
1289 | child.previousContext = this.previousContext.clone();
|
1290 | }
|
1291 | child.revertToPreviousContext = this.revertToPreviousContext;
|
1292 | if('@base' in this) {
|
1293 | child['@base'] = this['@base'];
|
1294 | }
|
1295 | if('@language' in this) {
|
1296 | child['@language'] = this['@language'];
|
1297 | }
|
1298 | if('@vocab' in this) {
|
1299 | child['@vocab'] = this['@vocab'];
|
1300 | }
|
1301 | return child;
|
1302 | }
|
1303 |
|
1304 | |
1305 |
|
1306 |
|
1307 |
|
1308 | function _revertToPreviousContext() {
|
1309 | if(!this.previousContext) {
|
1310 | return this;
|
1311 | }
|
1312 | return this.previousContext.clone();
|
1313 | }
|
1314 | };
|
1315 |
|
1316 |
|
1317 |
|
1318 |
|
1319 |
|
1320 |
|
1321 |
|
1322 |
|
1323 |
|
1324 |
|
1325 |
|
1326 |
|
1327 | api.getContextValue = (ctx, key, type) => {
|
1328 |
|
1329 | if(key === null) {
|
1330 | if(type === '@context') {
|
1331 | return undefined;
|
1332 | }
|
1333 | return null;
|
1334 | }
|
1335 |
|
1336 |
|
1337 | if(ctx.mappings.has(key)) {
|
1338 | const entry = ctx.mappings.get(key);
|
1339 |
|
1340 | if(_isUndefined(type)) {
|
1341 |
|
1342 | return entry;
|
1343 | }
|
1344 | if(entry.hasOwnProperty(type)) {
|
1345 |
|
1346 | return entry[type];
|
1347 | }
|
1348 | }
|
1349 |
|
1350 |
|
1351 | if(type === '@language' && type in ctx) {
|
1352 | return ctx[type];
|
1353 | }
|
1354 |
|
1355 |
|
1356 | if(type === '@direction' && type in ctx) {
|
1357 | return ctx[type];
|
1358 | }
|
1359 |
|
1360 | if(type === '@context') {
|
1361 | return undefined;
|
1362 | }
|
1363 | return null;
|
1364 | };
|
1365 |
|
1366 |
|
1367 |
|
1368 |
|
1369 |
|
1370 |
|
1371 |
|
1372 |
|
1373 |
|
1374 | api.processingMode = (activeCtx, version) => {
|
1375 | if(version.toString() >= '1.1') {
|
1376 | return !activeCtx.processingMode ||
|
1377 | activeCtx.processingMode >= 'json-ld-' + version.toString();
|
1378 | } else {
|
1379 | return activeCtx.processingMode === 'json-ld-1.0';
|
1380 | }
|
1381 | };
|
1382 |
|
1383 |
|
1384 |
|
1385 |
|
1386 |
|
1387 |
|
1388 |
|
1389 |
|
1390 | api.isKeyword = v => {
|
1391 | if(!_isString(v) || v[0] !== '@') {
|
1392 | return false;
|
1393 | }
|
1394 | switch(v) {
|
1395 | case '@base':
|
1396 | case '@container':
|
1397 | case '@context':
|
1398 | case '@default':
|
1399 | case '@direction':
|
1400 | case '@embed':
|
1401 | case '@explicit':
|
1402 | case '@graph':
|
1403 | case '@id':
|
1404 | case '@included':
|
1405 | case '@index':
|
1406 | case '@json':
|
1407 | case '@language':
|
1408 | case '@list':
|
1409 | case '@nest':
|
1410 | case '@none':
|
1411 | case '@omitDefault':
|
1412 | case '@prefix':
|
1413 | case '@preserve':
|
1414 | case '@protected':
|
1415 | case '@requireAll':
|
1416 | case '@reverse':
|
1417 | case '@set':
|
1418 | case '@type':
|
1419 | case '@value':
|
1420 | case '@version':
|
1421 | case '@vocab':
|
1422 | return true;
|
1423 | }
|
1424 | return false;
|
1425 | };
|
1426 |
|
1427 | function _deepCompare(x1, x2) {
|
1428 |
|
1429 | if((!(x1 && typeof x1 === 'object')) ||
|
1430 | (!(x2 && typeof x2 === 'object'))) {
|
1431 | return x1 === x2;
|
1432 | }
|
1433 |
|
1434 | const x1Array = Array.isArray(x1);
|
1435 | if(x1Array !== Array.isArray(x2)) {
|
1436 | return false;
|
1437 | }
|
1438 | if(x1Array) {
|
1439 | if(x1.length !== x2.length) {
|
1440 | return false;
|
1441 | }
|
1442 | for(let i = 0; i < x1.length; ++i) {
|
1443 | if(!_deepCompare(x1[i], x2[i])) {
|
1444 | return false;
|
1445 | }
|
1446 | }
|
1447 | return true;
|
1448 | }
|
1449 |
|
1450 | const k1s = Object.keys(x1);
|
1451 | const k2s = Object.keys(x2);
|
1452 | if(k1s.length !== k2s.length) {
|
1453 | return false;
|
1454 | }
|
1455 | for(const k1 in x1) {
|
1456 | let v1 = x1[k1];
|
1457 | let v2 = x2[k1];
|
1458 |
|
1459 | if(k1 === '@container') {
|
1460 | if(Array.isArray(v1) && Array.isArray(v2)) {
|
1461 | v1 = v1.slice().sort();
|
1462 | v2 = v2.slice().sort();
|
1463 | }
|
1464 | }
|
1465 | if(!_deepCompare(v1, v2)) {
|
1466 | return false;
|
1467 | }
|
1468 | }
|
1469 | return true;
|
1470 | }
|