Skip to content

Commit

Permalink
Merge pull request #50 from Informatievlaanderen/SDTT-219-Create-pack…
Browse files Browse the repository at this point in the history
…age-to-transform-OSLO-JSON-LD-to-JSON-template

SDTT-219 Create package to transform oslo json ld to json template
  • Loading branch information
ddvlanck authored Mar 13, 2024
2 parents a8726f8 + cce2fed commit a427b1b
Show file tree
Hide file tree
Showing 21 changed files with 1,112 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export class AttributeConverterHandler extends ConverterHandler<EaAttribute> {
attributeInternalId,
packageBaseUri.toString(),
uriRegistry.attributeIdUriMap,
this.df.defaultGraph(),
quads,
);

Expand Down Expand Up @@ -254,6 +255,7 @@ export class AttributeConverterHandler extends ConverterHandler<EaAttribute> {
...this.getElementInformationQuads(
this.df.namedNode(rangeURI),
uriRegistry,
model,
rangeElement,
),
);
Expand Down Expand Up @@ -354,6 +356,7 @@ export class AttributeConverterHandler extends ConverterHandler<EaAttribute> {
private getElementInformationQuads(
rangeInternalId: RDF.NamedNode,
uriRegistry: UriRegistry,
dataRegistry: DataRegistry,
rangeElement: EaElement,
): RDF.Quad[] {
const quads: RDF.Quad[] = [];
Expand Down Expand Up @@ -395,6 +398,22 @@ export class AttributeConverterHandler extends ConverterHandler<EaAttribute> {
),
);

const packageBaseUri = uriRegistry.packageIdUriMap.get(dataRegistry.targetDiagram.packageId);
if (!packageBaseUri) {
throw new Error(
`[AttributeConverterHandler]: Unable to find the URI of package where target diagram (${dataRegistry.targetDiagram.path}) is placed.`,
);
}

this.addScope(
<any>rangeElement,
rangeInternalId,
packageBaseUri.toString(),
uriRegistry.elementIdUriMap,
referencedEntitiesGraph,
quads,
)

const skosCodelist: string | null = getTagValue(rangeElement, TagNames.ApCodelist, null);
if (skosCodelist) {
quads.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export class ConnectorConverterHandler extends ConverterHandler<NormalizedConnec
connectorInternalId,
packageBaseUri.toString(),
uriRegistry.connectorOsloIdUriMap,
this.df.defaultGraph(),
quads,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class ElementConverterHandler extends ConverterHandler<EaElement> {
objectInternalId,
packageBaseUri.toString(),
uriRegistry.elementIdUriMap,
this.df.defaultGraph(),
quads,
);

Expand Down Expand Up @@ -287,6 +288,21 @@ export class ElementConverterHandler extends ConverterHandler<EaElement> {
quads,
);

const packageBaseUri = uriRegistry.packageIdUriMap.get(model.targetDiagram.packageId);
if (!packageBaseUri) {
throw new Error(
`[AttributeConverterHandler]: Unable to find the URI of package where target diagram (${model.targetDiagram.path}) is placed.`,
);
}
this.addScope(
parentClassObject,
parentInternalId,
packageBaseUri.toString(),
uriRegistry.elementIdUriMap,
referencedEntitiesGraph,
quads,
)

quads.push(
this.df.quad(
parentInternalId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export abstract class ConverterHandler<T extends EaObject> {
objectInternalId: RDF.NamedNode,
packageBaseUri: string,
idUriMap: Map<number, URL>,
graph: RDF.Quad_Graph,
quads: RDF.Quad[],
): void {
const uri: URL | undefined = idUriMap.get(object.id);
Expand All @@ -202,7 +203,7 @@ export abstract class ConverterHandler<T extends EaObject> {
}

quads.push(
this.df.quad(objectInternalId, ns.oslo('scope'), this.df.namedNode(scope)),
this.df.quad(objectInternalId, ns.oslo('scope'), this.df.namedNode(scope), graph),
);
}

Expand Down
21 changes: 21 additions & 0 deletions packages/oslo-core/lib/utils/storeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,24 @@ export function getVocabularyUsageNote(
return undefined;
}

export function getMinCount(
subject: RDF.Term,
store: QuadStore,
): string | undefined {
return store.findObject(subject, ns.shacl('minCount'))?.value;
}

export function getMaxCount(
subject: RDF.Term,
store: QuadStore,
): string | undefined {
return store.findObject(subject, ns.shacl('maxCount'))?.value;
}

export function getCodelist(
subject: RDF.Term,
store: QuadStore,
): string | undefined {
return store.findObject(subject, ns.oslo('codelist'))?.value;
}

29 changes: 29 additions & 0 deletions packages/oslo-generator-json-webuniversum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `oslo-generator-json-webuniversum` `OSLO JSON Webuniversum Generator

> This package transforms an OSLO JSON-LD file into a JSON configuration file which will be used by the Nuxt.JS Service to render vocabularies and application profiles
## Install

```bash
npm install @oslo-flanders/json-webuniversum-generator
```

## Global install

```bash
npm install -g @oslo-flanders/json-webuniversum-generator
```

## API

The service is executed from the CLI and expects the following parameters:
| Parameter | Description | Required | Possible values |
| ---------- | ----------------------------------------------------------- | --------------------------------- | --------------- |
| `--input` | The URL or local file path of an OSLO stakeholders csv file |:heavy_check_mark: | |
| `--output` | Name of the output file | No, default `webuniversum-config.json` | |
| `--language` | The language in which the config must be generated | :heavy_check_mark: | |

## Usage
```bash
oslo-webuniversum-json-generator --input report.jsonld --language nl
```
7 changes: 7 additions & 0 deletions packages/oslo-generator-json-webuniversum/bin/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import 'module-alias/register';
import {
JsonWebuniversumGenerationServiceRunner,
} from '@oslo-generator-json-webuniversum/JsonWebuniversumGenerationServiceRunner';
// eslint-disable-next-line no-sync
new JsonWebuniversumGenerationServiceRunner().runCliSync(process);
10 changes: 10 additions & 0 deletions packages/oslo-generator-json-webuniversum/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'reflect-metadata';
import 'module-alias/register';

export * from '@oslo-generator-json-webuniversum/JsonWebuniversumGenerationService';
export * from '@oslo-generator-json-webuniversum/JsonWebuniversumGenerationServiceRunner';
export * from '@oslo-generator-json-webuniversum/config/DependencyInjectionConfig';
export * from '@oslo-generator-json-webuniversum/config/JsonWebuniversumGenerationServiceConfiguration';
export * from '@oslo-generator-json-webuniversum/utils/utils';
export * from '@oslo-generator-json-webuniversum/types/WebuniversumObject';
export * from '@oslo-generator-json-webuniversum/types/WebuniversumProperty';
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import {
ServiceIdentifier,
type IService,
type Logger,
QuadStore,
getApplicationProfileLabel,
getVocabularyLabel,
getVocabularyDefinition,
getApplicationProfileDefinition,
getVocabularyUsageNote,
getApplicationProfileUsageNote,
ns,
getMinCount,
getMaxCount,
getCodelist
} from "@oslo-flanders/core";
import { inject, injectable } from "inversify";
import { JsonWebuniversumGenerationServiceConfiguration } from "@oslo-generator-json-webuniversum/config/JsonWebuniversumGenerationServiceConfiguration";
import type * as RDF from '@rdfjs/types';
import { writeFile } from "fs/promises";
import { WebuniversumObject } from "@oslo-generator-json-webuniversum/types/WebuniversumObject";
import { WebuniversumProperty } from "@oslo-generator-json-webuniversum/types/WebuniversumProperty";
import { sortWebuniversumObjects } from "@oslo-generator-json-webuniversum/utils/utils";

@injectable()
export class JsonWebuniversumGenerationService implements IService {
public readonly logger: Logger;
public readonly configuration: JsonWebuniversumGenerationServiceConfiguration;
public readonly store: QuadStore;

public constructor(
@inject(ServiceIdentifier.Logger) logger: Logger,
@inject(ServiceIdentifier.Configuration) configuration: JsonWebuniversumGenerationServiceConfiguration,
@inject(ServiceIdentifier.QuadStore) store: QuadStore
) {
this.logger = logger;
this.configuration = configuration;
this.store = store;
}

public async init(): Promise<void> {
return this.store.addQuadsFromFile(this.configuration.input);
}

public async run(): Promise<void> {
const classJobs = this.store.getClassIds()
.map(classId => this.generateEntityData(classId));

const datatypeJobs = this.store.findSubjects(ns.rdf('type'), ns.rdfs('Datatype'))
.map(datatypeId => this.generateEntityData(<RDF.NamedNode>datatypeId));

const classes: WebuniversumObject[] = await Promise.all(classJobs)
const datatypes: WebuniversumObject[] = await Promise.all(datatypeJobs)

// Sort entities
sortWebuniversumObjects(classes, this.configuration.language);
classes.forEach((classObject: WebuniversumObject) => sortWebuniversumObjects(classObject.properties || [], this.configuration.language));

sortWebuniversumObjects(datatypes, this.configuration.language);
datatypes.forEach((datatypeObject: WebuniversumObject) => sortWebuniversumObjects(datatypeObject.properties || [], this.configuration.language));

const baseURI = this.getBaseURI();

const template = { baseURI, classes: classes, dataTypes: datatypes };
await writeFile(this.configuration.output, JSON.stringify(template, null, 2), 'utf-8');
}

private getBaseURI(): string {
const packageSubject: RDF.Term = <RDF.Term>this.store.findQuad(null, ns.rdf('type'), ns.oslo('Package'))?.subject;

if (!packageSubject) {
throw new Error(`Unable to find the subject for the package.`);
}

const baseURIObject: RDF.Literal | undefined = <RDF.Literal | undefined>this.store.findObject(packageSubject, ns.oslo('baseURI'));

if (!baseURIObject) {
throw new Error(`Unable to find the baseURI for the package.`);
}

return baseURIObject.value;
}

private async generateEntityData(entity: RDF.NamedNode, includeProperties: boolean = true): Promise<WebuniversumObject> {
const assignedURI: RDF.NamedNode | undefined = this.store.getAssignedUri(entity);
if (!assignedURI) {
throw new Error(`Unable to find the assigned URI for entity ${entity.value}.`);
}

const fetchProperty = (fetchFunction: (subjectId: RDF.NamedNode, store: QuadStore, language: string) => RDF.Literal | undefined, subject: RDF.NamedNode) =>
fetchFunction(subject, this.store, this.configuration.language)?.value;

const vocabularyLabel: string | undefined = fetchProperty(getVocabularyLabel, entity);
const applicationProfileLabel: string | undefined = fetchProperty(getApplicationProfileLabel, entity);
const vocabularyDefinition: string | undefined = fetchProperty(getVocabularyDefinition, entity);
const applicationProfileDefinition: string | undefined = fetchProperty(getApplicationProfileDefinition, entity);
const vocabularyUsageNote: string | undefined = fetchProperty(getVocabularyUsageNote, entity);
const applicationProfileUsageNote: string | undefined = fetchProperty(getApplicationProfileUsageNote, entity);

const parentsIds: RDF.NamedNode[] = includeProperties ? this.store.getParentsOfClass(entity) : this.store.getParentOfProperty(entity) !== undefined ? [this.store.getParentOfProperty(entity)!] : [];
const parentObjects: Pick<WebuniversumObject, 'id' | 'vocabularyLabel' | 'applicationProfileLabel'>[] = parentsIds.map(parentId => this.createParentObject(parentId));

const scope: string | undefined = this.store.getScope(entity)?.value;

const entityData: WebuniversumObject = {
id: assignedURI.value,
...vocabularyLabel && {
vocabularyLabel: { [this.configuration.language]: vocabularyLabel },
},
...applicationProfileLabel && {
applicationProfileLabel: { [this.configuration.language]: applicationProfileLabel },
},
...vocabularyDefinition && {
vocabularyDefinition: { [this.configuration.language]: vocabularyDefinition },
},
...applicationProfileDefinition && {
applicationProfileDefinition: { [this.configuration.language]: applicationProfileDefinition },
},
...vocabularyUsageNote && {
vocabularyUsageNote: { [this.configuration.language]: vocabularyUsageNote },
},
...applicationProfileUsageNote && {
applicationProfileUsageNote: { [this.configuration.language]: applicationProfileUsageNote },
},
...parentObjects.length > 0 && {
parents: parentObjects
},
...scope && {
scope: scope,
}
};

if (includeProperties) {
const jobs: Promise<WebuniversumProperty>[] = this.store.findSubjects(ns.rdfs('domain'), entity)
.map<Promise<WebuniversumProperty>>(async (property: RDF.Term) => {
return <Promise<WebuniversumProperty>>this.generateEntityData(<RDF.NamedNode>property, false)
.then((propertyObject) => this.addPropertySpecificInformation(<RDF.NamedNode>property, <WebuniversumProperty>propertyObject, assignedURI));
});

const properties: WebuniversumProperty[] = await Promise.all(jobs)

if (properties.length) {
entityData.properties = properties;
}
}

return entityData;
}

private addPropertySpecificInformation(subject: RDF.NamedNode, propertyObject: WebuniversumProperty, domainId: RDF.NamedNode): WebuniversumProperty {
propertyObject.domain = domainId.value;

const range: RDF.NamedNode | undefined = this.store.getRange(subject);
if (!range) {
throw new Error(`No range found for class ${subject.value}.`);
}

const rangeAssignedURI: RDF.NamedNode | undefined = this.store.getAssignedUri(range);
if (!rangeAssignedURI) {
throw new Error(`Unable to find the assigned URI for range ${range.value} of attribute ${subject.value}.`);
}

propertyObject.range = { id: rangeAssignedURI.value };

const rangeVocabularyLabel: RDF.Literal | undefined = getVocabularyLabel(range, this.store, this.configuration.language);
const rangeApplicationProfileLabel: RDF.Literal | undefined = getApplicationProfileLabel(range, this.store, this.configuration.language);
rangeVocabularyLabel && (propertyObject.range.vocabularyLabel = { [this.configuration.language]: rangeVocabularyLabel.value });
rangeApplicationProfileLabel && (propertyObject.range.applicationProfileLabel = { [this.configuration.language]: rangeApplicationProfileLabel.value });

getMinCount(subject, this.store) && (propertyObject.minCount = getMinCount(subject, this.store));
getMaxCount(subject, this.store) && (propertyObject.maxCount = getMaxCount(subject, this.store));
getCodelist(subject, this.store) && (propertyObject.codelist = getCodelist(subject, this.store));

return propertyObject;
}

private createParentObject(subject: RDF.NamedNode): Pick<WebuniversumObject, 'id' | 'vocabularyLabel' | 'applicationProfileLabel'> {
const parentAssignedURI: RDF.NamedNode | undefined = this.store.getAssignedUri(subject);

if (!parentAssignedURI) {
throw new Error(`Unable to find the assigned URI for class ${subject.value} which acts as a parent.`);
}

const parentVocabularyLabel: RDF.Literal | undefined = getVocabularyLabel(subject, this.store, this.configuration.language);
const parentApplicationProfileLabel: RDF.Literal | undefined = getApplicationProfileLabel(subject, this.store, this.configuration.language);

return {
id: parentAssignedURI.value,
...parentVocabularyLabel && {
vocabularyLabel: { [this.configuration.language]: parentVocabularyLabel.value },
},
...parentApplicationProfileLabel && {
applicationProfileLabel: { [this.configuration.language]: parentApplicationProfileLabel.value },
},
}
}
}
Loading

0 comments on commit a427b1b

Please sign in to comment.