Skip to content

Commit 1268039

Browse files
committed
Parse annotated triples
1 parent 4a834c9 commit 1268039

File tree

2 files changed

+200
-14
lines changed

2 files changed

+200
-14
lines changed

lib/RdfXmlParser.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
350350
if (typedNode) {
351351
const type: RDF.NamedNode = this.uriToNamedNode(tag.uri + tag.local);
352352
this.emitTriple(activeTag.subject, this.dataFactory.namedNode(RdfXmlParser.RDF + 'type'),
353-
type, parentTag ? parentTag.reifiedStatementId : null, activeTag.childrenTripleTerms);
353+
type, parentTag ? parentTag.reifiedStatementId : null, activeTag.childrenTripleTerms, activeTag.reifier);
354354
}
355355

356356
if (parentTag) {
@@ -359,29 +359,30 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
359359
if (parentTag.childrenCollectionSubject) {
360360
// RDF:List-based properties
361361
const linkTerm: RDF.BlankNode = this.dataFactory.blankNode();
362+
const restTerm = this.dataFactory.namedNode(RdfXmlParser.RDF + 'rest');
362363

363364
// Emit <x> <p> <current-chain> OR <previous-chain> <rdf:rest> <current-chain>
364365
this.emitTriple(parentTag.childrenCollectionSubject,
365-
parentTag.childrenCollectionPredicate, linkTerm, parentTag.reifiedStatementId, parentTag.childrenTripleTerms);
366+
parentTag.childrenCollectionPredicate, linkTerm, parentTag.reifiedStatementId, parentTag.childrenTripleTerms, parentTag.childrenCollectionPredicate.equals(restTerm) ? null : parentTag.reifier);
366367

367368
// Emit <current-chain> <rdf:first> value
368369
this.emitTriple(linkTerm, this.dataFactory.namedNode(RdfXmlParser.RDF + 'first'),
369370
activeTag.subject, activeTag.reifiedStatementId, activeTag.childrenTripleTerms);
370371

371372
// Store <current-chain> in the parent node
372373
parentTag.childrenCollectionSubject = linkTerm;
373-
parentTag.childrenCollectionPredicate = this.dataFactory.namedNode(RdfXmlParser.RDF + 'rest');
374+
parentTag.childrenCollectionPredicate = restTerm;
374375
} else { // !parentTag.predicateEmitted
375376
// Set-based properties
376377
if (!parentTag.childrenTagsToTripleTerms) {
377-
this.emitTriple(parentTag.subject, parentTag.predicate, activeTag.subject, parentTag.reifiedStatementId, parentTag.childrenTripleTerms);
378+
this.emitTriple(parentTag.subject, parentTag.predicate, activeTag.subject, parentTag.reifiedStatementId, parentTag.childrenTripleTerms, parentTag.reifier);
378379
parentTag.predicateEmitted = true;
379380
}
380381

381382
// Emit pending properties on the parent tag that had no defined subject yet.
382383
for (let i = 0; i < parentTag.predicateSubPredicates.length; i++) {
383384
this.emitTriple(activeTag.subject, parentTag.predicateSubPredicates[i],
384-
parentTag.predicateSubObjects[i], null, parentTag.childrenTripleTerms);
385+
parentTag.predicateSubObjects[i], null, parentTag.childrenTripleTerms, parentTag.reifier);
385386
}
386387

387388
// Cleanup so we don't emit them again when the parent tag is closed
@@ -393,12 +394,12 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
393394
// Emit all collected triples
394395
for (let i = 0; i < predicates.length; i++) {
395396
const object: RDF.Term = this.createLiteral(objects[i], activeTag);
396-
this.emitTriple(activeTag.subject, predicates[i], object, parentTag.reifiedStatementId, parentTag.childrenTripleTerms);
397+
this.emitTriple(activeTag.subject, predicates[i], object, parentTag.reifiedStatementId, parentTag.childrenTripleTerms, parentTag.reifier);
397398
}
398399
// Emit the rdf:type as named node instead of literal
399400
if (explicitType) {
400401
this.emitTriple(activeTag.subject, this.dataFactory.namedNode(RdfXmlParser.RDF + 'type'),
401-
this.uriToNamedNode(explicitType), null, activeTag.childrenTripleTerms);
402+
this.uriToNamedNode(explicitType), null, activeTag.childrenTripleTerms, activeTag.reifier);
402403
}
403404
}
404405
}
@@ -509,7 +510,7 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
509510

510511
// Turn this property element into a node element
511512
const nestedBNode: RDF.BlankNode = this.dataFactory.blankNode();
512-
this.emitTriple(activeTag.subject, activeTag.predicate, nestedBNode, activeTag.reifiedStatementId, activeTag.childrenTripleTerms);
513+
this.emitTriple(activeTag.subject, activeTag.predicate, nestedBNode, activeTag.reifiedStatementId, activeTag.childrenTripleTerms, activeTag.reifier);
513514
activeTag.subject = nestedBNode;
514515
activeTag.predicate = null;
515516
} else if (propertyAttribute.value === 'Collection') {
@@ -536,6 +537,12 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
536537
activeTag.reifiedStatementId = this.valueToUri('#' + propertyAttribute.value, activeTag);
537538
this.claimNodeId(activeTag.reifiedStatementId);
538539
continue;
540+
case 'annotation':
541+
activeTag.reifier = this.dataFactory.namedNode(propertyAttribute.value);
542+
continue;
543+
case 'annotationNodeID':
544+
activeTag.reifier = this.dataFactory.blankNode(propertyAttribute.value);
545+
continue;
539546
}
540547
} else if (propertyAttribute.uri === RdfXmlParser.XML && propertyAttribute.local === 'lang') {
541548
activeTag.language = propertyAttribute.value === ''
@@ -567,11 +574,11 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
567574
const subjectParent: RDF.Term = activeTag.subject;
568575
activeTag.subject = subSubjectValueBlank
569576
? this.dataFactory.blankNode(activeSubSubjectValue) : this.valueToUri(activeSubSubjectValue, activeTag);
570-
this.emitTriple(subjectParent, activeTag.predicate, activeTag.subject, activeTag.reifiedStatementId, activeTag.childrenTripleTerms);
577+
this.emitTriple(subjectParent, activeTag.predicate, activeTag.subject, activeTag.reifiedStatementId, activeTag.childrenTripleTerms, activeTag.reifier);
571578

572579
// Emit our buffered triples
573580
for (let i = 0; i < predicates.length; i++) {
574-
this.emitTriple(activeTag.subject, predicates[i], objects[i], null, activeTag.childrenTripleTerms);
581+
this.emitTriple(activeTag.subject, predicates[i], objects[i], null, activeTag.childrenTripleTerms, activeTag.reifier);
575582
}
576583
activeTag.predicateEmitted = true;
577584
} else if (subSubjectValueBlank) {
@@ -592,15 +599,21 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
592599
* @param {Term} statementId An optional resource that identifies the triple.
593600
* If truthy, then the given triple will also be emitted reified.
594601
* @param childrenTripleTerms An optional array to push quads into instead of emitting them.
602+
* @param reifier The reifier to emit this triple under.
595603
*/
596604
protected emitTriple(subject: RDF.Quad_Subject, predicate: RDF.Quad_Predicate, object: RDF.Quad_Object,
597-
statementId?: RDF.NamedNode, childrenTripleTerms?: RDF.Quad[]) {
605+
statementId?: RDF.NamedNode,
606+
childrenTripleTerms?: RDF.Quad[],
607+
reifier?: RDF.NamedNode | RDF.BlankNode) {
598608
const quad = this.dataFactory.quad(subject, predicate, object, this.defaultGraph);
599609
if (childrenTripleTerms) {
600610
childrenTripleTerms.push(quad);
601611
} else {
602612
this.push(quad);
603613
}
614+
if (reifier) {
615+
this.push(this.dataFactory.quad(reifier, this.dataFactory.namedNode(RdfXmlParser.RDF + 'reifies'), quad));
616+
}
604617

605618
// Reify triple
606619
if (statementId) {
@@ -676,7 +689,7 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
676689
throw this.newParseError(`Expected exactly one triple term in rdf:parseType="Triple" but got ${poppedTag.childrenTripleTerms.length}`);
677690
}
678691
for (const tripleTerm of poppedTag.childrenTripleTerms) {
679-
this.emitTriple(poppedTag.subject, poppedTag.predicate, tripleTerm, null, parentTag.childrenTripleTerms);
692+
this.emitTriple(poppedTag.subject, poppedTag.predicate, tripleTerm, null, parentTag.childrenTripleTerms, parentTag.reifier);
680693
}
681694
poppedTag.predicateEmitted = true;
682695
}
@@ -689,11 +702,11 @@ while ${attribute.value} and ${activeSubjectValue} where found.`);
689702
if (!poppedTag.hadChildren && poppedTag.childrenParseType !== ParseType.PROPERTY) {
690703
// Property element contains text
691704
this.emitTriple(poppedTag.subject, poppedTag.predicate, this.createLiteral(poppedTag.text || '', poppedTag),
692-
poppedTag.reifiedStatementId, poppedTag.childrenTripleTerms);
705+
poppedTag.reifiedStatementId, poppedTag.childrenTripleTerms, poppedTag.reifier);
693706
} else if (!poppedTag.predicateEmitted) {
694707
// Emit remaining properties on an anonymous property element
695708
const subject: RDF.Term = this.dataFactory.blankNode();
696-
this.emitTriple(poppedTag.subject, poppedTag.predicate, subject, poppedTag.reifiedStatementId, poppedTag.childrenTripleTerms);
709+
this.emitTriple(poppedTag.subject, poppedTag.predicate, subject, poppedTag.reifiedStatementId, poppedTag.childrenTripleTerms, poppedTag.reifier);
697710
for (let i = 0; i < poppedTag.predicateSubPredicates.length; i++) {
698711
this.emitTriple(subject, poppedTag.predicateSubPredicates[i], poppedTag.predicateSubObjects[i], null, poppedTag.childrenTripleTerms);
699712
}
@@ -782,6 +795,7 @@ export interface IActiveTag {
782795
childrenCollectionPredicate?: RDF.NamedNode;
783796
childrenTagsToTripleTerms?: boolean;
784797
childrenTripleTerms?: RDF.Quad[];
798+
reifier?: RDF.NamedNode | RDF.BlankNode;
785799
}
786800

787801
export enum ParseType {

test/RdfXmlParser-test.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,6 +2687,178 @@ abc`)).rejects.toBeTruthy();
26872687
'<<http://example.org/stuff/1.0/s http://example.org/stuff/1.0/p <<http://example.org/stuff/1.0/s2 http://example.org/stuff/1.0/p2 http://example.org/stuff/1.0/o2>>>>'),
26882688
]);
26892689
});
2690+
2691+
it('on property elements with rdf:annotation', async () => {
2692+
const array = await parse(parser, `<?xml version="1.0"?>
2693+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2694+
xmlns:ex="http://example.org/stuff/1.0/"
2695+
xml:base="http://example.org/triples/">
2696+
<rdf:Description rdf:about="http://example.org/">
2697+
<ex:prop rdf:annotation="http://example.org/triple1">blah</ex:prop>
2698+
</rdf:Description>
2699+
<rdf:Description rdf:about="http://example.org/triple1">
2700+
<ex:prop>foo</ex:prop>
2701+
</rdf:Description>
2702+
</rdf:RDF>`);
2703+
return expect(array)
2704+
.toBeRdfIsomorphic([
2705+
quad('http://example.org/', 'http://example.org/stuff/1.0/prop', '"blah"'),
2706+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://example.org/stuff/1.0/prop "blah">>'),
2707+
quad('http://example.org/triple1', 'http://example.org/stuff/1.0/prop', '"foo"'),
2708+
]);
2709+
});
2710+
2711+
it('on property elements with rdf:annotationNodeID', async () => {
2712+
const array = await parse(parser, `<?xml version="1.0"?>
2713+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2714+
xmlns:ex="http://example.org/stuff/1.0/"
2715+
xml:base="http://example.org/triples/">
2716+
<rdf:Description rdf:about="http://example.org/">
2717+
<ex:prop rdf:annotationNodeID="triple1">blah</ex:prop>
2718+
</rdf:Description>
2719+
<rdf:Description rdf:nodeID="triple1">
2720+
<ex:prop>foo</ex:prop>
2721+
</rdf:Description>
2722+
</rdf:RDF>`);
2723+
return expect(array)
2724+
.toBeRdfIsomorphic([
2725+
quad('http://example.org/', 'http://example.org/stuff/1.0/prop', '"blah"'),
2726+
quad('_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://example.org/stuff/1.0/prop "blah">>'),
2727+
quad('_:b0', 'http://example.org/stuff/1.0/prop', '"foo"'),
2728+
]);
2729+
});
2730+
2731+
it('on property elements with rdf:annotation with empty object literal', async () => {
2732+
const array = await parse(parser, `<?xml version="1.0"?>
2733+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2734+
xmlns:ex="http://example.org/stuff/1.0/"
2735+
xml:base="http://example.org/triples/">
2736+
<rdf:Description rdf:about="http://example.org/">
2737+
<ex:prop rdf:annotation="http://example.org/triple1" />
2738+
</rdf:Description>
2739+
</rdf:RDF>`);
2740+
return expect(array)
2741+
.toBeRdfIsomorphic([
2742+
quad('http://example.org/', 'http://example.org/stuff/1.0/prop', '""'),
2743+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://example.org/stuff/1.0/prop "">>'),
2744+
]);
2745+
});
2746+
2747+
it('on property elements with rdf:annotation with rdf:parseType="Resource"', async () => {
2748+
const array = await parse(parser, `<?xml version="1.0"?>
2749+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2750+
xmlns:ex="http://example.org/stuff/1.0/"
2751+
xml:base="http://example.org/triples/">
2752+
<rdf:Description rdf:about="http://example.org/">
2753+
<ex:prop rdf:annotation="http://example.org/triple1" rdf:parseType="Resource" />
2754+
</rdf:Description>
2755+
</rdf:RDF>`);
2756+
return expect(array)
2757+
.toBeRdfIsomorphic([
2758+
quad('http://example.org/', 'http://example.org/stuff/1.0/prop', '_:b0'),
2759+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://example.org/stuff/1.0/prop _:b0>>'),
2760+
]);
2761+
});
2762+
2763+
it('on property elements with rdf:annotation with inline property', async () => {
2764+
const array = await parse(parser, `<?xml version="1.0"?>
2765+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2766+
xmlns:eg="http://example.org/">
2767+
<rdf:Description>
2768+
<eg:prop1 rdf:annotation="http://example.org/triple1" eg:prop2="val"></eg:prop1>
2769+
</rdf:Description>
2770+
</rdf:RDF>`);
2771+
return expect(array)
2772+
.toBeRdfIsomorphic([
2773+
quad('_:b', 'http://example.org/prop2', '"val"'),
2774+
quad('_:c', 'http://example.org/prop1', '_:b'),
2775+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<_:c http://example.org/prop1 _:b>>'),
2776+
]);
2777+
});
2778+
2779+
it('on property elements with rdf:annotation with rdf:resource', async () => {
2780+
const array = await parse(parser, `<?xml version="1.0"?>
2781+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2782+
xmlns:eg="http://example.org/">
2783+
2784+
<rdf:Description rdf:about="http://example.org/">
2785+
<rdf:type rdf:annotation="http://example.org/triple1"
2786+
rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Resource"/>
2787+
</rdf:Description>
2788+
</rdf:RDF>`);
2789+
return expect(array)
2790+
.toBeRdfIsomorphic([
2791+
quad('http://example.org/', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Resource'),
2792+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://www.w3.org/1999/02/22-rdf-syntax-ns#Resource>>'),
2793+
]);
2794+
});
2795+
2796+
it('on property elements with rdf:annotation with rdf:nodeID', async () => {
2797+
const array = await parse(parser, `<?xml version="1.0"?>
2798+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2799+
xmlns:eg="http://example.org/">
2800+
2801+
<rdf:Description rdf:about="http://example.org/">
2802+
<eg:prop rdf:annotation="http://example.org/triple1" rdf:nodeID="object"/>
2803+
</rdf:Description>
2804+
</rdf:RDF>`);
2805+
return expect(array)
2806+
.toBeRdfIsomorphic([
2807+
quad('http://example.org/', 'http://example.org/prop', '_:object'),
2808+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/ http://example.org/prop _:object>>'),
2809+
]);
2810+
});
2811+
2812+
it('on property elements with nested rdf:annotation', async () => {
2813+
const array = await parse(parser, `<?xml version="1.0"?>
2814+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2815+
xmlns:eg="http://example.org/">
2816+
2817+
<rdf:Description rdf:about="http://example.org/a">
2818+
<eg:prop rdf:annotation="http://example.org/triple1">
2819+
<rdf:Description rdf:about="http://example.org/b">
2820+
<eg:prop rdf:annotation="http://example.org/triple2" rdf:resource="http://example.org/c"/>
2821+
</rdf:Description>
2822+
</eg:prop>
2823+
</rdf:Description>
2824+
</rdf:RDF>`);
2825+
return expect(array)
2826+
.toBeRdfIsomorphic([
2827+
quad('http://example.org/b', 'http://example.org/prop', 'http://example.org/c'),
2828+
quad('http://example.org/triple2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/b http://example.org/prop http://example.org/c>>'),
2829+
quad('http://example.org/a', 'http://example.org/prop', 'http://example.org/b'),
2830+
quad('http://example.org/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<http://example.org/a http://example.org/prop http://example.org/b>>'),
2831+
]);
2832+
});
2833+
2834+
it('on property elements with rdf:annotation over a collection', async () => {
2835+
const array = await parse(parser, `<?xml version="1.0"?>
2836+
<rdf:RDF
2837+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2838+
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
2839+
xmlns:eg="http://example.org/eg#"
2840+
xml:base="http://example.com/">
2841+
2842+
<rdf:Description rdf:about="http://example.org/eg#eric">
2843+
<rdf:type rdf:parseType="Resource">
2844+
<eg:intersectionOf rdf:annotation="http://example.com/triple1" rdf:parseType="Collection">
2845+
<rdf:Description rdf:about="http://example.org/eg#Person"/>
2846+
<rdf:Description rdf:about="http://example.org/eg#Male"/>
2847+
</eg:intersectionOf>
2848+
</rdf:type>
2849+
</rdf:Description>
2850+
</rdf:RDF>`);
2851+
return expect(array)
2852+
.toBeRdfIsomorphic([
2853+
quad('http://example.org/eg#eric', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', '_:an0'),
2854+
quad('_:an0', 'http://example.org/eg#intersectionOf', '_:an1'),
2855+
quad('http://example.com/triple1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies', '<<_:an0 http://example.org/eg#intersectionOf _:an1>>'),
2856+
quad('_:an1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://example.org/eg#Person'),
2857+
quad('_:an1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:an2'),
2858+
quad('_:an2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://example.org/eg#Male'),
2859+
quad('_:an2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'),
2860+
]);
2861+
});
26902862
});
26912863

26922864
describe('streaming-wise', () => {

0 commit comments

Comments
 (0)