diff --git a/semantic-model/datamodel/README.md b/semantic-model/datamodel/README.md index 3133a84b..4933e000 100644 --- a/semantic-model/datamodel/README.md +++ b/semantic-model/datamodel/README.md @@ -294,7 +294,7 @@ In order to validate it with a JSON-Schema, first the base object must be descri } ``` This specifies the mandatory `id` and `type` field of every NGSI-LD object. `id` must be an *URN* and `type` must contain a *compacted* form. -The "$schema" field must be "https://json-schema.org/draft/2020-12/schema", the $id of the schema must be a valid URL with the additional constraint that all '#' fields **must be URL-Encoded**. An example for a schema and related data can be seen in the following: +The `$schema` field must be https://json-schema.org/draft/2020-12/schema, the `$id` of the schema must be a valid URL with the additional constraint that all '#' fields **must be URL-Encoded**. An example for a schema and related data can be seen in the following: ```json # JSON-Schema: @@ -445,7 +445,8 @@ The following JSON-Schema Keywords from the standard are forbidded: ## Added JSON-Schmea Keywords: The following JSON-Schema Keywords are added to the standard: -* **relationship:** Contains the *compacted* type for a NGSI-LD relationship. +* **relationship:** Contains the *compacted* type for a NGSI-LD relationship. +* **relationship_type** is further detailing the relationship type. There are two values allowed: `subcomponent` or `peer` ## Integrating ECLASS Properties `ECLASS` provides additional data for every `IRDI` which can be added/mapped to `JSON-Schema`: @@ -584,6 +585,55 @@ node tools/jsonschema2shacl.js -s examples/plasmacutter_schema.json -i https:// ``` +### Create OWL Entities descriptions + +The `entities.ttl` description contains all non validation relevant information about entities. It defines **domain** and **range** of attributes and more details, e.g. whether a NGSI-LD Relationship is a subcomponent or a peer relationship. The description is created in [OWL](https://www.w3.org/OWL/). + +#### Install + +``` +npm install +``` + +#### Usage + +``` +jsonschema2owl.js + +Converting an IFF Schema file for NGSI-LD objects into an OWL entity file. + +Options: + --version Show version number [boolean] + -s, --schema Schema File containing array of Schemas [string] [required] + -i, --schemaid Schma-id of object to generate SHACL for [string] [required] + -c, --context JSON-LD-Context [string] [required] + -n, --namespace default namespace (if not derived by context) [string] + -h, --help Show help [boolean] +``` + +#### Examples +``` +node ./tools/jsonschema2owl.js -s ./examples/filter_and_cartridge_subcomponent_schema.json -c file:$PWD/./examples/context.jsonld -i https://industry-fusion.org/eclass#0173-1#01-ACK991#016 +``` +``` +@prefix owl: . +@prefix iffb: . +@prefix rdfs: . +@prefix ngsi-ld: . +@prefix b: . + +iffb:hasCartridge + a owl:Property, b:SubComponentRelationship; + rdfs:domain ; + rdfs:range ngsi-ld:Relationship. +iffb:machine_state + a owl:Property; + rdfs:domain ; + rdfs:range ngsi-ld:Property. + a owl:Class. + +``` + ### Convert NGSI-LD forms #### Install diff --git a/semantic-model/datamodel/examples/context.jsonld b/semantic-model/datamodel/examples/context.jsonld index 6d7e122e..b21818d9 100644 --- a/semantic-model/datamodel/examples/context.jsonld +++ b/semantic-model/datamodel/examples/context.jsonld @@ -33,6 +33,10 @@ "sh": { "@id": "http://www.w3.org/ns/shacl#", "@prefix": true + }, + "base": { + "@id": "https://industryfusion.github.io/contexts/ontology/v0/base/", + "@prefix": true } } ] diff --git a/semantic-model/datamodel/tools/lib/owlUtils.js b/semantic-model/datamodel/tools/lib/owlUtils.js index d108acfd..0f90a7de 100644 --- a/semantic-model/datamodel/tools/lib/owlUtils.js +++ b/semantic-model/datamodel/tools/lib/owlUtils.js @@ -21,6 +21,7 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'); const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#'); const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/'); +const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/'); const globalAttributes = []; const globalEntities = []; @@ -40,6 +41,7 @@ class Attribute { this._entities = []; this._attributeName = attributeName; this._isProperty = true; + this._isSubcomponent = false; } addEntity (entity) { @@ -61,6 +63,14 @@ class Attribute { get entities () { return this._entities; } + + get isSubcomponent () { + return this._isSubcomponent; + } + + set isSubcomponent (isSc) { + this._isSubcomponent = isSc; + } } function dumpAttribute (attribute, entity, store) { @@ -71,6 +81,11 @@ function dumpAttribute (attribute, entity, store) { store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Property')); } else { store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Relationship')); + if (attribute.isSubcomponent) { + store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('SubComponentRelationship')); + } else { + store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('PeerRelationship')); + } } } } @@ -103,6 +118,10 @@ function scanProperties (entity, typeschema, contextManager) { if ('relationship' in typeschema.properties[property]) { isProperty = false; } + let isSubcomponent = false; + if ('relationship_type' in typeschema.properties[property] && typeschema.properties[property].relationship_type === 'subcomponent') { + isSubcomponent = true; + } let path = property; if (!contextManager.isValidIri(path)) { path = contextManager.expandTerm(path); @@ -111,6 +130,7 @@ function scanProperties (entity, typeschema, contextManager) { const attribute = new Attribute(path); attribute.addEntity(entity); attribute.isProperty = isProperty; + attribute.isSubcomponent = isSubcomponent; globalAttributes.push(attribute); }); } diff --git a/semantic-model/datamodel/tools/tests/schema2owl/c0 b/semantic-model/datamodel/tools/tests/schema2owl/c0 index 6d7e122e..b21818d9 100644 --- a/semantic-model/datamodel/tools/tests/schema2owl/c0 +++ b/semantic-model/datamodel/tools/tests/schema2owl/c0 @@ -33,6 +33,10 @@ "sh": { "@id": "http://www.w3.org/ns/shacl#", "@prefix": true + }, + "base": { + "@id": "https://industryfusion.github.io/contexts/ontology/v0/base/", + "@prefix": true } } ] diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result index eabb3a7d..db62cd49 100644 --- a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result @@ -1,10 +1,11 @@ @prefix owl: . @prefix iffb: . @prefix rdfs: . +@prefix base: . @prefix ngsi-ld: . iffb:hasFilter - a owl:Property; + a owl:Property, base:PeerRelationship; rdfs:domain ; rdfs:range ngsi-ld:Relationship. iffb:machine_state diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json new file mode 100644 index 00000000..d4df1dd4 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json @@ -0,0 +1,125 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/eclass%230173-1%2301-ACK991%23016", + "title": "Filter", + "description": "Schweissrauchabsauger", + "type": "object", + "properties": { + "type": { + "const": "eclass:0173-1#01-ACK991#016" + }, + "id": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{1,31}:([a-zA-Z0-9()+,.:=@;$_!*'-]|%[0-9a-fA-F]{2})*$" + } + }, + "required": ["type", "id"], + "allOf": [ + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/machine/properties" + }, + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/filter/relationships" + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/filter/relationships", + "title": "IFF template for filter relationship", + "description": "Cutter template for IFF", + "type": "object", + "properties": { + "hasCartridge": { + "relationship": "eclass:0173-1#01-AKE795#017", + "relationship_type": "subcomponent", + "$ref": "https://industry-fusion.org/base-objects/v0.1/link" + } + } + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/machine/properties", + "title": "Cutter properties", + "description": "Properties for class cutter", + "type": "object", + "properties": { + "machine_state": { + "type": "string", + "title": "Machine Status", + "description": "Current status of the machine (Online_Idle, Run, Online_Error, Online_Maintenance, Setup, Testing)", + "enum": [ + "Online_Idle", + "Run", + "Online_Error", + "Online_Maintenance", + "Setup", + "Testing" + ] + } + }, + "required": [ + "machine_state" + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/eclass%230173-1%2301-AKE795%23017", + "title": "Filterpatrone", + "description": "Filterpatrone", + "type": "object", + "properties": { + "type": { + "const": "eclass:0173-1#01-AKE795#017" + }, + "id": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{1,31}:([a-zA-Z0-9()+,.:=@;$_!*'-]|%[0-9a-fA-F]{2})*$" + } + }, + "required": ["type", "id"], + "allOf": [ + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/cartridge/properties" + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/cartridge/properties", + "title": "Cartridge properties", + "description": "Properties for class cutter", + "type": "object", + "properties": { + "waste_class": { + "type": "string", + "title": "Waste Class", + "description": "Current wasteclass of the cartridge (WC0, WC1, WC2, WC3)", + "enum": [ + "WC0", + "WC1", + "WC2", + "WC3" + ] + } + }, + "required": [ + "waste_class" + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/link", + "title": "IFF template for cutter relationship", + "description": "Cutter template for IFF", + "type": "object", + "properties": { + "object": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{0,31}:[a-zA-Z0-9()+,\\-.:=@;$_!*']*[a-zA-Z0-9()+,\\-.:=@;$_!*']$" + } + }, + "required": ["object"] + } +] \ No newline at end of file diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id new file mode 100644 index 00000000..a3451f15 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id @@ -0,0 +1 @@ +https://industry-fusion.org/eclass#0173-1#01-ACK991#016 diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result new file mode 100644 index 00000000..439d1a67 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result @@ -0,0 +1,17 @@ +@prefix owl: . +@prefix iffb: . +@prefix rdfs: . +@prefix base: . +@prefix ngsi-ld: . + +iffb:hasCartridge + a owl:Property, base:SubComponentRelationship; + rdfs:domain ; + rdfs:range ngsi-ld:Relationship. +iffb:machine_state + a owl:Property; + rdfs:domain ; + rdfs:range ngsi-ld:Property. + a owl:Class. + + diff --git a/semantic-model/datamodel/tools/tests/testOwlUtils.js b/semantic-model/datamodel/tools/tests/testOwlUtils.js index ea469727..20d3afd3 100644 --- a/semantic-model/datamodel/tools/tests/testOwlUtils.js +++ b/semantic-model/datamodel/tools/tests/testOwlUtils.js @@ -26,6 +26,7 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'); const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#'); const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/'); +const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/'); describe('Test class Entity', function () { it('Should create Entity with IRI', function () { @@ -75,7 +76,8 @@ describe('Test dumpAttribute', function () { const expected = [ [$rdf.namedNode(attributeName), RDF('type'), OWL('Property')], [$rdf.namedNode(attributeName), RDFS('domain'), $rdf.namedNode(entityName)], - [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')] + [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')], + [$rdf.namedNode(attributeName), RDF('type'), BASE('PeerRelationship')] ]; const store = { add: (s, p, o) => { added.push([s, p, o]); } diff --git a/semantic-model/datamodel/tools/validate.js b/semantic-model/datamodel/tools/validate.js index 56e21d1b..800846a2 100644 --- a/semantic-model/datamodel/tools/validate.js +++ b/semantic-model/datamodel/tools/validate.js @@ -25,7 +25,7 @@ const removedKeywords = [ 'valid', 'error', 'annotation', 'additionalProperties', 'propertyNames', '$vocabulary', '$defs', 'multipleOf', 'uniqueItems', 'maxContains', 'minContains', 'maxProperties', 'minPropeties', 'dependentRequired']; -const addedKeywords = ['relationship', 'datatype', 'unit']; +const addedKeywords = ['relationship', 'relationship_type', 'datatype', 'unit']; const argv = yargs .command('$0', 'Validate a JSON-LD object with IFF Schema.')