eyeling 1.27.9 → 1.28.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 +13 -3
- package/dist/browser/eyeling.browser.js +164 -43
- package/eyeling.js +164 -43
- package/index.d.ts +3 -2
- package/lib/engine.js +22 -18
- package/lib/rdfjs.js +142 -25
- package/notes/rdfjs-integration.md +378 -0
- package/package.json +1 -1
- package/test/api.test.js +75 -20
package/lib/rdfjs.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
XSD_NS,
|
|
12
|
+
LOG_NS,
|
|
12
13
|
Literal: InternalLiteral,
|
|
13
14
|
Iri,
|
|
14
15
|
Blank,
|
|
@@ -19,6 +20,7 @@ const {
|
|
|
19
20
|
Triple,
|
|
20
21
|
Rule,
|
|
21
22
|
PrefixEnv,
|
|
23
|
+
annotateQuotedGraphTerm,
|
|
22
24
|
literalParts,
|
|
23
25
|
} = require('./prelude');
|
|
24
26
|
const { termToN3, tripleToN3 } = require('./printing');
|
|
@@ -322,6 +324,49 @@ function internalRdf12TermToRdfJs(term, factory, position) {
|
|
|
322
324
|
return internalTermToRdfJs(term, rdfFactory, position);
|
|
323
325
|
}
|
|
324
326
|
|
|
327
|
+
function isDefaultGraphTerm(term) {
|
|
328
|
+
return isRdfJsTerm(term) && term.termType === 'DefaultGraph';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function assertDefaultGraphForRdfJsQuadTerm(term, position) {
|
|
332
|
+
if (!isDefaultGraphTerm(term.graph)) {
|
|
333
|
+
throw new TypeError(`RDF/JS Quad terms with named graphs are not supported in ${position}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function rdfJsGraphNameToInternal(term, position = 'quad.graph') {
|
|
338
|
+
assertSupportedRdfJsTerm(term, position);
|
|
339
|
+
switch (term.termType) {
|
|
340
|
+
case 'NamedNode':
|
|
341
|
+
return new Iri(term.value);
|
|
342
|
+
case 'BlankNode':
|
|
343
|
+
return new Blank(`_:${term.value}`);
|
|
344
|
+
case 'DefaultGraph':
|
|
345
|
+
return null;
|
|
346
|
+
default:
|
|
347
|
+
throw new TypeError(`Invalid RDF graph termType ${term.termType}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function internalGraphNameToRdfJs(term, factory, position = 'graph') {
|
|
352
|
+
if (term instanceof Iri || term instanceof Blank) return internalTermToRdfJs(term, factory, position);
|
|
353
|
+
return unsupportedRdfJsTerm(term, position);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function graphKey(term) {
|
|
357
|
+
return `${term.termType}:${term.value}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function isInternalLogNameOfTriple(triple) {
|
|
361
|
+
return (
|
|
362
|
+
triple instanceof Triple &&
|
|
363
|
+
(triple.s instanceof Iri || triple.s instanceof Blank) &&
|
|
364
|
+
triple.p instanceof Iri &&
|
|
365
|
+
triple.p.value === LOG_NS + 'nameOf' &&
|
|
366
|
+
triple.o instanceof GraphTerm
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
325
370
|
function assertRdfJsQuadShape(subject, predicate, object, graph) {
|
|
326
371
|
if (!['NamedNode', 'BlankNode', 'Quad'].includes(subject.termType)) {
|
|
327
372
|
throw new TypeError(`Invalid RDF subject termType ${subject.termType}`);
|
|
@@ -350,13 +395,22 @@ function internalTripleToRdfJsQuadInGraph(triple, graph, factory) {
|
|
|
350
395
|
function internalTripleToRdfJsQuad(triple, factory) {
|
|
351
396
|
const rdfFactory = getDataFactory(factory);
|
|
352
397
|
return rdfFactory.quad(
|
|
353
|
-
|
|
398
|
+
internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject'),
|
|
354
399
|
internalTermToRdfJs(triple.p, rdfFactory, 'predicate'),
|
|
355
|
-
|
|
400
|
+
internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object'),
|
|
356
401
|
rdfFactory.defaultGraph(),
|
|
357
402
|
);
|
|
358
403
|
}
|
|
359
404
|
|
|
405
|
+
function internalTripleToRdfJsQuads(triple, factory) {
|
|
406
|
+
const rdfFactory = getDataFactory(factory);
|
|
407
|
+
if (isInternalLogNameOfTriple(triple)) {
|
|
408
|
+
const graph = internalGraphNameToRdfJs(triple.s, rdfFactory, 'graph');
|
|
409
|
+
return (triple.o.triples || []).map((inner) => internalTripleToRdfJsQuadInGraph(inner, graph, rdfFactory));
|
|
410
|
+
}
|
|
411
|
+
return [internalTripleToRdfJsQuad(triple, rdfFactory)];
|
|
412
|
+
}
|
|
413
|
+
|
|
360
414
|
function escapeStringForN3(value) {
|
|
361
415
|
return JSON.stringify(String(value));
|
|
362
416
|
}
|
|
@@ -388,7 +442,14 @@ function rdfJsTermToN3(term, position = 'term') {
|
|
|
388
442
|
return `${lexical}^^<${datatype}>`;
|
|
389
443
|
}
|
|
390
444
|
case 'Quad':
|
|
391
|
-
|
|
445
|
+
if (String(position).endsWith('.predicate') || position === 'quad.predicate' || position === 'predicate') {
|
|
446
|
+
throw new TypeError(`Quoted triple terms are not valid RDF predicates in ${position}`);
|
|
447
|
+
}
|
|
448
|
+
assertDefaultGraphForRdfJsQuadTerm(term, position);
|
|
449
|
+
return `{ ${rdfJsTermToN3(term.subject, `${position}.subject`)} ${rdfJsTermToN3(
|
|
450
|
+
term.predicate,
|
|
451
|
+
`${position}.predicate`,
|
|
452
|
+
)} ${rdfJsTermToN3(term.object, `${position}.object`)} . }`;
|
|
392
453
|
default:
|
|
393
454
|
throw new TypeError(`Unsupported RDF/JS termType ${JSON.stringify(term.termType)} in ${position}`);
|
|
394
455
|
}
|
|
@@ -408,8 +469,21 @@ function rdfJsTermToInternal(term, position = 'term') {
|
|
|
408
469
|
return new InternalLiteral(rdfJsTermToN3(term, position));
|
|
409
470
|
case 'DefaultGraph':
|
|
410
471
|
throw new TypeError(`DefaultGraph is not a valid standalone N3 term in ${position}`);
|
|
411
|
-
case 'Quad':
|
|
412
|
-
|
|
472
|
+
case 'Quad': {
|
|
473
|
+
if (String(position).endsWith('.predicate') || position === 'quad.predicate' || position === 'predicate') {
|
|
474
|
+
throw new TypeError(`Quoted triple terms are not valid RDF predicates in ${position}`);
|
|
475
|
+
}
|
|
476
|
+
assertDefaultGraphForRdfJsQuadTerm(term, position);
|
|
477
|
+
return annotateQuotedGraphTerm(
|
|
478
|
+
new GraphTerm([
|
|
479
|
+
new Triple(
|
|
480
|
+
rdfJsTermToInternal(term.subject, `${position}.subject`),
|
|
481
|
+
rdfJsTermToInternal(term.predicate, `${position}.predicate`),
|
|
482
|
+
rdfJsTermToInternal(term.object, `${position}.object`),
|
|
483
|
+
),
|
|
484
|
+
]),
|
|
485
|
+
);
|
|
486
|
+
}
|
|
413
487
|
default:
|
|
414
488
|
throw new TypeError(`Unsupported RDF/JS termType ${JSON.stringify(term.termType)} in ${position}`);
|
|
415
489
|
}
|
|
@@ -417,22 +491,58 @@ function rdfJsTermToInternal(term, position = 'term') {
|
|
|
417
491
|
|
|
418
492
|
function rdfJsQuadToInternalTriple(quad) {
|
|
419
493
|
if (!isRdfJsQuad(quad)) throw new TypeError('Expected an RDF/JS Quad');
|
|
420
|
-
|
|
421
|
-
throw new TypeError('Named graph quads are not supported by Eyeling input; use the default graph only');
|
|
422
|
-
}
|
|
423
|
-
return new Triple(
|
|
494
|
+
const inner = new Triple(
|
|
424
495
|
rdfJsTermToInternal(quad.subject, 'quad.subject'),
|
|
425
496
|
rdfJsTermToInternal(quad.predicate, 'quad.predicate'),
|
|
426
497
|
rdfJsTermToInternal(quad.object, 'quad.object'),
|
|
427
498
|
);
|
|
499
|
+
const graph = rdfJsGraphNameToInternal(quad.graph, 'quad.graph');
|
|
500
|
+
if (!graph) return inner;
|
|
501
|
+
return new Triple(graph, new Iri(LOG_NS + 'nameOf'), annotateQuotedGraphTerm(new GraphTerm([inner])));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function rdfJsQuadsToInternalTriples(quads) {
|
|
505
|
+
const out = [];
|
|
506
|
+
const namedGraphs = new Map();
|
|
507
|
+
|
|
508
|
+
for (const quad of quads) {
|
|
509
|
+
if (!isRdfJsQuad(quad)) throw new TypeError('Expected an RDF/JS Quad');
|
|
510
|
+
const inner = new Triple(
|
|
511
|
+
rdfJsTermToInternal(quad.subject, 'quad.subject'),
|
|
512
|
+
rdfJsTermToInternal(quad.predicate, 'quad.predicate'),
|
|
513
|
+
rdfJsTermToInternal(quad.object, 'quad.object'),
|
|
514
|
+
);
|
|
515
|
+
const graph = rdfJsGraphNameToInternal(quad.graph, 'quad.graph');
|
|
516
|
+
if (!graph) {
|
|
517
|
+
out.push(inner);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const key = graphKey(quad.graph);
|
|
522
|
+
let group = namedGraphs.get(key);
|
|
523
|
+
if (!group) {
|
|
524
|
+
group = { graph, triples: [] };
|
|
525
|
+
namedGraphs.set(key, group);
|
|
526
|
+
}
|
|
527
|
+
group.triples.push(inner);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
for (const { graph, triples } of namedGraphs.values()) {
|
|
531
|
+
out.push(new Triple(graph, new Iri(LOG_NS + 'nameOf'), annotateQuotedGraphTerm(new GraphTerm(triples))));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return out;
|
|
428
535
|
}
|
|
429
536
|
|
|
430
537
|
function rdfJsQuadToN3(quad) {
|
|
431
538
|
if (!isRdfJsQuad(quad)) throw new TypeError('Expected an RDF/JS Quad');
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
539
|
+
return tripleToN3(rdfJsQuadToInternalTriple(quad), PrefixEnv.newDefault());
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function rdfJsQuadsToN3(quads) {
|
|
543
|
+
return rdfJsQuadsToInternalTriples(quads)
|
|
544
|
+
.map((triple) => tripleToN3(triple, PrefixEnv.newDefault()))
|
|
545
|
+
.join('\n');
|
|
436
546
|
}
|
|
437
547
|
|
|
438
548
|
function collectIterableToArray(iterable, label) {
|
|
@@ -477,6 +587,11 @@ function getPrefixesText(input) {
|
|
|
477
587
|
return '';
|
|
478
588
|
}
|
|
479
589
|
|
|
590
|
+
function getN3Text(input) {
|
|
591
|
+
if (!isObject(input)) return '';
|
|
592
|
+
return typeof input.n3 === 'string' ? input.n3 : '';
|
|
593
|
+
}
|
|
594
|
+
|
|
480
595
|
function joinN3Sections(parts) {
|
|
481
596
|
return parts
|
|
482
597
|
.filter((part) => typeof part === 'string' && part.length > 0)
|
|
@@ -745,7 +860,7 @@ function appendSyncQuadFacts(doc, input) {
|
|
|
745
860
|
const quads = collectIterableToArray(quadsInfo.value, quadsInfo.label);
|
|
746
861
|
return {
|
|
747
862
|
...doc,
|
|
748
|
-
triples: doc.triples.concat(quads
|
|
863
|
+
triples: doc.triples.concat(rdfJsQuadsToInternalTriples(quads)),
|
|
749
864
|
};
|
|
750
865
|
}
|
|
751
866
|
|
|
@@ -755,7 +870,7 @@ async function appendAsyncQuadFacts(doc, input) {
|
|
|
755
870
|
const quads = await collectAsyncIterableToArray(quadsInfo.value, quadsInfo.label);
|
|
756
871
|
return {
|
|
757
872
|
...doc,
|
|
758
|
-
triples: doc.triples.concat(quads
|
|
873
|
+
triples: doc.triples.concat(rdfJsQuadsToInternalTriples(quads)),
|
|
759
874
|
};
|
|
760
875
|
}
|
|
761
876
|
|
|
@@ -774,13 +889,13 @@ async function normalizeParsedReasonerInputAsync(input) {
|
|
|
774
889
|
function normalizeReasonerInputSync(input) {
|
|
775
890
|
if (typeof input === 'string') return input;
|
|
776
891
|
const parsed = normalizeParsedReasonerInputSync(input);
|
|
777
|
-
|
|
892
|
+
const n3Text = getN3Text(input);
|
|
893
|
+
if (parsed) return joinN3Sections([n3Text, serializeEyelingDocument(parsed)]);
|
|
778
894
|
if (!isObject(input)) {
|
|
779
895
|
throw new TypeError(
|
|
780
896
|
'Reasoner input must be an N3 string, an Eyeling AST/rule object, or an object containing RDF/JS quads plus optional rules',
|
|
781
897
|
);
|
|
782
898
|
}
|
|
783
|
-
if (typeof input.n3 === 'string') return input.n3;
|
|
784
899
|
|
|
785
900
|
const quadsInfo = pickInputQuadIterable(input);
|
|
786
901
|
const rulesText = getRulesText(input);
|
|
@@ -788,25 +903,25 @@ function normalizeReasonerInputSync(input) {
|
|
|
788
903
|
const prefixesText = getPrefixesText(input);
|
|
789
904
|
|
|
790
905
|
if (!quadsInfo) {
|
|
791
|
-
if (rulesText || factsText || prefixesText) return joinN3Sections([prefixesText, factsText, rulesText]);
|
|
906
|
+
if (n3Text || rulesText || factsText || prefixesText) return joinN3Sections([prefixesText, n3Text, factsText, rulesText]);
|
|
792
907
|
throw new TypeError('Input object must provide n3 text, Eyeling AST/rule objects, or RDF/JS quads/facts/dataset');
|
|
793
908
|
}
|
|
794
909
|
|
|
795
910
|
const quads = collectIterableToArray(quadsInfo.value, quadsInfo.label);
|
|
796
|
-
const quadText = quads
|
|
797
|
-
return joinN3Sections([prefixesText, factsText, quadText, rulesText]);
|
|
911
|
+
const quadText = rdfJsQuadsToN3(quads);
|
|
912
|
+
return joinN3Sections([prefixesText, n3Text, factsText, quadText, rulesText]);
|
|
798
913
|
}
|
|
799
914
|
|
|
800
915
|
async function normalizeReasonerInputAsync(input) {
|
|
801
916
|
if (typeof input === 'string') return input;
|
|
802
917
|
const parsed = await normalizeParsedReasonerInputAsync(input);
|
|
803
|
-
|
|
918
|
+
const n3Text = getN3Text(input);
|
|
919
|
+
if (parsed) return joinN3Sections([n3Text, serializeEyelingDocument(parsed)]);
|
|
804
920
|
if (!isObject(input)) {
|
|
805
921
|
throw new TypeError(
|
|
806
922
|
'Reasoner input must be an N3 string, an Eyeling AST/rule object, or an object containing RDF/JS quads plus optional rules',
|
|
807
923
|
);
|
|
808
924
|
}
|
|
809
|
-
if (typeof input.n3 === 'string') return input.n3;
|
|
810
925
|
|
|
811
926
|
const quadsInfo = pickInputQuadIterable(input);
|
|
812
927
|
const rulesText = getRulesText(input);
|
|
@@ -814,13 +929,13 @@ async function normalizeReasonerInputAsync(input) {
|
|
|
814
929
|
const prefixesText = getPrefixesText(input);
|
|
815
930
|
|
|
816
931
|
if (!quadsInfo) {
|
|
817
|
-
if (rulesText || factsText || prefixesText) return joinN3Sections([prefixesText, factsText, rulesText]);
|
|
932
|
+
if (n3Text || rulesText || factsText || prefixesText) return joinN3Sections([prefixesText, n3Text, factsText, rulesText]);
|
|
818
933
|
throw new TypeError('Input object must provide n3 text, Eyeling AST/rule objects, or RDF/JS quads/facts/dataset');
|
|
819
934
|
}
|
|
820
935
|
|
|
821
936
|
const quads = await collectAsyncIterableToArray(quadsInfo.value, quadsInfo.label);
|
|
822
|
-
const quadText = quads
|
|
823
|
-
return joinN3Sections([prefixesText, factsText, quadText, rulesText]);
|
|
937
|
+
const quadText = rdfJsQuadsToN3(quads);
|
|
938
|
+
return joinN3Sections([prefixesText, n3Text, factsText, quadText, rulesText]);
|
|
824
939
|
}
|
|
825
940
|
|
|
826
941
|
module.exports = {
|
|
@@ -831,8 +946,10 @@ module.exports = {
|
|
|
831
946
|
rdfJsTermToN3,
|
|
832
947
|
rdfJsQuadToN3,
|
|
833
948
|
rdfJsQuadToInternalTriple,
|
|
949
|
+
rdfJsQuadsToInternalTriples,
|
|
834
950
|
internalTermToRdfJs,
|
|
835
951
|
internalTripleToRdfJsQuad,
|
|
952
|
+
internalTripleToRdfJsQuads,
|
|
836
953
|
internalTripleToRdfJsQuadInGraph,
|
|
837
954
|
normalizeParsedReasonerInputSync,
|
|
838
955
|
normalizeReasonerInputSync,
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# RDF/JS integration in Eyeling
|
|
2
|
+
|
|
3
|
+
## Short version
|
|
4
|
+
|
|
5
|
+
Eyeling still reasons over its normal N3 data model. RDF/JS support is an adapter layer:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
RDF/JS quads, RDF/JS Quad terms, N3 text, or Eyeling rule objects
|
|
9
|
+
↓
|
|
10
|
+
lib/rdfjs.js input normalization
|
|
11
|
+
↓
|
|
12
|
+
Eyeling N3 triples/rules, including GraphTerm and log:nameOf encodings
|
|
13
|
+
↓
|
|
14
|
+
normal parser + reasoner
|
|
15
|
+
↓
|
|
16
|
+
N3 output and, when requested, RDF/JS quads
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The important RDF 1.2 mappings are:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
RDF/JS Quad term in subject/object position
|
|
23
|
+
↔ singleton N3 GraphTerm
|
|
24
|
+
↔ RDF 1.2 triple term
|
|
25
|
+
|
|
26
|
+
RDF/JS named graph quad
|
|
27
|
+
↔ graph log:nameOf { ... }
|
|
28
|
+
↔ TriG-style named graph output/input
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
So RDF/JS is now aligned with the same internal representation used by Eyeling's RDF 1.2/TriG compatibility mode.
|
|
32
|
+
|
|
33
|
+
## Public API pieces
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const {
|
|
37
|
+
reason,
|
|
38
|
+
reasonStream,
|
|
39
|
+
reasonRdfJs,
|
|
40
|
+
rdfjs,
|
|
41
|
+
} = require('eyeling');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `rdfjs` is Eyeling's lightweight RDF/JS-style `DataFactory`.
|
|
45
|
+
- `reasonStream(input, { rdfjs: true })` returns the normal structured result plus RDF/JS quad arrays.
|
|
46
|
+
- `reasonRdfJs(input, opts)` returns an async iterable of derived RDF/JS quads.
|
|
47
|
+
- `dataFactory` can be supplied in options to use another RDF/JS factory.
|
|
48
|
+
|
|
49
|
+
## Lightweight DataFactory
|
|
50
|
+
|
|
51
|
+
`lib/rdfjs.js` defines small RDF/JS-compatible term classes:
|
|
52
|
+
|
|
53
|
+
- `NamedNode`
|
|
54
|
+
- `BlankNode`
|
|
55
|
+
- `Literal`
|
|
56
|
+
- `Variable`
|
|
57
|
+
- `DefaultGraph`
|
|
58
|
+
- `Quad`
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const { rdfjs } = require('eyeling');
|
|
64
|
+
|
|
65
|
+
const ex = 'http://example.org/';
|
|
66
|
+
const s = rdfjs.namedNode(ex + 's');
|
|
67
|
+
const p = rdfjs.namedNode(ex + 'p');
|
|
68
|
+
const o = rdfjs.literal('hello');
|
|
69
|
+
const q = rdfjs.quad(s, p, o, rdfjs.defaultGraph());
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each term has `termType`, `value`, and `equals(other)`. Literals also carry `language` and `datatype`. `defaultGraph()` returns a singleton default graph term.
|
|
73
|
+
|
|
74
|
+
## RDF/JS input forms
|
|
75
|
+
|
|
76
|
+
Eyeling accepts RDF/JS quads through any of these object keys:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
{ quads: iterableOfQuads }
|
|
80
|
+
{ facts: iterableOfQuads }
|
|
81
|
+
{ dataset: iterableOfQuads }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The iterable may be synchronous for `reasonStream()` and may be synchronous or asynchronous for `reasonRdfJs()`.
|
|
85
|
+
|
|
86
|
+
Accepted RDF/JS term types are:
|
|
87
|
+
|
|
88
|
+
- `NamedNode`
|
|
89
|
+
- `BlankNode`
|
|
90
|
+
- `Literal`
|
|
91
|
+
- `Variable`
|
|
92
|
+
- `Quad` in subject or object position
|
|
93
|
+
- `DefaultGraph` only as a quad graph
|
|
94
|
+
|
|
95
|
+
`Quad` terms are rejected in predicate position, because RDF triple terms are not valid RDF predicates. A `Quad` term used as a quoted triple term must itself have the default graph.
|
|
96
|
+
|
|
97
|
+
## Default graph input quads
|
|
98
|
+
|
|
99
|
+
A normal RDF/JS quad:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
rdfjs.quad(
|
|
103
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
104
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
105
|
+
rdfjs.literal('hello'),
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
becomes the N3 fact:
|
|
110
|
+
|
|
111
|
+
```n3
|
|
112
|
+
<http://example.org/s> <http://example.org/p> "hello" .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
and internally:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
Triple(
|
|
119
|
+
Iri('http://example.org/s'),
|
|
120
|
+
Iri('http://example.org/p'),
|
|
121
|
+
Literal('"hello"')
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Language and datatype literals are preserved:
|
|
126
|
+
|
|
127
|
+
```n3
|
|
128
|
+
"hello"@en
|
|
129
|
+
"42"^^<http://www.w3.org/2001/XMLSchema#integer>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Named graph input quads
|
|
133
|
+
|
|
134
|
+
Named graph RDF/JS quads are now accepted.
|
|
135
|
+
|
|
136
|
+
Input:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
rdfjs.quad(
|
|
140
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
141
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
142
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
143
|
+
rdfjs.namedNode('http://example.org/g'),
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
is represented internally as the same shape used for TriG compatibility:
|
|
148
|
+
|
|
149
|
+
```n3
|
|
150
|
+
<http://example.org/g> log:nameOf {
|
|
151
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o> .
|
|
152
|
+
} .
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Multiple input quads with the same graph are grouped into one `log:nameOf` graph term:
|
|
156
|
+
|
|
157
|
+
```n3
|
|
158
|
+
:g log:nameOf {
|
|
159
|
+
:s1 :p :o1 .
|
|
160
|
+
:s2 :p :o2 .
|
|
161
|
+
} .
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
On RDF/JS output, this `log:nameOf` representation expands back to RDF/JS quads with the corresponding `graph` term.
|
|
165
|
+
|
|
166
|
+
## RDF/JS `Quad` terms as RDF 1.2 triple terms
|
|
167
|
+
|
|
168
|
+
RDF/JS `Quad` terms in subject or object position are now accepted as RDF 1.2 quoted triple terms.
|
|
169
|
+
|
|
170
|
+
Input:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const quoted = rdfjs.quad(
|
|
174
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
175
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
176
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
rdfjs.quad(
|
|
180
|
+
rdfjs.namedNode('http://example.org/obs'),
|
|
181
|
+
rdfjs.namedNode('http://example.org/about'),
|
|
182
|
+
quoted,
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
is normalized to a singleton N3 graph term:
|
|
187
|
+
|
|
188
|
+
```n3
|
|
189
|
+
<http://example.org/obs> <http://example.org/about> {
|
|
190
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o> .
|
|
191
|
+
} .
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
With RDF compatibility output enabled, the same structure can print as RDF 1.2 triple-term syntax:
|
|
195
|
+
|
|
196
|
+
```turtle
|
|
197
|
+
<http://example.org/obs> <http://example.org/about> <<(
|
|
198
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o>
|
|
199
|
+
)>> .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Internally, the object is:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
GraphTerm([
|
|
206
|
+
Triple(Iri(s), Iri(p), Iri(o))
|
|
207
|
+
])
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This is the same representation used by the lexer when `<<( s p o )>>` is parsed in RDF compatibility mode.
|
|
211
|
+
|
|
212
|
+
## Mixing `{ quads, n3 }`
|
|
213
|
+
|
|
214
|
+
The object form implied by the README is now supported: RDF/JS quads and N3 text are merged before reasoning.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const { reasonStream, rdfjs } = require('eyeling');
|
|
220
|
+
const ex = 'http://example.org/';
|
|
221
|
+
|
|
222
|
+
const result = reasonStream(
|
|
223
|
+
{
|
|
224
|
+
n3: `
|
|
225
|
+
@prefix : <http://example.org/> .
|
|
226
|
+
{ ?x :p ?y } => { ?x :q ?y } .
|
|
227
|
+
`,
|
|
228
|
+
quads: [
|
|
229
|
+
rdfjs.quad(
|
|
230
|
+
rdfjs.namedNode(ex + 'a'),
|
|
231
|
+
rdfjs.namedNode(ex + 'p'),
|
|
232
|
+
rdfjs.namedNode(ex + 'b'),
|
|
233
|
+
),
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
rdfjs: true,
|
|
238
|
+
includeInputFactsInClosure: false,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
console.log(result.closureN3);
|
|
243
|
+
console.log(result.closureQuads);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The RDF/JS fact supplies `:a :p :b`; the N3 rule derives `:a :q :b`.
|
|
247
|
+
|
|
248
|
+
The merge also works with Eyeling rule objects and RDF/JS facts. In that path, `normalizeParsedReasonerInputSync()` or `normalizeParsedReasonerInputAsync()` builds an Eyeling document and appends the RDF/JS quads as facts.
|
|
249
|
+
|
|
250
|
+
## RDF/JS output from `reasonStream()`
|
|
251
|
+
|
|
252
|
+
When `rdfjs: true` is passed to `reasonStream()`, the result can include:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
result.closureQuads
|
|
256
|
+
result.queryQuads
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The `onDerived` callback receives RDF/JS quads too:
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
reasonStream(input, {
|
|
263
|
+
rdfjs: true,
|
|
264
|
+
onDerived({ triple, quad, quads, df }) {
|
|
265
|
+
console.log(triple); // N3 or RDF-compatible text form
|
|
266
|
+
console.log(quad); // first RDF/JS quad when exactly/conveniently available
|
|
267
|
+
console.log(quads); // all RDF/JS quads emitted for this derived fact
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
A single internal triple can produce more than one RDF/JS quad when it is a `log:nameOf` named-graph wrapper, so the plural `quads` payload is the complete form. `quad` is present when there is exactly one emitted quad.
|
|
273
|
+
|
|
274
|
+
## Output conversion rules
|
|
275
|
+
|
|
276
|
+
The normal output path now uses `internalTripleToRdfJsQuads()`.
|
|
277
|
+
|
|
278
|
+
Ordinary terms map directly:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
Iri → NamedNode
|
|
282
|
+
Blank → BlankNode
|
|
283
|
+
Literal → Literal
|
|
284
|
+
Var → Variable
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Singleton graph terms in subject or object position map to RDF/JS `Quad` terms:
|
|
288
|
+
|
|
289
|
+
```n3
|
|
290
|
+
:x :holds { :s :p :o } .
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
becomes an RDF/JS quad whose object is:
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
rdfjs.quad(
|
|
297
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
298
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
299
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
300
|
+
rdfjs.defaultGraph(),
|
|
301
|
+
)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Named graph wrappers map back to named-graph RDF/JS quads:
|
|
305
|
+
|
|
306
|
+
```n3
|
|
307
|
+
:g log:nameOf {
|
|
308
|
+
:s :p :o .
|
|
309
|
+
} .
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
becomes:
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
rdfjs.quad(s, p, o, g)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Remaining N3-only cases
|
|
319
|
+
|
|
320
|
+
`skipUnsupportedRdfJs` is still useful, but the previous RDF 1.2 cases no longer require it.
|
|
321
|
+
|
|
322
|
+
Still unsupported as ordinary RDF/JS output:
|
|
323
|
+
|
|
324
|
+
- non-singleton `GraphTerm` in subject/object position;
|
|
325
|
+
- `GraphTerm` in predicate or graph position;
|
|
326
|
+
- `ListTerm` and `OpenListTerm`;
|
|
327
|
+
- other N3-only terms that do not have an RDF/JS representation.
|
|
328
|
+
|
|
329
|
+
By default, unsupported output raises a conversion error. With:
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
skipUnsupportedRdfJs: true
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Eyeling keeps the N3 result and omits the unsupported RDF/JS quads from `closureQuads`, `queryQuads`, and `onDerived` payloads.
|
|
336
|
+
|
|
337
|
+
## `reasonRdfJs()`
|
|
338
|
+
|
|
339
|
+
`reasonRdfJs(input, opts)` returns an async iterable of derived RDF/JS quads:
|
|
340
|
+
|
|
341
|
+
```js
|
|
342
|
+
const { reasonRdfJs, rdfjs } = require('eyeling');
|
|
343
|
+
|
|
344
|
+
for await (const quad of reasonRdfJs({
|
|
345
|
+
quads: [
|
|
346
|
+
rdfjs.quad(
|
|
347
|
+
rdfjs.namedNode('http://example.org/a'),
|
|
348
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
349
|
+
rdfjs.namedNode('http://example.org/b'),
|
|
350
|
+
),
|
|
351
|
+
],
|
|
352
|
+
n3: `
|
|
353
|
+
@prefix : <http://example.org/> .
|
|
354
|
+
{ ?x :p ?y } => { ?x :q ?y } .
|
|
355
|
+
`,
|
|
356
|
+
})) {
|
|
357
|
+
console.log(quad.subject.value, quad.predicate.value, quad.object.value);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Internally, `reasonRdfJs()`:
|
|
362
|
+
|
|
363
|
+
1. normalizes the input, collecting async RDF/JS input if necessary;
|
|
364
|
+
2. runs `reasonStream()` on the normalized N3/Eyeling document;
|
|
365
|
+
3. converts each derived fact with `internalTripleToRdfJsQuads()`;
|
|
366
|
+
4. yields all resulting RDF/JS quads.
|
|
367
|
+
|
|
368
|
+
It is an async output interface, not a streaming RDF parser. Async input quads are collected before reasoning starts.
|
|
369
|
+
|
|
370
|
+
## Practical summary
|
|
371
|
+
|
|
372
|
+
Use RDF/JS integration when Eyeling needs to sit inside a JavaScript RDF pipeline:
|
|
373
|
+
|
|
374
|
+
- feed default-graph or named-graph RDF/JS quads as facts;
|
|
375
|
+
- use RDF/JS `Quad` terms for RDF 1.2 triple terms in subject/object positions;
|
|
376
|
+
- mix RDF/JS facts with N3 text using `{ quads, n3 }`;
|
|
377
|
+
- request RDF/JS output with `rdfjs: true` or `reasonRdfJs()`;
|
|
378
|
+
- keep `skipUnsupportedRdfJs: true` only for genuinely N3-only terms such as lists or non-singleton formulas.
|