{"version":3,"file":"jsonld.min.js","sources":["webpack://[name]/webpack/universalModuleDefinition","webpack://[name]/./lib/context.js","webpack://[name]/./lib/jsonld.js","webpack://[name]/./lib/compact.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"jsonld\"] = factory();\n\telse\n\t\troot[\"jsonld\"] = factory();\n})(window, function() {\nreturn ","/*\n * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.\n */\n'use strict';\n\nconst util = require('./util');\nconst ActiveContextCache = require('./ActiveContextCache');\nconst JsonLdError = require('./JsonLdError');\n\nconst {\n isArray: _isArray,\n isObject: _isObject,\n isString: _isString,\n isUndefined: _isUndefined\n} = require('./types');\n\nconst {\n isAbsolute: _isAbsoluteIri,\n isRelative: _isRelativeIri,\n prependBase,\n parse: parseUrl\n} = require('./url');\n\nconst {\n asArray: _asArray,\n compareShortestLeast: _compareShortestLeast\n} = require('./util');\n\nconst MAX_CONTEXT_URLS = 10;\n\nconst INITIAL_CONTEXT_CACHE = new Map();\nconst INITIAL_CONTEXT_CACHE_MAX_SIZE = 10000;\n\nconst api = {};\nmodule.exports = api;\n\napi.cache = new ActiveContextCache();\n\n/**\n * Processes a local context and returns a new active context.\n *\n * @param activeCtx the current active context.\n * @param localCtx the local context to process.\n * @param options the context processing options.\n * @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context\n * from a property term.\n * @param isTypeScopedContext `true` if `localCtx` is a scoped context\n * from a type.\n *\n * @return the new active context.\n */\napi.process = ({\n activeCtx, localCtx, options,\n isPropertyTermScopedContext = false,\n isTypeScopedContext = false\n}) => {\n // normalize local context to an array of @context objects\n if(_isObject(localCtx) && '@context' in localCtx &&\n _isArray(localCtx['@context'])) {\n localCtx = localCtx['@context'];\n }\n const ctxs = _asArray(localCtx);\n\n // no contexts in array, return current active context w/o changes\n if(ctxs.length === 0) {\n return activeCtx;\n }\n\n // track the previous context\n const previousContext = activeCtx.previousContext || activeCtx;\n\n // if context is property scoped and there's a previous context, amend it,\n // not the current one\n if(isPropertyTermScopedContext && activeCtx.previousContext) {\n // TODO: consider optimizing to a shallow copy\n activeCtx = activeCtx.clone();\n activeCtx.isPropertyTermScoped = true;\n activeCtx.previousContext = api.process({\n activeCtx: activeCtx.previousContext,\n localCtx: ctxs,\n options,\n isPropertyTermScopedContext\n });\n return activeCtx;\n }\n\n // process each context in order, update active context\n // on each iteration to ensure proper caching\n let rval = activeCtx;\n for(let i = 0; i < ctxs.length; ++i) {\n let ctx = ctxs[i];\n\n // update active context to one computed from last iteration\n activeCtx = rval;\n\n // reset to initial context\n if(ctx === null) {\n // We can't nullify if there are protected terms and we're\n // not processing a property term scoped context\n if(!isPropertyTermScopedContext &&\n Object.keys(activeCtx.protected).length !== 0) {\n const protectedMode = (options && options.protectedMode) || 'error';\n if(protectedMode === 'error') {\n throw new JsonLdError(\n 'Tried to nullify a context with protected terms outside of ' +\n 'a term definition.',\n 'jsonld.SyntaxError',\n {code: 'invalid context nullification'});\n } else if(protectedMode === 'warn') {\n // FIXME: remove logging and use a handler\n console.warn('WARNING: invalid context nullification');\n const oldActiveCtx = activeCtx;\n // copy all protected term definitions to fresh initial context\n rval = activeCtx = api.getInitialContext(options).clone();\n for(const [term, _protected] of\n Object.entries(oldActiveCtx.protected)) {\n if(_protected) {\n activeCtx.mappings[term] =\n util.clone(oldActiveCtx.mappings[term]);\n }\n }\n activeCtx.protected = util.clone(oldActiveCtx.protected);\n\n // cache result\n if(api.cache) {\n api.cache.set(oldActiveCtx, ctx, rval);\n }\n\n continue;\n }\n throw new JsonLdError(\n 'Invalid protectedMode.',\n 'jsonld.SyntaxError',\n {code: 'invalid protected mode', context: localCtx, protectedMode});\n }\n rval = activeCtx = api.getInitialContext(options).clone();\n // if context is type-scoped, ensure previous context has been set\n if(isTypeScopedContext) {\n rval.previousContext = previousContext.clone();\n }\n continue;\n }\n\n // get context from cache if available\n if(api.cache) {\n const cached = api.cache.get(activeCtx, ctx);\n if(cached) {\n rval = activeCtx = cached;\n continue;\n }\n }\n\n // dereference @context key if present\n if(_isObject(ctx) && '@context' in ctx) {\n ctx = ctx['@context'];\n }\n\n // context must be an object by now, all URLs retrieved before this call\n if(!_isObject(ctx)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context must be an object.',\n 'jsonld.SyntaxError', {code: 'invalid local context', context: ctx});\n }\n\n // TODO: there is likely a `preivousContext` cloning optimization that\n // could be applied here (no need to copy it under certain conditions)\n\n // clone context before updating it\n rval = rval.clone();\n\n // define context mappings for keys in local context\n const defined = new Map();\n\n // handle @version\n if('@version' in ctx) {\n if(ctx['@version'] !== 1.1) {\n throw new JsonLdError(\n 'Unsupported JSON-LD version: ' + ctx['@version'],\n 'jsonld.UnsupportedVersion',\n {code: 'invalid @version value', context: ctx});\n }\n if(activeCtx.processingMode &&\n activeCtx.processingMode === 'json-ld-1.0') {\n throw new JsonLdError(\n '@version: ' + ctx['@version'] + ' not compatible with ' +\n activeCtx.processingMode,\n 'jsonld.ProcessingModeConflict',\n {code: 'processing mode conflict', context: ctx});\n }\n rval.processingMode = 'json-ld-1.1';\n rval['@version'] = ctx['@version'];\n defined.set('@version', true);\n }\n\n // if not set explicitly, set processingMode to \"json-ld-1.0\"\n rval.processingMode =\n rval.processingMode || activeCtx.processingMode || 'json-ld-1.0';\n\n // handle @base\n if('@base' in ctx) {\n let base = ctx['@base'];\n\n if(base === null) {\n // no action\n } else if(_isAbsoluteIri(base)) {\n base = parseUrl(base);\n } else if(_isRelativeIri(base)) {\n base = parseUrl(prependBase(activeCtx['@base'].href, base));\n } else {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; the value of \"@base\" in a ' +\n '@context must be an absolute IRI, a relative IRI, or null.',\n 'jsonld.SyntaxError', {code: 'invalid base IRI', context: ctx});\n }\n\n rval['@base'] = base;\n defined.set('@base', true);\n }\n\n // handle @vocab\n if('@vocab' in ctx) {\n const value = ctx['@vocab'];\n if(value === null) {\n delete rval['@vocab'];\n } else if(!_isString(value)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; the value of \"@vocab\" in a ' +\n '@context must be a string or null.',\n 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx});\n } else if(!_isAbsoluteIri(value)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; the value of \"@vocab\" in a ' +\n '@context must be an absolute IRI.',\n 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx});\n } else {\n rval['@vocab'] = value;\n }\n defined.set('@vocab', true);\n }\n\n // handle @language\n if('@language' in ctx) {\n const value = ctx['@language'];\n if(value === null) {\n delete rval['@language'];\n } else if(!_isString(value)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; the value of \"@language\" in a ' +\n '@context must be a string or null.',\n 'jsonld.SyntaxError',\n {code: 'invalid default language', context: ctx});\n } else {\n rval['@language'] = value.toLowerCase();\n }\n defined.set('@language', true);\n }\n\n // handle @protected; determine whether this sub-context is declaring\n // all its terms to be \"protected\" (exceptions can be made on a\n // per-definition basis)\n defined.set('@protected', ctx['@protected'] || false);\n\n // process all other keys\n for(const key in ctx) {\n api.createTermDefinition(\n rval, ctx, key, defined, options,\n isPropertyTermScopedContext);\n }\n\n // if context is type-scoped, ensure previous context has been set\n if(isTypeScopedContext && !rval.previousContext) {\n rval.previousContext = previousContext.clone();\n }\n\n // cache result\n if(api.cache) {\n api.cache.set(activeCtx, ctx, rval);\n }\n }\n\n return rval;\n};\n\n/**\n * Creates a term definition during context processing.\n *\n * @param activeCtx the current active context.\n * @param localCtx the local context being processed.\n * @param term the term in the local context to define the mapping for.\n * @param defined a map of defining/defined keys to detect cycles and prevent\n * double definitions.\n * @param {Object} [options] - creation options.\n * @param {string} [options.protectedMode=\"error\"] - \"error\" to throw error\n * on `@protected` constraint violation, \"warn\" to allow violations and\n * signal a warning.\n * @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context\n * from a property term.\n */\napi.createTermDefinition = (\n activeCtx, localCtx, term, defined, options,\n isPropertyTermScopedContext = false) => {\n if(defined.has(term)) {\n // term already defined\n if(defined.get(term)) {\n return;\n }\n // cycle detected\n throw new JsonLdError(\n 'Cyclical context definition detected.',\n 'jsonld.CyclicalContext',\n {code: 'cyclic IRI mapping', context: localCtx, term});\n }\n\n // now defining term\n defined.set(term, false);\n\n if(api.isKeyword(term)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; keywords cannot be overridden.',\n 'jsonld.SyntaxError',\n {code: 'keyword redefinition', context: localCtx, term});\n }\n\n if(term === '') {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a term cannot be an empty string.',\n 'jsonld.SyntaxError',\n {code: 'invalid term definition', context: localCtx});\n }\n\n // keep reference to previous mapping for potential `@protected` check\n const previousMapping = activeCtx.mappings.get(term);\n\n // remove old mapping\n if(activeCtx.mappings.has(term)) {\n activeCtx.mappings.delete(term);\n }\n\n // get context term value\n let value;\n if(localCtx.hasOwnProperty(term)) {\n value = localCtx[term];\n }\n\n // clear context entry\n if(value === null || (_isObject(value) && value['@id'] === null)) {\n activeCtx.mappings.set(term, null);\n defined.set(term, true);\n return;\n }\n\n // convert short-hand value to object w/@id\n let simpleTerm = false;\n if(_isString(value)) {\n simpleTerm = true;\n value = {'@id': value};\n }\n\n if(!_isObject(value)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context term values must be ' +\n 'strings or objects.',\n 'jsonld.SyntaxError',\n {code: 'invalid term definition', context: localCtx});\n }\n\n // create new mapping\n const mapping = {};\n activeCtx.mappings.set(term, mapping);\n mapping.reverse = false;\n\n // make sure term definition only has expected keywords\n const validKeys = ['@container', '@id', '@language', '@reverse', '@type'];\n\n // JSON-LD 1.1 support\n if(api.processingMode(activeCtx, 1.1)) {\n validKeys.push('@context', '@nest', '@prefix', '@protected');\n }\n\n for(const kw in value) {\n if(!validKeys.includes(kw)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a term definition must not contain ' + kw,\n 'jsonld.SyntaxError',\n {code: 'invalid term definition', context: localCtx});\n }\n }\n\n // always compute whether term has a colon as an optimization for\n // _compactIri\n const colon = term.indexOf(':');\n mapping._termHasColon = (colon !== -1);\n\n if('@reverse' in value) {\n if('@id' in value) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @reverse term definition must not ' +\n 'contain @id.', 'jsonld.SyntaxError',\n {code: 'invalid reverse property', context: localCtx});\n }\n if('@nest' in value) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @reverse term definition must not ' +\n 'contain @nest.', 'jsonld.SyntaxError',\n {code: 'invalid reverse property', context: localCtx});\n }\n const reverse = value['@reverse'];\n if(!_isString(reverse)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @context @reverse value must be a string.',\n 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});\n }\n\n // expand and add @id mapping\n const id = _expandIri(\n activeCtx, reverse, {vocab: true, base: false}, localCtx, defined,\n options);\n if(!_isAbsoluteIri(id)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @context @reverse value must be an ' +\n 'absolute IRI or a blank node identifier.',\n 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});\n }\n mapping['@id'] = id;\n mapping.reverse = true;\n } else if('@id' in value) {\n let id = value['@id'];\n if(!_isString(id)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @context @id value must be an array ' +\n 'of strings or a string.',\n 'jsonld.SyntaxError', {code: 'invalid IRI mapping', context: localCtx});\n }\n if(id !== term) {\n // expand and add @id mapping\n id = _expandIri(\n activeCtx, id, {vocab: true, base: false}, localCtx, defined, options);\n if(!_isAbsoluteIri(id) && !api.isKeyword(id)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; a @context @id value must be an ' +\n 'absolute IRI, a blank node identifier, or a keyword.',\n 'jsonld.SyntaxError',\n {code: 'invalid IRI mapping', context: localCtx});\n }\n mapping['@id'] = id;\n // indicate if this term may be used as a compact IRI prefix\n mapping._prefix = (!mapping._termHasColon &&\n id.match(/[:\\/\\?#\\[\\]@]$/) &&\n (simpleTerm || api.processingMode(activeCtx, 1.0)));\n }\n }\n\n if(!('@id' in mapping)) {\n // see if the term has a prefix\n if(mapping._termHasColon) {\n const prefix = term.substr(0, colon);\n if(localCtx.hasOwnProperty(prefix)) {\n // define parent prefix\n api.createTermDefinition(activeCtx, localCtx, prefix, defined, options);\n }\n\n if(activeCtx.mappings.has(prefix)) {\n // set @id based on prefix parent\n const suffix = term.substr(colon + 1);\n mapping['@id'] = activeCtx.mappings.get(prefix)['@id'] + suffix;\n } else {\n // term is an absolute IRI\n mapping['@id'] = term;\n }\n } else {\n // non-IRIs *must* define @ids if @vocab is not available\n if(!('@vocab' in activeCtx)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context terms must define an @id.',\n 'jsonld.SyntaxError',\n {code: 'invalid IRI mapping', context: localCtx, term});\n }\n // prepend vocab to term\n mapping['@id'] = activeCtx['@vocab'] + term;\n }\n }\n\n // Handle term protection\n if(value['@protected'] === true ||\n (defined.get('@protected') === true && value['@protected'] !== false)) {\n activeCtx.protected[term] = true;\n mapping.protected = true;\n }\n\n // IRI mapping now defined\n defined.set(term, true);\n\n if('@type' in value) {\n let type = value['@type'];\n if(!_isString(type)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; an @context @type value must be a string.',\n 'jsonld.SyntaxError',\n {code: 'invalid type mapping', context: localCtx});\n }\n\n if(type !== '@id' && type !== '@vocab') {\n // expand @type to full IRI\n type = _expandIri(\n activeCtx, type, {vocab: true, base: false}, localCtx, defined,\n options);\n if(!_isAbsoluteIri(type)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; an @context @type value must be an ' +\n 'absolute IRI.',\n 'jsonld.SyntaxError',\n {code: 'invalid type mapping', context: localCtx});\n }\n if(type.indexOf('_:') === 0) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; an @context @type value must be an IRI, ' +\n 'not a blank node identifier.',\n 'jsonld.SyntaxError',\n {code: 'invalid type mapping', context: localCtx});\n }\n }\n\n // add @type to mapping\n mapping['@type'] = type;\n }\n\n if('@container' in value) {\n // normalize container to an array form\n const container = _isString(value['@container']) ?\n [value['@container']] : (value['@container'] || []);\n const validContainers = ['@list', '@set', '@index', '@language'];\n let isValid = true;\n const hasSet = container.includes('@set');\n\n // JSON-LD 1.1 support\n if(api.processingMode(activeCtx, 1.1)) {\n validContainers.push('@graph', '@id', '@type');\n\n // check container length\n if(container.includes('@list')) {\n if(container.length !== 1) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @container with @list must ' +\n 'have no other values',\n 'jsonld.SyntaxError',\n {code: 'invalid container mapping', context: localCtx});\n }\n } else if(container.includes('@graph')) {\n if(container.some(key =>\n key !== '@graph' && key !== '@id' && key !== '@index' &&\n key !== '@set')) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @container with @graph must ' +\n 'have no other values other than @id, @index, and @set',\n 'jsonld.SyntaxError',\n {code: 'invalid container mapping', context: localCtx});\n }\n } else {\n // otherwise, container may also include @set\n isValid &= container.length <= (hasSet ? 2 : 1);\n }\n } else {\n // in JSON-LD 1.0, container must not be an array (it must be a string,\n // which is one of the validContainers)\n isValid &= !_isArray(value['@container']);\n\n // check container length\n isValid &= container.length <= 1;\n }\n\n // check against valid containers\n isValid &= container.every(c => validContainers.includes(c));\n\n // @set not allowed with @list\n isValid &= !(hasSet && container.includes('@list'));\n\n if(!isValid) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @container value must be ' +\n 'one of the following: ' + validContainers.join(', '),\n 'jsonld.SyntaxError',\n {code: 'invalid container mapping', context: localCtx});\n }\n\n if(mapping.reverse &&\n !container.every(c => ['@index', '@set'].includes(c))) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @container value for a @reverse ' +\n 'type definition must be @index or @set.', 'jsonld.SyntaxError',\n {code: 'invalid reverse property', context: localCtx});\n }\n\n // add @container to mapping\n mapping['@container'] = container;\n }\n\n // scoped contexts\n if('@context' in value) {\n mapping['@context'] = value['@context'];\n }\n\n if('@language' in value && !('@type' in value)) {\n let language = value['@language'];\n if(language !== null && !_isString(language)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @language value must be ' +\n 'a string or null.', 'jsonld.SyntaxError',\n {code: 'invalid language mapping', context: localCtx});\n }\n\n // add @language to mapping\n if(language !== null) {\n language = language.toLowerCase();\n }\n mapping['@language'] = language;\n }\n\n // term may be used as a prefix\n if('@prefix' in value) {\n if(mapping._termHasColon) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @prefix used on a compact IRI term',\n 'jsonld.SyntaxError',\n {code: 'invalid term definition', context: localCtx});\n }\n if(typeof value['@prefix'] === 'boolean') {\n mapping._prefix = value['@prefix'] === true;\n } else {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context value for @prefix must be boolean',\n 'jsonld.SyntaxError',\n {code: 'invalid @prefix value', context: localCtx});\n }\n }\n\n if('@nest' in value) {\n const nest = value['@nest'];\n if(!_isString(nest) || (nest !== '@nest' && nest.indexOf('@') === 0)) {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context @nest value must be ' +\n 'a string which is not a keyword other than @nest.',\n 'jsonld.SyntaxError',\n {code: 'invalid @nest value', context: localCtx});\n }\n mapping['@nest'] = nest;\n }\n\n // disallow aliasing @context and @preserve\n const id = mapping['@id'];\n if(id === '@context' || id === '@preserve') {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; @context and @preserve cannot be aliased.',\n 'jsonld.SyntaxError', {code: 'invalid keyword alias', context: localCtx});\n }\n\n // FIXME if(1.1) ... ?\n if(previousMapping && previousMapping.protected &&\n !isPropertyTermScopedContext) {\n // force new term to continue to be protected and see if the mappings would\n // be equal\n activeCtx.protected[term] = true;\n mapping.protected = true;\n if(!_deepCompare(previousMapping, mapping)) {\n const protectedMode = (options && options.protectedMode) || 'error';\n if(protectedMode === 'error') {\n throw new JsonLdError(\n 'Invalid JSON-LD syntax; tried to redefine a protected term.',\n 'jsonld.SyntaxError',\n {code: 'protected term redefinition', context: localCtx, term});\n } else if(protectedMode === 'warn') {\n // FIXME: remove logging and use a handler\n console.warn('WARNING: protected term redefinition', {term});\n return;\n }\n throw new JsonLdError(\n 'Invalid protectedMode.',\n 'jsonld.SyntaxError',\n {code: 'invalid protected mode', context: localCtx, term,\n protectedMode});\n }\n }\n};\n\n/**\n * Expands a string to a full IRI. The string may be a term, a prefix, a\n * relative IRI, or an absolute IRI. The associated absolute IRI will be\n * returned.\n *\n * @param activeCtx the current active context.\n * @param value the string to expand.\n * @param relativeTo options for how to resolve relative IRIs:\n * base: true to resolve against the base IRI, false not to.\n * vocab: true to concatenate after @vocab, false not to.\n * @param {Object} [options] - processing options.\n *\n * @return the expanded value.\n */\napi.expandIri = (activeCtx, value, relativeTo, options) => {\n return _expandIri(activeCtx, value, relativeTo, undefined, undefined,\n options);\n};\n\n/**\n * Expands a string to a full IRI. The string may be a term, a prefix, a\n * relative IRI, or an absolute IRI. The associated absolute IRI will be\n * returned.\n *\n * @param activeCtx the current active context.\n * @param value the string to expand.\n * @param relativeTo options for how to resolve relative IRIs:\n * base: true to resolve against the base IRI, false not to.\n * vocab: true to concatenate after @vocab, false not to.\n * @param localCtx the local context being processed (only given if called\n * during context processing).\n * @param defined a map for tracking cycles in context definitions (only given\n * if called during context processing).\n * @param {Object} [options] - processing options.\n *\n * @return the expanded value.\n */\nfunction _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) {\n // already expanded\n if(value === null || !_isString(value) || api.isKeyword(value)) {\n return value;\n }\n\n // define term dependency if not defined\n if(localCtx && localCtx.hasOwnProperty(value) &&\n defined.get(value) !== true) {\n api.createTermDefinition(activeCtx, localCtx, value, defined, options);\n }\n\n // if context is from a property term scoped context composed with a\n // type-scoped context, then use previous context instead\n if(activeCtx.isPropertyTermScoped && activeCtx.previousContext) {\n activeCtx = activeCtx.previousContext;\n }\n\n relativeTo = relativeTo || {};\n if(relativeTo.vocab) {\n const mapping = activeCtx.mappings.get(value);\n\n // value is explicitly ignored with a null mapping\n if(mapping === null) {\n return null;\n }\n\n if(mapping) {\n // value is a term\n return mapping['@id'];\n }\n }\n\n // split value into prefix:suffix\n const colon = value.indexOf(':');\n if(colon !== -1) {\n const prefix = value.substr(0, colon);\n const suffix = value.substr(colon + 1);\n\n // do not expand blank nodes (prefix of '_') or already-absolute\n // IRIs (suffix of '//')\n if(prefix === '_' || suffix.indexOf('//') === 0) {\n return value;\n }\n\n // prefix dependency not defined, define it\n if(localCtx && localCtx.hasOwnProperty(prefix)) {\n api.createTermDefinition(activeCtx, localCtx, prefix, defined, options);\n }\n\n // use mapping if prefix is defined\n if(activeCtx.mappings.has(prefix)) {\n const mapping = activeCtx.mappings.get(prefix);\n return mapping['@id'] + suffix;\n }\n\n // already absolute IRI\n return value;\n }\n\n // prepend vocab\n if(relativeTo.vocab && '@vocab' in activeCtx) {\n return activeCtx['@vocab'] + value;\n }\n\n // prepend base\n if(relativeTo.base) {\n return prependBase(activeCtx['@base'], value);\n }\n\n return value;\n}\n\n/**\n * Gets the initial context.\n *\n * @param options the options to use:\n * [base] the document base IRI.\n *\n * @return the initial context.\n */\napi.getInitialContext = options => {\n const base = parseUrl(options.base || '');\n const key = JSON.stringify({base, processingMode: options.processingMode});\n const cached = INITIAL_CONTEXT_CACHE.get(key);\n if(cached) {\n return cached;\n }\n\n const initialContext = {\n '@base': base,\n processingMode: options.processingMode,\n mappings: new Map(),\n inverse: null,\n getInverse: _createInverseContext,\n clone: _cloneActiveContext,\n revertTypeScopedContext: _revertTypeScopedContext,\n protected: {}\n };\n // TODO: consider using LRU cache instead\n if(INITIAL_CONTEXT_CACHE.size === INITIAL_CONTEXT_CACHE_MAX_SIZE) {\n // clear whole cache -- assumes scenario where the cache fills means\n // the cache isn't being used very efficiently anyway\n INITIAL_CONTEXT_CACHE.clear();\n }\n INITIAL_CONTEXT_CACHE.set(key, initialContext);\n return initialContext;\n\n /**\n * Generates an inverse context for use in the compaction algorithm, if\n * not already generated for the given active context.\n *\n * @return the inverse context.\n */\n function _createInverseContext() {\n const activeCtx = this;\n\n // lazily create inverse\n if(activeCtx.inverse) {\n return activeCtx.inverse;\n }\n const inverse = activeCtx.inverse = {};\n\n // variables for building fast CURIE map\n const fastCurieMap = activeCtx.fastCurieMap = {};\n const irisToTerms = {};\n\n // handle default language\n const defaultLanguage = activeCtx['@language'] || '@none';\n\n // create term selections for each mapping in the context, ordered by\n // shortest and then lexicographically least\n const mappings = activeCtx.mappings;\n const terms = [...mappings.keys()].sort(_compareShortestLeast);\n for(const term of terms) {\n const mapping = mappings.get(term);\n if(mapping === null) {\n continue;\n }\n\n let container = mapping['@container'] || '@none';\n container = [].concat(container).sort().join('');\n\n // iterate over every IRI in the mapping\n const ids = _asArray(mapping['@id']);\n for(const iri of ids) {\n let entry = inverse[iri];\n const isKeyword = api.isKeyword(iri);\n\n if(!entry) {\n // initialize entry\n inverse[iri] = entry = {};\n\n if(!isKeyword && !mapping._termHasColon) {\n // init IRI to term map and fast CURIE prefixes\n irisToTerms[iri] = [term];\n const fastCurieEntry = {iri, terms: irisToTerms[iri]};\n if(iri[0] in fastCurieMap) {\n fastCurieMap[iri[0]].push(fastCurieEntry);\n } else {\n fastCurieMap[iri[0]] = [fastCurieEntry];\n }\n }\n } else if(!isKeyword && !mapping._termHasColon) {\n // add IRI to term match\n irisToTerms[iri].push(term);\n }\n\n // add new entry\n if(!entry[container]) {\n entry[container] = {\n '@language': {},\n '@type': {},\n '@any': {}\n };\n }\n entry = entry[container];\n _addPreferredTerm(term, entry['@any'], '@none');\n\n if(mapping.reverse) {\n // term is preferred for values using @reverse\n _addPreferredTerm(term, entry['@type'], '@reverse');\n } else if('@type' in mapping) {\n // term is preferred for values using specific type\n _addPreferredTerm(term, entry['@type'], mapping['@type']);\n } else if('@language' in mapping) {\n // term is preferred for values using specific language\n const language = mapping['@language'] || '@null';\n _addPreferredTerm(term, entry['@language'], language);\n } else {\n // term is preferred for values w/default language or no type and\n // no language\n // add an entry for the default language\n _addPreferredTerm(term, entry['@language'], defaultLanguage);\n\n // add entries for no type and no language\n _addPreferredTerm(term, entry['@type'], '@none');\n _addPreferredTerm(term, entry['@language'], '@none');\n }\n }\n }\n\n // build fast CURIE map\n for(const key in fastCurieMap) {\n _buildIriMap(fastCurieMap, key, 1);\n }\n\n return inverse;\n }\n\n /**\n * Runs a recursive algorithm to build a lookup map for quickly finding\n * potential CURIEs.\n *\n * @param iriMap the map to build.\n * @param key the current key in the map to work on.\n * @param idx the index into the IRI to compare.\n */\n function _buildIriMap(iriMap, key, idx) {\n const entries = iriMap[key];\n const next = iriMap[key] = {};\n\n let iri;\n let letter;\n for(const entry of entries) {\n iri = entry.iri;\n if(idx >= iri.length) {\n letter = '';\n } else {\n letter = iri[idx];\n }\n if(letter in next) {\n next[letter].push(entry);\n } else {\n next[letter] = [entry];\n }\n }\n\n for(const key in next) {\n if(key === '') {\n continue;\n }\n _buildIriMap(next, key, idx + 1);\n }\n }\n\n /**\n * Adds the term for the given entry if not already added.\n *\n * @param term the term to add.\n * @param entry the inverse context typeOrLanguage entry to add to.\n * @param typeOrLanguageValue the key in the entry to add to.\n */\n function _addPreferredTerm(term, entry, typeOrLanguageValue) {\n if(!entry.hasOwnProperty(typeOrLanguageValue)) {\n entry[typeOrLanguageValue] = term;\n }\n }\n\n /**\n * Clones an active context, creating a child active context.\n *\n * @return a clone (child) of the active context.\n */\n function _cloneActiveContext() {\n const child = {};\n child['@base'] = this['@base'];\n child.mappings = util.clone(this.mappings);\n child.clone = this.clone;\n child.inverse = null;\n child.getInverse = this.getInverse;\n child.protected = util.clone(this.protected);\n if(this.previousContext) {\n child.isPropertyTermScoped = this.previousContext.isPropertyTermScoped;\n child.previousContext = this.previousContext.clone();\n }\n child.revertTypeScopedContext = this.revertTypeScopedContext;\n if('@language' in this) {\n child['@language'] = this['@language'];\n }\n if('@vocab' in this) {\n child['@vocab'] = this['@vocab'];\n }\n return child;\n }\n\n /**\n * Reverts any type-scoped context in this active context to the previous\n * context.\n */\n function _revertTypeScopedContext() {\n if(!this.previousContext) {\n return this;\n }\n return this.previousContext.clone();\n }\n};\n\n/**\n * Gets the value for the given active context key and type, null if none is\n * set or undefined if none is set and type is '@context'.\n *\n * @param ctx the active context.\n * @param key the context key.\n * @param [type] the type of value to get (eg: '@id', '@type'), if not\n * specified gets the entire entry for a key, null if not found.\n *\n * @return the value, null, or undefined.\n */\napi.getContextValue = (ctx, key, type) => {\n // invalid key\n if(key === null) {\n if(type === '@context') {\n return undefined;\n }\n return null;\n }\n\n // get specific entry information\n if(ctx.mappings.has(key)) {\n const entry = ctx.mappings.get(key);\n\n if(_isUndefined(type)) {\n // return whole entry\n return entry;\n }\n if(entry.hasOwnProperty(type)) {\n // return entry value for type\n return entry[type];\n }\n }\n\n // get default language\n if(type === '@language' && ctx.hasOwnProperty(type)) {\n return ctx[type];\n }\n\n if(type === '@context') {\n return undefined;\n }\n return null;\n};\n\n/**\n * Retrieves external @context URLs using the given document loader. Every\n * instance of @context in the input that refers to a URL will be replaced\n * with the JSON @context found at that URL.\n *\n * @param input the JSON-LD input with possible contexts.\n * @param options the options to use:\n * documentLoader(url, [callback(err, remoteDoc)]) the document loader.\n * @param callback(err, input) called once the operation completes.\n */\napi.getAllContexts = async (input, options) => {\n return _retrieveContextUrls(input, options);\n};\n\n/**\n * Processing Mode check.\n *\n * @param activeCtx the current active context.\n * @param version the string or numeric version to check.\n *\n * @return boolean.\n */\napi.processingMode = (activeCtx, version) => {\n if(version.toString() >= '1.1') {\n return activeCtx.processingMode &&\n activeCtx.processingMode >= 'json-ld-' + version.toString();\n } else {\n return !activeCtx.processingMode ||\n activeCtx.processingMode === 'json-ld-1.0';\n }\n};\n\n/**\n * Returns whether or not the given value is a keyword.\n *\n * @param v the value to check.\n *\n * @return true if the value is a keyword, false if not.\n */\napi.isKeyword = v => {\n if(!_isString(v)) {\n return false;\n }\n switch(v) {\n case '@base':\n case '@container':\n case '@context':\n case '@default':\n case '@embed':\n case '@explicit':\n case '@graph':\n case '@id':\n case '@index':\n case '@language':\n case '@list':\n case '@nest':\n case '@none':\n case '@omitDefault':\n case '@prefix':\n case '@preserve':\n case '@protected':\n case '@requireAll':\n case '@reverse':\n case '@set':\n case '@type':\n case '@value':\n case '@version':\n case '@vocab':\n return true;\n }\n return false;\n};\n\nasync function _retrieveContextUrls(input, options) {\n const documentLoader = util.normalizeDocumentLoader(options.documentLoader);\n\n // retrieve all @context URLs in input\n await retrieve(input, new Set(), documentLoader);\n\n return input;\n\n // recursive function that will retrieve all @context URLs in documents\n async function retrieve(doc, cycles, documentLoader) {\n if(cycles.size > MAX_CONTEXT_URLS) {\n throw new JsonLdError(\n 'Maximum number of @context URLs exceeded.',\n 'jsonld.ContextUrlError',\n {code: 'loading remote context failed', max: MAX_CONTEXT_URLS});\n }\n\n // find all URLs in the given document\n const urls = new Map();\n _findContextUrls(doc, urls, false, options.base);\n if(urls.size === 0) {\n return;\n }\n\n // queue all unretrieved URLs\n const queue = [...urls.keys()].filter(u => urls.get(u) === false);\n\n // retrieve URLs in queue\n return Promise.all(queue.map(async url => {\n // check for context URL cycle\n if(cycles.has(url)) {\n throw new JsonLdError(\n 'Cyclical @context URLs detected.',\n 'jsonld.ContextUrlError',\n {code: 'recursive context inclusion', url});\n }\n\n const _cycles = new Set(cycles);\n _cycles.add(url);\n let remoteDoc;\n let ctx;\n\n try {\n remoteDoc = await documentLoader(url);\n ctx = remoteDoc.document || null;\n // parse string context as JSON\n if(_isString(ctx)) {\n ctx = JSON.parse(ctx);\n }\n } catch(e) {\n throw new JsonLdError(\n 'Dereferencing a URL did not result in a valid JSON-LD object. ' +\n 'Possible causes are an inaccessible URL perhaps due to ' +\n 'a same-origin policy (ensure the server uses CORS if you are ' +\n 'using client-side JavaScript), too many redirects, a ' +\n 'non-JSON response, or more than one HTTP Link Header was ' +\n 'provided for a remote context.',\n 'jsonld.InvalidUrl',\n {code: 'loading remote context failed', url, cause: e});\n }\n\n // ensure ctx is an object\n if(!_isObject(ctx)) {\n throw new JsonLdError(\n 'Dereferencing a URL did not result in a JSON object. The ' +\n 'response was valid JSON, but it was not a JSON object.',\n 'jsonld.InvalidUrl',\n {code: 'invalid remote context', url});\n }\n\n // use empty context if no @context key is present\n if(!('@context' in ctx)) {\n ctx = {'@context': {}};\n } else {\n ctx = {'@context': ctx['@context']};\n }\n\n // append @context URL to context if given\n if(remoteDoc.contextUrl) {\n if(!_isArray(ctx['@context'])) {\n ctx['@context'] = [ctx['@context']];\n }\n ctx['@context'].push(remoteDoc.contextUrl);\n }\n\n // recurse\n await retrieve(ctx, _cycles, documentLoader);\n\n // store retrieved context w/replaced @context URLs\n urls.set(url, ctx['@context']);\n\n // replace all @context URLs in the document\n _findContextUrls(doc, urls, true, options.base);\n }));\n }\n}\n\n/**\n * Finds all @context URLs in the given JSON-LD input.\n *\n * @param input the JSON-LD input.\n * @param urls a map of URLs (url => false/@contexts).\n * @param replace true to replace the URLs in the given input with the\n * @contexts from the urls map, false not to.\n * @param base the base IRI to use to resolve relative IRIs.\n *\n * @return true if new URLs to retrieve were found, false if not.\n */\nfunction _findContextUrls(input, urls, replace, base) {\n if(_isArray(input)) {\n for(const element of input) {\n _findContextUrls(element, urls, replace, base);\n }\n return;\n }\n\n if(!_isObject(input)) {\n // no @context URLs can be found in non-object input\n return;\n }\n\n // input is an object\n for(const key in input) {\n if(key !== '@context') {\n _findContextUrls(input[key], urls, replace, base);\n continue;\n }\n\n // get @context\n const ctx = input[key];\n\n if(_isArray(ctx)) {\n // array @context\n let length = ctx.length;\n for(let i = 0; i < length; ++i) {\n const _ctx = ctx[i];\n if(_isString(_ctx)) {\n const prepended = prependBase(base, _ctx);\n const resolved = urls.get(prepended);\n // replace w/@context if requested\n if(replace) {\n if(_isArray(resolved)) {\n // add flattened context\n Array.prototype.splice.apply(ctx, [i, 1].concat(resolved));\n i += resolved.length - 1;\n length = ctx.length;\n } else if(resolved !== false) {\n ctx[i] = resolved;\n }\n } else if(resolved === undefined) {\n // @context URL found\n urls.set(prepended, false);\n }\n } else {\n // look for scoped context\n for(const key in _ctx) {\n if(_isObject(_ctx[key])) {\n _findContextUrls(_ctx[key], urls, replace, base);\n }\n }\n }\n }\n } else if(_isString(ctx)) {\n // string @context\n const prepended = prependBase(base, ctx);\n const resolved = urls.get(prepended);\n // replace w/@context if requested\n if(replace) {\n if(resolved !== false) {\n input[key] = resolved;\n }\n } else if(resolved === undefined) {\n // @context URL found\n urls.set(prepended, false);\n }\n } else {\n // look for scoped context\n for(const key in ctx) {\n if(_isObject(ctx[key])) {\n _findContextUrls(ctx[key], urls, replace, base);\n }\n }\n }\n }\n}\n\nfunction _deepCompare(x1, x2) {\n // compare `null` or primitive types directly\n if((!(x1 && typeof x1 === 'object')) ||\n (!(x2 && typeof x2 === 'object'))) {\n return x1 === x2;\n }\n // x1 and x2 are objects (also potentially arrays)\n const x1Array = Array.isArray(x1);\n if(x1Array !== Array.isArray(x2)) {\n return false;\n }\n if(x1Array) {\n if(x1.length !== x2.length) {\n return false;\n }\n for(let i = 0; i < x1.length; ++i) {\n if(!_deepCompare(x1[i], x2[i])) {\n return false;\n }\n }\n return true;\n }\n // x1 and x2 are non-array objects\n const k1s = Object.keys(x1);\n const k2s = Object.keys(x2);\n if(k1s.length !== k2s.length) {\n return false;\n }\n for(const k1 in x1) {\n let v1 = x1[k1];\n let v2 = x2[k1];\n // special case: `@container` can be in any order\n if(k1 === '@container') {\n if(Array.isArray(v1) && Array.isArray(v2)) {\n v1 = v1.slice().sort();\n v2 = v2.slice().sort();\n }\n }\n if(!_deepCompare(v1, v2)) {\n return false;\n }\n }\n return true;\n}\n","/**\n * A JavaScript implementation of the JSON-LD API.\n *\n * @author Dave Longley\n *\n * @license BSD 3-Clause License\n * Copyright (c) 2011-2017 Digital Bazaar, Inc.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * Neither the name of the Digital Bazaar, Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n * IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\nconst canonize = require('rdf-canonize');\nconst util = require('./util');\nconst IdentifierIssuer = util.IdentifierIssuer;\nconst JsonLdError = require('./JsonLdError');\nconst NQuads = require('./NQuads');\nconst Rdfa = require('./Rdfa');\n\nconst {expand: _expand} = require('./expand');\nconst {flatten: _flatten} = require('./flatten');\nconst {fromRDF: _fromRDF} = require('./fromRdf');\nconst {toRDF: _toRDF} = require('./toRdf');\n\nconst {\n frameMergedOrDefault: _frameMergedOrDefault\n} = require('./frame');\n\nconst {\n isArray: _isArray,\n isObject: _isObject,\n isString: _isString\n} = require('./types');\n\nconst {\n isSubjectReference: _isSubjectReference,\n} = require('./graphTypes');\n\nconst {\n getInitialContext: _getInitialContext,\n process: _processContext,\n getAllContexts: _getAllContexts\n} = require('./context');\n\nconst {\n compact: _compact,\n compactIri: _compactIri,\n removePreserve: _removePreserve\n} = require('./compact');\n\nconst {\n createNodeMap: _createNodeMap,\n createMergedNodeMap: _createMergedNodeMap,\n mergeNodeMaps: _mergeNodeMaps\n} = require('./nodeMap');\n\n// determine if in-browser or using node.js\nconst _nodejs = (\n typeof process !== 'undefined' && process.versions && process.versions.node);\nconst _browser = !_nodejs &&\n (typeof window !== 'undefined' || typeof self !== 'undefined');\n\n/* eslint-disable indent */\n// attaches jsonld API to the given object\nconst wrapper = function(jsonld) {\n\n/* Core API */\n\n/**\n * Performs JSON-LD compaction.\n *\n * @param input the JSON-LD input to compact.\n * @param ctx the context to compact with.\n * @param [options] options to use:\n * [base] the base IRI to use.\n * [compactArrays] true to compact arrays to single values when\n * appropriate, false not to (default: true).\n * [compactToRelative] true to compact IRIs to be relative to document\n * base, false to keep absolute (default: true)\n * [graph] true to always output a top-level graph (default: false).\n * [expandContext] a context to expand with.\n * [skipExpansion] true to assume the input is expanded and skip\n * expansion, false not to, defaults to false.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * [expansionMap(info)] a function that can be used to custom map\n * unmappable values (or to throw an error when they are detected);\n * if this function returns `undefined` then the default behavior\n * will be used.\n * [framing] true if compaction is occuring during a framing operation.\n * [compactionMap(info)] a function that can be used to custom map\n * unmappable values (or to throw an error when they are detected);\n * if this function returns `undefined` then the default behavior\n * will be used.\n * @param [callback(err, compacted)] called once the operation completes.\n *\n * @return a Promise that resolves to the compacted output.\n */\njsonld.compact = util.callbackify(async function(input, ctx, options) {\n if(arguments.length < 2) {\n throw new TypeError('Could not compact, too few arguments.');\n }\n\n if(ctx === null) {\n throw new JsonLdError(\n 'The compaction context must not be null.',\n 'jsonld.CompactError', {code: 'invalid local context'});\n }\n\n // nothing to compact\n if(input === null) {\n return null;\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : '',\n compactArrays: true,\n compactToRelative: true,\n graph: false,\n skipExpansion: false,\n link: false,\n issuer: new IdentifierIssuer('_:b')\n });\n if(options.link) {\n // force skip expansion when linking, \"link\" is not part of the public\n // API, it should only be called from framing\n options.skipExpansion = true;\n }\n if(!options.compactToRelative) {\n delete options.base;\n }\n\n // expand input\n let expanded;\n if(options.skipExpansion) {\n expanded = input;\n } else {\n expanded = await jsonld.expand(input, options);\n }\n\n // process context\n const activeCtx = await jsonld.processContext(\n _getInitialContext(options), ctx, options);\n\n // do compaction\n let compacted = _compact({\n activeCtx,\n element: expanded,\n options,\n compactionMap: options.compactionMap\n });\n\n // perform clean up\n if(options.compactArrays && !options.graph && _isArray(compacted)) {\n if(compacted.length === 1) {\n // simplify to a single item\n compacted = compacted[0];\n } else if(compacted.length === 0) {\n // simplify to an empty object\n compacted = {};\n }\n } else if(options.graph && _isObject(compacted)) {\n // always use array if graph option is on\n compacted = [compacted];\n }\n\n // follow @context key\n if(_isObject(ctx) && '@context' in ctx) {\n ctx = ctx['@context'];\n }\n\n // build output context\n ctx = util.clone(ctx);\n if(!_isArray(ctx)) {\n ctx = [ctx];\n }\n // remove empty contexts\n const tmp = ctx;\n ctx = [];\n for(let i = 0; i < tmp.length; ++i) {\n if(!_isObject(tmp[i]) || Object.keys(tmp[i]).length > 0) {\n ctx.push(tmp[i]);\n }\n }\n\n // remove array if only one context\n const hasContext = (ctx.length > 0);\n if(ctx.length === 1) {\n ctx = ctx[0];\n }\n\n // add context and/or @graph\n if(_isArray(compacted)) {\n // use '@graph' keyword\n const graphAlias = _compactIri({\n activeCtx, iri: '@graph', relativeTo: {vocab: true}\n });\n const graph = compacted;\n compacted = {};\n if(hasContext) {\n compacted['@context'] = ctx;\n }\n compacted[graphAlias] = graph;\n } else if(_isObject(compacted) && hasContext) {\n // reorder keys so @context is first\n const graph = compacted;\n compacted = {'@context': ctx};\n for(const key in graph) {\n compacted[key] = graph[key];\n }\n }\n\n if(options.framing) {\n // get graph alias\n const graph = _compactIri({\n activeCtx, iri: '@graph', relativeTo: {vocab: true}\n });\n // remove @preserve from results\n options.link = {};\n compacted[graph] = _removePreserve(activeCtx, compacted[graph], options);\n }\n\n return compacted;\n});\n\n/**\n * Performs JSON-LD expansion.\n *\n * @param input the JSON-LD input to expand.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [keepFreeFloatingNodes] true to keep free-floating nodes,\n * false not to, defaults to false.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * [expansionMap(info)] a function that can be used to custom map\n * unmappable values (or to throw an error when they are detected);\n * if this function returns `undefined` then the default behavior\n * will be used.\n * @param [callback(err, expanded)] called once the operation completes.\n *\n * @return a Promise that resolves to the expanded output.\n */\njsonld.expand = util.callbackify(async function(input, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not expand, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n keepFreeFloatingNodes: false\n });\n if(options.expansionMap === false) {\n options.expansionMap = undefined;\n }\n\n // build set of objects that may have @contexts to resolve\n const toResolve = {};\n\n // build set of contexts to process prior to expansion\n const contextsToProcess = [];\n\n // if an `expandContext` has been given ensure it gets resolved\n if('expandContext' in options) {\n const expandContext = util.clone(options.expandContext);\n if(_isObject(expandContext) && '@context' in expandContext) {\n toResolve.expandContext = expandContext;\n } else {\n toResolve.expandContext = {'@context': expandContext};\n }\n contextsToProcess.push(toResolve.expandContext);\n }\n\n // if input is a string, attempt to dereference remote document\n let defaultBase;\n if(!_isString(input)) {\n // input is not a URL, do not need to retrieve it first\n toResolve.input = util.clone(input);\n } else {\n // load remote doc\n const remoteDoc = await jsonld.get(input, options);\n defaultBase = remoteDoc.documentUrl;\n toResolve.input = remoteDoc.document;\n if(remoteDoc.contextUrl) {\n // context included in HTTP link header and must be resolved\n toResolve.remoteContext = {'@context': remoteDoc.contextUrl};\n contextsToProcess.push(toResolve.remoteContext);\n }\n }\n\n // set default base\n if(!('base' in options)) {\n options.base = defaultBase || '';\n }\n\n // get all contexts in `toResolve`\n await _getAllContexts(toResolve, options);\n\n // process any additional contexts\n let activeCtx = _getInitialContext(options);\n contextsToProcess.forEach(localCtx => {\n activeCtx = _processContext({activeCtx, localCtx, options});\n });\n\n // expand resolved input\n let expanded = _expand({\n activeCtx,\n element: toResolve.input,\n options,\n expansionMap: options.expansionMap\n });\n\n // optimize away @graph with no other properties\n if(_isObject(expanded) && ('@graph' in expanded) &&\n Object.keys(expanded).length === 1) {\n expanded = expanded['@graph'];\n } else if(expanded === null) {\n expanded = [];\n }\n\n // normalize to an array\n if(!_isArray(expanded)) {\n expanded = [expanded];\n }\n\n return expanded;\n});\n\n/**\n * Performs JSON-LD flattening.\n *\n * @param input the JSON-LD to flatten.\n * @param ctx the context to use to compact the flattened output, or null.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, flattened)] called once the operation completes.\n *\n * @return a Promise that resolves to the flattened output.\n */\njsonld.flatten = util.callbackify(async function(input, ctx, options) {\n if(arguments.length < 1) {\n return new TypeError('Could not flatten, too few arguments.');\n }\n\n if(typeof ctx === 'function') {\n ctx = null;\n } else {\n ctx = ctx || null;\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : ''\n });\n\n // expand input\n const expanded = await jsonld.expand(input, options);\n\n // do flattening\n const flattened = _flatten(expanded);\n\n if(ctx === null) {\n // no compaction required\n return flattened;\n }\n\n // compact result (force @graph option to true, skip expansion)\n options.graph = true;\n options.skipExpansion = true;\n const compacted = await jsonld.compact(flattened, ctx, options);\n\n return compacted;\n});\n\n/**\n * Performs JSON-LD framing.\n *\n * @param input the JSON-LD input to frame.\n * @param frame the JSON-LD frame to use.\n * @param [options] the framing options.\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [embed] default @embed flag: '@last', '@always', '@never', '@link'\n * (default: '@last').\n * [explicit] default @explicit flag (default: false).\n * [requireAll] default @requireAll flag (default: true).\n * [omitDefault] default @omitDefault flag (default: false).\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, framed)] called once the operation completes.\n *\n * @return a Promise that resolves to the framed output.\n */\njsonld.frame = util.callbackify(async function(input, frame, options) {\n if(arguments.length < 2) {\n throw new TypeError('Could not frame, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : '',\n embed: '@last',\n explicit: false,\n requireAll: true,\n omitDefault: false,\n pruneBlankNodeIdentifiers: true,\n bnodesToClear: []\n });\n\n // if frame is a string, attempt to dereference remote document\n if(_isString(frame)) {\n // load remote doc\n const remoteDoc = await jsonld.get(frame, options);\n frame = remoteDoc.document;\n\n if(remoteDoc.contextUrl) {\n // inject link header @context into frame\n let ctx = frame['@context'];\n if(!ctx) {\n ctx = remoteDoc.contextUrl;\n } else if(_isArray(ctx)) {\n ctx.push(remoteDoc.contextUrl);\n } else {\n ctx = [ctx, remoteDoc.contextUrl];\n }\n frame['@context'] = ctx;\n }\n }\n\n const frameContext = frame ? frame['@context'] || {} : {};\n\n // expand input\n const expanded = await jsonld.expand(input, options);\n\n // expand frame\n const opts = util.clone(options);\n opts.isFrame = true;\n opts.keepFreeFloatingNodes = true;\n const expandedFrame = await jsonld.expand(frame, opts);\n\n // if the unexpanded frame includes a key expanding to @graph, frame the\n // default graph, otherwise, the merged graph\n let framed;\n // FIXME should look for aliases of @graph\n opts.merged = !('@graph' in frame);\n // do framing\n framed = _frameMergedOrDefault(expanded, expandedFrame, opts);\n\n // compact result (force @graph option to true, skip expansion,\n // check for linked embeds)\n opts.graph = true;\n opts.skipExpansion = true;\n opts.link = {};\n opts.framing = true;\n const compacted = await jsonld.compact(framed, frameContext, opts);\n\n return compacted;\n});\n\n/**\n * **Experimental**\n *\n * Links a JSON-LD document's nodes in memory.\n *\n * @param input the JSON-LD document to link.\n * @param [ctx] the JSON-LD context to apply.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, linked)] called once the operation completes.\n *\n * @return a Promise that resolves to the linked output.\n */\njsonld.link = util.callbackify(async function(input, ctx, options) {\n // API matches running frame with a wildcard frame and embed: '@link'\n // get arguments\n const frame = {};\n if(ctx) {\n frame['@context'] = ctx;\n }\n frame['@embed'] = '@link';\n return jsonld.frame(input, frame, options);\n});\n\n/**\n * Performs RDF dataset normalization on the given input. The input is JSON-LD\n * unless the 'inputFormat' option is used. The output is an RDF dataset\n * unless the 'format' option is used.\n *\n * @param input the input to normalize as JSON-LD or as a format specified by\n * the 'inputFormat' option.\n * @param [options] the options to use:\n * [algorithm] the normalization algorithm to use, `URDNA2015` or\n * `URGNA2012` (default: `URDNA2015`).\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [skipExpansion] true to assume the input is expanded and skip\n * expansion, false not to, defaults to false.\n * [inputFormat] the format if input is not JSON-LD:\n * 'application/n-quads' for N-Quads.\n * [format] the format if output is a string:\n * 'application/n-quads' for N-Quads.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * [useNative] true to use a native canonize algorithm\n * @param [callback(err, normalized)] called once the operation completes.\n *\n * @return a Promise that resolves to the normalized output.\n */\njsonld.normalize = jsonld.canonize = util.callbackify(async function(\n input, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not canonize, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : '',\n algorithm: 'URDNA2015',\n skipExpansion: false\n });\n if('inputFormat' in options) {\n if(options.inputFormat !== 'application/n-quads' &&\n options.inputFormat !== 'application/nquads') {\n throw new JsonLdError(\n 'Unknown canonicalization input format.',\n 'jsonld.CanonizeError');\n }\n // TODO: `await` for async parsers\n const parsedInput = NQuads.parse(input);\n\n // do canonicalization\n return canonize.canonize(parsedInput, options);\n }\n\n // convert to RDF dataset then do normalization\n const opts = util.clone(options);\n delete opts.format;\n opts.produceGeneralizedRdf = false;\n const dataset = await jsonld.toRDF(input, opts);\n\n // do canonicalization\n return canonize.canonize(dataset, options);\n});\n\n/**\n * Converts an RDF dataset to JSON-LD.\n *\n * @param dataset a serialized string of RDF in a format specified by the\n * format option or an RDF dataset to convert.\n * @param [options] the options to use:\n * [format] the format if dataset param must first be parsed:\n * 'application/n-quads' for N-Quads (default).\n * [rdfParser] a custom RDF-parser to use to parse the dataset.\n * [useRdfType] true to use rdf:type, false to use @type\n * (default: false).\n * [useNativeTypes] true to convert XSD types into native types\n * (boolean, integer, double), false not to (default: false).\n * @param [callback(err, output)] called once the operation completes.\n *\n * @return a Promise that resolves to the JSON-LD document.\n */\njsonld.fromRDF = util.callbackify(async function(dataset, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not convert from RDF, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n format: _isString(dataset) ? 'application/n-quads' : undefined\n });\n\n let {format, rdfParser} = options;\n\n // handle special format\n if(format) {\n // check supported formats\n rdfParser = rdfParser || _rdfParsers[format];\n if(!rdfParser) {\n throw new JsonLdError(\n 'Unknown input format.',\n 'jsonld.UnknownFormat', {format});\n }\n } else {\n // no-op parser, assume dataset already parsed\n rdfParser = () => dataset;\n }\n\n // TODO: call `normalizeAsyncFn` on parser fn\n\n // rdfParser can be callback, promise-based, or synchronous\n let parsedDataset;\n if(rdfParser.length > 1) {\n // convert callback-based rdf parser to promise-based\n parsedDataset = new Promise((resolve, reject) => {\n rdfParser(dataset, (err, dataset) => {\n if(err) {\n reject(err);\n } else {\n resolve(dataset);\n }\n });\n });\n } else {\n parsedDataset = Promise.resolve(rdfParser(dataset));\n }\n\n parsedDataset = await parsedDataset;\n\n // back-compat with old parsers that produced legacy dataset format\n if(!Array.isArray(parsedDataset)) {\n parsedDataset = NQuads.legacyDatasetToQuads(parsedDataset);\n }\n\n return _fromRDF(parsedDataset, options);\n});\n\n/**\n * Outputs the RDF dataset found in the given JSON-LD object.\n *\n * @param input the JSON-LD input.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [skipExpansion] true to assume the input is expanded and skip\n * expansion, false not to, defaults to false.\n * [format] the format to use to output a string:\n * 'application/n-quads' for N-Quads.\n * [produceGeneralizedRdf] true to output generalized RDF, false\n * to produce only standard RDF (default: false).\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, dataset)] called once the operation completes.\n *\n * @return a Promise that resolves to the RDF dataset.\n */\njsonld.toRDF = util.callbackify(async function(input, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not convert to RDF, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : '',\n skipExpansion: false\n });\n\n // TODO: support toRDF custom map?\n let expanded;\n if(options.skipExpansion) {\n expanded = input;\n } else {\n // expand input\n expanded = await jsonld.expand(input, options);\n }\n\n // output RDF dataset\n const dataset = _toRDF(expanded, options);\n if(options.format) {\n if(options.format === 'application/n-quads' ||\n options.format === 'application/nquads') {\n return await NQuads.serialize(dataset);\n }\n throw new JsonLdError(\n 'Unknown output format.',\n 'jsonld.UnknownFormat', {format: options.format});\n }\n\n return dataset;\n});\n\n/**\n * **Experimental**\n *\n * Recursively flattens the nodes in the given JSON-LD input into a merged\n * map of node ID => node. All graphs will be merged into the default graph.\n *\n * @param input the JSON-LD input.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [issuer] a jsonld.IdentifierIssuer to use to label blank nodes.\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, nodeMap)] called once the operation completes.\n *\n * @return a Promise that resolves to the merged node map.\n */\njsonld.createNodeMap = util.callbackify(async function(input, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not create node map, too few arguments.');\n }\n\n // set default options\n options = _setDefaults(options, {\n base: _isString(input) ? input : ''\n });\n\n // expand input\n const expanded = await jsonld.expand(input, options);\n\n return _createMergedNodeMap(expanded, options);\n});\n\n/**\n * **Experimental**\n *\n * Merges two or more JSON-LD documents into a single flattened document.\n *\n * @param docs the JSON-LD documents to merge together.\n * @param ctx the context to use to compact the merged result, or null.\n * @param [options] the options to use:\n * [base] the base IRI to use.\n * [expandContext] a context to expand with.\n * [issuer] a jsonld.IdentifierIssuer to use to label blank nodes.\n * [mergeNodes] true to merge properties for nodes with the same ID,\n * false to ignore new properties for nodes with the same ID once\n * the ID has been defined; note that this may not prevent merging\n * new properties where a node is in the `object` position\n * (default: true).\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, merged)] called once the operation completes.\n *\n * @return a Promise that resolves to the merged output.\n */\njsonld.merge = util.callbackify(async function(docs, ctx, options) {\n if(arguments.length < 1) {\n throw new TypeError('Could not merge, too few arguments.');\n }\n if(!_isArray(docs)) {\n throw new TypeError('Could not merge, \"docs\" must be an array.');\n }\n\n if(typeof ctx === 'function') {\n ctx = null;\n } else {\n ctx = ctx || null;\n }\n\n // set default options\n options = _setDefaults(options, {});\n\n // expand all documents\n const expanded = await Promise.all(docs.map(doc => {\n const opts = Object.assign({}, options);\n return jsonld.expand(doc, opts);\n }));\n\n let mergeNodes = true;\n if('mergeNodes' in options) {\n mergeNodes = options.mergeNodes;\n }\n\n const issuer = options.issuer || new IdentifierIssuer('_:b');\n const graphs = {'@default': {}};\n\n for(let i = 0; i < expanded.length; ++i) {\n // uniquely relabel blank nodes\n const doc = util.relabelBlankNodes(expanded[i], {\n issuer: new IdentifierIssuer('_:b' + i + '-')\n });\n\n // add nodes to the shared node map graphs if merging nodes, to a\n // separate graph set if not\n const _graphs = (mergeNodes || i === 0) ? graphs : {'@default': {}};\n _createNodeMap(doc, _graphs, '@default', issuer);\n\n if(_graphs !== graphs) {\n // merge document graphs but don't merge existing nodes\n for(const graphName in _graphs) {\n const _nodeMap = _graphs[graphName];\n if(!(graphName in graphs)) {\n graphs[graphName] = _nodeMap;\n continue;\n }\n const nodeMap = graphs[graphName];\n for(const key in _nodeMap) {\n if(!(key in nodeMap)) {\n nodeMap[key] = _nodeMap[key];\n }\n }\n }\n }\n }\n\n // add all non-default graphs to default graph\n const defaultGraph = _mergeNodeMaps(graphs);\n\n // produce flattened output\n const flattened = [];\n const keys = Object.keys(defaultGraph).sort();\n for(let ki = 0; ki < keys.length; ++ki) {\n const node = defaultGraph[keys[ki]];\n // only add full subjects to top-level\n if(!_isSubjectReference(node)) {\n flattened.push(node);\n }\n }\n\n if(ctx === null) {\n return flattened;\n }\n\n // compact result (force @graph option to true, skip expansion)\n options.graph = true;\n options.skipExpansion = true;\n const compacted = await jsonld.compact(flattened, ctx, options);\n\n return compacted;\n});\n\n/**\n * The default document loader for external documents. If the environment\n * is node.js, a callback-continuation-style document loader is used; otherwise,\n * a promises-style document loader is used.\n *\n * @param url the URL to load.\n * @param callback(err, remoteDoc) called once the operation completes,\n * if using a non-promises API.\n *\n * @return a promise, if using a promises API.\n */\nObject.defineProperty(jsonld, 'documentLoader', {\n get: () => jsonld._documentLoader,\n set: v => jsonld._documentLoader = util.normalizeDocumentLoader(v)\n});\n// default document loader not implemented\njsonld.documentLoader = async url => {\n throw new JsonLdError(\n 'Could not retrieve a JSON-LD document from the URL. URL ' +\n 'dereferencing not implemented.', 'jsonld.LoadDocumentError',\n {code: 'loading document failed', url});\n};\n\n/**\n * Deprecated default document loader. Do not use or override.\n */\njsonld.loadDocument = util.callbackify(async function() {\n return jsonld.documentLoader.apply(null, arguments);\n});\n\n/**\n * Gets a remote JSON-LD document using the default document loader or\n * one given in the passed options.\n *\n * @param url the URL to fetch.\n * @param [options] the options to use:\n * [documentLoader] the document loader to use.\n * @param [callback(err, remoteDoc)] called once the operation completes.\n *\n * @return a Promise that resolves to the retrieved remote document.\n */\njsonld.get = util.callbackify(async function(url, options) {\n let load;\n if(typeof options.documentLoader === 'function') {\n load = util.normalizeDocumentLoader(options.documentLoader);\n } else {\n load = jsonld.documentLoader;\n }\n\n const remoteDoc = await load(url);\n\n // TODO: can this be moved into `normalizeDocumentLoader`?\n try {\n if(!remoteDoc.document) {\n throw new JsonLdError(\n 'No remote document found at the given URL.',\n 'jsonld.NullRemoteDocument');\n }\n if(_isString(remoteDoc.document)) {\n remoteDoc.document = JSON.parse(remoteDoc.document);\n }\n } catch(e) {\n throw new JsonLdError(\n 'Could not retrieve a JSON-LD document from the URL.',\n 'jsonld.LoadDocumentError', {\n code: 'loading document failed',\n cause: e,\n remoteDoc\n });\n }\n\n return remoteDoc;\n});\n\n/**\n * Processes a local context, resolving any URLs as necessary, and returns a\n * new active context in its callback.\n *\n * @param activeCtx the current active context.\n * @param localCtx the local context to process.\n * @param [options] the options to use:\n * [documentLoader(url, callback(err, remoteDoc))] the document loader.\n * @param [callback(err, activeCtx)] called once the operation completes.\n *\n * @return a Promise that resolves to the new active context.\n */\njsonld.processContext = util.callbackify(async function(\n activeCtx, localCtx, options) {\n // set default options\n options = _setDefaults(options, {\n base: ''\n });\n\n // return initial context early for null context\n if(localCtx === null) {\n return _getInitialContext(options);\n }\n\n // get URLs in localCtx\n localCtx = util.clone(localCtx);\n if(!(_isObject(localCtx) && '@context' in localCtx)) {\n localCtx = {'@context': localCtx};\n }\n const ctx = await _getAllContexts(localCtx, options);\n\n return _processContext({activeCtx, localCtx: ctx, options});\n});\n\n// backwards compatibility\njsonld.getContextValue = require('./context').getContextValue;\n\n/**\n * Document loaders.\n */\njsonld.documentLoaders = {};\njsonld.documentLoaders.node = require('./documentLoaders/node');\njsonld.documentLoaders.xhr = require('./documentLoaders/xhr');\n\n/**\n * Assigns the default document loader for external document URLs to a built-in\n * default. Supported types currently include: 'xhr' and 'node'.\n *\n * @param type the type to set.\n * @param [params] the parameters required to use the document loader.\n */\njsonld.useDocumentLoader = function(type) {\n if(!(type in jsonld.documentLoaders)) {\n throw new JsonLdError(\n 'Unknown document loader type: \"' + type + '\"',\n 'jsonld.UnknownDocumentLoader',\n {type});\n }\n\n // set document loader\n jsonld.documentLoader = jsonld.documentLoaders[type].apply(\n jsonld, Array.prototype.slice.call(arguments, 1));\n};\n\n/** Registered RDF dataset parsers hashed by content-type. */\nconst _rdfParsers = {};\n\n/**\n * Registers an RDF dataset parser by content-type, for use with\n * jsonld.fromRDF. An RDF dataset parser will always be given two parameters,\n * a string of input and a callback. An RDF dataset parser can be synchronous\n * or asynchronous.\n *\n * If the parser function returns undefined or null then it will be assumed to\n * be asynchronous w/a continuation-passing style and the callback parameter\n * given to the parser MUST be invoked.\n *\n * If it returns a Promise, then it will be assumed to be asynchronous, but the\n * callback parameter MUST NOT be invoked. It should instead be ignored.\n *\n * If it returns an RDF dataset, it will be assumed to be synchronous and the\n * callback parameter MUST NOT be invoked. It should instead be ignored.\n *\n * @param contentType the content-type for the parser.\n * @param parser(input, callback(err, dataset)) the parser function (takes a\n * string as a parameter and either returns null/undefined and uses\n * the given callback, returns a Promise, or returns an RDF dataset).\n */\njsonld.registerRDFParser = function(contentType, parser) {\n _rdfParsers[contentType] = parser;\n};\n\n/**\n * Unregisters an RDF dataset parser by content-type.\n *\n * @param contentType the content-type for the parser.\n */\njsonld.unregisterRDFParser = function(contentType) {\n delete _rdfParsers[contentType];\n};\n\n// register the N-Quads RDF parser\njsonld.registerRDFParser('application/n-quads', NQuads.parse);\njsonld.registerRDFParser('application/nquads', NQuads.parse);\n\n// register the RDFa API RDF parser\njsonld.registerRDFParser('rdfa-api', Rdfa.parse);\n\n/* URL API */\njsonld.url = require('./url');\n\n/* Utility API */\njsonld.util = util;\n// backwards compatibility\nObject.assign(jsonld, util);\n\n// reexpose API as jsonld.promises for backwards compatability\njsonld.promises = jsonld;\n\n// backwards compatibility\njsonld.RequestQueue = require('./RequestQueue');\n\n/* WebIDL API */\njsonld.JsonLdProcessor = require('./JsonLdProcessor')(jsonld);\n\n// setup browser global JsonLdProcessor\nif(_browser && typeof global.JsonLdProcessor === 'undefined') {\n Object.defineProperty(global, 'JsonLdProcessor', {\n writable: true,\n enumerable: false,\n configurable: true,\n value: jsonld.JsonLdProcessor\n });\n}\n\n// set platform-specific defaults/APIs\nif(_nodejs) {\n // use node document loader by default\n jsonld.useDocumentLoader('node');\n} else if(typeof XMLHttpRequest !== 'undefined') {\n // use xhr document loader by default\n jsonld.useDocumentLoader('xhr');\n}\n\nfunction _setDefaults(options, {\n documentLoader = jsonld.documentLoader,\n ...defaults\n}) {\n return Object.assign({}, {documentLoader}, defaults, options);\n}\n\n// end of jsonld API `wrapper` factory\nreturn jsonld;\n};\n\n// external APIs:\n\n// used to generate a new jsonld API instance\nconst factory = function() {\n return wrapper(function() {\n return factory();\n });\n};\n\n// wrap the main jsonld API instance\nwrapper(factory);\n// export API\nmodule.exports = factory;\n","/*\n * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.\n */\n'use strict';\n\nconst JsonLdError = require('./JsonLdError');\n\nconst {\n isArray: _isArray,\n isObject: _isObject,\n isString: _isString,\n isUndefined: _isUndefined\n} = require('./types');\n\nconst {\n isList: _isList,\n isValue: _isValue,\n isGraph: _isGraph,\n isSimpleGraph: _isSimpleGraph,\n isSubjectReference: _isSubjectReference\n} = require('./graphTypes');\n\nconst {\n expandIri: _expandIri,\n getContextValue: _getContextValue,\n isKeyword: _isKeyword,\n process: _processContext\n} = require('./context');\n\nconst {\n removeBase: _removeBase\n} = require('./url');\n\nconst {\n addValue: _addValue,\n asArray: _asArray,\n compareShortestLeast: _compareShortestLeast\n} = require('./util');\n\nconst api = {};\nmodule.exports = api;\n\n/**\n * Recursively compacts an element using the given active context. All values\n * must be in expanded form before this method is called.\n *\n * @param activeCtx the active context to use.\n * @param activeProperty the compacted property associated with the element\n * to compact, null for none.\n * @param element the element to compact.\n * @param options the compaction options.\n * @param compactionMap the compaction map to use.\n *\n * @return the compacted value.\n */\napi.compact = ({\n activeCtx,\n activeProperty = null,\n element,\n options = {},\n compactionMap = () => undefined\n}) => {\n // recursively compact array\n if(_isArray(element)) {\n let rval = [];\n for(let i = 0; i < element.length; ++i) {\n // compact, dropping any null values unless custom mapped\n let compacted = api.compact({\n activeCtx,\n activeProperty,\n element: element[i],\n options,\n compactionMap\n });\n if(compacted === null) {\n // TODO: use `await` to support async\n compacted = compactionMap({\n unmappedValue: element[i],\n activeCtx,\n activeProperty,\n parent: element,\n index: i,\n options\n });\n if(compacted === undefined) {\n continue;\n }\n }\n rval.push(compacted);\n }\n if(options.compactArrays && rval.length === 1) {\n // use single element if no container is specified\n const container = _getContextValue(\n activeCtx, activeProperty, '@container') || [];\n if(container.length === 0) {\n rval = rval[0];\n }\n }\n return rval;\n }\n\n // use any scoped context on activeProperty\n const ctx = _getContextValue(activeCtx, activeProperty, '@context');\n if(!_isUndefined(ctx)) {\n // Note: spec's `from term` var is named `isPropertyTermScopedContext`\n activeCtx = _processContext({\n activeCtx,\n localCtx: ctx,\n isPropertyTermScopedContext: true,\n options\n });\n }\n\n // recursively compact object\n if(_isObject(element)) {\n if(options.link && '@id' in element &&\n options.link.hasOwnProperty(element['@id'])) {\n // check for a linked element to reuse\n const linked = options.link[element['@id']];\n for(let i = 0; i < linked.length; ++i) {\n if(linked[i].expanded === element) {\n return linked[i].compacted;\n }\n }\n }\n\n // do value compaction on @values and subject references\n if(_isValue(element) || _isSubjectReference(element)) {\n const rval =\n api.compactValue({activeCtx, activeProperty, value: element, options});\n if(options.link && _isSubjectReference(element)) {\n // store linked element\n if(!(options.link.hasOwnProperty(element['@id']))) {\n options.link[element['@id']] = [];\n }\n options.link[element['@id']].push({expanded: element, compacted: rval});\n }\n return rval;\n }\n\n // FIXME: avoid misuse of active property as an expanded property?\n const insideReverse = (activeProperty === '@reverse');\n\n const rval = {};\n\n // revert type scoped context\n activeCtx = activeCtx.revertTypeScopedContext();\n\n if(options.link && '@id' in element) {\n // store linked element\n if(!options.link.hasOwnProperty(element['@id'])) {\n options.link[element['@id']] = [];\n }\n options.link[element['@id']].push({expanded: element, compacted: rval});\n }\n\n // apply any context defined on an alias of @type\n // if key is @type and any compacted value is a term having a local\n // context, overlay that context\n let types = element['@type'] || [];\n if(types.length > 1) {\n types = Array.from(types).sort();\n }\n // find all type-scoped contexts based on current context, prior to\n // updating it\n const typeContext = activeCtx;\n for(const type of types) {\n const compactedType = api.compactIri(\n {activeCtx: typeContext, iri: type, relativeTo: {vocab: true}});\n\n // Use any type-scoped context defined on this value\n const ctx = _getContextValue(typeContext, compactedType, '@context');\n if(!_isUndefined(ctx)) {\n activeCtx = _processContext({\n activeCtx,\n localCtx: ctx,\n options,\n isTypeScopedContext: true\n });\n }\n }\n\n // process element keys in order\n const keys = Object.keys(element).sort();\n for(const expandedProperty of keys) {\n const expandedValue = element[expandedProperty];\n\n // compact @id and @type(s)\n if(expandedProperty === '@id' || expandedProperty === '@type') {\n // if using a type-scoped context, resolve type values against previous\n // context\n const isType = expandedProperty === '@type';\n const valueContext = isType ?\n (activeCtx.previousContext || activeCtx) : activeCtx;\n let compactedValue = _asArray(expandedValue).map(\n expandedIri => api.compactIri({\n activeCtx: valueContext,\n iri: expandedIri,\n relativeTo: {vocab: isType}\n }));\n if(compactedValue.length === 1) {\n compactedValue = compactedValue[0];\n }\n\n // use keyword alias and add value\n const alias = api.compactIri(\n {activeCtx, iri: expandedProperty, relativeTo: {vocab: true}});\n const isArray = _isArray(compactedValue) && expandedValue.length === 0;\n _addValue(rval, alias, compactedValue, {propertyIsArray: isArray});\n continue;\n }\n\n // handle @reverse\n if(expandedProperty === '@reverse') {\n // recursively compact expanded value\n const compactedValue = api.compact({\n activeCtx,\n activeProperty: '@reverse',\n element: expandedValue,\n options,\n compactionMap\n });\n\n // handle double-reversed properties\n for(const compactedProperty in compactedValue) {\n if(activeCtx.mappings.has(compactedProperty) &&\n activeCtx.mappings.get(compactedProperty).reverse) {\n const value = compactedValue[compactedProperty];\n const container = _getContextValue(\n activeCtx, compactedProperty, '@container') || [];\n const useArray = (\n container.includes('@set') || !options.compactArrays);\n _addValue(\n rval, compactedProperty, value, {propertyIsArray: useArray});\n delete compactedValue[compactedProperty];\n }\n }\n\n if(Object.keys(compactedValue).length > 0) {\n // use keyword alias and add value\n const alias = api.compactIri({\n activeCtx,\n iri: expandedProperty,\n relativeTo: {vocab: true}\n });\n _addValue(rval, alias, compactedValue);\n }\n\n continue;\n }\n\n if(expandedProperty === '@preserve') {\n // compact using activeProperty\n const compactedValue = api.compact({\n activeCtx,\n activeProperty,\n element: expandedValue,\n options,\n compactionMap});\n\n if(!(_isArray(compactedValue) && compactedValue.length === 0)) {\n _addValue(rval, expandedProperty, compactedValue);\n }\n continue;\n }\n\n // handle @index property\n if(expandedProperty === '@index') {\n // drop @index if inside an @index container\n const container = _getContextValue(\n activeCtx, activeProperty, '@container') || [];\n if(container.includes('@index')) {\n continue;\n }\n\n // use keyword alias and add value\n const alias = api.compactIri({\n activeCtx,\n iri: expandedProperty,\n relativeTo: {vocab: true}\n });\n _addValue(rval, alias, expandedValue);\n continue;\n }\n\n // skip array processing for keywords that aren't @graph or @list\n if(expandedProperty !== '@graph' && expandedProperty !== '@list' &&\n _isKeyword(expandedProperty)) {\n // use keyword alias and add value as is\n const alias = api.compactIri({\n activeCtx,\n iri: expandedProperty,\n relativeTo: {vocab: true}\n });\n _addValue(rval, alias, expandedValue);\n continue;\n }\n\n // Note: expanded value must be an array due to expansion algorithm.\n if(!_isArray(expandedValue)) {\n throw new JsonLdError(\n 'JSON-LD expansion error; expanded value must be an array.',\n 'jsonld.SyntaxError');\n }\n\n // preserve empty arrays\n if(expandedValue.length === 0) {\n const itemActiveProperty = api.compactIri({\n activeCtx,\n iri: expandedProperty,\n value: expandedValue,\n relativeTo: {vocab: true},\n reverse: insideReverse\n });\n const nestProperty = activeCtx.mappings.has(itemActiveProperty) ?\n activeCtx.mappings.get(itemActiveProperty)['@nest'] : null;\n let nestResult = rval;\n if(nestProperty) {\n _checkNestProperty(activeCtx, nestProperty, options);\n if(!_isObject(rval[nestProperty])) {\n rval[nestProperty] = {};\n }\n nestResult = rval[nestProperty];\n }\n _addValue(\n nestResult, itemActiveProperty, expandedValue, {\n propertyIsArray: true\n });\n }\n\n // recusively process array values\n for(const expandedItem of expandedValue) {\n // compact property and get container type\n const itemActiveProperty = api.compactIri({\n activeCtx,\n iri: expandedProperty,\n value: expandedItem,\n relativeTo: {vocab: true},\n reverse: insideReverse\n });\n\n // if itemActiveProperty is a @nest property, add values to nestResult,\n // otherwise rval\n const nestProperty = activeCtx.mappings.has(itemActiveProperty) ?\n activeCtx.mappings.get(itemActiveProperty)['@nest'] : null;\n let nestResult = rval;\n if(nestProperty) {\n _checkNestProperty(activeCtx, nestProperty, options);\n if(!_isObject(rval[nestProperty])) {\n rval[nestProperty] = {};\n }\n nestResult = rval[nestProperty];\n }\n\n const container = _getContextValue(\n activeCtx, itemActiveProperty, '@container') || [];\n\n // get simple @graph or @list value if appropriate\n const isGraph = _isGraph(expandedItem);\n const isList = _isList(expandedItem);\n let inner;\n if(isList) {\n inner = expandedItem['@list'];\n } else if(isGraph) {\n inner = expandedItem['@graph'];\n }\n\n // recursively compact expanded item\n let compactedItem = api.compact({\n activeCtx,\n activeProperty: itemActiveProperty,\n element: (isList || isGraph) ? inner : expandedItem,\n options,\n compactionMap\n });\n\n // handle @list\n if(isList) {\n // ensure @list value is an array\n if(!_isArray(compactedItem)) {\n compactedItem = [compactedItem];\n }\n\n if(!container.includes('@list')) {\n // wrap using @list alias\n compactedItem = {\n [api.compactIri({\n activeCtx,\n iri: '@list',\n relativeTo: {vocab: true}\n })]: compactedItem\n };\n\n // include @index from expanded @list, if any\n if('@index' in expandedItem) {\n compactedItem[api.compactIri({\n activeCtx,\n iri: '@index',\n relativeTo: {vocab: true}\n })] = expandedItem['@index'];\n }\n } else if(nestResult.hasOwnProperty(itemActiveProperty)) {\n // can't use @list container for more than 1 list\n throw new JsonLdError(\n 'JSON-LD compact error; property has a \"@list\" @container ' +\n 'rule but there is more than a single @list that matches ' +\n 'the compacted term in the document. Compaction might mix ' +\n 'unwanted items into the list.',\n 'jsonld.SyntaxError', {code: 'compaction to list of lists'});\n }\n }\n\n // Graph object compaction cases\n if(isGraph) {\n if(container.includes('@graph') && (container.includes('@id') ||\n container.includes('@index') && _isSimpleGraph(expandedItem))) {\n // get or create the map object\n let mapObject;\n if(nestResult.hasOwnProperty(itemActiveProperty)) {\n mapObject = nestResult[itemActiveProperty];\n } else {\n nestResult[itemActiveProperty] = mapObject = {};\n }\n\n // index on @id or @index or alias of @none\n const key = (container.includes('@id') ?\n expandedItem['@id'] : expandedItem['@index']) ||\n api.compactIri({activeCtx, iri: '@none', vocab: true});\n // add compactedItem to map, using value of `@id` or a new blank\n // node identifier\n\n _addValue(\n mapObject, key, compactedItem, {\n propertyIsArray:\n (!options.compactArrays || container.includes('@set'))\n });\n } else if(container.includes('@graph') &&\n _isSimpleGraph(expandedItem)) {\n // container includes @graph but not @id or @index and value is a\n // simple graph object add compact value\n _addValue(\n nestResult, itemActiveProperty, compactedItem, {\n propertyIsArray:\n (!options.compactArrays || container.includes('@set'))\n });\n } else {\n // wrap using @graph alias, remove array if only one item and\n // compactArrays not set\n if(_isArray(compactedItem) && compactedItem.length === 1 &&\n options.compactArrays) {\n compactedItem = compactedItem[0];\n }\n compactedItem = {\n [api.compactIri({\n activeCtx,\n iri: '@graph',\n relativeTo: {vocab: true}\n })]: compactedItem\n };\n\n // include @id from expanded graph, if any\n if('@id' in expandedItem) {\n compactedItem[api.compactIri({\n activeCtx,\n iri: '@id',\n relativeTo: {vocab: true}\n })] = expandedItem['@id'];\n }\n\n // include @index from expanded graph, if any\n if('@index' in expandedItem) {\n compactedItem[api.compactIri({\n activeCtx,\n iri: '@index',\n relativeTo: {vocab: true}\n })] = expandedItem['@index'];\n }\n _addValue(\n nestResult, itemActiveProperty, compactedItem, {\n propertyIsArray:\n (!options.compactArrays || container.includes('@set'))\n });\n }\n } else if(container.includes('@language') ||\n container.includes('@index') || container.includes('@id') ||\n container.includes('@type')) {\n // handle language and index maps\n // get or create the map object\n let mapObject;\n if(nestResult.hasOwnProperty(itemActiveProperty)) {\n mapObject = nestResult[itemActiveProperty];\n } else {\n nestResult[itemActiveProperty] = mapObject = {};\n }\n\n let key;\n if(container.includes('@language')) {\n // if container is a language map, simplify compacted value to\n // a simple string\n if(_isValue(compactedItem)) {\n compactedItem = compactedItem['@value'];\n }\n key = expandedItem['@language'];\n } else if(container.includes('@index')) {\n key = expandedItem['@index'];\n } else if(container.includes('@id')) {\n const idKey = api.compactIri({activeCtx, iri: '@id', vocab: true});\n key = compactedItem[idKey];\n delete compactedItem[idKey];\n } else if(container.includes('@type')) {\n const typeKey = api.compactIri({\n activeCtx,\n iri: '@type',\n vocab: true\n });\n let types;\n [key, ...types] = _asArray(compactedItem[typeKey] || []);\n switch(types.length) {\n case 0:\n delete compactedItem[typeKey];\n break;\n case 1:\n compactedItem[typeKey] = types[0];\n break;\n default:\n compactedItem[typeKey] = types;\n break;\n }\n }\n\n // if compacting this value which has no key, index on @none\n if(!key) {\n key = api.compactIri({activeCtx, iri: '@none', vocab: true});\n }\n // add compact value to map object using key from expanded value\n // based on the container type\n _addValue(\n mapObject, key, compactedItem, {\n propertyIsArray: container.includes('@set')\n });\n } else {\n // use an array if: compactArrays flag is false,\n // @container is @set or @list , value is an empty\n // array, or key is @graph\n const isArray = (!options.compactArrays ||\n container.includes('@set') || container.includes('@list') ||\n (_isArray(compactedItem) && compactedItem.length === 0) ||\n expandedProperty === '@list' || expandedProperty === '@graph');\n\n // add compact value\n _addValue(\n nestResult, itemActiveProperty, compactedItem,\n {propertyIsArray: isArray});\n }\n }\n }\n\n return rval;\n }\n\n // only primitives remain which are already compact\n return element;\n};\n\n/**\n * Compacts an IRI or keyword into a term or prefix if it can be. If the\n * IRI has an associated value it may be passed.\n *\n * @param activeCtx the active context to use.\n * @param iri the IRI to compact.\n * @param value the value to check or null.\n * @param relativeTo options for how to compact IRIs:\n * vocab: true to split after @vocab, false not to.\n * @param reverse true if a reverse property is being compacted, false if not.\n *\n * @return the compacted term, prefix, keyword alias, or the original IRI.\n */\napi.compactIri = ({\n activeCtx,\n iri,\n value = null,\n relativeTo = {vocab: false},\n reverse = false\n}) => {\n // can't compact null\n if(iri === null) {\n return iri;\n }\n\n // if context is from a property term scoped context composed with a\n // type-scoped context, then use the previous context instead\n if(activeCtx.isPropertyTermScoped && activeCtx.previousContext) {\n activeCtx = activeCtx.previousContext;\n }\n\n const inverseCtx = activeCtx.getInverse();\n\n // if term is a keyword, it may be compacted to a simple alias\n if(_isKeyword(iri) &&\n iri in inverseCtx &&\n '@none' in inverseCtx[iri] &&\n '@type' in inverseCtx[iri]['@none'] &&\n '@none' in inverseCtx[iri]['@none']['@type']) {\n return inverseCtx[iri]['@none']['@type']['@none'];\n }\n\n // use inverse context to pick a term if iri is relative to vocab\n if(relativeTo.vocab && iri in inverseCtx) {\n const defaultLanguage = activeCtx['@language'] || '@none';\n\n // prefer @index if available in value\n const containers = [];\n if(_isObject(value) && '@index' in value && !('@graph' in value)) {\n containers.push('@index', '@index@set');\n }\n\n // if value is a preserve object, use its value\n if(_isObject(value) && '@preserve' in value) {\n value = value['@preserve'][0];\n }\n\n // prefer most specific container including @graph, prefering @set\n // variations\n if(_isGraph(value)) {\n // favor indexmap if the graph is indexed\n if('@index' in value) {\n containers.push(\n '@graph@index', '@graph@index@set', '@index', '@index@set');\n }\n // favor idmap if the graph is has an @id\n if('@id' in value) {\n containers.push(\n '@graph@id', '@graph@id@set');\n }\n containers.push('@graph', '@graph@set', '@set');\n // allow indexmap if the graph is not indexed\n if(!('@index' in value)) {\n containers.push(\n '@graph@index', '@graph@index@set', '@index', '@index@set');\n }\n // allow idmap if the graph does not have an @id\n if(!('@id' in value)) {\n containers.push('@graph@id', '@graph@id@set');\n }\n } else if(_isObject(value) && !_isValue(value)) {\n containers.push('@id', '@id@set', '@type', '@set@type');\n }\n\n // defaults for term selection based on type/language\n let typeOrLanguage = '@language';\n let typeOrLanguageValue = '@null';\n\n if(reverse) {\n typeOrLanguage = '@type';\n typeOrLanguageValue = '@reverse';\n containers.push('@set');\n } else if(_isList(value)) {\n // choose the most specific term that works for all elements in @list\n // only select @list containers if @index is NOT in value\n if(!('@index' in value)) {\n containers.push('@list');\n }\n const list = value['@list'];\n if(list.length === 0) {\n // any empty list can be matched against any term that uses the\n // @list container regardless of @type or @language\n typeOrLanguage = '@any';\n typeOrLanguageValue = '@none';\n } else {\n let commonLanguage = (list.length === 0) ? defaultLanguage : null;\n let commonType = null;\n for(let i = 0; i < list.length; ++i) {\n const item = list[i];\n let itemLanguage = '@none';\n let itemType = '@none';\n if(_isValue(item)) {\n if('@language' in item) {\n itemLanguage = item['@language'];\n } else if('@type' in item) {\n itemType = item['@type'];\n } else {\n // plain literal\n itemLanguage = '@null';\n }\n } else {\n itemType = '@id';\n }\n if(commonLanguage === null) {\n commonLanguage = itemLanguage;\n } else if(itemLanguage !== commonLanguage && _isValue(item)) {\n commonLanguage = '@none';\n }\n if(commonType === null) {\n commonType = itemType;\n } else if(itemType !== commonType) {\n commonType = '@none';\n }\n // there are different languages and types in the list, so choose\n // the most generic term, no need to keep iterating the list\n if(commonLanguage === '@none' && commonType === '@none') {\n break;\n }\n }\n commonLanguage = commonLanguage || '@none';\n commonType = commonType || '@none';\n if(commonType !== '@none') {\n typeOrLanguage = '@type';\n typeOrLanguageValue = commonType;\n } else {\n typeOrLanguageValue = commonLanguage;\n }\n }\n } else {\n if(_isValue(value)) {\n if('@language' in value && !('@index' in value)) {\n containers.push('@language', '@language@set');\n typeOrLanguageValue = value['@language'];\n } else if('@type' in value) {\n typeOrLanguage = '@type';\n typeOrLanguageValue = value['@type'];\n }\n } else {\n typeOrLanguage = '@type';\n typeOrLanguageValue = '@id';\n }\n containers.push('@set');\n }\n\n // do term selection\n containers.push('@none');\n\n // an index map can be used to index values using @none, so add as a low\n // priority\n if(_isObject(value) && !('@index' in value)) {\n // allow indexing even if no @index present\n containers.push('@index', '@index@set');\n }\n\n // values without type or language can use @language map\n if(_isValue(value) && Object.keys(value).length === 1) {\n // allow indexing even if no @index present\n containers.push('@language', '@language@set');\n }\n\n const term = _selectTerm(\n activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue);\n if(term !== null) {\n return term;\n }\n }\n\n // no term match, use @vocab if available\n if(relativeTo.vocab) {\n if('@vocab' in activeCtx) {\n // determine if vocab is a prefix of the iri\n const vocab = activeCtx['@vocab'];\n if(iri.indexOf(vocab) === 0 && iri !== vocab) {\n // use suffix as relative iri if it is not a term in the active context\n const suffix = iri.substr(vocab.length);\n if(!activeCtx.mappings.has(suffix)) {\n return suffix;\n }\n }\n }\n }\n\n // no term or @vocab match, check for possible CURIEs\n let choice = null;\n // TODO: make FastCurieMap a class with a method to do this lookup\n const partialMatches = [];\n let iriMap = activeCtx.fastCurieMap;\n // check for partial matches of against `iri`, which means look until\n // iri.length - 1, not full length\n const maxPartialLength = iri.length - 1;\n for(let i = 0; i < maxPartialLength && iri[i] in iriMap; ++i) {\n iriMap = iriMap[iri[i]];\n if('' in iriMap) {\n partialMatches.push(iriMap[''][0]);\n }\n }\n // check partial matches in reverse order to prefer longest ones first\n for(let i = partialMatches.length - 1; i >= 0; --i) {\n const entry = partialMatches[i];\n const terms = entry.terms;\n for(const term of terms) {\n // a CURIE is usable if:\n // 1. it has no mapping, OR\n // 2. value is null, which means we're not compacting an @value, AND\n // the mapping matches the IRI\n const curie = term + ':' + iri.substr(entry.iri.length);\n const isUsableCurie = (activeCtx.mappings.get(term)._prefix &&\n (!activeCtx.mappings.has(curie) ||\n (value === null && activeCtx.mappings.get(curie)['@id'] === iri)));\n\n // select curie if it is shorter or the same length but lexicographically\n // less than the current choice\n if(isUsableCurie && (choice === null ||\n _compareShortestLeast(curie, choice) < 0)) {\n choice = curie;\n }\n }\n }\n\n // return chosen curie\n if(choice !== null) {\n return choice;\n }\n\n // compact IRI relative to base\n if(!relativeTo.vocab) {\n return _removeBase(activeCtx['@base'], iri);\n }\n\n // return IRI as is\n return iri;\n};\n\n/**\n * Performs value compaction on an object with '@value' or '@id' as the only\n * property.\n *\n * @param activeCtx the active context.\n * @param activeProperty the active property that points to the value.\n * @param value the value to compact.\n * @param {Object} [options] - processing options.\n *\n * @return the compaction result.\n */\napi.compactValue = ({activeCtx, activeProperty, value, options}) => {\n // value is a @value\n if(_isValue(value)) {\n // get context rules\n const type = _getContextValue(activeCtx, activeProperty, '@type');\n const language = _getContextValue(activeCtx, activeProperty, '@language');\n const container =\n _getContextValue(activeCtx, activeProperty, '@container') || [];\n\n // whether or not the value has an @index that must be preserved\n const preserveIndex = '@index' in value && !container.includes('@index');\n\n // if there's no @index to preserve ...\n if(!preserveIndex) {\n // matching @type or @language specified in context, compact value\n if(value['@type'] === type || value['@language'] === language) {\n return value['@value'];\n }\n }\n\n // return just the value of @value if all are true:\n // 1. @value is the only key or @index isn't being preserved\n // 2. there is no default language or @value is not a string or\n // the key has a mapping with a null @language\n const keyCount = Object.keys(value).length;\n const isValueOnlyKey = (keyCount === 1 ||\n (keyCount === 2 && '@index' in value && !preserveIndex));\n const hasDefaultLanguage = ('@language' in activeCtx);\n const isValueString = _isString(value['@value']);\n const hasNullMapping = (activeCtx.mappings.has(activeProperty) &&\n activeCtx.mappings.get(activeProperty)['@language'] === null);\n if(isValueOnlyKey &&\n (!hasDefaultLanguage || !isValueString || hasNullMapping)) {\n return value['@value'];\n }\n\n const rval = {};\n\n // preserve @index\n if(preserveIndex) {\n rval[api.compactIri({\n activeCtx,\n iri: '@index',\n relativeTo: {vocab: true}\n })] = value['@index'];\n }\n\n if('@type' in value) {\n // compact @type IRI\n rval[api.compactIri({\n activeCtx,\n iri: '@type',\n relativeTo: {vocab: true}\n })] = api.compactIri(\n {activeCtx, iri: value['@type'], relativeTo: {vocab: true}});\n } else if('@language' in value) {\n // alias @language\n rval[api.compactIri({\n activeCtx,\n iri: '@language',\n relativeTo: {vocab: true}\n })] = value['@language'];\n }\n\n // alias @value\n rval[api.compactIri({\n activeCtx,\n iri: '@value',\n relativeTo: {vocab: true}\n })] = value['@value'];\n\n return rval;\n }\n\n // value is a subject reference\n const expandedProperty = _expandIri(activeCtx, activeProperty, {vocab: true},\n options);\n const type = _getContextValue(activeCtx, activeProperty, '@type');\n const compacted = api.compactIri(\n {activeCtx, iri: value['@id'], relativeTo: {vocab: type === '@vocab'}});\n\n // compact to scalar\n if(type === '@id' || type === '@vocab' || expandedProperty === '@graph') {\n return compacted;\n }\n\n return {\n [api.compactIri({\n activeCtx,\n iri: '@id',\n relativeTo: {vocab: true}\n })]: compacted\n };\n};\n\n/**\n * Removes the @preserve keywords as the last step of the compaction\n * algorithm when it is running on framed output.\n *\n * @param ctx the active context used to compact the input.\n * @param input the framed, compacted output.\n * @param options the compaction options used.\n *\n * @return the resulting output.\n */\napi.removePreserve = (ctx, input, options) => {\n // recurse through arrays\n if(_isArray(input)) {\n const output = [];\n for(let i = 0; i < input.length; ++i) {\n const result = api.removePreserve(ctx, input[i], options);\n // drop nulls from arrays\n if(result !== null) {\n output.push(result);\n }\n }\n input = output;\n } else if(_isObject(input)) {\n // remove @preserve\n if('@preserve' in input) {\n if(input['@preserve'] === '@null') {\n return null;\n }\n return input['@preserve'];\n }\n\n // skip @values\n if(_isValue(input)) {\n return input;\n }\n\n // recurse through @lists\n if(_isList(input)) {\n input['@list'] = api.removePreserve(ctx, input['@list'], options);\n return input;\n }\n\n // handle in-memory linked nodes\n const idAlias = api.compactIri({\n activeCtx: ctx,\n iri: '@id',\n relativeTo: {vocab: true}\n });\n if(input.hasOwnProperty(idAlias)) {\n const id = input[idAlias];\n if(options.link.hasOwnProperty(id)) {\n const idx = options.link[id].indexOf(input);\n if(idx !== -1) {\n // already visited\n return options.link[id][idx];\n }\n // prevent circular visitation\n options.link[id].push(input);\n } else {\n // prevent circular visitation\n options.link[id] = [input];\n }\n }\n\n // recurse through properties\n const graphAlias = api.compactIri({\n activeCtx: ctx,\n iri: '@graph',\n relativeTo: {vocab: true}\n });\n for(const prop in input) {\n // potentially remove the id, if it is an unreference bnode\n if(prop === idAlias && options.bnodesToClear.includes(input[prop])) {\n delete input[idAlias];\n continue;\n }\n\n let result = api.removePreserve(ctx, input[prop], options);\n const container = _getContextValue(ctx, prop, '@container') || [];\n if(options.compactArrays && _isArray(result) && result.length === 1 &&\n container.length === 0 && prop !== graphAlias) {\n result = result[0];\n }\n input[prop] = result;\n }\n }\n return input;\n};\n\n/**\n * Picks the preferred compaction term from the given inverse context entry.\n *\n * @param activeCtx the active context.\n * @param iri the IRI to pick the term for.\n * @param value the value to pick the term for.\n * @param containers the preferred containers.\n * @param typeOrLanguage either '@type' or '@language'.\n * @param typeOrLanguageValue the preferred value for '@type' or '@language'.\n *\n * @return the preferred term.\n */\nfunction _selectTerm(\n activeCtx, iri, value, containers, typeOrLanguage, typeOrLanguageValue) {\n if(typeOrLanguageValue === null) {\n typeOrLanguageValue = '@null';\n }\n\n // preferences for the value of @type or @language\n const prefs = [];\n\n // determine prefs for @id based on whether or not value compacts to a term\n if((typeOrLanguageValue === '@id' || typeOrLanguageValue === '@reverse') &&\n _isSubjectReference(value)) {\n // prefer @reverse first\n if(typeOrLanguageValue === '@reverse') {\n prefs.push('@reverse');\n }\n // try to compact value to a term\n const term = api.compactIri(\n {activeCtx, iri: value['@id'], relativeTo: {vocab: true}});\n if(activeCtx.mappings.has(term) &&\n activeCtx.mappings.get(term) &&\n activeCtx.mappings.get(term)['@id'] === value['@id']) {\n // prefer @vocab\n prefs.push.apply(prefs, ['@vocab', '@id']);\n } else {\n // prefer @id\n prefs.push.apply(prefs, ['@id', '@vocab']);\n }\n } else {\n prefs.push(typeOrLanguageValue);\n }\n prefs.push('@none');\n\n const containerMap = activeCtx.inverse[iri];\n for(let ci = 0; ci < containers.length; ++ci) {\n // if container not available in the map, continue\n const container = containers[ci];\n if(!(container in containerMap)) {\n continue;\n }\n\n const typeOrLanguageValueMap = containerMap[container][typeOrLanguage];\n for(let pi = 0; pi < prefs.length; ++pi) {\n // if type/language option not available in the map, continue\n const pref = prefs[pi];\n if(!(pref in typeOrLanguageValueMap)) {\n continue;\n }\n\n // select term\n return typeOrLanguageValueMap[pref];\n }\n }\n\n return null;\n}\n\n/**\n * The value of `@nest` in the term definition must either be `@nest`, or a term\n * which resolves to `@nest`.\n *\n * @param activeCtx the active context.\n * @param nestProperty a term in the active context or `@nest`.\n * @param {Object} [options] - processing options.\n */\nfunction _checkNestProperty(activeCtx, nestProperty, options) {\n if(_expandIri(activeCtx, nestProperty, {vocab: true}, options) !== '@nest') {\n throw new JsonLdError(\n 'JSON-LD compact error; nested property must have an @nest value ' +\n 'resolving to @nest.',\n 'jsonld.SyntaxError', {code: 'invalid @nest value'});\n }\n}\n"],"mappings":"AAAA;ACwoBA;ACvZA;;;;;;;;;;;ACorBA;;AAcA","sourceRoot":""}