Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update rdf-canonize, node versions, and more. #548

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand All @@ -23,9 +23,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [14.x, 16.x, 18.x, 20.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand All @@ -41,10 +41,10 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [20.x]
bundler: [webpack, browserify]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand All @@ -62,9 +62,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [16.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand All @@ -78,9 +78,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x]
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# jsonld ChangeLog

## 9.0.0 - 2023-xx-xx

### Changed
- **BREAKING**: Drop support for Node.js < 18.
- **BREAKING**: Upgrade dependencies.
- `@digitalbazaar/http-client@4`.
- `canonicalize@2`.
- `rdf-canonize@4`: See the [rdf-canonize][] 4.0.0 changelog for
**important** changes and upgrade notes. Of note:
- 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.

### Removed
- **BREAKING**: Remove `application/nquads` alias for `application/n-quads`.

## 8.3.2 - 2023-12-06

### Fixed
Expand Down
43 changes: 31 additions & 12 deletions lib/fromRdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ api.fromRDF = async (
const nodeMap = graphMap[name];

// get subject, predicate, object
const s = quad.subject.value;
const s = _nodeId(quad.subject);
const p = quad.predicate.value;
const o = quad.object;

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

const objectIsNode = o.termType.endsWith('Node');
if(objectIsNode && !(o.value in nodeMap)) {
nodeMap[o.value] = {'@id': o.value};
const objectNodeId = _nodeId(o);
const objectIsNode = !!objectNodeId;
if(objectIsNode && !(objectNodeId in nodeMap)) {
nodeMap[objectNodeId] = {'@id': objectNodeId};
}

if(p === RDF_TYPE && !useRdfType && objectIsNode) {
_addValue(node, '@type', o.value, {propertyIsArray: true});
_addValue(node, '@type', objectNodeId, {propertyIsArray: true});
continue;
}

Expand All @@ -114,9 +115,9 @@ api.fromRDF = async (
// object may be an RDF list/partial list node but we can't know easily
// until all triples are read
if(objectIsNode) {
if(o.value === RDF_NIL) {
if(objectNodeId === RDF_NIL) {
// track rdf:nil uniquely per graph
const object = nodeMap[o.value];
const object = nodeMap[objectNodeId];
if(!('usages' in object)) {
object.usages = [];
}
Expand All @@ -125,12 +126,12 @@ api.fromRDF = async (
property: p,
value
});
} else if(o.value in referencedOnce) {
} else if(objectNodeId in referencedOnce) {
// object referenced more than once
referencedOnce[o.value] = false;
referencedOnce[objectNodeId] = false;
} else {
// keep track of single reference
referencedOnce[o.value] = {
referencedOnce[objectNodeId] = {
node,
property: p,
value
Expand Down Expand Up @@ -303,8 +304,9 @@ api.fromRDF = async (
*/
function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
// convert NamedNode/BlankNode object to JSON-LD
if(o.termType.endsWith('Node')) {
return {'@id': o.value};
const nodeId = _nodeId(o);
if(nodeId) {
return {'@id': nodeId};
}

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

return rval;
}

/**
* Return id for a term. Handles BlankNodes and NamedNodes. Adds a '_:' prefix
* for BlanksNodes.
*
* @param term a term object.
*
* @return the Node term id or null.
*/
function _nodeId(term) {
if(term.termType === 'NamedNode') {
return term.value;
} else if(term.termType === 'BlankNode') {
return '_:' + term.value;
}
return null;
}
53 changes: 30 additions & 23 deletions lib/jsonld.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,33 +523,39 @@ jsonld.link = async function(input, ctx, options) {
/**
* Performs RDF dataset normalization on the given input. The input is JSON-LD
* unless the 'inputFormat' option is used. The output is an RDF dataset
* unless the 'format' option is used.
* unless a non-null 'format' option is used.
*
* Note: Canonicalization sets `safe` to `true` and `base` to `null` by
* default in order to produce safe outputs and "fail closed" by default. This
* is different from the other API transformations in this version which
* allow unsafe defaults (for cryptographic usage) in order to comply with the
* JSON-LD 1.1 specification.
*
* @param input the input to normalize as JSON-LD or as a format specified by
* the 'inputFormat' option.
* @param input the input to normalize as JSON-LD given as an RDF dataset or as
* a format specified by the 'inputFormat' option.
* @param [options] the options to use:
* [algorithm] the normalization algorithm to use, `URDNA2015` or
* `URGNA2012` (default: `URDNA2015`).
* [base] the base IRI to use (default: `null`).
* [expandContext] a context to expand with.
* [skipExpansion] true to assume the input is expanded and skip
* expansion, false not to, defaults to false. Some well-formed
* and safe-mode checks may be omitted.
* [inputFormat] the format if input is not JSON-LD:
* 'application/n-quads' for N-Quads.
* [format] the format if output is a string:
* 'application/n-quads' for N-Quads.
* [inputFormat] the input format. null for a JSON-LD object,
* 'application/n-quads' for N-Quads. (default: null)
* [format] the output format. null for an RDF dataset,
* 'application/n-quads' for an N-Quads string. (default: N-Quads)
* [documentLoader(url, options)] the document loader.
* [useNative] true to use a native canonize algorithm
* [rdfDirection] null or 'i18n-datatype' to support RDF
* transformation of @direction (default: null).
* [safe] true to use safe mode. (default: true).
* [canonizeOptions] options to pass to rdf-canonize canonize(). See
* rdf-canonize for more details. Commonly used options, and their
* defaults, are:
* algorithm="RDFC-1.0",
* messageDigestAlgorithm="sha256",
* canonicalIdMap,
* maxWorkFactor=1,
* maxDeepIterations=-1,
* and signal=null.
* [contextResolver] internal use only.
*
* @return a Promise that resolves to the normalized output.
Expand All @@ -559,18 +565,21 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
throw new TypeError('Could not canonize, too few arguments.');
}

// set default options
// set toRDF options
options = _setDefaults(options, {
base: _isString(input) ? input : null,
algorithm: 'URDNA2015',
skipExpansion: false,
safe: true,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
});

// set canonize options
const canonizeOptions = Object.assign({}, {
algorithm: 'RDFC-1.0'
}, options.canonizeOptions || null);

if('inputFormat' in options) {
if(options.inputFormat !== 'application/n-quads' &&
options.inputFormat !== 'application/nquads') {
if(options.inputFormat !== 'application/n-quads') {
throw new JsonLdError(
'Unknown canonicalization input format.',
'jsonld.CanonizeError');
Expand All @@ -579,17 +588,18 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
const parsedInput = NQuads.parse(input);

// do canonicalization
return canonize.canonize(parsedInput, options);
return canonize.canonize(parsedInput, canonizeOptions);
}

// convert to RDF dataset then do normalization
const opts = {...options};
delete opts.format;
delete opts.canonizeOptions;
opts.produceGeneralizedRdf = false;
const dataset = await jsonld.toRDF(input, opts);

// do canonicalization
return canonize.canonize(dataset, options);
return canonize.canonize(dataset, canonizeOptions);
};

/**
Expand Down Expand Up @@ -653,8 +663,8 @@ jsonld.fromRDF = async function(dataset, options) {
* [skipExpansion] true to assume the input is expanded and skip
* expansion, false not to, defaults to false. Some well-formed
* and safe-mode checks may be omitted.
* [format] the format to use to output a string:
* 'application/n-quads' for N-Quads.
* [format] the output format. null for an RDF dataset,
* 'application/n-quads' for an N-Quads string. (default: null)
* [produceGeneralizedRdf] true to output generalized RDF, false
* to produce only standard RDF (default: false).
* [documentLoader(url, options)] the document loader.
Expand All @@ -672,7 +682,6 @@ jsonld.toRDF = async function(input, options) {

// set default options
options = _setDefaults(options, {
base: _isString(input) ? input : '',
skipExpansion: false,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
Expand All @@ -690,8 +699,7 @@ jsonld.toRDF = async function(input, options) {
// output RDF dataset
const dataset = _toRDF(expanded, options);
if(options.format) {
if(options.format === 'application/n-quads' ||
options.format === 'application/nquads') {
if(options.format === 'application/n-quads') {
return NQuads.serialize(dataset);
}
throw new JsonLdError(
Expand Down Expand Up @@ -997,7 +1005,6 @@ jsonld.unregisterRDFParser = function(contentType) {

// register the N-Quads RDF parser
jsonld.registerRDFParser('application/n-quads', NQuads.parse);
jsonld.registerRDFParser('application/nquads', NQuads.parse);

/* URL API */
jsonld.url = require('./url');
Expand Down
Loading
Loading