From e88207d1d325ce252ff57352f2bf06192369815a Mon Sep 17 00:00:00 2001 From: Ege Korkan Date: Mon, 14 Oct 2024 23:08:18 +0200 Subject: [PATCH 1/5] fix: exclude dist from targets to avoid overwrite warning --- node/aas-aid/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/aas-aid/tsconfig.json b/node/aas-aid/tsconfig.json index 4beac51..e8c244f 100644 --- a/node/aas-aid/tsconfig.json +++ b/node/aas-aid/tsconfig.json @@ -23,5 +23,5 @@ "strictNullChecks": true }, "include": ["*.ts", "**/*.ts", "../src/**/*.ts", "**/AIDSchema.json"], - "exclude": [] + "exclude": ["./dist/**/*"] } From 6e909fd9b92a6a28be59ee7cf3176562783bbe86 Mon Sep 17 00:00:00 2001 From: Ege Korkan Date: Mon, 14 Oct 2024 23:09:13 +0200 Subject: [PATCH 2/5] add handling of keys that contain a colon --- .../src/asset-interfaces-description.ts | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/node/aas-aid/src/asset-interfaces-description.ts b/node/aas-aid/src/asset-interfaces-description.ts index 4f7e116..bd489f2 100644 --- a/node/aas-aid/src/asset-interfaces-description.ts +++ b/node/aas-aid/src/asset-interfaces-description.ts @@ -1213,12 +1213,15 @@ export class AssetInterfacesDescription { for (const propertyKey in td.properties) { const property: PropertyElement = td.properties[propertyKey]; + // we get all the property keys and remove known keys by the TD spec at each if clause + let propertyKeys: any = Object.keys(property); // check whether form exists for a given protocol (prefix) const formElementPicked = this.getFormForProtocol(property, protocol); if (formElementPicked === undefined) { // do not add this property, since there will be no href of interest continue; } + propertyKeys.splice(propertyKeys.indexOf("forms"), 1); const propertyValues: Array = []; // type @@ -1230,6 +1233,7 @@ export class AssetInterfacesDescription { value: property.type, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("type"), 1); // special AID treatment if (property.minimum != null || property.maximum != null) { const minMax: { [k: string]: unknown } = { @@ -1246,12 +1250,14 @@ export class AssetInterfacesDescription { (minMax.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minimum") ); + propertyKeys.splice(propertyKeys.indexOf("minimum"), 1); } if (property.maximum != null) { minMax.max = property.maximum.toString(); (minMax.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maximum") ); + propertyKeys.splice(propertyKeys.indexOf("maximum"), 1); } propertyValues.push(minMax); } @@ -1270,12 +1276,14 @@ export class AssetInterfacesDescription { (itemsRange.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minItems") ); + propertyKeys.splice(propertyKeys.indexOf("minItems"), 1); } if (property.maxItems != null) { itemsRange.max = property.maxItems.toString(); (itemsRange.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maxItems") ); + propertyKeys.splice(propertyKeys.indexOf("maxItems"), 1); } propertyValues.push(itemsRange); } @@ -1294,12 +1302,14 @@ export class AssetInterfacesDescription { (lengthRange.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minLength") ); + propertyKeys.splice(propertyKeys.indexOf("minLength"), 1); } if (property.maxLength != null) { lengthRange.max = property.maxLength.toString(); (lengthRange.supplementalSemanticIds as Array).push( this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maxLength") ); + propertyKeys.splice(propertyKeys.indexOf("maxLength"), 1); } propertyValues.push(lengthRange); } @@ -1313,10 +1323,12 @@ export class AssetInterfacesDescription { value: property.title, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("title"), 1); } // description if (property.description != null) { // AID deals with description in level above + propertyKeys.splice(propertyKeys.indexOf("description"), 1); } // observable (if it deviates from the default == false only) if (property.observable != null && property.observable === true) { @@ -1327,6 +1339,7 @@ export class AssetInterfacesDescription { value: `${property.observable}`, // in AID represented as string modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("observable"), 1); } // contentMediaType if (property.contentMediaType != null) { @@ -1339,6 +1352,7 @@ export class AssetInterfacesDescription { value: property.contentMediaType, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("contentMediaType"), 1); } // TODO enum // const @@ -1349,6 +1363,7 @@ export class AssetInterfacesDescription { value: property.const, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("const"), 1); } // default if (property.default != null) { @@ -1359,19 +1374,30 @@ export class AssetInterfacesDescription { value: property.default, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("default"), 1); } // unit if (property.unit != null) { + // TODO: handle if value has : in it propertyValues.push({ idShort: "unit", valueType: "xs:string", value: property.unit, modelType: "Property", }); + propertyKeys.splice(propertyKeys.indexOf("unit"), 1); } // TODO items - // readOnly and writeOnly marked as EXTERNAL in AID spec + if (property.readOnly != null) { + propertyKeys.splice(propertyKeys.indexOf("readOnly"), 1); + // TODO: readOnly and writeOnly marked as EXTERNAL in AID spec + } + + if (property.writeOnly != null) { + propertyKeys.splice(propertyKeys.indexOf("writeOnly"), 1); + } + // range and others? Simply add them as is? // forms @@ -1498,6 +1524,56 @@ export class AssetInterfacesDescription { }); } + // Handling of any key that has : in it by creating semantic ids for each + // no need to bother if there is no object in the context or it is a string + const tdContext = td["@context"]; + let prefixObject: any = {}; + + if (Array.isArray(tdContext)) { + if (tdContext.length > 3) { + // if it is longer than 3, assume 3rd item is the object. This offset is needed as node-wot always adds @language at the end + // this is the case with 1.0 and 1.1 context together and then an object + prefixObject = tdContext[2]; + } else if (tdContext.length > 2) { + // this is the case only with 1.1 context and object + prefixObject = tdContext[1]; + } else { + // then it is an array with only a string + continue; + } + } else { + continue; + } + + // iterate through the remaining property keys to add extended semantic ids + propertyKeys.forEach((element: string) => { + if (element.includes(":")) { + // find the prefix + const colonLoc = element.indexOf(":"); + const prefix = element.slice(0, colonLoc); + + // check if the prefix is in the @context + if (prefixObject.hasOwnProperty(prefix)) { + const baseUri = prefixObject[prefix]; + + const semanticId = baseUri + element.slice(colonLoc + 1); + + propertyValues.push({ + idShort: element, + semanticId: this.createSemanticId(semanticId), + valueType: this.getSimpleValueTypeXsd(property.element), + value: property[element], + modelType: "Property", + }); + } else { + // TODO: What to do for unknown keys by the TD spec, have a : but not present in the @context? + // Is this a JSON-LD bug? No error in JSON-LD playground + } + } else { + // TODO: What to do for unknown keys by the TD spec and do not have a :, thus not in the context with a prefix. Should we do full json-ld processing? + } + }); + properties.push({ idShort: propertyKey, description, From e557ca1b44b0ad77ca8facc7db7a265a472ad3dd Mon Sep 17 00:00:00 2001 From: Ege Korkan Date: Tue, 15 Oct 2024 10:57:49 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: danielpeintner --- node/aas-aid/src/asset-interfaces-description.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/aas-aid/src/asset-interfaces-description.ts b/node/aas-aid/src/asset-interfaces-description.ts index bd489f2..8a642b2 100644 --- a/node/aas-aid/src/asset-interfaces-description.ts +++ b/node/aas-aid/src/asset-interfaces-description.ts @@ -1391,11 +1391,12 @@ export class AssetInterfacesDescription { if (property.readOnly != null) { propertyKeys.splice(propertyKeys.indexOf("readOnly"), 1); - // TODO: readOnly and writeOnly marked as EXTERNAL in AID spec + // TODO: readOnly marked as EXTERNAL in AID spec } if (property.writeOnly != null) { propertyKeys.splice(propertyKeys.indexOf("writeOnly"), 1); + // TODO: writeOnly marked as EXTERNAL in AID spec } // range and others? Simply add them as is? From a753e0e5262cae818bceba1651d82be48a148b02 Mon Sep 17 00:00:00 2001 From: Ege Korkan Date: Tue, 5 Nov 2024 23:54:12 +0100 Subject: [PATCH 4/5] fix: remove continue statements and sanitize idshort --- .../src/asset-interfaces-description.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/node/aas-aid/src/asset-interfaces-description.ts b/node/aas-aid/src/asset-interfaces-description.ts index 8a642b2..e8eb171 100644 --- a/node/aas-aid/src/asset-interfaces-description.ts +++ b/node/aas-aid/src/asset-interfaces-description.ts @@ -1531,19 +1531,22 @@ export class AssetInterfacesDescription { let prefixObject: any = {}; if (Array.isArray(tdContext)) { - if (tdContext.length > 3) { - // if it is longer than 3, assume 3rd item is the object. This offset is needed as node-wot always adds @language at the end - // this is the case with 1.0 and 1.1 context together and then an object - prefixObject = tdContext[2]; - } else if (tdContext.length > 2) { - // this is the case only with 1.1 context and object - prefixObject = tdContext[1]; - } else { - // then it is an array with only a string - continue; - } + // iterate through the array and add key-value pairs of each object to a single one + tdContext.forEach((arrayItem) => { + if (typeof arrayItem === "object") { + // insert every pair into the prefixObject + for (const key in arrayItem) { + prefixObject[key] = arrayItem[key as keyof typeof arrayItem]; + } + } else if (typeof arrayItem === "string") { + // it is either the standard context or another non-prefix term. + // in second case, it is not useful without full rdf processing + } else { + //probably a json-ld error? + } + }); } else { - continue; + // then it is a simple string } // iterate through the remaining property keys to add extended semantic ids @@ -1560,7 +1563,7 @@ export class AssetInterfacesDescription { const semanticId = baseUri + element.slice(colonLoc + 1); propertyValues.push({ - idShort: element, + idShort: this.sanitizeIdShort(element), semanticId: this.createSemanticId(semanticId), valueType: this.getSimpleValueTypeXsd(property.element), value: property[element], From 73705560d964bb67bdd8cb9ceb3edf8ba09c0e28 Mon Sep 17 00:00:00 2001 From: Ege Korkan Date: Wed, 6 Nov 2024 21:35:20 +0100 Subject: [PATCH 5/5] test: add tests for semantics conversion --- .../test/asset-interfaces-description-test.ts | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/node/aas-aid/test/asset-interfaces-description-test.ts b/node/aas-aid/test/asset-interfaces-description-test.ts index d8ebbe9..435cb9c 100644 --- a/node/aas-aid/test/asset-interfaces-description-test.ts +++ b/node/aas-aid/test/asset-interfaces-description-test.ts @@ -1327,4 +1327,90 @@ class AssetInterfaceDescriptionTest { expect(isValid.valid, isValid.errors).to.equal(true); expect(submodel).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf(1); } + + td4: ThingDescription = { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + myPrefix1: "https://example.com/myContext1", + myPrefix2: "https://example.com/myContext2", + }, + ], + title: "test thing", + "@type": ["myPrefix1:suffix1", "myPrefix2:suffix2"], + securityDefinitions: { + nosec_sc: { + scheme: "nosec", + }, + }, + base: "http://example.com:3003", + security: ["nosec_sc"], + "myPrefix2:suffix3": "value1", + properties: { + myProperty1: { + type: "number", + title: "Property Title", + "myPrefix1:suffix4": "myPrefix2:value4", + unit: "myPrefix2:value2", + forms: [ + { + href: "relative/value", + contentType: "application/json", + "htv:methodName": "GET", + }, + ], + }, + }, + }; + + @test async "should correctly include semantic annotations into AAS from TD"() { + const sm = this.assetInterfacesDescription.transformTD2AID(JSON.stringify(this.td4)); + + const smObj = JSON.parse(sm); + console.log("###\n\n" + JSON.stringify(smObj) + "\n\n###"); + // const isValid = this.validateAID(smObj); + // expect(isValid.valid, isValid.errors).to.equal(true); + expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); + const smInterface = smObj.submodelElements[0]; + + // InteractionMetadata with properties etc + let hasInteractionMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "InteractionMetadata") { + hasInteractionMetadata = true; + expect(smValue).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); + let hasProperties = false; + for (const interactionValues of smValue.value) { + if (interactionValues.idShort === "properties") { + hasProperties = true; + expect(interactionValues) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasProperty1 = false; + for (const propertyValue of interactionValues.value) { + if (propertyValue.idShort === "myProperty1") { + hasProperty1 = true; + expect(propertyValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasMyPrefix1_suffix4 = false; + for (const propProperty of propertyValue.value) { + if (propProperty.idShort === "myPrefix1_suffix4") { + hasMyPrefix1_suffix4 = true; + expect(propProperty.value).to.equal("myPrefix2:value4"); + } + } + expect(hasMyPrefix1_suffix4).to.equal(true); + } + } + expect(hasProperty1).to.equal(true); + } + } + expect(hasProperties).to.equal(true); + } + } + expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true); + } }