diff --git a/src/solid/Email.ts b/src/solid/Email.ts new file mode 100644 index 0000000..505e4e0 --- /dev/null +++ b/src/solid/Email.ts @@ -0,0 +1,20 @@ +import { TermWrapper, ValueMappings, TermMappings } from 'rdfjs-wrapper'; +import { VCARD, RDF } from '../vocabulary/mod.js'; + +export class Email extends TermWrapper { + get emailAddress(): string { + return this.singular(VCARD.value, ValueMappings.literalToString); + } + + set emailAddress(value: string) { + this.overwrite(VCARD.value, value, TermMappings.stringToLiteral); + } + + get emailType(): string | undefined { + return this.singularNullable(RDF.type, ValueMappings.iriToString); + } + + set emailType(value: string | undefined) { + this.overwriteNullable(RDF.type, value, TermMappings.stringToIri); + } +} diff --git a/src/solid/EmailDataset.ts b/src/solid/EmailDataset.ts new file mode 100644 index 0000000..a45557a --- /dev/null +++ b/src/solid/EmailDataset.ts @@ -0,0 +1,15 @@ +import { DatasetWrapper } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/mod.js'; +import { Email } from './Email.js'; + +export class EmailDataset extends DatasetWrapper { + get emails(): Iterable { + const objects = new Set([ + ...this.instancesOf(VCARD.Email, Email), + ...this.objectsOf(VCARD.hasEmail, Email), + ...this.objectsOf(VCARD.email, Email), + ]) + + return objects + } +} diff --git a/src/solid/Telephone.ts b/src/solid/Telephone.ts new file mode 100644 index 0000000..5e96ea3 --- /dev/null +++ b/src/solid/Telephone.ts @@ -0,0 +1,20 @@ +import { TermWrapper, ValueMappings, TermMappings } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/mod.js'; + +export class Telephone extends TermWrapper { + get phoneNumber(): string { + return this.singular(VCARD.hasValue, ValueMappings.literalToString) || ''; + } + + set phoneNumber(value: string) { + this.overwrite(VCARD.hasValue, value, TermMappings.stringToLiteral); + } + + get phoneType(): string | undefined { + return this.singularNullable(VCARD.telephoneType, ValueMappings.iriToString); + } + + set phoneType(value: string | undefined) { + this.overwriteNullable(VCARD.telephoneType, value, TermMappings.stringToIri); + } +} diff --git a/src/solid/TelephoneDataset.ts b/src/solid/TelephoneDataset.ts new file mode 100644 index 0000000..b3f052c --- /dev/null +++ b/src/solid/TelephoneDataset.ts @@ -0,0 +1,14 @@ +import { DatasetWrapper } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/vcard.js'; +import { Telephone } from './Telephone.js'; + +export class TelephoneDataset extends DatasetWrapper { + get telephones(): Iterable { + const objects = new Set([ + ...this.objectsOf(VCARD.hasTelephone, Telephone), + ...this.objectsOf(VCARD.tel, Telephone), + ]) + + return objects + } +} diff --git a/src/solid/mod.ts b/src/solid/mod.ts index fb84c5f..16c242a 100644 --- a/src/solid/mod.ts +++ b/src/solid/mod.ts @@ -1,3 +1,7 @@ export * from "./Container.js" export * from "./ContainerDataset.js" export * from "./Resource.js" +export * from "./Email.js"; +export * from "./EmailDataset.js"; +export * from "./Telephone.js"; +export * from "./TelephoneDataset.js"; diff --git a/src/vocabulary/vcard.ts b/src/vocabulary/vcard.ts index 99ae2d3..19a3b39 100644 --- a/src/vocabulary/vcard.ts +++ b/src/vocabulary/vcard.ts @@ -1,12 +1,17 @@ - export const VCARD = { - fn: "http://www.w3.org/2006/vcard/ns#fn", - hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", - hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", - hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", - hasTelephone: "http://www.w3.org/2006/vcard/ns#hasTelephone", - title: "http://www.w3.org/2006/vcard/ns#title", - hasUrl: "http://www.w3.org/2006/vcard/ns#hasUrl", - organizationName: "http://www.w3.org/2006/vcard/ns#organization-name", - role: "http://www.w3.org/2006/vcard/ns#organization-name", + fn: "http://www.w3.org/2006/vcard/ns#fn", + Email: "http://www.w3.org/2006/vcard/ns#Email", + email: "http://www.w3.org/2006/vcard/ns#email", + hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", + hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", + hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", + tel: "http://www.w3.org/2006/vcard/ns#tel", + hasTelephone: "http://www.w3.org/2006/vcard/ns#hasTelephone", + title: "http://www.w3.org/2006/vcard/ns#title", + hasUrl: "http://www.w3.org/2006/vcard/ns#hasUrl", + organizationName: "http://www.w3.org/2006/vcard/ns#organization-name", + phone: "http://www.w3.org/2006/vcard/ns#phone", + role: "http://www.w3.org/2006/vcard/ns#role", + value: "http://www.w3.org/2006/vcard/ns#value", + telephoneType: "http://www.w3.org/2006/vcard/ns#TelephoneType", } as const; diff --git a/test/unit/email.test.ts b/test/unit/email.test.ts new file mode 100644 index 0000000..04d286a --- /dev/null +++ b/test/unit/email.test.ts @@ -0,0 +1,85 @@ +import { DataFactory, Parser, Store } from "n3" +import assert from "node:assert" +import { describe, it } from "node:test" + +import { Email } from "@solid/object"; + +describe("Email tests", () => { + + const sampleRDF = ` +@prefix vcard: . +@prefix rdf: . + + + a vcard:Individual ; + vcard:fn "Alice" ; + vcard:hasEmail . + + + a vcard:Email ; + vcard:value "alice@example.org" ; + rdf:type vcard:Work . +`; + + it("should parse and retrieve email address", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + assert.equal(email.emailAddress, "alice@example.org") + assert.equal(typeof email.emailAddress, "string") + }) + + it("should allow setting email address", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + email.emailAddress = "bob@example.org" + + assert.equal(email.emailAddress, "bob@example.org") + }) + + it("should parse and retrieve email type", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + const emailType = DataFactory.namedNode("http://www.w3.org/2006/vcard/ns#Home") + + assert.ok(emailType !== undefined) + assert.equal(typeof emailType, "object") + assert.equal(emailType.value, "http://www.w3.org/2006/vcard/ns#Home") + }) + + it("should allow setting email type", () => { + const store = new Store() + + const email = new Email( + DataFactory.namedNode("https://example.org/email/2"), + store, + DataFactory + ) + + email.emailAddress = "test@example.org" + email.emailType = "http://www.w3.org/2006/vcard/ns#Home" + + assert.equal(email.emailType, "http://www.w3.org/2006/vcard/ns#Home") + }) + +}) diff --git a/test/unit/telephone.test.ts b/test/unit/telephone.test.ts new file mode 100644 index 0000000..29b8ebd --- /dev/null +++ b/test/unit/telephone.test.ts @@ -0,0 +1,105 @@ +import { DataFactory, Parser, Store } from "n3" +import assert from "node:assert" +import { describe, it } from "node:test" + +import { Telephone } from "@solid/object"; + +describe("Telephone tests", () => { + + const sampleRDF = ` +@prefix vcard: . + + + a vcard:Individual ; + vcard:fn "Alice" ; + vcard:hasTelephone . + + + a vcard:Telephone ; + vcard:hasValue "+1234567890" ; + vcard:TelephoneType vcard:Cell . +`; + + it("should parse and retrieve phone number", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + assert.equal(telephone.phoneNumber, "+1234567890") + assert.equal(typeof telephone.phoneNumber, "string") + }) + + it("should allow setting phone number", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + telephone.phoneNumber = "+0987654321" + + assert.equal(telephone.phoneNumber, "+0987654321") + }) + + it("should parse and retrieve phone type", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + const phoneType = telephone.phoneType + + assert.ok(phoneType !== undefined) + assert.equal(typeof phoneType, "string") + assert.equal(phoneType, "http://www.w3.org/2006/vcard/ns#Cell") + }) + + it("should allow setting phone type", () => { + const store = new Store() + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/2"), + store, + DataFactory + ) + + telephone.phoneNumber = "+1112223333" + telephone.phoneType = "http://www.w3.org/2006/vcard/ns#Car" + + assert.equal(telephone.phoneType, "http://www.w3.org/2006/vcard/ns#Car") + }) + + it("should throw when phone number is missing", () => { + const noPhoneRDF = ` +@prefix vcard: . + + + a vcard:Telephone . +` + const store = new Store() + store.addQuads(new Parser().parse(noPhoneRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/empty"), + store, + DataFactory + ) + + assert.throws(() => { + telephone.phoneNumber + }) + }) + +})