From 332f7393a2ad3078d34eed79e87c4995d5fd1eb3 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 9 Nov 2023 14:32:25 +0200 Subject: [PATCH] Add optional direction for literals --- README.md | 16 ++++++++++++++- lib/DataFactory.ts | 9 ++++++--- lib/Literal.ts | 28 ++++++++++++++++++++++----- test/DataFactory-test.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0ec31d6..e0e98f1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Concretely, it provides an implementation of the following interfaces: * [`DataFactory`](http://rdf.js.org/data-model-spec/#datafactory-interface): A factory for instantiating RDF terms and quads. * [`NamedNode`](http://rdf.js.org/data-model-spec/#namednode-interface): A term that contains an IRI. * [`BlankNode`](http://rdf.js.org/data-model-spec/#blanknode-interface): A term that represents an RDF blank node with a label. -* [`Literal`](http://rdf.js.org/data-model-spec/#literal-interface): A term that represents an RDF literal, containing a string with an optional language tag or datatype. +* [`Literal`](http://rdf.js.org/data-model-spec/#literal-interface): A term that represents an RDF literal, containing a string with an optional language tag and optional direction or datatype. * [`Variable`](http://rdf.js.org/data-model-spec/#variable-interface): A term that represents a variable. * [`DefaultGraph`](http://rdf.js.org/data-model-spec/#defaultgraph-interface): A singleton term instance that represents the default graph. @@ -87,6 +87,7 @@ const term: RDF.Literal = factory.literal('abc'); console.log(term.value); // 'abc' console.log(term.termType); // 'Literal' console.log(term.language); // '' +console.log(term.direction); // '' console.log(term.datatype); // namedNode('http://www.w3.org/2001/XMLSchema#string') console.log(term.equals(term)); // true ``` @@ -97,16 +98,29 @@ const term: RDF.Literal = factory.literal('abc', 'en-us'); console.log(term.value); // 'abc' console.log(term.termType); // 'Literal' console.log(term.language); // 'en-us' +console.log(term.direction); // '' console.log(term.datatype); // namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString') console.log(term.equals(term)); // true ``` +Directional languaged tagged string literal: +```typescript +const term: RDF.Literal = factory.literal('abc', { language: 'en-us', direction: 'ltr' }); +console.log(term.value); // 'abc' +console.log(term.termType); // 'Literal' +console.log(term.language); // 'en-us' +console.log(term.direction); // 'ltr' +console.log(term.datatype); // namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString') +console.log(term.equals(term)); // true +``` + Datatyped literal: ```typescript const term: RDF.Literal = factory.literal('1.2', factory.namedNode('http://www.w3.org/2001/XMLSchema#double')); console.log(term.value); // 'abc' console.log(term.termType); // 'Literal' console.log(term.language); // '' +console.log(term.direction); // '' console.log(term.datatype); // namedNode('http://www.w3.org/2001/XMLSchema#double') console.log(term.equals(term)); // true ``` diff --git a/lib/DataFactory.ts b/lib/DataFactory.ts index 9918f5c..87db460 100644 --- a/lib/DataFactory.ts +++ b/lib/DataFactory.ts @@ -42,15 +42,18 @@ export class DataFactory implements RDF.DataF /** * @param value The literal value. - * @param languageOrDatatype The optional language or datatype. + * @param languageOrDatatype The optional language, datatype, or directional language. * If `languageOrDatatype` is a NamedNode, * then it is used for the value of `NamedNode.datatype`. - * Otherwise `languageOrDatatype` is used for the value + * If `languageOrDatatype` is a NamedNode, it is used for the value * of `NamedNode.language`. + * Otherwise, it is used as a directional language, + * from which the language is set to `languageOrDatatype.language` + * and the direction to `languageOrDatatype.direction`. * @return A new instance of Literal. * @see Literal */ - public literal(value: string, languageOrDatatype?: string | RDF.NamedNode): Literal { + public literal(value: string, languageOrDatatype?: string | RDF.NamedNode | RDF.DirectionalLanguage): Literal { return new Literal(value, languageOrDatatype); } diff --git a/lib/Literal.ts b/lib/Literal.ts index f0d5fd8..f9aae67 100644 --- a/lib/Literal.ts +++ b/lib/Literal.ts @@ -2,36 +2,54 @@ import type * as RDF from '@rdfjs/types'; import { NamedNode } from './NamedNode'; /** - * A term that represents an RDF literal, containing a string with an optional language tag or datatype. + * A term that represents an RDF literal, + * containing a string with an optional language tag and optional direction + * or datatype. */ export class Literal implements RDF.Literal { public readonly termType = 'Literal'; public readonly value: string; public readonly language: string; public readonly datatype: RDF.NamedNode; + public readonly direction: 'ltr' | 'rtl' | ''; public static readonly RDF_LANGUAGE_STRING: RDF.NamedNode = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString'); + public static readonly RDF_DIRECTIONAL_LANGUAGE_STRING: RDF.NamedNode = + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString'); + public static readonly XSD_STRING: RDF.NamedNode = new NamedNode('http://www.w3.org/2001/XMLSchema#string'); - public constructor(value: string, languageOrDatatype?: string | RDF.NamedNode) { + public constructor(value: string, languageOrDatatype?: string | RDF.NamedNode | RDF.DirectionalLanguage) { this.value = value; if (typeof languageOrDatatype === 'string') { this.language = languageOrDatatype; this.datatype = Literal.RDF_LANGUAGE_STRING; + this.direction = ''; } else if (languageOrDatatype) { - this.language = ''; - this.datatype = languageOrDatatype; + if ('termType' in languageOrDatatype) { + this.language = ''; + this.datatype = languageOrDatatype; + this.direction = ''; + } else { + this.language = languageOrDatatype.language; + this.datatype = languageOrDatatype.direction ? + Literal.RDF_DIRECTIONAL_LANGUAGE_STRING : + Literal.RDF_LANGUAGE_STRING; + this.direction = languageOrDatatype.direction || ''; + } } else { this.language = ''; this.datatype = Literal.XSD_STRING; + this.direction = ''; } } public equals(other?: RDF.Term | null): boolean { return !!other && other.termType === 'Literal' && other.value === this.value && - other.language === this.language && this.datatype.equals(other.datatype); + other.language === this.language && other.direction === this.direction && + this.datatype.equals(other.datatype); } } diff --git a/test/DataFactory-test.ts b/test/DataFactory-test.ts index cba7ee3..8a94edf 100644 --- a/test/DataFactory-test.ts +++ b/test/DataFactory-test.ts @@ -75,6 +75,7 @@ describe('DataFactory', () => { expect(literal.value).toEqual('abc'); expect(literal.language).toEqual(''); expect(literal.datatype).toEqual(factory.namedNode('http://www.w3.org/2001/XMLSchema#string')); + expect(literal.direction).toEqual(''); }); it('should produce a valid language tagged literal', () => { @@ -83,6 +84,34 @@ describe('DataFactory', () => { expect(literal.value).toEqual('abc'); expect(literal.language).toEqual('en-us'); expect(literal.datatype).toEqual(factory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')); + expect(literal.direction).toEqual(''); + }); + + it('should produce a valid language tagged literal in extended form', () => { + const literal: RDF.Literal = factory.literal('abc', { language: 'en-us' }); + expect(literal.termType).toEqual('Literal'); + expect(literal.value).toEqual('abc'); + expect(literal.language).toEqual('en-us'); + expect(literal.datatype).toEqual(factory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')); + expect(literal.direction).toEqual(''); + }); + + it('should produce a valid language tagged literal with ltr direction', () => { + const literal: RDF.Literal = factory.literal('abc', { language: 'en-us', direction: 'ltr' }); + expect(literal.termType).toEqual('Literal'); + expect(literal.value).toEqual('abc'); + expect(literal.language).toEqual('en-us'); + expect(literal.datatype).toEqual(factory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString')); + expect(literal.direction).toEqual('ltr'); + }); + + it('should produce a valid language tagged literal with rtl direction', () => { + const literal: RDF.Literal = factory.literal('abc', { language: 'en-us', direction: 'rtl' }); + expect(literal.termType).toEqual('Literal'); + expect(literal.value).toEqual('abc'); + expect(literal.language).toEqual('en-us'); + expect(literal.datatype).toEqual(factory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString')); + expect(literal.direction).toEqual('rtl'); }); it('should produce a valid datatyped literal', () => { @@ -91,6 +120,7 @@ describe('DataFactory', () => { expect(literal.value).toEqual('abc'); expect(literal.language).toEqual(''); expect(literal.datatype).toEqual(factory.namedNode('ex:dt')); + expect(literal.direction).toEqual(''); }); it('should handle equals', () => { @@ -104,6 +134,11 @@ describe('DataFactory', () => { .equals(factory.literal('a', 'en-us'))).toEqual(true); expect(factory.literal('a', factory.namedNode('ex:dt')) .equals(factory.literal('a', factory.namedNode('ex:dt')))).toEqual(true); + expect(factory.literal('a', { language: 'en-us' }) + .equals(factory.literal('a', 'en-us'))).toEqual(true); + + expect(factory.literal('a', { language: 'en-us', direction: 'ltr' }) + .equals(factory.literal('a', { language: 'en-us', direction: 'ltr' }))).toEqual(true); expect(factory.literal('a') .equals(factory.literal('a', 'en-us'))).toEqual(false); @@ -112,6 +147,13 @@ describe('DataFactory', () => { expect(factory.literal('a') .equals(factory.literal('a', factory.namedNode('http://www.w3.org/2001/XMLSchema#string')))).toEqual(true); + expect(factory.literal('a', { language: 'en-us', direction: 'ltr' }) + .equals(factory.literal('a', { language: 'en-us', direction: 'rtl' }))).toEqual(false); + expect(factory.literal('a', { language: 'en-us' }) + .equals(factory.literal('a', { language: 'en-us', direction: 'rtl' }))).toEqual(false); + expect(factory.literal('a', 'en-us') + .equals(factory.literal('a', { language: 'en-us', direction: 'rtl' }))).toEqual(false); + expect(factory.literal('a', 'en-us') .equals(factory.literal('a'))).toEqual(false); expect(factory.literal('a', 'en-us')