Skip to content

Commit bcae961

Browse files
committed
Add expansionMap and compactionMap options.
1 parent 1335b53 commit bcae961

File tree

2 files changed

+167
-22
lines changed

2 files changed

+167
-22
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# jsonld ChangeLog
22

3+
### Added
4+
- Add `expansionMap` and `compactionMap` options. These
5+
functions may be provided that will be called when an
6+
unmapped value or property will be dropped during expansion
7+
or compaction, respectively. The function map return either
8+
`undefined` to cause the default behavior, some other
9+
value to use instead of the default expanded/compacted value,
10+
or it may throw an error to stop expansion/compaction.
11+
312
## 0.5.9 - 2017-09-21
413

514
### Fixed

lib/jsonld.js

Lines changed: 158 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ var wrapper = function(jsonld) {
105105
* [skipExpansion] true to assume the input is expanded and skip
106106
* expansion, false not to, defaults to false.
107107
* [documentLoader(url, callback(err, remoteDoc))] the document loader.
108+
* [expansionMap(info)] a function that can be used to custom map
109+
* unmappable values (or to throw an error when they are detected);
110+
* if this function returns `undefined` then the default behavior
111+
* will be used.
112+
* [compactionMap(info)] a function that can be used to custom map
113+
* unmappable values (or to throw an error when they are detected);
114+
* if this function returns `undefined` then the default behavior
115+
* will be used.
108116
* @param callback(err, compacted, ctx) called once the operation completes.
109117
*/
110118
jsonld.compact = function(input, ctx, options, callback) {
@@ -184,7 +192,11 @@ jsonld.compact = function(input, ctx, options, callback) {
184192
var compacted;
185193
try {
186194
// do compaction
187-
compacted = new Processor().compact(activeCtx, null, expanded, options);
195+
const processor = new Processor();
196+
if(typeof options.compactionMap === 'function') {
197+
processor.compactionMap = options.compactionMap;
198+
}
199+
compacted = processor.compact(activeCtx, null, expanded, options);
188200
} catch(ex) {
189201
return callback(ex);
190202
}
@@ -270,6 +282,10 @@ jsonld.compact = function(input, ctx, options, callback) {
270282
* [keepFreeFloatingNodes] true to keep free-floating nodes,
271283
* false not to, defaults to false.
272284
* [documentLoader(url, callback(err, remoteDoc))] the document loader.
285+
* [expansionMap(info)] a function that can be used to custom map
286+
* unmappable values (or to throw an error when they are detected);
287+
* if this function returns `undefined` then the default behavior
288+
* will be used.
273289
* @param [callback(err, expanded)] called once the operation completes.
274290
*
275291
* @return a Promise that resolves to the expanded output.
@@ -357,6 +373,10 @@ jsonld.expand = util.callbackify(async (input, options) => {
357373
let expanded;
358374
try {
359375
const processor = new Processor();
376+
if(typeof options.expansionMap === 'function') {
377+
processor.expansionMap = options.expansionMap;
378+
}
379+
360380
let activeCtx = _getInitialContext(options);
361381
const doc = remoteUrlContainer.document;
362382
const remoteContext = remoteUrlContainer.remoteContext['@context'];
@@ -949,6 +969,8 @@ jsonld.toRDF = util.callbackify(async function(input, options) {
949969
options.documentLoader = jsonld.loadDocument;
950970
}
951971

972+
// TODO: support toRDF custom map?
973+
952974
// TODO: use `await` once jsonld.expand is updated
953975
// expand input
954976
//const expanded = await jsonld.expand(input)
@@ -1564,7 +1586,11 @@ const JsonLdError = require('./JsonLdError');
15641586
/**
15651587
* Constructs a new JSON-LD Processor.
15661588
*/
1567-
var Processor = function() {};
1589+
var Processor = function() {
1590+
// drop unmapped values by default
1591+
this.expansionMap = () => undefined;
1592+
this.compactionMap = () => undefined;
1593+
};
15681594

15691595
/**
15701596
* Recursively compacts an element using the given active context. All values
@@ -1584,12 +1610,24 @@ Processor.prototype.compact = function(
15841610
if(_isArray(element)) {
15851611
var rval = [];
15861612
for(var i = 0; i < element.length; ++i) {
1587-
// compact, dropping any null values
1613+
// compact, dropping any null values unless custom mapped
15881614
var compacted = this.compact(
15891615
activeCtx, activeProperty, element[i], options);
1590-
if(compacted !== null) {
1591-
rval.push(compacted);
1616+
if(compacted === null) {
1617+
// TODO: use `await` to support async
1618+
compacted = this.compactionMap({
1619+
unmappedValue: element[i],
1620+
activeCtx,
1621+
activeProperty,
1622+
parent: element,
1623+
index: i,
1624+
options
1625+
});
1626+
if(compacted === undefined) {
1627+
continue;
1628+
}
15921629
}
1630+
rval.push(compacted);
15931631
}
15941632
if(options.compactArrays && rval.length === 1) {
15951633
// use single element if no container is specified
@@ -1728,6 +1766,11 @@ Processor.prototype.compact = function(
17281766
}
17291767

17301768
// Note: expanded value must be an array due to expansion algorithm.
1769+
if(!_isArray(expandedValue)) {
1770+
throw new JsonLdError(
1771+
'JSON-LD expansion error; expanded value must be an array.',
1772+
'jsonld.SyntaxError');
1773+
}
17311774

17321775
// preserve empty arrays
17331776
if(expandedValue.length === 0) {
@@ -1874,10 +1917,21 @@ Processor.prototype.expand = function(
18741917
}
18751918

18761919
if(!_isArray(element) && !_isObject(element)) {
1877-
// drop free-floating scalars that are not in lists
1920+
// drop free-floating scalars that are not in lists unless custom mapped
18781921
if(!insideList && (activeProperty === null ||
18791922
_expandIri(activeCtx, activeProperty, {vocab: true}) === '@graph')) {
1880-
return null;
1923+
// TODO: use `await` to support async
1924+
const mapped = self.expansionMap({
1925+
unmappedValue: element,
1926+
activeCtx,
1927+
activeProperty,
1928+
options,
1929+
insideList
1930+
});
1931+
if(mapped === undefined) {
1932+
return null;
1933+
}
1934+
return mapped;
18811935
}
18821936

18831937
// expand element according to value expansion rules
@@ -1899,14 +1953,29 @@ Processor.prototype.expand = function(
18991953
'Invalid JSON-LD syntax; lists of lists are not permitted.',
19001954
'jsonld.SyntaxError', {code: 'list of lists'});
19011955
}
1902-
// drop null values
1903-
if(e !== null) {
1904-
if(_isArray(e)) {
1905-
rval = rval.concat(e);
1906-
} else {
1907-
rval.push(e);
1956+
1957+
if(e === null) {
1958+
// TODO: add `await` for async support
1959+
e = self.expansionMap({
1960+
unmappedValue: element[i],
1961+
activeCtx,
1962+
activeProperty,
1963+
parent: element,
1964+
index: i,
1965+
options,
1966+
expandedParent: rval,
1967+
insideList
1968+
});
1969+
if(e === undefined) {
1970+
continue;
19081971
}
19091972
}
1973+
1974+
if(_isArray(e)) {
1975+
rval = rval.concat(e);
1976+
} else {
1977+
rval.push(e);
1978+
}
19101979
}
19111980
return rval;
19121981
}
@@ -1937,10 +2006,23 @@ Processor.prototype.expand = function(
19372006
// expand property
19382007
var expandedProperty = _expandIri(activeCtx, key, {vocab: true});
19392008

1940-
// drop non-absolute IRI keys that aren't keywords
2009+
// drop non-absolute IRI keys that aren't keywords unless custom mapped
19412010
if(expandedProperty === null ||
19422011
!(_isAbsoluteIri(expandedProperty) || _isKeyword(expandedProperty))) {
1943-
continue;
2012+
// TODO: use `await` to support async
2013+
expandedProperty = self.expansionMap({
2014+
unmappedProperty: key,
2015+
activeCtx,
2016+
activeProperty,
2017+
parent: element,
2018+
options,
2019+
insideList,
2020+
value,
2021+
expandedParent: rval
2022+
});
2023+
if(expandedProperty === undefined) {
2024+
continue;
2025+
}
19442026
}
19452027

19462028
if(_isKeyword(expandedProperty)) {
@@ -2120,7 +2202,21 @@ Processor.prototype.expand = function(
21202202

21212203
// drop null values if property is not @value
21222204
if(expandedValue === null && expandedProperty !== '@value') {
2123-
continue;
2205+
// TODO: use `await` to support async
2206+
expandedValue = self.expansionMap({
2207+
unmappedValue: value,
2208+
expandedProperty,
2209+
activeCtx,
2210+
activeProperty,
2211+
parent: element,
2212+
options,
2213+
insideList,
2214+
key: key,
2215+
expandedParent: rval
2216+
});
2217+
if(expandedValue === undefined) {
2218+
continue;
2219+
}
21242220
}
21252221

21262222
// convert expanded value to @list if container specifies it
@@ -2198,9 +2294,22 @@ Processor.prototype.expand = function(
21982294
'which can be "@type" or "@language".',
21992295
'jsonld.SyntaxError', {code: 'invalid value object', element: rval});
22002296
}
2201-
// drop null @values
2297+
// drop null @values unless custom mapped
22022298
if(rval['@value'] === null) {
2203-
rval = null;
2299+
// TODO: use `await` to support async
2300+
const mapped = self.expansionMap({
2301+
unmappedValue: rval,
2302+
activeCtx,
2303+
activeProperty,
2304+
element,
2305+
options,
2306+
insideList
2307+
});
2308+
if(mapped !== undefined) {
2309+
rval = mapped;
2310+
} else {
2311+
rval = null;
2312+
}
22042313
} else if('@language' in rval && !_isString(rval['@value'])) {
22052314
// if @language is present, @value must be a string
22062315
throw new JsonLdError(
@@ -2233,18 +2342,45 @@ Processor.prototype.expand = function(
22332342
count = keys.length;
22342343
}
22352344
} else if(count === 1 && '@language' in rval) {
2236-
// drop objects with only @language
2237-
rval = null;
2345+
// drop objects with only @language unless custom mapped
2346+
// TODO: use `await` to support async
2347+
const mapped = self.expansionMap(rval, {
2348+
unmappedValue: rval,
2349+
activeCtx,
2350+
activeProperty,
2351+
element,
2352+
options,
2353+
insideList
2354+
});
2355+
if(mapped !== undefined) {
2356+
rval = mapped;
2357+
} else {
2358+
rval = null;
2359+
}
22382360
}
22392361

2240-
// drop certain top-level objects that do not occur in lists
2362+
// drop certain top-level objects that do not occur in lists, unless custom
2363+
// mapped
22412364
if(_isObject(rval) &&
22422365
!options.keepFreeFloatingNodes && !insideList &&
22432366
(activeProperty === null || expandedActiveProperty === '@graph')) {
22442367
// drop empty object, top-level @value/@list, or object with only @id
22452368
if(count === 0 || '@value' in rval || '@list' in rval ||
22462369
(count === 1 && '@id' in rval)) {
2247-
rval = null;
2370+
// TODO: use `await` to support async
2371+
const mapped = self.expansionMap({
2372+
unmappedValue: rval,
2373+
activeCtx,
2374+
activeProperty,
2375+
element,
2376+
options,
2377+
insideList
2378+
});
2379+
if(mapped !== undefined) {
2380+
rval = mapped;
2381+
} else {
2382+
rval = null;
2383+
}
22482384
}
22492385
}
22502386

0 commit comments

Comments
 (0)