From 88a6ce94084e7afaa89d9a0ea7296ff63a8ceebf Mon Sep 17 00:00:00 2001 From: pmcb55 Date: Sun, 1 Oct 2023 23:30:14 +0100 Subject: [PATCH] support getting rdf:type values --- README.md | 22 +++++----- src/VocabTerm.test.ts | 62 +++++++++++++++++++++++++- src/VocabTerm.ts | 100 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 171 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8d4078df..3df81bf0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # The Solid Common Vocab library for JavaScript A library providing JavaScript objects to represent the individual terms -(i.e., the classes and properties) defined in RDF vocabularies (both existing +(i.e., the classes and properties) defined in RDF vocabularies, both existing vocabularies (like http://schema.org, FOAF, vCard, LDP, ActivityStreams, etc.), -and your own custom RDF vocabularies). +and your own custom RDF vocabularies. A major feature of this library is that it provides easy access to any `rdfs:label` and `rdfs:comment` values provided for these vocabulary terms, and -provides very easy-to-use support for multi-lingual values for these labels and +provides very easy-to-use support for multilingual values for these labels and comments. We also support other term metadata (such as `rdfs:seeAlso` and `rdfs:isDefinedBy`), and also generic message strings (using [SKOS-XL](https://www.w3.org/TR/skos-reference/skos-xl.html)) that can be used @@ -28,7 +28,7 @@ npm ci node index.js ``` -The `solid-common-vocab` library is distributed as a GitHub npm package: +The `solid-common-vocab` library is distributed as a GitHub `npm` package: `@inrupt/solid-common-vocab`. For more information about GitHub npm packages, please visit [the dedicated documentation](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-npm-for-use-with-github-packages). @@ -62,12 +62,13 @@ jest demonstrateByUsage.test.js The Vocab Term object from this library is intended to be a simple wrapper around the 'NamedNode' object conforming to the [RDF/JS interface](http://rdf.js.org/data-model-spec/). + This means that Vocab Term instances can be used natively with libraries that are RDF/JS-compliant, such as `rdf-ext`, `rdflib.js`, `rdf-data-factory`, `graphy`, etc. An instance of a `VocabTerm` may be built by passing an RDF/JS `DataFactory` implemented by any library, but it also includes its own very -basic `DataFactory` implementation for convenience if you don't wish to include -an existing implementation. +basic `DataFactory` implementation, just for convenience if you don't wish to +include an existing implementation. ### Introductory example @@ -157,7 +158,7 @@ const person: VocabTerm = buildBasicTerm( ### Messages An important feature of `solid-common-vocab` is support for parameterized -messages. This can be extremely useful when defining your own RDF vocabularies +messages. This can be extremely useful when creating your own RDF vocabularies and including message strings (thereby providing those messages with globally unique IRI identifiers, and allowing for easy translations of those message strings). For instance, to report errors to the user with contextual information @@ -175,7 +176,7 @@ term.messageParams('Current Account').value // Evaluates to "Your account (Curre Unless we explicitly mandate a specific language, English will be used by default. Best practice for RDF vocabularies in general is to provide labels -(short human readable descriptions) and comments (longer, more detailed +(short human-readable descriptions) and comments (longer, more detailed descriptions), and to also provide these descriptions in multiple languages if appropriate and possible. @@ -206,9 +207,9 @@ personLabel = person.label // personLabel now contains the Spanish literal. ### Strictness The last parameter to the Vocab Term constructor indicates if the behaviour of -the term should be strict or loose. In the case of "loose" behaviour, in the +the term should be strict or loose. In the case of 'loose' behaviour, in the absence of any label, `term.label` will default to the local part of the term's -IRI (i.e., the last segment of the full path component). With "strict" behaviour +IRI (i.e., the last segment of the full path component). With 'strict' behaviour it will return `undefined`. When the local part of the IRI is returned as a label, the language tag will be empty (i.e., ""). @@ -237,3 +238,4 @@ const person = new VocabTerm('https://example.com#Person', rdf, buildStore(), tr // An exception will be thrown here, because our term has no label. const personLabel = person.mandatory.label ``` + \ No newline at end of file diff --git a/src/VocabTerm.test.ts b/src/VocabTerm.test.ts index 121310a9..f02bff95 100755 --- a/src/VocabTerm.test.ts +++ b/src/VocabTerm.test.ts @@ -570,7 +570,7 @@ describe("VocabTerm tests", () => { expect(myTerm.seeAlso!.has(TEST_TERM_NAME)).toBe(true); }); - it("should treat as a set", () => { + it("should treat seAlso as a set", () => { const myTerm = buildBasicTerm( "http://some.vocab#myTerm", getLocalStore(), @@ -596,4 +596,64 @@ describe("VocabTerm tests", () => { expect(myTerm.isDefinedBy!).toBe(TEST_TERM_NAME); }); }); + + describe("rdf:type support", () => { + it("should retrieve 'rdf:type'", () => { + const myTerm = buildBasicTerm( + "http://some.vocab#myTerm", + getLocalStore(), + false, + ); + + expect(myTerm.type).toBeUndefined(); + myTerm.addType(TEST_TERM_NAME); + expect(myTerm.type!.size).toBe(1); + expect(myTerm.type!.has(TEST_TERM_NAME)).toBe(true); + }); + + it("should treat type as a set", () => { + const myTerm = buildBasicTerm( + "http://some.vocab#myTerm", + getLocalStore(), + false, + ) + .addType(TEST_TERM_NAME) + .addType(TEST_TERM_NAME); + + expect(myTerm.type!.size).toBe(1); + + myTerm.addType(rdfFactory.namedNode("https://example.com/myNewType")); + expect(myTerm.type!.size).toBe(2); + }); + + it("should handle rdf:type of Class", () => { + const RDF_CLASS = rdfFactory.namedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#Class", + ); + const myTerm = buildBasicTerm( + "http://some.vocab#myTerm", + getLocalStore(), + false, + ); + + expect(myTerm.isClass).toBe(false); + myTerm.addType(RDF_CLASS); + expect(myTerm.isClass).toBe(true); + }); + + it("should handle rdf:type of Property", () => { + const OWL_OBJECT_PROPERTY = rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#ObjectProperty", + ); + const myTerm = buildBasicTerm( + "http://some.vocab#myTerm", + getLocalStore(), + false, + ); + + expect(myTerm.isProperty).toBe(false); + myTerm.addType(OWL_OBJECT_PROPERTY); + expect(myTerm.isProperty).toBe(true); + }); + }); }); diff --git a/src/VocabTerm.ts b/src/VocabTerm.ts index cdb9bc6c..bfeca9c9 100755 --- a/src/VocabTerm.ts +++ b/src/VocabTerm.ts @@ -39,6 +39,22 @@ import { IriString } from "./index"; const DEFAULT_LOCALE = "en"; +// We need an instance of an RDF Factory to instantiate a Named Node, but we +// only want to create instances of these RDF types if we are checking for the +// +let LAZY_TYPE_RDF_CLASS: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_CLASS: NamedNode | undefined = undefined; + +let LAZY_TYPE_RDFS_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_DATATYPE_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_OBJECT_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_ANNOTATION_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_TRANSITIVE_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_FUNCTIONAL_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_SYMMETRIC_PROPERTY: NamedNode | undefined = undefined; +let LAZY_TYPE_OWL_INVERSE_FUNCTIONAL_PROPERTY: NamedNode | undefined = + undefined; + /** * Class to represent vocabulary terms. We expect derived classes to extend * an IRI (e.g. a NamedNode in RDF/JS), but we just provide effectively an @@ -93,6 +109,7 @@ class VocabTerm implements NamedNode { private _languageOverride: string | undefined; private _isDefinedBy: NamedNode | undefined; // Only allow one value. private _seeAlso: Set | undefined; + private _type: Set | undefined; // Implementation of the NamedNode interface. termType: "NamedNode" = "NamedNode"; @@ -170,6 +187,7 @@ class VocabTerm implements NamedNode { this._languageOverride = undefined; this._isDefinedBy = undefined; this._seeAlso = undefined; + this._type = undefined; this.resetState(); } @@ -184,6 +202,10 @@ class VocabTerm implements NamedNode { return this._seeAlso; } + get type(): Set | undefined { + return this._type; + } + get isDefinedBy(): NamedNode | undefined { return this._isDefinedBy; } @@ -243,6 +265,71 @@ class VocabTerm implements NamedNode { return message && message.value; } + createNamedNodeConstantsClass(): void { + LAZY_TYPE_RDF_CLASS = this.rdfFactory.namedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#Class", + ); + + LAZY_TYPE_OWL_CLASS = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#Property", + ); + } + + createNamedNodeConstantsProperty(): void { + LAZY_TYPE_RDFS_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2000/01/rdf-schema#Property", + ); + LAZY_TYPE_OWL_DATATYPE_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#Property", + ); + LAZY_TYPE_OWL_OBJECT_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#ObjectProperty", + ); + LAZY_TYPE_OWL_ANNOTATION_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#AnnotationProperty", + ); + LAZY_TYPE_OWL_TRANSITIVE_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#TransitiveProperty", + ); + LAZY_TYPE_OWL_FUNCTIONAL_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#FunctionalProperty", + ); + LAZY_TYPE_OWL_INVERSE_FUNCTIONAL_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#InverseFunctionalProperty", + ); + LAZY_TYPE_OWL_SYMMETRIC_PROPERTY = this.rdfFactory.namedNode( + "http://www.w3.org/2002/07/owl#SymmetricProperty", + ); + } + + get isClass(): boolean { + if (!LAZY_TYPE_RDF_CLASS) { + this.createNamedNodeConstantsClass(); + } + + return ( + (this._type?.has(LAZY_TYPE_RDF_CLASS!) || + this._type?.has(LAZY_TYPE_OWL_CLASS!)) !== undefined + ); + } + + get isProperty(): boolean { + if (!LAZY_TYPE_RDFS_PROPERTY) { + this.createNamedNodeConstantsProperty(); + } + + return ( + (this._type?.has(LAZY_TYPE_RDFS_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_OBJECT_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_DATATYPE_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_ANNOTATION_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_FUNCTIONAL_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_INVERSE_FUNCTIONAL_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_SYMMETRIC_PROPERTY!) || + this._type?.has(LAZY_TYPE_OWL_TRANSITIVE_PROPERTY!)) !== undefined + ); + } + // Get the IRI of this term as a String (means we can treat this object // instance as a string more easily). // NOTE: This is *NOT* an accessor, but deliberately overriding the @@ -282,6 +369,15 @@ class VocabTerm implements NamedNode { return this; } + addType(value: NamedNode) { + if (!this._type) { + this._type = new Set(); + } + + this._type.add(value); + return this; + } + addIsDefinedBy(value: NamedNode) { this._isDefinedBy = value; return this; @@ -362,7 +458,7 @@ class VocabTerm implements NamedNode { * @param stringOrNamedNode The IRI to extract from. * @returns {string} */ - static extractIriLocalName(stringOrNamedNode: string | NamedNode) { + static extractIriLocalName(stringOrNamedNode: string | NamedNode): string { const iri = this.isString(stringOrNamedNode) ? stringOrNamedNode : stringOrNamedNode.value; @@ -403,7 +499,7 @@ class VocabTerm implements NamedNode { * @param value * @returns {boolean} */ - static isStringIri(value: string) { + static isStringIri(value: string): boolean { if (!this.isString(value)) { return false; }