@@ -105,6 +105,14 @@ var wrapper = function(jsonld) {
105
105
* [skipExpansion] true to assume the input is expanded and skip
106
106
* expansion, false not to, defaults to false.
107
107
* [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.
108
116
* @param callback(err, compacted, ctx) called once the operation completes.
109
117
*/
110
118
jsonld . compact = function ( input , ctx , options , callback ) {
@@ -184,7 +192,11 @@ jsonld.compact = function(input, ctx, options, callback) {
184
192
var compacted ;
185
193
try {
186
194
// 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 ) ;
188
200
} catch ( ex ) {
189
201
return callback ( ex ) ;
190
202
}
@@ -270,6 +282,10 @@ jsonld.compact = function(input, ctx, options, callback) {
270
282
* [keepFreeFloatingNodes] true to keep free-floating nodes,
271
283
* false not to, defaults to false.
272
284
* [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.
273
289
* @param [callback(err, expanded)] called once the operation completes.
274
290
*
275
291
* @return a Promise that resolves to the expanded output.
@@ -357,6 +373,10 @@ jsonld.expand = util.callbackify(async (input, options) => {
357
373
let expanded ;
358
374
try {
359
375
const processor = new Processor ( ) ;
376
+ if ( typeof options . expansionMap === 'function' ) {
377
+ processor . expansionMap = options . expansionMap ;
378
+ }
379
+
360
380
let activeCtx = _getInitialContext ( options ) ;
361
381
const doc = remoteUrlContainer . document ;
362
382
const remoteContext = remoteUrlContainer . remoteContext [ '@context' ] ;
@@ -949,6 +969,8 @@ jsonld.toRDF = util.callbackify(async function(input, options) {
949
969
options . documentLoader = jsonld . loadDocument ;
950
970
}
951
971
972
+ // TODO: support toRDF custom map?
973
+
952
974
// TODO: use `await` once jsonld.expand is updated
953
975
// expand input
954
976
//const expanded = await jsonld.expand(input)
@@ -1564,7 +1586,11 @@ const JsonLdError = require('./JsonLdError');
1564
1586
/**
1565
1587
* Constructs a new JSON-LD Processor.
1566
1588
*/
1567
- var Processor = function ( ) { } ;
1589
+ var Processor = function ( ) {
1590
+ // drop unmapped values by default
1591
+ this . expansionMap = ( ) => undefined ;
1592
+ this . compactionMap = ( ) => undefined ;
1593
+ } ;
1568
1594
1569
1595
/**
1570
1596
* Recursively compacts an element using the given active context. All values
@@ -1584,12 +1610,24 @@ Processor.prototype.compact = function(
1584
1610
if ( _isArray ( element ) ) {
1585
1611
var rval = [ ] ;
1586
1612
for ( var i = 0 ; i < element . length ; ++ i ) {
1587
- // compact, dropping any null values
1613
+ // compact, dropping any null values unless custom mapped
1588
1614
var compacted = this . compact (
1589
1615
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
+ }
1592
1629
}
1630
+ rval . push ( compacted ) ;
1593
1631
}
1594
1632
if ( options . compactArrays && rval . length === 1 ) {
1595
1633
// use single element if no container is specified
@@ -1728,6 +1766,11 @@ Processor.prototype.compact = function(
1728
1766
}
1729
1767
1730
1768
// 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
+ }
1731
1774
1732
1775
// preserve empty arrays
1733
1776
if ( expandedValue . length === 0 ) {
@@ -1874,10 +1917,21 @@ Processor.prototype.expand = function(
1874
1917
}
1875
1918
1876
1919
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
1878
1921
if ( ! insideList && ( activeProperty === null ||
1879
1922
_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 ;
1881
1935
}
1882
1936
1883
1937
// expand element according to value expansion rules
@@ -1899,14 +1953,29 @@ Processor.prototype.expand = function(
1899
1953
'Invalid JSON-LD syntax; lists of lists are not permitted.' ,
1900
1954
'jsonld.SyntaxError' , { code : 'list of lists' } ) ;
1901
1955
}
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 ;
1908
1971
}
1909
1972
}
1973
+
1974
+ if ( _isArray ( e ) ) {
1975
+ rval = rval . concat ( e ) ;
1976
+ } else {
1977
+ rval . push ( e ) ;
1978
+ }
1910
1979
}
1911
1980
return rval ;
1912
1981
}
@@ -1937,10 +2006,23 @@ Processor.prototype.expand = function(
1937
2006
// expand property
1938
2007
var expandedProperty = _expandIri ( activeCtx , key , { vocab : true } ) ;
1939
2008
1940
- // drop non-absolute IRI keys that aren't keywords
2009
+ // drop non-absolute IRI keys that aren't keywords unless custom mapped
1941
2010
if ( expandedProperty === null ||
1942
2011
! ( _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
+ }
1944
2026
}
1945
2027
1946
2028
if ( _isKeyword ( expandedProperty ) ) {
@@ -2120,7 +2202,21 @@ Processor.prototype.expand = function(
2120
2202
2121
2203
// drop null values if property is not @value
2122
2204
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
+ }
2124
2220
}
2125
2221
2126
2222
// convert expanded value to @list if container specifies it
@@ -2198,9 +2294,22 @@ Processor.prototype.expand = function(
2198
2294
'which can be "@type" or "@language".' ,
2199
2295
'jsonld.SyntaxError' , { code : 'invalid value object' , element : rval } ) ;
2200
2296
}
2201
- // drop null @values
2297
+ // drop null @values unless custom mapped
2202
2298
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
+ }
2204
2313
} else if ( '@language' in rval && ! _isString ( rval [ '@value' ] ) ) {
2205
2314
// if @language is present, @value must be a string
2206
2315
throw new JsonLdError (
@@ -2233,18 +2342,45 @@ Processor.prototype.expand = function(
2233
2342
count = keys . length ;
2234
2343
}
2235
2344
} 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
+ }
2238
2360
}
2239
2361
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
2241
2364
if ( _isObject ( rval ) &&
2242
2365
! options . keepFreeFloatingNodes && ! insideList &&
2243
2366
( activeProperty === null || expandedActiveProperty === '@graph' ) ) {
2244
2367
// drop empty object, top-level @value/@list, or object with only @id
2245
2368
if ( count === 0 || '@value' in rval || '@list' in rval ||
2246
2369
( 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
+ }
2248
2384
}
2249
2385
}
2250
2386
0 commit comments