@lde/pipeline-void 0.31.3 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/partitionIri.d.ts +28 -0
- package/dist/partitionIri.d.ts.map +1 -0
- package/dist/partitionIri.js +56 -0
- package/dist/partitionMerge.d.ts +55 -0
- package/dist/partitionMerge.d.ts.map +1 -0
- package/dist/partitionMerge.js +341 -0
- package/dist/stage.d.ts.map +1 -1
- package/dist/stage.js +2 -1
- package/package.json +2 -2
- package/queries/class-partition.rq +5 -5
- package/queries/class-properties-objects.rq +1 -1
- package/queries/class-properties-subjects.rq +2 -2
- package/queries/class-property-datatypes.rq +2 -2
- package/queries/class-property-languages.rq +2 -2
- package/queries/class-property-object-classes.rq +2 -2
package/README.md
CHANGED
|
@@ -72,6 +72,27 @@ Global and domain-specific factories accept `VoidStageOptions` (`transform`) and
|
|
|
72
72
|
| `detectVocabularies()` | [`entity-properties.rq`](queries/entity-properties.rq) — Entity properties with automatic `void:vocabulary` detection. Accepts `DetectVocabulariesOptions` with an optional `vocabularies` array to extend the built-in defaults. |
|
|
73
73
|
| `uriSpaces(uriSpaceMap)` | [`object-uri-space.rq`](queries/object-uri-space.rq) — Object URI namespace linksets, aggregated against a provided URI space map |
|
|
74
74
|
|
|
75
|
+
## Namespace normalization
|
|
76
|
+
|
|
77
|
+
Some vocabularies publish under both `http://` and `https://` variants of the same namespace (notably schema.org), and datasets mix them. Without normalization each variant gets its own `void:classPartition`/`void:propertyPartition`, so consumers see two partitions for one class — which crashed the Dataset Register browser (netwerk-digitaal-erfgoed/dataset-knowledge-graph#334).
|
|
78
|
+
|
|
79
|
+
`schemaOrgPartitionMergePlugin` normalizes `http://schema.org/` to `https://schema.org/` **and** merges the duplicate partition nodes the two variants produced. It is a [`beforeDatasetWrite`](../pipeline) plugin — it runs once over a whole dataset’s output at the pipeline edge, so the analysis queries stay unaware of namespace aliases (see [ADR 7](../../docs/decisions/0007-namespace-merge-as-a-dataset-plugin.md)):
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { voidStages, schemaOrgPartitionMergePlugin } from '@lde/pipeline-void';
|
|
83
|
+
import { Pipeline, provenancePlugin } from '@lde/pipeline';
|
|
84
|
+
|
|
85
|
+
await new Pipeline({
|
|
86
|
+
stages: await voidStages(), // plain, no namespace options
|
|
87
|
+
plugins: [schemaOrgPartitionMergePlugin(), provenancePlugin()],
|
|
88
|
+
// …
|
|
89
|
+
}).run();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use `namespacePartitionMergePlugin(aliases)` for namespaces other than schema.org. The transform streams — it buffers only partition quads (bounded by the summary), passing everything else straight through. Datasets typically use a single schema.org namespace, so within one dataset there is one variant per class and every count stays exact; a dataset that mixes both namespaces on one property has its `void:distinctObjects` summed (an over-count for shared objects) rather than deduped.
|
|
93
|
+
|
|
94
|
+
> This plugin does more than rename IRIs: rewriting the `void:class` objects alone would still leave two `void:classPartition` nodes for one class. If you only need a blanket namespace rewrite over a dataset’s own quads (not a VoID partition merge) — for example when mapping instance data to an application profile — use the generic [`schemaOrgNormalizationPlugin` / `namespaceNormalizationPlugin`](../pipeline) from `@lde/pipeline` instead.
|
|
95
|
+
|
|
75
96
|
## Stage transforms
|
|
76
97
|
|
|
77
98
|
A VoID stage decorates its reader’s output with a `QuadTransform<ReaderContext>` attached as data (see [@lde/pipeline](../pipeline)’s extension model and [ADR 2](../../docs/decisions/0002-unify-pipeline-extension-on-quad-transforms.md)). It runs once per reader call and may fire its own SPARQL queries against the `distribution` in scope — so write it to accept being called more than once: a global stage calls it once over the complete output, a per-class stage with batching enabled once per batch (one class at `batchSize: 1`).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { Stage, NotSupported } from '@lde/pipeline';
|
|
2
|
-
export type { AttachedReader, ReaderContext, QuadTransform, } from '@lde/pipeline';
|
|
2
|
+
export type { AttachedReader, NamespaceAlias, ReaderContext, QuadTransform, } from '@lde/pipeline';
|
|
3
3
|
export * from './stage.js';
|
|
4
|
+
export * from './partitionMerge.js';
|
|
5
|
+
export * from './partitionIri.js';
|
|
4
6
|
export * from './vocabularyTransform.js';
|
|
5
7
|
export * from './uriSpaceTransform.js';
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EACV,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each partition kind's IRI prefix and the ordered SPARQL string expressions
|
|
3
|
+
* whose MD5 hash keys it. The queries must bind the standard component
|
|
4
|
+
* variables (`?class`, `?p`, `?dt`, `?objectClass`, `?lang`).
|
|
5
|
+
*/
|
|
6
|
+
declare const PARTITION_KINDS: {
|
|
7
|
+
readonly class: readonly ["STR(?class)"];
|
|
8
|
+
readonly 'class-property': readonly ["STR(?class)", "STR(?p)"];
|
|
9
|
+
readonly datatype: readonly ["STR(?class)", "STR(?p)", "STR(?dt)"];
|
|
10
|
+
readonly 'object-class': readonly ["STR(?class)", "STR(?p)", "STR(?objectClass)"];
|
|
11
|
+
readonly language: readonly ["STR(?class)", "STR(?p)", "?lang"];
|
|
12
|
+
};
|
|
13
|
+
/** A VoID partition kind; also its IRI prefix. */
|
|
14
|
+
export type PartitionKind = keyof typeof PARTITION_KINDS;
|
|
15
|
+
/**
|
|
16
|
+
* Mint a partition IRI from its canonical component *string values* (the class
|
|
17
|
+
* IRI, property IRI, datatype IRI, or language tag — the `STR()` forms the
|
|
18
|
+
* SPARQL side concatenates). The TypeScript counterpart of the query minting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mintPartitionIri(datasetIri: string, kind: PartitionKind, components: readonly string[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Replace every `#mint:<kind>#` marker in a query with the SPARQL value
|
|
23
|
+
* expression that mints that kind's partition IRI, e.g.
|
|
24
|
+
* `URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?class))))`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function substituteMintMarkers(query: string): string;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=partitionIri.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partitionIri.d.ts","sourceRoot":"","sources":["../src/partitionIri.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,QAAA,MAAM,eAAe;;;;;;CAOX,CAAC;AAEX,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,eAAe,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,EACnB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAY3D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for the VoID partition-IRI scheme
|
|
4
|
+
* (`<dataset>/.well-known/void#<prefix>-<MD5(component…)>`), shared by two
|
|
5
|
+
* derivations that must agree byte-for-byte:
|
|
6
|
+
*
|
|
7
|
+
* - {@link mintPartitionIri} mints an IRI in TypeScript (used by the
|
|
8
|
+
* partition-merge transform to re-key a partition to its canonical form);
|
|
9
|
+
* - {@link substituteMintMarkers} generates the equivalent SPARQL `BIND` value
|
|
10
|
+
* expression, injected into the analysis queries in place of `#mint:<kind>#`.
|
|
11
|
+
*
|
|
12
|
+
* Because both come from {@link PARTITION_KINDS}, a change to the scheme (a
|
|
13
|
+
* prefix, a component, its order) updates the query minting and the TS minting
|
|
14
|
+
* together — they cannot silently diverge.
|
|
15
|
+
*/
|
|
16
|
+
/** The `/.well-known/void#` path under a dataset IRI where partitions live. */
|
|
17
|
+
const WELL_KNOWN_VOID = '/.well-known/void#';
|
|
18
|
+
/**
|
|
19
|
+
* Each partition kind's IRI prefix and the ordered SPARQL string expressions
|
|
20
|
+
* whose MD5 hash keys it. The queries must bind the standard component
|
|
21
|
+
* variables (`?class`, `?p`, `?dt`, `?objectClass`, `?lang`).
|
|
22
|
+
*/
|
|
23
|
+
const PARTITION_KINDS = {
|
|
24
|
+
class: ['STR(?class)'],
|
|
25
|
+
'class-property': ['STR(?class)', 'STR(?p)'],
|
|
26
|
+
datatype: ['STR(?class)', 'STR(?p)', 'STR(?dt)'],
|
|
27
|
+
'object-class': ['STR(?class)', 'STR(?p)', 'STR(?objectClass)'],
|
|
28
|
+
// ?lang is a plain string literal, so it is hashed without STR().
|
|
29
|
+
language: ['STR(?class)', 'STR(?p)', '?lang'],
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Mint a partition IRI from its canonical component *string values* (the class
|
|
33
|
+
* IRI, property IRI, datatype IRI, or language tag — the `STR()` forms the
|
|
34
|
+
* SPARQL side concatenates). The TypeScript counterpart of the query minting.
|
|
35
|
+
*/
|
|
36
|
+
export function mintPartitionIri(datasetIri, kind, components) {
|
|
37
|
+
const hash = createHash('md5').update(components.join('')).digest('hex');
|
|
38
|
+
return `${datasetIri}${WELL_KNOWN_VOID}${kind}-${hash}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Replace every `#mint:<kind>#` marker in a query with the SPARQL value
|
|
42
|
+
* expression that mints that kind's partition IRI, e.g.
|
|
43
|
+
* `URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?class))))`.
|
|
44
|
+
*/
|
|
45
|
+
export function substituteMintMarkers(query) {
|
|
46
|
+
return query.replace(/#mint:([a-z-]+)#/g, (_match, kind) => {
|
|
47
|
+
const components = PARTITION_KINDS[kind];
|
|
48
|
+
if (components === undefined) {
|
|
49
|
+
throw new Error(`Unknown partition kind in #mint:# marker: ${kind}`);
|
|
50
|
+
}
|
|
51
|
+
const hashed = components.length === 1
|
|
52
|
+
? components[0]
|
|
53
|
+
: `CONCAT(${components.join(', ')})`;
|
|
54
|
+
return `URI(CONCAT(STR(?dataset), "${WELL_KNOWN_VOID}${kind}-", MD5(${hashed})))`;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type BeforeDatasetWriteContext, type NamespaceAlias, type PipelinePlugin, type QuadTransform } from '@lde/pipeline';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link QuadTransform} for the {@link PipelinePlugin.beforeDatasetWrite} hook
|
|
4
|
+
* that merges the `void:classPartition` / `void:propertyPartition` subtrees of
|
|
5
|
+
* namespace-alias variants (e.g. `http://schema.org/CreativeWork` and
|
|
6
|
+
* `https://schema.org/CreativeWork`) into one partition per canonical
|
|
7
|
+
* class/property.
|
|
8
|
+
*
|
|
9
|
+
* VoID partitions are keyed by an opaque `MD5(class[, property[, …]])` IRI, so
|
|
10
|
+
* two namespace variants produce two partition nodes that, once the class IRI is
|
|
11
|
+
* canonicalized, describe the same class. Seeing a whole dataset's output at
|
|
12
|
+
* once, this transform re-mints every partition IRI from its **canonical** key
|
|
13
|
+
* components via {@link mintPartitionIri} — the single source of truth the
|
|
14
|
+
* queries' SPARQL minting is also generated from — collapses the duplicates,
|
|
15
|
+
* and sums their numeric measures.
|
|
16
|
+
*
|
|
17
|
+
* It streams: only partition quads are buffered (bounded by the summary — a
|
|
18
|
+
* handful of classes × properties, not the dataset), and every other quad
|
|
19
|
+
* passes straight through.
|
|
20
|
+
*
|
|
21
|
+
* Datasets typically use a single schema.org namespace, so within one dataset
|
|
22
|
+
* there is one variant per class and the transform merely renames and re-keys —
|
|
23
|
+
* `void:distinctObjects` and every count stay exact. A dataset that genuinely
|
|
24
|
+
* mixes both namespaces on the same property collapses the variants by summing,
|
|
25
|
+
* which over-counts shared distinct objects; this is not optimized for.
|
|
26
|
+
*
|
|
27
|
+
* With no aliases configured the transform is a no-op.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mergeNamespaceVariants(namespaceAliases: readonly NamespaceAlias[]): QuadTransform<BeforeDatasetWriteContext>;
|
|
30
|
+
/**
|
|
31
|
+
* A {@link PipelinePlugin} that canonicalizes schema.org namespace variants in
|
|
32
|
+
* the VoID output — rewriting `http://schema.org/` to `https://schema.org/` —
|
|
33
|
+
* _and_ merges the duplicate partition nodes the two variants produced. Runs on
|
|
34
|
+
* the whole dataset's output via {@link PipelinePlugin.beforeDatasetWrite}, so
|
|
35
|
+
* the analysis queries stay unaware of namespace aliases.
|
|
36
|
+
*
|
|
37
|
+
* This does more than a plain namespace rewrite: rewriting the `void:class`
|
|
38
|
+
* objects alone would leave two `void:classPartition` nodes for the same class.
|
|
39
|
+
* For a non-VoID, blanket namespace rewrite (e.g. mapping instance data to an
|
|
40
|
+
* application profile), use `schemaOrgNormalizationPlugin` from `@lde/pipeline`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function schemaOrgPartitionMergePlugin(): PipelinePlugin;
|
|
43
|
+
/**
|
|
44
|
+
* A {@link PipelinePlugin} that canonicalizes the given namespace aliases in the
|
|
45
|
+
* VoID output and merges the duplicate partition nodes their variants produced.
|
|
46
|
+
* Generic form of {@link schemaOrgPartitionMergePlugin}.
|
|
47
|
+
*
|
|
48
|
+
* Required stages: re-keying a datatype/language/object-class partition walks up
|
|
49
|
+
* its `cp → pp → dp` chain, reading `void:class` and `void:property` that
|
|
50
|
+
* `classPartitions` and `classPropertySubjects` emit. Use this plugin with a
|
|
51
|
+
* stage set that includes both (as {@link voidStages} does); without them a
|
|
52
|
+
* void-ext partition cannot be re-keyed and its alias variants ship unmerged.
|
|
53
|
+
*/
|
|
54
|
+
export declare function namespacePartitionMergePlugin(namespaceAliases: readonly NamespaceAlias[]): PipelinePlugin;
|
|
55
|
+
//# sourceMappingURL=partitionMerge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partitionMerge.d.ts","sourceRoot":"","sources":["../src/partitionMerge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAqEvB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,SAAS,cAAc,EAAE,GAC1C,aAAa,CAAC,yBAAyB,CAAC,CAgB1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,IAAI,cAAc,CAI9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,SAAS,cAAc,EAAE,GAC1C,cAAc,CAKhB"}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { canonicalizeIri, } from '@lde/pipeline';
|
|
2
|
+
import { DataFactory } from 'n3';
|
|
3
|
+
import { mintPartitionIri } from './partitionIri.js';
|
|
4
|
+
const { namedNode, literal, quad } = DataFactory;
|
|
5
|
+
const VOID = 'http://rdfs.org/ns/void#';
|
|
6
|
+
const VOID_EXT = 'http://ldf.fi/void-ext#';
|
|
7
|
+
const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
|
|
8
|
+
const SCHEMA_HTTP = 'http://schema.org/';
|
|
9
|
+
const SCHEMA_HTTPS = 'https://schema.org/';
|
|
10
|
+
const VOID_CLASS = `${VOID}class`;
|
|
11
|
+
const VOID_PROPERTY = `${VOID}property`;
|
|
12
|
+
const VOID_ENTITIES = `${VOID}entities`;
|
|
13
|
+
const VOID_TRIPLES = `${VOID}triples`;
|
|
14
|
+
const VOID_DISTINCT_OBJECTS = `${VOID}distinctObjects`;
|
|
15
|
+
const VOID_CLASS_PARTITION = `${VOID}classPartition`;
|
|
16
|
+
const VOID_PROPERTY_PARTITION = `${VOID}propertyPartition`;
|
|
17
|
+
const VOIDEXT_DATATYPE = `${VOID_EXT}datatype`;
|
|
18
|
+
const VOIDEXT_LANGUAGE = `${VOID_EXT}language`;
|
|
19
|
+
const VOIDEXT_DATATYPE_PARTITION = `${VOID_EXT}datatypePartition`;
|
|
20
|
+
const VOIDEXT_OBJECTCLASS_PARTITION = `${VOID_EXT}objectClassPartition`;
|
|
21
|
+
const VOIDEXT_LANGUAGE_PARTITION = `${VOID_EXT}languagePartition`;
|
|
22
|
+
/**
|
|
23
|
+
* Integer-literal measures summed when partitions collapse.
|
|
24
|
+
* `void:distinctObjects` is included: a dataset that uses a single schema.org
|
|
25
|
+
* namespace has one variant per class, so there is nothing to combine and the
|
|
26
|
+
* count is unchanged; only a dataset that mixes both namespaces on the same
|
|
27
|
+
* property (rare) sums two distinct-object counts — an over-count we accept
|
|
28
|
+
* rather than optimize for.
|
|
29
|
+
*/
|
|
30
|
+
const NUMERIC_MEASURES = new Set([
|
|
31
|
+
VOID_ENTITIES,
|
|
32
|
+
VOID_TRIPLES,
|
|
33
|
+
VOID_DISTINCT_OBJECTS,
|
|
34
|
+
]);
|
|
35
|
+
/** Structural links whose object is a child partition node. */
|
|
36
|
+
const CHILD_LINKS = new Set([
|
|
37
|
+
VOID_CLASS_PARTITION,
|
|
38
|
+
VOID_PROPERTY_PARTITION,
|
|
39
|
+
VOIDEXT_DATATYPE_PARTITION,
|
|
40
|
+
VOIDEXT_OBJECTCLASS_PARTITION,
|
|
41
|
+
VOIDEXT_LANGUAGE_PARTITION,
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Every predicate that describes a partition. A quad with one of these
|
|
45
|
+
* predicates is buffered for merging; every other quad streams through
|
|
46
|
+
* untouched, so the transform's memory is bounded by the VoID summary, not the
|
|
47
|
+
* dataset.
|
|
48
|
+
*/
|
|
49
|
+
const PARTITION_VOCABULARY = new Set([
|
|
50
|
+
VOID_CLASS,
|
|
51
|
+
VOID_PROPERTY,
|
|
52
|
+
VOIDEXT_DATATYPE,
|
|
53
|
+
VOIDEXT_LANGUAGE,
|
|
54
|
+
// Derived, not re-listed: a measure added to NUMERIC_MEASURES must also be
|
|
55
|
+
// buffered here, or it would stream through un-summed.
|
|
56
|
+
...NUMERIC_MEASURES,
|
|
57
|
+
...CHILD_LINKS,
|
|
58
|
+
]);
|
|
59
|
+
/** `void:class` / `void:property` objects are IRIs subject to canonicalization. */
|
|
60
|
+
const CANONICALIZED_OBJECT_PREDICATES = new Set([VOID_CLASS, VOID_PROPERTY]);
|
|
61
|
+
/**
|
|
62
|
+
* A {@link QuadTransform} for the {@link PipelinePlugin.beforeDatasetWrite} hook
|
|
63
|
+
* that merges the `void:classPartition` / `void:propertyPartition` subtrees of
|
|
64
|
+
* namespace-alias variants (e.g. `http://schema.org/CreativeWork` and
|
|
65
|
+
* `https://schema.org/CreativeWork`) into one partition per canonical
|
|
66
|
+
* class/property.
|
|
67
|
+
*
|
|
68
|
+
* VoID partitions are keyed by an opaque `MD5(class[, property[, …]])` IRI, so
|
|
69
|
+
* two namespace variants produce two partition nodes that, once the class IRI is
|
|
70
|
+
* canonicalized, describe the same class. Seeing a whole dataset's output at
|
|
71
|
+
* once, this transform re-mints every partition IRI from its **canonical** key
|
|
72
|
+
* components via {@link mintPartitionIri} — the single source of truth the
|
|
73
|
+
* queries' SPARQL minting is also generated from — collapses the duplicates,
|
|
74
|
+
* and sums their numeric measures.
|
|
75
|
+
*
|
|
76
|
+
* It streams: only partition quads are buffered (bounded by the summary — a
|
|
77
|
+
* handful of classes × properties, not the dataset), and every other quad
|
|
78
|
+
* passes straight through.
|
|
79
|
+
*
|
|
80
|
+
* Datasets typically use a single schema.org namespace, so within one dataset
|
|
81
|
+
* there is one variant per class and the transform merely renames and re-keys —
|
|
82
|
+
* `void:distinctObjects` and every count stay exact. A dataset that genuinely
|
|
83
|
+
* mixes both namespaces on the same property collapses the variants by summing,
|
|
84
|
+
* which over-counts shared distinct objects; this is not optimized for.
|
|
85
|
+
*
|
|
86
|
+
* With no aliases configured the transform is a no-op.
|
|
87
|
+
*/
|
|
88
|
+
export function mergeNamespaceVariants(namespaceAliases) {
|
|
89
|
+
if (namespaceAliases.length === 0) {
|
|
90
|
+
return (quads) => quads;
|
|
91
|
+
}
|
|
92
|
+
return async function* (quads, { dataset }) {
|
|
93
|
+
const datasetIri = dataset.iri.toString();
|
|
94
|
+
const partitionQuads = [];
|
|
95
|
+
for await (const q of quads) {
|
|
96
|
+
if (PARTITION_VOCABULARY.has(q.predicate.value)) {
|
|
97
|
+
partitionQuads.push(q);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
yield q;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
yield* mergeBuffered(partitionQuads, datasetIri, namespaceAliases);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A {@link PipelinePlugin} that canonicalizes schema.org namespace variants in
|
|
108
|
+
* the VoID output — rewriting `http://schema.org/` to `https://schema.org/` —
|
|
109
|
+
* _and_ merges the duplicate partition nodes the two variants produced. Runs on
|
|
110
|
+
* the whole dataset's output via {@link PipelinePlugin.beforeDatasetWrite}, so
|
|
111
|
+
* the analysis queries stay unaware of namespace aliases.
|
|
112
|
+
*
|
|
113
|
+
* This does more than a plain namespace rewrite: rewriting the `void:class`
|
|
114
|
+
* objects alone would leave two `void:classPartition` nodes for the same class.
|
|
115
|
+
* For a non-VoID, blanket namespace rewrite (e.g. mapping instance data to an
|
|
116
|
+
* application profile), use `schemaOrgNormalizationPlugin` from `@lde/pipeline`.
|
|
117
|
+
*/
|
|
118
|
+
export function schemaOrgPartitionMergePlugin() {
|
|
119
|
+
return namespacePartitionMergePlugin([
|
|
120
|
+
{ canonical: SCHEMA_HTTPS, alias: SCHEMA_HTTP },
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A {@link PipelinePlugin} that canonicalizes the given namespace aliases in the
|
|
125
|
+
* VoID output and merges the duplicate partition nodes their variants produced.
|
|
126
|
+
* Generic form of {@link schemaOrgPartitionMergePlugin}.
|
|
127
|
+
*
|
|
128
|
+
* Required stages: re-keying a datatype/language/object-class partition walks up
|
|
129
|
+
* its `cp → pp → dp` chain, reading `void:class` and `void:property` that
|
|
130
|
+
* `classPartitions` and `classPropertySubjects` emit. Use this plugin with a
|
|
131
|
+
* stage set that includes both (as {@link voidStages} does); without them a
|
|
132
|
+
* void-ext partition cannot be re-keyed and its alias variants ship unmerged.
|
|
133
|
+
*/
|
|
134
|
+
export function namespacePartitionMergePlugin(namespaceAliases) {
|
|
135
|
+
return {
|
|
136
|
+
name: 'void-namespace-partition-merge',
|
|
137
|
+
beforeDatasetWrite: mergeNamespaceVariants(namespaceAliases),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function* mergeBuffered(buffered, datasetIri, namespaceAliases) {
|
|
141
|
+
const nodes = indexNodes(buffered);
|
|
142
|
+
const remap = buildRemap(nodes, datasetIri, namespaceAliases);
|
|
143
|
+
const measureSums = new Map();
|
|
144
|
+
const emitted = new Set();
|
|
145
|
+
for (const original of buffered) {
|
|
146
|
+
const subject = remapTerm(original.subject, remap);
|
|
147
|
+
let object = remapTerm(original.object, remap);
|
|
148
|
+
// Canonicalize a void:class/void:property object only when its own
|
|
149
|
+
// partition node is being re-keyed. The top-level property partitions from
|
|
150
|
+
// entity-properties.rq are never merged (their parent is the dataset, not a
|
|
151
|
+
// class), so they keep the source namespace — consumers can still see which
|
|
152
|
+
// namespace the dataset actually uses (see ADR 7).
|
|
153
|
+
if (remap.has(original.subject.value)) {
|
|
154
|
+
object = canonicalizeObject(object, original.predicate.value, namespaceAliases);
|
|
155
|
+
}
|
|
156
|
+
const rewritten = quad(subject, original.predicate, object, original.graph);
|
|
157
|
+
if (NUMERIC_MEASURES.has(original.predicate.value)) {
|
|
158
|
+
accumulateMeasure(measureSums, rewritten);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const key = quadKey(rewritten);
|
|
162
|
+
if (!emitted.has(key)) {
|
|
163
|
+
emitted.add(key);
|
|
164
|
+
yield rewritten;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const sum of measureSums.values()) {
|
|
168
|
+
yield reconstructMeasure(sum);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function indexNodes(buffered) {
|
|
172
|
+
const nodes = new Map();
|
|
173
|
+
const nodeOf = (value) => {
|
|
174
|
+
let node = nodes.get(value);
|
|
175
|
+
if (!node) {
|
|
176
|
+
node = { values: new Map() };
|
|
177
|
+
nodes.set(value, node);
|
|
178
|
+
}
|
|
179
|
+
return node;
|
|
180
|
+
};
|
|
181
|
+
for (const q of buffered) {
|
|
182
|
+
if (CHILD_LINKS.has(q.predicate.value) &&
|
|
183
|
+
q.object.termType === 'NamedNode') {
|
|
184
|
+
nodeOf(q.object.value).incomingLink = {
|
|
185
|
+
predicate: q.predicate.value,
|
|
186
|
+
parent: q.subject.value,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
nodeOf(q.subject.value).values.set(q.predicate.value, q.object);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return nodes;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build a raw-IRI → canonical-IRI map for every partition node whose canonical
|
|
197
|
+
* key differs from its current IRI.
|
|
198
|
+
*/
|
|
199
|
+
function buildRemap(nodes, datasetIri, namespaceAliases) {
|
|
200
|
+
const remap = new Map();
|
|
201
|
+
for (const [iri, node] of nodes) {
|
|
202
|
+
const canonicalIri = canonicalPartitionIri(node, nodes, datasetIri, namespaceAliases);
|
|
203
|
+
if (canonicalIri !== undefined && canonicalIri !== iri) {
|
|
204
|
+
remap.set(iri, canonicalIri);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return remap;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The canonical partition IRI for a node, or `undefined` if the node is not a
|
|
211
|
+
* partition (no incoming structural link). Replicates the queries' minting:
|
|
212
|
+
* `<dataset>/.well-known/void#<prefix>-<MD5(STR(component)…)>`.
|
|
213
|
+
*/
|
|
214
|
+
function canonicalPartitionIri(node, nodes, datasetIri, namespaceAliases) {
|
|
215
|
+
const link = node.incomingLink;
|
|
216
|
+
if (link === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
const canon = (value) => canonicalizeIri(value, namespaceAliases);
|
|
219
|
+
const classOf = (partition) => nodes.get(partition)?.values.get(VOID_CLASS)?.value;
|
|
220
|
+
switch (link.predicate) {
|
|
221
|
+
case VOID_CLASS_PARTITION: {
|
|
222
|
+
const klass = node.values.get(VOID_CLASS)?.value;
|
|
223
|
+
return klass
|
|
224
|
+
? mintPartitionIri(datasetIri, 'class', [canon(klass)])
|
|
225
|
+
: undefined;
|
|
226
|
+
}
|
|
227
|
+
case VOID_PROPERTY_PARTITION: {
|
|
228
|
+
const klass = classOf(link.parent);
|
|
229
|
+
const property = node.values.get(VOID_PROPERTY)?.value;
|
|
230
|
+
return klass && property
|
|
231
|
+
? mintPartitionIri(datasetIri, 'class-property', [
|
|
232
|
+
canon(klass),
|
|
233
|
+
canon(property),
|
|
234
|
+
])
|
|
235
|
+
: undefined;
|
|
236
|
+
}
|
|
237
|
+
case VOIDEXT_DATATYPE_PARTITION: {
|
|
238
|
+
const [klass, property] = classProperty(link.parent, nodes);
|
|
239
|
+
const datatype = node.values.get(VOIDEXT_DATATYPE)?.value;
|
|
240
|
+
return klass && property && datatype
|
|
241
|
+
? mintPartitionIri(datasetIri, 'datatype', [
|
|
242
|
+
canon(klass),
|
|
243
|
+
canon(property),
|
|
244
|
+
datatype,
|
|
245
|
+
])
|
|
246
|
+
: undefined;
|
|
247
|
+
}
|
|
248
|
+
case VOIDEXT_OBJECTCLASS_PARTITION: {
|
|
249
|
+
const [klass, property] = classProperty(link.parent, nodes);
|
|
250
|
+
const objectClass = node.values.get(VOID_CLASS)?.value;
|
|
251
|
+
return klass && property && objectClass
|
|
252
|
+
? mintPartitionIri(datasetIri, 'object-class', [
|
|
253
|
+
canon(klass),
|
|
254
|
+
canon(property),
|
|
255
|
+
canon(objectClass),
|
|
256
|
+
])
|
|
257
|
+
: undefined;
|
|
258
|
+
}
|
|
259
|
+
case VOIDEXT_LANGUAGE_PARTITION: {
|
|
260
|
+
const [klass, property] = classProperty(link.parent, nodes);
|
|
261
|
+
const language = node.values.get(VOIDEXT_LANGUAGE)?.value;
|
|
262
|
+
return klass && property && language !== undefined
|
|
263
|
+
? mintPartitionIri(datasetIri, 'language', [
|
|
264
|
+
canon(klass),
|
|
265
|
+
canon(property),
|
|
266
|
+
language,
|
|
267
|
+
])
|
|
268
|
+
: undefined;
|
|
269
|
+
}
|
|
270
|
+
default:
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** The (class, property) of a property partition, following it up to its class partition. */
|
|
275
|
+
function classProperty(propertyPartition, nodes) {
|
|
276
|
+
const node = nodes.get(propertyPartition);
|
|
277
|
+
const property = node?.values.get(VOID_PROPERTY)?.value;
|
|
278
|
+
const classPartition = node?.incomingLink?.parent;
|
|
279
|
+
const klass = classPartition
|
|
280
|
+
? nodes.get(classPartition)?.values.get(VOID_CLASS)?.value
|
|
281
|
+
: undefined;
|
|
282
|
+
return [klass, property];
|
|
283
|
+
}
|
|
284
|
+
function remapTerm(term, remap) {
|
|
285
|
+
if (term.termType === 'NamedNode') {
|
|
286
|
+
const canonical = remap.get(term.value);
|
|
287
|
+
if (canonical !== undefined)
|
|
288
|
+
return namedNode(canonical);
|
|
289
|
+
}
|
|
290
|
+
return term;
|
|
291
|
+
}
|
|
292
|
+
function canonicalizeObject(object, predicate, namespaceAliases) {
|
|
293
|
+
if (CANONICALIZED_OBJECT_PREDICATES.has(predicate) &&
|
|
294
|
+
object.termType === 'NamedNode') {
|
|
295
|
+
const canonical = canonicalizeIri(object.value, namespaceAliases);
|
|
296
|
+
if (canonical !== object.value)
|
|
297
|
+
return namedNode(canonical);
|
|
298
|
+
}
|
|
299
|
+
return object;
|
|
300
|
+
}
|
|
301
|
+
function accumulateMeasure(sums, measure) {
|
|
302
|
+
const key = `${measure.subject.value} ${measure.predicate.value} ${measure.graph.value}`;
|
|
303
|
+
const existing = sums.get(key);
|
|
304
|
+
if (existing === undefined) {
|
|
305
|
+
sums.set(key, {
|
|
306
|
+
subject: measure.subject,
|
|
307
|
+
predicate: measure.predicate,
|
|
308
|
+
graph: measure.graph,
|
|
309
|
+
datatype: measure.object.termType === 'Literal'
|
|
310
|
+
? measure.object.datatype.value
|
|
311
|
+
: XSD_INTEGER,
|
|
312
|
+
total: parseInteger(measure.object),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
existing.total += parseInteger(measure.object);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function parseInteger(object) {
|
|
320
|
+
if (object.termType === 'Literal' && /^[+-]?\d+$/.test(object.value)) {
|
|
321
|
+
return BigInt(object.value);
|
|
322
|
+
}
|
|
323
|
+
return 0n;
|
|
324
|
+
}
|
|
325
|
+
function reconstructMeasure(sum) {
|
|
326
|
+
return quad(sum.subject, sum.predicate, literal(sum.total.toString(), namedNode(sum.datatype)), sum.graph);
|
|
327
|
+
}
|
|
328
|
+
function quadKey(q) {
|
|
329
|
+
return [
|
|
330
|
+
q.subject.value,
|
|
331
|
+
q.predicate.value,
|
|
332
|
+
termKey(q.object),
|
|
333
|
+
q.graph.value,
|
|
334
|
+
].join(' ');
|
|
335
|
+
}
|
|
336
|
+
function termKey(term) {
|
|
337
|
+
if (term.termType === 'Literal') {
|
|
338
|
+
return `"${term.value}"^^${term.datatype.value}@${term.language}`;
|
|
339
|
+
}
|
|
340
|
+
return term.value;
|
|
341
|
+
}
|
package/dist/stage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAKL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"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"}
|
package/dist/stage.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolve, dirname } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { withVocabularies, defaultVocabularies, } from './vocabularyTransform.js';
|
|
6
6
|
import { withUriSpaces } from './uriSpaceTransform.js';
|
|
7
|
+
import { substituteMintMarkers } from './partitionIri.js';
|
|
7
8
|
const queriesDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'queries');
|
|
8
9
|
/**
|
|
9
10
|
* Stable names for every VoID stage, equal to the underlying query filename.
|
|
@@ -31,7 +32,7 @@ export const VOID_STAGE_NAMES = {
|
|
|
31
32
|
objectUriSpace: 'object-uri-space.rq',
|
|
32
33
|
};
|
|
33
34
|
async function createVoidStage(filename, options) {
|
|
34
|
-
const query = await readQueryFile(resolve(queriesDir, filename));
|
|
35
|
+
const query = substituteMintMarkers(await readQueryFile(resolve(queriesDir, filename)));
|
|
35
36
|
const reader = {
|
|
36
37
|
reader: new SparqlConstructReader({ query }),
|
|
37
38
|
transform: options?.transform,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/pipeline-void",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "VOiD (Vocabulary of Interlinked Datasets) statistical analysis for RDF datasets",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@lde/dataset": "^0.7.8",
|
|
37
|
-
"@lde/pipeline": "^0.
|
|
37
|
+
"@lde/pipeline": "^0.34.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -3,17 +3,17 @@ PREFIX void: <http://rdfs.org/ns/void#>
|
|
|
3
3
|
CONSTRUCT {
|
|
4
4
|
?dataset a void:Dataset ;
|
|
5
5
|
void:classPartition ?classPartition .
|
|
6
|
-
?classPartition void:class ?
|
|
6
|
+
?classPartition void:class ?class ;
|
|
7
7
|
void:entities ?entities .
|
|
8
8
|
}
|
|
9
9
|
WHERE {
|
|
10
10
|
{
|
|
11
|
-
SELECT (COUNT(?
|
|
11
|
+
SELECT (COUNT(?class) AS ?entities) ?class {
|
|
12
12
|
#subjectFilter#
|
|
13
|
-
?s a ?
|
|
13
|
+
?s a ?class .
|
|
14
14
|
}
|
|
15
|
-
GROUP BY ?
|
|
15
|
+
GROUP BY ?class
|
|
16
16
|
}
|
|
17
|
-
BIND(
|
|
17
|
+
BIND(#mint:class# AS ?classPartition)
|
|
18
18
|
}
|
|
19
19
|
LIMIT 10000
|
|
@@ -21,7 +21,7 @@ WHERE {
|
|
|
21
21
|
}
|
|
22
22
|
GROUP BY ?class ?p
|
|
23
23
|
}
|
|
24
|
-
BIND(
|
|
25
|
-
BIND(
|
|
24
|
+
BIND(#mint:class# AS ?classPartition)
|
|
25
|
+
BIND(#mint:class-property# AS ?propertyPartition)
|
|
26
26
|
}
|
|
27
27
|
LIMIT 100000
|
|
@@ -22,7 +22,7 @@ WHERE {
|
|
|
22
22
|
}
|
|
23
23
|
GROUP BY ?class ?p ?dt
|
|
24
24
|
}
|
|
25
|
-
BIND(
|
|
26
|
-
BIND(
|
|
25
|
+
BIND(#mint:class-property# AS ?propertyPartition)
|
|
26
|
+
BIND(#mint:datatype# AS ?datatypePartition)
|
|
27
27
|
}
|
|
28
28
|
LIMIT 100000
|
|
@@ -24,7 +24,7 @@ WHERE {
|
|
|
24
24
|
}
|
|
25
25
|
GROUP BY ?class ?p ?lang
|
|
26
26
|
}
|
|
27
|
-
BIND(
|
|
28
|
-
BIND(
|
|
27
|
+
BIND(#mint:class-property# AS ?propertyPartition)
|
|
28
|
+
BIND(#mint:language# AS ?languagePartition)
|
|
29
29
|
}
|
|
30
30
|
LIMIT 100000
|
|
@@ -22,7 +22,7 @@ WHERE {
|
|
|
22
22
|
}
|
|
23
23
|
GROUP BY ?class ?p ?objectClass
|
|
24
24
|
}
|
|
25
|
-
BIND(
|
|
26
|
-
BIND(
|
|
25
|
+
BIND(#mint:class-property# AS ?propertyPartition)
|
|
26
|
+
BIND(#mint:object-class# AS ?objectClassPartition)
|
|
27
27
|
}
|
|
28
28
|
LIMIT 100000
|