Skip to content

Commit

Permalink
support getting rdf:type values
Browse files Browse the repository at this point in the history
  • Loading branch information
pmcb55 committed Oct 1, 2023
1 parent 808eb31 commit 88a6ce9
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 13 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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., "").

Expand Down Expand Up @@ -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
```

62 changes: 61 additions & 1 deletion src/VocabTerm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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);
});
});
});
100 changes: 98 additions & 2 deletions src/VocabTerm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -93,6 +109,7 @@ class VocabTerm implements NamedNode {
private _languageOverride: string | undefined;
private _isDefinedBy: NamedNode | undefined; // Only allow one value.
private _seeAlso: Set<NamedNode> | undefined;
private _type: Set<NamedNode> | undefined;

// Implementation of the NamedNode interface.
termType: "NamedNode" = "NamedNode";
Expand Down Expand Up @@ -170,6 +187,7 @@ class VocabTerm implements NamedNode {
this._languageOverride = undefined;
this._isDefinedBy = undefined;
this._seeAlso = undefined;
this._type = undefined;

this.resetState();
}
Expand All @@ -184,6 +202,10 @@ class VocabTerm implements NamedNode {
return this._seeAlso;
}

get type(): Set<NamedNode> | undefined {
return this._type;
}

get isDefinedBy(): NamedNode | undefined {
return this._isDefinedBy;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -282,6 +369,15 @@ class VocabTerm implements NamedNode {
return this;
}

addType(value: NamedNode) {
if (!this._type) {
this._type = new Set<NamedNode>();
}

this._type.add(value);
return this;
}

addIsDefinedBy(value: NamedNode) {
this._isDefinedBy = value;
return this;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 88a6ce9

Please sign in to comment.