Skip to content

Commit 20fa0cf

Browse files
committed
Update to rdf-canonize@4.
- **BREAKING**: See the `rdf-canonize` 4.0.0 changelog for **important** changes and upgrade notes. - Update to handle different RDF/JS dataset `BlankNode` format. - Enable pass through of numerous possible `rdf-canonize` options in a `canonize()` `canonizeOptions` parameter. - Update to use `rdf-canonize` options. - The `URDNA2015` default algorithm has been changed to `RDFC-1.0` from `rdf-canon`. - Complexity control defaults `maxWorkFactor` or `maxDeepIterations` may need to be adjusted to process graphs with certain blank node constructs. - A `signal` option is available to use an `AbortSignal` to limit resource usage. - The internal digest algorithm can be changed.
1 parent 2117c3b commit 20fa0cf

File tree

7 files changed

+330
-178
lines changed

7 files changed

+330
-178
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
### Changed
66
- **BREAKING**: Drop support for Node.js < 18.
7+
- **BREAKING**: Upgrade dependencies.
8+
- `rdf-canonize@4`: See the [rdf-canonize][] 4.0.0 changelog for
9+
**important** changes and upgrade notes. Of note:
10+
- The `URDNA2015` default algorithm has been changed to `RDFC-1.0` from
11+
[rdf-canon][].
12+
- Complexity control defaults `maxWorkFactor` or `maxDeepIterations` may
13+
need to be adjusted to process graphs with certain blank node constructs.
14+
- A `signal` option is available to use an `AbortSignal` to limit resource
15+
usage.
16+
- The internal digest algorithm can be changed.
717

818
## 8.3.2 - 2023-12-06
919

lib/fromRdf.js

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ api.fromRDF = async (
8989
const nodeMap = graphMap[name];
9090

9191
// get subject, predicate, object
92-
const s = quad.subject.value;
92+
const s = _nodeId(quad.subject);
9393
const p = quad.predicate.value;
9494
const o = quad.object;
9595

@@ -98,13 +98,14 @@ api.fromRDF = async (
9898
}
9999
const node = nodeMap[s];
100100

101-
const objectIsNode = o.termType.endsWith('Node');
102-
if(objectIsNode && !(o.value in nodeMap)) {
103-
nodeMap[o.value] = {'@id': o.value};
101+
const objectNodeId = _nodeId(o);
102+
const objectIsNode = !!objectNodeId;
103+
if(objectIsNode && !(objectNodeId in nodeMap)) {
104+
nodeMap[objectNodeId] = {'@id': objectNodeId};
104105
}
105106

106107
if(p === RDF_TYPE && !useRdfType && objectIsNode) {
107-
_addValue(node, '@type', o.value, {propertyIsArray: true});
108+
_addValue(node, '@type', objectNodeId, {propertyIsArray: true});
108109
continue;
109110
}
110111

@@ -114,9 +115,9 @@ api.fromRDF = async (
114115
// object may be an RDF list/partial list node but we can't know easily
115116
// until all triples are read
116117
if(objectIsNode) {
117-
if(o.value === RDF_NIL) {
118+
if(objectNodeId === RDF_NIL) {
118119
// track rdf:nil uniquely per graph
119-
const object = nodeMap[o.value];
120+
const object = nodeMap[objectNodeId];
120121
if(!('usages' in object)) {
121122
object.usages = [];
122123
}
@@ -125,12 +126,12 @@ api.fromRDF = async (
125126
property: p,
126127
value
127128
});
128-
} else if(o.value in referencedOnce) {
129+
} else if(objectNodeId in referencedOnce) {
129130
// object referenced more than once
130-
referencedOnce[o.value] = false;
131+
referencedOnce[objectNodeId] = false;
131132
} else {
132133
// keep track of single reference
133-
referencedOnce[o.value] = {
134+
referencedOnce[objectNodeId] = {
134135
node,
135136
property: p,
136137
value
@@ -303,8 +304,9 @@ api.fromRDF = async (
303304
*/
304305
function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
305306
// convert NamedNode/BlankNode object to JSON-LD
306-
if(o.termType.endsWith('Node')) {
307-
return {'@id': o.value};
307+
const nodeId = _nodeId(o);
308+
if(nodeId) {
309+
return {'@id': nodeId};
308310
}
309311

310312
// convert literal to JSON-LD
@@ -397,3 +399,20 @@ function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
397399

398400
return rval;
399401
}
402+
403+
/**
404+
* Return id for a term. Handles BlankNodes and NamedNodes. Adds a '_:' prefix
405+
* for BlanksNodes.
406+
*
407+
* @param term a term object.
408+
*
409+
* @return the Node term id or null.
410+
*/
411+
function _nodeId(term) {
412+
if(term.termType === 'NamedNode') {
413+
return term.value;
414+
} else if(term.termType === 'BlankNode') {
415+
return '_:' + term.value;
416+
}
417+
return null;
418+
}

lib/jsonld.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -523,33 +523,39 @@ jsonld.link = async function(input, ctx, options) {
523523
/**
524524
* Performs RDF dataset normalization on the given input. The input is JSON-LD
525525
* unless the 'inputFormat' option is used. The output is an RDF dataset
526-
* unless the 'format' option is used.
526+
* unless a non-null 'format' option is used.
527527
*
528528
* Note: Canonicalization sets `safe` to `true` and `base` to `null` by
529529
* default in order to produce safe outputs and "fail closed" by default. This
530530
* is different from the other API transformations in this version which
531531
* allow unsafe defaults (for cryptographic usage) in order to comply with the
532532
* JSON-LD 1.1 specification.
533533
*
534-
* @param input the input to normalize as JSON-LD or as a format specified by
535-
* the 'inputFormat' option.
534+
* @param input the input to normalize as JSON-LD given as an RDF dataset or as
535+
* a format specified by the 'inputFormat' option.
536536
* @param [options] the options to use:
537-
* [algorithm] the normalization algorithm to use, `URDNA2015` or
538-
* `URGNA2012` (default: `URDNA2015`).
539537
* [base] the base IRI to use (default: `null`).
540538
* [expandContext] a context to expand with.
541539
* [skipExpansion] true to assume the input is expanded and skip
542540
* expansion, false not to, defaults to false. Some well-formed
543541
* and safe-mode checks may be omitted.
544-
* [inputFormat] the format if input is not JSON-LD:
545-
* 'application/n-quads' for N-Quads.
546-
* [format] the format if output is a string:
547-
* 'application/n-quads' for N-Quads.
542+
* [inputFormat] the input format. null for a JSON-LD object,
543+
* 'application/n-quads' for N-Quads. (default: null)
544+
* [format] the output format. null for an RDF dataset,
545+
* 'application/n-quads' for an N-Quads string. (default: N-Quads)
548546
* [documentLoader(url, options)] the document loader.
549-
* [useNative] true to use a native canonize algorithm
550547
* [rdfDirection] null or 'i18n-datatype' to support RDF
551548
* transformation of @direction (default: null).
552549
* [safe] true to use safe mode. (default: true).
550+
* [canonizeOptions] options to pass to rdf-canonize canonize(). See
551+
* rdf-canonize for more details. Commonly used options, and their
552+
* defaults, are:
553+
* algorithm="RDFC-1.0",
554+
* messageDigestAlgorithm="sha256",
555+
* canonicalIdMap,
556+
* maxWorkFactor=1,
557+
* maxDeepIterations=-1,
558+
* and signal=null.
553559
* [contextResolver] internal use only.
554560
*
555561
* @return a Promise that resolves to the normalized output.
@@ -559,15 +565,19 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
559565
throw new TypeError('Could not canonize, too few arguments.');
560566
}
561567

562-
// set default options
568+
// set toRDF options
563569
options = _setDefaults(options, {
564-
base: _isString(input) ? input : null,
565-
algorithm: 'URDNA2015',
566570
skipExpansion: false,
567571
safe: true,
568572
contextResolver: new ContextResolver(
569573
{sharedCache: _resolvedContextCache})
570574
});
575+
576+
// set canonize options
577+
const canonizeOptions = Object.assign({}, {
578+
algorithm: 'RDFC-1.0'
579+
}, options.canonizeOptions || null);
580+
571581
if('inputFormat' in options) {
572582
if(options.inputFormat !== 'application/n-quads' &&
573583
options.inputFormat !== 'application/nquads') {
@@ -579,17 +589,18 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
579589
const parsedInput = NQuads.parse(input);
580590

581591
// do canonicalization
582-
return canonize.canonize(parsedInput, options);
592+
return canonize.canonize(parsedInput, canonizeOptions);
583593
}
584594

585595
// convert to RDF dataset then do normalization
586596
const opts = {...options};
587597
delete opts.format;
598+
delete opts.canonizeOptions;
588599
opts.produceGeneralizedRdf = false;
589600
const dataset = await jsonld.toRDF(input, opts);
590601

591602
// do canonicalization
592-
return canonize.canonize(dataset, options);
603+
return canonize.canonize(dataset, canonizeOptions);
593604
};
594605

595606
/**
@@ -653,8 +664,8 @@ jsonld.fromRDF = async function(dataset, options) {
653664
* [skipExpansion] true to assume the input is expanded and skip
654665
* expansion, false not to, defaults to false. Some well-formed
655666
* and safe-mode checks may be omitted.
656-
* [format] the format to use to output a string:
657-
* 'application/n-quads' for N-Quads.
667+
* [format] the output format. null for an RDF dataset,
668+
* 'application/n-quads' for an N-Quads string. (default: null)
658669
* [produceGeneralizedRdf] true to output generalized RDF, false
659670
* to produce only standard RDF (default: false).
660671
* [documentLoader(url, options)] the document loader.
@@ -672,7 +683,6 @@ jsonld.toRDF = async function(input, options) {
672683

673684
// set default options
674685
options = _setDefaults(options, {
675-
base: _isString(input) ? input : '',
676686
skipExpansion: false,
677687
contextResolver: new ContextResolver(
678688
{sharedCache: _resolvedContextCache})

lib/toRdf.js

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,7 @@ api.toRDF = (input, options) => {
6363
if(graphName === '@default') {
6464
graphTerm = {termType: 'DefaultGraph', value: ''};
6565
} else if(_isAbsoluteIri(graphName)) {
66-
if(graphName.startsWith('_:')) {
67-
graphTerm = {termType: 'BlankNode'};
68-
} else {
69-
graphTerm = {termType: 'NamedNode'};
70-
}
71-
graphTerm.value = graphName;
66+
graphTerm = _makeTerm(graphName);
7267
} else {
7368
// skip relative IRIs (not valid RDF)
7469
if(options.eventHandler) {
@@ -119,10 +114,7 @@ function _graphToRDF(dataset, graph, graphTerm, issuer, options) {
119114

120115
for(const item of items) {
121116
// RDF subject
122-
const subject = {
123-
termType: id.startsWith('_:') ? 'BlankNode' : 'NamedNode',
124-
value: id
125-
};
117+
const subject = _makeTerm(id);
126118

127119
// skip relative IRI subjects (not valid RDF)
128120
if(!_isAbsoluteIri(id)) {
@@ -144,10 +136,7 @@ function _graphToRDF(dataset, graph, graphTerm, issuer, options) {
144136
}
145137

146138
// RDF predicate
147-
const predicate = {
148-
termType: property.startsWith('_:') ? 'BlankNode' : 'NamedNode',
149-
value: property
150-
};
139+
const predicate = _makeTerm(property);
151140

152141
// skip relative IRI predicates (not valid RDF)
153142
if(!_isAbsoluteIri(property)) {
@@ -226,13 +215,16 @@ function _listToRDF(list, issuer, dataset, graphTerm, rdfDirection, options) {
226215

227216
const last = list.pop();
228217
// Result is the head of the list
229-
const result = last ? {termType: 'BlankNode', value: issuer.getId()} : nil;
218+
const result = last ? {
219+
termType: 'BlankNode',
220+
value: issuer.getId().slice(2)
221+
} : nil;
230222
let subject = result;
231223

232224
for(const item of list) {
233225
const object = _objectToRDF(
234226
item, issuer, dataset, graphTerm, rdfDirection, options);
235-
const next = {termType: 'BlankNode', value: issuer.getId()};
227+
const next = {termType: 'BlankNode', value: issuer.getId().slice(2)};
236228
dataset.push({
237229
subject,
238230
predicate: first,
@@ -284,14 +276,16 @@ function _listToRDF(list, issuer, dataset, graphTerm, rdfDirection, options) {
284276
function _objectToRDF(
285277
item, issuer, dataset, graphTerm, rdfDirection, options
286278
) {
287-
const object = {};
279+
let object;
288280

289281
// convert value object to RDF
290282
if(graphTypes.isValue(item)) {
291-
object.termType = 'Literal';
292-
object.value = undefined;
293-
object.datatype = {
294-
termType: 'NamedNode'
283+
object = {
284+
termType: 'Literal',
285+
value: undefined,
286+
datatype: {
287+
termType: 'NamedNode'
288+
}
295289
};
296290
let value = item['@value'];
297291
const datatype = item['@type'] || null;
@@ -374,13 +368,14 @@ function _objectToRDF(
374368
} else if(graphTypes.isList(item)) {
375369
const _list = _listToRDF(
376370
item['@list'], issuer, dataset, graphTerm, rdfDirection, options);
377-
object.termType = _list.termType;
378-
object.value = _list.value;
371+
object = {
372+
termType: _list.termType,
373+
value: _list.value
374+
};
379375
} else {
380376
// convert string/node object to RDF
381377
const id = types.isObject(item) ? item['@id'] : item;
382-
object.termType = id.startsWith('_:') ? 'BlankNode' : 'NamedNode';
383-
object.value = id;
378+
object = _makeTerm(id);
384379
}
385380

386381
// skip relative IRIs, not valid RDF
@@ -404,3 +399,24 @@ function _objectToRDF(
404399

405400
return object;
406401
}
402+
403+
/**
404+
* Make a term from an id. Handles BlankNodes and NamedNodes based on a
405+
* possible '_:' id prefix. The prefix is removed for BlankNodes.
406+
*
407+
* @param id a term id.
408+
*
409+
* @return a term object.
410+
*/
411+
function _makeTerm(id) {
412+
if(id.startsWith('_:')) {
413+
return {
414+
termType: 'BlankNode',
415+
value: id.slice(2)
416+
};
417+
}
418+
return {
419+
termType: 'NamedNode',
420+
value: id
421+
};
422+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@digitalbazaar/http-client": "^3.4.1",
3333
"canonicalize": "^1.0.1",
3434
"lru-cache": "^6.0.0",
35-
"rdf-canonize": "^3.4.0"
35+
"rdf-canonize": "^4.0.1"
3636
},
3737
"devDependencies": {
3838
"@babel/core": "^7.21.8",

tests/misc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4030,7 +4030,7 @@ _:b0 <ex:p> "v" .
40304030
]
40314031
;
40324032
const nq = `\
4033-
_:b0 <_:b1> "v" .
4033+
_:b0 _:b1 "v" .
40344034
`;
40354035

40364036
await _test({

0 commit comments

Comments
 (0)