From a8c11bfb98dc397e198625ebdc5bf5955ccbbb78 Mon Sep 17 00:00:00 2001 From: Kristof Vandenbroucke Date: Wed, 4 Dec 2024 08:29:52 +0100 Subject: [PATCH] Sdtt 343 add an extra flag the ea converter that allows to export all tags from the uml diagram instead of a controlled selection (#88) * Temp commit * temp commit * Added support for `allTags` to export all possible Tagged Values from `EA` * Merge main * Cleanup --- packages/oslo-converter-uml-ea/README.md | 2 + .../lib/EaUmlConversionServiceRunner.ts | 5 + .../lib/config/EaUmlConverterConfiguration.ts | 20 ++- .../ElementConverterHandler.ts | 5 + .../lib/interfaces/ConverterHandler.ts | 132 +++++++++++++----- .../output-handlers/JsonLdOutputHandler.ts | 74 ++++++---- .../lib/utils/osloContext.ts | 4 + .../oslo-converter-uml-ea/lib/utils/utils.ts | 1 + packages/oslo-converter-uml-ea/package.json | 2 +- packages/oslo-core/lib/store/QuadStore.ts | 131 +++++++++++------ packages/oslo-extractor-uml-ea/package.json | 2 +- .../lib/TranslationService.ts | 7 +- .../lib/locales/es.json | 1 + .../test/TranslationService.unit.test.ts | 5 +- 14 files changed, 280 insertions(+), 111 deletions(-) create mode 100644 packages/oslo-generator-shacl-template/lib/locales/es.json diff --git a/packages/oslo-converter-uml-ea/README.md b/packages/oslo-converter-uml-ea/README.md index b834886..80de316 100644 --- a/packages/oslo-converter-uml-ea/README.md +++ b/packages/oslo-converter-uml-ea/README.md @@ -27,6 +27,7 @@ The service is executed from the CLI and expects the following parameters: | `--versionId` | Version identifier for the document | :heavy_check_mark: || | `--outputFile` | The name of the RDF output file | No, but if omitted, output is written to process.stdout || | `--outputFormat` | RDF content-type specifiying the output format | :heavy_check_mark: | `application/ld+json` | +| `--allTags` | Add all tags from EA to the generated output | No, default `false` | `true` or `false` | | `--debug` | A flag to enable debug mode which is more resilient to errors | | `false` | ## Usage @@ -34,4 +35,5 @@ The service is executed from the CLI and expects the following parameters: ```bash oslo-converter-ea --umlFile path/to/uml/diagram.eap --diagramName "diagramName" --versionId "test/1" --outputFile path/to/output.jsonld --outputFormat application/ld+json --publicationEnvironment https://data.vlaanderen.be oslo-converter-ea --umlFile https://github.com/path/to/uml/diagram.eap --diagramName "My UML diagram" --versionId "test/1" --outputFormat application/ld+json --publicationEnvironment https://data.vlaanderen.be +oslo-converter-ea --umlFile https://github.com/path/to/uml/diagram.eap --diagramName "My UML diagram" --versionId "test/1" --outputFormat application/ld+json --publicationEnvironment https://data.vlaanderen.be --allTags true ``` diff --git a/packages/oslo-converter-uml-ea/lib/EaUmlConversionServiceRunner.ts b/packages/oslo-converter-uml-ea/lib/EaUmlConversionServiceRunner.ts index 9aec998..a47a178 100644 --- a/packages/oslo-converter-uml-ea/lib/EaUmlConversionServiceRunner.ts +++ b/packages/oslo-converter-uml-ea/lib/EaUmlConversionServiceRunner.ts @@ -43,6 +43,11 @@ export class EaUmlConversionServiceRunner extends AppRunner< default: 'info', choices: LOG_LEVELS, }) + .option('allTags', { + describe: 'Add all tags from EA to the generated output', + default: false, + boolean: true, + }) .option('debug', { describe: 'A flag to enable debug mode which is more resilient to errors', diff --git a/packages/oslo-converter-uml-ea/lib/config/EaUmlConverterConfiguration.ts b/packages/oslo-converter-uml-ea/lib/config/EaUmlConverterConfiguration.ts index d33d419..2c8d010 100644 --- a/packages/oslo-converter-uml-ea/lib/config/EaUmlConverterConfiguration.ts +++ b/packages/oslo-converter-uml-ea/lib/config/EaUmlConverterConfiguration.ts @@ -37,6 +37,11 @@ export class EaUmlConverterConfiguration implements IConfiguration { */ private _debug: boolean | undefined; + /** + * A boolean that decided whether or not all tags will be added to the generated output + */ + private _allTags: boolean | undefined; + public async createFromCli(params: YargsParams): Promise { this._umlFile = params.umlFile; this._diagramName = params.diagramName; @@ -44,6 +49,7 @@ export class EaUmlConverterConfiguration implements IConfiguration { this._versionId = params.versionId; this._outputFormat = params.outputFormat; this._publicationEnvironment = params.publicationEnvironment; + this._allTags = params.allTags; this._debug = params.debug; } @@ -57,7 +63,7 @@ export class EaUmlConverterConfiguration implements IConfiguration { public get diagramName(): string { if (!this._diagramName) { throw new Error( - `Trying to access property "diagramName" before it was set.` + `Trying to access property "diagramName" before it was set.`, ); } return this._diagramName; @@ -66,7 +72,7 @@ export class EaUmlConverterConfiguration implements IConfiguration { public get outputFile(): string { if (!this._outputFile) { throw new Error( - `Trying to access property "outputFile" before it was set.` + `Trying to access property "outputFile" before it was set.`, ); } return this._outputFile; @@ -75,7 +81,7 @@ export class EaUmlConverterConfiguration implements IConfiguration { public get versionId(): string { if (!this._versionId) { throw new Error( - `Trying to access property "versionId" before it was set.` + `Trying to access property "versionId" before it was set.`, ); } return this._versionId; @@ -84,7 +90,7 @@ export class EaUmlConverterConfiguration implements IConfiguration { public get outputFormat(): string { if (!this._outputFormat) { throw new Error( - `Trying to access property "outputFormat" before it was set.` + `Trying to access property "outputFormat" before it was set.`, ); } return this._outputFormat; @@ -93,12 +99,16 @@ export class EaUmlConverterConfiguration implements IConfiguration { public get publicationEnvironment(): string { if (!this._publicationEnvironment) { throw new Error( - `Trying to access property "publicationEnvironment" before it was set.` + `Trying to access property "publicationEnvironment" before it was set.`, ); } return this._publicationEnvironment; } + public get allTags(): boolean { + return !!this._allTags; + } + public get debug(): boolean { return !!this._debug; } diff --git a/packages/oslo-converter-uml-ea/lib/converter-handlers/ElementConverterHandler.ts b/packages/oslo-converter-uml-ea/lib/converter-handlers/ElementConverterHandler.ts index 0b3b826..14529ea 100644 --- a/packages/oslo-converter-uml-ea/lib/converter-handlers/ElementConverterHandler.ts +++ b/packages/oslo-converter-uml-ea/lib/converter-handlers/ElementConverterHandler.ts @@ -15,6 +15,7 @@ import { TagNames } from '../enums/TagNames'; import { ConverterHandler } from '../interfaces/ConverterHandler'; import type { UriRegistry } from '../UriRegistry'; import { getTagValue, ignore, toPascalCase } from '../utils/utils'; +import { Language } from '@oslo-flanders/core/lib/enums/Language'; @injectable() export class ElementConverterHandler extends ConverterHandler { @@ -179,6 +180,10 @@ export class ElementConverterHandler extends ConverterHandler { this.addLabels(object, objectInternalId, this.df.defaultGraph(), quads); this.addUsageNotes(object, objectInternalId, this.df.defaultGraph(), quads); this.addStatus(object, objectInternalId, this.df.defaultGraph(), quads); + // Add the remaining tags that are not in TagNames enum if the config requires so. + if (this.config.allTags) { + this.addOtherTags(object, objectInternalId, this.df.defaultGraph(), quads); + } // To be able to determine the scope of the element, // we need to compare it to the base URI of the package diff --git a/packages/oslo-converter-uml-ea/lib/interfaces/ConverterHandler.ts b/packages/oslo-converter-uml-ea/lib/interfaces/ConverterHandler.ts index a187696..fffeb7c 100644 --- a/packages/oslo-converter-uml-ea/lib/interfaces/ConverterHandler.ts +++ b/packages/oslo-converter-uml-ea/lib/interfaces/ConverterHandler.ts @@ -14,6 +14,7 @@ import { EaUmlConverterServiceIdentifier } from '../config/EaUmlConverterService import { TagNames } from '../enums/TagNames'; import type { UriRegistry } from '../UriRegistry'; import { Status } from '../enums/Status'; +import { Language } from '@oslo-flanders/core/lib/enums/Language'; @injectable() export abstract class ConverterHandler { @@ -32,7 +33,7 @@ export abstract class ConverterHandler { public abstract convert( model: DataRegistry, uriRegistry: UriRegistry, - store: QuadStore + store: QuadStore, ): Promise; /** @@ -45,7 +46,7 @@ export abstract class ConverterHandler { */ public abstract assignUris( normalizedModel: DataRegistry, - uriRegistry: UriRegistry + uriRegistry: UriRegistry, ): Promise; /** @@ -54,7 +55,7 @@ export abstract class ConverterHandler { public abstract createQuads( object: T, uriRegistry: UriRegistry, - model?: DataRegistry + model?: DataRegistry, ): RDF.Quad[]; /** @@ -62,7 +63,7 @@ export abstract class ConverterHandler { * @param model - The data registry containing the entities */ public abstract filterIgnoredObjects( - model: DataRegistry + model: DataRegistry, ): Promise; /** @@ -76,42 +77,87 @@ export abstract class ConverterHandler { object: T, objectInternalId: RDF.NamedNode, quads: RDF.Quad[], - graph: RDF.Quad_Graph = this.df.defaultGraph() + graph: RDF.Quad_Graph = this.df.defaultGraph(), ): void { this.addDefinitions(object, objectInternalId, graph, quads); this.addLabels(object, objectInternalId, graph, quads); this.addUsageNotes(object, objectInternalId, graph, quads); this.addStatus(object, objectInternalId, graph, quads); + this.addOtherTags(object, objectInternalId, graph, quads); + } + + public addOtherTags( + object: T, + objectInternalId: RDF.NamedNode, + graph: RDF.Quad_Graph, + quads: RDF.Quad[], + ) { + const processedTagNames = new Set(Object.values(TagNames)); + const allTags: EaTag[] = object.tags || []; + + const unknownTags = allTags.filter((tag) => { + // Remove the language code suffix if it matches a known language code + const tagNameBase = this.getBaseTagName( + tag.tagName, + Object.values(Language), + ); + return !processedTagNames.has(tagNameBase); + }); + + if (unknownTags.length) { + this.logger.info( + `[ElementConverterHandler]: Unknown tags for element (${object.path}): ${unknownTags?.map((item) => item.tagName).join(', ')}. These tags will be added.`, + ); + unknownTags.forEach((tag) => { + const tagNameBase = this.getBaseTagName( + tag.tagName, + Object.values(Language), + ); + // Cast this unknown tag to a TagNames + const values: RDF.Literal[] = this.getTagValue( + object, + tagNameBase, + ); + + this.addValuesToQuads( + values, + objectInternalId, + ns.oslo(`any:${tagNameBase}`), + graph, + quads, + ); + }); + } } public addDefinitions( object: T, objectInternalId: RDF.NamedNode, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { const apDefinitions: RDF.Literal[] = this.getTagValue( object, - TagNames.ApDefinition + TagNames.ApDefinition, ); this.addValuesToQuads( apDefinitions, objectInternalId, ns.oslo('apDefinition'), graph, - quads + quads, ); const vocDefinitions: RDF.Literal[] = this.getTagValue( object, - TagNames.Definition + TagNames.Definition, ); this.addValuesToQuads( vocDefinitions, objectInternalId, ns.oslo('vocDefinition'), graph, - quads + quads, ); } @@ -119,7 +165,7 @@ export abstract class ConverterHandler { object: T, objectInternalId: RDF.NamedNode, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { const apLabels: RDF.Literal[] = this.getTagValue(object, TagNames.ApLabel); this.addValuesToQuads( @@ -127,7 +173,7 @@ export abstract class ConverterHandler { objectInternalId, ns.oslo('apLabel'), graph, - quads + quads, ); const vocLabels: RDF.Literal[] = this.getTagValue(object, TagNames.Label); @@ -136,7 +182,7 @@ export abstract class ConverterHandler { objectInternalId, ns.oslo('vocLabel'), graph, - quads + quads, ); // The name of the object as it appears on the diagram is also provided @@ -145,7 +191,7 @@ export abstract class ConverterHandler { objectInternalId, ns.oslo('diagramLabel'), graph, - quads + quads, ); } @@ -153,30 +199,30 @@ export abstract class ConverterHandler { object: T, objectInternalId: RDF.NamedNode, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { const apUsageNotes: RDF.Literal[] = this.getTagValue( object, - TagNames.ApUsageNote + TagNames.ApUsageNote, ); this.addValuesToQuads( apUsageNotes, objectInternalId, ns.oslo('apUsageNote'), graph, - quads + quads, ); const vocUsageNotes: RDF.Literal[] = this.getTagValue( object, - TagNames.UsageNote + TagNames.UsageNote, ); this.addValuesToQuads( vocUsageNotes, objectInternalId, ns.oslo('vocUsageNote'), graph, - quads + quads, ); } @@ -184,7 +230,7 @@ export abstract class ConverterHandler { object: T, objectInternalId: RDF.NamedNode, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { const status: RDF.Literal[] = this.getTagValue(object, TagNames.Status); @@ -201,12 +247,12 @@ export abstract class ConverterHandler { objectInternalId, ns.oslo('status'), this.df.namedNode(status[0].value), - graph - ) + graph, + ), ); } else { this.logger.warn( - `[ConverterHandler]: Incorrect status found for ${object.path}. The status will be ignored.` + `[ConverterHandler]: Incorrect status found for ${object.path}. The status will be ignored.`, ); } } @@ -225,7 +271,7 @@ export abstract class ConverterHandler { packageBaseUri: string, idUriMap: Map, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { const uri: URL | undefined = idUriMap.get(object.id); @@ -233,7 +279,7 @@ export abstract class ConverterHandler { if (!uri) { this.logger.warn( - `[ConverterHandler]: Unable to find the URI for object with path ${object.path}. Setting scope to "Undefined".` + `[ConverterHandler]: Unable to find the URI for object with path ${object.path}. Setting scope to "Undefined".`, ); scope = Scope.Undefined; return; @@ -252,11 +298,25 @@ export abstract class ConverterHandler { objectInternalId, ns.oslo('scope'), this.df.namedNode(scope), - graph - ) + graph, + ), ); } + /** + * Extract the value for a tag + * @param tagName - The name of the tag to extract the value for + * @param languages - An array of the possible LanguageCodes + * @returns - A string with the value of the tag without any language code suffix + */ + private getBaseTagName(tagName: string, languages: string[]): string { + const parts: string[] = tagName.split('-'); + const languageCode: string = parts[parts.length - 1]; + return languages.includes(languageCode) + ? parts.slice(0, -1).join('-') + : tagName; + } + /** * Extract the value for a tag * @param object - The entity to extract the tag values from @@ -280,9 +340,10 @@ export abstract class ConverterHandler { objectInternalId: RDF.NamedNode, predicate: RDF.NamedNode, graph: RDF.Quad_Graph, - quads: RDF.Quad[] + quads: RDF.Quad[], ): void { values.forEach((value) => { + // console.log(value); quads.push(this.df.quad(objectInternalId, predicate, value, graph)); }); } @@ -294,7 +355,7 @@ export abstract class ConverterHandler { */ private getLanguageDependentTag(object: T, name: TagNames): RDF.Literal[] { const tags: EaTag[] = object.tags.filter((x: EaTag) => - x.tagName.startsWith(name) + x.tagName.startsWith(name), ); const literals: RDF.Literal[] = []; @@ -302,19 +363,26 @@ export abstract class ConverterHandler { tags.forEach((tag: EaTag) => { const parts: string[] = tag.tagName.split('-'); - const languageCode: string = parts[parts.length - 1]; + let languageCode: string = parts[parts.length - 1]; + + // Add fallback for when the language is not defined to always pick the dutch language as default. Implemented as part of the + // https://vlaamseoverheid.atlassian.net/jira/software/projects/SDTT/issues/SDTT-343 solution + const knownLanguageCodes = Object.values(Language); + languageCode = knownLanguageCodes.includes(languageCode) + ? languageCode + : Language.NL; const tagValue: string = tag.tagValue; if (!tagValue) { this.logger.warn( - `[ConverterHandler]: Entity with path ${object.path} has an empty value for tag ${tag.tagName}.` + `[ConverterHandler]: Entity with path ${object.path} has an empty value for tag ${tag.tagName}.`, ); return; } if (languageToTagValueMap.has(languageCode)) { this.logger.warn( - `[ConverterHandler]: Entity with path ${object.path} has already a value for ${tag.tagName} in language ${languageCode}, but will be overwritten.` + `[ConverterHandler]: Entity with path ${object.path} has already a value for ${tag.tagName} in language ${languageCode}, but will be overwritten.`, ); } diff --git a/packages/oslo-converter-uml-ea/lib/output-handlers/JsonLdOutputHandler.ts b/packages/oslo-converter-uml-ea/lib/output-handlers/JsonLdOutputHandler.ts index fe355f0..0788665 100644 --- a/packages/oslo-converter-uml-ea/lib/output-handlers/JsonLdOutputHandler.ts +++ b/packages/oslo-converter-uml-ea/lib/output-handlers/JsonLdOutputHandler.ts @@ -33,7 +33,7 @@ export class JsonLdOutputHandler implements IOutputHandler { const versionIdQuad: RDF.Quad | undefined = store.findQuad( null, ns.prov('generatedAtTime'), - null + null, ); if (!versionIdQuad) { @@ -47,22 +47,22 @@ export class JsonLdOutputHandler implements IOutputHandler { private async getPackages(store: QuadStore): Promise { const packageIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), - ns.oslo('Package') + ns.oslo('Package'), ); return packageIds.map((id) => { const packageQuads: RDF.Quad[] = store.findQuads(id, null, null); const baseURIValue: RDF.Quad | undefined = packageQuads.find((x) => - x.predicate.equals(ns.oslo('baseURI')) + x.predicate.equals(ns.oslo('baseURI')), ); if (!baseURIValue) { throw new Error( - `Unable to find base URI for package with internal id ${id.value}` + `Unable to find base URI for package with internal id ${id.value}`, ); } const assignedURI: RDF.Quad | undefined = packageQuads.find((x) => - x.predicate.equals(ns.oslo('assignedURI')) + x.predicate.equals(ns.oslo('assignedURI')), ); return { '@id': id.value, @@ -81,7 +81,7 @@ export class JsonLdOutputHandler implements IOutputHandler { const classIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), ns.owl('Class'), - df.defaultGraph() + df.defaultGraph(), ); return classIds.reduce((jsonLdClasses, subject) => { @@ -97,6 +97,8 @@ export class JsonLdOutputHandler implements IOutputHandler { const parentQuads: RDF.NamedNode[] = store.getParentsOfClass(subject); const codelist: RDF.NamedNode | undefined = store.getCodelist(subject); const statuses: RDF.NamedNode | undefined = store.getStatus(subject); + const other: RDF.Quad[] | undefined = + store.getSanitizedOtherTags(subject); const usageNoteQuads: RDF.Quad[] = store.getUsageNotes(subject); @@ -109,6 +111,7 @@ export class JsonLdOutputHandler implements IOutputHandler { ...this.mapLabels(labelQuads), ...this.mapDefinitions(definitionQuads), ...this.mapUsageNotes(usageNoteQuads), + ...this.mapOtherTags(other), scope: scopeQuad?.value, ...(parentQuads.length > 0 && { parent: parentQuads.map((x) => ({ '@id': x.value })), @@ -126,15 +129,15 @@ export class JsonLdOutputHandler implements IOutputHandler { private async getAttributes(store: QuadStore): Promise { const dataTypeAttributeIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), - ns.owl('DatatypeProperty') + ns.owl('DatatypeProperty'), ); const objectPropertyAttributeIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), - ns.owl('ObjectProperty') + ns.owl('ObjectProperty'), ); const propertyAttributeIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), - ns.rdf('Property') + ns.rdf('Property'), ); return [ @@ -147,7 +150,7 @@ export class JsonLdOutputHandler implements IOutputHandler { const definitionQuads: RDF.Quad[] = store.getDefinitions(subject); const attributeTypeQuad: RDF.Term | undefined = store.findObject( subject, - ns.rdf('type') + ns.rdf('type'), ); const labelQuads: RDF.Quad[] = store.getLabels(subject); @@ -165,6 +168,8 @@ export class JsonLdOutputHandler implements IOutputHandler { store.getMaxCardinality(subject); const codelist: RDF.NamedNode | undefined = store.getCodelist(subject); const statuses: RDF.NamedNode | undefined = store.getStatus(subject); + const other: RDF.Quad[] | undefined = + store.getSanitizedOtherTags(subject); return { '@id': subject.value, @@ -175,6 +180,7 @@ export class JsonLdOutputHandler implements IOutputHandler { ...this.mapLabels(labelQuads), ...this.mapDefinitions(definitionQuads), ...this.mapUsageNotes(usageNoteQuads), + ...this.mapOtherTags(other), ...(domainQuad && { domain: { '@id': domainQuad.value, @@ -204,7 +210,7 @@ export class JsonLdOutputHandler implements IOutputHandler { private async getDatatypes(store: QuadStore): Promise { const datatypeIds: RDF.Term[] = store.findSubjects( ns.rdf('type'), - ns.rdfs('Datatype') + ns.rdfs('Datatype'), ); return datatypeIds.map((subject) => { const assignedURI: RDF.NamedNode | undefined = @@ -214,6 +220,8 @@ export class JsonLdOutputHandler implements IOutputHandler { const usageNoteQuads: RDF.Quad[] = store.getUsageNotes(subject); const scopeQuad: RDF.NamedNode | undefined = store.getScope(subject); const statuses: RDF.NamedNode | undefined = store.getStatus(subject); + const other: RDF.Quad[] | undefined = + store.getSanitizedOtherTags(subject); return { '@id': subject.value, @@ -224,6 +232,7 @@ export class JsonLdOutputHandler implements IOutputHandler { ...this.mapLabels(labelQuads), ...this.mapDefinitions(definitionQuads), ...this.mapUsageNotes(usageNoteQuads), + ...this.mapOtherTags(other), ...(scopeQuad && { scope: scopeQuad.value, }), @@ -241,7 +250,7 @@ export class JsonLdOutputHandler implements IOutputHandler { null, null, null, - referencedEntitiesGraph + referencedEntitiesGraph, ); const subjects = new Set(quads.map((x) => x.subject)); @@ -250,33 +259,35 @@ export class JsonLdOutputHandler implements IOutputHandler { subjects.forEach((subject) => { const assignedURI: RDF.NamedNode | undefined = store.getAssignedUri( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); const subjectType: RDF.Term | undefined = store.findObject( subject, ns.rdf('type'), - referencedEntitiesGraph + referencedEntitiesGraph, ); const definitions: RDF.Quad[] = store.getDefinitions( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); const labels: RDF.Quad[] = store.getLabels( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); const usageNotes: RDF.Quad[] = store.getUsageNotes( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); + const other: RDF.Quad[] | undefined = + store.getSanitizedOtherTags(subject); const scope: RDF.NamedNode | undefined = store.getScope( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); const statuses: RDF.NamedNode | undefined = store.getStatus( subject, - referencedEntitiesGraph + referencedEntitiesGraph, ); result.push({ @@ -290,6 +301,7 @@ export class JsonLdOutputHandler implements IOutputHandler { ...this.mapLabels(labels), ...this.mapDefinitions(definitions), ...this.mapUsageNotes(usageNotes), + ...this.mapOtherTags(other), ...this.mapStatuses(statuses), ...(scope && { scope: scope.value, @@ -301,13 +313,13 @@ export class JsonLdOutputHandler implements IOutputHandler { private mapLabels(labels: RDF.Quad[]): any { const vocLabels: RDF.Quad[] = labels.filter((x) => - x.predicate.equals(ns.oslo('vocLabel')) + x.predicate.equals(ns.oslo('vocLabel')), ); const apLabels: RDF.Quad[] = labels.filter((x) => - x.predicate.equals(ns.oslo('apLabel')) + x.predicate.equals(ns.oslo('apLabel')), ); const diagramLabels: RDF.Quad[] = labels.filter((x) => - x.predicate.equals(ns.oslo('diagramLabel')) + x.predicate.equals(ns.oslo('diagramLabel')), ); return { @@ -325,10 +337,10 @@ export class JsonLdOutputHandler implements IOutputHandler { private mapDefinitions(definitions: RDF.Quad[]): any { const vocDefinitions: RDF.Quad[] = definitions.filter((x) => - x.predicate.equals(ns.oslo('vocDefinition')) + x.predicate.equals(ns.oslo('vocDefinition')), ); const apDefinitions: RDF.Quad[] = definitions.filter((x) => - x.predicate.equals(ns.oslo('apDefinition')) + x.predicate.equals(ns.oslo('apDefinition')), ); return { @@ -341,12 +353,22 @@ export class JsonLdOutputHandler implements IOutputHandler { }; } + private mapOtherTags(other: RDF.Quad[]): any { + const result: any = {}; + + other.forEach((quad) => { + result[quad.predicate.value] = this.mapToLiteral(quad); + }); + + return result; + } + private mapUsageNotes(usageNotes: RDF.Quad[]): any { const vocUsageNotes: RDF.Quad[] = usageNotes.filter((x) => - x.predicate.equals(ns.oslo('vocUsageNote')) + x.predicate.equals(ns.oslo('vocUsageNote')), ); const apUsageNotes: RDF.Quad[] = usageNotes.filter((x) => - x.predicate.equals(ns.oslo('apUsageNote')) + x.predicate.equals(ns.oslo('apUsageNote')), ); return { diff --git a/packages/oslo-converter-uml-ea/lib/utils/osloContext.ts b/packages/oslo-converter-uml-ea/lib/utils/osloContext.ts index 2ca43db..09652bc 100644 --- a/packages/oslo-converter-uml-ea/lib/utils/osloContext.ts +++ b/packages/oslo-converter-uml-ea/lib/utils/osloContext.ts @@ -111,5 +111,9 @@ export function getOsloContext(): any { '@id': 'adms:status', '@type': '@id', }, + other: { + '@id': 'oslo:other', + '@type': '@id', + }, }; } diff --git a/packages/oslo-converter-uml-ea/lib/utils/utils.ts b/packages/oslo-converter-uml-ea/lib/utils/utils.ts index bf32a22..3053b22 100644 --- a/packages/oslo-converter-uml-ea/lib/utils/utils.ts +++ b/packages/oslo-converter-uml-ea/lib/utils/utils.ts @@ -71,6 +71,7 @@ export function updateNameTag(tags: EaTag[], connectorName: string): EaTag[] { return tags; } +// Historical usage. Geert Thijs used a lot of carets in his models function removeCaret(text: string): string { return text.replace(/^\^/u, ''); } diff --git a/packages/oslo-converter-uml-ea/package.json b/packages/oslo-converter-uml-ea/package.json index 16708d5..ace4c93 100644 --- a/packages/oslo-converter-uml-ea/package.json +++ b/packages/oslo-converter-uml-ea/package.json @@ -1,6 +1,6 @@ { "name": "@oslo-flanders/ea-converter", - "version": "0.0.39-alpha.0", + "version": "0.0.40-alpha.0", "description": "Transform an Enterprise Architect UML diagram to RDF", "author": "Digitaal Vlaanderen ", "homepage": "https://github.com/informatievlaanderen/OSLO-UML-Transformer/tree/main/packages/oslo-converter-uml-ea#readme", diff --git a/packages/oslo-core/lib/store/QuadStore.ts b/packages/oslo-core/lib/store/QuadStore.ts index 86d8306..32ee8a7 100644 --- a/packages/oslo-core/lib/store/QuadStore.ts +++ b/packages/oslo-core/lib/store/QuadStore.ts @@ -38,7 +38,7 @@ export class QuadStore { subject: RDF.Term | null, predicate: RDF.Term | null, object: RDF.Term | null, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Quad[] { return this.store.getQuads(subject, predicate, object, graph); } @@ -47,7 +47,7 @@ export class QuadStore { subject: RDF.Term | null, predicate: RDF.Term | null, object: RDF.Term | null, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Quad | undefined { return this.findQuads(subject, predicate, object, graph).shift(); } @@ -55,7 +55,7 @@ export class QuadStore { public findSubjects( predicate: RDF.Term, object: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Term[] { return this.store.getSubjects(predicate, object, graph); } @@ -63,7 +63,7 @@ export class QuadStore { public findSubject( predicate: RDF.Term, object: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Term | undefined { return this.findSubjects(predicate, object, graph).shift(); } @@ -71,7 +71,7 @@ export class QuadStore { public findObjects( subject: RDF.Term, predicate: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Term[] { return this.store.getObjects(subject, predicate, graph); } @@ -79,7 +79,7 @@ export class QuadStore { public findObject( subject: RDF.Term, predicate: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Term | undefined { return this.findObjects(subject, predicate, graph).shift(); } @@ -119,7 +119,7 @@ export class QuadStore { * @returns an array of RDF.NamedNodes */ public getDatatypePropertyIds( - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode[] { return ( this.store.getSubjects(ns.rdf('type'), ns.owl('DatatypeProperty'), graph) @@ -144,7 +144,7 @@ export class QuadStore { */ public getAssignedUri( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.oslo('assignedURI'), graph).shift() @@ -159,25 +159,25 @@ export class QuadStore { */ public getLabels( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Quad[] { const vocLabel: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('vocLabel'), null, - graph + graph, ); const apLabel: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('apLabel'), null, - graph + graph, ); const diagramLabel: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('diagramLabel'), null, - graph + graph, ); return vocLabel.concat(apLabel).concat(diagramLabel); } @@ -192,13 +192,13 @@ export class QuadStore { public getVocLabel( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getLabels(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('vocLabel')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -213,13 +213,13 @@ export class QuadStore { public getApLabel( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getLabels(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('apLabel')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -233,11 +233,11 @@ export class QuadStore { */ public getDiagramLabel( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getLabels(subject, graph).find((x: RDF.Quad) => - x.predicate.equals(ns.oslo('diagramLabel')) + x.predicate.equals(ns.oslo('diagramLabel')), )?.object ); } @@ -250,20 +250,20 @@ export class QuadStore { */ public getDefinitions( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Quad[] { const vocDefinitions: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('vocDefinition'), null, - graph + graph, ); const apDefinitions: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('apDefinition'), null, - graph + graph, ); return vocDefinitions.concat(apDefinitions); } @@ -278,13 +278,13 @@ export class QuadStore { public getVocDefinition( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getDefinitions(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('vocDefinition')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -299,13 +299,13 @@ export class QuadStore { public getApDefinition( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getDefinitions(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('apDefinition')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -318,7 +318,7 @@ export class QuadStore { */ public getRange( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.rdfs('range'), graph).shift() @@ -333,7 +333,7 @@ export class QuadStore { */ public getDomain( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.rdfs('domain'), graph).shift() @@ -348,20 +348,20 @@ export class QuadStore { */ public getUsageNotes( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Quad[] { const vocUsageNotes: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('vocUsageNote'), null, - graph + graph, ); const apUsageNotes: RDF.Quad[] = this.store.getQuads( subject, ns.oslo('apUsageNote'), null, - graph + graph, ); return vocUsageNotes.concat(apUsageNotes); } @@ -376,13 +376,13 @@ export class QuadStore { public getVocUsageNote( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getUsageNotes(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('vocUsageNote')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -397,13 +397,13 @@ export class QuadStore { public getApUsageNote( subject: RDF.Term, language?: string, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.getUsageNotes(subject, graph).find( (x: RDF.Quad) => x.predicate.equals(ns.oslo('apUsageNote')) && - (x.object).language === (language || '') + (x.object).language === (language || ''), )?.object ); } @@ -415,7 +415,7 @@ export class QuadStore { */ public getScope( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.oslo('scope'), graph).shift() @@ -429,7 +429,7 @@ export class QuadStore { */ public getMinCardinality( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.store.getObjects(subject, ns.shacl('minCount'), graph).shift() @@ -443,7 +443,7 @@ export class QuadStore { */ public getMaxCardinality( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.Literal | undefined { return ( this.store.getObjects(subject, ns.shacl('maxCount'), graph).shift() @@ -458,7 +458,7 @@ export class QuadStore { */ public getParentsOfClass( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode[] { return ( this.store.getObjects(subject, ns.rdfs('subClassOf'), graph) @@ -473,7 +473,7 @@ export class QuadStore { */ public getParentOfProperty( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.rdfs('subPropertyOf'), graph).shift() @@ -482,7 +482,7 @@ export class QuadStore { public getCodelist( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.oslo('codelist'), graph).shift() @@ -497,10 +497,59 @@ export class QuadStore { */ public getStatus( subject: RDF.Term, - graph: RDF.Term | null = null + graph: RDF.Term | null = null, ): RDF.NamedNode | undefined { return ( this.store.getObjects(subject, ns.adms('status'), graph).shift() ); } + + /** + * Finds all the other tags. + * This could be any tag that is not a label, definition, usage note, scope, minCardinality, maxCardinality, parent, range, domain, status or codelist + * @param subject The RDF.Term to find the other tags for + * @param store A N3 quad store + * @returns An array of RDF.Literals + */ + public getOtherTags( + subject: RDF.Term, + graph: RDF.NamedNode | null = null, + ): RDF.Quad[] { + const allQuads: RDF.Quad[] = this.store.getQuads( + subject, + null, + null, + graph, + ); + + // Filter quads where the predicate includes 'any' partially + const anyQuads: RDF.Quad[] = allQuads.filter((quad) => + quad.predicate.value.includes(ns.oslo('any').value), + ); + return anyQuads; + } + + /** + * Use the other tags method from earlier and sanitize the results so the ns('any') is removed + * @param subject The RDF.Term to find the other tags for + * @param store A N3 quad store + * @returns An array of RDF.Literals + */ + public getSanitizedOtherTags( + subject: RDF.Term, + graph: RDF.NamedNode | null = null, + ): RDF.Quad[] { + const quads: RDF.Quad[] = this.getOtherTags(subject, graph); + return quads.map((quad) => { + const sanitizedPredicate = N3.DataFactory.namedNode( + quad.predicate.value.replace(`${ns.oslo('any').value}:`, ''), + ); + return N3.DataFactory.quad( + quad.subject, + sanitizedPredicate, + quad.object, + quad.graph, + ); + }); + } } diff --git a/packages/oslo-extractor-uml-ea/package.json b/packages/oslo-extractor-uml-ea/package.json index 01768a4..559baf6 100644 --- a/packages/oslo-extractor-uml-ea/package.json +++ b/packages/oslo-extractor-uml-ea/package.json @@ -1,6 +1,6 @@ { "name": "@oslo-flanders/ea-uml-extractor", - "version": "0.0.31-alpha.0", + "version": "0.0.32-alpha.0", "description": "Reads all the information from an Enterprise Architect UML diagram", "author": "Digitaal Vlaanderen ", "homepage": "https://github.com/informatievlaanderen/OSLO-UML-Transformer/tree/main/packages/oslo-extract-uml-ea#readme", diff --git a/packages/oslo-generator-shacl-template/lib/TranslationService.ts b/packages/oslo-generator-shacl-template/lib/TranslationService.ts index 0715863..2914e5c 100644 --- a/packages/oslo-generator-shacl-template/lib/TranslationService.ts +++ b/packages/oslo-generator-shacl-template/lib/TranslationService.ts @@ -4,6 +4,7 @@ import { ShaclTemplateGenerationServiceIdentifier } from './config/ShaclTemplate import { inject, injectable } from 'inversify'; import { TranslationKey } from './enums/TranslationKey'; import { TranslationConfig } from './types/TranslationConfig'; +import { Language } from '@oslo-flanders/core/lib/enums/Language'; @injectable() export class TranslationService { @@ -11,11 +12,11 @@ export class TranslationService { private readonly i18n: I18n; public constructor( - @inject(ShaclTemplateGenerationServiceIdentifier.Logger) logger: Logger + @inject(ShaclTemplateGenerationServiceIdentifier.Logger) logger: Logger, ) { this.logger = logger; this.i18n = new I18n({ - locales: ['nl', 'en', 'fr', 'de'], + locales: Object.values(Language), directory: `${__dirname}/locales`, defaultLocale: 'nl', }); @@ -24,4 +25,4 @@ export class TranslationService { public translate(key: TranslationKey, config: TranslationConfig): string { return this.i18n.__mf(key, config); } -} \ No newline at end of file +} diff --git a/packages/oslo-generator-shacl-template/lib/locales/es.json b/packages/oslo-generator-shacl-template/lib/locales/es.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/oslo-generator-shacl-template/lib/locales/es.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/oslo-generator-shacl-template/test/TranslationService.unit.test.ts b/packages/oslo-generator-shacl-template/test/TranslationService.unit.test.ts index 3baca4c..92271e0 100644 --- a/packages/oslo-generator-shacl-template/test/TranslationService.unit.test.ts +++ b/packages/oslo-generator-shacl-template/test/TranslationService.unit.test.ts @@ -4,6 +4,7 @@ import 'reflect-metadata'; import { VoidLogger } from '@oslo-flanders/core'; import type { Logger } from '@oslo-flanders/core'; +import { Language } from '@oslo-flanders/core/lib/enums/Language'; import { I18n } from 'i18n'; import { TranslationKey } from '../lib/enums/TranslationKey'; import { TranslationService } from '../lib/TranslationService'; @@ -20,7 +21,7 @@ describe('TranslationService', () => { service = new TranslationService(logger); i18n = new I18n({ - locales: ['nl', 'en', 'fr', 'de'], + locales: Object.values(Language), directory: `${__dirname}/../lib/locales`, defaultLocale: 'nl', }); @@ -44,4 +45,4 @@ describe('TranslationService', () => { // This assumes that the `__mf` method of the i18n instance returns a string expect(translatedString).toBe(i18n.__mf(key, config)); }); -}); \ No newline at end of file +});